├── .gitignore
├── README.md
├── helix-snippets-ls.gif
├── out
├── server.js
└── server.js.map
├── package.json
├── pnpm-lock.yaml
├── src
└── server.ts
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # helix-snippets-ls
2 |
3 | [](https://npmjs.org/package/helix-snippets-ls 'View this project on npm')
4 |
5 | Language server for snippets in Helix
6 |
7 | This package allows you to add snippets to the [Helix](https://helix-editor.com) editor
8 | using a LSP server for autocompleting snippets.
9 |
10 | You can define your snippets in a TOML file and use it in Helix.
11 |
12 | 
13 |
14 |
15 | ## Install
16 | ```
17 | npm i -g helix-snippets-ls
18 | ```
19 |
20 |
21 | ### Usage
22 | Create an `snippets.toml` file inside your Helix config dir `~/.config/helix/snippets.toml`
23 |
24 | The snippets follow the [VSCode snippets](https://code.visualstudio.com/docs/editor/userdefinedsnippets) standard.
25 | Add snippets inside it like:
26 |
27 | ```toml
28 | li = "
$0"
29 | img = "
"
30 | inbx = "$0"
31 |
32 | # Multiline snippets
33 | for = """for await (const ${1:iterator} of ${2:object}) {
34 | \t$0
35 | }"""
36 | sim = "setImmediate(() => {\n\t${0}\n})"
37 |
38 | ```
39 |
40 | ### Config
41 | Config your `languages.toml` to use this Language server
42 |
43 | ```toml
44 | [[language]]
45 | name = "handlebars"
46 | roots = ["package.json"]
47 | file-types = ["hbs"]
48 | scope = "source.hbs"
49 | language-server = { command = "helix-snippets-ls" }
50 | ```
51 |
52 | ### Known limitations
53 | - The language server can be only used for one language at a time
54 |
--------------------------------------------------------------------------------
/helix-snippets-ls.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajasegar/helix-snippets-ls/dc03c431e1aeff2fa827a009c6cbe5b67a0f7a79/helix-snippets-ls.gif
--------------------------------------------------------------------------------
/out/server.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | "use strict";
3 | Object.defineProperty(exports, "__esModule", { value: true });
4 | const fs = require("node:fs");
5 | const toml = require("toml");
6 | const os = require("node:os");
7 | const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
8 | const node_1 = require("vscode-languageserver/node");
9 | console.log(`Inside lsp: ${new Date().toTimeString()}`);
10 | const contents = fs.readFileSync(`${os.homedir()}/.config/helix/snippets.toml`, "utf8");
11 | const snippets = toml.parse(contents);
12 | const connection = (0, node_1.createConnection)(process.stdin, process.stdout);
13 | //Create a simple text document manager
14 | const documents = new node_1.TextDocuments(vscode_languageserver_textdocument_1.TextDocument);
15 | let hasConfigurationCapability = false;
16 | let hasWorkspaceFolderCapability = false;
17 | connection.onInitialize((params) => {
18 | const capabilities = params.capabilities;
19 | console.log("Initializing unity frontend lsp...");
20 | // Does the client support the `workspace/configuration` request?
21 | // If not, we fall back using global settings.
22 | hasConfigurationCapability = !!(capabilities.workspace && !!capabilities.workspace.configuration);
23 | hasWorkspaceFolderCapability = !!(capabilities.workspace && !!capabilities.workspace.workspaceFolders);
24 | const triggerCharacters = Object.keys(snippets);
25 | const result = {
26 | capabilities: {
27 | textDocumentSync: node_1.TextDocumentSyncKind.Incremental,
28 | // Tell the client that this server supports code completion.
29 | completionProvider: {
30 | resolveProvider: true,
31 | triggerCharacters: triggerCharacters,
32 | },
33 | },
34 | };
35 | if (hasWorkspaceFolderCapability) {
36 | result.capabilities.workspace = {
37 | workspaceFolders: {
38 | supported: true,
39 | },
40 | };
41 | }
42 | console.log(result);
43 | return result;
44 | });
45 | connection.onInitialized(() => {
46 | if (hasConfigurationCapability) {
47 | // Register for all configuration changes.
48 | connection.client.register(node_1.DidChangeConfigurationNotification.type, undefined);
49 | }
50 | if (hasWorkspaceFolderCapability) {
51 | connection.workspace.onDidChangeWorkspaceFolders((_event) => {
52 | connection.console.log("Workspace folder change event received.");
53 | });
54 | }
55 | });
56 | // This handler provides the initial list of the completion items.
57 | connection.onCompletion((textDocumentPosition) => {
58 | try {
59 | const docs = documents.get(textDocumentPosition.textDocument.uri);
60 | if (!docs)
61 | throw "failed to find document";
62 | const languageId = docs.languageId;
63 | const content = docs.getText();
64 | const linenr = textDocumentPosition.position.line;
65 | const line = String(content.split(/\r?\n/g)[linenr]);
66 | const character = textDocumentPosition.position.character;
67 | return Object.keys(snippets).map((key, idx) => {
68 | const label = snippets[key];
69 | return {
70 | label,
71 | kind: node_1.CompletionItemKind.Snippet,
72 | data: idx + 1
73 | };
74 | });
75 | }
76 | catch (error) {
77 | connection.console.log(`ERR: ${error}`);
78 | }
79 | return [];
80 | });
81 | // This handler resolve additional information for the item selected in
82 | // the completion list.
83 | connection.onCompletionResolve((item) => {
84 | /*
85 | if (item.data === 1) {
86 | item.detail = 'TypeScript details',
87 | item.documentation = 'TypeScript documentation'
88 | } else if (item.data === 2) {
89 | item.detail = 'JavaScript details',
90 | item.documentation = 'JavaScript documentation'
91 | }
92 | */
93 | return item;
94 | });
95 | documents.listen(connection);
96 | connection.listen();
97 | //# sourceMappingURL=server.js.map
--------------------------------------------------------------------------------
/out/server.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";;;AAEA,8BAA8B;AAC9B,6BAA6B;AAC7B,8BAA8B;AAC9B,2FAAkE;AAClE,qDAWoC;AAEpC,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,EAAE,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;AAGxD,MAAM,QAAQ,GAAG,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,8BAA8B,EAAC,MAAM,CAAC,CAAC;AACvF,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;AAEtC,MAAM,UAAU,GAAG,IAAA,uBAAgB,EAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;AAEnE,uCAAuC;AACvC,MAAM,SAAS,GAAgC,IAAI,oBAAa,CAAC,iDAAY,CAAC,CAAC;AAE/E,IAAI,0BAA0B,GAAY,KAAK,CAAC;AAChD,IAAI,4BAA4B,GAAY,KAAK,CAAC;AAElD,UAAU,CAAC,YAAY,CAAC,CAAC,MAAwB,EAAE,EAAE;IACnD,MAAM,YAAY,GAAG,MAAM,CAAC,YAAY,CAAC;IAEzC,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;IAClD,iEAAiE;IACjE,8CAA8C;IAC9C,0BAA0B,GAAG,CAAC,CAAC,CAC7B,YAAY,CAAC,SAAS,IAAI,CAAC,CAAC,YAAY,CAAC,SAAS,CAAC,aAAa,CACjE,CAAC;IACF,4BAA4B,GAAG,CAAC,CAAC,CAC/B,YAAY,CAAC,SAAS,IAAI,CAAC,CAAC,YAAY,CAAC,SAAS,CAAC,gBAAgB,CACpE,CAAC;IAGF,MAAM,iBAAiB,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAEhD,MAAM,MAAM,GAAqB;QAC/B,YAAY,EAAE;YACZ,gBAAgB,EAAE,2BAAoB,CAAC,WAAW;YAClD,6DAA6D;YAC7D,kBAAkB,EAAE;gBAClB,eAAe,EAAE,IAAI;gBACrB,iBAAiB,EAAE,iBAAiB;aACrC;SACF;KACF,CAAC;IACF,IAAI,4BAA4B,EAAE;QAChC,MAAM,CAAC,YAAY,CAAC,SAAS,GAAG;YAC9B,gBAAgB,EAAE;gBAChB,SAAS,EAAE,IAAI;aAChB;SACF,CAAC;KACH;IACD,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACpB,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC,CAAC;AAEH,UAAU,CAAC,aAAa,CAAC,GAAG,EAAE;IAC5B,IAAI,0BAA0B,EAAE;QAC9B,0CAA0C;QAC1C,UAAU,CAAC,MAAM,CAAC,QAAQ,CACxB,yCAAkC,CAAC,IAAI,EACvC,SAAS,CACV,CAAC;KACH;IACD,IAAI,4BAA4B,EAAE;QAChC,UAAU,CAAC,SAAS,CAAC,2BAA2B,CAAC,CAAC,MAAM,EAAE,EAAE;YAC1D,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAC;QACpE,CAAC,CAAC,CAAC;KACJ;AACH,CAAC,CAAC,CAAC;AAEH,kEAAkE;AAClE,UAAU,CAAC,YAAY,CAAC,CAAC,oBAAgD,EAAoB,EAAE;IAC7F,IAAI;QACF,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,oBAAoB,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QAClE,IAAI,CAAC,IAAI;YAAE,MAAM,yBAAyB,CAAC;QAC3C,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,oBAAoB,CAAC,QAAQ,CAAC,IAAI,CAAC;QAClD,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QACrD,MAAM,SAAS,GAAG,oBAAoB,CAAC,QAAQ,CAAC,SAAS,CAAC;QAE1D,OAAO,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAC,GAAG,EAAE,EAAE;YAC3C,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;YAC5B,OAAO;gBACL,KAAK;gBACL,IAAI,EAAE,yBAAkB,CAAC,OAAO;gBAChC,IAAI,EAAE,GAAG,GAAG,CAAC;aACd,CAAC;QACJ,CAAC,CAAC,CAAC;KAEJ;IAAC,OAAO,KAAK,EAAE;QACd,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,EAAE,CAAC,CAAC;KACzC;IAED,OAAO,EAAE,CAAC;AACZ,CAAC,CAAC,CAAC;AAEH,uEAAuE;AACvE,uBAAuB;AACvB,UAAU,CAAC,mBAAmB,CAAC,CAAC,IAAoB,EAAkB,EAAE;IACtE;;;;;;;;MAQE;IACF,OAAO,IAAI,CAAC;AACd,CAAC,CAAC,CAAC;AAMH,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;AAE7B,UAAU,CAAC,MAAM,EAAE,CAAC"}
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "helix-snippets-ls",
3 | "version": "1.0.1",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "dev": "npm run build && npm link",
9 | "build": "tsc"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "@types/node": "^20.4.0",
16 | "toml": "^3.0.0",
17 | "typescript": "^5.1.6",
18 | "vscode-languageserver": "^8.1.0",
19 | "vscode-languageserver-textdocument": "^1.0.8"
20 | },
21 | "bin": {
22 | "helix-snippets-ls": "./out/server.js"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: '6.0'
2 |
3 | dependencies:
4 | '@types/node':
5 | specifier: ^20.4.0
6 | version: 20.4.0
7 | toml:
8 | specifier: ^3.0.0
9 | version: 3.0.0
10 | typescript:
11 | specifier: ^5.1.6
12 | version: 5.1.6
13 | vscode-languageserver:
14 | specifier: ^8.1.0
15 | version: 8.1.0
16 | vscode-languageserver-textdocument:
17 | specifier: ^1.0.8
18 | version: 1.0.8
19 |
20 | packages:
21 |
22 | /@types/node@20.4.0:
23 | resolution: {integrity: sha512-jfT7iTf/4kOQ9S7CHV9BIyRaQqHu67mOjsIQBC3BKZvzvUB6zLxEwJ6sBE3ozcvP8kF6Uk5PXN0Q+c0dfhGX0g==}
24 | dev: false
25 |
26 | /toml@3.0.0:
27 | resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==}
28 | dev: false
29 |
30 | /typescript@5.1.6:
31 | resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==}
32 | engines: {node: '>=14.17'}
33 | hasBin: true
34 | dev: false
35 |
36 | /vscode-jsonrpc@8.1.0:
37 | resolution: {integrity: sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw==}
38 | engines: {node: '>=14.0.0'}
39 | dev: false
40 |
41 | /vscode-languageserver-protocol@3.17.3:
42 | resolution: {integrity: sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA==}
43 | dependencies:
44 | vscode-jsonrpc: 8.1.0
45 | vscode-languageserver-types: 3.17.3
46 | dev: false
47 |
48 | /vscode-languageserver-textdocument@1.0.8:
49 | resolution: {integrity: sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q==}
50 | dev: false
51 |
52 | /vscode-languageserver-types@3.17.3:
53 | resolution: {integrity: sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==}
54 | dev: false
55 |
56 | /vscode-languageserver@8.1.0:
57 | resolution: {integrity: sha512-eUt8f1z2N2IEUDBsKaNapkz7jl5QpskN2Y0G01T/ItMxBxw1fJwvtySGB9QMecatne8jFIWJGWI61dWjyTLQsw==}
58 | hasBin: true
59 | dependencies:
60 | vscode-languageserver-protocol: 3.17.3
61 | dev: false
62 |
--------------------------------------------------------------------------------
/src/server.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import * as fs from 'node:fs';
4 | import * as toml from 'toml';
5 | import * as os from 'node:os';
6 | import { TextDocument } from "vscode-languageserver-textdocument";
7 | import {
8 | CompletionItem,
9 | CompletionItemKind,
10 | createConnection,
11 | DidChangeConfigurationNotification,
12 | InitializeParams,
13 | InitializeResult,
14 | TextDocumentPositionParams,
15 | TextDocuments,
16 | TextDocumentSyncKind
17 |
18 | } from "vscode-languageserver/node";
19 |
20 |
21 |
22 | const contents = fs.readFileSync(`${os.homedir()}/.config/helix/snippets.toml`,"utf8");
23 | const snippets = toml.parse(contents);
24 |
25 | const connection = createConnection(process.stdin, process.stdout);
26 | connection.console.log(`Inside lsp: ${new Date().toTimeString()}`);
27 |
28 | //Create a simple text document manager
29 | const documents: TextDocuments = new TextDocuments(TextDocument);
30 |
31 | let hasConfigurationCapability: boolean = false;
32 | let hasWorkspaceFolderCapability: boolean = false;
33 |
34 | connection.onInitialize((params: InitializeParams) => {
35 | const capabilities = params.capabilities;
36 |
37 | connection.console.log("Initializing helix-snippets lsp...");
38 | // Does the client support the `workspace/configuration` request?
39 | // If not, we fall back using global settings.
40 | hasConfigurationCapability = !!(
41 | capabilities.workspace && !!capabilities.workspace.configuration
42 | );
43 | hasWorkspaceFolderCapability = !!(
44 | capabilities.workspace && !!capabilities.workspace.workspaceFolders
45 | );
46 |
47 |
48 | const triggerCharacters = Object.keys(snippets);
49 |
50 | const result: InitializeResult = {
51 | capabilities: {
52 | textDocumentSync: TextDocumentSyncKind.Incremental,
53 | // Tell the client that this server supports code completion.
54 | completionProvider: {
55 | resolveProvider: true,
56 | triggerCharacters: triggerCharacters,
57 | },
58 | },
59 | };
60 | if (hasWorkspaceFolderCapability) {
61 | result.capabilities.workspace = {
62 | workspaceFolders: {
63 | supported: true,
64 | },
65 | };
66 | }
67 | console.log(result);
68 | return result;
69 | });
70 |
71 | connection.onInitialized(() => {
72 | if (hasConfigurationCapability) {
73 | // Register for all configuration changes.
74 | connection.client.register(
75 | DidChangeConfigurationNotification.type,
76 | undefined
77 | );
78 | }
79 | if (hasWorkspaceFolderCapability) {
80 | connection.workspace.onDidChangeWorkspaceFolders((_event) => {
81 | connection.console.log("Workspace folder change event received.");
82 | });
83 | }
84 | });
85 |
86 | // This handler provides the initial list of the completion items.
87 | connection.onCompletion((textDocumentPosition: TextDocumentPositionParams): CompletionItem[] => {
88 | try {
89 | const docs = documents.get(textDocumentPosition.textDocument.uri);
90 | if (!docs) throw "failed to find document";
91 | const languageId = docs.languageId;
92 | const content = docs.getText();
93 | const linenr = textDocumentPosition.position.line;
94 | const line = String(content.split(/\r?\n/g)[linenr]);
95 | const character = textDocumentPosition.position.character;
96 |
97 | return Object.keys(snippets).map((key,idx) => {
98 | const label = snippets[key];
99 | return {
100 | label,
101 | kind: CompletionItemKind.Snippet,
102 | data: idx + 1
103 | };
104 | });
105 |
106 | } catch (error) {
107 | connection.console.log(`ERR: ${error}`);
108 | }
109 |
110 | return [];
111 | });
112 |
113 | // This handler resolve additional information for the item selected in
114 | // the completion list.
115 | connection.onCompletionResolve((item: CompletionItem): CompletionItem => {
116 | /*
117 | if (item.data === 1) {
118 | item.detail = 'TypeScript details',
119 | item.documentation = 'TypeScript documentation'
120 | } else if (item.data === 2) {
121 | item.detail = 'JavaScript details',
122 | item.documentation = 'JavaScript documentation'
123 | }
124 | */
125 | return item;
126 | });
127 |
128 |
129 |
130 |
131 |
132 | documents.listen(connection);
133 |
134 | connection.listen();
135 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2019",
4 | "lib": [
5 | "ES2019"
6 | ],
7 | "module": "commonjs",
8 | "moduleResolution": "node",
9 | "sourceMap": true,
10 | "strict": true,
11 | "outDir": "out",
12 | "rootDir": "src",
13 | "types": [
14 | "node"
15 | ]
16 | },
17 | "include": [
18 | "src"
19 | ],
20 | "exclude": [
21 | "node_modules",
22 | ".vscode-test"
23 | ]
24 | }
--------------------------------------------------------------------------------