summaryrefslogtreecommitdiffhomepage
path: root/packages/tui/internal/fileutil
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2025-05-30 20:47:56 -0400
committerDax Raad <[email protected]>2025-05-30 20:48:36 -0400
commitf3da73553c45f17e04b1e77cb13eb0fca714d1bd (patch)
treea24317a19e1ab2a89da50db669dc6894f15d00d1 /packages/tui/internal/fileutil
parent9a26b3058ffc1023e5c7e54b6d571c903d15888e (diff)
downloadopencode-f3da73553c45f17e04b1e77cb13eb0fca714d1bd.tar.gz
opencode-f3da73553c45f17e04b1e77cb13eb0fca714d1bd.zip
sync
Diffstat (limited to 'packages/tui/internal/fileutil')
-rw-r--r--packages/tui/internal/fileutil/fileutil.go163
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
+}