hledger-lsp 0.1.5 → 0.1.7

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 (81) hide show
  1. package/README.md +87 -167
  2. package/out/features/codeActions.d.ts +0 -4
  3. package/out/features/codeActions.d.ts.map +1 -1
  4. package/out/features/codeActions.js +11 -25
  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 +105 -0
  9. package/out/features/codeLens.js.map +1 -0
  10. package/out/features/completion.d.ts.map +1 -1
  11. package/out/features/completion.js +21 -9
  12. package/out/features/completion.js.map +1 -1
  13. package/out/features/definition.d.ts.map +1 -1
  14. package/out/features/definition.js.map +1 -1
  15. package/out/features/foldingRanges.d.ts.map +1 -1
  16. package/out/features/foldingRanges.js +9 -2
  17. package/out/features/foldingRanges.js.map +1 -1
  18. package/out/features/formatter.d.ts +2 -0
  19. package/out/features/formatter.d.ts.map +1 -1
  20. package/out/features/formatter.js +36 -7
  21. package/out/features/formatter.js.map +1 -1
  22. package/out/features/formattingUtils.d.ts +38 -0
  23. package/out/features/formattingUtils.d.ts.map +1 -0
  24. package/out/features/formattingUtils.js +107 -0
  25. package/out/features/formattingUtils.js.map +1 -0
  26. package/out/features/hover.d.ts +2 -5
  27. package/out/features/hover.d.ts.map +1 -1
  28. package/out/features/hover.js +60 -36
  29. package/out/features/hover.js.map +1 -1
  30. package/out/features/inlayHints.d.ts +7 -5
  31. package/out/features/inlayHints.d.ts.map +1 -1
  32. package/out/features/inlayHints.js +164 -91
  33. package/out/features/inlayHints.js.map +1 -1
  34. package/out/features/semanticTokens.d.ts.map +1 -1
  35. package/out/features/semanticTokens.js +22 -1
  36. package/out/features/semanticTokens.js.map +1 -1
  37. package/out/features/symbols.d.ts.map +1 -1
  38. package/out/features/symbols.js +9 -3
  39. package/out/features/symbols.js.map +1 -1
  40. package/out/features/validator.d.ts +0 -8
  41. package/out/features/validator.d.ts.map +1 -1
  42. package/out/features/validator.js +367 -171
  43. package/out/features/validator.js.map +1 -1
  44. package/out/parser/ast.d.ts +2 -2
  45. package/out/parser/ast.d.ts.map +1 -1
  46. package/out/parser/ast.js +10 -5
  47. package/out/parser/ast.js.map +1 -1
  48. package/out/parser/index.d.ts +8 -5
  49. package/out/parser/index.d.ts.map +1 -1
  50. package/out/parser/index.js +0 -7
  51. package/out/parser/index.js.map +1 -1
  52. package/out/server/configFile.d.ts +104 -0
  53. package/out/server/configFile.d.ts.map +1 -0
  54. package/out/server/configFile.js +231 -0
  55. package/out/server/configFile.js.map +1 -0
  56. package/out/server/settings.d.ts +9 -0
  57. package/out/server/settings.d.ts.map +1 -1
  58. package/out/server/settings.js +9 -0
  59. package/out/server/settings.js.map +1 -1
  60. package/out/server/workspace.d.ts +126 -0
  61. package/out/server/workspace.d.ts.map +1 -0
  62. package/out/server/workspace.js +605 -0
  63. package/out/server/workspace.js.map +1 -0
  64. package/out/server.js +438 -29
  65. package/out/server.js.map +1 -1
  66. package/out/types.d.ts +15 -4
  67. package/out/types.d.ts.map +1 -1
  68. package/out/types.js +11 -0
  69. package/out/types.js.map +1 -1
  70. package/out/utils/amountFormatter.d.ts +26 -0
  71. package/out/utils/amountFormatter.d.ts.map +1 -0
  72. package/out/utils/amountFormatter.js +91 -0
  73. package/out/utils/amountFormatter.js.map +1 -0
  74. package/out/utils/balanceCalculator.d.ts +32 -0
  75. package/out/utils/balanceCalculator.d.ts.map +1 -0
  76. package/out/utils/balanceCalculator.js +93 -0
  77. package/out/utils/balanceCalculator.js.map +1 -0
  78. package/out/utils/index.d.ts.map +1 -1
  79. package/out/utils/index.js +2 -1
  80. package/out/utils/index.js.map +1 -1
  81. 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,7 +389,7 @@ 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)
