@intlayer/lsp 8.11.2 → 8.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/findKeyAtOffset.ts
|
|
2
|
+
const INTLAYER_GETTERS = ["useIntlayer", "getIntlayer"];
|
|
3
|
+
const buildGetterRegex = () => new RegExp(`\\b(${INTLAYER_GETTERS.join("|")})\\b\\s*(?:<[^<>()]*>)?\\s*\\(\\s*(['"\`])([^'"\`]+)\\2`, "g");
|
|
4
|
+
/**
|
|
5
|
+
* Scan `text` for an Intlayer getter call whose match span contains `offset`.
|
|
6
|
+
* Returns the dictionary key string, or `null` if the cursor is not inside any
|
|
7
|
+
* recognised call.
|
|
8
|
+
*/
|
|
9
|
+
const findKeyAtOffset = (text, offset) => {
|
|
10
|
+
const regex = buildGetterRegex();
|
|
11
|
+
for (const match of text.matchAll(regex)) {
|
|
12
|
+
const start = match.index;
|
|
13
|
+
const end = start + match[0].length;
|
|
14
|
+
if (offset >= start && offset <= end) return match[3];
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
export { findKeyAtOffset };
|
|
21
|
+
//# sourceMappingURL=findKeyAtOffset.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findKeyAtOffset.mjs","names":[],"sources":["../../src/findKeyAtOffset.ts"],"sourcesContent":["const INTLAYER_GETTERS = ['useIntlayer', 'getIntlayer'] as const;\n\n// \\s* deliberately uses \\s (which includes \\n) so multi-line calls match:\n// useIntlayer(\n// \"my-key\"\n// )\n// Capture group 3 is the dictionary key.\nconst buildGetterRegex = () =>\n new RegExp(\n `\\\\b(${INTLAYER_GETTERS.join('|')})\\\\b\\\\s*(?:<[^<>()]*>)?\\\\s*\\\\(\\\\s*(['\"\\`])([^'\"\\`]+)\\\\2`,\n 'g'\n );\n\n/**\n * Scan `text` for an Intlayer getter call whose match span contains `offset`.\n * Returns the dictionary key string, or `null` if the cursor is not inside any\n * recognised call.\n */\nexport const findKeyAtOffset = (\n text: string,\n offset: number\n): string | null => {\n const regex = buildGetterRegex();\n for (const match of text.matchAll(regex)) {\n const start = match.index;\n const end = start + match[0].length;\n if (offset >= start && offset <= end) {\n return match[3];\n }\n }\n return null;\n};\n"],"mappings":";AAAA,MAAM,mBAAmB,CAAC,eAAe,aAAa;AAOtD,MAAM,yBACJ,IAAI,OACF,OAAO,iBAAiB,KAAK,GAAG,EAAE,0DAClC,GACF;;;;;;AAOF,MAAa,mBACX,MACA,WACkB;CAClB,MAAM,QAAQ,iBAAiB;CAC/B,KAAK,MAAM,SAAS,KAAK,SAAS,KAAK,GAAG;EACxC,MAAM,QAAQ,MAAM;EACpB,MAAM,MAAM,QAAQ,MAAM,GAAG;EAC7B,IAAI,UAAU,SAAS,UAAU,KAC/B,OAAO,MAAM;CAEjB;CACA,OAAO;AACT"}
|
package/dist/esm/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { findKeyAtOffset } from "./findKeyAtOffset.mjs";
|
|
2
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
4
|
import { isAbsolute, join } from "node:path";
|
|
4
5
|
import { pathToFileURL } from "node:url";
|
|
@@ -8,21 +9,6 @@ import { Location, Position, ProposedFeatures, Range, TextDocumentSyncKind, Text
|
|
|
8
9
|
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
9
10
|
|
|
10
11
|
//#region src/index.ts
|
|
11
|
-
/**
|
|
12
|
-
* Intlayer content getters that take a dictionary key as their first argument.
|
|
13
|
-
* They are exported (under the same names) by every `*-intlayer` package, so we
|
|
14
|
-
* only need to match the call name + the string-literal key to support all of:
|
|
15
|
-
* next-intlayer, react-intlayer, vue-intlayer, svelte-intlayer,
|
|
16
|
-
* preact-intlayer, solid-intlayer, angular-intlayer, lit-intlayer,
|
|
17
|
-
* express-intlayer, hono-intlayer, fastify-intlayer, adonis-intlayer, intlayer
|
|
18
|
-
*/
|
|
19
|
-
const INTLAYER_GETTERS = [
|
|
20
|
-
"useIntlayer",
|
|
21
|
-
"getIntlayer",
|
|
22
|
-
"useDictionary",
|
|
23
|
-
"getDictionary"
|
|
24
|
-
];
|
|
25
|
-
const buildGetterRegex = () => new RegExp(`\\b(${INTLAYER_GETTERS.join("|")})\\b\\s*(?:<[^<>()]*>)?\\s*\\(\\s*(['"\`])([^'"\`]+)\\2`, "g");
|
|
26
12
|
const connection = createConnection(ProposedFeatures.all);
|
|
27
13
|
const documents = new TextDocuments(TextDocument);
|
|
28
14
|
let workspaceRoot;
|
|
@@ -84,17 +70,10 @@ connection.onInitialize((params) => {
|
|
|
84
70
|
connection.onDefinition((params) => {
|
|
85
71
|
const document = documents.get(params.textDocument.uri);
|
|
86
72
|
if (!document) return null;
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const start = match.index;
|
|
92
|
-
const end = start + match[0].length;
|
|
93
|
-
if (offset >= start && offset <= end) {
|
|
94
|
-
const key = match[3];
|
|
95
|
-
const locations = getContentDeclarationLocations(key);
|
|
96
|
-
return locations.length ? locations : null;
|
|
97
|
-
}
|
|
73
|
+
const key = findKeyAtOffset(document.getText(), document.offsetAt(params.position));
|
|
74
|
+
if (key) {
|
|
75
|
+
const locations = getContentDeclarationLocations(key);
|
|
76
|
+
return locations.length ? locations : null;
|
|
98
77
|
}
|
|
99
78
|
return null;
|
|
100
79
|
});
|
package/dist/esm/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { existsSync, readFileSync } from 'node:fs';\nimport { isAbsolute, join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { getConfiguration } from '@intlayer/config';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport {\n createConnection,\n type InitializeParams,\n type InitializeResult,\n Location,\n Position,\n ProposedFeatures,\n Range,\n TextDocumentSyncKind,\n TextDocuments,\n} from 'vscode-languageserver/node.js';\nimport { TextDocument } from 'vscode-languageserver-textdocument';\
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { existsSync, readFileSync } from 'node:fs';\nimport { isAbsolute, join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { getConfiguration } from '@intlayer/config';\nimport type { IntlayerConfig } from '@intlayer/types/config';\nimport { getUnmergedDictionaries } from '@intlayer/unmerged-dictionaries-entry';\nimport {\n createConnection,\n type InitializeParams,\n type InitializeResult,\n Location,\n Position,\n ProposedFeatures,\n Range,\n TextDocumentSyncKind,\n TextDocuments,\n} from 'vscode-languageserver/node.js';\nimport { TextDocument } from 'vscode-languageserver-textdocument';\nimport { findKeyAtOffset } from './findKeyAtOffset';\n\n// Create a connection for the server, using Node's IPC as a transport.\n// Also include all preview / proposed LSP features.\nconst connection = createConnection(ProposedFeatures.all);\n\n// Create a simple text document manager.\nconst documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);\n\nlet workspaceRoot: string | null | undefined;\n\n/**\n * Resolve (and cache) the Intlayer configuration for the current workspace.\n * The LSP process cwd is not the project root, so we must resolve the config\n * from `workspaceRoot` to get the right `system`/`build` paths.\n */\nlet cachedConfig: Pick<IntlayerConfig, 'system' | 'build'> | null = null;\nconst getWorkspaceConfig = () => {\n if (cachedConfig) return cachedConfig;\n if (!workspaceRoot) return null;\n\n const { system, build } = getConfiguration({ baseDir: workspaceRoot });\n cachedConfig = { system, build };\n return cachedConfig;\n};\n\n/**\n * Return every `.content` file location that declares the given key.\n * A key can be split across several content files (merged dictionaries), so we\n * return one `Location` per source file, pointing at the `key:` declaration.\n */\nconst getContentDeclarationLocations = (key: string): Location[] => {\n const config = getWorkspaceConfig();\n if (!config) return [];\n\n const unmergedDictionaries = getUnmergedDictionaries(config);\n\n const dictionaries = Object.values(unmergedDictionaries)\n .flat()\n .filter((dictionary) => dictionary?.key === key && dictionary.filePath);\n\n const locations: Location[] = [];\n\n for (const dictionary of dictionaries) {\n const filePath = dictionary.filePath!;\n const absolutePath = isAbsolute(filePath)\n ? filePath\n : join(config.system.baseDir, filePath);\n\n if (!existsSync(absolutePath)) continue;\n\n locations.push(\n Location.create(\n pathToFileURL(absolutePath).href,\n getKeyRange(absolutePath, key)\n )\n );\n }\n\n return locations;\n};\n\n/** Find the `key: \"<key>\"` line inside a content file so the cursor lands on it. */\nconst getKeyRange = (absolutePath: string, key: string): Range => {\n try {\n const content = readFileSync(absolutePath, 'utf-8');\n const lines = content.split(/\\r?\\n/);\n const keyRegex = new RegExp(\n `key\\\\s*:\\\\s*['\"\\`]${key.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}['\"\\`]`\n );\n\n for (let line = 0; line < lines.length; line++) {\n const column = lines[line].search(keyRegex);\n if (column !== -1) {\n return Range.create(\n Position.create(line, column),\n Position.create(line, lines[line].length)\n );\n }\n }\n } catch {\n // fall through to the top of the file\n }\n\n return Range.create(Position.create(0, 0), Position.create(0, 0));\n};\n\nconnection.onInitialize((params: InitializeParams): InitializeResult => {\n workspaceRoot =\n params.rootPath ||\n (params.workspaceFolders?.[0]?.uri\n ? decodeURIComponent(new URL(params.workspaceFolders[0].uri).pathname)\n : undefined);\n cachedConfig = null;\n\n return {\n capabilities: {\n textDocumentSync: TextDocumentSyncKind.Incremental,\n // Declare that this server provides \"Go to Definition\" support\n definitionProvider: true,\n },\n };\n});\n\nconnection.onDefinition((params) => {\n const document = documents.get(params.textDocument.uri);\n if (!document) return null;\n\n const text = document.getText();\n const offset = document.offsetAt(params.position);\n\n const key = findKeyAtOffset(text, offset);\n if (key) {\n const locations = getContentDeclarationLocations(key);\n return locations.length ? locations : null;\n }\n\n return null;\n});\n\n// Make the text document manager listen on the connection\n// for open, change and close text document events\ndocuments.listen(connection);\n\n// Listen on the connection\nconnection.listen();\n"],"mappings":";;;;;;;;;;;AAuBA,MAAM,aAAa,iBAAiB,iBAAiB,GAAG;AAGxD,MAAM,YAAyC,IAAI,cAAc,YAAY;AAE7E,IAAI;;;;;;AAOJ,IAAI,eAAgE;AACpE,MAAM,2BAA2B;CAC/B,IAAI,cAAc,OAAO;CACzB,IAAI,CAAC,eAAe,OAAO;CAE3B,MAAM,EAAE,QAAQ,UAAU,iBAAiB,EAAE,SAAS,cAAc,CAAC;CACrE,eAAe;EAAE;EAAQ;CAAM;CAC/B,OAAO;AACT;;;;;;AAOA,MAAM,kCAAkC,QAA4B;CAClE,MAAM,SAAS,mBAAmB;CAClC,IAAI,CAAC,QAAQ,OAAO,CAAC;CAErB,MAAM,uBAAuB,wBAAwB,MAAM;CAE3D,MAAM,eAAe,OAAO,OAAO,oBAAoB,EACpD,KAAK,EACL,QAAQ,eAAe,YAAY,QAAQ,OAAO,WAAW,QAAQ;CAExE,MAAM,YAAwB,CAAC;CAE/B,KAAK,MAAM,cAAc,cAAc;EACrC,MAAM,WAAW,WAAW;EAC5B,MAAM,eAAe,WAAW,QAAQ,IACpC,WACA,KAAK,OAAO,OAAO,SAAS,QAAQ;EAExC,IAAI,CAAC,WAAW,YAAY,GAAG;EAE/B,UAAU,KACR,SAAS,OACP,cAAc,YAAY,EAAE,MAC5B,YAAY,cAAc,GAAG,CAC/B,CACF;CACF;CAEA,OAAO;AACT;;AAGA,MAAM,eAAe,cAAsB,QAAuB;CAChE,IAAI;EAEF,MAAM,QADU,aAAa,cAAc,OACvB,EAAE,MAAM,OAAO;EACnC,MAAM,WAAW,IAAI,OACnB,qBAAqB,IAAI,QAAQ,uBAAuB,MAAM,EAAE,OAClE;EAEA,KAAK,IAAI,OAAO,GAAG,OAAO,MAAM,QAAQ,QAAQ;GAC9C,MAAM,SAAS,MAAM,MAAM,OAAO,QAAQ;GAC1C,IAAI,WAAW,IACb,OAAO,MAAM,OACX,SAAS,OAAO,MAAM,MAAM,GAC5B,SAAS,OAAO,MAAM,MAAM,MAAM,MAAM,CAC1C;EAEJ;CACF,QAAQ,CAER;CAEA,OAAO,MAAM,OAAO,SAAS,OAAO,GAAG,CAAC,GAAG,SAAS,OAAO,GAAG,CAAC,CAAC;AAClE;AAEA,WAAW,cAAc,WAA+C;CACtE,gBACE,OAAO,aACN,OAAO,mBAAmB,IAAI,MAC3B,mBAAmB,IAAI,IAAI,OAAO,iBAAiB,GAAG,GAAG,EAAE,QAAQ,IACnE;CACN,eAAe;CAEf,OAAO,EACL,cAAc;EACZ,kBAAkB,qBAAqB;EAEvC,oBAAoB;CACtB,EACF;AACF,CAAC;AAED,WAAW,cAAc,WAAW;CAClC,MAAM,WAAW,UAAU,IAAI,OAAO,aAAa,GAAG;CACtD,IAAI,CAAC,UAAU,OAAO;CAKtB,MAAM,MAAM,gBAHC,SAAS,QAGS,GAFhB,SAAS,SAAS,OAAO,QAED,CAAC;CACxC,IAAI,KAAK;EACP,MAAM,YAAY,+BAA+B,GAAG;EACpD,OAAO,UAAU,SAAS,YAAY;CACxC;CAEA,OAAO;AACT,CAAC;AAID,UAAU,OAAO,UAAU;AAG3B,WAAW,OAAO"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/findKeyAtOffset.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Scan `text` for an Intlayer getter call whose match span contains `offset`.
|
|
4
|
+
* Returns the dictionary key string, or `null` if the cursor is not inside any
|
|
5
|
+
* recognised call.
|
|
6
|
+
*/
|
|
7
|
+
declare const findKeyAtOffset: (text: string, offset: number) => string | null;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { findKeyAtOffset };
|
|
10
|
+
//# sourceMappingURL=findKeyAtOffset.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findKeyAtOffset.d.ts","names":[],"sources":["../../src/findKeyAtOffset.ts"],"mappings":";;AAkBA;;;;cAAa,eAAA,GACX,IAAA,UACA,MAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intlayer/lsp",
|
|
3
|
-
"version": "8.11.
|
|
3
|
+
"version": "8.11.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Intlayer Language Server. Handle LSP to help IDE and AI agent to use Intlayer",
|
|
6
6
|
"keywords": [
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
"lsp",
|
|
9
9
|
"ide",
|
|
10
10
|
"ai",
|
|
11
|
-
"agent"
|
|
11
|
+
"agent",
|
|
12
|
+
"mcp-lsp",
|
|
13
|
+
"mcp-language-server"
|
|
12
14
|
],
|
|
13
15
|
"homepage": "https://intlayer.org",
|
|
14
16
|
"bugs": {
|
|
@@ -86,9 +88,9 @@
|
|
|
86
88
|
"typecheck": "tsc --noEmit --project tsconfig.types.json"
|
|
87
89
|
},
|
|
88
90
|
"dependencies": {
|
|
89
|
-
"@intlayer/config": "8.11.
|
|
90
|
-
"@intlayer/types": "8.11.
|
|
91
|
-
"@intlayer/unmerged-dictionaries-entry": "8.11.
|
|
91
|
+
"@intlayer/config": "8.11.3",
|
|
92
|
+
"@intlayer/types": "8.11.3",
|
|
93
|
+
"@intlayer/unmerged-dictionaries-entry": "8.11.3",
|
|
92
94
|
"vscode-languageserver": "^9.0.1",
|
|
93
95
|
"vscode-languageserver-textdocument": "^1.0.12"
|
|
94
96
|
},
|