UNPKG

@shopify/theme-language-server-common

Version:

<h1 align="center" style="position: relative;" > <br> <img src="https://github.com/Shopify/theme-check-vscode/blob/main/images/shopify_glyph.png?raw=true" alt="logo" width="141" height="160"> <br> Theme Language Server </h1>

130 lines 6.96 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ContentForParameterCompletionProvider = void 0; const liquid_html_parser_1 = require("@shopify/liquid-html-parser"); const vscode_languageserver_1 = require("vscode-languageserver"); const params_1 = require("../params"); const contentForParameterCompletionOptions_1 = require("./data/contentForParameterCompletionOptions"); const liquidDoc_1 = require("../../utils/liquidDoc"); /** * Offers completions for parameters for the `content_for` tag after a user has * specificied the type. * * @example {% content_for "block", █ %} */ class ContentForParameterCompletionProvider { constructor(getDocDefinitionForURI) { this.getDocDefinitionForURI = getDocDefinitionForURI; } async completions(params) { var _a; if (!params.completionContext) return []; const { node, ancestors } = params.completionContext; const parentNode = ancestors.at(-1); const parentIsContentFor = (parentNode === null || parentNode === void 0 ? void 0 : parentNode.type) == liquid_html_parser_1.NodeTypes.ContentForMarkup; const nodeIsVariableLookup = (node === null || node === void 0 ? void 0 : node.type) == liquid_html_parser_1.NodeTypes.VariableLookup; if (!parentIsContentFor || !nodeIsVariableLookup) { return []; } if (!node.name || node.lookups.length > 0) { return []; } const completionItems = this.staticCompletions(node, parentNode.contentForType.value == 'blocks', params.document); if (parentNode.contentForType.value === 'block') { const typeArg = (_a = parentNode.args.find((arg) => arg.name === 'type')) === null || _a === void 0 ? void 0 : _a.value; if ((typeArg === null || typeArg === void 0 ? void 0 : typeArg.type) === liquid_html_parser_1.NodeTypes.String) { const snippetDefinition = await this.getDocDefinitionForURI(params.textDocument.uri, 'blocks', typeArg.value); completionItems.push(...this.liquidDocParameterCompletions(node, params.document, snippetDefinition)); } } // We need to find out existing params in the content_for tag so we don't offer it again for completion const existingParams = parentNode.args .filter((arg) => arg.type === liquid_html_parser_1.NodeTypes.NamedArgument) .map((arg) => arg.name); return completionItems.filter((item) => !existingParams.includes(item.label)); } textEdit(node, document, name, textTemplate = `${name}: '$1'`) { var _a, _b; const remainingText = document.source.slice(node.position.end); // Match all the way up to the termination of the parameter which could be // another parameter (`,`), filter (`|`), or the end of a liquid statement. const match = remainingText.match(/^(.*?)\s*(?=,|\||-?\}\}|-?\%\})|^(.*)$/); const offset = match ? match[0].trimEnd().length : remainingText.length; const existingParameterOffset = (_b = (_a = remainingText.match(/[^a-zA-Z]/)) === null || _a === void 0 ? void 0 : _a.index) !== null && _b !== void 0 ? _b : remainingText.length; let start = document.textDocument.positionAt(node.position.start); let end = document.textDocument.positionAt(node.position.end + offset); let newText = name === 'closest' ? `${name}.` : textTemplate; let format = name === 'closest' ? vscode_languageserver_1.InsertTextFormat.PlainText : vscode_languageserver_1.InsertTextFormat.Snippet; // If the cursor is inside the parameter or at the end and it's the same // value as the one we're offering a completion for then we want to restrict // the insert to just the name of the parameter. // e.g. `{% content_for "block", t█ype: "button" %}` and we're offering `type` if (node.name + remainingText.slice(0, existingParameterOffset) == name) { newText = name; format = vscode_languageserver_1.InsertTextFormat.PlainText; end = document.textDocument.positionAt(node.position.end + existingParameterOffset); } // If the cursor is at the beginning of the string we can consider all // options and should not replace any text. // e.g. `{% content_for "block", █type: "button" %}` // e.g. `{% content_for "block", █ %}` if (node.name === params_1.CURSOR) { end = start; // If we're inserting text in front of an existing parameter then we need // to add a comma to separate them. if (existingParameterOffset > 0) { newText += ', '; } } return { textEdit: vscode_languageserver_1.TextEdit.replace({ start, end, }, newText), format, }; } staticCompletions(node, isTypeBlocks, document) { let options = contentForParameterCompletionOptions_1.DEFAULT_COMPLETION_OPTIONS; const partial = node.name.replace(params_1.CURSOR, ''); if (isTypeBlocks) { options = { closest: contentForParameterCompletionOptions_1.DEFAULT_COMPLETION_OPTIONS.closest, }; } return Object.entries(options) .filter(([keyword, _description]) => keyword.startsWith(partial)) .map(([keyword, description]) => { const { textEdit, format } = this.textEdit(node, document, keyword); return { label: keyword, kind: vscode_languageserver_1.CompletionItemKind.Keyword, documentation: { kind: 'markdown', value: description, }, insertTextFormat: format, textEdit, }; }); } liquidDocParameterCompletions(node, document, docDefinition) { var _a; return (((_a = docDefinition === null || docDefinition === void 0 ? void 0 : docDefinition.liquidDoc) === null || _a === void 0 ? void 0 : _a.parameters) || []).map((liquidDocParam) => { const { textEdit, format } = this.textEdit(node, document, liquidDocParam.name, (0, liquidDoc_1.getParameterCompletionTemplate)(liquidDocParam.name, liquidDocParam.type)); return { label: liquidDocParam.name, kind: vscode_languageserver_1.CompletionItemKind.Keyword, documentation: { kind: 'markdown', value: liquidDocParam.description || '', }, insertTextFormat: format, textEdit, }; }); } } exports.ContentForParameterCompletionProvider = ContentForParameterCompletionProvider; //# sourceMappingURL=ContentForParameterCompletionProvider.js.map