前言:人都不知道自己是谁,所以想让自己成为什么样的人,就多给自己说什么样的话。我爱学习!学习使我快乐!回顾一下上一篇文章的内容。还记得 Monaco Editor 的三个命名空间吗?分别是 editor
、languages
、worker
。CodeLens
和 Completion
自动完成配置都归languages
管,因为是针对不同语言进行的配置。并且用法非常的类似,都是注册一个 Provider
,然后传参类型也类似,就是名字都好长啊🙀🙀🙀
-
编辑器中的 git 提交记录提示是用
CodeLens
实现的,使用monaco.editor.registerCodeLensProvider
注册CodeLens
。
参数一通常使用语言字符串,如javascript
参数二是一个对象,接口类型为CodeLensProvider
,通常只填一个属性provideCodeLenses
就可以
provideCodeLenses
属性是一个方法,最终要返回一个对象,对象里面有一个属性lenses
表示配置项列表
配置项列表的元素的几个关键属性:
1、range
必填 其实只有startLineNumber
有影响,因为CodeLens
一定从第一列开始显示,并且不会多行显示
2、id
非必填
3、command
点击的时候触发的命令 非必填var commandId = editor.addCommand( 0, function () { alert('CodeLens被点击啦'); }, "" ); monaco.languages.registerCodeLensProvider("javascript", { provideCodeLenses: function (model, token) { return { lenses: [ { range: { startLineNumber: 1, startColumn: 1, endLineNumber: 2, endColumn: 1, }, id: "First Line", command: { id: commandId, title: "我是第一行", }, }, ], }; }, });
-
使用API触发键盘事件有两种方式
1、editor.trigger(source, handlerId)
第一个source
是自己定义的一个字符串,随便写就行;第二个是action
的id
2、先使用editor.getAction(actionId)
获取action
实例,然后再使用action
的run()
方法执行对应的回调函数。 -
内置的
action
的id
可以通过打印editor
获取,在editor
实例的_action
属性上 -
自动补全功能使用
registerCompletionItemProvider
,用法和上面的 CodeLens 的配置及其类似,但是就是配置自动补全的数组元素的配置项有一丢丢多。function createDependencyProposals(range) { return [ { label: 'monaco', kind: monaco.languages.CompletionItemKind.Reference, // 控制图标 documentation: "定义一个不能修改的常量", // 点击右侧按钮出现在下边,详细说明 detail: '我是detail属性', // 出现在选项的最右侧,几个字的内容 insertText: 'console.log("monaco可真是太好用了")', // 实际插入的代码 range: range, // 范围 // tags: [monaco.languages.CompletionItemTag.Deprecated], // 出现划线,表示不建议 preselect: true, // 预选中 // filterText: 'falsy', // 不显示 insertTextRules: monaco.languages.CompletionItemInsertTextRule.KeepWhitespace, // 插入的规则 InsertAsSnippet:作为代码块插入;KeepWhitespace:插入后自动格式化 commitCharacters: ['a', 'b','c'], // 选中后输入这几个字符,自动插入代码和字符 // additionalTextEdits: [ // { // range: { // startLineNumber: 1, // startColumn: 1, // endLineMumber: 1, // endColumn:1 // }, // text: '// 这是一行额外加进来的代码', // forceMoveMarkers: true, // } // ], command: { id: 'editor.action.commentLine', title: '注释选中的行', } }, ]; } monaco.languages.registerCompletionItemProvider("javascript", { provideCompletionItems: function (model, position) { var word = model.getWordUntilPosition(position); var range = { startLineNumber: position.lineNumber, endLineNumber: position.lineNumber, startColumn: word.startColumn, endColumn: word.endColumn, }; return { suggestions: createDependencyProposals(range), }; }, });
这篇文章继续来学习 monaco editor更多的功能吧!
一、插入自定义DOM
在编辑器中插入DOM的使用场景还是挺常见的,例如
查找单词的时候复现出来的查找框
编辑时候浮现出来的建议列表
以及 gitlab 中,给别人的合并请求添加修改建议的小框框
可以发现上面几个框框虽然都是独立于编辑区域的,但是显示上还是有区别的,搜索的框框和建议列表框框是浮在编辑器上的,给合并请求提建议的框框是插入到上下两行的中间的,会把两行撑开,并且不会占用行号。
所以其实 monaco 的编辑器之外的DOM有两种,一种是 IContentWidget,一种是 IOverlayWidget。
(一)IOverlayWidget
是一个渲染在文本之上的浮窗组件。查找组件就是用它实现的。
1、实例属性
我们先看一下接口定义。源码中有更详细的解释
out/monaco-editor/monaco.d.ts
export interface IOverlayWidget {
// 是否允许组件溢出到编辑器外
allowEditorOverflow?: boolean;
// 设置唯一标识 字符串
getId(): string;
// 设置该组件要显示
getDomNode(): HTMLElement;
// 设置组件的显示位置 枚举值,有三个位置可选:右下角、顶部中心、右下角,如果返回 null 的话就会自适应
getPosition(): IOverlayWidgetPosition | null;
// 编辑器将会确保滚动宽度大于这个值
getMinContentWidthInPx?(): number;
}
2、相关方法
addOverlayWidget(widget: IOverlayWidget): void
新增一个IOverlayWidget
。必须提供独一无二的 id,否则会被视为更新操作layoutOverlayWidget(widget: IOverlayWidget): void
重新渲染IOverlayWidget
removeOverlayWidget(widget: IOverlayWidget): void
移除IOverlayWidget
新增:
// 1、新增 OverlayWidget
editor.addOverlayWidget({
getId: function() {
return 'monaco.editor.ymj.overlay.widget'
},
getDomNode: function() {
var domNode = document.createElement('div')
domNode.style.cssText = 'position: absolute;background-color: red;'
domNode.innerHTML = '我是一个叠加的Widget'
return domNode
},
getPosition: function() {
return {
preference: monaco.editor.OverlayWidgetPositionPreference.TOP_RIGHT_CORNER,
// preference: monaco.editor.OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER,
// preference: monaco.editor.OverlayWidgetPositionPreference.TOP_CENTER,
}
}
})
重新渲染,只能更改位置,不能更改 DOM 元素,样式、文本都不能改变
// 2、重新渲染 overlaywidget
setTimeout(function() {
editor.layoutOverlayWidget({
getId: function() {
return 'monaco.editor.ymj.overlay.widget'
},
getPosition: function() {
return {
preference: monaco.editor.OverlayWidgetPositionPreference.TOP_CENTER,
}
}
})
}, 2000)
删除,只需要指定id
// 3、移除 OverlayWidget
setTimeout(function() {
editor.removeOverlayWidget({
getId: function() {
return 'monaco.editor.ymj.overlay.widget'
}
})
}, 4000)
这三个方法的传参都是传 OverlayWidget 对象,但是其实重新渲染的方法只需要传 getId
和 getPosition
,删除的方法只需要 getId
(二)IContentWidget
是出现在编辑区域附近的内容框框,和编辑的位置密切相关。
1、实例属性
export interface IContentWidget {
// 是否允许组件溢出编辑器
allowEditorOverflow?: boolean;
// 是否阻止鼠标按下操作,如果设置为false,则用户可以点击组件
suppressMouseDown?: boolean;
// 唯一标识
getId(): string;
// 创建的 DOM 元素
getDomNode(): HTMLElement;
// 位置信息
getPosition(): IContentWidgetPosition | null;
// 渲染之前会执行的函数 可以设置组件的宽高尺寸
beforeRender?(): IDimension | null;
// 渲染完成执行的回调 设置组件的显示位置
afterRender?(position: ContentWidgetPositionPreference | null): void;
}
其中 getPosition()
的返回值比上面浮窗组件中的稍微复杂一些
export interface IContentWidgetPosition {
// 组件被放置的坐标 包括 lineNumber、column
position: IPosition | null;
// 进一步定义组件的位置,必须和position在同一行,设置组件结束的位置
secondaryPosition?: IPosition | null;
// 枚举值 EXACT 精确位置 ABOVE 放在position的上方 BELOW 放在position的下方
preference: ContentWidgetPositionPreference[];
/**
* Placement preference when multiple view positions refer to the same (model) position.
* This plays a role when injected text is involved.
*/
// 枚举值 多个视图位置指向同一个(模型)位置的时候的位置偏好 Left 靠左放 Right 靠右放 None
// LeftOfInjectedText 如果给定的位置是在注入的文本中,那么更偏好将视图放置在该文本的左侧。
// RightOfInjectedText 如果给定的位置在注入的文本中,那么将视图放置该在文本的右侧
// 注入文本时起作用
positionAffinity?: PositionAffinity;
}
根据上面的定义,我们可以创建一个 IContentWidget
const newContentWidget = {
allowEditorOverflow: true, // 允许组件溢出编辑器
suppressMouseDown: true, // 阻止鼠标事件
getId: function() {
return 'monaco.editor.ymj.content.widget'
},
getDomNode: function() {
var domNode = document.createElement('div')
domNode.style.cssText = 'position: absolute;background-color: red;width: 200px;height: 200px;'
domNode.innerHTML = '我是一个内容组件';
return domNode
},
getPosition: function() {
return {
position: {
lineNumber: 1,
column: 3
},
preference: [
monaco.editor.ContentWidgetPositionPreference.EXACT
],
}
},
beforeRender: function() {
return {
width: 200,
height: 200
}
},
afterRender: function() {
console.log('afterRender')
}
}
2、editor 上的相关方法
addContentWidget(widget: IContentWidget): void;
新增一个IContentWidget
。必须提供独一无二的 id,否则会被视为更新操作layoutContentWidget(widget:IContentWidget): void
重新渲染IContentWidget
removeContentWidget(widget: IContentWidget): void
移除IContentWidget
1、新增
editor.addContentWidget(newContentWidget)
另外,根据源码中的注释, beforeRender
返回的宽高会被应用到组件上,但是通过实践,根本没用🤡
preference = [monaco.editor.ContentWidgetPositionPreference.EXACT]
会直接覆盖文字
-
preference: [ monaco.editor.ContentWidgetPositionPreference.BELOW ],
会在指定行的下面
-
preference: [ monaco.editor.ContentWidgetPositionPreference.ABOVE ],
会在指定行的上面,如果指定的是第一行,它就溢出了,此时allowEditorOverflow
属性的配置就起作用了,在代码中我们的allowEditorOverflow: true
;
如果它是allowEditorOverflow: false
,直接就不显示了
2、重新渲染,依然是只能修改位置,不能修改 DOM元素
const updatedContentWidget = {
getId: function() {
return 'monaco.editor.ymj.content.widget'
},
getPosition: function() {
return {
position: {
lineNumber: 3,
column: 3
},
preference: [
monaco.editor.ContentWidgetPositionPreference.BELOW
],
}
}
}
setTimeout(function() {
editor.layoutContentWidget(updatedContentWidget)
}, 2000)
3、移除
setTimeout(function() {
editor.removeContentWidget(newContentWidget)
}, 4000)
二、删除指定位置的单词
指定位置,我们就直接用光标所在的位置,那么就是说,点击光标后,延时两秒删除当前光标所在位置的单词
删除文本需要用到的方法是ITextModel 实例上的方法 applyEdits,这个方法用来修改文本,接收参数:
IIdentifiedSingleEditOperation
interface IIdentifiedSingleEditOperation {
forceMoveMarkers?: boolean; // 是否删除markers
range: IRange; // 修改文本的区域
text: string; // 修改成什么文本
}
将 text
设置为空字符串,即可实现删除的效果
通过下面两个方法,可以获取指定位置的单词的范围
getWordAtPosition
获取某个位置处的完整单词,位置必须在单词的闭区间内
getWordUntilPosition
获取该位置之前的完整单词,可能会截取单词
下面通过实际示例看一下它俩的不同
首先介绍一下一个枚举值:
monaco.editor.MouseTargetType 表示鼠标在哪里点击,如果是在文本上方点击,就是monaco.editor.MouseTargetType.CONTENT_TEXT
,通过这个枚举值我们就可以控制只有鼠标落在文本上的时候再执行下面的操作
监听鼠标落下的方法:editor.onMouseDown(e=>{})
editor.onMouseDown(function(e) {
// monaco.editor.MouseTargetType.CONTENT_TEXT:光标落在文本上
if (e.target.type === monaco.editor.MouseTargetType.CONTENT_TEXT) {
const model = editor.getModel()
const position = e.target.position
const word = model.getWordAtPosition(position)
const word2 = model.getWordUntilPosition(position)
console.log(word, word2)
}
})
下面我们在一个单词的中间落下鼠标,看看这两个方法获得的结果有啥区别
可以看到,getWordAtPosition()
获取的是一整个单词,getWordUntilPosition()
获取的是单词截取到点击的位置
然后我们还需要组装一个 range
,因为上述两个方法返回的没有行号,行号还要取 position
的行号
editor.onMouseDown(function(e) {
// monaco.editor.MouseTargetType.CONTENT_TEXT:光标落在文本上
if (e.target.type === monaco.editor.MouseTargetType.CONTENT_TEXT) {
const model = editor.getModel()
const position = e.target.position
const word = model.getWordAtPosition(position)
const word2 = model.getWordUntilPosition(position)
const range = {
startLineNumber: position.lineNumber,
startColumn: word.startColumn,
endLineNumber: position.lineNumber,
endColumn: word.endColumn,
}
model.applyEdits([{
range: range,
text: ''
}])
}
})
点击编辑器中的 alert
单词
就会直接删除啦👻👻👻
三、给特定单词着色
前面我们讲过 defineTheme
方法,它可以定义主题,其中的 rules
属性可以规定给不同类型的 token
渲染不同的颜色。如果我们的项目中有一些关键词,我们想给它们一个名分,让它们显示出不一样的光彩,那么该怎么做呢?
首先我们需要给这些个特别的单词设置 token
,需要用到的方法:setMonarchTokensProvider(languageId, languageDef)
第一个参数是语言,第二个参数 languageDef
类型是 IMonarchLanguage
只有一个必填的属性
tokenizer: {
[name: string]: IMonarchLanguageRule[];
}
key
是表示匹配规则的字符串,IMonarchLanguageRule
是定义 token 匹配规则的数组,每一条匹配规则也是一个数组,匹配规则的第一个元素是匹配 token
的具体规则,第二个元素是 token
的名字。确实这么讲,有一丢丢绕哈哈,还是看具体代码吧
monaco.languages.setMonarchTokensProvider('javascript',{
tokenizer:{
root:[
['monaco', 'ymj-monaco'], // 常量
[/ymj\w+/,'ymj-tip'], // ymj 后面跟字符
[/\[[a-zA-Z 0-9:]+\]/, 'ymj-date'] // 以 [ 开头 以 ] 结尾
]
}
})
然后注册完 token
之后需要定义主题,在主题里面给这些个 token
上色
monaco.editor.defineTheme('ymj-theme',{
base: 'vs',
inherit: true,
rules: [
{ token: 'ymj-monaco', foreground: '#ad28ee', fontStyle: 'bold' },
{ token: 'ymj-tip', foreground: '#ad28ee', fontStyle: 'italic' },
{ token: 'ymj-date', foreground: '#ad28ee', fontStyle: 'underline' },
],
colors: {}
})
这里有一个需要注意的点,就是创建编辑器必须在上述两个操作的后面,必须要先注册 token
、注册 theme
,然后再创建编辑器,主题就可以直接写到编辑器的配置对象中
完整代码:
require(['vs/editor/editor.main'], function() {
const container = document.getElementById('container')
// 定义tokens并特殊着色
monaco.languages.setMonarchTokensProvider('javascript',{
tokenizer:{
root:[
['monaco', 'ymj-monaco'], // 常量
[/ymj\w+/,'ymj-tip'], // ymj 后面跟字符
[/\[[a-zA-Z 0-9:]+\]/, 'ymj-date'] // 以 [ 开头 以 ] 结尾
]
}
})
monaco.editor.defineTheme('ymj-theme',{
base: 'vs',
inherit: true,
rules: [
{ token: 'ymj-monaco', foreground: '#ad28ee', fontStyle: 'bold' }, // 加粗
{ token: 'ymj-tip', foreground: '#ad28ee', fontStyle: 'italic' }, // 斜体
{ token: 'ymj-date', foreground: '#ad28ee', fontStyle: 'underline' }, // 有下划线
],
colors: {}
})
var editor = monaco.editor.create(container, {
language: 'javascript',
value: `
console.log('ymjisbeautiful')
console,log('[hello]')
console.log('monaco is awesome!')
monaco
`,
theme: 'ymj-theme'
});
})
显示: