mirror of
https://github.com/charmbracelet/crush.git
synced 2025-08-02 05:20:46 +03:00
Merge remote-tracking branch 'origin/main' into list
This commit is contained in:
@@ -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 }}"
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
Reference in New Issue
Block a user