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.
- package/README.md +87 -167
- package/out/features/codeActions.d.ts +0 -4
- package/out/features/codeActions.d.ts.map +1 -1
- package/out/features/codeActions.js +11 -25
- package/out/features/codeActions.js.map +1 -1
- package/out/features/codeLens.d.ts +29 -0
- package/out/features/codeLens.d.ts.map +1 -0
- package/out/features/codeLens.js +105 -0
- package/out/features/codeLens.js.map +1 -0
- package/out/features/completion.d.ts.map +1 -1
- package/out/features/completion.js +21 -9
- package/out/features/completion.js.map +1 -1
- package/out/features/definition.d.ts.map +1 -1
- package/out/features/definition.js.map +1 -1
- package/out/features/foldingRanges.d.ts.map +1 -1
- package/out/features/foldingRanges.js +9 -2
- package/out/features/foldingRanges.js.map +1 -1
- package/out/features/formatter.d.ts +2 -0
- package/out/features/formatter.d.ts.map +1 -1
- package/out/features/formatter.js +36 -7
- package/out/features/formatter.js.map +1 -1
- package/out/features/formattingUtils.d.ts +38 -0
- package/out/features/formattingUtils.d.ts.map +1 -0
- package/out/features/formattingUtils.js +107 -0
- package/out/features/formattingUtils.js.map +1 -0
- package/out/features/hover.d.ts +2 -5
- package/out/features/hover.d.ts.map +1 -1
- package/out/features/hover.js +60 -36
- package/out/features/hover.js.map +1 -1
- package/out/features/inlayHints.d.ts +7 -5
- package/out/features/inlayHints.d.ts.map +1 -1
- package/out/features/inlayHints.js +164 -91
- package/out/features/inlayHints.js.map +1 -1
- package/out/features/semanticTokens.d.ts.map +1 -1
- package/out/features/semanticTokens.js +22 -1
- package/out/features/semanticTokens.js.map +1 -1
- package/out/features/symbols.d.ts.map +1 -1
- package/out/features/symbols.js +9 -3
- package/out/features/symbols.js.map +1 -1
- package/out/features/validator.d.ts +0 -8
- package/out/features/validator.d.ts.map +1 -1
- package/out/features/validator.js +367 -171
- package/out/features/validator.js.map +1 -1
- package/out/parser/ast.d.ts +2 -2
- package/out/parser/ast.d.ts.map +1 -1
- package/out/parser/ast.js +10 -5
- package/out/parser/ast.js.map +1 -1
- package/out/parser/index.d.ts +8 -5
- package/out/parser/index.d.ts.map +1 -1
- package/out/parser/index.js +0 -7
- package/out/parser/index.js.map +1 -1
- package/out/server/configFile.d.ts +104 -0
- package/out/server/configFile.d.ts.map +1 -0
- package/out/server/configFile.js +231 -0
- package/out/server/configFile.js.map +1 -0
- package/out/server/settings.d.ts +9 -0
- package/out/server/settings.d.ts.map +1 -1
- package/out/server/settings.js +9 -0
- package/out/server/settings.js.map +1 -1
- package/out/server/workspace.d.ts +126 -0
- package/out/server/workspace.d.ts.map +1 -0
- package/out/server/workspace.js +605 -0
- package/out/server/workspace.js.map +1 -0
- package/out/server.js +438 -29
- package/out/server.js.map +1 -1
- package/out/types.d.ts +15 -4
- package/out/types.d.ts.map +1 -1
- package/out/types.js +11 -0
- package/out/types.js.map +1 -1
- package/out/utils/amountFormatter.d.ts +26 -0
- package/out/utils/amountFormatter.d.ts.map +1 -0
- package/out/utils/amountFormatter.js +91 -0
- package/out/utils/amountFormatter.js.map +1 -0
- package/out/utils/balanceCalculator.d.ts +32 -0
- package/out/utils/balanceCalculator.d.ts.map +1 -0
- package/out/utils/balanceCalculator.js +93 -0
- package/out/utils/balanceCalculator.js.map +1 -0
- package/out/utils/index.d.ts.map +1 -1
- package/out/utils/index.js +2 -1
- package/out/utils/index.js.map +1 -1
- 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
|
|
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
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
149
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|