9 files changed,
220 insertions(+),
10 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed at:
2026-07-11 12:58:15 +0300
Authored at:
2026-06-28 14:55:36 +0300
Change ID:
uxpownpksskyorzxoovtvluoroprzqwu
Parent:
e62c721
M
go.mod
··· 4 4 5 5 tool golang.org/x/tools/cmd/stringer 6 6 7 -require github.com/urfave/cli/v3 v3.10.0 7 +require ( 8 + github.com/urfave/cli/v3 v3.10.0 9 + go.lsp.dev/jsonrpc2 v1.0.1 10 + go.lsp.dev/protocol v1.0.1 11 +) 8 12 9 13 require ( 14 + github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68 // indirect 15 + go.lsp.dev/uri v1.0.1 // indirect 10 16 golang.org/x/mod v0.36.0 // indirect 11 17 golang.org/x/sync v0.20.0 // indirect 12 18 golang.org/x/tools v0.45.0 // indirect
M
go.sum
··· 1 1 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 2 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 4 -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 3 +github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68 h1:KZaTBSyshWX3MP5jukJcNSuXDQTO+rNpt0J564dX/eg= 4 +github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg= 5 +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 6 +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 5 7 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 8 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 9 github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 8 10 github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 9 11 github.com/urfave/cli/v3 v3.10.0 h1:0aU8yOObVDMkM13Cj4G+zb4P0PdeJMec65f81Ak1ioM= 10 12 github.com/urfave/cli/v3 v3.10.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= 13 +go.lsp.dev/jsonrpc2 v1.0.1 h1:JZAchpm6RcOuwEOnZ7kF3kA1EpEjdJZ/6rMSfOF3+F4= 14 +go.lsp.dev/jsonrpc2 v1.0.1/go.mod h1:WN+TesqrPumBmUdsuWyn9DHq3zl4i85L/qG0NLdj8qg= 15 +go.lsp.dev/protocol v1.0.1 h1:kLWUIk1v/deXxo1D0VO4avjZnfeuZ82mouJgJd/NbdU= 16 +go.lsp.dev/protocol v1.0.1/go.mod h1:e//i41aEqc9ml32xiNuneUwbjOjuvTA4+uv+AkssJgk= 17 +go.lsp.dev/uri v1.0.1 h1:ywxJN1UXoQ+v0I9lkoQNMmsm7lub5G9ZG7KXT8YTx30= 18 +go.lsp.dev/uri v1.0.1/go.mod h1:06a0ghafs4PuqkLsJGUmlgPHw6+kH79edRXjOT/rA8s= 11 19 golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= 12 20 golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= 13 21 golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
A
internal/cli/cmd_lsp.go
··· 1 +package cli 2 + 3 +import ( 4 + "context" 5 + "os" 6 + 7 + "github.com/urfave/cli/v3" 8 + 9 + "olexsmir.xyz/clerk/internal/lsp" 10 +) 11 + 12 +func (c *Cli) lspAction(ctx context.Context, cmd *cli.Command) error { 13 + server := lsp.NewServer(c.version) 14 + 15 + return server.Run(ctx, os.Stdin, os.Stdout) 16 +}
A
internal/lsp/lsp.go
··· 1 +package lsp 2 + 3 +import ( 4 + "context" 5 + "io" 6 + 7 + "go.lsp.dev/jsonrpc2" 8 + "go.lsp.dev/protocol" 9 + "go.lsp.dev/uri" 10 + 11 + "olexsmir.xyz/clerk/internal/linter" 12 + "olexsmir.xyz/clerk/journal/printer" 13 + "olexsmir.xyz/clerk/journal/semantic" 14 +) 15 + 16 +type Server struct { 17 + sema *semantic.Context 18 + linter *linter.Linter 19 + 20 + version string 21 + server *server 22 +} 23 + 24 +func NewServer(version string) *Server { 25 + return &Server{ 26 + // sema: sema, 27 + // linter: linter, 28 + server: &server{ 29 + version: version, 30 + printerCfg: printer.DefaultConfig, 31 + docs: make(map[uri.URI]docState), 32 + }, 33 + } 34 +} 35 + 36 +func (s *Server) Run(ctx context.Context, stdin io.ReadCloser, stdout io.WriteCloser) error { 37 + stream := jsonrpc2.NewStream(readWriterCloser{ 38 + Reader: stdin, 39 + Writer: stdout, 40 + Closer: stdin, 41 + }) 42 + 43 + _, conn, client := protocol.NewServer(ctx, s.server, stream) 44 + defer conn.Close() 45 + 46 + s.server.client = client 47 + 48 + <-conn.Done() 49 + return conn.Err() 50 +} 51 + 52 +type readWriterCloser struct { 53 + io.Reader 54 + io.Writer 55 + io.Closer 56 +}
A
internal/lsp/server.go
··· 1 +package lsp 2 + 3 +import ( 4 + "context" 5 + "sync" 6 + 7 + "go.lsp.dev/protocol" 8 + "go.lsp.dev/uri" 9 + 10 + "olexsmir.xyz/clerk/journal/printer" 11 +) 12 + 13 +type docState struct { 14 + text string 15 + version int32 16 + languageID protocol.LanguageKind 17 +} 18 + 19 +type server struct { 20 + protocol.UnimplementedServer 21 + 22 + client protocol.Client 23 + version string 24 + 25 + printerCfg *printer.Config 26 + 27 + mu sync.RWMutex 28 + docs map[uri.URI]docState 29 +} 30 + 31 +func (s *server) Initialize(ctx context.Context, params *protocol.InitializeParams) (*protocol.InitializeResult, error) { 32 + return &protocol.InitializeResult{ 33 + ServerInfo: protocol.ServerInfo{ 34 + Name: "clerk", 35 + Version: protocol.NewOptional(s.version), 36 + }, 37 + Capabilities: protocol.ServerCapabilities{ 38 + DocumentFormattingProvider: &protocol.DocumentFormattingOptions{}, 39 + TextDocumentSync: &protocol.TextDocumentSyncOptions{ 40 + OpenClose: new(true), 41 + Change: new(protocol.TextDocumentSyncKindFull), // TODO: Incremental 42 + }, 43 + // TODO: diagnostics provider 44 + // TODO: completion 45 + }, 46 + }, nil 47 +} 48 + 49 +func (s *server) Initialized(ctx context.Context, params *protocol.InitializedParams) error { 50 + return nil 51 +} 52 + 53 +func (s *server) Shutdown(ctx context.Context) error { 54 + return nil 55 +} 56 + 57 +func (s *server) Exit(ctx context.Context) error { 58 + return nil 59 +}
A
internal/lsp/textdocument_sync.go
··· 1 +package lsp 2 + 3 +import ( 4 + "context" 5 + 6 + "go.lsp.dev/protocol" 7 + "go.lsp.dev/uri" 8 +) 9 + 10 +func (s *server) DidOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error { 11 + s.mu.Lock() 12 + s.docs[params.TextDocument.URI] = docState{ 13 + text: params.TextDocument.Text, 14 + version: params.TextDocument.Version, 15 + languageID: params.TextDocument.LanguageID, 16 + } 17 + s.mu.Unlock() 18 + return nil 19 +} 20 + 21 +func (s *server) DidChange(ctx context.Context, params *protocol.DidChangeTextDocumentParams) error { 22 + s.mu.Lock() 23 + defer s.mu.Unlock() 24 + 25 + state, ok := s.docs[params.TextDocument.URI] 26 + if !ok { 27 + return nil 28 + } 29 + 30 + state.version = params.TextDocument.Version 31 + for _, ch := range params.ContentChanges { 32 + switch ev := ch.(type) { 33 + case *protocol.TextDocumentContentChangeWholeDocument: 34 + state.text = ev.Text 35 + case *protocol.TextDocumentContentChangePartial: 36 + // TODO: incremental edit — apply range replacement 37 + _ = ev 38 + } 39 + } 40 + s.docs[params.TextDocument.URI] = state 41 + return nil 42 +} 43 + 44 +func (s *server) DidClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error { 45 + s.mu.Lock() 46 + defer s.mu.Unlock() 47 + delete(s.docs, params.TextDocument.URI) 48 + return nil 49 +} 50 + 51 +func (s *server) DidSave(ctx context.Context, params *protocol.DidSaveTextDocumentParams) error { 52 + return nil 53 +} 54 + 55 +func (s *server) GetText(uri uri.URI) (string, bool) { 56 + s.mu.RLock() 57 + defer s.mu.RUnlock() 58 + state, ok := s.docs[uri] 59 + return state.text, ok 60 +}
M
journal/printer/printer.go
··· 34 34 CommodityPos CommodityPos // where to place commodity 35 35 } 36 36 37 -var defaultConfig = &Config{ 37 +var DefaultConfig = &Config{ 38 38 TabIndent: false, 39 39 IndentWidth: 2, 40 40 PreserveBlankLines: false, ··· 63 63 } 64 64 65 65 // Fprint formats using the default config. 66 -func Fprint(w io.Writer, j *ast.Journal) error { return defaultConfig.Fprint(w, j) } 66 +func Fprint(w io.Writer, j *ast.Journal) error { return DefaultConfig.Fprint(w, j) } 67 67 68 68 // Fprint formats a parsed journal. 69 69 func (c *Config) Fprint(w io.Writer, j *ast.Journal) error { 70 70 if c == nil { 71 - c = defaultConfig 71 + c = DefaultConfig 72 72 } 73 73 p := printer{cfg: c, indent: c.indent()} 74 74 ··· 83 83 } 84 84 85 85 // FprintEntry formats a single ast entry using the default config. 86 -func FprintEntry(w io.Writer, e ast.Entry) error { return defaultConfig.FprintEntry(w, e) } 86 +func FprintEntry(w io.Writer, e ast.Entry) error { return DefaultConfig.FprintEntry(w, e) } 87 87 88 88 // FprintEntry formats a single journal entry. 89 89 func (c *Config) FprintEntry(w io.Writer, e ast.Entry) error { 90 90 if c == nil { 91 - c = defaultConfig 91 + c = DefaultConfig 92 92 } 93 93 p := printer{cfg: c, indent: c.indent()} 94 94 p.formatEntry(e)
M
journal/printer/printer_test.go
··· 20 20 t.Fatal(err) 21 21 } 22 22 var b strings.Builder 23 - if err := defaultConfig.Fprint(&b, pf.Ast); err != nil { 23 + if err := DefaultConfig.Fprint(&b, pf.Ast); err != nil { 24 24 t.Fatal(err) 25 25 } 26 26 ··· 41 41 b.ResetTimer() 42 42 for i := 0; i < b.N; i++ { 43 43 var buf strings.Builder 44 - if err := defaultConfig.Fprint(&buf, pf.Ast); err != nil { 44 + if err := DefaultConfig.Fprint(&buf, pf.Ast); err != nil { 45 45 b.Fatal(err) 46 46 } 47 47 }