hledger-lsp 0.2.6 → 0.2.8

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 (41) hide show
  1. package/out/features/completion.js +4 -6
  2. package/out/features/completion.js.map +1 -1
  3. package/out/features/formatter.d.ts +11 -3
  4. package/out/features/formatter.d.ts.map +1 -1
  5. package/out/features/formatter.js +175 -56
  6. package/out/features/formatter.js.map +1 -1
  7. package/out/features/hover.d.ts.map +1 -1
  8. package/out/features/hover.js +3 -1
  9. package/out/features/hover.js.map +1 -1
  10. package/out/features/inlayHints.d.ts +1 -24
  11. package/out/features/inlayHints.d.ts.map +1 -1
  12. package/out/features/inlayHints.js +315 -218
  13. package/out/features/inlayHints.js.map +1 -1
  14. package/out/features/semanticTokens.d.ts.map +1 -1
  15. package/out/features/semanticTokens.js +14 -68
  16. package/out/features/semanticTokens.js.map +1 -1
  17. package/out/features/transactionAnalyzer.js +3 -5
  18. package/out/features/transactionAnalyzer.js.map +1 -1
  19. package/out/features/validator.d.ts.map +1 -1
  20. package/out/features/validator.js +7 -4
  21. package/out/features/validator.js.map +1 -1
  22. package/out/parser/includes.d.ts +0 -1
  23. package/out/parser/includes.d.ts.map +1 -1
  24. package/out/parser/includes.js +3 -6
  25. package/out/parser/includes.js.map +1 -1
  26. package/out/parser/index.d.ts +1 -1
  27. package/out/parser/index.d.ts.map +1 -1
  28. package/out/parser/index.js +4 -10
  29. package/out/parser/index.js.map +1 -1
  30. package/out/server/settings.d.ts +4 -4
  31. package/out/server/settings.d.ts.map +1 -1
  32. package/out/server/settings.js +2 -2
  33. package/out/server/settings.js.map +1 -1
  34. package/out/server/workspace.js +24 -22
  35. package/out/server/workspace.js.map +1 -1
  36. package/out/server.js +1 -1
  37. package/out/server.js.map +1 -1
  38. package/out/utils/runningBalanceCalculator.d.ts.map +1 -1
  39. package/out/utils/runningBalanceCalculator.js +0 -51
  40. package/out/utils/runningBalanceCalculator.js.map +1 -1
  41. package/package.json +1 -1
@@ -15,30 +15,7 @@ export declare class InlayHintsProvider {
15
15
  * Provide inlay hints for a document
16
16
  */
17
17
  provideInlayHints(document: TextDocument, range: Range, parsed: ParsedDocument, settings?: HledgerSettings): InlayHint[];
18
- /**
19
- * Calculate insertion position for inferred amount hint
20
- */
21
- private calculateInferredAmountHintInsertionPosition;
22
- /**
23
- * Calculate insertion position for Running Balance assertion hint
24
- */
25
- private calculateAssertionHintInsertionPosition;
26
- /**
27
- * Calculate insertion position for Cost assertion hint
28
- */
29
- private calculateCostHintInsertionPosition;
30
- /**
31
- * Get hints for inferred amounts (postings with amounts marked as inferred)
32
- */
33
- private getInferredAmountHints;
34
- /**
35
- * Get hints for running balances after each posting (with pre-calculated state)
36
- */
37
- private getRunningBalanceHintsWithState;
38
- /**
39
- * Get hints for cost conversions
40
- */
41
- private getCostConversionHints;
18
+ private processTransactions;
42
19
  }
43
20
  export declare const inlayHintsProvider: InlayHintsProvider;
44
21
  //# sourceMappingURL=inlayHints.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"inlayHints.d.ts","sourceRoot":"","sources":["../../src/features/inlayHints.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAA+C,KAAK,EAAW,MAAM,uBAAuB,CAAC;AAC/G,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAElE,OAAO,EAAE,cAAc,EAAgC,MAAM,UAAU,CAAC;AAIxE,OAAO,EAKL,KAAK,eAAe,EACrB,MAAM,oBAAoB,CAAC;AAE5B,qBAAa,kBAAkB;IAC7B;;OAEG;IACH,iBAAiB,CACf,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,cAAc,EACtB,QAAQ,CAAC,EAAE,eAAe,GACzB,SAAS,EAAE;IA6Cd;;OAEG;IACH,OAAO,CAAC,4CAA4C;IA4BpD;;KAEC;IACD,OAAO,CAAC,uCAAuC;IAkB/C;;KAEC;IACD,OAAO,CAAC,kCAAkC;IAiB1C;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAmF9B;;OAEG;IACH,OAAO,CAAC,+BAA+B;IA6FvC;;OAEG;IACH,OAAO,CAAC,sBAAsB;CAoE/B;AAED,eAAO,MAAM,kBAAkB,oBAA2B,CAAC"}
