all repos

clerk @ 25c8f80

missing tooling for ledger/hledger

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
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
}