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`. diff --git a/src/server.ts b/src/server.ts index ff33da1..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,92 +135,76 @@ 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); }); +// 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 { - 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; - let extractPosition = - languageId != "css" - ? extract(line, character) - : extract(line, character, { type: "stylesheet" }); - - if (extractPosition?.abbreviation == undefined) { + 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 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 extractedPosition = extract(line, character, { type }); + + 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 htmlLanguages = [ - "html", - "blade", - "twig", - "eruby", - "erb", - "razor", - "javascript", - "javascriptreact", - "javascript.jsx", - "typescript", - "typescriptreact", - "typescript.tsx", - ]; + const emmetConfig = resolveConfig({ + syntax, + type, + options: { + "output.field": (index, placeholder) => + `\$\{${index}${placeholder ? ":" + placeholder : ""}\}`, + }, + }); - if (htmlLanguages.includes(languageId)) { - const htmlconfig = resolveConfig({ - options: { - "output.field": (index, placeholder) => - `\$\{${index}${placeholder ? ":" + placeholder : ""}\}`, - }, - }); - const markup = parseMarkup(abbreviation, htmlconfig); - textResult = stringifyMarkup(markup, htmlconfig); + let textResult = ""; + 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: {