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.
- package/README.md +91 -173
- package/out/features/codeActions.d.ts +16 -0
- package/out/features/codeActions.d.ts.map +1 -1
- package/out/features/codeActions.js +129 -0
- 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 +102 -0
- package/out/features/codeLens.js.map +1 -0
- package/out/features/completion.d.ts +16 -4
- package/out/features/completion.d.ts.map +1 -1
- package/out/features/completion.js +23 -10
- package/out/features/completion.js.map +1 -1
- package/out/features/definition.d.ts.map +1 -1
- package/out/features/definition.js +4 -4
- package/out/features/definition.js.map +1 -1
- package/out/features/foldingRanges.d.ts.map +1 -1
- package/out/features/foldingRanges.js +6 -2
- package/out/features/foldingRanges.js.map +1 -1
- package/out/features/formatter.d.ts +4 -20
- package/out/features/formatter.d.ts.map +1 -1
- package/out/features/formatter.js +271 -223
- 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 +0 -4
- package/out/features/hover.d.ts.map +1 -1
- package/out/features/hover.js +11 -20
- package/out/features/hover.js.map +1 -1
- package/out/features/inlayHints.d.ts +12 -6
- package/out/features/inlayHints.d.ts.map +1 -1
- package/out/features/inlayHints.js +202 -84
- package/out/features/inlayHints.js.map +1 -1
- package/out/features/semanticTokens.d.ts.map +1 -1
- package/out/features/semanticTokens.js +17 -14
- package/out/features/semanticTokens.js.map +1 -1
- package/out/features/symbols.d.ts.map +1 -1
- package/out/features/symbols.js +8 -5
- package/out/features/symbols.js.map +1 -1
- package/out/features/validator.d.ts +5 -4
- package/out/features/validator.d.ts.map +1 -1
- package/out/features/validator.js +145 -105
- package/out/features/validator.js.map +1 -1
- package/out/parser/ast.d.ts +48 -7
- package/out/parser/ast.d.ts.map +1 -1
- package/out/parser/ast.js +552 -363
- package/out/parser/ast.js.map +1 -1
- package/out/parser/document.d.ts +7 -0
- package/out/parser/document.d.ts.map +1 -0
- package/out/parser/document.js +70 -0
- package/out/parser/document.js.map +1 -0
- package/out/parser/includes.d.ts.map +1 -1
- package/out/parser/includes.js +21 -33
- package/out/parser/includes.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 +39 -14
- 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 +13 -0
- package/out/server/settings.d.ts.map +1 -1
- package/out/server/settings.js +13 -3
- 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 +599 -0
- package/out/server/workspace.js.map +1 -0
- package/out/server.js +435 -27
- package/out/server.js.map +1 -1
- package/out/types.d.ts +27 -12
- package/out/types.d.ts.map +1 -1
- package/out/types.js +12 -0
- package/out/types.js.map +1 -1
- package/out/utils/amountFormatter.d.ts +14 -0
- package/out/utils/amountFormatter.d.ts.map +1 -0
- package/out/utils/amountFormatter.js +55 -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.js +1 -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,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
|
|
165
|
-
completion_1.completionProvider.updateAccounts(parsedDoc.accounts
|
|
166
|
-
completion_1.completionProvider.updatePayees(parsedDoc.payees
|
|
167
|
-
completion_1.completionProvider.updateCommodities(parsedDoc.commodities
|
|
168
|
-
completion_1.completionProvider.updateTags(parsedDoc.tags
|
|
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
|
-
//
|
|
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
|