hledger-lsp 0.1.2 → 0.1.6

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.
Files changed (89) hide show
  1. package/README.md +91 -173
  2. package/out/features/codeActions.d.ts +16 -0
  3. package/out/features/codeActions.d.ts.map +1 -1
  4. package/out/features/codeActions.js +129 -0
  5. package/out/features/codeActions.js.map +1 -1
  6. package/out/features/codeLens.d.ts +29 -0
  7. package/out/features/codeLens.d.ts.map +1 -0
  8. package/out/features/codeLens.js +102 -0
  9. package/out/features/codeLens.js.map +1 -0
  10. package/out/features/completion.d.ts +16 -4
  11. package/out/features/completion.d.ts.map +1 -1
  12. package/out/features/completion.js +23 -10
  13. package/out/features/completion.js.map +1 -1
  14. package/out/features/definition.d.ts.map +1 -1
  15. package/out/features/definition.js +4 -4
  16. package/out/features/definition.js.map +1 -1
  17. package/out/features/foldingRanges.d.ts.map +1 -1
  18. package/out/features/foldingRanges.js +6 -2
  19. package/out/features/foldingRanges.js.map +1 -1
  20. package/out/features/formatter.d.ts +4 -20
  21. package/out/features/formatter.d.ts.map +1 -1
  22. package/out/features/formatter.js +271 -223
  23. package/out/features/formatter.js.map +1 -1
  24. package/out/features/formattingUtils.d.ts +38 -0
  25. package/out/features/formattingUtils.d.ts.map +1 -0
  26. package/out/features/formattingUtils.js +107 -0
  27. package/out/features/formattingUtils.js.map +1 -0
  28. package/out/features/hover.d.ts +0 -4
  29. package/out/features/hover.d.ts.map +1 -1
  30. package/out/features/hover.js +11 -20
  31. package/out/features/hover.js.map +1 -1
  32. package/out/features/inlayHints.d.ts +12 -6
  33. package/out/features/inlayHints.d.ts.map +1 -1
  34. package/out/features/inlayHints.js +202 -84
  35. package/out/features/inlayHints.js.map +1 -1
  36. package/out/features/semanticTokens.d.ts.map +1 -1
  37. package/out/features/semanticTokens.js +17 -14
  38. package/out/features/semanticTokens.js.map +1 -1
  39. package/out/features/symbols.d.ts.map +1 -1
  40. package/out/features/symbols.js +8 -5
  41. package/out/features/symbols.js.map +1 -1
  42. package/out/features/validator.d.ts +5 -4
  43. package/out/features/validator.d.ts.map +1 -1
  44. package/out/features/validator.js +145 -105
  45. package/out/features/validator.js.map +1 -1
  46. package/out/parser/ast.d.ts +48 -7
  47. package/out/parser/ast.d.ts.map +1 -1
  48. package/out/parser/ast.js +552 -363
  49. package/out/parser/ast.js.map +1 -1
  50. package/out/parser/document.d.ts +7 -0
  51. package/out/parser/document.d.ts.map +1 -0
  52. package/out/parser/document.js +70 -0
  53. package/out/parser/document.js.map +1 -0
  54. package/out/parser/includes.d.ts.map +1 -1
  55. package/out/parser/includes.js +21 -33
  56. package/out/parser/includes.js.map +1 -1
  57. package/out/parser/index.d.ts +8 -5
  58. package/out/parser/index.d.ts.map +1 -1
  59. package/out/parser/index.js +39 -14
  60. package/out/parser/index.js.map +1 -1
  61. package/out/server/configFile.d.ts +104 -0
  62. package/out/server/configFile.d.ts.map +1 -0
  63. package/out/server/configFile.js +231 -0
  64. package/out/server/configFile.js.map +1 -0
  65. package/out/server/settings.d.ts +13 -0
  66. package/out/server/settings.d.ts.map +1 -1
  67. package/out/server/settings.js +13 -3
  68. package/out/server/settings.js.map +1 -1
  69. package/out/server/workspace.d.ts +126 -0
  70. package/out/server/workspace.d.ts.map +1 -0
  71. package/out/server/workspace.js +599 -0
  72. package/out/server/workspace.js.map +1 -0
  73. package/out/server.js +435 -27
  74. package/out/server.js.map +1 -1
  75. package/out/types.d.ts +27 -12
  76. package/out/types.d.ts.map +1 -1
  77. package/out/types.js +12 -0
  78. package/out/types.js.map +1 -1
  79. package/out/utils/amountFormatter.d.ts +14 -0
  80. package/out/utils/amountFormatter.d.ts.map +1 -0
  81. package/out/utils/amountFormatter.js +55 -0
  82. package/out/utils/amountFormatter.js.map +1 -0
  83. package/out/utils/balanceCalculator.d.ts +32 -0
  84. package/out/utils/balanceCalculator.d.ts.map +1 -0
  85. package/out/utils/balanceCalculator.js +93 -0
  86. package/out/utils/balanceCalculator.js.map +1 -0
  87. package/out/utils/index.js +1 -1
  88. package/out/utils/index.js.map +1 -1
  89. package/package.json +1 -1
