vscode插件快餐教程(9) - LSP補全與本地補全
我們接續第5講未介紹完的LSP的onCompletion補全的部分。
TextDocumentPositionParams
在第5講,我們曾經介紹過LSP處理onCompletion的例子,我們再複習一下:
connection.onCompletion(
(_textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => {
return [
{
label: 'TextView',
kind: CompletionItemKind.Text,
data: 1
},
{
label: 'Button',
kind: CompletionItemKind.Text,
data: 2
},
{
label: 'ListView',
kind: CompletionItemKind.Text,
data: 3
}
];
}
)
這其中的TextDocumentPositionParams其實非常簡單,隻有文檔uri,行,列三個參數。
我們來看下其定義:
export interface TextDocumentPositionParams {
/**
* The text document.
*/
textDocument: TextDocumentIdentifier;
/**
* The position inside the text document.
*/
position: Position;
}
TextDocumentIdentifier
TextDocumentIdentifier封裝了兩層,本質上就是一個URI的字元串。
/**
* A literal to identify a text document in the client.
*/
export interface TextDocumentIdentifier {
/**
* The text document's uri.
*/
uri: DocumentUri;
}
DocumentUri其實就是string的馬甲,請看定義:
/**
* A tagging type for string properties that are actually URIs.
*/
export type DocumentUri = string;
這個URI位址,一般是所編輯檔案位址,以Windows上的位址為例:
file:///c%3A/working/temp/completions-sample/test.bas
Position
Position由行号line和列号character組成:
export interface Position {
/**
* Line position in a document (zero-based).
* If a line number is greater than the number of lines in a document, it defaults back to the number of lines in the document.
* If a line number is negative, it defaults to 0.
*/
line: number;
/**
* Character offset on a line in a document (zero-based). Assuming that the line is
* represented as a string, the `character` value represents the gap between the
* `character` and `character + 1`.
*
* If the character value is greater than the line length it defaults back to the
* line length.
* If a line number is negative, it defaults to 0.
*/
character: number;
}
LSP與本地CompleteProvider的對照
LSP畢竟是一套完整的協定,可以多條消息或指令配合執行。而本地Provider提供的功能相對更全面一些。
上面我們介紹了onComplete的參數是一個URI字元串,而在CompleteProvider中,則直接擷取到完整的TextDocument的内容:
provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context: vscode.CompletionContext)
通過TextDocument對象,我們就可以擷取到文本的内容,版本号,所對應的語言等等:
let provider1 = vscode.languages.registerCompletionItemProvider('plaintext', {
provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context: vscode.CompletionContext) {
console.log('document version=' + document.version);
console.log('text is:' + document.getText());
console.log('URI is:' + document.uri);
console.log('Language ID=' + document.languageId);
console.log('Line Count=' + document.lineCount);
CompleteItem
說完參數,我們再說說傳回值中的CompleteItem。
最簡單的CompleteItem類型 - 字元串補全
最簡單的就是直接給一個字元串,例:
const simpleCompletion = new vscode.CompletionItem('console.log');
這樣,當使用者輸入c的時候,就會提示是否要補全console.log。
Code Snippets補全
另外進階一點的補全,是允許使用者進行選擇和替換的補全,類似于Code Snippets功能。
比如我們可以提供log, warn, error三個選項給console做補全:
const snippetCompletion = new vscode.CompletionItem('console');
snippetCompletion.insertText = new vscode.SnippetString('console.${1|log,warn,error|}. Is it console.${1}?');
snippetCompletion.documentation = new vscode.MarkdownString("Code snippet for console");
也就是說,除了預設的label屬性,這個例子中還指定了insertText和documentation屬性。
指定commit鍵的補全
這一節我們增加commitCharacters,文檔也選用更強大的MarkdownString:
const commitCharacterCompletion = new vscode.CompletionItem('console');
commitCharacterCompletion.commitCharacters = ['.'];
commitCharacterCompletion.documentation = new vscode.MarkdownString('Press `.` to get `console.`');
然後,如第5節中所述一樣,我們還需要為二段補全提供一個新的provider:
const provider2 = vscode.languages.registerCompletionItemProvider(
'plaintext',
{
provideCompletionItems(document: vscode.TextDocument, position: vscode.Position) {
// get all text until the `position` and check if it reads `console.`
// and if so then complete if `log`, `warn`, and `error`
let linePrefix = document.lineAt(position).text.substr(0, position.character);
if (!linePrefix.endsWith('console.')) {
return undefined;
}
return [
new vscode.CompletionItem('log', vscode.CompletionItemKind.Method),
new vscode.CompletionItem('warn', vscode.CompletionItemKind.Method),
new vscode.CompletionItem('error', vscode.CompletionItemKind.Method),
];
}
},
'.' // triggered whenever a '.' is being typed
);
終極大招:調用其它指令進行補全
最後,我們如果自己搞不定了,還可以通過指定command屬性來調用其它指令來進行補全,比如本例中我們調用editor.action.triggerSuggest指令來進行進一步的處理:
const commandCompletion = new vscode.CompletionItem('new');
commandCompletion.kind = vscode.CompletionItemKind.Keyword;
commandCompletion.insertText = 'new ';
commandCompletion.command = { command: 'editor.action.triggerSuggest', title: 'Re-trigger completions...' };
實作異步補全
vscode的CompletionProvider另外強大的一點是,provideCompletionItems是可以async的,這樣就可以去等待另一個費時的線程甚至是遠端的服務傳回來進行補全計算了,隻要await真正計算的線程就好了。
我們來個需要伺服器傳回的例子看下:
let provider1 = vscode.languages.registerCompletionItemProvider('javascript', {
async provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context: vscode.CompletionContext) {
let item: vscode.CompletionItem = await instance.post('/complete', { code: getLine(document, position) })
.then(function (response: any) {
console.log('complete: ' + response.data);
return new vscode.CompletionItem(response.data);
})
.catch(function (error: Error) {
console.log(error);
return new vscode.CompletionItem('No suggestion');
});
return [item];