From 1ff56baad7ffe971e3ebbef500ed6642a3d84cff Mon Sep 17 00:00:00 2001 From: ivanwonder Date: Mon, 15 Jun 2020 17:25:06 +0800 Subject: [PATCH] feat: provide snippets for attribute and method completions when the user selects the attribute in the completion list, the position of the cursor is not expected. It should be in the middle of quotes. --- server/src/completion.ts | 78 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 3 deletions(-) diff --git a/server/src/completion.ts b/server/src/completion.ts index 6076eefd6b..0eeb2af600 100644 --- a/server/src/completion.ts +++ b/server/src/completion.ts @@ -77,9 +77,81 @@ export function tsCompletionEntryToLspCompletionItem( // Text that actually gets inserted to the document. It could be different // from 'entry.name'. For example, a method name could be 'greet', but the // insertText is 'greet()'. - const insertText = entry.insertText || entry.name; - item.textEdit = entry.replacementSpan ? - lsp.TextEdit.replace(tsTextSpanToLspRange(scriptInfo, entry.replacementSpan), insertText) : + let insertText = entry.insertText || entry.name; + let replaceSpan = entry.replacementSpan; + if (kind === CompletionKind.attribute) { + //
+ // ^-------------- start + // ^------- end + let start = scriptInfo.lineOffsetToPosition(position.line + 1, position.character) - 1; + let end = scriptInfo.lineOffsetToPosition(position.line + 1, position.character) + 4; + if (replaceSpan) { + start = replaceSpan.start - 2; + end = replaceSpan.start + replaceSpan.length + 3; + } + const attributeText = scriptInfo.getSnapshot().getText(start, end); + switch (attributeText[1]) { + case '*': + case '-': + // *ngI| => *ngIf="|" + // on-cli| => on-click="|" + if (attributeText[attributeText.length - 3] !== '=') { + item.insertTextFormat = lsp.InsertTextFormat.Snippet; + insertText = `${insertText}="\${0}"`; + replaceSpan = {start: start + 2, length: end - start - 5}; + } + break; + case '[': + // [ngMo|] => [ngModel]="|" + if (attributeText[attributeText.length - 2] !== '=') { + item.insertTextFormat = lsp.InsertTextFormat.Snippet; + insertText = `${insertText}]="\${0}"`; + replaceSpan = {start: start + 2, length: end - start - 4}; + } + break; + case '(': + if (attributeText[0] === '[') { + // [(ngMod|)] => [(ngModel)]="|" + if (attributeText[attributeText.length - 1] !== '=') { + item.insertTextFormat = lsp.InsertTextFormat.Snippet; + insertText = `${insertText})]="\${0}"`; + replaceSpan = {start: start + 2, length: end - start - 3}; + } + } else { + // (clic|) => (click)="|" + if (attributeText[attributeText.length - 2] !== '=') { + item.insertTextFormat = lsp.InsertTextFormat.Snippet; + insertText = `${insertText})="\${0}"`; + replaceSpan = {start: start + 2, length: end - start - 4}; + } + } + break; + default: + break; + } + } + + if (kind === CompletionKind.method) { + let start = scriptInfo.lineOffsetToPosition(position.line + 1, position.character) + 1; + let end = scriptInfo.lineOffsetToPosition(position.line + 1, position.character) + 2; + if (replaceSpan) { + start = replaceSpan.start; + end = replaceSpan.start + replaceSpan.length + 1; + } + const methodText = scriptInfo.getSnapshot().getText(start, end); + if (methodText.endsWith('(')) { + // meth|() => method(|) + replaceSpan = {start: start, length: end - start}; + insertText = insertText.slice(0, insertText.length - 1) + '\${0}'; + } else { + // meth| => method(|) + insertText = insertText.slice(0, insertText.length - 1) + '\${0})'; + } + item.insertTextFormat = lsp.InsertTextFormat.Snippet; + } + + item.textEdit = replaceSpan ? + lsp.TextEdit.replace(tsTextSpanToLspRange(scriptInfo, replaceSpan), insertText) : lsp.TextEdit.insert(position, insertText); return item; }