10 files changed,
175 insertions(+),
1 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed at:
2026-08-03 13:20:24 +0300
Authored at:
2026-08-03 12:59:18 +0300
Change ID:
wvlomxxpyznwuyqtrxskvqmzulnmvzms
Parent:
1c9c428
jump to
M
internal/lsp/lsp.go
··· 5 5 "io" 6 6 "log/slog" 7 7 "os" 8 + "path/filepath" 8 9 9 10 "go.lsp.dev/jsonrpc2" 10 11 "go.lsp.dev/protocol" 11 12 "go.lsp.dev/uri" 12 13 13 14 "olexsmir.xyz/clerk/internal/linter" 15 + "olexsmir.xyz/clerk/internal/xdg" 14 16 "olexsmir.xyz/clerk/journal" 15 17 "olexsmir.xyz/clerk/journal/printer" 16 18 ) ··· 18 20 type Server struct{ server *server } 19 21 20 22 func NewServer(version string) Server { 23 + var logger *slog.Logger 24 + logFile, err := openLogFile() 25 + if err == nil { 26 + logger = slog.New(slog.NewTextHandler(logFile, nil)) 27 + } else { 28 + logger = slog.New(slog.NewTextHandler(os.Stderr, nil)) 29 + } 30 + 21 31 return Server{ 22 32 &server{ 23 33 name: "clerk", ··· 30 40 loader: journal.NewLoader(), 31 41 printer: printer.DefaultConfig, 32 42 33 - log: slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{})), 43 + log: logger, 34 44 }, 35 45 } 46 +} 47 + 48 +func openLogFile() (*os.File, error) { 49 + dir, err := xdg.StateDir() 50 + if err != nil { 51 + return nil, err 52 + } 53 + dir = filepath.Join(dir, "clerk") 54 + if err := os.MkdirAll(dir, 0o700); err != nil { 55 + return nil, err 56 + } 57 + return os.OpenFile(filepath.Join(dir, "lsp.log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644) 36 58 } 37 59 38 60 func (s *Server) Run(ctx context.Context, stdin io.ReadCloser, stdout io.WriteCloser) error {
A
internal/xdg/state.go
··· 1 +package xdg 2 + 3 +import ( 4 + "os" 5 + "path/filepath" 6 +) 7 + 8 +// StateDir returns the base directory for user-specific state data, 9 +// per the XDG Base Directory Specification: $XDG_STATE_HOME if it is set to 10 +// an absolute path, otherwise the platform-specific default. 11 +// Vendored from github.com/adrg/xdg (MIT), StateHome. 12 +func StateDir() (string, error) { 13 + if dir := os.Getenv("XDG_STATE_HOME"); dir != "" && filepath.IsAbs(dir) { 14 + return dir, nil 15 + } 16 + home, err := os.UserHomeDir() 17 + if err != nil { 18 + return "", err 19 + } 20 + return defaultStateDir(home), nil 21 +}
A
internal/xdg/state_darwin_test.go
··· 1 +//go:build darwin 2 + 3 +package xdg 4 + 5 +import ( 6 + "path/filepath" 7 + "testing" 8 +) 9 + 10 +func TestStateDirDefault(t *testing.T) { 11 + t.Setenv("XDG_STATE_HOME", "") 12 + t.Setenv("HOME", "/Users/test") 13 + 14 + want := filepath.Join("/Users/test", "Library", "Application Support") 15 + got, err := StateDir() 16 + if err != nil { 17 + t.Fatal(err) 18 + } 19 + if got != want { 20 + t.Fatalf("StateDir() = %q, want %q", got, want) 21 + } 22 +}
A
internal/xdg/state_plan9_test.go
··· 1 +//go:build plan9 2 + 3 +package xdg 4 + 5 +import ( 6 + "path/filepath" 7 + "testing" 8 +) 9 + 10 +func TestStateDirDefault(t *testing.T) { 11 + t.Setenv("XDG_STATE_HOME", "") 12 + t.Setenv("home", "/home/test") 13 + 14 + want := filepath.Join("/home/test", "lib", "state") 15 + got, err := StateDir() 16 + if err != nil { 17 + t.Fatal(err) 18 + } 19 + if got != want { 20 + t.Fatalf("StateDir() = %q, want %q", got, want) 21 + } 22 +}
A
internal/xdg/state_unix_test.go
··· 1 +//go:build aix || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris 2 + 3 +package xdg 4 + 5 +import ( 6 + "path/filepath" 7 + "testing" 8 +) 9 + 10 +func TestStateDirDefault(t *testing.T) { 11 + t.Setenv("XDG_STATE_HOME", "") 12 + t.Setenv("HOME", "/home/test") 13 + 14 + want := filepath.Join("/home/test", ".local", "state") 15 + got, err := StateDir() 16 + if err != nil { 17 + t.Fatal(err) 18 + } 19 + if got != want { 20 + t.Fatalf("StateDir() = %q, want %q", got, want) 21 + } 22 +}
A
internal/xdg/state_windows.go
··· 1 +//go:build windows 2 + 3 +package xdg 4 + 5 +import ( 6 + "os" 7 + "path/filepath" 8 +) 9 + 10 +func defaultStateDir(home string) string { 11 + if dir := os.Getenv("LOCALAPPDATA"); dir != "" { 12 + return dir 13 + } 14 + return filepath.Join(home, "AppData", "Local") 15 +}
A
internal/xdg/state_windows_test.go
··· 1 +//go:build windows 2 + 3 +package xdg 4 + 5 +import ( 6 + "path/filepath" 7 + "testing" 8 +) 9 + 10 +func TestStateDirDefault(t *testing.T) { 11 + t.Setenv("XDG_STATE_HOME", "") 12 + t.Setenv("LOCALAPPDATA", "") 13 + t.Setenv("USERPROFILE", `C:\Users\test`) 14 + 15 + want := filepath.Join(`C:\Users\test`, "AppData", "Local") 16 + got, err := StateDir() 17 + if err != nil { 18 + t.Fatal(err) 19 + } 20 + if got != want { 21 + t.Fatalf("StateDir() = %q, want %q", got, want) 22 + } 23 +}