Monaco Editor 是一款开源的在线代码编辑器,它是 VSCode 的浏览器版本。
Monaco Editor的文档:
Monaco Editor的文档目前并不是很全,主要可以看它的palyground,如下图:
一开始看这个playground也比较懵,两个编辑器?
左边是代码加注释,这就算是文档了,右边就是实现的效果了。
还有一个示例切换的select,开始选择不同的示例:
快速上手
安装:
yarn monaco-editor@0.31.0
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js';
// editor.api.js只是基础功能,你可以还需要导入一些文件,比如javascript语法支持文件
import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution.js';
monaco.editor.create(document.getElementById('container'), {
value: "function hello() {\n\talert('Hello world!');\n}",
language: 'javascript'
});
自动补全
文档:
文档很少,可以直接看代码注释,通过typescript查看定义可以很方便的找到,vscode和monaco-editor可以结合起来看,manaco-editor中的api基本都能在vscode中找到对应的,vscode中的注释写的更详细一些,但是它们还是有一些细微的区别的。
- vscode-extension-samples
- IntelliSense (这个链接就是在代码注释中找到的)
provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken, context: CompletionContext): ProviderResult<T[] | CompletionList<T>>;
每次输入会触发provideCompletionItems
, 主要根据文档内容document
和光标位置position
得到CompletionList
。document
是纯文本内容,
如有需要,可以要自己来做语法解析得到相关信息。
CompletionItemProvider
/**
* The completion item provider interface defines the contract between extensions and
* [IntelliSense](https://code.visualstudio.com/docs/editor/intellisense).
*
* Providers can delay the computation of the {@linkcode CompletionItem.detail detail}
* and {@linkcode CompletionItem.documentation documentation} properties by implementing the
* {@linkcode CompletionItemProvider.resolveCompletionItem resolveCompletionItem}-function. However, properties that
* are needed for the initial sorting and filtering, like `sortText`, `filterText`, `insertText`, and `range`, must
* not be changed during resolve.
*
* Providers are asked for completions either explicitly by a user gesture or -depending on the configuration-
* implicitly when typing words or trigger characters.
*/
export interface CompletionItemProvider<T extends CompletionItem = CompletionItem> {
/**
* Provide completion items for the given position and document.
*
* @param document The document in which the command was invoked.
* @param position The position at which the command was invoked.
* @param token A cancellation token.
* @param context How the completion was triggered.
*
* @return An array of completions, a {@link CompletionList completion list}, or a thenable that resolves to either.
* The lack of a result can be signaled by returning `undefined`, `null`, or an empty array.
*/
provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken, context: CompletionContext): ProviderResult<T[] | CompletionList<T>>;
/**
* Given a completion item fill in more data, like {@link CompletionItem.documentation doc-comment}
* or {@link CompletionItem.detail details}.
*
* The editor will only resolve a completion item once.
*
* *Note* that this function is called when completion items are already showing in the UI or when an item has been
* selected for insertion. Because of that, no property that changes the presentation (label, sorting, filtering etc)
* or the (primary) insert behaviour ({@link CompletionItem.insertText insertText}) can be changed.
*
* This function may fill in {@link CompletionItem.additionalTextEdits additionalTextEdits}. However, that means an item might be
* inserted *before* resolving is done and in that case the editor will do a best effort to still apply those additional
* text edits.
*
* @param item A completion item currently active in the UI.
* @param token A cancellation token.
* @return The resolved completion item or a thenable that resolves to of such. It is OK to return the given
* `item`. When no result is returned, the given `item` will be used.
*/
resolveCompletionItem?(item: T, token: CancellationToken): ProviderResult<T>;
}
TextDocument
表示一个文本文档,例如源文件。TextDocument包含 {@link TextLine lines} 和 底层文件的一些信息,比如文件路径uri
。
/**
* Represents a text document, such as a source file. Text documents have
* {@link TextLine lines} and knowledge about an underlying resource like a file.
*/
export interface TextDocument {
/**
* The associated uri for this document.
*
* *Note* that most documents use the `file`-scheme, which means they are files on disk. However, **not** all documents are
* saved on disk and therefore the `scheme` must be checked before trying to access the underlying file or siblings on disk.
*
* @see {@link FileSystemProvider}
* @see {@link TextDocumentContentProvider}
*/
/**
* document的关联uri。
* 注意: 多数document使用`file`属性,这意味着它们是磁盘上的文件。然而,并不是所有的document都是保存在磁盘上的。
* 因此在尝试访问底层文件之前,必须检查这个属性。
*
*/
readonly uri: Uri;
/**
* The file system path of the associated resource. Shorthand
* notation for {@link TextDocument.uri TextDocument.uri.fsPath}. Independent of the uri scheme.
*/
/**
* 关联的文件系统路径。{@link TextDocument.uri TextDocument.uri.fsPath}的速记符。
*/
readonly fileName: string;
/**
* Is this document representing an untitled file which has never been saved yet. *Note* that
* this does not mean the document will be saved to disk, use {@linkcode Uri.scheme}
* to figure out where a document will be {@link FileSystemProvider saved}, e.g. `file`, `ftp` etc.
*/
/**
* 表示这是还没有被保存一个无标题的文件,*注意*: 这并不意味着document将被保存到磁盘,
* 使用{@linkcode Uri.scheme}来决定document将要被保存在何处,例如, `file`, `ftp`等。
*/
readonly isUntitled: boolean;
/**
* The identifier of the language associated with this document.
*/
/**
* language标识
*/
readonly languageId: string;
/**
* The version number of this document (it will strictly increase after each
* change, including undo/redo).
*/
/**
* document的版本(它将在每次更改后严格增加,包括撤消/重做)
*/
readonly version: number;
/**
* `true` if there are unpersisted changes.
*/
/**
* 如果修改了未保存则为`true`。
*/
readonly isDirty: boolean;
/**
* `true` if the document has been closed. A closed document isn't synchronized anymore
* and won't be re-used when the same resource is opened again.
*/
/**
* 如果document已经关闭,则为`true`。一个关闭的document将不再同步,
* 并且在同一资源再次打开时不会被重用。
*/
readonly isClosed: boolean;
/**
* Save the underlying file.
*
* @return A promise that will resolve to true when the file
* has been saved. If the file was not dirty or the save failed,
* will return false.
*/
/**
* 保存底层文件。
* 返回一个promise,如果promise resolve为`true`,表示文件保存成功。
* 如果为`false`,则文件未修改或者保存失败。
*/
save(): Thenable<boolean>;
/**
* The {@link EndOfLine end of line} sequence that is predominately
* used in this document.
*/
/**
* 此document使用的endOfLine。
*/
readonly eol: EndOfLine;
/**
* The number of lines in this document.
*/
/**
* 此document的行数。
*/
readonly lineCount: number;
/**
* Returns a text line denoted by the line number. Note
* that the returned object is *not* live and changes to the
* document are not reflected.
*
* @param line A line number in [0, lineCount).
* @return A {@link TextLine line}.
*/
/**
* 返回某行的text,一个TextLine对象。
* 返回的对象是*非*活动的,对document的更改不会反映出来。
*/
lineAt(line: number): TextLine;
/**
* Returns a text line denoted by the position. Note
* that the returned object is *not* live and changes to the
* document are not reflected.
*
* The position will be {@link TextDocument.validatePosition adjusted}.
*
* @see {@link TextDocument.lineAt}
*
* @param position A position.
* @return A {@link TextLine line}.
*/
lineAt(position: Position): TextLine;
/**
* Converts the position to a zero-based offset.
*
* The position will be {@link TextDocument.validatePosition adjusted}.
*
* @param position A position.
* @return A valid zero-based offset.
*/
/**
* 将position对象转换为基于零的偏移量。
*/
offsetAt(position: Position): number;
/**
* Converts a zero-based offset to a position.
*
* @param offset A zero-based offset.
* @return A valid {@link Position}.
*/
positionAt(offset: number): Position;
/**
* Get the text of this document. A substring can be retrieved by providing
* a range. The range will be {@link TextDocument.validateRange adjusted}.
*
* @param range Include only the text included by the range.
* @return The text inside the provided range or the entire text.
*/
getText(range?: Range): string;
/**
* Get a word-range at the given position. By default words are defined by
* common separators, like space, -, _, etc. In addition, per language custom
* [word definitions} can be defined. It
* is also possible to provide a custom regular expression.
*
* * *Note 1:* A custom regular expression must not match the empty string and
* if it does, it will be ignored.
* * *Note 2:* A custom regular expression will fail to match multiline strings
* and in the name of speed regular expressions should not match words with
* spaces. Use {@linkcode TextLine.text} for more complex, non-wordy, scenarios.
*
* The position will be {@link TextDocument.validatePosition adjusted}.
*
* @param position A position.
* @param regex Optional regular expression that describes what a word is.
* @return A range spanning a word, or `undefined`.
*/
/**
* 给定一个position,通常是光标的位置,得到光标所在单词的range。
* 默认单词使用空格,-,_等分隔,每个language可以自定义([word definitions})。
*/
getWordRangeAtPosition(position: Position, regex?: RegExp): Range | undefined;
/**
* Ensure a range is completely contained in this document.
*
* @param range A range.
* @return The given range or a new, adjusted range.
*/
/**
* 确保range完全包含在document之中,不会超出范围。
* 返回传入的range或经过调整的新的range。
*/
validateRange(range: Range): Range;
/**
* Ensure a position is contained in the range of this document.
*
* @param position A position.
* @return The given position or a new, adjusted position.
*/
/**
* 确保position在本文档的范围内。
*/
validatePosition(position: Position): Position;
}
Position
表示一行或者一个字符的位置,比如说光标的位置; line表示行,character表示列,索引从0开始计数,{ line: 2, character: 5 }
表示第3行第6列。
Postion 对象是不可变的,可以使用with
和translate
方法根据存在的position
生成新的position
。
/**
* Represents a line and character position, such as
* the position of the cursor.
*
* Position objects are __immutable__. Use the {@link Position.with with} or
* {@link Position.translate translate} methods to derive new positions
* from an existing position.
*/
export class Position {
/**
* The zero-based line value.
*/
readonly line: number;
/**
* The zero-based character value.
*/
readonly character: number;
/**
* @param line A zero-based line value.
* @param character A zero-based character value.
*/
constructor(line: number, character: number);
/**
* Check if this position is before `other`.
*
* @param other A position.
* @return `true` if position is on a smaller line
* or on the same line on a smaller character.
*/
isBefore(other: Position): boolean;
/**
* Check if this position is before or equal to `other`.
*
* @param other A position.
* @return `true` if position is on a smaller line
* or on the same line on a smaller or equal character.
*/
isBeforeOrEqual(other: Position): boolean;
/**
* Check if this position is after `other`.
*
* @param other A position.
* @return `true` if position is on a greater line
* or on the same line on a greater character.
*/
isAfter(other: Position): boolean;
/**
* Check if this position is after or equal to `other`.
*
* @param other A position.
* @return `true` if position is on a greater line
* or on the same line on a greater or equal character.
*/
isAfterOrEqual(other: Position): boolean;
/**
* Check if this position is equal to `other`.
*
* @param other A position.
* @return `true` if the line and character of the given position are equal to
* the line and character of this position.
*/
isEqual(other: Position): boolean;
/**
* Compare this to `other`.
*
* @param other A position.
* @return A number smaller than zero if this position is before the given position,
* a number greater than zero if this position is after the given position, or zero when
* this and the given position are equal.
*/
compareTo(other: Position): number;
/**
* Create a new position relative to this position.
*
* @param lineDelta Delta value for the line value, default is `0`.
* @param characterDelta Delta value for the character value, default is `0`.
* @return A position which line and character is the sum of the current line and
* character and the corresponding deltas.
*/
translate(lineDelta?: number, characterDelta?: number): Position;
/**
* Derived a new position relative to this position.
*
* @param change An object that describes a delta to this position.
* @return A position that reflects the given delta. Will return `this` position if the change
* is not changing anything.
*/
translate(change: { lineDelta?: number; characterDelta?: number; }): Position;
/**
* Create a new position derived from this position.
*
* @param line Value that should be used as line value, default is the {@link Position.line existing value}
* @param character Value that should be used as character value, default is the {@link Position.character existing value}
* @return A position where line and character are replaced by the given values.
*/
with(line?: number, character?: number): Position;
/**
* Derived a new position from this position.
*
* @param change An object that describes a change to this position.
* @return A position that reflects the given change. Will return `this` position if the change
* is not changing anything.
*/
with(change: { line?: number; character?: number; }): Position;
}
Range
通过两个position指定的一个范围。{ start, end }
。
TextLine
表示一行文本。
/**
* Represents a line of text, such as a line of source code.
*
* TextLine objects are __immutable__. When a {@link TextDocument document} changes,
* previously retrieved lines will not represent the latest state.
*/
export interface TextLine {
/**
* The zero-based line number.
*/
readonly lineNumber: number;
/**
* The text of this line without the line separator characters.
*/
readonly text: string;
/**
* The range this line covers without the line separator characters.
*/
readonly range: Range;
/**
* The range this line covers with the line separator characters.
*/
readonly rangeIncludingLineBreak: Range;
/**
* The offset of the first character which is not a whitespace character as defined
* by `/\s/`. **Note** that if a line is all whitespace the length of the line is returned.
*/
/**
* 第一个不为Whitespace的字符的index。
*/
readonly firstNonWhitespaceCharacterIndex: number;
/**
* Whether this line is whitespace only, shorthand
* for {@link TextLine.firstNonWhitespaceCharacterIndex} === {@link TextLine.text TextLine.text.length}.
*/
/**
* 这一行是否仅为空格
*/
readonly isEmptyOrWhitespace: boolean;
}
参考项目
(vscode-docker)[github.com/microsoft/v…], 编写Dockfile
的语法比较简单,适合拿来做参考。
主要涉及3个项目:
- (vscode-docker)[github.com/microsoft/v…]
- (dockerfile-language-server-nodejs)[github.com/rcjsuen/doc…]
- (dockerfile-language-service)[github.com/rcjsuen/doc…]:
vscode-docker
依赖dockerfile-language-server-nodejs
, dockerfile-language-server-nodejs
依赖dockerfile-language-service
。
dockerfile-language-server-nodejs
只包含启动符合LSP
的Dockerfile language server
所需的代码。解析Dockerfile和提供编辑器特性(如代码完成或悬浮)的实际代码在dockerfile-language-service
。
所以我们可以直接看dockerfile-language-service
的代码就好了。
我以一个例子来说明:
当检测到输入满足COPY --from=
的时候,给出自动补全提示,包括为在文件开头定义的两个image
。
这是一个很好的例子,因为它包含语义分析的结果。
如何跟踪dockerfile-language-service
代码?
跟踪dockerfile-language-service
代码
根据dockerfile-language-service
的单元测试,我找到一种比较方便的方式。
- 在
dockerfile-language-service
源码中新建dockerfile-language-service/temp/main.ts
文件:
import {
Position, CompletionItem
} from 'vscode-languageserver-types';
import { DockerfileLanguageServiceFactory } from '../src/main';
const service = DockerfileLanguageServiceFactory.createLanguageService();
function computePosition(content: string, line: number, character: number, snippetSupport?: boolean): CompletionItem[] {
if (snippetSupport === undefined) {
snippetSupport = true;
}
service.setCapabilities({ completion: { completionItem: { snippetSupport: snippetSupport } }});
let items = service.computeCompletionItems(content, Position.create(line, character));
return items as CompletionItem[];
}
const text = `From node:latest as node
From nginx:latest as nginx
COPY --from=`;
// 光标位置,第4行第13列
var proposals = computePosition(text, 3, 12);
console.log(proposals);
- 在vscode中配置调整main.ts
.vscode/launch.json
:
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceFolder}/temp/main.ts",
"preLaunchTask": "tsc:build", // 要在调试会话开始之前执行的任务,任务在`.vscode/tasks.json`定义,这里的作用是编译ts文件。
"outFiles": ["${workspaceFolder}/out/**/*.js"]
}
]
}
.vscode/tasks.json
:
{
"version": "2.0.0",
"tasks": [
{
"type": "shell",
"label": "tsc:build",
"command": "${workspaceFolder}/node_modules/.bin/tsc",
"args": ["-p", "."],
"problemMatcher": "$tsc"
}
]
}
这样操作之后,我们就可以很方便的调试dockerfile-language-service
的代码了。