392
+ // Update completion data from the parsed document (includes all workspace files)
165
393
  completion_1.completionProvider.updateAccounts(parsedDoc.accounts);
166
394
  completion_1.completionProvider.updatePayees(parsedDoc.payees);
167
395
  completion_1.completionProvider.updateCommodities(parsedDoc.commodities);
@@ -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);
@@ -207,12 +435,13 @@ connection.onCompletionResolve((item) => {
207
435
  return item;
208
436
  });
209
437
  // Provide hover information
210
- connection.onHover((params, token) => {
438
+ connection.onHover(async (params, token) => {
211
439
  const document = documents.get(params.textDocument.uri);
212
440
  if (!document)
213
441
  return null;
214
- const parsed = parseDocument(document);
215
- const hover = hover_1.hoverProvider.provideHover(document, params.position.line, params.position.character, parsed);
442
+ const parsed = parseDocument(document, { parseMode: 'workspace' });
443
+ const settings = await getDocumentSettings(params.textDocument.uri);
444
+ const hover = hover_1.hoverProvider.provideHover(document, params.position.line, params.position.character, parsed, settings);
216
445
  return hover;
217
446
  });
218
447
  // Provide definition locations (go-to-definition)
@@ -221,7 +450,7 @@ connection.onDefinition((params) => {
221
450
  if (!document)
222
451
  return null;
223
452
  // Parse document with includes using server's fileReader
224
- const parsed = parseDocument(document);
453
+ const parsed = parseDocument(document, { parseMode: 'workspace' });
225
454
  const loc = definition_1.definitionProvider.provideDefinition(document, params.position.line, params.position.character, parsed);
226
455
  return loc ? [loc] : null;
227
456
  });
@@ -231,7 +460,7 @@ connection.onReferences((params) => {
231
460
  if (!document)
232
461
  return null;
233
462
  // Parse document with includes using server's fileReader
234
- const parsed = parseDocument(document);
463
+ const parsed = parseDocument(document, { parseMode: 'workspace' });
235
464
  return findReferences_1.findReferencesProvider.findReferences(document, params.position, parsed, params.context.includeDeclaration);
236
465
  });
237
466
  // Provide document symbols (outline view)
@@ -337,18 +566,38 @@ connection.languages.inlayHint.on(async (params) => {
337
566
  const document = documents.get(params.textDocument.uri);
338
567
  if (!document)
339
568
  return [];
340
- // Parse document
341
- const parsed = parseDocument(document);
342
569
  // Get settings for inlay hints
343
570
  const settings = await getDocumentSettings(params.textDocument.uri);
344
571
  const inlayHintsSettings = settings?.inlayHints || undefined;
345
- return inlayHints_1.inlayHintsProvider.provideInlayHints(document, params.range, parsed, inlayHintsSettings);
572
+ // Use workspace mode for running balances (needs all transactions chronologically)
573
+ // Use document mode for inferred amounts (single-file context sufficient)
574
+ const parseMode = inlayHintsSettings?.showRunningBalances ? 'workspace' : 'document';
575
+ const parsed = parseDocument(document, { parseMode });
576
+ return inlayHints_1.inlayHintsProvider.provideInlayHints(document, params.range, parsed, settings);
346
577
  }
347
578
  catch (error) {
348
579
  connection.console.error(`Error providing inlay hints: ${error}`);
349
580
  return [];
350
581
  }
351
582
  });
583
+ // Provide code lenses
584
+ connection.onCodeLens(async (params) => {
585
+ try {
586
+ const document = documents.get(params.textDocument.uri);
587
+ if (!document)
588
+ return [];
589
+ // CodeLens always needs workspace state for accurate transaction counts and balances
590
+ const parsed = parseDocument(document, { parseMode: 'workspace' });
591
+ // Get settings for code lens
592
+ const settings = await getDocumentSettings(params.textDocument.uri);
593
+ const codeLensSettings = settings?.codeLens || undefined;
594
+ return codeLens_1.codeLensProvider.provideCodeLenses(document, parsed, codeLensSettings);
595
+ }
596
+ catch (error) {
597
+ connection.console.error(`Error providing code lenses: ${error}`);
598
+ return [];
599
+ }
600
+ });
352
601
  // Prepare rename - validate that position is on a renameable item
353
602
  connection.onPrepareRename((params) => {
354
603
  const document = documents.get(params.textDocument.uri);
@@ -413,8 +662,168 @@ connection.onSelectionRanges((params) => {
413
662
  const parsed = parseDocument(document);
414
663
  return selectionRange_1.selectionRangeProvider.provideSelectionRanges(document, params.positions, parsed) || [];
415
664
  });
665
+ // Handle command execution
666
+ connection.onExecuteCommand(async (params) => {
667
+ connection.console.log(`[ExecuteCommand] ${params.command}`);
668
+ if (params.command === 'hledger.addBalanceAssertion' || params.command === 'hledger.insertBalanceAssertion') {
669
+ const [uri, line, account, amounts] = params.arguments;
670
+ const document = documents.get(uri);
671
+ if (!document) {
672
+ return;
673
+ }
674
+ // Get the line text
675
+ const lineText = document.getText({
676
+ start: { line, character: 0 },
677
+ end: { line, character: Number.MAX_SAFE_INTEGER }
678
+ });
679
+ // Find where to insert the assertion
680
+ // Look for the end of the amount, or end of the account if no amount
681
+ // Skip leading whitespace and account name
682
+ let insertPos = lineText.length;
683
+ // Find the end of non-comment, non-whitespace content
684
+ const commentMatch = lineText.match(/\s*[;#]/);
685
+ if (commentMatch && commentMatch.index !== undefined) {
686
+ insertPos = commentMatch.index;
687
+ }
688
+ else {
689
+ // Trim trailing whitespace
690
+ insertPos = lineText.trimEnd().length;
691
+ }
692
+ // Build the assertion text
693
+ const assertionText = ` = ${amounts.join(', ')}`;
694
+ // Create and apply the edit
695
+ await connection.workspace.applyEdit({
696
+ changes: {
697
+ [uri]: [{
698
+ range: {
699
+ start: { line, character: insertPos },
700
+ end: { line, character: insertPos }
701
+ },
702
+ newText: assertionText
703
+ }]
704
+ }
705
+ });
706
+ }
707
+ else if (params.command === 'hledger.insertInferredAmount') {
708
+ const [uri, line, account, amountText] = params.arguments;
709
+ const document = documents.get(uri);
710
+ if (!document) {
711
+ return;
712
+ }
713
+ // Get the line text
714
+ const lineText = document.getText({
715
+ start: { line, character: 0 },
716
+ end: { line, character: Number.MAX_SAFE_INTEGER }
717
+ });
718
+ // Find where to insert the amount - after the account name
719
+ // Skip leading whitespace and find account name
720
+ const accountPos = lineText.indexOf(account);
721
+ if (accountPos === -1) {
722
+ return;
723
+ }
724
+ const insertPos = accountPos + account.length;
725
+ // Build the amount text with proper spacing
726
+ const insertText = ` ${amountText}`;
727
+ // Create and apply the edit
728
+ await connection.workspace.applyEdit({
729
+ changes: {
730
+ [uri]: [{
731
+ range: {
732
+ start: { line, character: insertPos },
733
+ end: { line, character: insertPos }
734
+ },
735
+ newText: insertText
736
+ }]
737
+ }
738
+ });
739
+ }
740
+ else if (params.command === 'hledger.convertToTotalCost') {
741
+ const [uri, line, account, totalCostText] = params.arguments;
742
+ const document = documents.get(uri);
743
+ if (!document) {
744
+ return;
745
+ }
746
+ // Get the line text
747
+ const lineText = document.getText({
748
+ start: { line, character: 0 },
749
+ end: { line, character: Number.MAX_SAFE_INTEGER }
750
+ });
751
+ // Find the unit cost notation (@ but not @@)
752
+ // Match @ followed by non-@ character
753
+ const unitCostMatch = lineText.match(/@(?!@)\s*[^;#\s]+/);
754
+ if (!unitCostMatch || unitCostMatch.index === undefined) {
755
+ return;
756
+ }
757
+ const startPos = unitCostMatch.index;
758
+ const endPos = startPos + unitCostMatch[0].length;
759
+ // Replace with total cost notation
760
+ const newText = `@@ ${totalCostText}`;
761
+ // Create and apply the edit
762
+ await connection.workspace.applyEdit({
763
+ changes: {
764
+ [uri]: [{
765
+ range: {
766
+ start: { line, character: startPos },
767
+ end: { line, character: endPos }
768
+ },
769
+ newText: newText
770
+ }]
771
+ }
772
+ });
773
+ }
774
+ else if (params.command === 'hledger.refreshInlayHints') {
775
+ // Manually refresh inlay hints
776
+ if (hasInlayHintRefreshSupport) {
777
+ await connection.languages.inlayHint.refresh().catch((err) => {
778
+ connection.console.error(`Failed to refresh inlay hints: ${err}`);
779
+ });
780
+ }
781
+ else {
782
+ connection.window.showInformationMessage('Inlay hint refresh not supported by your editor');
783
+ }
784
+ }
785
+ else if (params.command === 'hledger.showWorkspaceGraph') {
786
+ connection.console.log(`[ExecuteCommand] showWorkspaceGraph called. workspaceManager exists: ${!!workspaceManager}`);
787
+ if (workspaceManager) {
788
+ try {
789
+ const graph = workspaceManager.getWorkspaceTree();
790
+ connection.console.log(`[ExecuteCommand] Tree generated, length: ${graph.length}`);
791
+ return graph;
792
+ }
793
+ catch (error) {
794
+ connection.console.error(`[ExecuteCommand] Error generating tree: ${error}`);
795
+ return `Error generating tree: ${error}`;
796
+ }
797
+ }
798
+ else {
799
+ connection.console.warn('[ExecuteCommand] Workspace manager not initialized');
800
+ connection.window.showErrorMessage('Workspace manager not initialized');
801
+ return 'Error: Workspace manager not initialized. Check LSP logs.';
802
+ }
803
+ }
804
+ else if (params.command === 'hledger.showWorkspaceGraphStructured') {
805
+ connection.console.log(`[ExecuteCommand] showWorkspaceGraphStructured called. workspaceManager exists: ${!!workspaceManager}`);
806
+ if (workspaceManager) {
807
+ try {
808
+ const entries = workspaceManager.getWorkspaceTreeStructured();
809
+ connection.console.log(`[ExecuteCommand] Structured tree generated, ${entries.length} entries`);
810
+ return entries;
811
+ }
812
+ catch (error) {
813
+ connection.console.error(`[ExecuteCommand] Error generating structured tree: ${error}`);
814
+ return [];
815
+ }
816
+ }
817
+ else {
818
+ connection.console.warn('[ExecuteCommand] Workspace manager not initialized');
819
+ connection.window.showErrorMessage('Workspace manager not initialized');
820
+ return [];
821
+ }
822
+ }
823
+ });
416
824
  // Make the text document manager listen on the connection
417
825
  documents.listen(connection);
418
826
  // Listen on the connection
419
827
  connection.listen();
828
+ connection.console.log('========== HLEDGER LSP SERVER LISTENING ==========');
420
829
  //# sourceMappingURL=server.js.map