svelte-language-server
Version:
A language server for Svelte
193 lines • 7.71 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getCompletions = getCompletions;
const os_1 = require("os");
const vscode_languageserver_1 = require("vscode-languageserver");
const SvelteTags_1 = require("./SvelteTags");
const parseHtml_1 = require("../../../lib/documents/parseHtml");
const getModifierData_1 = require("./getModifierData");
const utils_1 = require("./utils");
const HTML_COMMENT_START = '<!--';
const componentDocumentationCompletion = {
label: '@component',
insertText: `component${os_1.EOL}$1${os_1.EOL}`,
documentation: 'Documentation for this component. ' +
'It will show up on hover. You can use markdown and code blocks here',
insertTextFormat: vscode_languageserver_1.InsertTextFormat.Snippet,
kind: vscode_languageserver_1.CompletionItemKind.Snippet,
sortText: '-1',
filterText: 'component',
preselect: true
};
function getCompletions(document, svelteDoc, position) {
if ((0, utils_1.inStyleOrScript)(svelteDoc, position)) {
return null;
}
const offset = svelteDoc.offsetAt(position);
const lastCharactersBeforePosition = svelteDoc
.getText()
// use last 10 characters, should cover 99% of all cases
.substr(Math.max(offset - 10, 0), Math.min(offset, 10));
const precededByOpeningBracket = /[\s\S]*{\s*[#:/@]\w*$/.test(lastCharactersBeforePosition);
if (precededByOpeningBracket) {
return getTagCompletionsWithinMoustache();
}
const attributeContext = (0, parseHtml_1.getAttributeContextAtPosition)(document, position);
if (attributeContext) {
return getEventModifierCompletion(attributeContext);
}
return getComponentDocumentationCompletions();
/**
* Get completions for special svelte tags within moustache tags.
*/
function getTagCompletionsWithinMoustache() {
const triggerCharacter = getTriggerCharacter(lastCharactersBeforePosition);
// return all, filtering with regards to user input will be done client side
return getCompletionsWithRegardToTriggerCharacter(triggerCharacter, svelteDoc, offset);
}
function getComponentDocumentationCompletions() {
if (!lastCharactersBeforePosition.includes(HTML_COMMENT_START)) {
return null;
}
const commentStartIndex = lastCharactersBeforePosition.lastIndexOf(HTML_COMMENT_START);
const text = lastCharactersBeforePosition
.substring(commentStartIndex + HTML_COMMENT_START.length)
.trimLeft();
if (componentDocumentationCompletion.label.includes(text)) {
return vscode_languageserver_1.CompletionList.create([componentDocumentationCompletion], false);
}
return null;
}
}
function getEventModifierCompletion(attributeContext) {
const modifiers = (0, getModifierData_1.getModifierData)();
if (!attributeContext || !(0, utils_1.attributeCanHaveEventModifier)(attributeContext)) {
return null;
}
const items = modifiers
.filter((modifier) => !attributeContext.name.includes('|' + modifier.modifier) &&
!modifier.modifiersInvalidWith?.some((invalidWith) => attributeContext.name.includes(invalidWith)))
.map((m) => ({
label: m.modifier,
documentation: m.documentation,
kind: vscode_languageserver_1.CompletionItemKind.Event
}));
return vscode_languageserver_1.CompletionList.create(items);
}
const atCompletionItems = createCompletionItems([
{ tag: 'html', label: 'html' },
{ tag: 'debug', label: 'debug' },
{ tag: 'const', label: 'const' },
{ tag: 'render', label: 'render' },
{ tag: 'attach', label: 'attach' }
]);
const hashtagCompletionItems = createCompletionItems([
{ tag: 'if', label: 'if', insertText: 'if $1}\n\t$2\n{/if' },
{ tag: 'each', label: 'each', insertText: 'each $1 as $2}\n\t$3\n{/each' },
{
tag: 'await',
label: 'await :then',
insertText: 'await $1}\n\t$2\n{:then $3} \n\t$4\n{/await'
},
{
tag: 'await',
label: 'await then',
insertText: 'await $1 then $2}\n\t$3\n{/await'
},
{ tag: 'key', label: 'key', insertText: 'key $1}\n\t$2\n{/key' },
{ tag: 'snippet', label: 'snippet', insertText: 'snippet $1($2)}\n\t$3\n{/snippet' }
]);
const beginningAwaitOpenCompletionItems = createCompletionItems([
{ tag: 'await', label: 'then' },
{ tag: 'await', label: 'catch' }
]);
const beginningEachOpenCompletionItems = createCompletionItems([{ tag: 'each', label: 'else' }]);
const beginningIfOpenCompletionItems = createCompletionItems([
{ tag: 'if', label: 'else' },
{ tag: 'if', label: 'else if' }
]);
const endAwaitOpenCompletionItems = createCompletionItems([{ tag: 'await', label: 'await' }]);
const endEachOpenCompletionItems = createCompletionItems([{ tag: 'each', label: 'each' }]);
const endIfOpenCompletionItems = createCompletionItems([{ tag: 'if', label: 'if' }]);
const endKeyOpenCompletionItems = createCompletionItems([{ tag: 'key', label: 'key' }]);
/**
* Get completions with regard to trigger character.
*/
function getCompletionsWithRegardToTriggerCharacter(triggerCharacter, svelteDoc, offset) {
if (triggerCharacter === '@') {
return atCompletionItems;
}
if (triggerCharacter === '#') {
return hashtagCompletionItems;
}
if (triggerCharacter === ':') {
return showCompletionWithRegardsToOpenedTags({
awaitOpen: beginningAwaitOpenCompletionItems,
eachOpen: beginningEachOpenCompletionItems,
ifOpen: beginningIfOpenCompletionItems
}, svelteDoc, offset);
}
if (triggerCharacter === '/') {
return showCompletionWithRegardsToOpenedTags({
awaitOpen: endAwaitOpenCompletionItems,
eachOpen: endEachOpenCompletionItems,
ifOpen: endIfOpenCompletionItems,
keyOpen: endKeyOpenCompletionItems
}, svelteDoc, offset);
}
return null;
}
/**
* Get trigger character in front of current position.
*/
function getTriggerCharacter(content) {
const chars = [
getLastIndexOf('#'),
getLastIndexOf('/'),
getLastIndexOf(':'),
getLastIndexOf('@')
];
return chars.sort((c1, c2) => c2.idx - c1.idx)[0].char;
function getLastIndexOf(char) {
return { char, idx: content.lastIndexOf(char) };
}
}
/**
* Return completions with regards to last opened tag.
*/
function showCompletionWithRegardsToOpenedTags(on, svelteDoc, offset) {
switch ((0, SvelteTags_1.getLatestOpeningTag)(svelteDoc, offset)) {
case 'each':
return on.eachOpen;
case 'if':
return on.ifOpen;
case 'await':
return on.awaitOpen;
case 'key':
return on?.keyOpen ?? null;
case 'snippet':
return on.snippetOpen ?? null;
default:
return null;
}
}
/**
* Create the completion items for given labels and tags.
*/
function createCompletionItems(items) {
return vscode_languageserver_1.CompletionList.create(
// add sortText/preselect so it is ranked higher than other completions and selected first
items.map((item) => ({
insertTextFormat: vscode_languageserver_1.InsertTextFormat.Snippet,
insertText: item.insertText,
label: item.label,
sortText: '-1',
kind: vscode_languageserver_1.CompletionItemKind.Keyword,
preselect: true,
documentation: {
kind: vscode_languageserver_1.MarkupKind.Markdown,
value: SvelteTags_1.documentation[item.tag]
}
})));
}
//# sourceMappingURL=getCompletions.js.map