@dialexjs/language-server 0.7.2 → 0.9.2

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.
package/dist/server.mjs CHANGED
@@ -1,30 +1,10 @@
1
- import { addMissingKeys, addUnknownKey, buildHover, buildModel, completionContextAt, completionEntries, definitionFor, discoverProjects, findBindings, findReferenceAt, modelForFile } from "./features.mjs";
1
+ import { a as completionEntries, f as findBindings, i as completionContextAt, m as modelForFile, n as addMissingKeys, o as definitionFor, p as buildModel, r as addUnknownKey, s as buildHover, t as discoverProjects, u as findReferenceAt } from "./features-Bpcwl0Es.mjs";
2
+ import { analyzeProject, clearGitignore, createAnalysisCache, generateDictionaries, readStaticConfig, renderGenerated, startGenerateWatcher } from "@dialexjs/cli/api";
3
+ import { TextDocument } from "vscode-languageserver-textdocument";
2
4
  import { CodeActionKind, CompletionItemKind, DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesNotification, FileChangeType, TextDocumentSyncKind, TextDocuments } from "vscode-languageserver/node.js";
3
5
  import fs from "node:fs";
4
6
  import { fileURLToPath, pathToFileURL } from "node:url";
5
- import { analyzeProject, clearGitignore, createAnalysisCache, generateDictionaries, readStaticConfig, renderGenerated, startGenerateWatcher } from "@dialexjs/cli/api";
6
- import { TextDocument } from "vscode-languageserver-textdocument";
7
- //#region src/server.ts
8
- const DEFAULT_SETTINGS = {
9
- enable: true,
10
- unusedKeys: false,
11
- autoGenerate: true
12
- };
13
- /** Files whose creation or removal can change the set of Dialex projects or what is scanned. */
14
- const WATCHED_GLOBS = [
15
- "**/*.content.ts",
16
- "**/dialex.config.*",
17
- "**/i18n.config.*",
18
- "**/.gitignore"
19
- ];
20
- function severityOf(issue) {
21
- if (issue.code === "unused-key" || issue.code === "unused-dictionary") return DiagnosticSeverity.Hint;
22
- switch (issue.level) {
23
- case "error": return DiagnosticSeverity.Error;
24
- case "warning": return DiagnosticSeverity.Warning;
25
- default: return DiagnosticSeverity.Information;
26
- }
27
- }
7
+ //#region src/server/convert.ts
28
8
  const COMPLETION_KINDS = {
29
9
  dictionary: CompletionItemKind.Module,
30
10
  group: CompletionItemKind.Folder,
@@ -47,6 +27,28 @@ function replaceAll(text, newText) {
47
27
  newText
48
28
  };
49
29
  }
30
+ const uriToPath = (uri) => uri.startsWith("file:") ? fileURLToPath(uri) : void 0;
31
+ /** One line of an open document, without its line break. */
32
+ const lineOf = (document, line) => document.getText({
33
+ start: {
34
+ line,
35
+ character: 0
36
+ },
37
+ end: {
38
+ line: line + 1,
39
+ character: 0
40
+ }
41
+ }).replace(/\r?\n$/, "");
42
+ //#endregion
43
+ //#region src/server/diagnostics.ts
44
+ function severityOf(issue) {
45
+ if (issue.code === "unused-key" || issue.code === "unused-dictionary") return DiagnosticSeverity.Hint;
46
+ switch (issue.level) {
47
+ case "error": return DiagnosticSeverity.Error;
48
+ case "warning": return DiagnosticSeverity.Warning;
49
+ default: return DiagnosticSeverity.Information;
50
+ }
51
+ }
50
52
  function toDiagnostic(issue) {
51
53
  const severity = severityOf(issue);
52
54
  return {
@@ -83,53 +85,15 @@ function toDiagnostic(issue) {
83
85
  }
84
86
  };
85
87
  }
86
- const uriToPath = (uri) => uri.startsWith("file:") ? fileURLToPath(uri) : void 0;
87
88
  /**
88
- * Wires Dialex's editor features to an LSP connection. Nothing in the project is executed:
89
- * dictionaries and configs are read from the syntax tree (`runtime: false`).
89
+ * Publishes the issues of every project, one list per file, and an explicit empty list for
90
+ * files that had problems before and have none now.
90
91
  */
