summaryrefslogtreecommitdiffhomepage
path: root/internal/llm
diff options
context:
space:
mode:
authorAdictya <[email protected]>2025-05-15 08:42:45 +0530
committerAdam <[email protected]>2025-05-15 08:29:54 -0500
commit15bf40bc102ed5426fa2148b9e8f39acef1174a0 (patch)
tree4b005eba92703864a4bc2884b3cdc1fb5ab21a3c /internal/llm
parenta33e3e25b6646b6128975836d7f182e89f5eb891 (diff)
downloadopencode-15bf40bc102ed5426fa2148b9e8f39acef1174a0.tar.gz
opencode-15bf40bc102ed5426fa2148b9e8f39acef1174a0.zip
feat(complete-module): add completions logic, dialog and providers
Diffstat (limited to 'internal/llm')
-rw-r--r--internal/llm/tools/glob.go160
-rw-r--r--internal/llm/tools/grep.go3
2 files changed, 20 insertions, 143 deletions
diff --git a/internal/llm/tools/glob.go b/internal/llm/tools/glob.go
index fad6e2371..6b6cab191 100644
--- a/internal/llm/tools/glob.go
+++ b/internal/llm/tools/glob.go
@@ -5,16 +5,13 @@ import (
"context"
"encoding/json"
"fmt"
- "io/fs"
- "os"
"os/exec"
"path/filepath"
"sort"
"strings"
- "time"
- "github.com/bmatcuk/doublestar/v4"
"github.com/sst/opencode/internal/config"
+ "github.com/sst/opencode/internal/fileutil"
)
const (
@@ -55,11 +52,6 @@ TIPS:
- Always check if results are truncated and refine your search pattern if needed`
)
-type fileInfo struct {
- path string
- modTime time.Time
-}
-
type GlobParams struct {
Pattern string `json:"pattern"`
Path string `json:"path"`
@@ -134,41 +126,20 @@ func (g *globTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
}
func globFiles(pattern, searchPath string, limit int) ([]string, bool, error) {
- matches, err := globWithRipgrep(pattern, searchPath, limit)
- if err == nil {
- return matches, len(matches) >= limit, nil
+ cmdRg := fileutil.GetRgCmd(pattern)
+ if cmdRg != nil {
+ cmdRg.Dir = searchPath
+ matches, err := runRipgrep(cmdRg, searchPath, limit)
+ if err == nil {
+ return matches, len(matches) >= limit && limit > 0, nil
+ }
+ // logging.Warn(fmt.Sprintf("Ripgrep execution failed: %v. Falling back to doublestar.", err))
}
- return globWithDoublestar(pattern, searchPath, limit)
+ return fileutil.GlobWithDoublestar(pattern, searchPath, limit)
}
-func globWithRipgrep(
- pattern, searchRoot string,
- limit int,
-) ([]string, error) {
- if searchRoot == "" {
- searchRoot = "."
- }
-
- rgBin, err := exec.LookPath("rg")
- if err != nil {
- return nil, fmt.Errorf("ripgrep not found in $PATH: %w", err)
- }
-
- if !filepath.IsAbs(pattern) && !strings.HasPrefix(pattern, "/") {
- pattern = "/" + pattern
- }
-
- args := []string{
- "--files",
- "--null",
- "--glob", pattern,
- "-L",
- }
-
- cmd := exec.Command(rgBin, args...)
- cmd.Dir = searchRoot
-
+func runRipgrep(cmd *exec.Cmd, searchRoot string, limit int) ([]string, error) {
out, err := cmd.CombinedOutput()
if err != nil {
if ee, ok := err.(*exec.ExitError); ok && ee.ExitCode() == 1 {
@@ -182,117 +153,22 @@ func globWithRipgrep(
if len(p) == 0 {
continue
}
- abs := filepath.Join(searchRoot, string(p))
- if skipHidden(abs) {
+ absPath := string(p)
+ if !filepath.IsAbs(absPath) {
+ absPath = filepath.Join(searchRoot, absPath)
+ }
+ if fileutil.SkipHidden(absPath) {
continue
}
- matches = append(matches, abs)
+ matches = append(matches, absPath)
}
sort.SliceStable(matches, func(i, j int) bool {
return len(matches[i]) < len(matches[j])
})
- if len(matches) > limit {
+ if limit > 0 && len(matches) > limit {
matches = matches[:limit]
}
return matches, nil
}
-
-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 // Skip files we can't access
- }
-
- absPath := path // Restore absolute path
- if !strings.HasPrefix(absPath, searchPath) {
- absPath = filepath.Join(searchPath, absPath)
- }
-
- matches = append(matches, fileInfo{
- path: absPath,
- modTime: info.ModTime(),
- })
-
- if len(matches) >= limit*2 { // Collect more than needed for sorting
- 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 := len(matches) > limit
- if truncated {
- matches = matches[:limit]
- }
-
- results := make([]string, len(matches))
- for i, m := range matches {
- results[i] = m.path
- }
-
- return results, truncated, nil
-}
-
-func skipHidden(path string) bool {
- // Check for hidden files (starting with a dot)
- base := filepath.Base(path)
- if base != "." && strings.HasPrefix(base, ".") {
- return true
- }
-
- // List of commonly ignored directories in development projects
- commonIgnoredDirs := map[string]bool{
- "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,
- }
-
- // Check if any path component is in our ignore list
- parts := strings.SplitSeq(path, string(os.PathSeparator))
- for part := range parts {
- if commonIgnoredDirs[part] {
- return true
- }
- }
-
- return false
-}
diff --git a/internal/llm/tools/grep.go b/internal/llm/tools/grep.go
index 505c3c093..c2cd97810 100644
--- a/internal/llm/tools/grep.go
+++ b/internal/llm/tools/grep.go
@@ -15,6 +15,7 @@ import (
"time"
"github.com/sst/opencode/internal/config"
+ "github.com/sst/opencode/internal/fileutil"
)
type GrepParams struct {
@@ -288,7 +289,7 @@ func searchFilesWithRegex(pattern, rootPath, include string) ([]grepMatch, error
return nil // Skip directories
}
- if skipHidden(path) {
+ if fileutil.SkipHidden(path) {
return nil
}