@@ -8,13 +8,13 @@ import (
"maps"
"os"
"path/filepath"
- "strings"
"sync"
"sync/atomic"
"time"
"github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/csync"
+ "github.com/charmbracelet/crush/internal/fsext"
"github.com/charmbracelet/crush/internal/home"
powernap "github.com/charmbracelet/x/powernap/pkg/lsp"
"github.com/charmbracelet/x/powernap/pkg/lsp/protocol"
@@ -35,7 +35,7 @@ type Client struct {
debug bool
// Working directory this LSP is scoped to.
- workDir string
+ cwd string
// File types this LSP server handles (e.g., .go, .rs, .py)
fileTypes []string
@@ -66,7 +66,14 @@ type Client struct {
}
// New creates a new LSP client using the powernap implementation.
-func New(ctx context.Context, name string, cfg config.LSPConfig, resolver config.VariableResolver, debug bool) (*Client, error) {
+func New(
+ ctx context.Context,
+ name string,
+ cfg config.LSPConfig,
+ resolver config.VariableResolver,
+ cwd string,
+ debug bool,
+) (*Client, error) {
client := &Client{
name: name,
fileTypes: cfg.FileTypes,
@@ -76,6 +83,7 @@ func New(ctx context.Context, name string, cfg config.LSPConfig, resolver config
ctx: ctx,
debug: debug,
resolver: resolver,
+ cwd: cwd,
}
client.serverState.Store(StateStarting)
@@ -134,13 +142,7 @@ func (c *Client) Close(ctx context.Context) error {
// createPowernapClient creates a new powernap client with the current configuration.
func (c *Client) createPowernapClient() error {
- workDir, err := os.Getwd()
- if err != nil {
- return fmt.Errorf("failed to get working directory: %w", err)
- }
-
- rootURI := string(protocol.URIFromPath(workDir))
- c.workDir = workDir
+ rootURI := string(protocol.URIFromPath(c.cwd))
command, err := c.resolver.ResolveValue(c.config.Command)
if err != nil {
@@ -157,7 +159,7 @@ func (c *Client) createPowernapClient() error {
WorkspaceFolders: []protocol.WorkspaceFolder{
{
URI: rootURI,
- Name: filepath.Base(workDir),
+ Name: filepath.Base(c.cwd),
},
},
}
@@ -321,15 +323,8 @@ type OpenFileInfo struct {
// HandlesFile checks if this LSP client handles the given file based on its
// extension and whether it's within the working directory.
func (c *Client) HandlesFile(path string) bool {
- // Check if file is within working directory.
- absPath, err := filepath.Abs(path)
- if err != nil {
- slog.Debug("Cannot resolve path", "name", c.name, "file", path, "error", err)
- return false
- }
- relPath, err := filepath.Rel(c.workDir, absPath)
- if err != nil || strings.HasPrefix(relPath, "..") {
- slog.Debug("File outside workspace", "name", c.name, "file", path, "workDir", c.workDir)
+ if !fsext.HasPrefix(path, c.cwd) {
+ slog.Debug("File outside workspace", "name", c.name, "file", path, "workDir", c.cwd)
return false
}
return handlesFiletype(c.name, c.fileTypes, path)
@@ -472,31 +467,6 @@ func (c *Client) OpenFileOnDemand(ctx context.Context, filepath string) error {
return c.OpenFile(ctx, filepath)
}
-// GetDiagnosticsForFile ensures a file is open and returns its diagnostics.
-func (c *Client) GetDiagnosticsForFile(ctx context.Context, filepath string) ([]protocol.Diagnostic, error) {
- documentURI := protocol.URIFromPath(filepath)
-
- // Make sure the file is open
- if !c.IsFileOpen(filepath) {
- if err := c.OpenFile(ctx, filepath); err != nil {
- return nil, fmt.Errorf("failed to open file for diagnostics: %w", err)
- }
-
- // Give the LSP server a moment to process the file
- time.Sleep(100 * time.Millisecond)
- }
-
- // Get diagnostics
- diagnostics, _ := c.diagnostics.Get(documentURI)
-
- return diagnostics, nil
-}
-
-// ClearDiagnosticsForURI removes diagnostics for a specific URI from the cache.
-func (c *Client) ClearDiagnosticsForURI(uri protocol.DocumentURI) {
- c.diagnostics.Del(uri)
-}
-
// RegisterNotificationHandler registers a notification handler.
func (c *Client) RegisterNotificationHandler(method string, handler transport.NotificationHandler) {
c.client.RegisterNotificationHandler(method, handler)
@@ -507,11 +477,6 @@ func (c *Client) RegisterServerRequestHandler(method string, handler transport.H
c.client.RegisterHandler(method, handler)
}
-// DidChangeWatchedFiles sends a workspace/didChangeWatchedFiles notification to the server.
-func (c *Client) DidChangeWatchedFiles(ctx context.Context, params protocol.DidChangeWatchedFilesParams) error {
- return c.client.NotifyDidChangeWatchedFiles(ctx, params.Changes)
-}
-
// openKeyConfigFiles opens important configuration files that help initialize the server.
func (c *Client) openKeyConfigFiles(ctx context.Context) {
wd, err := os.Getwd()
@@ -23,7 +23,7 @@ func TestClient(t *testing.T) {
// but we can still test the basic structure
client, err := New(ctx, "test", cfg, config.NewEnvironmentVariableResolver(env.NewFromMap(map[string]string{
"THE_CMD": "echo",
- })), false)
+ })), ".", false)
if err != nil {
// Expected to fail with echo command, skip the rest
t.Skipf("Powernap client creation failed as expected with dummy command: %v", err)
@@ -65,8 +65,8 @@ func NewManager(cfg *config.Config) *Manager {
}
// Clients returns the map of LSP clients.
-func (m *Manager) Clients() *csync.Map[string, *Client] {
- return m.clients
+func (s *Manager) Clients() *csync.Map[string, *Client] {
+ return s.clients
}
// SetCallback sets a callback that is invoked when a new LSP
@@ -79,13 +79,17 @@ func (s *Manager) SetCallback(cb func(name string, client *Client)) {
// Start starts an LSP server that can handle the given file path.
// If an appropriate LSP is already running, this is a no-op.
-func (s *Manager) Start(ctx context.Context, filePath string) {
+func (s *Manager) Start(ctx context.Context, path string) {
+ if !fsext.HasPrefix(path, s.cfg.WorkingDir()) {
+ return
+ }
+
s.mu.Lock()
defer s.mu.Unlock()
var wg sync.WaitGroup
for name, server := range s.manager.GetServers() {
- if !handles(server, filePath, s.cfg.WorkingDir()) {
+ if !handles(server, path, s.cfg.WorkingDir()) {
continue
}
wg.Go(func() {
@@ -150,7 +154,14 @@ func (s *Manager) startServer(ctx context.Context, name string, server *powernap
return
}
}
- client, err := New(ctx, name, cfg, s.cfg.Resolver(), s.cfg.Options.DebugLSP)
+ client, err := New(
+ ctx,
+ name,
+ cfg,
+ s.cfg.Resolver(),
+ s.cfg.WorkingDir(),
+ s.cfg.Options.DebugLSP,
+ )
if err != nil {
slog.Error("Failed to create LSP client", "name", name, "error", err)
return