Detailed changes
@@ -203,9 +203,17 @@ func Load(workingDir string, debug bool) (*Config, error) {
func configureViper() {
viper.SetConfigName(fmt.Sprintf(".%s", appName))
viper.SetConfigType("json")
+
+ // Unix-style paths
viper.AddConfigPath("$HOME")
viper.AddConfigPath(fmt.Sprintf("$XDG_CONFIG_HOME/%s", appName))
viper.AddConfigPath(fmt.Sprintf("$HOME/.config/%s", appName))
+
+ // Windows-style paths
+ viper.AddConfigPath(fmt.Sprintf("$USERPROFILE"))
+ viper.AddConfigPath(fmt.Sprintf("$APPDATA/%s", appName))
+ viper.AddConfigPath(fmt.Sprintf("$LOCALAPPDATA/%s", appName))
+
viper.SetEnvPrefix(strings.ToUpper(appName))
viper.AutomaticEnv()
}
@@ -61,7 +61,7 @@ func GetRgSearchCmd(pattern, path, include string) *exec.Cmd {
}
args = append(args, path)
- return exec.Command("rg", args...)
+ return exec.Command(rgPath, args...)
}
type FileInfo struct {
@@ -59,6 +59,14 @@ func bashDescription() string {
bannedCommandsStr := strings.Join(bannedCommands, ", ")
return fmt.Sprintf(`Executes a given bash command in a persistent shell session with optional timeout, ensuring proper handling and security measures.
+IMPORTANT FOR WINDOWS USERS:
+- This tool uses a POSIX shell emulator (mvdan.cc/sh/v3) that works cross-platform, including Windows
+- On Windows, this provides bash-like functionality without requiring WSL or Git Bash
+- Use forward slashes (/) in paths - they work on all platforms and are converted automatically
+- Windows-specific commands (like 'dir', 'type', 'copy') are not available - use Unix equivalents ('ls', 'cat', 'cp')
+- Environment variables use Unix syntax: $VAR instead of %%VAR%%
+- File paths are automatically converted between Windows and Unix formats as needed
+
Before executing the command, please follow these steps:
1. Directory Verification:
@@ -90,6 +90,11 @@ When making edits:
- Do not leave the code in a broken state
- Always use absolute file paths (starting with /)
+WINDOWS NOTES:
+- File paths should use forward slashes (/) for cross-platform compatibility
+- On Windows, absolute paths start with drive letters (C:/) but forward slashes work throughout
+- File permissions are handled automatically by the Go runtime
+
Remember: when making multiple file edits in a row to the same file, you should prefer to send all edits in a single message with multiple calls to this tool, rather than multiple messages with a single call each.`
)
@@ -47,6 +47,12 @@ LIMITATIONS:
- Does not search file contents (use Grep tool for that)
- Hidden files (starting with '.') are skipped
+WINDOWS NOTES:
+- Uses ripgrep (rg) command if available, otherwise falls back to built-in Go implementation
+- On Windows, install ripgrep via: winget install BurntSushi.ripgrep.MSVC
+- Path separators are handled automatically (both / and \ work)
+- Patterns should use forward slashes (/) for cross-platform compatibility
+
TIPS:
- For the most useful results, combine with the Grep tool: first find files with Glob, then search their contents with Grep
- When doing iterative exploration that may require multiple rounds of searching, consider using the Agent tool instead
@@ -124,6 +124,12 @@ LIMITATIONS:
- Very large binary files may be skipped
- Hidden files (starting with '.') are skipped
+WINDOWS NOTES:
+- Uses ripgrep (rg) command if available for better performance
+- On Windows, install ripgrep via: winget install BurntSushi.ripgrep.MSVC
+- Falls back to built-in Go implementation if ripgrep is not available
+- File paths are normalized automatically for Windows compatibility
+
TIPS:
- For faster, more targeted searches, first use Glob to find relevant files, then use Grep
- When doing iterative exploration that may require multiple rounds of searching, consider using the Agent tool instead
@@ -58,6 +58,12 @@ LIMITATIONS:
- Does not show file sizes or permissions
- Cannot recursively list all directories in a large project
+WINDOWS NOTES:
+- Hidden file detection uses Unix convention (files starting with '.')
+- Windows-specific hidden files (with hidden attribute) are not automatically skipped
+- Common Windows directories like System32, Program Files are not in default ignore list
+- Path separators are handled automatically (both / and \ work)
+
TIPS:
- Use Glob tool for finding files by name patterns instead of browsing
- Use Grep tool for searching file contents
@@ -1,3 +1,14 @@
+// Package shell provides cross-platform shell execution capabilities.
+//
+// WINDOWS COMPATIBILITY NOTE:
+// This implementation uses mvdan.cc/sh/v3 which provides POSIX shell emulation
+// on Windows. While this works for basic commands, it has limitations:
+// - Windows-specific commands (dir, type, copy) are not available
+// - PowerShell and cmd.exe specific features are not supported
+// - Some Windows path handling may be inconsistent
+//
+// For full Windows compatibility, consider adding native Windows shell support
+// using os/exec with cmd.exe or PowerShell for Windows-specific commands.
package shell
import (
@@ -60,6 +60,11 @@ LIMITATIONS:
- Cannot display binary files or images
- Images can be identified but not displayed
+WINDOWS NOTES:
+- Handles both Windows (CRLF) and Unix (LF) line endings automatically
+- File paths work with both forward slashes (/) and backslashes (\)
+- Text encoding is detected automatically for most common formats
+
TIPS:
- Use with Glob tool to first find files you want to view
- For code exploration, first use Grep to find relevant files, then View to examine them
@@ -64,6 +64,10 @@ LIMITATIONS:
- You should read a file before writing to it to avoid conflicts
- Cannot append to files (rewrites the entire file)
+WINDOWS NOTES:
+- File permissions (0o755, 0o644) are Unix-style but work on Windows with appropriate translations
+- Use forward slashes (/) in paths for cross-platform compatibility
+- Windows file attributes and permissions are handled automatically by the Go runtime
TIPS:
- Use the View tool first to examine existing files before modifying them
@@ -131,7 +131,7 @@ func (c *Client) InitializeLSPClient(ctx context.Context, workspaceDir string) (
WorkspaceFoldersInitializeParams: protocol.WorkspaceFoldersInitializeParams{
WorkspaceFolders: []protocol.WorkspaceFolder{
{
- URI: protocol.URI("file://" + workspaceDir),
+ URI: protocol.URI(protocol.URIFromPath(workspaceDir)),
Name: workspaceDir,
},
},
@@ -144,7 +144,7 @@ func (c *Client) InitializeLSPClient(ctx context.Context, workspaceDir string) (
Version: "0.1.0",
},
RootPath: workspaceDir,
- RootURI: protocol.DocumentUri("file://" + workspaceDir),
+ RootURI: protocol.URIFromPath(workspaceDir),
Capabilities: protocol.ClientCapabilities{
Workspace: protocol.WorkspaceClientCapabilities{
Configuration: true,
@@ -448,7 +448,7 @@ func (c *Client) pingTypeScriptServer(ctx context.Context) error {
// If we have any open files, try to get document symbols for one
for uri := range c.openFiles {
- filePath := strings.TrimPrefix(uri, "file://")
+ filePath := protocol.DocumentUri(uri).Path()
if strings.HasSuffix(filePath, ".ts") || strings.HasSuffix(filePath, ".js") ||
strings.HasSuffix(filePath, ".tsx") || strings.HasSuffix(filePath, ".jsx") {
var symbols []protocol.DocumentSymbol
@@ -586,7 +586,7 @@ type OpenFileInfo struct {
}
func (c *Client) OpenFile(ctx context.Context, filepath string) error {
- uri := fmt.Sprintf("file://%s", filepath)
+ uri := string(protocol.URIFromPath(filepath))
c.openFilesMu.Lock()
if _, exists := c.openFiles[uri]; exists {
@@ -625,7 +625,7 @@ func (c *Client) OpenFile(ctx context.Context, filepath string) error {
}
func (c *Client) NotifyChange(ctx context.Context, filepath string) error {
- uri := fmt.Sprintf("file://%s", filepath)
+ uri := string(protocol.URIFromPath(filepath))
content, err := os.ReadFile(filepath)
if err != nil {
@@ -665,7 +665,7 @@ func (c *Client) NotifyChange(ctx context.Context, filepath string) error {
func (c *Client) CloseFile(ctx context.Context, filepath string) error {
cnf := config.Get()
- uri := fmt.Sprintf("file://%s", filepath)
+ uri := string(protocol.URIFromPath(filepath))
c.openFilesMu.Lock()
if _, exists := c.openFiles[uri]; !exists {
@@ -695,7 +695,7 @@ func (c *Client) CloseFile(ctx context.Context, filepath string) error {
}
func (c *Client) IsFileOpen(filepath string) bool {
- uri := fmt.Sprintf("file://%s", filepath)
+ uri := string(protocol.URIFromPath(filepath))
c.openFilesMu.RLock()
defer c.openFilesMu.RUnlock()
_, exists := c.openFiles[uri]
@@ -710,8 +710,8 @@ func (c *Client) CloseAllFiles(ctx context.Context) {
// First collect all URIs that need to be closed
for uri := range c.openFiles {
- // Convert URI back to file path by trimming "file://" prefix
- filePath := strings.TrimPrefix(uri, "file://")
+ // Convert URI back to file path using proper URI handling
+ filePath := protocol.DocumentUri(uri).Path()
filesToClose = append(filesToClose, filePath)
}
c.openFilesMu.Unlock()
@@ -756,8 +756,7 @@ func (c *Client) OpenFileOnDemand(ctx context.Context, filepath string) error {
// GetDiagnosticsForFile ensures a file is open and returns its diagnostics
// This is useful for on-demand diagnostics when using lazy loading
func (c *Client) GetDiagnosticsForFile(ctx context.Context, filepath string) ([]protocol.Diagnostic, error) {
- uri := fmt.Sprintf("file://%s", filepath)
- documentUri := protocol.DocumentUri(uri)
+ documentUri := protocol.URIFromPath(filepath)
// Make sure the file is open
if !c.IsFileOpen(filepath) {
@@ -2,7 +2,6 @@ package protocol
import (
"fmt"
- "strings"
)
// PatternInfo is an interface for types that represent glob patterns
@@ -45,9 +44,9 @@ func (g *GlobPattern) AsPattern() (PatternInfo, error) {
basePath := ""
switch baseURI := v.BaseURI.Value.(type) {
case string:
- basePath = strings.TrimPrefix(baseURI, "file://")
+ basePath = DocumentUri(baseURI).Path()
case DocumentUri:
- basePath = strings.TrimPrefix(string(baseURI), "file://")
+ basePath = baseURI.Path()
default:
return nil, fmt.Errorf("unknown BaseURI type: %T", v.BaseURI.Value)
}
@@ -11,7 +11,7 @@ import (
)
func applyTextEdits(uri protocol.DocumentUri, edits []protocol.TextEdit) error {
- path := strings.TrimPrefix(string(uri), "file://")
+ path := uri.Path()
// Read the file content
content, err := os.ReadFile(path)
@@ -148,7 +148,7 @@ func applyTextEdit(lines []string, edit protocol.TextEdit) ([]string, error) {
// applyDocumentChange applies a DocumentChange (create/rename/delete operations)
func applyDocumentChange(change protocol.DocumentChange) error {
if change.CreateFile != nil {
- path := strings.TrimPrefix(string(change.CreateFile.URI), "file://")
+ path := change.CreateFile.URI.Path()
if change.CreateFile.Options != nil {
if change.CreateFile.Options.Overwrite {
// Proceed with overwrite
@@ -164,7 +164,7 @@ func applyDocumentChange(change protocol.DocumentChange) error {
}
if change.DeleteFile != nil {
- path := strings.TrimPrefix(string(change.DeleteFile.URI), "file://")
+ path := change.DeleteFile.URI.Path()
if change.DeleteFile.Options != nil && change.DeleteFile.Options.Recursive {
if err := os.RemoveAll(path); err != nil {
return fmt.Errorf("failed to delete directory recursively: %w", err)
@@ -177,8 +177,8 @@ func applyDocumentChange(change protocol.DocumentChange) error {
}
if change.RenameFile != nil {
- oldPath := strings.TrimPrefix(string(change.RenameFile.OldURI), "file://")
- newPath := strings.TrimPrefix(string(change.RenameFile.NewURI), "file://")
+ oldPath := change.RenameFile.OldURI.Path()
+ newPath := change.RenameFile.NewURI.Path()
if change.RenameFile.Options != nil {
if !change.RenameFile.Options.Overwrite {
if _, err := os.Stat(newPath); err == nil {
@@ -387,7 +387,7 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
return
}
- uri := fmt.Sprintf("file://%s", event.Name)
+ uri := string(protocol.URIFromPath(event.Name))
// Add new directories to the watcher
if event.Op&fsnotify.Create != 0 {
@@ -614,7 +614,7 @@ func (w *WorkspaceWatcher) matchesPattern(path string, pattern protocol.GlobPatt
}
// For relative patterns
- basePath = strings.TrimPrefix(basePath, "file://")
+ basePath = protocol.DocumentUri(basePath).Path()
basePath = filepath.ToSlash(basePath)
// Make path relative to basePath for matching
@@ -657,7 +657,7 @@ func (w *WorkspaceWatcher) debounceHandleFileEvent(ctx context.Context, uri stri
// handleFileEvent sends file change notifications
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
- filePath := uri[7:] // Remove "file://" prefix
+ filePath := protocol.DocumentUri(uri).Path()
if changeType == protocol.FileChangeType(protocol.Deleted) {
w.client.ClearDiagnosticsForURI(protocol.DocumentUri(uri))
} else if changeType == protocol.FileChangeType(protocol.Changed) && w.client.IsFileOpen(filePath) {
@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"os/exec"
+ "runtime"
"slices"
"strings"
"unicode"
@@ -70,7 +71,12 @@ const (
func (m *editorCmp) openEditor() tea.Cmd {
editor := os.Getenv("EDITOR")
if editor == "" {
- editor = "nvim"
+ // Use platform-appropriate default editor
+ if runtime.GOOS == "windows" {
+ editor = "notepad"
+ } else {
+ editor = "nvim"
+ }
}
tmpfile, err := os.CreateTemp("", "msg_*.md")