package/out/server.js CHANGED
@@ -1,5 +1,38 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
3
36
  Object.defineProperty(exports, "__esModule", { value: true });
4
37
  const node_1 = require("vscode-languageserver/node");
5
38
  const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
@@ -11,6 +44,7 @@ const codeActions_1 = require("./features/codeActions");
11
44
  const formatter_1 = require("./features/formatter");
12
45
  const semanticTokens_1 = require("./features/semanticTokens");
13
46
  const inlayHints_1 = require("./features/inlayHints");
47
+ const codeLens_1 = require("./features/codeLens");
14
48
  const findReferences_1 = require("./features/findReferences");
15
49
  const validator_1 = require("./features/validator");
16
50
  const foldingRanges_1 = require("./features/foldingRanges");
@@ -19,30 +53,61 @@ const selectionRange_1 = require("./features/selectionRange");
19
53
  const index_1 = require("./parser/index");
20
54
  const settings_1 = require("./server/settings");
21
55
  const uri_1 = require("./utils/uri");
56
+ const workspace_1 = require("./server/workspace");
57
+ const path = __importStar(require("path"));
58
+ // Check for version flag
59
+ if (process.argv.includes('--version') || process.argv.includes('-v')) {
60
+ try {
61
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
62
+ const packageJson = require('../package.json');
63
+ console.log(`hledger-lsp v${packageJson.version}`);
64
+ process.exit(0);
65
+ }
66
+ catch (error) {
67
+ console.error('Failed to read version information');
68
+ process.exit(1);
69
+ }
70
+ }
22
71
  // Create a connection for the server using Node's IPC as a transport
23
72
  const connection = (0, node_1.createConnection)(node_1.ProposedFeatures.all);
73
+ connection.console.log('========== HLEDGER LSP SERVER STARTING ==========');
24
74
  // Create a simple text document manager
25
75
  const documents = new node_1.TextDocuments(vscode_languageserver_textdocument_1.TextDocument);
26
76
  let hasConfigurationCapability = false;
27
- let hasWorkspaceFolderCapability = false;
28
- let hasDiagnosticRelatedInformationCapability = false;
29
77
  let hasDidChangeConfigurationDynamicRegistration = false;
