diff options
| author | Dax Raad <[email protected]> | 2025-05-30 20:47:56 -0400 |
|---|---|---|
| committer | Dax Raad <[email protected]> | 2025-05-30 20:48:36 -0400 |
| commit | f3da73553c45f17e04b1e77cb13eb0fca714d1bd (patch) | |
| tree | a24317a19e1ab2a89da50db669dc6894f15d00d1 /packages/tui/internal/fileutil | |
| parent | 9a26b3058ffc1023e5c7e54b6d571c903d15888e (diff) | |
| download | opencode-f3da73553c45f17e04b1e77cb13eb0fca714d1bd.tar.gz opencode-f3da73553c45f17e04b1e77cb13eb0fca714d1bd.zip | |
sync
Diffstat (limited to 'packages/tui/internal/fileutil')
| -rw-r--r-- | packages/tui/internal/fileutil/fileutil.go | 163 |
1 files changed, 163 insertions, 0 deletions
diff --git a/packages/tui/internal/fileutil/fileutil.go b/packages/tui/internal/fileutil/fileutil.go new file mode 100644 index 000000000..b48152f7a --- /dev/null +++ b/packages/tui/internal/fileutil/fileutil.go @@ -0,0 +1,163 @@ +package fileutil + +import ( + "fmt" + "io/fs" + "os" + "os/exec" + "path/filepath" + "sort" + "strings" + "time" + + "github.com/bmatcuk/doublestar/v4" + "github.com/sst/opencode/internal/status" +) + +var ( + rgPath string + fzfPath string +) + +func Init() { + var err error + rgPath, err = exec.LookPath("rg") + if err != nil { + status.Warn("Ripgrep (rg) not found in $PATH. Some features might be limited or slower.") + rgPath = "" + } + fzfPath, err = exec.LookPath("fzf") + if err != nil { + status.Warn("FZF not found in $PATH. Some features might be limited or slower.") + fzfPath = "" + } +} + +func GetRgCmd(globPattern string) *exec.Cmd { + if rgPath == "" { + return nil + } + rgArgs := []string{ + "--files", + "-L", + "--null", + } + if globPattern != "" { + if !filepath.IsAbs(globPattern) && !strings.HasPrefix(globPattern, "/") { + globPattern = "/" + globPattern + } + rgArgs = append(rgArgs, "--glob", globPattern) + } + cmd := exec.Command(rgPath, rgArgs...) + cmd.Dir = "." + return cmd +} + +func GetFzfCmd(query string) *exec.Cmd { + if fzfPath == "" { + return nil + } + fzfArgs := []string{ + "--filter", + query, + "--read0", + "--print0", + } + cmd := exec.Command(fzfPath, fzfArgs...) + cmd.Dir = "." + return cmd +} + +type FileInfo struct { + Path string + ModTime time.Time +} + +func SkipHidden(path string) bool { + // Check for hidden files (starting with a dot) + base := filepath.Base(path) + if base != "." && strings.HasPrefix(base, ".") { + return true + } + + commonIgnoredDirs := map[string]bool{ + ".opencode": true, + "node_modules": true, + "vendor": true, + "dist": true, + "build": true, + "target": true, + ".git": true, + ".idea": true, + ".vscode": true, + "__pycache__": true, + "bin": true, + "obj": true, + "out": true, + "coverage": true, + "tmp": true, + "temp": true, + "logs": true, + "generated": true, + "bower_components": true, + "jspm_packages": true, + } + + parts := strings.Split(path, string(os.PathSeparator)) + for _, part := range parts { + if commonIgnoredDirs[part] { + return true + } + } + return false +} + +func GlobWithDoublestar(pattern, searchPath string, limit int) ([]string, bool, error) { + fsys := os.DirFS(searchPath) + relPattern := strings.TrimPrefix(pattern, "/") + var matches []FileInfo + + err := doublestar.GlobWalk(fsys, relPattern, func(path string, d fs.DirEntry) error { + if d.IsDir() { + return nil + } + if SkipHidden(path) { + return nil + } + info, err := d.Info() + if err != nil { + return nil + } + absPath := path + if !strings.HasPrefix(absPath, searchPath) && searchPath != "." { + absPath = filepath.Join(searchPath, absPath) + } else if !strings.HasPrefix(absPath, "/") && searchPath == "." { + absPath = filepath.Join(searchPath, absPath) // Ensure relative paths are joined correctly + } + + matches = append(matches, FileInfo{Path: absPath, ModTime: info.ModTime()}) + if limit > 0 && len(matches) >= limit*2 { + return fs.SkipAll + } + return nil + }) + if err != nil { + return nil, false, fmt.Errorf("glob walk error: %w", err) + } + + sort.Slice(matches, func(i, j int) bool { + return matches[i].ModTime.After(matches[j].ModTime) + }) + + truncated := false + if limit > 0 && len(matches) > limit { + matches = matches[:limit] + truncated = true + } + + results := make([]string, len(matches)) + for i, m := range matches { + results[i] = m.Path + } + return results, truncated, nil +} |
