Merge remote-tracking branch 'origin/main' into list

This commit is contained in:
Kujtim Hoxha
2025-07-26 12:27:44 +02:00
10 changed files with 132 additions and 23 deletions

View File

@@ -110,6 +110,7 @@ homebrew_casks:
- repository: - repository:
owner: charmbracelet owner: charmbracelet
name: homebrew-tap name: homebrew-tap
token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
npms: npms:
- name: "@charmland/crush" - name: "@charmland/crush"
@@ -134,6 +135,32 @@ nfpms:
- src: ./manpages/crush.1.gz - src: ./manpages/crush.1.gz
dst: /usr/share/man/man1/crush.1.gz dst: /usr/share/man/man1/crush.1.gz
nix:
- repository:
owner: "charmbracelet"
name: nur
token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
extra_install: |-
installManPage ./manpages/crush.1.gz.
installShellCompletion ./completions/*
winget:
- publisher: charmbracelet
copyright: Charmbracelet, Inc
repository:
owner: "charmbracelet"
name: winget-pkgs
token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
branch: "crush-{{.Version}}"
pull_request:
enabled: true
draft: false
check_boxes: true
base:
owner: microsoft
name: winget-pkgs
branch: master
changelog: changelog:
sort: asc sort: asc
disable: "{{ .IsNightly }}" disable: "{{ .IsNightly }}"

View File

@@ -17,7 +17,7 @@ import (
"github.com/charmbracelet/crush/internal/log" "github.com/charmbracelet/crush/internal/log"
) )
const catwalkURL = "https://catwalk.charm.sh" const defaultCatwalkURL = "https://catwalk.charm.sh"
// LoadReader config via io.Reader. // LoadReader config via io.Reader.
func LoadReader(fd io.Reader) (*Config, error) { func LoadReader(fd io.Reader) (*Config, error) {

View File

@@ -1,6 +1,7 @@
package config package config
import ( import (
"cmp"
"encoding/json" "encoding/json"
"fmt" "fmt"
"log/slog" "log/slog"
@@ -74,6 +75,7 @@ func loadProvidersFromCache(path string) ([]catwalk.Provider, error) {
} }
func Providers() ([]catwalk.Provider, error) { func Providers() ([]catwalk.Provider, error) {
catwalkURL := cmp.Or(os.Getenv("CATWALK_URL"), defaultCatwalkURL)
client := catwalk.NewWithURL(catwalkURL) client := catwalk.NewWithURL(catwalkURL)
path := providerCacheFileData() path := providerCacheFileData()
return loadProvidersOnce(client, path) return loadProvidersOnce(client, path)

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"log/slog"
"maps" "maps"
"sort" "sort"
"strings" "strings"
@@ -118,7 +119,13 @@ func waitForLspDiagnostics(ctx context.Context, filePath string, lsps map[string
return return
} }
if diagParams.URI.Path() == filePath || hasDiagnosticsChanged(client.GetDiagnostics(), originalDiags) { path, err := diagParams.URI.Path()
if err != nil {
slog.Error("Failed to convert diagnostic URI to path", "uri", diagParams.URI, "error", err)
return
}
if path == filePath || hasDiagnosticsChanged(client.GetDiagnostics(), originalDiags) {
select { select {
case diagChan <- struct{}{}: case diagChan <- struct{}{}:
default: default:
@@ -216,10 +223,15 @@ func getDiagnostics(filePath string, lsps map[string]*lsp.Client) string {
diagnostics := client.GetDiagnostics() diagnostics := client.GetDiagnostics()
if len(diagnostics) > 0 { if len(diagnostics) > 0 {
for location, diags := range diagnostics { for location, diags := range diagnostics {
isCurrentFile := location.Path() == filePath path, err := location.Path()
if err != nil {
slog.Error("Failed to convert diagnostic location URI to path", "uri", location, "error", err)
continue
}
isCurrentFile := path == filePath
for _, diag := range diags { for _, diag := range diags {
formattedDiag := formatDiagnostic(location.Path(), diag, lspName) formattedDiag := formatDiagnostic(path, diag, lspName)
if isCurrentFile { if isCurrentFile {
fileDiagnostics = append(fileDiagnostics, formattedDiag) fileDiagnostics = append(fileDiagnostics, formattedDiag)

View File

@@ -449,7 +449,12 @@ func (c *Client) pingTypeScriptServer(ctx context.Context) error {
// If we have any open files, try to get document symbols for one // If we have any open files, try to get document symbols for one
for uri := range c.openFiles { for uri := range c.openFiles {
filePath := protocol.DocumentURI(uri).Path() filePath, err := protocol.DocumentURI(uri).Path()
if err != nil {
slog.Error("Failed to convert URI to path for TypeScript symbol collection", "uri", uri, "error", err)
continue
}
if strings.HasSuffix(filePath, ".ts") || strings.HasSuffix(filePath, ".js") || if strings.HasSuffix(filePath, ".ts") || strings.HasSuffix(filePath, ".js") ||
strings.HasSuffix(filePath, ".tsx") || strings.HasSuffix(filePath, ".jsx") { strings.HasSuffix(filePath, ".tsx") || strings.HasSuffix(filePath, ".jsx") {
var symbols []protocol.DocumentSymbol var symbols []protocol.DocumentSymbol
@@ -712,7 +717,11 @@ func (c *Client) CloseAllFiles(ctx context.Context) {
// First collect all URIs that need to be closed // First collect all URIs that need to be closed
for uri := range c.openFiles { for uri := range c.openFiles {
// Convert URI back to file path using proper URI handling // Convert URI back to file path using proper URI handling
filePath := protocol.DocumentURI(uri).Path() filePath, err := protocol.DocumentURI(uri).Path()
if err != nil {
slog.Error("Failed to convert URI to path for file closing", "uri", uri, "error", err)
continue
}
filesToClose = append(filesToClose, filePath) filesToClose = append(filesToClose, filePath)
} }
c.openFilesMu.Unlock() c.openFilesMu.Unlock()

View File

@@ -2,6 +2,7 @@ package protocol
import ( import (
"fmt" "fmt"
"log/slog"
) )
// PatternInfo is an interface for types that represent glob patterns // PatternInfo is an interface for types that represent glob patterns
@@ -36,21 +37,36 @@ func (g *GlobPattern) AsPattern() (PatternInfo, error) {
return nil, fmt.Errorf("nil pattern") return nil, fmt.Errorf("nil pattern")
} }
var err error
switch v := g.Value.(type) { switch v := g.Value.(type) {
case string: case string:
return StringPattern{Pattern: v}, nil return StringPattern{Pattern: v}, nil
case RelativePattern: case RelativePattern:
// Handle BaseURI which could be string or DocumentUri // Handle BaseURI which could be string or DocumentUri
basePath := "" basePath := ""
switch baseURI := v.BaseURI.Value.(type) { switch baseURI := v.BaseURI.Value.(type) {
case string: case string:
basePath = DocumentURI(baseURI).Path() basePath, err = DocumentURI(baseURI).Path()
if err != nil {
slog.Error("Failed to convert URI to path", "uri", baseURI, "error", err)
return nil, fmt.Errorf("invalid URI: %s", baseURI)
}
case DocumentURI: case DocumentURI:
basePath = baseURI.Path() basePath, err = baseURI.Path()
if err != nil {
slog.Error("Failed to convert DocumentURI to path", "uri", baseURI, "error", err)
return nil, fmt.Errorf("invalid DocumentURI: %s", baseURI)
}
default: default:
return nil, fmt.Errorf("unknown BaseURI type: %T", v.BaseURI.Value) return nil, fmt.Errorf("unknown BaseURI type: %T", v.BaseURI.Value)
} }
return RelativePatternInfo{RP: v, BasePath: basePath}, nil return RelativePatternInfo{RP: v, BasePath: basePath}, nil
default: default:
return nil, fmt.Errorf("unknown pattern type: %T", g.Value) return nil, fmt.Errorf("unknown pattern type: %T", g.Value)
} }

View File

@@ -70,7 +70,7 @@ func (uri *DocumentURI) UnmarshalText(data []byte) (err error) {
// DocumentUri("").Path() returns the empty string. // DocumentUri("").Path() returns the empty string.
// //
// Path panics if called on a URI that is not a valid filename. // Path panics if called on a URI that is not a valid filename.
func (uri DocumentURI) Path() string { func (uri DocumentURI) Path() (string, error) {
filename, err := filename(uri) filename, err := filename(uri)
if err != nil { if err != nil {
// e.g. ParseRequestURI failed. // e.g. ParseRequestURI failed.
@@ -79,22 +79,33 @@ func (uri DocumentURI) Path() string {
// direct string manipulation; all DocumentUris // direct string manipulation; all DocumentUris
// received from the client pass through // received from the client pass through
// ParseRequestURI, which ensures validity. // ParseRequestURI, which ensures validity.
panic(err) return "", fmt.Errorf("invalid URI %q: %w", uri, err)
} }
return filepath.FromSlash(filename) return filepath.FromSlash(filename), nil
} }
// Dir returns the URI for the directory containing the receiver. // Dir returns the URI for the directory containing the receiver.
func (uri DocumentURI) Dir() DocumentURI { func (uri DocumentURI) Dir() (DocumentURI, error) {
// XXX: Legacy comment:
// This function could be more efficiently implemented by avoiding any call // This function could be more efficiently implemented by avoiding any call
// to Path(), but at least consolidates URI manipulation. // to Path(), but at least consolidates URI manipulation.
return URIFromPath(uri.DirPath())
path, err := uri.DirPath()
if err != nil {
return "", fmt.Errorf("invalid URI %q: %w", uri, err)
}
return URIFromPath(path), nil
} }
// DirPath returns the file path to the directory containing this URI, which // DirPath returns the file path to the directory containing this URI, which
// must be a file URI. // must be a file URI.
func (uri DocumentURI) DirPath() string { func (uri DocumentURI) DirPath() (string, error) {
return filepath.Dir(uri.Path()) path, err := uri.Path()
if err != nil {
return "", err
}
return filepath.Dir(path), nil
} }
func filename(uri DocumentURI) (string, error) { func filename(uri DocumentURI) (string, error) {

View File

@@ -11,7 +11,10 @@ import (
) )
func applyTextEdits(uri protocol.DocumentURI, edits []protocol.TextEdit) error { func applyTextEdits(uri protocol.DocumentURI, edits []protocol.TextEdit) error {
path := uri.Path() path, err := uri.Path()
if err != nil {
return fmt.Errorf("invalid URI: %w", err)
}
// Read the file content // Read the file content
content, err := os.ReadFile(path) content, err := os.ReadFile(path)
@@ -148,7 +151,11 @@ func applyTextEdit(lines []string, edit protocol.TextEdit) ([]string, error) {
// applyDocumentChange applies a DocumentChange (create/rename/delete operations) // applyDocumentChange applies a DocumentChange (create/rename/delete operations)
func applyDocumentChange(change protocol.DocumentChange) error { func applyDocumentChange(change protocol.DocumentChange) error {
if change.CreateFile != nil { if change.CreateFile != nil {
path := change.CreateFile.URI.Path() path, err := change.CreateFile.URI.Path()
if err != nil {
return fmt.Errorf("invalid URI: %w", err)
}
if change.CreateFile.Options != nil { if change.CreateFile.Options != nil {
if change.CreateFile.Options.Overwrite { if change.CreateFile.Options.Overwrite {
// Proceed with overwrite // Proceed with overwrite
@@ -164,7 +171,11 @@ func applyDocumentChange(change protocol.DocumentChange) error {
} }
if change.DeleteFile != nil { if change.DeleteFile != nil {
path := change.DeleteFile.URI.Path() path, err := change.DeleteFile.URI.Path()
if err != nil {
return fmt.Errorf("invalid URI: %w", err)
}
if change.DeleteFile.Options != nil && change.DeleteFile.Options.Recursive { if change.DeleteFile.Options != nil && change.DeleteFile.Options.Recursive {
if err := os.RemoveAll(path); err != nil { if err := os.RemoveAll(path); err != nil {
return fmt.Errorf("failed to delete directory recursively: %w", err) return fmt.Errorf("failed to delete directory recursively: %w", err)
@@ -177,8 +188,19 @@ func applyDocumentChange(change protocol.DocumentChange) error {
} }
if change.RenameFile != nil { if change.RenameFile != nil {
oldPath := change.RenameFile.OldURI.Path() var newPath, oldPath string
newPath := change.RenameFile.NewURI.Path() var err error
oldPath, err = change.RenameFile.OldURI.Path()
if err != nil {
return err
}
newPath, err = change.RenameFile.NewURI.Path()
if err != nil {
return err
}
if change.RenameFile.Options != nil { if change.RenameFile.Options != nil {
if !change.RenameFile.Options.Overwrite { if !change.RenameFile.Options.Overwrite {
if _, err := os.Stat(newPath); err == nil { if _, err := os.Stat(newPath); err == nil {

View File

@@ -617,7 +617,11 @@ func (w *WorkspaceWatcher) matchesPattern(path string, pattern protocol.GlobPatt
return false return false
} }
// For relative patterns // For relative patterns
basePath = protocol.DocumentURI(basePath).Path() if basePath, err = protocol.DocumentURI(basePath).Path(); err != nil {
// XXX: Do we want to return here, or send the error up the stack?
slog.Error("Error converting base path to URI", "basePath", basePath, "error", err)
}
basePath = filepath.ToSlash(basePath) basePath = filepath.ToSlash(basePath)
// Make path relative to basePath for matching // Make path relative to basePath for matching
@@ -660,7 +664,13 @@ func (w *WorkspaceWatcher) debounceHandleFileEvent(ctx context.Context, uri stri
// handleFileEvent sends file change notifications // handleFileEvent sends file change notifications
func (w *WorkspaceWatcher) handleFileEvent(ctx context.Context, uri string, changeType protocol.FileChangeType) { func (w *WorkspaceWatcher) handleFileEvent(ctx context.Context, uri string, changeType protocol.FileChangeType) {
// If the file is open and it's a change event, use didChange notification // If the file is open and it's a change event, use didChange notification
filePath := protocol.DocumentURI(uri).Path() filePath, err := protocol.DocumentURI(uri).Path()
if err != nil {
// XXX: Do we want to return here, or send the error up the stack?
slog.Error("Error converting URI to path", "uri", uri, "error", err)
return
}
if changeType == protocol.FileChangeType(protocol.Deleted) { if changeType == protocol.FileChangeType(protocol.Deleted) {
w.client.ClearDiagnosticsForURI(protocol.DocumentURI(uri)) w.client.ClearDiagnosticsForURI(protocol.DocumentURI(uri))
} else if changeType == protocol.FileChangeType(protocol.Changed) && w.client.IsFileOpen(filePath) { } else if changeType == protocol.FileChangeType(protocol.Changed) && w.client.IsFileOpen(filePath) {

View File

@@ -843,7 +843,7 @@ func (p *chatPage) Help() help.KeyMap {
), ),
key.NewBinding( key.NewBinding(
key.WithKeys("g", "home"), key.WithKeys("g", "home"),
key.WithHelp("g", "hone"), key.WithHelp("g", "home"),
), ),
key.NewBinding( key.NewBinding(
key.WithKeys("G", "end"), key.WithKeys("G", "end"),