30
- let hasWorkspaceFoldersDynamicRegistration = false;
78
+ let hasInlayHintRefreshSupport = false;
79
+ let hasCodeLensRefreshSupport = false;
80
+ // Workspace state
81
+ let workspaceFolders = [];
82
+ let workspaceManager = null;
31
83
  connection.onInitialize((params) => {
84
+ connection.console.log('========== ON INITIALIZE CALLED ==========');
85
+ connection.console.log(`rootUri: ${params.rootUri}`);
86
+ connection.console.log(`rootPath: ${params.rootPath}`);
87
+ connection.console.log(`workspaceFolders: ${JSON.stringify(params.workspaceFolders)}`);
32
88
  const capabilities = params.capabilities;
33
89
  // Does the client support the `workspace/configuration` request?
34
90
  hasConfigurationCapability = !!(capabilities.workspace && !!capabilities.workspace.configuration);
35
- hasWorkspaceFolderCapability = !!(capabilities.workspace && !!capabilities.workspace.workspaceFolders);
36
- hasDiagnosticRelatedInformationCapability = !!(capabilities.textDocument &&
37
- capabilities.textDocument.publishDiagnostics &&
38
- capabilities.textDocument.publishDiagnostics.relatedInformation);
39
91
  // Check if client supports dynamic registration for didChangeConfiguration
40
92
  hasDidChangeConfigurationDynamicRegistration = !!(capabilities.workspace &&
41
93
  capabilities.workspace.didChangeConfiguration &&
42
94
  capabilities.workspace.didChangeConfiguration.dynamicRegistration);
43
- // Check if client supports dynamic registration for workspace folders
44
- // (Not currently used, kept for future reference)
45
- hasWorkspaceFoldersDynamicRegistration = false;
95
+ // Check if client supports inlay hint refresh
96
+ hasInlayHintRefreshSupport = !!(capabilities.workspace &&
97
+ capabilities.workspace.inlayHint &&
98
+ capabilities.workspace.inlayHint.refreshSupport);
99
+ hasCodeLensRefreshSupport = !!(capabilities.workspace &&
100
+ capabilities.workspace.codeLens &&
101
+ capabilities.workspace.codeLens.refreshSupport);
102
+ connection.console.log(`Inlay hint refresh support: ${hasInlayHintRefreshSupport}`);
103
+ connection.console.log(`Code lens refresh support: ${hasCodeLensRefreshSupport}`);
104
+ // Store workspace folders
105
+ if (params.workspaceFolders && params.workspaceFolders.length > 0) {
106
+ workspaceFolders = params.workspaceFolders.map(folder => folder.uri);
107
+ }
108
+ else if (params.rootUri) {
109
+ workspaceFolders = [params.rootUri];
110
+ }
46
111
  const result = {
47
112
  capabilities: {
48
113
  textDocumentSync: node_1.TextDocumentSyncKind.Incremental,
@@ -70,6 +135,9 @@ connection.onInitialize((params) => {
70
135
  full: true
71
136
  },
72
137
  inlayHintProvider: true,
138
+ codeLensProvider: {
139
+ resolveProvider: false
140
+ },
73
141
  renameProvider: {
74
142
  prepareProvider: true
75
143
  },
@@ -77,21 +145,89 @@ connection.onInitialize((params) => {
77
145
  documentLinkProvider: {
78
146
  resolveProvider: false
79
147
  },
80
- selectionRangeProvider: true
148
+ selectionRangeProvider: true,
149
+ executeCommandProvider: {
150
+ commands: [
151
+ 'hledger.addBalanceAssertion',
152
+ 'hledger.insertBalanceAssertion',
153
+ 'hledger.insertInferredAmount',
154
+ 'hledger.convertToTotalCost',
155
+ 'hledger.refreshInlayHints',
156
+ 'hledger.showWorkspaceGraph',
157
+ 'hledger.showWorkspaceGraphStructured'
158
+ ]
159
+ }
81
160
  }
82
161
  };
83
162
  // Note: Workspace folders support removed to avoid warnings with clients
84
163
  // that don't support dynamic registration (like Neovim)
164
+ connection.console.log('========== ON INITIALIZE COMPLETE ==========');
165
+ connection.console.log(`workspaceFolders array: ${JSON.stringify(workspaceFolders)}`);
85
166
  return result;
86
167
  });
87
- connection.onInitialized(() => {
168
+ connection.onInitialized(async () => {
169
+ connection.console.log('========== ON INITIALIZED CALLED ==========');
88
170
  // Only use dynamic registration if the client supports it
89
171
  if (hasConfigurationCapability && hasDidChangeConfigurationDynamicRegistration) {
90
172
  // Register for all configuration changes dynamically
91
173
  connection.client.register(node_1.DidChangeConfigurationNotification.type, undefined);
92
174
  }
93
- connection.console.log('hledger Language Server initialized');
175
+ // Initialize WorkspaceManager if we have workspace folders
176
+ if (workspaceFolders.length > 0) {
177
+ await initializeWorkspaceManager(workspaceFolders);
178
+ // Watch for config file changes
179
+ // Note: Many clients (including Neovim LSP) don't support dynamic file watchers,
180
+ // so we rely on onDidChangeWatchedFiles being called for any file changes
181
+ // If the client supports it, the workspace/configuration already watches files
182
+ }
183
+ else {
184
+ connection.console.log('hledger Language Server initialized (no workspace folders provided)');
185
+ connection.console.log('WorkspaceManager will be initialized when first document is opened');
186
+ }
94
187
  });
188
+ // Helper function to initialize workspace manager
189
+ async function initializeWorkspaceManager(folders, forceReinit = false) {
190
+ if (workspaceManager && !forceReinit) {
191
+ connection.console.log('WorkspaceManager already initialized');
192
+ return;
193
+ }
194
+ // Get workspace settings to pass as runtime config
195
+ const settings = await getDocumentSettings(folders[0]);
196
+ const runtimeConfig = settings?.workspace ? {
197
+ workspace: settings.workspace
198
+ } : undefined;
199
+ workspaceManager = new workspace_1.WorkspaceManager();
200
+ try {
201
+ connection.console.log(`Initializing WorkspaceManager with folders: ${folders.join(', ')}`);
202
+ await workspaceManager.initialize(folders, sharedParser, fileReader, connection, runtimeConfig);
203
+ // Log the root file being used
204
+ const diagnostics = workspaceManager.getDiagnosticInfo();
205
+ if (diagnostics.rootFile) {
206
+ connection.console.info(`✓ Workspace root file: ${diagnostics.rootFile}`);
207
+ }
208
+ else {
209
+ connection.console.warn(`⚠ No workspace root file detected - workspace features disabled`);
210
+ }
211
+ connection.console.log('hledger Language Server initialized with workspace awareness');
212
+ // Refresh all open documents now that workspace context is available
213
+ // This ensures features like inlay hints and diagnostics reflect the full workspace tree
214
+ connection.console.log('Refreshing open documents with workspace context...');
215
+ const openDocuments = documents.all();
216
+ for (const doc of openDocuments) {
217
+ // Re-validate with full workspace context
218
+ await validateTextDocument(doc);
219
+ }
220
+ // Refresh inlay hints for all open documents
221
+ if (hasInlayHintRefreshSupport) {
222
+ connection.languages.inlayHint.refresh();
223
+ }
224
+ connection.console.log(`Refreshed ${openDocuments.length} open document(s) with workspace context`);
225
+ }
226
+ catch (error) {
227
+ connection.console.error(`Failed to initialize WorkspaceManager: ${error}`);
228
+ workspaceManager = null;
229
+ }
230
+ }
95
231
  // Global settings used when client does not support workspace/configuration
96
232
  let globalSettings = settings_1.defaultSettings;
97
233
  // include dependency functions are centralized in src/server/deps.ts
@@ -99,7 +235,23 @@ const deps_1 = require("./server/deps");
99
235
  // Create a shared parser instance with caching
100
236
  const sharedParser = new index_1.HledgerParser();
101
237
  // Small helper to centralize parsing options and reduce duplication across handlers
102
- function parseDocument(document) {
238
+ function parseDocument(document, options) {
239
+ const mode = options?.parseMode || 'document';
240
+ // Workspace mode: parse from root file for global state
241
+ if (mode === 'workspace' && workspaceManager) {
242
+ const root = workspaceManager.getRootForFile(document.uri);
243
+ if (root) {
244
+ connection.console.info(`[parseDocument] Workspace mode: using root ${root}`);
245
+ const parsed = workspaceManager.parseWorkspace();
246
+ if (parsed) {
247
+ return parsed;
248
+ }
249
+ }
250
+ // Fallback to document mode if no root found (normal during initialization)
251
+ connection.console.info(`[parseDocument] No root found yet for ${document.uri}, using document mode`);
252
+ }
253
+ // Document mode: standard include-based parsing
254
+ connection.console.info(`[parseDocument] Document mode for ${document.uri}`);
103
255
  return sharedParser.parse(document, {
104
256
  baseUri: document.uri,
105
257
  fileReader
@@ -122,6 +274,23 @@ connection.onDidChangeConfiguration(change => {
122
274
  function getDocumentSettings(resource) {
123
275
  return (0, settings_1.getDocumentSettings)(connection, resource, hasConfigurationCapability);
124
276
  }
277
+ // Lazy initialize workspace manager when first document is opened
278
+ documents.onDidOpen(async (e) => {
279
+ connection.console.log('========== DOCUMENT OPENED ==========');
280
+ connection.console.log(`Document URI: ${e.document.uri}`);
281
+ connection.console.log(`workspaceManager exists: ${!!workspaceManager}`);
282
+ connection.console.log(`workspaceFolders.length: ${workspaceFolders.length}`);
283
+ // If workspace manager not initialized and we don't have workspace folders,
284
+ // use the directory of the opened document as the workspace
285
+ if (!workspaceManager && workspaceFolders.length === 0) {
286
+ const uri = e.document.uri;
287
+ const filePath = (0, uri_1.toFilePath)(uri);
288
+ const dirPath = path.dirname(filePath);
289
+ const workspaceUri = (0, uri_1.toFileUri)(dirPath);
290
+ connection.console.log(`Lazy-initializing WorkspaceManager with directory: ${workspaceUri}`);
291
+ await initializeWorkspaceManager([workspaceUri]);
292
+ }
293
+ });
125
294
  // Only keep settings for open documents
126
295
  documents.onDidClose(e => {
127
296
  (0, settings_1.clearDocumentSettings)(e.document.uri);
@@ -132,6 +301,10 @@ documents.onDidChangeContent(change => {
132
301
  // Clear parser cache since a file changed
133
302
  // This ensures we re-parse files with fresh data
134
303
  sharedParser.clearCache();
304
+ // Invalidate workspace cache for affected roots
305
+ if (workspaceManager) {
306
+ workspaceManager.invalidateFile(change.document.uri);
307
+ }
135
308
  validateTextDocument(change.document);
136
309
  // Re-validate all files that depend on this one
137
310
  const dependents = (0, deps_1.getDependents)(change.document.uri);
@@ -143,15 +316,70 @@ documents.onDidChangeContent(change => {
143
316
  }
144
317
  }
145
318
  }
319
+ // In workspace mode, changes to one file affect all files in the workspace
320
+ // (e.g., running balances, transaction counts, completions)
321
+ // Refresh workspace-wide features for all open documents
322
+ if (workspaceManager) {
323
+ const changedRoot = workspaceManager.getRootForFile(change.document.uri);
324
+ if (changedRoot) {
325
+ // Find all open documents that share the same root
326
+ const allDocs = documents.all();
327
+ const affectedDocs = allDocs.filter(doc => {
328
+ const docRoot = workspaceManager.getRootForFile(doc.uri);
329
+ const isSameRoot = docRoot === changedRoot;
330
+ const isDifferentFile = doc.uri !== change.document.uri;
331
+ return isSameRoot && isDifferentFile;
332
+ });
333
+ // Re-validate affected documents (updates diagnostics)
334
+ for (const doc of affectedDocs) {
335
+ validateTextDocument(doc);
336
+ }
337
+ // Refresh inlay hints for all affected documents
338
+ // This updates running balances, inferred amounts, etc.
339
+ if (hasInlayHintRefreshSupport && affectedDocs.length > 0) {
340
+ connection.console.log(`[Cascade Refresh] Refreshing inlay hints for ${affectedDocs.length} affected document(s)`);
341
+ connection.languages.inlayHint.refresh();
342
+ }
343
+ else if (affectedDocs.length > 0) {
344
+ connection.console.warn(`[Cascade Refresh] Cannot refresh inlay hints - client does not support refresh (${affectedDocs.length} affected docs)`);
345
+ }
346
+ // Note: Code lenses will refresh when the client next requests them
347
+ // We've already invalidated the workspace cache above, which is sufficient
348
+ }
349
+ }
350
+ });
351
+ // Watch for config file changes (.hledger-lsp.json)
352
+ connection.onDidChangeWatchedFiles(async (params) => {
353
+ for (const change of params.changes) {
354
+ if (change.uri.endsWith('.hledger-lsp.json')) {
355
+ connection.console.log(`Config file changed: ${change.uri}, reinitializing workspace`);
356
+ // Reinitialize workspace manager with new config
357
+ if (workspaceFolders.length > 0) {
358
+ await initializeWorkspaceManager(workspaceFolders, true);
359
+ // Revalidate all open documents with new configuration
360
+ documents.all().forEach(validateTextDocument);
361
+ }
362
+ break; // Only reinitialize once even if multiple config files changed
363
+ }
364
+ }
146
365
  });
147
366
  // dependency tracking moved to src/server/deps.ts
148
- // Use centralized file reader implementation
149
- const fileReader = uri_1.defaultFileReader;
367
+ // Create a file reader that uses in-memory documents when available
368
+ // This ensures we see unsaved changes in the editor
369
+ const fileReader = (uri) => {
370
+ // First, check if we have this document open in memory
371
+ const openDoc = documents.get(uri);
372
+ if (openDoc) {
373
+ return openDoc;
374
+ }
375
+ // Fall back to reading from disk
376
+ return (0, uri_1.defaultFileReader)(uri);
377
+ };
150
378
  async function validateTextDocument(textDocument) {
151
379
  // Get document settings
152
380
  const settings = (await getDocumentSettings(textDocument.uri)) ?? settings_1.defaultSettings;
153
- // Parse the document with includes enabled (uses shared parser with caching)
154
- const parsedDoc = parseDocument(textDocument);
381
+ // Validation needs workspace state for balance assertions and full transaction history
382
+ const parsedDoc = parseDocument(textDocument, { parseMode: 'workspace' });
155
383
  // Track which files this document includes
156
384
  const includedFiles = new Set();
157
385
  for (const directive of parsedDoc.directives) {
@@ -161,11 +389,11 @@ async function validateTextDocument(textDocument) {
161
389
  }
162
390
  }
163
391
  (0, deps_1.updateDependencies)(textDocument.uri, includedFiles);
164
- // Update completion data from the parsed document (includes all included files)
165
- completion_1.completionProvider.updateAccounts(parsedDoc.accounts.map(a => ({ name: a.name, declared: a.declared })));
166
- completion_1.completionProvider.updatePayees(parsedDoc.payees.map(p => ({ name: p.name, declared: p.declared })));
167
- completion_1.completionProvider.updateCommodities(parsedDoc.commodities.map(c => ({ name: c.name, declared: c.declared })));
168
- completion_1.completionProvider.updateTags(parsedDoc.tags.map(t => ({ name: t.name, declared: t.declared })));
392
+ // Update completion data from the parsed document (includes all workspace files)
393
+ completion_1.completionProvider.updateAccounts(parsedDoc.accounts);
394
+ completion_1.completionProvider.updatePayees(parsedDoc.payees);
395
+ completion_1.completionProvider.updateCommodities(parsedDoc.commodities);
396
+ completion_1.completionProvider.updateTags(parsedDoc.tags);
169
397
  // Validate the document with settings
170
398
  const validationResult = validator_1.validator.validate(textDocument, parsedDoc, {
171
399
  baseUri: textDocument.uri,
@@ -189,8 +417,8 @@ connection.onCompletion(async (textDocumentPosition) => {
189
417
  if (!document) {
190
418
  return [];
191
419
  }
192
- // Parse document for smart completions
193
- const parsed = parseDocument(document);
420
+ // Completion needs workspace-wide accounts/payees/commodities for accurate suggestions
421
+ const parsed = parseDocument(document, { parseMode: 'workspace' });
194
422
  // Get settings for completion filtering
195
423
  const settings = await (0, settings_1.getDocumentSettings)(connection, textDocumentPosition.textDocument.uri, hasConfigurationCapability);
196
424
  return completion_1.completionProvider.getCompletionItems(document, textDocumentPosition.position, parsed, settings?.completion);
@@ -337,11 +565,13 @@ connection.languages.inlayHint.on(async (params) => {
337
565
  const document = documents.get(params.textDocument.uri);
338
566
  if (!document)
339
567
  return [];
340
- // Parse document
341
- const parsed = parseDocument(document);
342
568
  // Get settings for inlay hints
343
569
  const settings = await getDocumentSettings(params.textDocument.uri);
344
570
  const inlayHintsSettings = settings?.inlayHints || undefined;
571
+ // Use workspace mode for running balances (needs all transactions chronologically)
572
+ // Use document mode for inferred amounts (single-file context sufficient)
573
+ const parseMode = inlayHintsSettings?.showRunningBalances ? 'workspace' : 'document';
574
+ const parsed = parseDocument(document, { parseMode });
345
575
  return inlayHints_1.inlayHintsProvider.provideInlayHints(document, params.range, parsed, inlayHintsSettings);
346
576
  }
347
577
  catch (error) {
@@ -349,6 +579,24 @@ connection.languages.inlayHint.on(async (params) => {
349
579
  return [];
350
580
  }
351
581
  });
582
+ // Provide code lenses
583
+ connection.onCodeLens(async (params) => {
584
+ try {
585
+ const document = documents.get(params.textDocument.uri);
586
+ if (!document)
587
+ return [];
588
+ // CodeLens always needs workspace state for accurate transaction counts and balances
589
+ const parsed = parseDocument(document, { parseMode: 'workspace' });
590
+ // Get settings for code lens
591
+ const settings = await getDocumentSettings(params.textDocument.uri);
592
+ const codeLensSettings = settings?.codeLens || undefined;
593
+ return codeLens_1.codeLensProvider.provideCodeLenses(document, parsed, codeLensSettings);
594
+ }
595
+ catch (error) {
596
+ connection.console.error(`Error providing code lenses: ${error}`);
597
+ return [];
598
+ }
599
+ });
352
600
  // Prepare rename - validate that position is on a renameable item
353
601
  connection.onPrepareRename((params) => {
354
602
  const document = documents.get(params.textDocument.uri);
@@ -413,8 +661,168 @@ connection.onSelectionRanges((params) => {
413
661
  const parsed = parseDocument(document);
414
662
  return selectionRange_1.selectionRangeProvider.provideSelectionRanges(document, params.positions, parsed) || [];
415
663
  });
664
+ // Handle command execution
665
+ connection.onExecuteCommand(async (params) => {
666
+ connection.console.log(`[ExecuteCommand] ${params.command}`);
667
+ if (params.command === 'hledger.addBalanceAssertion' || params.command === 'hledger.insertBalanceAssertion') {
668
+ const [uri, line, account, amounts] = params.arguments;
669
+ const document = documents.get(uri);
670
+ if (!document) {
671
+ return;
672
+ }
673
+ // Get the line text
674
+ const lineText = document.getText({
675
+ start: { line, character: 0 },
676
+ end: { line, character: Number.MAX_SAFE_INTEGER }
677
+ });
678
+ // Find where to insert the assertion
679
+ // Look for the end of the amount, or end of the account if no amount
680
+ // Skip leading whitespace and account name
681
+ let insertPos = lineText.length;
682
+ // Find the end of non-comment, non-whitespace content
683
+ const commentMatch = lineText.match(/\s*[;#]/);
684
+ if (commentMatch && commentMatch.index !== undefined) {
685
+ insertPos = commentMatch.index;
686
+ }
687
+ else {
688
+ // Trim trailing whitespace
689
+ insertPos = lineText.trimEnd().length;
690
+ }
691
+ // Build the assertion text
692
+ const assertionText = ` = ${amounts.join(', ')}`;
693
+ // Create and apply the edit
694
+ await connection.workspace.applyEdit({
695
+ changes: {
696
+ [uri]: [{
697
+ range: {
698
+ start: { line, character: insertPos },
699
+ end: { line, character: insertPos }
700
+ },
701
+ newText: assertionText
702
+ }]
703
+ }
704
+ });
705
+ }
706
+ else if (params.command === 'hledger.insertInferredAmount') {
707
+ const [uri, line, account, amountText] = params.arguments;
708
+ const document = documents.get(uri);
709
+ if (!document) {
710
+ return;
711
+ }
712
+ // Get the line text
713
+ const lineText = document.getText({
714
+ start: { line, character: 0 },
715
+ end: { line, character: Number.MAX_SAFE_INTEGER }
716
+ });
717
+ // Find where to insert the amount - after the account name
718
+ // Skip leading whitespace and find account name
719
+ const accountPos = lineText.indexOf(account);
720
+ if (accountPos === -1) {
721
+ return;
722
+ }
723
+ const insertPos = accountPos + account.length;
724
+ // Build the amount text with proper spacing
725
+ const insertText = ` ${amountText}`;
726
+ // Create and apply the edit
727
+ await connection.workspace.applyEdit({
728
+ changes: {
729
+ [uri]: [{
730
+ range: {
731
+ start: { line, character: insertPos },
732
+ end: { line, character: insertPos }
733
+ },
734
+ newText: insertText
735
+ }]
736
+ }
737
+ });
738
+ }
739
+ else if (params.command === 'hledger.convertToTotalCost') {
740
+ const [uri, line, account, totalCostText] = params.arguments;
741
+ const document = documents.get(uri);
742
+ if (!document) {
743
+ return;
744
+ }
745
+ // Get the line text
746
+ const lineText = document.getText({
747
+ start: { line, character: 0 },
748
+ end: { line, character: Number.MAX_SAFE_INTEGER }
749
+ });
750
+ // Find the unit cost notation (@ but not @@)
751
+ // Match @ followed by non-@ character
752
+ const unitCostMatch = lineText.match(/@(?!@)\s*[^;#\s]+/);
753
+ if (!unitCostMatch || unitCostMatch.index === undefined) {
754
+ return;
755
+ }
756
+ const startPos = unitCostMatch.index;
757
+ const endPos = startPos + unitCostMatch[0].length;
758
+ // Replace with total cost notation
759
+ const newText = `@@ ${totalCostText}`;
760
+ // Create and apply the edit
761
+ await connection.workspace.applyEdit({
762
+ changes: {
763
+ [uri]: [{
764
+ range: {
765
+ start: { line, character: startPos },
766
+ end: { line, character: endPos }
767
+ },
768
+ newText: newText
769
+ }]
770
+ }
771
+ });
772
+ }
773
+ else if (params.command === 'hledger.refreshInlayHints') {
774
+ // Manually refresh inlay hints
775
+ if (hasInlayHintRefreshSupport) {
776
+ await connection.languages.inlayHint.refresh().catch((err) => {
777
+ connection.console.error(`Failed to refresh inlay hints: ${err}`);
778
+ });
779
+ }
780
+ else {
781
+ connection.window.showInformationMessage('Inlay hint refresh not supported by your editor');
782
+ }
783
+ }
784
+ else if (params.command === 'hledger.showWorkspaceGraph') {
785
+ connection.console.log(`[ExecuteCommand] showWorkspaceGraph called. workspaceManager exists: ${!!workspaceManager}`);
786
+ if (workspaceManager) {
787
+ try {
788
+ const graph = workspaceManager.getWorkspaceTree();
789
+ connection.console.log(`[ExecuteCommand] Tree generated, length: ${graph.length}`);
790
+ return graph;
791
+ }
792
+ catch (error) {
793
+ connection.console.error(`[ExecuteCommand] Error generating tree: ${error}`);
794
+ return `Error generating tree: ${error}`;
795
+ }
796
+ }
797
+ else {
798
+ connection.console.warn('[ExecuteCommand] Workspace manager not initialized');
799
+ connection.window.showErrorMessage('Workspace manager not initialized');
800
+ return 'Error: Workspace manager not initialized. Check LSP logs.';
801
+ }
802
+ }
803
+ else if (params.command === 'hledger.showWorkspaceGraphStructured') {
804
+ connection.console.log(`[ExecuteCommand] showWorkspaceGraphStructured called. workspaceManager exists: ${!!workspaceManager}`);
805
+ if (workspaceManager) {
806
+ try {
807
+ const entries = workspaceManager.getWorkspaceTreeStructured();
808
+ connection.console.log(`[ExecuteCommand] Structured tree generated, ${entries.length} entries`);
809
+ return entries;
810
+ }
811
+ catch (error) {
812
+ connection.console.error(`[ExecuteCommand] Error generating structured tree: ${error}`);
813
+ return [];
814
+ }
815
+ }
816
+ else {
817
+ connection.console.warn('[ExecuteCommand] Workspace manager not initialized');
818
+ connection.window.showErrorMessage('Workspace manager not initialized');
819
+ return [];
820
+ }
821
+ }
822
+ });
416
823
  // Make the text document manager listen on the connection
417
824
  documents.listen(connection);
418
825
  // Listen on the connection
419
826
  connection.listen();
827
+ connection.console.log('========== HLEDGER LSP SERVER LISTENING ==========');
420
828
  //# sourceMappingURL=server.js.map