all repos

clerk @ b238a8c852e3f47486117f346684c4e92f7b98b1

missing tooling for ledger/hledger

clerk/internal/lsp/workspace_symbol.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
lsp: workspace/symbol and textdocument/references, 1 month ago
1
package lsp
2
3
import (
4
	"context"
5
	"sort"
6
7
	"go.lsp.dev/protocol"
8
	"go.lsp.dev/uri"
9
10
	"olexsmir.xyz/clerk/internal/analyzer"
11
	"olexsmir.xyz/clerk/internal/lsp/fuzzy"
12
)
13
14
func (s *server) Symbols(_ context.Context, params *protocol.WorkspaceSymbolParams) (protocol.WorkspaceSymbolResult, error) {
15
	if params.Query == "" {
16
		return nil, nil
17
	}
18
19
	s.mu.Lock()
20
	var u uri.URI
21
	for u = range s.openDocs {
22
		break
23
	}
24
	s.mu.Unlock()
25
	an := s.analysisFor(u)
26
	if an == nil {
27
		return nil, nil
28
	}
29
30
	symbols := searchSymbols(an, params.Query)
31
	if len(symbols) == 0 {
32
		return nil, nil
33
	}
34
	return protocol.WorkspaceSymbolSlice(symbols), nil
35
}
36
37
const maxSymbolResults = 100
38
39
type scoredSymbol struct {
40
	kind  symbolKind
41
	name  string
42
	score float64
43
}
44
45
func searchSymbols(an *analyzer.Analysis, query string) []protocol.WorkspaceSymbol {
46
	matcher := fuzzy.Compile(query)
47
48
	scored := make([]scoredSymbol, 0, len(an.AccountNames)+len(an.Commodities)+len(an.PayeeNames)+len(an.TagNames))
49
	add := func(kind symbolKind, name string) {
50
		if score := matcher.Score(name); score > 0 {
51
			scored = append(scored, scoredSymbol{kind, name, score})
52
		}
53
	}
54
	for _, name := range an.AccountNames {
55
		add(symbolAccount, name)
56
	}
57
	for name := range an.Commodities {
58
		add(symbolCommodity, name)
59
	}
60
	for _, name := range an.PayeeNames {
61
		add(symbolPayee, name)
62
	}
63
	for _, name := range an.TagNames {
64
		add(symbolTag, name)
65
	}
66
67
	sortScoredSymbols(scored)
68
	if len(scored) > maxSymbolResults {
69
		scored = scored[:maxSymbolResults]
70
	}
71
72
	symbols := make([]protocol.WorkspaceSymbol, 0, len(scored))
73
	for _, s := range scored {
74
		loc := definitionLocation(an, s.kind, s.name)
75
		if loc == nil {
76
			continue
77
		}
78
		symbols = append(symbols, protocol.WorkspaceSymbol{
79
			BaseSymbolInformation: protocol.BaseSymbolInformation{
80
				Name: s.name,
81
				Kind: s.kind.ToProtocol(),
82
			},
83
			Location: loc,
84
		})
85
	}
86
	return symbols
87
}
88
89
func definitionLocation(an *analyzer.Analysis, kind symbolKind, name string) *protocol.Location {
90
	switch kind {
91
	case symbolAccount:
92
		return findAccountDefinition(an, name)
93
	case symbolCommodity:
94
		return findCommodityDefinition(an, name)
95
	case symbolPayee:
96
		return findPayeeDefinition(an, name)
97
	case symbolTag:
98
		return findTagDefinition(an, name)
99
	}
100
	return nil
101
}
102
103
func sortScoredSymbols(scored []scoredSymbol) {
104
	sort.Slice(scored, func(i, j int) bool {
105
		if scored[i].score != scored[j].score {
106
			return scored[i].score > scored[j].score
107
		}
108
		return scored[i].name < scored[j].name
109
	})
110
}