all repos

clerk @ c2e8832

missing tooling for ledger/hledger

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

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
lsp: cache per-document analyses with include-aware invalidation..., 1 month ago
1
package lsp
2
3
import (
4
	"context"
5
	"io"
6
	"log/slog"
7
	"os"
8
	"path/filepath"
9
10
	"go.lsp.dev/jsonrpc2"
11
	"go.lsp.dev/protocol"
12
	"go.lsp.dev/uri"
13
14
	"olexsmir.xyz/clerk/internal/linter"
15
	"olexsmir.xyz/clerk/internal/xdg"
16
	"olexsmir.xyz/clerk/journal"
17
	"olexsmir.xyz/clerk/journal/printer"
18
)
19
20
type Server struct{ server *server }
21
22
func NewServer(version string) Server {
23
	logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
24
	if logFile, err := openLogFile(); err == nil {
25
		logger = slog.New(slog.NewTextHandler(logFile, nil))
26
	}
27
28
	srv := &server{
29
		name:    "clerk",
30
		version: version,
31
32
		openDocs: make(map[uri.URI]docState),
33
34
		config:  DefaultConfig,
35
		linter:  linter.NewLinter(linter.Rules),
36
		loader:  journal.NewLoader(),
37
		printer: printer.DefaultConfig,
38
39
		log: logger,
40
	}
41
	srv.loader.ContentProvider = srv.bufferContent
42
	return Server{srv}
43
}
44
45
func (s *Server) Run(ctx context.Context, stdin io.ReadCloser, stdout io.WriteCloser) error {
46
	stream := jsonrpc2.NewStream(readWriterCloser{
47
		Reader: stdin,
48
		Writer: stdout,
49
		Closer: stdin,
50
	})
51
52
	_, conn, client := protocol.NewServer(ctx, s.server, stream)
53
	defer conn.Close()
54
55
	s.server.client = client
56
57
	<-conn.Done()
58
	return conn.Err()
59
}
60
61
// bufferContent returns the open buffer text for a path, if any.
62
// called by the loader during include resolution; must NOT hold the loader lock.
63
func (s *server) bufferContent(path string) ([]byte, bool) {
64
	s.mu.Lock()
65
	defer s.mu.Unlock()
66
	st, ok := s.openDocs[uri.File(path)]
67
	return []byte(st.text), ok
68
}
69
70
type readWriterCloser struct {
71
	io.Reader
72
	io.Writer
73
	io.Closer
74
}
75
76
func openLogFile() (*os.File, error) {
77
	dir, err := xdg.StateDir()
78
	if err != nil {
79
		return nil, err
80
	}
81
	dir = filepath.Join(dir, "clerk")
82
	if err := os.MkdirAll(dir, 0o700); err != nil {
83
		return nil, err
84
	}
85
	return os.OpenFile(filepath.Join(dir, "lsp.log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
86
}