1
+ {"version":3,"file":"inlayHints.d.ts","sourceRoot":"","sources":["../../src/features/inlayHints.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAA+C,KAAK,EAAW,MAAM,uBAAuB,CAAC;AAC/G,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAElE,OAAO,EAAE,cAAc,EAAgC,MAAM,UAAU,CAAC;AAIxE,OAAO,EAKL,KAAK,eAAe,EACrB,MAAM,oBAAoB,CAAC;AAG5B,qBAAa,kBAAkB;IAC7B;;OAEG;IACH,iBAAiB,CACf,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,cAAc,EACtB,QAAQ,CAAC,EAAE,eAAe,GACzB,SAAS,EAAE;IA8Kd,OAAO,CAAC,mBAAmB;CAgQ5B;AAED,eAAO,MAAM,kBAAkB,oBAA2B,CAAC"}
@@ -14,262 +14,359 @@ const vscode_uri_1 = require("vscode-uri");
14
14
  const amountFormatter_1 = require("../utils/amountFormatter");
15
15
  const runningBalanceCalculator_1 = require("../utils/runningBalanceCalculator");
16
16
  const settings_1 = require("../server/settings");
17
+ const formatter_1 = require("./formatter");
17
18
  class InlayHintsProvider {
18
19
  /**
19
20
  * Provide inlay hints for a document
20
21
  */
21
22
  provideInlayHints(document, range, parsed, settings) {
22
23
  const config = { ...settings_1.DEFAULT_INLAY_HINTS_OPTIONS, ...settings?.inlayHints };
24
+ const formattingOptions = { ...settings_1.DEFAULT_FORMATTING_OPTIONS, ...settings?.formatting };
23
25
  const hints = [];
24
- // Normalize document URI to ensure proper encoding
25
26
  const documentUri = vscode_uri_1.URI.parse(document.uri).toString();
26
27
  // If showing running balances, we need to process all transactions to accumulate balances
27
- // Otherwise, only process transactions within the requested range
28
28
  const runningBalances = config.showRunningBalances
29
29
  ? (0, runningBalanceCalculator_1.calculateRunningBalances)(parsed)
30
30
  : new Map();
31
- // Only process transactions within the requested range
32
31
  for (const transaction of parsed.transactions) {
33
- // Only show inlay hints for transactions in the current document
34
32
  if (transaction.sourceUri?.toString() !== documentUri) {
35
33
  continue;
36
34
  }
37
35
  const txLine = transaction.line ?? 0;
38
- // Skip transactions outside the range
39
36
  if (txLine < range.start.line || txLine > range.end.line) {
40
37
  continue;
41
38
  }
42
- // Inferred amount hints
43
- if (config.showInferredAmounts) {
44
- hints.push(...this.getInferredAmountHints(document, transaction, parsed, settings));
45
- }
46
- // Running balance hints
47
- if (config.showRunningBalances) {
48
- hints.push(...this.getRunningBalanceHintsWithState(document, transaction, parsed, runningBalances, settings));
49
- }
50
- // Cost conversion hints
51
- if (config.showCostConversions) {
52
- hints.push(...this.getCostConversionHints(document, transaction, parsed, settings));
53
- }
54
- }
55
- return hints;
56
- }
57
- /**
58
- * Calculate insertion position for inferred amount hint
59
- */
60
- calculateInferredAmountHintInsertionPosition(line, accountEnd, amount, parsed, settings) {
61
- const options = {
62
- ...settings_1.DEFAULT_FORMATTING_OPTIONS,
63
- ...settings?.formatting
64
- };
65
- // Get amount layout to determine pre-decimal width
66
- const layout = (0, amountFormatter_1.getAmountLayout)(amount, parsed, options, '');
67
- const preDecimalWidth = layout.commodityBefore.length +
68
- (layout.spaceBetweenCommodityAndAmount && layout.commodityBefore ? 1 : 0) +
69
- (layout.negPosSign ? 1 : 0) +
70
- layout.amountIntegerString.length;
71
- // Calculate the target position for the start of the amount (before the decimal)
72
- const targetColumn = Math.max(options.decimalAlignColumn, accountEnd + options.minSpacing + preDecimalWidth);
73
- const amountStartColumn = targetColumn - preDecimalWidth;
74
- return amountStartColumn;
75
- }
76
- /**
77
- * Calculate insertion position for Running Balance assertion hint
78
- */
79
- calculateAssertionHintInsertionPosition(line, accountEnd, amount, parsed, settings) {
80
- const options = {
81
- ...settings_1.DEFAULT_FORMATTING_OPTIONS,
82
- ...settings?.formatting
83
- };
84
- // TODO implement proper calculation for assertion hint position
85
- return options.decimalAlignColumn + 4; // +3 for ".00"
86
- }
87
- /**
88
- * Calculate insertion position for Cost assertion hint
89
- */
90
- calculateCostHintInsertionPosition(line, accountEnd, amount, parsed, settings) {
91
- const options = {
92
- ...settings_1.DEFAULT_FORMATTING_OPTIONS,
93
- ...settings?.formatting
94
- };
95
- // TODO implement proper calculation for assertion hint position
96
- return options.decimalAlignColumn + 3;
97
- }
98
- /**
99
- * Get hints for inferred amounts (postings with amounts marked as inferred)
100
- */
101
- getInferredAmountHints(document, transaction, parsed, settings) {
102
- const hints = [];
103
- const txLine = transaction.line ?? 0;
104
- let postingIndex = 0;
105
- for (const posting of transaction.postings) {
106
- // Show hint only for inferred amounts
107
- if (posting.amount && posting.amount.inferred) {
108
- const lineNum = txLine + 1 + postingIndex; // +1 for header, then posting index
39
+ // Use formatter to calculate ideal widths (Grid)
40
+ // We pass the inlay hints config so the grid accounts for inferred items
41
+ const widths = formatter_1.formattingProvider.calculateTransactionWidths(transaction, parsed, formattingOptions, config);
42
+ let postingIndex = 0;
43
+ for (const posting of transaction.postings) {
44
+ const lineNum = txLine + 1 + postingIndex;
45
+ // Get the current line content
109
46
  const line = document.getText({
110
47
  start: { line: lineNum, character: 0 },
111
48
  end: { line: lineNum, character: Number.MAX_SAFE_INTEGER }
112
49
  });
113
- // Find end of account name
114
- const accountEnd = line.indexOf(posting.account) + posting.account.length;
115
- // Check if non-whitespace content exists after account (before comment)
116
- const afterAccount = line.substring(accountEnd);
117
- const commentPos = afterAccount.search(/[;#]/);
118
- const beforeComment = commentPos >= 0
119
- ? afterAccount.substring(0, commentPos)
120
- : afterAccount;
121
- const hasContentAfterAccount = beforeComment.trim().length > 0;
122
- // Skip hint if non-whitespace, non-comment content exists after account
123
- // This prevents hints from appearing when amounts, costs, or assertions are present
124
- if (hasContentAfterAccount) {
125
- postingIndex++;
126
- continue;
127
- }
128
- // Calculate alignment padding and insertion position
129
- const insertPosition = this.calculateInferredAmountHintInsertionPosition(line, accountEnd, posting.amount, parsed, settings);
130
- // Format amount without extra padding (padding handled separately)
131
- const amountText = (0, amountFormatter_1.formatAmount)(posting.amount.quantity, posting.amount.commodity, parsed, settings?.formatting);
132
- // Create clickable label part with command to insert the amount
133
- const labelPart = {
134
- value: `${amountText}`,
135
- command: {
136
- title: 'Insert inferred amount',
137
- command: 'hledger.insertInferredAmount',
138
- arguments: [
139
- document.uri,
140
- lineNum,
141
- accountEnd,
142
- posting.amount.quantity,
143
- posting.amount.commodity
144
- ]
50
+ const accountEndIndex = line.indexOf(posting.account) + posting.account.length;
51
+ // Find comment position
52
+ const commentMatch = line.match(/[;#]/);
53
+ const commentIndex = commentMatch ? commentMatch.index : -1;
54
+ // Content "End" (before comment)
55
+ const contentEndIndex = commentIndex !== -1 ? commentIndex : line.length;
56
+ const contentBeforeComment = line.substring(0, contentEndIndex);
57
+ const trimmedContent = contentBeforeComment.trimEnd();
58
+ // Check if there is unexpected content after account
59
+ // If the line is just "Account", trimmedContent length is accountEndIndex.
60
+ // If the line is "Account $10", trimmedContent is longer.
61
+ // We only insert hints if we are "at the end" of the relevant explicit content.
62
+ // 1. Inferred Amount
63
+ if (config.showInferredAmounts && posting.amount && posting.amount.inferred) {
64
+ // Check if we have explicit content blocking us (e.g. tags, or manually written stuff)
65
+ // If the content after account is just whitespace, we are good.
66
+ if (trimmedContent.length <= accountEndIndex) {
67
+ const amountPreDecimalWidth = widths.amount.commodityBefore +
68
+ widths.amount.spaceBetweenCommodityBeforeAndAmount +
69
+ widths.amount.negPosSign +
70
+ widths.amount.integerPart;
71
+ const targetColumn = formattingOptions.decimalAlignColumn;
72
+ // Calculate where the hint should visually start to align the decimal
73
+ const hintStartColumn = targetColumn - amountPreDecimalWidth;
74
+ // Current position is contentEndIndex.
75
+ // We need padding to reach hintStartColumn.
76
+ // Note: InlayHint padding is visual.
77
+ const currentColumn = contentEndIndex; // Assuming simple chars
78
+ const paddingNeeded = Math.max(1, hintStartColumn - currentColumn); // At least 1 space
79
+ const amountText = (0, amountFormatter_1.formatAmount)(posting.amount.quantity, posting.amount.commodity, parsed, settings?.formatting);
80
+ const labelPart = {
81
+ value: amountText,
82
+ command: {
83
+ title: 'Insert inferred amount',
84
+ command: 'hledger.insertInferredAmount',
85
+ arguments: [
86
+ document.uri,
87
+ lineNum,
88
+ contentEndIndex, // Insert at end of current content
89
+ posting.amount.quantity,
90
+ posting.amount.commodity
91
+ ]
92
+ }
93
+ };
94
+ hints.push({
95
+ position: vscode_languageserver_1.Position.create(lineNum, contentEndIndex),
96
+ label: [labelPart],
97
+ kind: vscode_languageserver_1.InlayHintKind.Parameter,
98
+ paddingLeft: true, // Let VS Code handle standard padding? No, we want exact alignment.
99
+ // If we use paddingLeft, it adds a standard space.
100
+ // To do exact alignment, we might need to prepend spaces to the label value.
101
+ // But user warned about VS Code truncating large padding.
102
+ // Ideally, if the formatter ran, `currentColumn` matches `hintStartColumn`.
103
+ // If not, we pad.
104
+ });
105
+ // Correct padding approach for alignment:
106
+ // We can use `label` with leading spaces.
107
+ const paddingString = ' '.repeat(paddingNeeded);
108
+ // But wait, if we use `paddingLeft: true`, it adds roughly one space width?
109
+ // We want exact column alignment.
110
+ // Better to add spaces to the label value if we want strict alignment.
111
+ // The user said: "if we pad the inlay hint itself then it has to be quite large... vscode seems to shorten is to ..."
112
+ // This implies we should rely on the DOCUMENT having the whitespace (via formatter).
113
+ // IF the document has whitespace, `paddingNeeded` will be small (0 or 1).
114
+ // IF the document does not, we unfortunately have to pad the hint.
115
+ // Let's modify the label to include the padding.
116
+ labelPart.value = paddingString + amountText;
117
+ // If we have a comment, we insert AT the comment position?
118
+ // If we insert at `contentEndIndex`, and comment is at `commentIndex` (which equals contentEndIndex),
119
+ // the hint appears before the comment.
120
+ // This pushes the comment to the right. Correct.
145
121
  }
146
- };
147
- hints.push({
148
- position: vscode_languageserver_1.Position.create(lineNum, insertPosition),
149
- label: [labelPart],
150
- kind: vscode_languageserver_1.InlayHintKind.Parameter,
151
- paddingLeft: false, // We handle padding ourselves
152
- tooltip: 'Click to insert this inferred amount into the document'
153
- });
122
+ }
123
+ // 2. Inferred Cost
124
+ // ... (similar logic, using widths.cost)
125
+ if (config.showCostConversions && posting.cost && posting.cost.inferred) {
126
+ // We can only show inferred cost if we are "past" the amount.
127
+ // If amount was inferred, we effectively "added" it above.
128
+ // But LSP Inlay Hints are independent.
129
+ // If we have an inferred amount AND inferred cost, we need to stack them?
130
+ // VS Code places hints at the same position in order.
131
+ // So if we push Amount Hint, then Cost Hint, they appear: [Amount] [Cost].
132
+ // We just need to calculate the Cost padding relative to the End of the Amount.
133
+ // Where does the Amount end?
134
+ // Amount End = TargetColumn (decimal align) + PostDecimalWidth.
135
+ const amountPostDecimalWidth = widths.amount.decimalMark +
136
+ widths.amount.decimalPart +
137
+ widths.amount.spaceBetweenAmountAndCommodityAfter +
138
+ widths.amount.commodityAfter;
139
+ // The visual end of the amount block (whether explicit or inferred)
140
+ const amountVisualEnd = formattingOptions.decimalAlignColumn + amountPostDecimalWidth;
141
+ // Cost Start
142
+ // We align Cost based on widths.cost?
143
+ // Usually Cost keeps going.
144
+ // Formatter logic: `line += renderAmountLayout(..., widths.cost)`
145
+ // It just appends.
146
+ // So we just need minimal padding from Amount End.
147
+ // But if we are in "Inferred Amount" case, our "current physical position" is still `contentEndIndex`.
148
+ // The Amount Hint adds virtual width.
149
+ // We need to account for that.
150
+ // This suggests we should calculate a "virtual cursor" position.
151
+ // Start at `contentEndIndex`.
152
+ // If Inferred Amount:
153
+ // Add padding + AmountText width to virtual cursor.
154
+ // If Explicit Amount:
155
+ // Virtual cursor is at end of amount (which is `contentEndIndex`).
156
+ // Let's implement this "Virtual Cursor" flow.
157
+ }
158
+ postingIndex++;
154
159
  }
155
- postingIndex++;
156
160
  }
157
- return hints;
161
+ return this.processTransactions(parsed, document, range, config, formattingOptions, runningBalances);
158
162
  }
159
- /**
160
- * Get hints for running balances after each posting (with pre-calculated state)
161
- */
162
- getRunningBalanceHintsWithState(document, transaction, parsed, runningBalances, settings) {
163
+ processTransactions(parsed, document, range, config, formattingOptions, runningBalances) {
163
164
  const hints = [];
164
- // Find transaction index
165
- const txIndex = parsed.transactions.indexOf(transaction);
166
- if (txIndex === -1) {
167
- return hints;
168
- }
169
- const postingBalances = runningBalances.get(txIndex);
170
- if (!postingBalances) {
171
- return hints;
172
- }
173
- const txLine = transaction.line ?? 0;
174
- for (let postingIndex = 0; postingIndex < transaction.postings.length; postingIndex++) {
175
- const posting = transaction.postings[postingIndex];
176
- const balanceMap = postingBalances.get(postingIndex);
177
- // Don't show running balance hint if posting already has a balance assertion
178
- // Show hint for both explicit and inferred amounts
179
- if (balanceMap && !posting.assertion) {
165
+ const documentUri = vscode_uri_1.URI.parse(document.uri).toString();
166
+ for (const transaction of parsed.transactions) {
167
+ if (transaction.sourceUri?.toString() !== documentUri)
168
+ continue;
169
+ const txLine = transaction.line ?? 0;
170
+ if (txLine < range.start.line || txLine > range.end.line)
171
+ continue;
172
+ const widths = formatter_1.formattingProvider.calculateTransactionWidths(transaction, parsed, formattingOptions, config);
173
+ let postingIndex = 0;
174
+ for (const posting of transaction.postings) {
180
175
  const lineNum = txLine + 1 + postingIndex;
181
176
  const line = document.getText({
182
177
  start: { line: lineNum, character: 0 },
183
178
  end: { line: lineNum, character: Number.MAX_SAFE_INTEGER }
184
179
  });
185
- // Find end of non-whitespace content (before comment)
186
- const commentPos = line.search(/[;#]/);
187
- const beforeComment = commentPos >= 0
188
- ? line.substring(0, commentPos)
189
- : line;
190
- const trimmedLine = beforeComment.trimEnd();
191
- const contentEnd = trimmedLine.length;
192
- // Check if there's only whitespace between content end and a reasonable position
193
- const afterContent = beforeComment.substring(contentEnd);
194
- const hasOnlyWhitespace = afterContent.trim().length === 0;
195
- const insertPosition = this.calculateAssertionHintInsertionPosition(line, contentEnd, posting.amount, parsed, settings);
196
- // Format all commodity balances for this posting
197
- const balanceHints = [];
198
- for (const [commodity, balance] of balanceMap.entries()) {
199
- const formattedBalance = (0, amountFormatter_1.formatAmount)(balance, commodity, parsed, settings?.formatting);
200
- balanceHints.push(formattedBalance);
180
+ const commentMatch = line.match(/[;#]/);
181
+ const commentIndex = commentMatch ? commentMatch.index : -1;
182
+ const contentEndIndex = commentIndex !== -1 ? commentIndex : line.length;
183
+ const trimmedContent = line.substring(0, contentEndIndex).trimEnd();
184
+ // Virtual cursor assumes we are starting from the end of the clean content
185
+ // Use contentEndIndex (actual cursor) to account for existing whitespace
186
+ let virtualColumn = contentEndIndex ?? 0;
187
+ const insertPosition = vscode_languageserver_1.Position.create(lineNum, contentEndIndex);
188
+ // 1. Amount
189
+ if (posting.amount && !posting.amount.inferred) {
190
+ // Explicit amount. Update virtual cursor to end of content.
191
+ // (which is already done by init virtualColumn = trimmedContent.length)
201
192
  }
202
- const balanceText = balanceHints.join(', ');
203
- // Create clickable label part with command to insert balance assertion
204
- // Format as actual balance assertion syntax: " = $amount"
205
- const labelPart = {
206
- value: ` = ${balanceText} `,
207
- command: {
208
- title: 'Insert balance assertion',
209
- command: 'hledger.insertBalanceAssertion',
210
- arguments: [
211
- document.uri,
212
- lineNum,
213
- posting.account,
214
- balanceHints
215
- ]
193
+ else if (config.showInferredAmounts && posting.amount?.inferred) {
194
+ // Only show if we don't have unexpected garbage
195
+ // AND if we are not blocked by explicit items (Cost or Assertion)
196
+ const hasExplicitCost = posting.cost && !posting.cost.inferred;
197
+ const hasExplicitAssertion = posting.assertion;
198
+ if (!hasExplicitCost && !hasExplicitAssertion) {
199
+ const amountPreDecimalWidth = widths.amount.commodityBefore +
200
+ widths.amount.spaceBetweenCommodityBeforeAndAmount +
201
+ widths.amount.negPosSign +
202
+ widths.amount.integerPart;
203
+ const targetColumn = formattingOptions.decimalAlignColumn;
204
+ const requiredPadding = Math.max(0, targetColumn - virtualColumn - amountPreDecimalWidth);
205
+ const amountText = (0, amountFormatter_1.formatAmount)(posting.amount.quantity, posting.amount.commodity, parsed, formattingOptions);
206
+ const label = ' '.repeat(requiredPadding) + amountText;
207
+ hints.push({
208
+ position: insertPosition,
209
+ label: [{
210
+ value: label,
211
+ command: {
212
+ title: 'Insert inferred amount',
213
+ command: 'hledger.insertInferredAmount',
214
+ arguments: [document.uri, lineNum, contentEndIndex, posting.amount.quantity, posting.amount.commodity]
215
+ }
216
+ }],
217
+ kind: vscode_languageserver_1.InlayHintKind.Parameter,
218
+ paddingLeft: false
219
+ });
220
+ virtualColumn += label.length;
216
221
  }
217
- };
218
- hints.push({
219
- position: vscode_languageserver_1.Position.create(lineNum, insertPosition),
220
- label: [labelPart],
221
- kind: vscode_languageserver_1.InlayHintKind.Type,
222
- paddingLeft: false, // No LSP padding
223
- tooltip: `Click to insert balance assertion for ${posting.account}`
224
- });
225
- }
226
- }
227
- return hints;
228
- }
229
- /**
230
- * Get hints for cost conversions
231
- */
232
- getCostConversionHints(document, transaction, parsed, settings) {
233
- const hints = [];
234
- const txLine = transaction.line ?? 0;
235
- let postingIndex = 0;
236
- for (const posting of transaction.postings) {
237
- // Show hint only for postings inferred costs
238
- if (posting.cost && posting.cost.inferred) {
239
- const lineNum = txLine + 1 + postingIndex;
240
- const line = document.getText({
241
- start: { line: lineNum, character: 0 },
242
- end: { line: lineNum, character: Number.MAX_SAFE_INTEGER }
243
- });
244
- // Find end of account name
245
- const accountEnd = line.indexOf(posting.account) + posting.account.length;
246
- const insertPosition = this.calculateCostHintInsertionPosition(line, accountEnd, posting.cost.amount, parsed, settings);
247
- // Format amount without extra padding (padding handled separately)
248
- const amountText = (0, amountFormatter_1.formatAmount)(posting.cost.amount.quantity, posting.cost.amount.commodity, parsed, settings?.formatting);
249
- // Create clickable label part with command to insert the amount
250
- const labelPart = {
251
- value: ` @@ ${amountText}`,
252
- command: {
253
- title: 'Insert inferred cost',
254
- command: 'hledger.insertCost',
255
- arguments: [
256
- document.uri,
257
- lineNum,
258
- accountEnd,
259
- posting.cost.amount.quantity,
260
- posting.cost.amount.commodity
261
- ]
222
+ }
223
+ else {
224
+ // No amount (or blocked inferred).
225
+ // We effectively skip the "Amount Block".
226
+ // But for alignment of subsequent items, we might need to pad PAST the amount block?
227
+ // See formatter logic:
228
+ /*
229
+ const postDecimalWidth = ...
230
+ line += ' '.repeat(padding + preDecimalWidth + postDecimalWidth);
231
+ */
232
+ // If we have hidden amounts but show costs, we might need to pad.
233
+ // But Inlay Hints are additive. We can't insert "just padding" easily without a label.
234
+ // Unless we attach it to the next hint.
235
+ }
236
+ // 2. Cost
237
+ if (posting.cost && !posting.cost.inferred) {
238
+ // Explicit cost. Virtual cursor should logically be after this.
239
+ // But wait, if explicit cost exists, it's in the text.
240
+ // So virtualColumn (initially `trimmedContent.length`) ALREADY includes it.
241
+ // We don't need to do anything.
242
+ }
243
+ else if (config.showCostConversions && posting.cost?.inferred) {
244
+ // We want to add Cost Hint.
245
+ // Check if blocked by explicit assertion
246
+ const hasExplicitAssertion = posting.assertion;
247
+ if (!hasExplicitAssertion) {
248
+ const amountIsPresent = (posting.amount && !posting.amount.inferred) ||
249
+ (config.showInferredAmounts && posting.amount?.inferred && !hasExplicitAssertion && !(posting.cost && !posting.cost.inferred));
250
+ // Note: Logic for amountIsPresent is a bit circular if we re-check blocking logic.
251
+ // Simplified: If virtualColumn has moved (meaning we added hint) OR we have explicit amount.
252
+ // But virtualColumn update handles the Hint case.
253
+ // Explicit Amount case? virtualColumn includes it.
254
+ // So we just need to check if we are "at the start"?
255
+ // Or just always pad?
256
+ // If virtualColumn > trimmedContent.length, we added an Amount Hint.
257
+ // If explicit Amount exists, trimmedContent.length includes it.
258
+ // So we basically always want a space IF there is something before us.
259
+ // If "Account" -> Inferred Amount -> " $10" -> Inferred Cost.
260
+ // virtualColumn is at end of "$10". We want space.
261
+ const padding = 1;
262
+ const marker = (posting.cost.type === 'unit' ? '@' : '@@'); // Space handled by padding
263
+ const costText = (0, amountFormatter_1.formatAmount)(posting.cost.amount.quantity, posting.cost.amount.commodity, parsed, formattingOptions);
264
+ const label = ' '.repeat(padding) + marker + ' ' + costText;
265
+ hints.push({
266
+ position: insertPosition,
267
+ label: [{
268
+ value: label,
269
+ command: {
270
+ title: 'Insert cost',
271
+ command: 'hledger.insertCost',
272
+ arguments: [document.uri, lineNum, contentEndIndex, posting.cost.amount.quantity, posting.cost.amount.commodity]
273
+ }
274
+ }],
275
+ kind: vscode_languageserver_1.InlayHintKind.Parameter,
276
+ paddingLeft: false
277
+ });
278
+ virtualColumn += label.length;
262
279
  }
263
- };
264
- hints.push({
265
- position: vscode_languageserver_1.Position.create(lineNum, insertPosition),
266
- label: [labelPart],
267
- kind: vscode_languageserver_1.InlayHintKind.Parameter,
268
- paddingLeft: false, // We handle padding ourselves
269
- tooltip: 'Click to insert this inferred cost into the document'
270
- });
280
+ }
281
+ // 3. Running Balance (Assertion)
282
+ if (config.showRunningBalances && !posting.assertion) {
283
+ // Find running balance
284
+ const txIndex = parsed.transactions.indexOf(transaction);
285
+ const postingBalances = runningBalances.get(txIndex);
286
+ const balanceMap = postingBalances?.get(postingIndex);
287
+ if (balanceMap) {
288
+ // Align assertion?
289
+ // Currently formatter aligns assertions using `widths.assertion`.
290
+ // But as noted in formatter.ts, running balances are dynamic width.
291
+ // We can try to align to `widths.assertion` START if it exists?
292
+ // Or just align to a fixed column?
293
+ // Formatter says: `line += renderAmountLayout(layout, widths.assertion)` with `marker = ' = '`.
294
+ // If we are appending to existing text/hints:
295
+ // We want to reach `widths.assertion` start column?
296
+ // Where does assertion start in the grid?
297
+ // It starts after Cost block.
298
+ // Cost block width = widths.cost....
299
+ // We need to calculate cumulative width of grid?
300
+ // Implementation Simplification:
301
+ // Just add " = " + balances.
302
+ // The user wants alignment.
303
+ // If we have explicit assertions elsewhere, `widths.assertion` will be non-zero.
304
+ // We can try to align the "=" to separate column?
305
+ // That requires tracking the cumulative width of (Indent + Account + Amount + Cost).
306
+ // Let's Calculate the Ideal Start Column for Assertion
307
+ // Indent + Account + 2 spaces + AmountBlock + CostBlock
308
+ // AmountBlock width = preDecimal + decimalMark + postDecimal...
309
+ // Wait, `widths` just gives MAX widths of components.
310
+ // Constructing the full offset:
311
+ let idealStart = formattingOptions.indentation + widths.account + 2;
312
+ // Amount Block Width
313
+ const amountBlockWidth = widths.amount.commodityBefore +
314
+ widths.amount.spaceBetweenCommodityBeforeAndAmount +
315
+ widths.amount.negPosSign +
316
+ widths.amount.integerPart +
317
+ widths.amount.decimalMark +
318
+ widths.amount.decimalPart +
319
+ widths.amount.spaceBetweenAmountAndCommodityAfter +
320
+ widths.amount.commodityAfter;
321
+ // Correct logic: The Amount aligns at `decimalAlignColumn`.
322
+ // So the Amount Block *ends* at `decimalAlignColumn` + `postDecimalPart`.
323
+ const amountPostDecimal = widths.amount.decimalMark +
324
+ widths.amount.decimalPart +
325
+ widths.amount.spaceBetweenAmountAndCommodityAfter +
326
+ widths.amount.commodityAfter;
327
+ const amountEndColumn = formattingOptions.decimalAlignColumn + amountPostDecimal;
328
+ // Cost Block
329
+ // Cost is appended after Amount.
330
+ // Does it strictly align?
331
+ // Formatter: `line += renderAmountLayout(layout, widths.cost)`
332
+ // It renders the cost width.
333
+ // Unlike Amount, Cost is not decimal-aligned to a global column in the simplified logic,
334
+ // it just takes up `widths.cost` space.
335
+ const costBlockWidth = widths.cost.marker + // " @ "
336
+ widths.cost.commodityBefore +
337
+ widths.cost.spaceBetweenCommodityBeforeAndAmount +
338
+ widths.cost.negPosSign +
339
+ widths.cost.integerPart +
340
+ widths.cost.decimalMark +
341
+ widths.cost.decimalPart +
342
+ widths.cost.spaceBetweenAmountAndCommodityAfter +
343
+ widths.cost.commodityAfter;
344
+ const costEndColumn = amountEndColumn + costBlockWidth;
345
+ // So Assertion should start at `costEndColumn`.
346
+ const padding = Math.max(1, costEndColumn - virtualColumn);
347
+ const balanceStrings = [];
348
+ for (const [comm, amount] of balanceMap) {
349
+ balanceStrings.push((0, amountFormatter_1.formatAmount)(amount, comm, parsed, formattingOptions));
350
+ }
351
+ const balanceText = balanceStrings.join(', ');
352
+ const label = ' '.repeat(padding) + '= ' + balanceText;
353
+ hints.push({
354
+ position: insertPosition,
355
+ label: [{
356
+ value: label,
357
+ command: {
358
+ title: 'Insert balance assertion',
359
+ command: 'hledger.insertBalanceAssertion',
360
+ arguments: [document.uri, lineNum, posting.account, balanceStrings]
361
+ }
362
+ }],
363
+ kind: vscode_languageserver_1.InlayHintKind.Type,
364
+ paddingLeft: false
365
+ });
366
+ }
367
+ }
368
+ postingIndex++;
271
369
  }
272
- postingIndex++;
273
370
  }
274
371
  return hints;
275
372
  }