Monaco Editor系列(五)Position 类型详解、设置Marker、指定位置插入或替换文本
前情回顾: 上一篇文章主要探索了三个功能点,分别是版本对比功能、新建命令和菜单项和滚动条滚动的功能,涉及到的方法在这里稍稍的回顾一下。这样把所有的方法列出来,等到真正使用 Monaco 的时候,就比较方便了-
🪡 创建版本对比的编辑器
monaco.editor.createDiffEditor(容器, 配置)
、 -
🪡 给
diffEditor
设置内容editor.setModel({original:旧版本model, modified:新版本model})
-
🪡 新增命令
editor.addCommand(按键id, 配置)
-
🪡 新增菜单项
editor.addAction(配置)
-
🪡 滚动条滚动
editor.revealPositionInCenter(位置信息, 滚动类型);
类似的方法有很多,就不一一列举了
这一篇文章继续来学习啦!!加油哇💪🏻💪🏻💪🏻💪🏻💪🏻💪🏻早日啃完这块硬骨头哇!!
一、Position 类型详解
在编辑器中,单词的位置、光标的位置都非常的重要,创建 Overlay 组件,也就是输入过程中复现出来的这种组件以及其他重要的功能都需要依赖于位置信息
在 Monaco 中,提供位置信息的有两个类,一个是 IPosition
,一个是Position
。之前我们用过的滚动方法 revealPositionInCenter()
,第一个参数就是 IPosition
类型。我们先来看一下 IPosition
的定义,很简单,就两个属性,一个行数一个列数
export interface IPosition {
readonly lineNumber: number;
readonly column: number;
}
获取光标位置的方法是 editor.getPosition()
。下面的小示例,在鼠标点击的时候,显示光标的位置
require(['vs/editor/editor.main'], function () {
const container = document.getElementById('container')
var editor = monaco.editor.create(container);
const model = editor.getModel();
monaco.editor.setModelLanguage(model, 'javascript')
model.setValue(`// jQuery List DragSort v0.5.2
// Website: http://dragsort.codeplex.com/
// License: http://dragsort.codeplex.com/license
$.fn.size = function () {
return this.length;
}`)
// 2024年4月19日 14:02
// 显示位置
container.addEventListener('click', e=>{
const position = editor.getPosition();
console.log(position)
const dom = document.createElement('p')
dom.innerText = `位置:${position}`
document.querySelector('body').appendChild(dom)
})
});
需要注意的是,lineNumber
、column
都是从 1 开始的,最后一下我是在左上角点击的,出来的时 (1, 1)
editor.getPosition()
返回的数据打印在控制台长这样:
但是为什么到 html
里面直接显示 (1,1)
这种形式呢?
我们来看一下 Position
的定义吧~
export class Position {
/**
* line number (starts at 1) 行号 从1开始
*/
readonly lineNumber: number;
/**
* column (the first character in a line is between column 1 and column 2)
* 列号 一行中的第一个元素的列号在 1和2 之间
*/
readonly column: number;
constructor(lineNumber: number, column: number);
/**
* 创建一个 Position
*/
with(newLineNumber?: number, newColumn?: number): Position;
/**
* Derive a new position from this position.
*
* @param deltaLineNumber line number delta
* @param deltaColumn column delta
*/
delta(deltaLineNumber?: number, deltaColumn?: number): Position;
/**
* 检查当前位置和另一个位置是否是同一个位置
*/
equals(other: IPosition): boolean;
/**
* 检查两个位置是否一样
*/
static equals(a: IPosition | null, b: IPosition | null): boolean;
/**
* 检查当前位置是否在 other 之前
* 如果两个位置一样,返回false
*/
isBefore(other: IPosition): boolean;
/**
* 检查位置 a 是否在 b 之前
* 如果两个位置一样,返回false
*/
static isBefore(a: IPosition, b: IPosition): boolean;
/**
* 当前位置比 other 前或者一样都是true
*/
isBeforeOrEqual(other: IPosition): boolean;
/**
* a 比 b 前或者一样都是true
*/
static isBeforeOrEqual(a: IPosition, b: IPosition): boolean;
/**
* 对比两个 位置,用于排序
*/
static compare(a: IPosition, b: IPosition): number;
/**
* 克隆
*/
clone(): Position;
/**
* 转义成易读格式
*/
toString(): string;
/**
* 将 Position 转换成 IPosition
*/
static lift(pos: IPosition): Position;
/**
* 检测对象是不是 IPosition 类型
*/
static isIPosition(obj: any): obj is IPosition;
toJSON(): IPosition;
}
上面有一个 toString()
方法,合理怀疑,访问 Position 的时候执行了这个方法,所以在 html 里面,直接会把坐标显示出来
打了个断点,发现果不其然啊哈哈哈。怎么可能逃得过我的火眼金睛
在项目中,代码在这里 node_modules/monaco-editor-core/dev/vs/editor/editor.main.js
IPosition
是 Position
的一部分,在 Monaco 中,有很多类似的定义,一个基类中功能非常的全乎,还有一个接口,只包含关键数据,比如
IPosition
与 Position
IRange
与 Range
ISelection
与Selection
上面的三对,两两之间都是可以相互转化的,例如 Position
的 with
方法,可以将 IPosition
转化为 Position
位置相关的还有一个很有用的方法,就是 model.getWordAtPosition(IPosition)
,获取某个位置上的单词,这个方法返回的数据类型:
export interface IWordAtPosition {
/**
* The word.
*/
readonly word: string;
/**
* The column where the word starts.
*/
readonly startColumn: number;
/**
* The column where the word ends.
*/
readonly endColumn: number;
}
container.addEventListener('click', e=>{
const position = editor.getPosition();
const dom = document.createElement('p')
dom.innerText = `位置:${position}
${model.getWordAtPosition({
lineNumber: position.lineNumber,
column: position.column
}).word
}`
document.querySelector('body').appendChild(dom)
})
显示效果
二、Marker 提示
在编辑器中,如果我们代码写的有问题,代码下面会有波浪线,有的时候是灰色的警告,有的时候是红色的错误,鼠标移动上去会有提示
或者有的时候把鼠标放在类名上会有类的信息的提示。
这个功能就是 Marker 实现的,我们可以用 Marker 实现自定义提示或者注释
官网实例地址:markers-example
鼠标浮动上去,会展现一层提示,提示里面可以放置按钮,点击按钮还可以展现一层详细的信息
点击
markers 相关的方法有:
monaco.editor.setModelMarkers(model, owner字符串, markers)
设置 model
的 markers
。其中的 owner
就类似于 id
,可以用来获取 markers
monaco.editor.getModelMarkers(filter: { owner?: string; resource?: Uri; take?: number; })
获取 model
的 markers
,可以通过 owner
、resource
、take
属性过滤出符合条件的 markers
。
resource
是相关资源链接,在 marker
对象中有这个属性
monaco.editor.removeAllMarkers()
删除 model
的 所有 marker
monaco.editor.onDidChangeMarkers(监听回调)
监听 marker
修改的监听器
设置 markers
的时候,需要传进去一个 markers
对象数组,类型是
export function setModelMarkers(model: ITextModel, owner: string, markers: IMarkerData[]): void;
那么来看看 IMarkerData
类型都有哪些属性吧
node_modules/monaco-editor-core/monaco.d.ts
export interface IMarker {
code?: string | {
value: string;
target: Uri;
}; // 相关代码 参数定义或者函数定义等
severity: MarkerSeverity; // 信息类型;可选值:Hint(提示 灰色)、Info(蓝色)、Warning(橙色)、Error(红色)
message: string; // 主要信息,会直接显示在marker上
source?: string; // 代码的来源,与code搭配使用,表示该分词来自哪个文件哪个对象
startLineNumber: number; // 开始行
startColumn: number; // 开始列
endLineNumber: number; // 结束行
endColumn: number; // 结束列
modelVersionId?: number; // model版本id
relatedInformation?: IRelatedInformation[]; // 相关信息
tags?: MarkerTag[]; // 标签,可选值:Unnecessary、Deprecated
}
然后我们还得看一下其中的 relatedInformation
类型的定义,这个类型主要是描述相关的代码的信息,比如类的原型等
export interface IRelatedInformation {
resource: Uri; // 点击跳转的链接
message: string; // 显示的文本
startLineNumber: number; // 相关代码的位置
startColumn: number;
endLineNumber: number;
endColumn: number;
}
根据上面的属性我们可以知道,marker
的设置是依赖于代码的位置的。所以要想给一个单词精确的设置 marker
,需要获取单词的 Position
信息。
下面我们通过代码,给固定的单词设置 marker
。暂定为 options
吧,检测所有的 options
,给它们加上 marker
那么要找某个一单词,其实就是实现 ctrl + f
的功能了,涉及到另一个知识点咯
findMatches
这个方法的传参就有一丢丢复杂了,接收 7 个参数!
searchString
:string
搜索的关键词,如果是正在表达式,需要将 isRegex 设置为truesearchOnlyEditableRange
:boolean
搜索区域是否为当前编辑器的当前modelisRegex
:boolean
是否是正则表达式matchCase
:boolean
是否大小写敏感wordSeparators
:string
强制匹配整个单词,当为 null 时表示搜索一个完整单词。captureMatches
:boolean
结果将包含在搜索群组内limitResultCount?
:number
限制结果数量
那么此时我们先来使用这个方法搜索一下所有的 options
。当然前提是 setValue()
设置内容的时候,里面有 options
因为我用的是 jQuery List DragSort
这个库的源码,大家可以随便用其他的存在的单词来搜索
function findWordMatches(word) {
return model.findMatches(
word,
true, // 搜索区域是否是当前model内部
false, // 是否是正则表达式
false, // 是否大小写敏感
null, // 强制匹配整个单词,当为 null 时表示搜索一个完整单词
true // 结果将包含在搜索群组内
)
}
const optionsMatches = findWordMatches('options')
console.log("---optionsMatches")
console.log(optionsMatches)
查看一下输出结果吧:
可以看到输出的 match
元素里面就有位置信息,这是我们在设置 marker
的时候需要用到的。然后根据位置信息,设置 marker
吧!
const markers = optionsMatches.map(match=>{
const marker = markerCreator(match)
return marker
})
// markers
function markerCreator({matches, range}) {
const uri = monaco.Uri.parse('https://www.baidu.com')
const {
startLineNumber,
startColumn,
endLineNumber,
endColumn
} = {...range}
return {
source: '我是source属性',
severity: 'Error', // 信息类型;可选值:Hint(提示 灰色)、Info(蓝色)、Warning(橙色)、Error(红色)
code: {
value: "我是code属性",
target: uri,
}, // 相关代码 参数定义或者函数定义等
message: `this is the word: ${matches}`, // 主要信息,会直接显示在marker上
source: '我是source', // 代码的来源,与code搭配使用,表示该分词来自哪个文件哪个对象
startLineNumber, // 开始行
startColumn, // 开始列
endLineNumber, // 结束行
endColumn, // 结束列
relatedInformation: [{
owner: 'Ymj',
startLineNumber,
startColumn,
endLineNumber,
endColumn,
message: '我是relatedInformation的message',
resource: uri
}], // 相关信息
tags: [monaco.MarkerTag.Unnecessary] // 标签,可选值:Unnecessary(代码会变成灰色)、Deprecated(会在代码上画横线)
}
}
monaco.editor.setModelMarkers(model, 'owner', markers)
显示效果:
三、指定区域插入或者替换文本
在开发时,有时候会需要在头部添加版权信息等文本内容。这一章来实现如何在指定位置添加或修改内容。
使用到的关键方法是 model.applyEdits()
通过重写定义了实现了多种参数的传递
第一个参数类型 IIdentifiedSingleEditOperation
继承了 ISingleEditOperation
export interface ISingleEditOperation {
range: IRange; // 要在哪个范围进行替换
text: string | null; // 替换后的文本
forceMoveMarkers?: boolean; // 是否删除这个位置的 marker
}
第二个参数是 computeUndoEdits
是否把该操作放到操作的栈中,如果设置为 true,则可以通过撤销撤销操作,并且会有返回值 IValidEditOperation
下面我们就在代码开头增加一丢丢文本
function applyStartMessage() {
const date = new Date()
const editResult = model.applyEdits([{
range: {
startColumn: 0, startLineNumber: 1, endColumn: 1, endLineNumber: 0,
},
text: date.getFullYear() + '-' + date.getMonth() + '-' + date.getDate()
}], true)
console.log(editResult)
}
applyStartMessage()
然后我们看一下 model.applyEdits()
方法的返回值
包含了修改的范围,text
是被删掉的文本,textChange
就是修改的信息,就可以通过这些个属性,恢复原来的样子
// 在指定位置修改或替换内容
function applyStartMessage() {
const date = new Date()
const editResult = model.applyEdits([{
range: {
startColumn: 0, startLineNumber: 1, endColumn: 1, endLineNumber: 0,
},
text: date.getFullYear() + '-' + date.getMonth() + '-' + date.getDate()
}], true)
setTimeout(()=>{
// 3s自动恢复
undoEdits(editResult)
}, 3000)
}
applyStartMessage()
function undoEdits(edits){
edits.forEach(e=>{
model.applyEdits([{
range: e.range,
text: e.text
}], true)
})
}
参考文章:
Monaco Editor教程(十二):使用Marker来增加分词注释,标记,优化编辑器交互体验
Monaco Editor 教程(二五):使用FindMatchs搜索结果批量增加代码注释
原文地址:https://blog.csdn.net/weixin_45855469/article/details/137925190
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!