on main help sign in
fix: windows basic issues
commit9d75e31304fa0082ac328bcd0d94810af71f7c11 authorRaphael Amorim <rapha850@gmail.com> dateWed 25 Jun 2025, 12:20 (1 year ago) parent2f8e696
15 files changed+87−24
internal/config/config.go+8 internal/fsext/fileutil.go+1−1 internal/llm/tools/bash.go+8 internal/llm/tools/edit.go+5 internal/llm/tools/glob.go+6 internal/llm/tools/grep.go+6 internal/llm/tools/ls.go+6 internal/llm/tools/shell/shell.go+11 internal/llm/tools/view.go+5 internal/llm/tools/write.go+4 internal/lsp/client.go+10−11 internal/lsp/protocol/pattern_interfaces.go+2−3 internal/lsp/util/edit.go+5−5 internal/lsp/watcher/watcher.go+3−3 internal/tui/components/chat/editor/editor.go+7−1
internal/config/config.go+8
@@ -203,9 +203,17 @@ func Load(workingDir string, debug bool) (*Config, error) {
203203func configureViper() {
204204 viper.SetConfigName(fmt.Sprintf(".%s", appName))
205205 viper.SetConfigType("json")
206+
207+ // Unix-style paths
206208 viper.AddConfigPath("$HOME")
207209 viper.AddConfigPath(fmt.Sprintf("$XDG_CONFIG_HOME/%s", appName))
208210 viper.AddConfigPath(fmt.Sprintf("$HOME/.config/%s", appName))
211+
212+ // Windows-style paths
213+ viper.AddConfigPath(fmt.Sprintf("$USERPROFILE"))
214+ viper.AddConfigPath(fmt.Sprintf("$APPDATA/%s", appName))
215+ viper.AddConfigPath(fmt.Sprintf("$LOCALAPPDATA/%s", appName))
216+
209217 viper.SetEnvPrefix(strings.ToUpper(appName))
210218 viper.AutomaticEnv()
211219}
internal/fsext/fileutil.go+1−1
@@ -61,7 +61,7 @@ func GetRgSearchCmd(pattern, path, include string) *exec.Cmd {
6161 }
6262 args = append(args, path)
6363
64− return exec.Command("rg", args...)
64+ return exec.Command(rgPath, args...)
6565}
6666
6767type FileInfo struct {
internal/llm/tools/bash.go+8
@@ -59,6 +59,14 @@ func bashDescription() string {
5959 bannedCommandsStr := strings.Join(bannedCommands, ", ")
6060 return fmt.Sprintf(`Executes a given bash command in a persistent shell session with optional timeout, ensuring proper handling and security measures.
6161
62+IMPORTANT FOR WINDOWS USERS:
63+- This tool uses a POSIX shell emulator (mvdan.cc/sh/v3) that works cross-platform, including Windows
64+- On Windows, this provides bash-like functionality without requiring WSL or Git Bash
65+- Use forward slashes (/) in paths - they work on all platforms and are converted automatically
66+- Windows-specific commands (like 'dir', 'type', 'copy') are not available - use Unix equivalents ('ls', 'cat', 'cp')
67+- Environment variables use Unix syntax: $VAR instead of %%VAR%%
68+- File paths are automatically converted between Windows and Unix formats as needed
69+
6270Before executing the command, please follow these steps:
6371
64721. Directory Verification:
internal/llm/tools/edit.go+5
@@ -90,6 +90,11 @@ When making edits:
9090 - Do not leave the code in a broken state
9191 - Always use absolute file paths (starting with /)
9292
93+WINDOWS NOTES:
94+- File paths should use forward slashes (/) for cross-platform compatibility
95+- On Windows, absolute paths start with drive letters (C:/) but forward slashes work throughout
96+- File permissions are handled automatically by the Go runtime
97+
9398Remember: 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.`
9499)
95100
internal/llm/tools/glob.go+6
@@ -47,6 +47,12 @@ LIMITATIONS:
4747- Does not search file contents (use Grep tool for that)
4848- Hidden files (starting with '.') are skipped
4949
50+WINDOWS NOTES:
51+- Uses ripgrep (rg) command if available, otherwise falls back to built-in Go implementation
52+- On Windows, install ripgrep via: winget install BurntSushi.ripgrep.MSVC
53+- Path separators are handled automatically (both / and \ work)
54+- Patterns should use forward slashes (/) for cross-platform compatibility
55+
5056TIPS:
5157- For the most useful results, combine with the Grep tool: first find files with Glob, then search their contents with Grep
5258- When doing iterative exploration that may require multiple rounds of searching, consider using the Agent tool instead
internal/llm/tools/grep.go+6
@@ -124,6 +124,12 @@ LIMITATIONS:
124124- Very large binary files may be skipped
125125- Hidden files (starting with '.') are skipped
126126
127+WINDOWS NOTES:
128+- Uses ripgrep (rg) command if available for better performance
129+- On Windows, install ripgrep via: winget install BurntSushi.ripgrep.MSVC
130+- Falls back to built-in Go implementation if ripgrep is not available
131+- File paths are normalized automatically for Windows compatibility
132+
127133TIPS:
128134- For faster, more targeted searches, first use Glob to find relevant files, then use Grep
129135- When doing iterative exploration that may require multiple rounds of searching, consider using the Agent tool instead
internal/llm/tools/ls.go+6
@@ -58,6 +58,12 @@ LIMITATIONS:
5858- Does not show file sizes or permissions
5959- Cannot recursively list all directories in a large project
6060
61+WINDOWS NOTES:
62+- Hidden file detection uses Unix convention (files starting with '.')
63+- Windows-specific hidden files (with hidden attribute) are not automatically skipped
64+- Common Windows directories like System32, Program Files are not in default ignore list
65+- Path separators are handled automatically (both / and \ work)
66+
6167TIPS:
6268- Use Glob tool for finding files by name patterns instead of browsing
6369- Use Grep tool for searching file contents
internal/llm/tools/shell/shell.go+11
@@ -1,3 +1,14 @@
1+// Package shell provides cross-platform shell execution capabilities.
2+//
3+// WINDOWS COMPATIBILITY NOTE:
4+// This implementation uses mvdan.cc/sh/v3 which provides POSIX shell emulation
5+// on Windows. While this works for basic commands, it has limitations:
6+// - Windows-specific commands (dir, type, copy) are not available
7+// - PowerShell and cmd.exe specific features are not supported
8+// - Some Windows path handling may be inconsistent
9+//
10+// For full Windows compatibility, consider adding native Windows shell support
11+// using os/exec with cmd.exe or PowerShell for Windows-specific commands.
112package shell
213
314import (
internal/llm/tools/view.go+5
@@ -60,6 +60,11 @@ LIMITATIONS:
6060- Cannot display binary files or images
6161- Images can be identified but not displayed
6262
63+WINDOWS NOTES:
64+- Handles both Windows (CRLF) and Unix (LF) line endings automatically
65+- File paths work with both forward slashes (/) and backslashes (\)
66+- Text encoding is detected automatically for most common formats
67+
6368TIPS:
6469- Use with Glob tool to first find files you want to view
6570- For code exploration, first use Grep to find relevant files, then View to examine them
internal/llm/tools/write.go+4
@@ -64,6 +64,10 @@ LIMITATIONS:
6464- You should read a file before writing to it to avoid conflicts
6565- Cannot append to files (rewrites the entire file)
6666
67+WINDOWS NOTES:
68+- File permissions (0o755, 0o644) are Unix-style but work on Windows with appropriate translations
69+- Use forward slashes (/) in paths for cross-platform compatibility
70+- Windows file attributes and permissions are handled automatically by the Go runtime
6771
6872TIPS:
6973- Use the View tool first to examine existing files before modifying them
internal/lsp/client.go+10−11
@@ -131,7 +131,7 @@ func (c *Client) InitializeLSPClient(ctx context.Context, workspaceDir string) (
131131 WorkspaceFoldersInitializeParams: protocol.WorkspaceFoldersInitializeParams{
132132 WorkspaceFolders: []protocol.WorkspaceFolder{
133133 {
134− URI: protocol.URI("file://" + workspaceDir),
134+ URI: protocol.URI(protocol.URIFromPath(workspaceDir)),
135135 Name: workspaceDir,
136136 },
137137 },
@@ -144,7 +144,7 @@ func (c *Client) InitializeLSPClient(ctx context.Context, workspaceDir string) (
144144 Version: "0.1.0",
145145 },
146146 RootPath: workspaceDir,
147− RootURI: protocol.DocumentUri("file://" + workspaceDir),
147+ RootURI: protocol.URIFromPath(workspaceDir),
148148 Capabilities: protocol.ClientCapabilities{
149149 Workspace: protocol.WorkspaceClientCapabilities{
150150 Configuration: true,
@@ -448,7 +448,7 @@ func (c *Client) pingTypeScriptServer(ctx context.Context) error {
448448
449449 // If we have any open files, try to get document symbols for one
450450 for uri := range c.openFiles {
451− filePath := strings.TrimPrefix(uri, "file://")
451+ filePath := protocol.DocumentUri(uri).Path()
452452 if strings.HasSuffix(filePath, ".ts") || strings.HasSuffix(filePath, ".js") ||
453453 strings.HasSuffix(filePath, ".tsx") || strings.HasSuffix(filePath, ".jsx") {
454454 var symbols []protocol.DocumentSymbol
@@ -586,7 +586,7 @@ type OpenFileInfo struct {
586586}
587587
588588func (c *Client) OpenFile(ctx context.Context, filepath string) error {
589− uri := fmt.Sprintf("file://%s", filepath)
589+ uri := string(protocol.URIFromPath(filepath))
590590
591591 c.openFilesMu.Lock()
592592 if _, exists := c.openFiles[uri]; exists {
@@ -625,7 +625,7 @@ func (c *Client) OpenFile(ctx context.Context, filepath string) error {
625625}
626626
627627func (c *Client) NotifyChange(ctx context.Context, filepath string) error {
628− uri := fmt.Sprintf("file://%s", filepath)
628+ uri := string(protocol.URIFromPath(filepath))
629629
630630 content, err := os.ReadFile(filepath)
631631 if err != nil {
@@ -665,7 +665,7 @@ func (c *Client) NotifyChange(ctx context.Context, filepath string) error {
665665
666666func (c *Client) CloseFile(ctx context.Context, filepath string) error {
667667 cnf := config.Get()
668− uri := fmt.Sprintf("file://%s", filepath)
668+ uri := string(protocol.URIFromPath(filepath))
669669
670670 c.openFilesMu.Lock()
671671 if _, exists := c.openFiles[uri]; !exists {
@@ -695,7 +695,7 @@ func (c *Client) CloseFile(ctx context.Context, filepath string) error {
695695}
696696
697697func (c *Client) IsFileOpen(filepath string) bool {
698− uri := fmt.Sprintf("file://%s", filepath)
698+ uri := string(protocol.URIFromPath(filepath))
699699 c.openFilesMu.RLock()
700700 defer c.openFilesMu.RUnlock()
701701 _, exists := c.openFiles[uri]
@@ -710,8 +710,8 @@ func (c *Client) CloseAllFiles(ctx context.Context) {
710710
711711 // First collect all URIs that need to be closed
712712 for uri := range c.openFiles {
713− // Convert URI back to file path by trimming "file://" prefix
714− filePath := strings.TrimPrefix(uri, "file://")
713+ // Convert URI back to file path using proper URI handling
714+ filePath := protocol.DocumentUri(uri).Path()
715715 filesToClose = append(filesToClose, filePath)
716716 }
717717 c.openFilesMu.Unlock()
@@ -756,8 +756,7 @@ func (c *Client) OpenFileOnDemand(ctx context.Context, filepath string) error {
756756// GetDiagnosticsForFile ensures a file is open and returns its diagnostics
757757// This is useful for on-demand diagnostics when using lazy loading
758758func (c *Client) GetDiagnosticsForFile(ctx context.Context, filepath string) ([]protocol.Diagnostic, error) {
759− uri := fmt.Sprintf("file://%s", filepath)
760− documentUri := protocol.DocumentUri(uri)
759+ documentUri := protocol.URIFromPath(filepath)
761760
762761 // Make sure the file is open
763762 if !c.IsFileOpen(filepath) {
internal/lsp/protocol/pattern_interfaces.go+2−3
@@ -2,7 +2,6 @@ package protocol
22
33import (
44 "fmt"
5− "strings"
65)
76
87// PatternInfo is an interface for types that represent glob patterns
@@ -45,9 +44,9 @@ func (g *GlobPattern) AsPattern() (PatternInfo, error) {
4544 basePath := ""
4645 switch baseURI := v.BaseURI.Value.(type) {
4746 case string:
48− basePath = strings.TrimPrefix(baseURI, "file://")
47+ basePath = DocumentUri(baseURI).Path()
4948 case DocumentUri:
50− basePath = strings.TrimPrefix(string(baseURI), "file://")
49+ basePath = baseURI.Path()
5150 default:
5251 return nil, fmt.Errorf("unknown BaseURI type: %T", v.BaseURI.Value)
5352 }
internal/lsp/util/edit.go+5−5
@@ -11,7 +11,7 @@ import (
1111)
1212
1313func applyTextEdits(uri protocol.DocumentUri, edits []protocol.TextEdit) error {
14− path := strings.TrimPrefix(string(uri), "file://")
14+ path := uri.Path()
1515
1616 // Read the file content
1717 content, err := os.ReadFile(path)
@@ -148,7 +148,7 @@ func applyTextEdit(lines []string, edit protocol.TextEdit) ([]string, error) {
148148// applyDocumentChange applies a DocumentChange (create/rename/delete operations)
149149func applyDocumentChange(change protocol.DocumentChange) error {
150150 if change.CreateFile != nil {
151− path := strings.TrimPrefix(string(change.CreateFile.URI), "file://")
151+ path := change.CreateFile.URI.Path()
152152 if change.CreateFile.Options != nil {
153153 if change.CreateFile.Options.Overwrite {
154154 // Proceed with overwrite
@@ -164,7 +164,7 @@ func applyDocumentChange(change protocol.DocumentChange) error {
164164 }
165165
166166 if change.DeleteFile != nil {
167− path := strings.TrimPrefix(string(change.DeleteFile.URI), "file://")
167+ path := change.DeleteFile.URI.Path()
168168 if change.DeleteFile.Options != nil && change.DeleteFile.Options.Recursive {
169169 if err := os.RemoveAll(path); err != nil {
170170 return fmt.Errorf("failed to delete directory recursively: %w", err)
@@ -177,8 +177,8 @@ func applyDocumentChange(change protocol.DocumentChange) error {
177177 }
178178
179179 if change.RenameFile != nil {
180− oldPath := strings.TrimPrefix(string(change.RenameFile.OldURI), "file://")
181− newPath := strings.TrimPrefix(string(change.RenameFile.NewURI), "file://")
180+ oldPath := change.RenameFile.OldURI.Path()
181+ newPath := change.RenameFile.NewURI.Path()
182182 if change.RenameFile.Options != nil {
183183 if !change.RenameFile.Options.Overwrite {
184184 if _, err := os.Stat(newPath); err == nil {
internal/lsp/watcher/watcher.go+3−3
@@ -387,7 +387,7 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
387387 return
388388 }
389389
390− uri := fmt.Sprintf("file://%s", event.Name)
390+ uri := string(protocol.URIFromPath(event.Name))
391391
392392 // Add new directories to the watcher
393393 if event.Op&fsnotify.Create != 0 {
@@ -614,7 +614,7 @@ func (w *WorkspaceWatcher) matchesPattern(path string, pattern protocol.GlobPatt
614614 }
615615
616616 // For relative patterns
617− basePath = strings.TrimPrefix(basePath, "file://")
617+ basePath = protocol.DocumentUri(basePath).Path()
618618 basePath = filepath.ToSlash(basePath)
619619
620620 // Make path relative to basePath for matching
@@ -657,7 +657,7 @@ func (w *WorkspaceWatcher) debounceHandleFileEvent(ctx context.Context, uri stri
657657// handleFileEvent sends file change notifications
658658func (w *WorkspaceWatcher) handleFileEvent(ctx context.Context, uri string, changeType protocol.FileChangeType) {
659659 // If the file is open and it's a change event, use didChange notification
660− filePath := uri[7:] // Remove "file://" prefix
660+ filePath := protocol.DocumentUri(uri).Path()
661661 if changeType == protocol.FileChangeType(protocol.Deleted) {
662662 w.client.ClearDiagnosticsForURI(protocol.DocumentUri(uri))
663663 } else if changeType == protocol.FileChangeType(protocol.Changed) && w.client.IsFileOpen(filePath) {
internal/tui/components/chat/editor/editor.go+7−1
@@ -4,6 +4,7 @@ import (
44 "fmt"
55 "os"
66 "os/exec"
7+ "runtime"
78 "slices"
89 "strings"
910 "unicode"
@@ -70,7 +71,12 @@ const (
7071func (m *editorCmp) openEditor() tea.Cmd {
7172 editor := os.Getenv("EDITOR")
7273 if editor == "" {
73− editor = "nvim"
74+ // Use platform-appropriate default editor
75+ if runtime.GOOS == "windows" {
76+ editor = "notepad"
77+ } else {
78+ editor = "nvim"
79+ }
7480 }
7581
7682 tmpfile, err := os.CreateTemp("", "msg_*.md")