diff --git a/internal/room/tools/executor.go b/internal/room/tools/executor.go index a925651..852aa34 100644 --- a/internal/room/tools/executor.go +++ b/internal/room/tools/executor.go @@ -71,23 +71,46 @@ func (e *Executor) glob(args string) (string, error) { return "", err } - pattern := filepath.Join(e.workspaceDir, a.Pattern) - files, err := filepath.Glob(pattern) - if err != nil { - return "", err - } - var result []string - for _, f := range files { - abs, err := filepath.Abs(f) + pattern := a.Pattern + + if strings.HasPrefix(pattern, "**") { + baseDir := e.workspaceDir + ext := strings.TrimPrefix(pattern, "**/") + err := filepath.Walk(baseDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return nil + } + if info.IsDir() { + return nil + } + matched, _ := filepath.Match(ext, info.Name()) + if matched { + abs, _ := filepath.Abs(path) + rel, _ := filepath.Rel(e.workspaceDir, abs) + result = append(result, rel) + } + return nil + }) if err != nil { - continue + return "", err } - if !strings.HasPrefix(abs, e.workspaceDir) { - continue + } else { + files, err := filepath.Glob(filepath.Join(e.workspaceDir, pattern)) + if err != nil { + return "", err + } + for _, f := range files { + abs, err := filepath.Abs(f) + if err != nil { + continue + } + if !strings.HasPrefix(abs, e.workspaceDir) { + continue + } + rel, _ := filepath.Rel(e.workspaceDir, abs) + result = append(result, rel) } - rel, _ := filepath.Rel(e.workspaceDir, abs) - result = append(result, rel) } if len(result) == 0 { diff --git a/internal/room/tools/executor_test.go b/internal/room/tools/executor_test.go new file mode 100644 index 0000000..e537193 --- /dev/null +++ b/internal/room/tools/executor_test.go @@ -0,0 +1,118 @@ +package tools + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestGlob(t *testing.T) { + tmpDir := t.TempDir() + os.WriteFile(filepath.Join(tmpDir, "test.go"), []byte("package test"), 0644) + os.WriteFile(filepath.Join(tmpDir, "test.ts"), []byte("const x = 1"), 0644) + os.MkdirAll(filepath.Join(tmpDir, "sub"), 0755) + os.WriteFile(filepath.Join(tmpDir, "sub", "nested.go"), []byte("package sub"), 0644) + + exec := NewExecutor(tmpDir) + + result, err := exec.glob(`{"pattern": "*.go"}`) + if err != nil { + t.Fatal(err) + } + if !contains(result, "test.go") { + t.Errorf("expected test.go in result, got: %s", result) + } + + result, err = exec.glob(`{"pattern": "**/*.go"}`) + if err != nil { + t.Fatal(err) + } + if !contains(result, "test.go") || !contains(result, "sub/nested.go") { + t.Errorf("expected test.go and sub/nested.go, got: %s", result) + } +} + +func TestReadFile(t *testing.T) { + tmpDir := t.TempDir() + content := "line1\nline2\nline3\nline4\nline5" + os.WriteFile(filepath.Join(tmpDir, "test.txt"), []byte(content), 0644) + + exec := NewExecutor(tmpDir) + + result, err := exec.readFile(`{"filename": "test.txt"}`) + if err != nil { + t.Fatal(err) + } + if !contains(result, "line1") { + t.Errorf("expected line1 in result, got: %s", result) + } + + result, err = exec.readFile(`{"filename": "test.txt", "offset": 1, "limit": 2}`) + if err != nil { + t.Fatal(err) + } + if !contains(result, "line2") || !contains(result, "line3") { + t.Errorf("expected line2 and line3, got: %s", result) + } +} + +func TestEditFile(t *testing.T) { + tmpDir := t.TempDir() + os.WriteFile(filepath.Join(tmpDir, "test.txt"), []byte("hello world"), 0644) + + exec := NewExecutor(tmpDir) + + result, err := exec.editFile(`{"filename": "test.txt", "old_string": "world", "new_string": "opencode"}`) + if err != nil { + t.Fatal(err) + } + + data, _ := os.ReadFile(filepath.Join(tmpDir, "test.txt")) + if string(data) != "hello opencode" { + t.Errorf("expected 'hello opencode', got: %s", string(data)) + } + + _ = result +} + +func TestWriteFile(t *testing.T) { + tmpDir := t.TempDir() + + exec := NewExecutor(tmpDir) + + result, err := exec.writeFile(`{"filename": "new.txt", "content": "new content"}`) + if err != nil { + t.Fatal(err) + } + + data, _ := os.ReadFile(filepath.Join(tmpDir, "new.txt")) + if string(data) != "new content" { + t.Errorf("expected 'new content', got: %s", string(data)) + } + + _ = result +} + +func TestListWorkspace(t *testing.T) { + tmpDir := t.TempDir() + os.WriteFile(filepath.Join(tmpDir, "a.txt"), []byte("a"), 0644) + os.WriteFile(filepath.Join(tmpDir, "b.txt"), []byte("b"), 0644) + os.MkdirAll(filepath.Join(tmpDir, "sub"), 0755) + os.WriteFile(filepath.Join(tmpDir, "sub", "c.txt"), []byte("c"), 0644) + + exec := NewExecutor(tmpDir) + + result, err := exec.listWorkspace() + if err != nil { + t.Fatal(err) + } + + if !contains(result, "a.txt") || !contains(result, "b.txt") || !contains(result, "sub/c.txt") { + t.Errorf("expected all files, got: %s", result) + } +} + +func contains(s, substr string) bool { + return len(s) > 0 && strings.Contains(s, substr) +}