all repos

clerk @ b457e3f78e8397f82f727c2e5d4e335a0d32c9b1

missing tooling for ledger/hledger

clerk/internal/lsp/server.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
	"log/slog"
6
	"sync"
7
8
	"go.lsp.dev/protocol"
9
	"go.lsp.dev/uri"
10
11
	"olexsmir.xyz/clerk/internal/analyzer"
12
	"olexsmir.xyz/clerk/internal/linter"
13
	"olexsmir.xyz/clerk/journal"
14
	"olexsmir.xyz/clerk/journal/printer"
15
)
16
17
type server struct {
18
	protocol.UnimplementedServer
19
20
	client protocol.Client
21
	log    *slog.Logger
22
23
	version, name string
24
25
	linter  *linter.Linter
26
	loader  *journal.Loader
27
	printer *printer.Config
28
29
	mu         sync.Mutex
30
	openDocs   map[uri.URI]docState
31
	diagCancel context.CancelFunc
32
33
	cfgMu  sync.RWMutex
34
	config Config
35
}
36
37
// analysisFor returns the cached analysis for an open doc, rebuilds when the doc or a file it inclues changed.
38
func (s *server) analysisFor(u uri.URI) *analyzer.Analysis {
39
	s.mu.Lock()
40
	state, ok := s.openDocs[u]
41
	if !ok {
42
		s.mu.Unlock()
43
		return nil
44
	}
45
	if !state.dirty {
46
		an := state.analysis
47
		s.mu.Unlock()
48
		return an
49
	}
50
	text := state.text
51
	version := state.version
52
	s.mu.Unlock()
53
54
	an := analyzer.Build(s.loader.ResolveBytes(u.Path(), []byte(text)))
55
56
	s.mu.Lock()
57
	state, ok = s.openDocs[u]
58
	if !ok || state.version != version {
59
		// editot or closed while building. doc stays dirty so te request rebuilds
60
		s.mu.Unlock()
61
		return an
62
	}
63
	state.analysis = an
64
	state.dirty = false
65
	state.paths = make(map[string]bool, len(an.Files))
66
	for _, pf := range an.Files {
67
		state.paths[journal.CanonicalPath(pf.Path)] = true
68
	}
69
	s.openDocs[u] = state
70
	s.mu.Unlock()
71
	return an
72
}
73
74
func (s *server) Initialize(ctx context.Context, params *protocol.InitializeParams) (*protocol.InitializeResult, error) {
75
	s.applySettings(params.InitializationOptions)
76
	return &protocol.InitializeResult{
77
		ServerInfo: protocol.ServerInfo{
78
			Name:    s.name,
79
			Version: protocol.NewOptional(s.version),
80
		},
81
		Capabilities: protocol.ServerCapabilities{
82
			DocumentFormattingProvider: &protocol.DocumentFormattingOptions{},
83
			DefinitionProvider:         protocol.Boolean(true),
84
			HoverProvider:              protocol.Boolean(true),
85
			ReferencesProvider:         protocol.Boolean(true),
86
			WorkspaceSymbolProvider:    protocol.Boolean(true),
87
			RenameProvider: &protocol.RenameOptions{
88
				PrepareProvider: new(true),
89
			},
90
			CompletionProvider: &protocol.CompletionOptions{
91
				TriggerCharacters: []string{":", "@"},
92
			},
93
			TextDocumentSync: &protocol.TextDocumentSyncOptions{
94
				OpenClose: new(true),
95
				Change:    new(protocol.TextDocumentSyncKindFull),
96
			},
97
			SemanticTokensProvider: &protocol.SemanticTokensOptions{
98
				Legend: getSemanticTokensLegend(),
99
				Range:  protocol.Boolean(true),
100
				Full:   protocol.Boolean(true),
101
			},
102
		},
103
	}, nil
104
}
105
106
func (s *server) Initialized(ctx context.Context, params *protocol.InitializedParams) error {
107
	s.scheduleDiagnostics(ctx)
108
	return nil
109
}
110
111
func (s *server) DidChangeConfiguration(ctx context.Context, params *protocol.DidChangeConfigurationParams) error {
112
	s.applySettings(params.Settings)
113
	return nil
114
}
115
116
func (s *server) Shutdown(ctx context.Context) error {
117
	return nil
118
}
119
120
func (s *server) Exit(ctx context.Context) error {
121
	return nil
122
}
123
124
func (s *server) applySettings(v protocol.LSPAny) {
125
	s.cfgMu.Lock()
126
	if err := s.config.merge(v); err != nil {
127
		s.log.Error("failed to merge config", "err", err)
128
	}
129
	s.cfgMu.Unlock()
130
}