Monaco Editor系列(八)插入自定义DOM、删除指定位置的单词、给特定单词着色

前言:人都不知道自己是谁,所以想让自己成为什么样的人,就多给自己说什么样的话。我爱学习!学习使我快乐!回顾一下上一篇文章的内容。还记得 Monaco Editor 的三个命名空间吗?分别是 editorlanguagesworkerCodeLensCompletion 自动完成配置都归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 是自己定义的一个字符串,随便写就行;第二个是 actionid
    2、先使用 editor.getAction(actionId) 获取 action 实例,然后再使用 actionrun() 方法执行对应的回调函数。

  • 内置的 actionid 可以通过打印 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 对象,但是其实重新渲染的方法只需要传 getIdgetPosition,删除的方法只需要 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'
    });
})

显示:
在这里插入图片描述

  • 12
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在使用Monaco Editor进行编程时,我们经常需要在编辑器中的指定位置插入一些代码或文本。这种操作在Monaco Editor中非常简单。 第一步是获取编辑器的实例。可以通过以下方式来获取: ```javascript var editor = monaco.editor.create(document.getElementById("container"), { value: "some text", language: "javascript" }); ``` 在这里,我们将Monaco Editor绑定到HTML页面上的一个容器元素中,并指定了编程语言为JavaScript。 接下来,我们可以通过monaco.editor.getModelAtPosition方法来获取指定位置的模型。例如,如果我们要在文件中第3行第10列的位置插入文本,可以使用如下代码: ```javascript var position = new monaco.Position(3,10); var model = editor.getModel(); model.applyEdits([{ range: new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column), text: "insert content", forceMoveMarkers: true }]); ``` 在这里,我们首先创建了一个monaco.Position对象来描述需要插入内容的位置。然后使用editor.getModel方法获取编辑器的模型,再调用model.applyEdits方法来进行插入操作。该方法接受一个编辑操作的数组,其中range属性表示需要插入的范围,text属性表示需要插入的文本内容,forceMoveMarkers属性表示是否需要移动标记。在这里,我们将范围定义为一个点,即只要在指定位置插入内容即可。 通过以上步骤,我们就可以在指定位置插入内容了。需要注意的是,这种操作也可以通过调用editor.executeEdits方法来实现,这个方法可以对多个范围进行编辑操作。此外,还需要注意避免在代码中硬编码位置信息,而是应该使用monaco.editor.getCursorPosition方法,动态获取当前光标位置

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值