91
- function createServer(connection) {
92
- const documents = new TextDocuments(TextDocument);
93
- const cache = createAnalysisCache();
94
- const states = /* @__PURE__ */ new Map();
95
- const projectCache = /* @__PURE__ */ new Map();
96
- let folders = [];
97
- let canConfigure = false;
98
- let settings = { ...DEFAULT_SETTINGS };
92
+ function createPublisher(connection) {
99
93
  let published = /* @__PURE__ */ new Set();
100
- let timer;
101
- let generateTimer;
102
- /** True when the client sends file events itself; otherwise the server watches the disk. */
103
- let clientWatches = false;
104
- const watchers = /* @__PURE__ */ new Map();
105
- const projectsIn = (folder) => {
106
- let projects = projectCache.get(folder);
107
- if (!projects) {
108
- projects = discoverProjects(folder);
109
- projectCache.set(folder, projects);
110
- }
111
- return projects;
112
- };
113
- const forgetLayout = () => {
114
- projectCache.clear();
115
- clearGitignore(cache);
116
- };
117
- async function loadSettings() {
118
- settings = { ...DEFAULT_SETTINGS };
119
- if (!canConfigure) return;
120
- try {
121
- const value = await connection.workspace.getConfiguration("dialex");
122
- if (value && typeof value === "object") {
123
- if (typeof value.enable === "boolean") settings.enable = value.enable;
124
- if (typeof value.unusedKeys === "boolean") settings.unusedKeys = value.unusedKeys;
125
- if (typeof value.autoGenerate === "boolean") settings.autoGenerate = value.autoGenerate;
126
- if (typeof value.configPath === "string" && value.configPath) settings.configPath = value.configPath;
127
- }
128
- } catch {}
129
- }
130
- function publish() {
94
+ return (issues) => {
131
95
  const byFile = /* @__PURE__ */ new Map();
132
- for (const { issues } of states.values()) for (const issue of issues) {
96
+ for (const issue of issues) {
133
97
  const list = byFile.get(issue.file) ?? [];
134
98
  list.push(toDiagnostic(issue));
135
99
  byFile.set(issue.file, list);
@@ -148,13 +112,20 @@ function createServer(connection) {
148
112
  diagnostics: []
149
113
  });
150
114
  published = next;
151
- }
115
+ };
116
+ }
117
+ //#endregion
118
+ //#region src/server/generation.ts
119
+ /** Keeps the generated files of the projects that have them current, and watches the disk. */
120
+ function createGeneration(context) {
121
+ const { connection } = context;
122
+ const watchers = /* @__PURE__ */ new Map();
152
123
  /** The projects whose generated file exists; the others do not use one. */
153
124
  function generatedProjects() {
154
125
  const found = [];
155
- for (const folder of folders) for (const { root } of projectsIn(folder)) {
126
+ for (const folder of context.folders()) for (const { root } of context.projectsIn(folder)) {
156
127
  const options = {
157
- config: settings.configPath,
128
+ config: context.settings().configPath,
158
129
  static: true
159
130
  };
160
131
  try {
@@ -173,6 +144,7 @@ function createServer(connection) {
173
144
  * tree, so no project code runs.
174
145
  */
175
146
  async function regenerate() {
147
+ const settings = context.settings();
176
148
  if (!settings.autoGenerate || !settings.enable) return;
177
149
  for (const { root, options } of generatedProjects()) try {
178
150
  const { outputPath } = renderGenerated(root, options);
@@ -186,7 +158,8 @@ function createServer(connection) {
186
158
  /** Without client file events, watch each project that has a generated file ourselves. */
187
159
  async function syncWatchers() {
188
160
  const wanted = /* @__PURE__ */ new Map();
189
- if (!clientWatches && settings.autoGenerate && settings.enable) for (const { root, options } of generatedProjects()) wanted.set(root, options);
161
+ const settings = context.settings();
162
+ if (!context.clientWatches() && settings.autoGenerate && settings.enable) for (const { root, options } of generatedProjects()) wanted.set(root, options);
190
163
  for (const [root, watcher] of watchers) if (!wanted.has(root)) {
191
164
  watchers.delete(root);
192
165
  await watcher.close();
@@ -195,13 +168,232 @@ function createServer(connection) {
195
168
  if (watchers.has(root)) continue;
196
169
  try {
197
170
  watchers.set(root, startGenerateWatcher(root, options, () => {
198
- regenerate().then(() => schedule());
171
+ regenerate().then(() => context.afterWatchRegenerate());
199
172
  }, 250, (message) => connection.console.info(`[${root}] ${message}`)));
200
173
  } catch (error) {
201
174
  connection.console.error(`[${root}] could not watch: ${error.message}`);
202
175
  }
203
176
  }
204
177
  }
178
+ async function dispose() {
179
+ await Promise.all([...watchers.values()].map((watcher) => watcher.close()));
180
+ watchers.clear();
181
+ }
182
+ return {
183
+ regenerate,
184
+ syncWatchers,
185
+ dispose
186
+ };
187
+ }
188
+ //#endregion
189
+ //#region src/server/handlers.ts
190
+ const hoverHandler = (ctx) => (params) => {
191
+ const document = ctx.documents.get(params.textDocument.uri);
192
+ const file = uriToPath(params.textDocument.uri);
193
+ const model = document && file ? ctx.modelFor(file) : void 0;
194
+ if (!document || !model) return null;
195
+ const ref = findReferenceAt(lineOf(document, params.position.line), params.position.character, findBindings(document.getText()));
196
+ const markdown = ref ? buildHover(model, ref) : void 0;
197
+ if (!ref || !markdown) return null;
198
+ return {
199
+ contents: {
200
+ kind: "markdown",
201
+ value: markdown
202
+ },
203
+ range: {
204
+ start: {
205
+ line: params.position.line,
206
+ character: ref.start
207
+ },
208
+ end: {
209
+ line: params.position.line,
210
+ character: ref.end
211
+ }
212
+ }
213
+ };
214
+ };
215
+ const definitionHandler = (ctx) => (params) => {
216
+ const document = ctx.documents.get(params.textDocument.uri);
217
+ const file = uriToPath(params.textDocument.uri);
218
+ const model = document && file ? ctx.modelFor(file) : void 0;
219
+ if (!document || !model) return null;
220
+ const ref = findReferenceAt(lineOf(document, params.position.line), params.position.character, findBindings(document.getText()));
221
+ const target = ref ? definitionFor(model, ref) : void 0;
222
+ if (!target) return null;
223
+ return {
224
+ uri: pathToFileURL(target.file).href,
225
+ range: {
226
+ start: {
227
+ line: target.range.start.line,
228
+ character: target.range.start.column
229
+ },
230
+ end: {
231
+ line: target.range.end.line,
232
+ character: target.range.end.column
233
+ }
234
+ }
235
+ };
236
+ };
237
+ const completionHandler = (ctx) => (params) => {
238
+ const document = ctx.documents.get(params.textDocument.uri);
239
+ const file = uriToPath(params.textDocument.uri);
240
+ const model = document && file ? ctx.modelFor(file) : void 0;
241
+ if (!document || !model) return [];
242
+ const before = lineOf(document, params.position.line).slice(0, params.position.character);
243
+ const context = completionContextAt(before, findBindings(document.getText()));
244
+ if (!context) return [];
245
+ const range = {
246
+ start: {
247
+ line: params.position.line,
248
+ character: params.position.character - context.length
249
+ },
250
+ end: params.position
251
+ };
252
+ return completionEntries(model, context).map((entry) => ({
253
+ label: entry.label,
254
+ kind: COMPLETION_KINDS[entry.kind],
255
+ detail: entry.detail,
256
+ documentation: entry.documentation,
257
+ filterText: entry.label,
258
+ textEdit: {
259
+ range,
260
+ newText: entry.insertText
261
+ }
262
+ }));
263
+ };
264
+ const codeActionHandler = (ctx) => (params) => {
265
+ const file = uriToPath(params.textDocument.uri);
266
+ const document = ctx.documents.get(params.textDocument.uri);
267
+ if (!file || !document || !ctx.enabled()) return [];
268
+ const actions = [];
269
+ const text = document.getText();
270
+ const model = ctx.modelFor(file);
271
+ for (const diagnostic of params.context.diagnostics) {
272
+ const data = diagnostic.data;
273
+ if (diagnostic.source !== "dialex" || !data?.path) continue;
274
+ if (data.code === "missing-key" && data.locale) {
275
+ const updated = addMissingKeys(file, text, [{
276
+ locale: data.locale,
277
+ path: data.path,
278
+ sourceLocale: data.sourceLocale
279
+ }]);
280
+ if (!updated) continue;
281
+ actions.push({
282
+ title: `Add "${data.path.join(".")}" to ${data.locale} (marked [TODO])`,
283
+ kind: CodeActionKind.QuickFix,
284
+ diagnostics: [diagnostic],
285
+ isPreferred: true,
286
+ edit: { changes: { [params.textDocument.uri]: [replaceAll(text, updated)] } }
287
+ });
288
+ }
289
+ if (data.code === "unknown-path" && data.dictionary) {
290
+ const dictionary = model?.dictionaries.get(data.dictionary);
291
+ if (!dictionary) continue;
292
+ const dictionaryUri = pathToFileURL(dictionary.file).href;
293
+ const open = ctx.documents.get(dictionaryUri);
294
+ let dictionaryText;
295
+ try {
296
+ dictionaryText = open ? open.getText() : fs.readFileSync(dictionary.file, "utf-8");
297
+ } catch {
298
+ continue;
299
+ }
300
+ const updated = addUnknownKey(dictionary.file, dictionaryText, data.path);
301
+ if (!updated) continue;
302
+ actions.push({
303
+ title: `Add "${data.path.join(".")}" to the "${dictionary.name}" dictionary`,
304
+ kind: CodeActionKind.QuickFix,
305
+ diagnostics: [diagnostic],
306
+ edit: { changes: { [dictionaryUri]: [replaceAll(dictionaryText, updated)] } }
307
+ });
308
+ }
309
+ }
310
+ const missing = ctx.issues().filter((issue) => issue.file === file && issue.code === "missing-key").filter((issue) => issue.locale && issue.path).map((issue) => ({
311
+ locale: issue.locale,
312
+ path: issue.path,
313
+ sourceLocale: issue.sourceLocale
314
+ }));
315
+ if (missing.length > 1) {
316
+ const updated = addMissingKeys(file, text, missing);
317
+ if (updated) actions.push({
318
+ title: `Add all ${missing.length} missing keys (marked [TODO])`,
319
+ kind: CodeActionKind.QuickFix,
320
+ edit: { changes: { [params.textDocument.uri]: [replaceAll(text, updated)] } }
321
+ });
322
+ }
323
+ return actions;
324
+ };
325
+ //#endregion
326
+ //#region src/server/settings.ts
327
+ const DEFAULT_SETTINGS = {
328
+ enable: true,
329
+ diagnostics: true,
330
+ unusedKeys: false,
331
+ autoGenerate: true
332
+ };
333
+ /** Files whose creation or removal can change the set of Dialex projects or what is scanned. */
334
+ const WATCHED_GLOBS = [
335
+ "**/*.content.ts",
336
+ "**/dialex.config.*",
337
+ "**/i18n.config.*",
338
+ "**/.gitignore"
339
+ ];
340
+ /** Copies the values the client sent for the `dialex` section onto `settings`. */
341
+ function applyClientSettings(settings, value) {
342
+ const sent = value;
343
+ if (sent && typeof sent === "object") {
344
+ if (typeof sent.enable === "boolean") settings.enable = sent.enable;
345
+ if (typeof sent.diagnostics === "boolean") settings.diagnostics = sent.diagnostics;
346
+ if (typeof sent.unusedKeys === "boolean") settings.unusedKeys = sent.unusedKeys;
347
+ if (typeof sent.autoGenerate === "boolean") settings.autoGenerate = sent.autoGenerate;
348
+ if (typeof sent.configPath === "string" && sent.configPath) settings.configPath = sent.configPath;
349
+ }
350
+ }
351
+ //#endregion
352
+ //#region src/server.ts
353
+ /**
354
+ * Wires Dialex's editor features to an LSP connection. Nothing in the project is executed:
355
+ * dictionaries and configs are read from the syntax tree (`runtime: false`).
356
+ */
357
+ function createServer(connection) {
358
+ const documents = new TextDocuments(TextDocument);
359
+ const cache = createAnalysisCache();
360
+ const states = /* @__PURE__ */ new Map();
361
+ const projectCache = /* @__PURE__ */ new Map();
362
+ const publish = createPublisher(connection);
363
+ let folders = [];
364
+ let canConfigure = false;
365
+ let settings = { ...DEFAULT_SETTINGS };
366
+ let timer;
367
+ let generateTimer;
368
+ /** True when the client sends file events itself; otherwise the server watches the disk. */
369
+ let clientWatches = false;
370
+ const projectsIn = (folder) => {
371
+ let projects = projectCache.get(folder);
372
+ if (!projects) {
373
+ projects = discoverProjects(folder);
374
+ projectCache.set(folder, projects);
375
+ }
376
+ return projects;
377
+ };
378
+ const forgetLayout = () => {
379
+ projectCache.clear();
380
+ clearGitignore(cache);
381
+ };
382
+ const generation = createGeneration({
383
+ connection,
384
+ folders: () => folders,
385
+ projectsIn,
386
+ settings: () => settings,
387
+ clientWatches: () => clientWatches,
388
+ afterWatchRegenerate: () => schedule()
389
+ });
390
+ async function loadSettings() {
391
+ settings = { ...DEFAULT_SETTINGS };
392
+ if (!canConfigure) return;
393
+ try {
394
+ applyClientSettings(settings, await connection.workspace.getConfiguration("dialex"));
395
+ } catch {}
396
+ }
205
397
  async function refresh() {
206
398
  await loadSettings();
207
399
  states.clear();
@@ -223,8 +415,8 @@ function createServer(connection) {
223
415
  } catch (error) {
224
416
  connection.console.error(`[${root}] analysis failed: ${error.message}`);
225
417
  }
226
- publish();
227
- await syncWatchers();
418
+ publish(settings.diagnostics ? allIssues() : []);
419
+ await generation.syncWatchers();
228
420
  }
229
421
  function schedule() {
230
422
  if (timer) clearTimeout(timer);
@@ -232,7 +424,7 @@ function createServer(connection) {
232
424
  }
233
425
  function scheduleGenerate() {
234
426
  if (generateTimer) clearTimeout(generateTimer);
235
- generateTimer = setTimeout(() => void regenerate(), 250);
427
+ generateTimer = setTimeout(() => void generation.regenerate(), 250);
236
428
  }
237
429
  connection.onInitialize((params) => {
238
430
  folders = (params.workspaceFolders ?? []).map((folder) => uriToPath(folder.uri)).filter((path) => path !== void 0);
@@ -285,162 +477,27 @@ function createServer(connection) {
285
477
  connection.onShutdown(() => dispose());
286
478
  /** The project model for a file, unless the features are turned off. */
287
479
  const modelFor = (file) => settings.enable ? modelForFile([...states.values()].map((state) => state.model), file) : void 0;
288
- /** One line of an open document, without its line break. */
289
- const lineOf = (document, line) => document.getText({
290
- start: {
291
- line,
292
- character: 0
293
- },
294
- end: {
295
- line: line + 1,
296
- character: 0
297
- }
298
- }).replace(/\r?\n$/, "");
299
- connection.onHover((params) => {
300
- const document = documents.get(params.textDocument.uri);
301
- const file = uriToPath(params.textDocument.uri);
302
- const model = document && file ? modelFor(file) : void 0;
303
- if (!document || !model) return null;
304
- const ref = findReferenceAt(lineOf(document, params.position.line), params.position.character, findBindings(document.getText()));
305
- const markdown = ref ? buildHover(model, ref) : void 0;
306
- if (!ref || !markdown) return null;
307
- return {
308
- contents: {
309
- kind: "markdown",
310
- value: markdown
311
- },
312
- range: {
313
- start: {
314
- line: params.position.line,
315
- character: ref.start
316
- },
317
- end: {
318
- line: params.position.line,
319
- character: ref.end
320
- }
321
- }
322
- };
323
- });
324
- connection.onDefinition((params) => {
325
- const document = documents.get(params.textDocument.uri);
326
- const file = uriToPath(params.textDocument.uri);
327
- const model = document && file ? modelFor(file) : void 0;
328
- if (!document || !model) return null;
329
- const ref = findReferenceAt(lineOf(document, params.position.line), params.position.character, findBindings(document.getText()));
330
- const target = ref ? definitionFor(model, ref) : void 0;
331
- if (!target) return null;
332
- return {
333
- uri: pathToFileURL(target.file).href,
334
- range: {
335
- start: {
336
- line: target.range.start.line,
337
- character: target.range.start.column
338
- },
339
- end: {
340
- line: target.range.end.line,
341
- character: target.range.end.column
342
- }
343
- }
344
- };
345
- });
346
- connection.onCompletion((params) => {
347
- const document = documents.get(params.textDocument.uri);
348
- const file = uriToPath(params.textDocument.uri);
349
- const model = document && file ? modelFor(file) : void 0;
350
- if (!document || !model) return [];
351
- const before = lineOf(document, params.position.line).slice(0, params.position.character);
352
- const context = completionContextAt(before, findBindings(document.getText()));
353
- if (!context) return [];
354
- const range = {
355
- start: {
356
- line: params.position.line,
357
- character: params.position.character - context.length
358
- },
359
- end: params.position
360
- };
361
- return completionEntries(model, context).map((entry) => ({
362
- label: entry.label,
363
- kind: COMPLETION_KINDS[entry.kind],
364
- detail: entry.detail,
365
- documentation: entry.documentation,
366
- filterText: entry.label,
367
- textEdit: {
368
- range,
369
- newText: entry.insertText
370
- }
371
- }));
372
- });
373
- connection.onCodeAction((params) => {
374
- const file = uriToPath(params.textDocument.uri);
375
- const document = documents.get(params.textDocument.uri);
376
- if (!file || !document || !settings.enable) return [];
377
- const actions = [];
378
- const text = document.getText();
379
- const model = modelFor(file);
380
- for (const diagnostic of params.context.diagnostics) {
381
- const data = diagnostic.data;
382
- if (diagnostic.source !== "dialex" || !data?.path) continue;
383
- if (data.code === "missing-key" && data.locale) {
384
- const updated = addMissingKeys(file, text, [{
385
- locale: data.locale,
386
- path: data.path,
387
- sourceLocale: data.sourceLocale
388
- }]);
389
- if (!updated) continue;
390
- actions.push({
391
- title: `Add "${data.path.join(".")}" to ${data.locale} (marked [TODO])`,
392
- kind: CodeActionKind.QuickFix,
393
- diagnostics: [diagnostic],
394
- isPreferred: true,
395
- edit: { changes: { [params.textDocument.uri]: [replaceAll(text, updated)] } }
396
- });
397
- }
398
- if (data.code === "unknown-path" && data.dictionary) {
399
- const dictionary = model?.dictionaries.get(data.dictionary);
400
- if (!dictionary) continue;
401
- const dictionaryUri = pathToFileURL(dictionary.file).href;
402
- const open = documents.get(dictionaryUri);
403
- let dictionaryText;
404
- try {
405
- dictionaryText = open ? open.getText() : fs.readFileSync(dictionary.file, "utf-8");
406
- } catch {
407
- continue;
408
- }
409
- const updated = addUnknownKey(dictionary.file, dictionaryText, data.path);
410
- if (!updated) continue;
411
- actions.push({
412
- title: `Add "${data.path.join(".")}" to the "${dictionary.name}" dictionary`,
413
- kind: CodeActionKind.QuickFix,
414
- diagnostics: [diagnostic],
415
- edit: { changes: { [dictionaryUri]: [replaceAll(dictionaryText, updated)] } }
416
- });
417
- }
418
- }
419
- const missing = [...states.values()].flatMap((state) => state.issues).filter((issue) => issue.file === file && issue.code === "missing-key").filter((issue) => issue.locale && issue.path).map((issue) => ({
420
- locale: issue.locale,
421
- path: issue.path,
422
- sourceLocale: issue.sourceLocale
423
- }));
424
- if (missing.length > 1) {
425
- const updated = addMissingKeys(file, text, missing);
426
- if (updated) actions.push({
427
- title: `Add all ${missing.length} missing keys (marked [TODO])`,
428
- kind: CodeActionKind.QuickFix,
429
- edit: { changes: { [params.textDocument.uri]: [replaceAll(text, updated)] } }
430
- });
431
- }
432
- return actions;
433
- });
480
+ /** The issues of every project, in project order. */
481
+ const allIssues = () => [...states.values()].flatMap((state) => state.issues);
482
+ const handlers = {
483
+ documents,
484
+ modelFor,
485
+ issues: allIssues,
486
+ enabled: () => settings.enable
487
+ };
488
+ connection.onHover(hoverHandler(handlers));
489
+ connection.onDefinition(definitionHandler(handlers));
490
+ connection.onCompletion(completionHandler(handlers));
491
+ connection.onCodeAction(codeActionHandler(handlers));
434
492
  documents.listen(connection);
435
493
  async function dispose() {
436
494
  if (timer) clearTimeout(timer);
437
495
  if (generateTimer) clearTimeout(generateTimer);
438
- await Promise.all([...watchers.values()].map((watcher) => watcher.close()));
439
- watchers.clear();
496
+ await generation.dispose();
440
497
  }
441
498
  return {
442
499
  refresh,
443
- regenerate,
500
+ regenerate: generation.regenerate,
444
501
  dispose
445
502
  };
446
503
  }