3 files changed,
82 insertions(+),
89 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed at:
2026-09-10 13:33:34 +0300
Authored at:
2026-09-09 14:10:26 +0300
Change ID:
xmukzkynsxmntxwysrzzspqnoxtvqnxl
Parent:
1b9e6e1
M
internal/lsp/textdocument_definition.go
··· 149 149 return nil 150 150 } 151 151 152 +func definitionLocation(an *analyzer.Analysis, s scoredSymbol) *protocol.Location { 153 + switch s.kind { 154 + case symbolAccount: 155 + return findAccountDefinition(an, s.name) 156 + case symbolTransaction: 157 + return findTransactionDefinition(an, s.tnxEntry) 158 + case symbolCommodity: 159 + return findCommodityDefinition(an, s.name) 160 + case symbolPayee: 161 + return findPayeeDefinition(an, s.name) 162 + case symbolTag: 163 + return findTagDefinition(an, s.name) 164 + } 165 + return nil 166 +} 167 + 152 168 func includeTargets(an *analyzer.Analysis, docPath, pattern string) protocol.LocationSlice { 153 169 target := filepath.Join(filepath.Dir(docPath), pattern) 154 170 glob := strings.ContainsAny(target, "*?[")
M
internal/lsp/textdocument_selection_range.go
··· 60 60 func selectionInEntry(content string, e ast.Entry, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) protocol.SelectionRange { 61 61 switch t := e.(type) { 62 62 case *ast.Transaction: 63 - return transactionSelection(content, t, li, cursor, parent) 63 + return selTransaction(content, t, li, cursor, parent) 64 64 case *ast.PeriodicTransaction: 65 - return periodicSelection(content, t, li, cursor, parent) 65 + return selPeriodicTransaction(content, t, li, cursor, parent) 66 66 case *ast.AutomatedTransaction: 67 - return automatedSelection(content, t, li, cursor, parent) 67 + return selAutomatedTransaction(content, t, li, cursor, parent) 68 68 case *ast.AccountDirective: 69 - return accountDirectiveSelection(content, t, li, cursor, parent) 69 + return selAccountDirective(content, t, li, cursor, parent) 70 70 case *ast.CommodityDirective: 71 - return commodityDirectiveSelection(content, t, li, cursor, parent) 71 + return selCommodityDirective(content, t, li, cursor, parent) 72 72 case *ast.PayeeDirective: 73 73 if t.Name != "" { 74 - if sel, ok := selectionForSpan(content, li, t.NameSpan, cursor, parent); ok { 74 + if sel, ok := selForSpan(content, li, t.NameSpan, cursor, parent); ok { 75 75 return sel 76 76 } 77 77 } 78 78 return commentOrParent(content, t.Comment, li, cursor, parent) 79 79 case *ast.TagDirective: 80 80 if sp, ok := tagDirectiveSpan(content, t); ok { 81 - if sel, ok := selectionForSpan(content, li, sp, cursor, parent); ok { 81 + if sel, ok := selForSpan(content, li, sp, cursor, parent); ok { 82 82 return sel 83 83 } 84 84 } 85 85 return commentOrParent(content, t.Comment, li, cursor, parent) 86 86 case *ast.AliasDirective: 87 - if sel, ok := accountSelection(content, &t.From, li, cursor, parent); ok { 87 + if sel, ok := selAccount(content, &t.From, li, cursor, parent); ok { 88 88 return sel 89 89 } 90 - if sel, ok := accountSelection(content, &t.To, li, cursor, parent); ok { 90 + if sel, ok := selAccount(content, &t.To, li, cursor, parent); ok { 91 91 return sel 92 92 } 93 93 return commentOrParent(content, t.Comment, li, cursor, parent) 94 94 case *ast.DefaultCommodityDirective: 95 - if sel, ok := amountSelection(content, &t.Amount, li, cursor, parent); ok { 95 + if sel, ok := selAmount(content, &t.Amount, li, cursor, parent); ok { 96 96 return sel 97 97 } 98 98 return commentOrParent(content, t.Comment, li, cursor, parent) 99 99 case *ast.MarketPriceDirective: 100 - if sel, ok := selectionForSpan(content, li, t.DateTime.Date.Span, cursor, parent); ok { 100 + if sel, ok := selForSpan(content, li, t.DateTime.Date.Span, cursor, parent); ok { 101 101 return sel 102 102 } 103 103 if t.DateTime.Time != nil { 104 - if sel, ok := selectionForSpan(content, li, t.DateTime.Time.Span, cursor, parent); ok { 104 + if sel, ok := selForSpan(content, li, t.DateTime.Time.Span, cursor, parent); ok { 105 105 return sel 106 106 } 107 107 } 108 - if sel, ok := amountSelection(content, &t.Amount, li, cursor, parent); ok { 108 + if sel, ok := selAmount(content, &t.Amount, li, cursor, parent); ok { 109 109 return sel 110 110 } 111 111 return commentOrParent(content, t.Comment, li, cursor, parent) 112 112 case *ast.ConversionDirective: 113 - if sel, ok := amountSelection(content, &t.From, li, cursor, parent); ok { 113 + if sel, ok := selAmount(content, &t.From, li, cursor, parent); ok { 114 114 return sel 115 115 } 116 - if sel, ok := amountSelection(content, &t.To, li, cursor, parent); ok { 116 + if sel, ok := selAmount(content, &t.To, li, cursor, parent); ok { 117 117 return sel 118 118 } 119 119 return commentOrParent(content, t.Comment, li, cursor, parent) ··· 121 121 return parent 122 122 } 123 123 124 -func transactionSelection(content string, t *ast.Transaction, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) protocol.SelectionRange { 124 +func selTransaction(content string, t *ast.Transaction, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) protocol.SelectionRange { 125 125 var header [6]token.Span // date, second date, status, code, payee, note 126 126 sps := append(header[:0], t.Date.Span) 127 127 if t.SecondDate != nil { ··· 140 140 sps = append(sps, t.NoteSpan) 141 141 } 142 142 for _, sp := range sps { 143 - if sel, ok := selectionForSpan(content, li, sp, cursor, parent); ok { 143 + if sel, ok := selForSpan(content, li, sp, cursor, parent); ok { 144 144 return sel 145 145 } 146 146 } 147 - return commentsAndPostingsSelection(content, t.Comment, t.HeaderComments, t.Postings, li, cursor, parent) 147 + return selCommentsAndPostings(content, t.Comment, t.HeaderComments, t.Postings, li, cursor, parent) 148 148 } 149 149 150 -// commentsAndPostingsSelection descends into an entry's inline comment, header 151 -// comments, then postings; returns parent when none contains the cursor. 152 -func commentsAndPostingsSelection(content string, inline *ast.Comment, headers []*ast.Comment, postings []ast.Posting, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) protocol.SelectionRange { 153 - if sel, ok := commentSelection(content, inline, li, cursor, parent); ok { 150 +func selCommentsAndPostings(content string, inline *ast.Comment, headers []*ast.Comment, postings []ast.Posting, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) protocol.SelectionRange { 151 + if sel, ok := selComment(content, inline, li, cursor, parent); ok { 154 152 return sel 155 153 } 156 154 for _, c := range headers { 157 - if sel, ok := commentSelection(content, c, li, cursor, parent); ok { 155 + if sel, ok := selComment(content, c, li, cursor, parent); ok { 158 156 return sel 159 157 } 160 158 } 161 - if sel, ok := postingsSelection(content, postings, li, cursor, parent); ok { 159 + if sel, ok := selPostings(content, postings, li, cursor, parent); ok { 162 160 return sel 163 161 } 164 162 return parent 165 163 } 166 164 167 -func periodicSelection(content string, pt *ast.PeriodicTransaction, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) protocol.SelectionRange { 168 - if sel, ok := selectionForSpan(content, li, pt.Period.Span, cursor, parent); ok { 165 +func selPeriodicTransaction(content string, pt *ast.PeriodicTransaction, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) protocol.SelectionRange { 166 + if sel, ok := selForSpan(content, li, pt.Period.Span, cursor, parent); ok { 169 167 if d := pt.Period.From; d != nil { 170 - if sub, ok := selectionForSpan(content, li, d.Span, cursor, sel); ok { 168 + if sub, ok := selForSpan(content, li, d.Span, cursor, sel); ok { 171 169 return sub 172 170 } 173 171 } 174 172 if d := pt.Period.To; d != nil { 175 - if sub, ok := selectionForSpan(content, li, d.Span, cursor, sel); ok { 173 + if sub, ok := selForSpan(content, li, d.Span, cursor, sel); ok { 176 174 return sub 177 175 } 178 176 } 179 177 return sel 180 178 } 181 179 if pt.Description != "" { 182 - if sel, ok := selectionForSpan(content, li, pt.DescriptionSpan, cursor, parent); ok { 180 + if sel, ok := selForSpan(content, li, pt.DescriptionSpan, cursor, parent); ok { 183 181 return sel 184 182 } 185 183 } 186 - return commentsAndPostingsSelection(content, pt.Comment, pt.HeaderComments, pt.Postings, li, cursor, parent) 184 + return selCommentsAndPostings(content, pt.Comment, pt.HeaderComments, pt.Postings, li, cursor, parent) 187 185 } 188 186 189 -func automatedSelection(content string, at *ast.AutomatedTransaction, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) protocol.SelectionRange { 190 - if sel, ok := selectionForSpan(content, li, at.ExprSpan, cursor, parent); ok { 187 +func selAutomatedTransaction(content string, at *ast.AutomatedTransaction, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) protocol.SelectionRange { 188 + if sel, ok := selForSpan(content, li, at.ExprSpan, cursor, parent); ok { 191 189 return sel 192 190 } 193 - return commentsAndPostingsSelection(content, at.Comment, at.HeaderComments, at.Postings, li, cursor, parent) 191 + return selCommentsAndPostings(content, at.Comment, at.HeaderComments, at.Postings, li, cursor, parent) 194 192 } 195 193 196 -func accountDirectiveSelection(content string, d *ast.AccountDirective, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) protocol.SelectionRange { 197 - if sel, ok := accountSelection(content, &d.Account, li, cursor, parent); ok { 194 +func selAccountDirective(content string, d *ast.AccountDirective, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) protocol.SelectionRange { 195 + if sel, ok := selAccount(content, &d.Account, li, cursor, parent); ok { 198 196 return sel 199 197 } 200 198 for i := range d.Subdirectives { 201 199 sd := &d.Subdirectives[i] 202 200 if sd.Kind == ast.SubdirectiveComment { 203 - if sel, ok := commentSelection(content, sd.Comment, li, cursor, parent); ok { 201 + if sel, ok := selComment(content, sd.Comment, li, cursor, parent); ok { 204 202 return sel 205 203 } 206 204 continue 207 205 } 208 - if sel, ok := selectionForSpan(content, li, sd.ValueSpan, cursor, parent); ok { 206 + if sel, ok := selForSpan(content, li, sd.ValueSpan, cursor, parent); ok { 209 207 return sel 210 208 } 211 209 } 212 210 return commentOrParent(content, d.Comment, li, cursor, parent) 213 211 } 214 212 215 -func commodityDirectiveSelection(content string, d *ast.CommodityDirective, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) protocol.SelectionRange { 216 - if sel, ok := selectionForSpan(content, li, d.CommoditySpan, cursor, parent); ok { 213 +func selCommodityDirective(content string, d *ast.CommodityDirective, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) protocol.SelectionRange { 214 + if sel, ok := selForSpan(content, li, d.CommoditySpan, cursor, parent); ok { 217 215 return sel 218 216 } 219 217 if d.FormatSub != nil { 220 - if sel, ok := amountSelection(content, &d.FormatSub.Amount, li, cursor, parent); ok { 218 + if sel, ok := selAmount(content, &d.FormatSub.Amount, li, cursor, parent); ok { 221 219 return sel 222 220 } 223 221 } 224 222 return commentOrParent(content, d.Comment, li, cursor, parent) 225 223 } 226 224 227 -// postingsSelection returns the selection inside the posting containing cursor. 228 -func postingsSelection(content string, postings []ast.Posting, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) (protocol.SelectionRange, bool) { 225 +func selPostings(content string, postings []ast.Posting, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) (protocol.SelectionRange, bool) { 229 226 for i := range postings { 230 227 p := &postings[i] 231 - postingSel, ok := selectionForSpan(content, li, p.Span, cursor, parent) 228 + postingSel, ok := selForSpan(content, li, p.Span, cursor, parent) 232 229 if !ok { 233 230 continue 234 231 } 235 - return postingSelection(content, postings[i], li, cursor, postingSel), true 232 + return selPosting(content, postings[i], li, cursor, postingSel), true 236 233 } 237 234 return protocol.SelectionRange{}, false 238 235 } 239 236 240 -func postingSelection(content string, p ast.Posting, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) protocol.SelectionRange { 237 +func selPosting(content string, p ast.Posting, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) protocol.SelectionRange { 241 238 if p.Status != ast.StatusNone { 242 - if sel, ok := selectionForSpan(content, li, p.StatusSpan, cursor, parent); ok { 239 + if sel, ok := selForSpan(content, li, p.StatusSpan, cursor, parent); ok { 243 240 return sel 244 241 } 245 242 } 246 - if sel, ok := accountSelection(content, &p.Account, li, cursor, parent); ok { 243 + if sel, ok := selAccount(content, &p.Account, li, cursor, parent); ok { 247 244 return sel 248 245 } 249 - if sel, ok := amountSelection(content, p.Amount, li, cursor, parent); ok { 246 + if sel, ok := selAmount(content, p.Amount, li, cursor, parent); ok { 250 247 return sel 251 248 } 252 249 if p.Cost != nil { 253 - if sel, ok := selectionForSpan(content, li, p.Cost.Span, cursor, parent); ok { 250 + if sel, ok := selForSpan(content, li, p.Cost.Span, cursor, parent); ok { 254 251 return sel 255 252 } 256 253 } 257 254 if p.Balance != nil { 258 - if sel, ok := selectionForSpan(content, li, p.Balance.Span, cursor, parent); ok { 255 + if sel, ok := selForSpan(content, li, p.Balance.Span, cursor, parent); ok { 259 256 return sel 260 257 } 261 258 } 262 - if sel, ok := commentSelection(content, p.Comment, li, cursor, parent); ok { 259 + if sel, ok := selComment(content, p.Comment, li, cursor, parent); ok { 263 260 return sel 264 261 } 265 262 for i := range p.Comments { 266 - if sel, ok := commentSelection(content, &p.Comments[i], li, cursor, parent); ok { 263 + if sel, ok := selComment(content, &p.Comments[i], li, cursor, parent); ok { 267 264 return sel 268 265 } 269 266 } 270 267 return parent 271 268 } 272 269 273 -func accountSelection(content string, a *ast.Account, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) (protocol.SelectionRange, bool) { 274 - accountSel, ok := selectionForSpan(content, li, a.Span, cursor, parent) 270 +func selAccount(content string, a *ast.Account, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) (protocol.SelectionRange, bool) { 271 + accountSel, ok := selForSpan(content, li, a.Span, cursor, parent) 275 272 if !ok { 276 273 return protocol.SelectionRange{}, false 277 274 } ··· 279 276 return accountSel, true 280 277 } 281 278 for i := range a.Name { 282 - if sel, ok := selectionForSpan(content, li, a.Name[i].Span, cursor, accountSel); ok { 279 + if sel, ok := selForSpan(content, li, a.Name[i].Span, cursor, accountSel); ok { 283 280 return sel, true 284 281 } 285 282 } 286 283 return accountSel, true 287 284 } 288 285 289 -func amountSelection(content string, am *ast.Amount, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) (protocol.SelectionRange, bool) { 286 +func selAmount(content string, am *ast.Amount, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) (protocol.SelectionRange, bool) { 290 287 if am == nil { 291 288 return protocol.SelectionRange{}, false 292 289 } 293 - amountSel, ok := selectionForSpan(content, li, am.Span, cursor, parent) 290 + amountSel, ok := selForSpan(content, li, am.Span, cursor, parent) 294 291 if !ok { 295 292 return protocol.SelectionRange{}, false 296 293 } 297 - if sel, ok := selectionForSpan(content, li, am.CommoditySpan, cursor, amountSel); ok { 294 + if sel, ok := selForSpan(content, li, am.CommoditySpan, cursor, amountSel); ok { 298 295 return sel, true 299 296 } 300 297 qStart, qEnd := quantitySpan(content, am) 301 298 if qEnd > qStart { 302 - if sel, ok := selectionForSpan(content, li, token.Span{Start: token.Pos{Offset: qStart}, End: token.Pos{Offset: qEnd}}, cursor, amountSel); ok { 299 + if sel, ok := selForSpan(content, li, token.Span{Start: token.Pos{Offset: qStart}, End: token.Pos{Offset: qEnd}}, cursor, amountSel); ok { 303 300 return sel, true 304 301 } 305 302 } 306 303 return amountSel, true 307 304 } 308 305 309 -// selectionForSpan selects span nested in parent when span contains cursor. 310 -func selectionForSpan(content string, li *lsputil.LineIndex, span token.Span, cursor int, parent protocol.SelectionRange) (protocol.SelectionRange, bool) { 311 - if !spanContains(content, span, cursor) { 312 - return protocol.SelectionRange{}, false 313 - } 314 - return protocol.SelectionRange{Range: li.SpanRange(span), Parent: &parent}, true 315 -} 316 - 317 -// commentSelection returns a selection inside comment when the cursor is on it: 318 -// the tag key when the cursor is on a tag, else the whole comment line. 319 -func commentSelection(content string, c *ast.Comment, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) (protocol.SelectionRange, bool) { 306 +func selComment(content string, c *ast.Comment, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) (protocol.SelectionRange, bool) { 320 307 if c == nil { 321 308 return protocol.SelectionRange{}, false 322 309 } 323 - commentSel, ok := selectionForSpan(content, li, c.Span, cursor, parent) 310 + commentSel, ok := selForSpan(content, li, c.Span, cursor, parent) 324 311 if !ok { 325 312 return protocol.SelectionRange{}, false 326 313 } ··· 330 317 return commentSel, true 331 318 } 332 319 333 -// commentOrParent is commentSelection with parent as the fallback. 320 +func selForSpan(content string, li *lsputil.LineIndex, span token.Span, cursor int, parent protocol.SelectionRange) (protocol.SelectionRange, bool) { 321 + if !spanContains(content, span, cursor) { 322 + return protocol.SelectionRange{}, false 323 + } 324 + return protocol.SelectionRange{Range: li.SpanRange(span), Parent: &parent}, true 325 +} 326 + 334 327 func commentOrParent(content string, c *ast.Comment, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) protocol.SelectionRange { 335 - if sel, ok := commentSelection(content, c, li, cursor, parent); ok { 328 + if sel, ok := selComment(content, c, li, cursor, parent); ok { 336 329 return sel 337 330 } 338 331 return parent
M
internal/lsp/workspace_symbol.go
··· 95 95 return symbols 96 96 } 97 97 98 -func definitionLocation(an *analyzer.Analysis, s scoredSymbol) *protocol.Location { 99 - switch s.kind { 100 - case symbolAccount: 101 - return findAccountDefinition(an, s.name) 102 - case symbolTransaction: 103 - return findTransactionDefinition(an, s.tnxEntry) 104 - case symbolCommodity: 105 - return findCommodityDefinition(an, s.name) 106 - case symbolPayee: 107 - return findPayeeDefinition(an, s.name) 108 - case symbolTag: 109 - return findTagDefinition(an, s.name) 110 - } 111 - return nil 112 -} 113 - 114 98 func transactionName(e ast.Entry) string { 115 99 var b strings.Builder 116 100 switch e := e.(type) {