From 3e17db7cf0147f8077f95ecf5b5ebad88d89f6ff Mon Sep 17 00:00:00 2001 From: Lior Kummer Date: Sun, 17 Apr 2022 14:10:35 +0300 Subject: [PATCH 1/3] Fix JSX/TSX support and add Sass, Less support Fix language syntax not being set in Emmet config. Refactor to treat unknown languages as markup with html syntax to avoid having to manually configure every markup language. Add support for Sass and Less stylesheets. --- src/server.ts | 69 +++++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/src/server.ts b/src/server.ts index ff33da1..12d3607 100644 --- a/src/server.ts +++ b/src/server.ts @@ -169,6 +169,24 @@ documents.onDidClose((e) => { documentSettings.delete(e.document.uri); }); +// For list of language identifiers, see: +// https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentItem +// For list of supported syntax options, see: +// https://github.com/emmetio/emmet/blob/master/src/config.ts#L280-L283 +const markupIdentifierOverrides = { + // Identifiers not in stylesheetIdentifiers are treated as markup. + // Markup languages are treated as html syntax by default. + // So html, blade, razor and the like don't need to be listed. + javascriptreact: 'jsx', + typescriptreact: 'jsx', +} as { [key: string]: string | undefined }; +const stylesheetIdentifiers = [ + 'css', + 'sass', + 'scss', + 'less' +]; + connection.onCompletion( (_textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => { try { @@ -179,10 +197,16 @@ connection.onCompletion( let linenr = _textDocumentPosition.position.line; let line = String(content.split(/\r?\n/g)[linenr]); let character = _textDocumentPosition.position.character; - let extractPosition = - languageId != "css" - ? extract(line, character) - : extract(line, character, { type: "stylesheet" }); + + // Non-stylesheet identifiers are treated as markup. + const isStylesheet = stylesheetIdentifiers.includes(languageId); + // Emmet uses the same identifiers for stylesheets as language servers. + // Unfortunately some markup Emmet syntax names are different from LSP identifiers. + // Treat markup languages as html if not in markupIdentifierOverrides. + const syntax = isStylesheet ? languageId : markupIdentifierOverrides[languageId] ?? 'html'; + const type = isStylesheet ? 'stylesheet' : 'markup'; + + const extractPosition = extract(line, character, { type }) if (extractPosition?.abbreviation == undefined) { throw "failed to parse line"; @@ -193,40 +217,21 @@ connection.onCompletion( let abbreviation = extractPosition.abbreviation; let textResult = ""; - const htmlLanguages = [ - "html", - "blade", - "twig", - "eruby", - "erb", - "razor", - "javascript", - "javascriptreact", - "javascript.jsx", - "typescript", - "typescriptreact", - "typescript.tsx", - ]; - - if (htmlLanguages.includes(languageId)) { - const htmlconfig = resolveConfig({ + const emmetConfig = resolveConfig({ + syntax, + type, options: { "output.field": (index, placeholder) => `\$\{${index}${placeholder ? ":" + placeholder : ""}\}`, }, }); - const markup = parseMarkup(abbreviation, htmlconfig); - textResult = stringifyMarkup(markup, htmlconfig); + + if (!isStylesheet) { + const markup = parseMarkup(abbreviation, emmetConfig); + textResult = stringifyMarkup(markup, emmetConfig); } else { - const cssConfig = resolveConfig({ - type: "stylesheet", - options: { - "output.field": (index, placeholder) => - `\$\{${index}${placeholder ? ":" + placeholder : ""}\}`, - }, - }); - const markup = parseStylesheet(abbreviation, cssConfig); - textResult = stringifyStylesheet(markup, cssConfig); + const markup = parseStylesheet(abbreviation, emmetConfig); + textResult = stringifyStylesheet(markup, emmetConfig); } const range = { start: { From 8605ef0eb996b9f11bac0b176c82ebd178a7f3e1 Mon Sep 17 00:00:00 2001 From: Lior Kummer Date: Sun, 17 Apr 2022 15:57:21 +0300 Subject: [PATCH 2/3] Update readme with newly supported filetypes --- README.md | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 5ab2628..1ba00b1 100644 --- a/README.md +++ b/README.md @@ -13,16 +13,24 @@ npm install -g emmet-ls #### Configuration -- [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig) - ```lua - local lspconfig = require'lspconfig' - local configs = require'lspconfig/configs' - local capabilities = vim.lsp.protocol.make_client_capabilities() - capabilities.textDocument.completion.completionItem.snippetSupport = true - - lspconfig.emmet_ls.setup({ - -- on_attach = on_attach, - capabilities = capabilities, - filetypes = { "html", "css", "typescriptreact", "javascriptreact" }, - }) - ``` +##### Example Configuration + +With [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig): + +```lua +local lspconfig = require('lspconfig') +local configs = require('lspconfig/configs') +local capabilities = vim.lsp.protocol.make_client_capabilities() +capabilities.textDocument.completion.completionItem.snippetSupport = true + +lspconfig.emmet_ls.setup({ + -- on_attach = on_attach, + capabilities = capabilities, + filetypes = { 'html', 'typescriptreact', 'javascriptreact', 'css', 'sass', 'scss', 'less' }, +}) +``` + +##### Supported Filetypes + +- `html`, `typescriptreact`, `javascriptreact`, `css`, `sass`, `scss` and `less` filetypes are fully supported. +- Any other filetype is treated as `html`. From 37ce109e5cd4a8b35797049c69f6dc6f233d43f9 Mon Sep 17 00:00:00 2001 From: Lior Kummer Date: Sun, 17 Apr 2022 17:23:01 +0300 Subject: [PATCH 3/3] Tidy up the server Refactor let to const where possible. Remove unused code, seems to be a remnant from this example: https://code.visualstudio.com/api/language-extensions/language-server-extension-guide#explaining-the-language-server Add missing semicolons, fix indentation and generally clean up. --- src/server.ts | 77 +++++++++++++++++---------------------------------- 1 file changed, 25 insertions(+), 52 deletions(-) diff --git a/src/server.ts b/src/server.ts index 12d3607..791ba2f 100644 --- a/src/server.ts +++ b/src/server.ts @@ -23,17 +23,16 @@ import { TextDocumentSyncKind, } from "vscode-languageserver/node"; -let connection = createConnection(ProposedFeatures.all); +const connection = createConnection(ProposedFeatures.all); // Create a simple text document manager. -let documents: TextDocuments = new TextDocuments(TextDocument); +const documents: TextDocuments = new TextDocuments(TextDocument); let hasConfigurationCapability: boolean = false; let hasWorkspaceFolderCapability: boolean = false; -let hasDiagnosticRelatedInformationCapability: boolean = false; connection.onInitialize((params: InitializeParams) => { - let capabilities = params.capabilities; + const capabilities = params.capabilities; // Does the client support the `workspace/configuration` request? // If not, we fall back using global settings. @@ -43,11 +42,6 @@ connection.onInitialize((params: InitializeParams) => { hasWorkspaceFolderCapability = !!( capabilities.workspace && !!capabilities.workspace.workspaceFolders ); - hasDiagnosticRelatedInformationCapability = !!( - capabilities.textDocument && - capabilities.textDocument.publishDiagnostics && - capabilities.textDocument.publishDiagnostics.relatedInformation - ); const triggerCharacters = [ ">", @@ -141,29 +135,8 @@ interface ExampleSettings { maxNumberOfProblems: number; } -// The global settings, used when the `workspace/configuration` request is not supported by the client. -// Please note that this is not the case when using this server with the client provided in this example -// but could happen with other clients. -const defaultSettings: ExampleSettings = { maxNumberOfProblems: 1000 }; -let globalSettings: ExampleSettings = defaultSettings; - // Cache the settings of all open documents -let documentSettings: Map> = new Map(); - -function getDocumentSettings(resource: string): Thenable { - if (!hasConfigurationCapability) { - return Promise.resolve(globalSettings); - } - let result = documentSettings.get(resource); - if (!result) { - result = connection.workspace.getConfiguration({ - scopeUri: resource, - section: "languageServerExample", - }); - documentSettings.set(resource, result); - } - return result; -} +const documentSettings: Map> = new Map(); documents.onDidClose((e) => { documentSettings.delete(e.document.uri); @@ -190,42 +163,42 @@ const stylesheetIdentifiers = [ connection.onCompletion( (_textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => { try { - let docs = documents.get(_textDocumentPosition.textDocument.uri); + const docs = documents.get(_textDocumentPosition.textDocument.uri); if (!docs) throw "failed to find document"; - let languageId = docs.languageId; - let content = docs.getText(); - let linenr = _textDocumentPosition.position.line; - let line = String(content.split(/\r?\n/g)[linenr]); - let character = _textDocumentPosition.position.character; + const languageId = docs.languageId; + const content = docs.getText(); + const linenr = _textDocumentPosition.position.line; + const line = String(content.split(/\r?\n/g)[linenr]); + const character = _textDocumentPosition.position.character; // Non-stylesheet identifiers are treated as markup. const isStylesheet = stylesheetIdentifiers.includes(languageId); - // Emmet uses the same identifiers for stylesheets as language servers. - // Unfortunately some markup Emmet syntax names are different from LSP identifiers. + // Emmet syntax names are the same as language server identifiers for stylesheets, + // but not for markup languages. // Treat markup languages as html if not in markupIdentifierOverrides. const syntax = isStylesheet ? languageId : markupIdentifierOverrides[languageId] ?? 'html'; const type = isStylesheet ? 'stylesheet' : 'markup'; - const extractPosition = extract(line, character, { type }) + const extractedPosition = extract(line, character, { type }); - if (extractPosition?.abbreviation == undefined) { + if (extractedPosition?.abbreviation == undefined) { throw "failed to parse line"; } - let left = extractPosition.start; - let right = extractPosition.end; - let abbreviation = extractPosition.abbreviation; - let textResult = ""; + const left = extractedPosition.start; + const right = extractedPosition.end; + const abbreviation = extractedPosition.abbreviation; const emmetConfig = resolveConfig({ - syntax, - type, - options: { - "output.field": (index, placeholder) => - `\$\{${index}${placeholder ? ":" + placeholder : ""}\}`, - }, - }); + syntax, + type, + options: { + "output.field": (index, placeholder) => + `\$\{${index}${placeholder ? ":" + placeholder : ""}\}`, + }, + }); + let textResult = ""; if (!isStylesheet) { const markup = parseMarkup(abbreviation, emmetConfig); textResult = stringifyMarkup(markup, emmetConfig);