all repos

clerk @ 122b384

missing tooling for ledger/hledger

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

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
lsp: implement textDocument/semanticTokens/delta, 1 month ago
1
package lsp
2
3
import (
4
	"context"
5
	"strconv"
6
7
	"go.lsp.dev/protocol"
8
	"go.lsp.dev/uri"
9
10
	"olexsmir.xyz/clerk/internal/analyzer"
11
	"olexsmir.xyz/clerk/internal/lsp/lsputil"
12
	"olexsmir.xyz/clerk/journal"
13
	"olexsmir.xyz/clerk/journal/ast"
14
	"olexsmir.xyz/clerk/journal/lexer"
15
	"olexsmir.xyz/clerk/journal/parser"
16
)
17
18
func (s *server) DidOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error {
19
	s.openDoc(params.TextDocument.URI, params.TextDocument.Text, params.TextDocument.Version, params.TextDocument.LanguageID)
20
	s.scheduleDiagnostics(ctx)
21
	return nil
22
}
23
24
func (s *server) DidChange(ctx context.Context, params *protocol.DidChangeTextDocumentParams) error {
25
	s.updateDoc(params.TextDocument.URI, params.TextDocument.Version, params.ContentChanges)
26
	s.scheduleDiagnostics(ctx)
27
	return nil
28
}
29
30
func (s *server) DidClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error {
31
	u := params.TextDocument.URI
32
	s.closeDoc(u)
33
34
	// clear closed doc's diagnostics; dependents rebuild from disk since the buffer text is gone.
35
	s.markDependentsDirty(u)
36
	if err := s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
37
		URI:         u,
38
		Diagnostics: []protocol.Diagnostic{},
39
	}); err != nil {
40
		s.log.Warn("clear diagnostics failed", "uri", u, "err", err)
41
	}
42
43
	s.scheduleDiagnostics(ctx)
44
	return nil
45
}
46
47
func (s *server) DidSave(ctx context.Context, params *protocol.DidSaveTextDocumentParams) error {
48
	return nil
49
}
50
51
// Document management
52
53
type docState struct {
54
	text       string
55
	version    int32
56
	languageID protocol.LanguageKind
57
	paths      map[string]bool    // canonical paths of every file in the cached analysis
58
	lineIdx    *lsputil.LineIndex // cached line index for the text
59
60
	analysis *analyzer.Analysis // cached analysis, nil until first build
61
	dirty    bool               // true while the cached analysis may not reflect the current text
62
63
	semTokens   []semanticToken // cached tokens for the current text
64
	semBaseline []uint32        // encoded data of the last response; diff baseline for the next delta request
65
	semGen      uint64          // semantic token generation, increments per response; 0 before the first
66
}
67
68
func (d *docState) resultID() string { return strconv.FormatUint(d.semGen, 10) }
69
func (d *docState) nextResultID() string {
70
	d.semGen++
71
	return d.resultID()
72
}
73
74
func (s *server) openDoc(u uri.URI, text string, version int32, langID protocol.LanguageKind) {
75
	s.mu.Lock()
76
	s.openDocs[u] = docState{
77
		text:       text,
78
		version:    version,
79
		languageID: langID,
80
		lineIdx:    lsputil.NewLineIndex(text),
81
		dirty:      true,
82
	}
83
	s.mu.Unlock()
84
}
85
86
func (s *server) updateDoc(u uri.URI, version int32, changes []protocol.TextDocumentContentChangeEvent) {
87
	s.mu.Lock()
88
	state, ok := s.openDocs[u]
89
	if !ok {
90
		s.mu.Unlock()
91
		return
92
	}
93
	state.version = version
94
	for _, ch := range changes {
95
		switch ev := ch.(type) {
96
		case *protocol.TextDocumentContentChangeWholeDocument:
97
			state.text = ev.Text
98
			state.semTokens = nil
99
			state.lineIdx = lsputil.NewLineIndex(ev.Text)
100
		case *protocol.TextDocumentContentChangePartial:
101
			_ = ev // TODO: incremental edit support
102
		}
103
	}
104
	state.analysis = nil
105
	state.dirty = true
106
	s.openDocs[u] = state
107
	s.mu.Unlock()
108
109
	s.markDependentsDirty(u)
110
}
111
112
// markDependentsDirty dirties every open doc whose cached analysis includes u,
113
// e.g. after u is edited or closed.
114
func (s *server) markDependentsDirty(u uri.URI) {
115
	canon := journal.CanonicalPath(u.Path())
116
	s.mu.Lock()
117
	for du, dstate := range s.openDocs {
118
		if dstate.paths[canon] {
119
			dstate.dirty = true
120
			s.openDocs[du] = dstate
121
		}
122
	}
123
	s.mu.Unlock()
124
}
125
126
func (s *server) getDocState(u uri.URI) (docState, bool) {
127
	s.mu.RLock()
128
	state, ok := s.openDocs[u]
129
	s.mu.RUnlock()
130
	return state, ok
131
}
132
133
func (s *server) closeDoc(u uri.URI) {
134
	s.mu.Lock()
135
	delete(s.openDocs, u)
136
	s.mu.Unlock()
137
}
138
139
func parseJournalStr(content string) *ast.Journal {
140
	l := lexer.New("", []byte(content))
141
	p := parser.New(l)
142
	return p.ParseJournal()
143
}