clerk/internal/lsp/textdocument_selection_range.go (view raw)
Oleksandr Smirnov
Oleksandr Smirnov
olexsmir@gmail.com refactor: lsp: better naming of functions; move some stuff around, 29 days ago
olexsmir@gmail.com refactor: lsp: better naming of functions; move some stuff around, 29 days ago
| 1 | package lsp |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | |
| 6 | "go.lsp.dev/protocol" |
| 7 | |
| 8 | "olexsmir.xyz/clerk/internal/lsp/lsputil" |
| 9 | "olexsmir.xyz/clerk/journal" |
| 10 | "olexsmir.xyz/clerk/journal/ast" |
| 11 | "olexsmir.xyz/clerk/journal/token" |
| 12 | ) |
| 13 | |
| 14 | func (s *server) SelectionRange(_ context.Context, params *protocol.SelectionRangeParams) ([]protocol.SelectionRange, error) { |
| 15 | u := params.TextDocument.URI |
| 16 | state, ok := s.getDocState(u) |
| 17 | if !ok { |
| 18 | return nil, nil |
| 19 | } |
| 20 | an := s.analysisFor(u) |
| 21 | if an == nil { |
| 22 | return nil, nil |
| 23 | } |
| 24 | pf := parsedFileFor(an, u.Path()) |
| 25 | if pf == nil { |
| 26 | return nil, nil |
| 27 | } |
| 28 | |
| 29 | li := state.lineIdx |
| 30 | docSel := protocol.SelectionRange{Range: li.SpanRange(fullDocSpan(pf.Src))} |
| 31 | out := make([]protocol.SelectionRange, len(params.Positions)) |
| 32 | for i, pos := range params.Positions { |
| 33 | cursor := li.Offset(int(pos.Line), int(pos.Character)) |
| 34 | out[i] = selectionAt(state.text, pf, li, cursor, docSel) |
| 35 | } |
| 36 | return out, nil |
| 37 | } |
| 38 | |
| 39 | func fullDocSpan(src []byte) token.Span { return token.Span{End: token.Pos{Offset: len(src)}} } |
| 40 | |
| 41 | func selectionAt(content string, pf *journal.ParsedFile, li *lsputil.LineIndex, cursor int, docSel protocol.SelectionRange) protocol.SelectionRange { |
| 42 | e := entryAt(pf.Ast.Entries, cursor) |
| 43 | if e == nil { |
| 44 | return docSel |
| 45 | } |
| 46 | es := entrySpan(e) |
| 47 | if !spanContains(content, es, cursor) { |
| 48 | return docSel |
| 49 | } |
| 50 | switch c := e.(type) { |
| 51 | case *ast.BlankLine: |
| 52 | return docSel |
| 53 | case *ast.Comment: // the comment span is the entry span itself: no extra level |
| 54 | return commentOrParent(content, c, li, cursor, docSel) |
| 55 | } |
| 56 | parent := protocol.SelectionRange{Range: li.SpanRange(es), Parent: &docSel} |
| 57 | return selectionInEntry(content, e, li, cursor, parent) |
| 58 | } |
| 59 | |
| 60 | func selectionInEntry(content string, e ast.Entry, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) protocol.SelectionRange { |
| 61 | switch t := e.(type) { |
| 62 | case *ast.Transaction: |
| 63 | return selTransaction(content, t, li, cursor, parent) |
| 64 | case *ast.PeriodicTransaction: |
| 65 | return selPeriodicTransaction(content, t, li, cursor, parent) |
| 66 | case *ast.AutomatedTransaction: |
| 67 | return selAutomatedTransaction(content, t, li, cursor, parent) |
| 68 | case *ast.AccountDirective: |
| 69 | return selAccountDirective(content, t, li, cursor, parent) |
| 70 | case *ast.CommodityDirective: |
| 71 | return selCommodityDirective(content, t, li, cursor, parent) |
| 72 | case *ast.PayeeDirective: |
| 73 | if t.Name != "" { |
| 74 | if sel, ok := selForSpan(content, li, t.NameSpan, cursor, parent); ok { |
| 75 | return sel |
| 76 | } |
| 77 | } |
| 78 | return commentOrParent(content, t.Comment, li, cursor, parent) |
| 79 | case *ast.TagDirective: |
| 80 | if sp, ok := tagDirectiveSpan(content, t); ok { |
| 81 | if sel, ok := selForSpan(content, li, sp, cursor, parent); ok { |
| 82 | return sel |
| 83 | } |
| 84 | } |
| 85 | return commentOrParent(content, t.Comment, li, cursor, parent) |
| 86 | case *ast.AliasDirective: |
| 87 | if sel, ok := selAccount(content, &t.From, li, cursor, parent); ok { |
| 88 | return sel |
| 89 | } |
| 90 | if sel, ok := selAccount(content, &t.To, li, cursor, parent); ok { |
| 91 | return sel |
| 92 | } |
| 93 | return commentOrParent(content, t.Comment, li, cursor, parent) |
| 94 | case *ast.DefaultCommodityDirective: |
| 95 | if sel, ok := selAmount(content, &t.Amount, li, cursor, parent); ok { |
| 96 | return sel |
| 97 | } |
| 98 | return commentOrParent(content, t.Comment, li, cursor, parent) |
| 99 | case *ast.MarketPriceDirective: |
| 100 | if sel, ok := selForSpan(content, li, t.DateTime.Date.Span, cursor, parent); ok { |
| 101 | return sel |
| 102 | } |
| 103 | if t.DateTime.Time != nil { |
| 104 | if sel, ok := selForSpan(content, li, t.DateTime.Time.Span, cursor, parent); ok { |
| 105 | return sel |
| 106 | } |
| 107 | } |
| 108 | if sel, ok := selAmount(content, &t.Amount, li, cursor, parent); ok { |
| 109 | return sel |
| 110 | } |
| 111 | return commentOrParent(content, t.Comment, li, cursor, parent) |
| 112 | case *ast.ConversionDirective: |
| 113 | if sel, ok := selAmount(content, &t.From, li, cursor, parent); ok { |
| 114 | return sel |
| 115 | } |
| 116 | if sel, ok := selAmount(content, &t.To, li, cursor, parent); ok { |
| 117 | return sel |
| 118 | } |
| 119 | return commentOrParent(content, t.Comment, li, cursor, parent) |
| 120 | } |
| 121 | return parent |
| 122 | } |
| 123 | |
| 124 | func selTransaction(content string, t *ast.Transaction, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) protocol.SelectionRange { |
| 125 | var header [6]token.Span // date, second date, status, code, payee, note |
| 126 | sps := append(header[:0], t.Date.Span) |
| 127 | if t.SecondDate != nil { |
| 128 | sps = append(sps, t.SecondDate.Span) |
| 129 | } |
| 130 | if t.Status != ast.StatusNone { |
| 131 | sps = append(sps, t.StatusSpan) |
| 132 | } |
| 133 | if t.Code != "" { |
| 134 | sps = append(sps, t.CodeSpan) |
| 135 | } |
| 136 | if t.Payee != "" { |
| 137 | sps = append(sps, t.PayeeSpan) |
| 138 | } |
| 139 | if t.Note != "" { |
| 140 | sps = append(sps, t.NoteSpan) |
| 141 | } |
| 142 | for _, sp := range sps { |
| 143 | if sel, ok := selForSpan(content, li, sp, cursor, parent); ok { |
| 144 | return sel |
| 145 | } |
| 146 | } |
| 147 | return selCommentsAndPostings(content, t.Comment, t.HeaderComments, t.Postings, li, cursor, parent) |
| 148 | } |
| 149 | |
| 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 { |
| 152 | return sel |
| 153 | } |
| 154 | for _, c := range headers { |
| 155 | if sel, ok := selComment(content, c, li, cursor, parent); ok { |
| 156 | return sel |
| 157 | } |
| 158 | } |
| 159 | if sel, ok := selPostings(content, postings, li, cursor, parent); ok { |
| 160 | return sel |
| 161 | } |
| 162 | return parent |
| 163 | } |
| 164 | |
| 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 { |
| 167 | if d := pt.Period.From; d != nil { |
| 168 | if sub, ok := selForSpan(content, li, d.Span, cursor, sel); ok { |
| 169 | return sub |
| 170 | } |
| 171 | } |
| 172 | if d := pt.Period.To; d != nil { |
| 173 | if sub, ok := selForSpan(content, li, d.Span, cursor, sel); ok { |
| 174 | return sub |
| 175 | } |
| 176 | } |
| 177 | return sel |
| 178 | } |
| 179 | if pt.Description != "" { |
| 180 | if sel, ok := selForSpan(content, li, pt.DescriptionSpan, cursor, parent); ok { |
| 181 | return sel |
| 182 | } |
| 183 | } |
| 184 | return selCommentsAndPostings(content, pt.Comment, pt.HeaderComments, pt.Postings, li, cursor, parent) |
| 185 | } |
| 186 | |
| 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 { |
| 189 | return sel |
| 190 | } |
| 191 | return selCommentsAndPostings(content, at.Comment, at.HeaderComments, at.Postings, li, cursor, parent) |
| 192 | } |
| 193 | |
| 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 { |
| 196 | return sel |
| 197 | } |
| 198 | for i := range d.Subdirectives { |
| 199 | sd := &d.Subdirectives[i] |
| 200 | if sd.Kind == ast.SubdirectiveComment { |
| 201 | if sel, ok := selComment(content, sd.Comment, li, cursor, parent); ok { |
| 202 | return sel |
| 203 | } |
| 204 | continue |
| 205 | } |
| 206 | if sel, ok := selForSpan(content, li, sd.ValueSpan, cursor, parent); ok { |
| 207 | return sel |
| 208 | } |
| 209 | } |
| 210 | return commentOrParent(content, d.Comment, li, cursor, parent) |
| 211 | } |
| 212 | |
| 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 { |
| 215 | return sel |
| 216 | } |
| 217 | if d.FormatSub != nil { |
| 218 | if sel, ok := selAmount(content, &d.FormatSub.Amount, li, cursor, parent); ok { |
| 219 | return sel |
| 220 | } |
| 221 | } |
| 222 | return commentOrParent(content, d.Comment, li, cursor, parent) |
| 223 | } |
| 224 | |
| 225 | func selPostings(content string, postings []ast.Posting, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) (protocol.SelectionRange, bool) { |
| 226 | for i := range postings { |
| 227 | p := &postings[i] |
| 228 | postingSel, ok := selForSpan(content, li, p.Span, cursor, parent) |
| 229 | if !ok { |
| 230 | continue |
| 231 | } |
| 232 | return selPosting(content, postings[i], li, cursor, postingSel), true |
| 233 | } |
| 234 | return protocol.SelectionRange{}, false |
| 235 | } |
| 236 | |
| 237 | func selPosting(content string, p ast.Posting, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) protocol.SelectionRange { |
| 238 | if p.Status != ast.StatusNone { |
| 239 | if sel, ok := selForSpan(content, li, p.StatusSpan, cursor, parent); ok { |
| 240 | return sel |
| 241 | } |
| 242 | } |
| 243 | if sel, ok := selAccount(content, &p.Account, li, cursor, parent); ok { |
| 244 | return sel |
| 245 | } |
| 246 | if sel, ok := selAmount(content, p.Amount, li, cursor, parent); ok { |
| 247 | return sel |
| 248 | } |
| 249 | if p.Cost != nil { |
| 250 | if sel, ok := selForSpan(content, li, p.Cost.Span, cursor, parent); ok { |
| 251 | return sel |
| 252 | } |
| 253 | } |
| 254 | if p.Balance != nil { |
| 255 | if sel, ok := selForSpan(content, li, p.Balance.Span, cursor, parent); ok { |
| 256 | return sel |
| 257 | } |
| 258 | } |
| 259 | if sel, ok := selComment(content, p.Comment, li, cursor, parent); ok { |
| 260 | return sel |
| 261 | } |
| 262 | for i := range p.Comments { |
| 263 | if sel, ok := selComment(content, &p.Comments[i], li, cursor, parent); ok { |
| 264 | return sel |
| 265 | } |
| 266 | } |
| 267 | return parent |
| 268 | } |
| 269 | |
| 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) |
| 272 | if !ok { |
| 273 | return protocol.SelectionRange{}, false |
| 274 | } |
| 275 | if len(a.Name) <= 1 { |
| 276 | return accountSel, true |
| 277 | } |
| 278 | for i := range a.Name { |
| 279 | if sel, ok := selForSpan(content, li, a.Name[i].Span, cursor, accountSel); ok { |
| 280 | return sel, true |
| 281 | } |
| 282 | } |
| 283 | return accountSel, true |
| 284 | } |
| 285 | |
| 286 | func selAmount(content string, am *ast.Amount, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) (protocol.SelectionRange, bool) { |
| 287 | if am == nil { |
| 288 | return protocol.SelectionRange{}, false |
| 289 | } |
| 290 | amountSel, ok := selForSpan(content, li, am.Span, cursor, parent) |
| 291 | if !ok { |
| 292 | return protocol.SelectionRange{}, false |
| 293 | } |
| 294 | if sel, ok := selForSpan(content, li, am.CommoditySpan, cursor, amountSel); ok { |
| 295 | return sel, true |
| 296 | } |
| 297 | qStart, qEnd := quantitySpan(content, am) |
| 298 | if qEnd > qStart { |
| 299 | if sel, ok := selForSpan(content, li, token.Span{Start: token.Pos{Offset: qStart}, End: token.Pos{Offset: qEnd}}, cursor, amountSel); ok { |
| 300 | return sel, true |
| 301 | } |
| 302 | } |
| 303 | return amountSel, true |
| 304 | } |
| 305 | |
| 306 | func selComment(content string, c *ast.Comment, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) (protocol.SelectionRange, bool) { |
| 307 | if c == nil { |
| 308 | return protocol.SelectionRange{}, false |
| 309 | } |
| 310 | commentSel, ok := selForSpan(content, li, c.Span, cursor, parent) |
| 311 | if !ok { |
| 312 | return protocol.SelectionRange{}, false |
| 313 | } |
| 314 | if ref := tagRefInComment(content, c, cursor); ref != nil { |
| 315 | return protocol.SelectionRange{Range: li.SpanRange(ref.span), Parent: &commentSel}, true |
| 316 | } |
| 317 | return commentSel, true |
| 318 | } |
| 319 | |
| 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 | |
| 327 | func commentOrParent(content string, c *ast.Comment, li *lsputil.LineIndex, cursor int, parent protocol.SelectionRange) protocol.SelectionRange { |
| 328 | if sel, ok := selComment(content, c, li, cursor, parent); ok { |
| 329 | return sel |
| 330 | } |
| 331 | return parent |
| 332 | } |