Skip to content

Commit

Permalink
Merge pull request #39 from LKummer/fix-syntax
Browse files Browse the repository at this point in the history
Fix TSX, JSX support, add Sass, Less support, and add support for arbitrary markup
  • Loading branch information
aca authored May 15, 2022
2 parents 85efe9d + 37ce109 commit 14db837
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 92 deletions.
34 changes: 21 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
136 changes: 57 additions & 79 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<TextDocument> = new TextDocuments(TextDocument);
const documents: TextDocuments<TextDocument> = 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.
Expand All @@ -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 = [
">",
Expand Down Expand Up @@ -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<string, Thenable<ExampleSettings>> = new Map();

function getDocumentSettings(resource: string): Thenable<ExampleSettings> {
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<string, Thenable<ExampleSettings>> = 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: {
Expand Down

0 comments on commit 14db837

Please sign in to comment.