1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
// The project's files as an agent window offers them: what "@" lists in the
// box, and what a mention is resolved against when a prompt is sent.
package app
import (
"io/fs"
"path/filepath"
"codeberg.org/turbo-editors/turbo-core/acp"
)
// projectFileLimit caps how many files an agent window is offered.
//
// Past this the picker is a list to scroll rather than a list to choose from,
// and a walk of a monorepo on every "@" would make the box stutter. Typing one
// more letter after the "@" is the answer for a project this size, exactly as
// it is for the Open File dialog.
const projectFileLimit = 5000
// projectFiles lists the project for the agent windows.
//
// It walks on each call rather than caching: the picker asks once when "@" is
// typed and keeps the answer while it is open, and a list that never noticed a
// file the agent just created would be wrong at the moment it mattered.
func (a *App) projectFiles() []acp.Mention {
return listProjectFiles(a.projectRoot(), projectFileLimit)
}
// listProjectFiles walks root and returns its regular files, relative to root
// with forward slashes, in the order the walk finds them. The .git directory
// is skipped for the reason the file tree skips it: nothing in it is meant to
// be read by hand, and it is large enough to bury everything else. The walk
// stops at limit files.
//
// listProjectFiles("/src/p", 5000) // [{app/menus.go /src/p/app/menus.go} …]
func listProjectFiles(root string, limit int) []acp.Mention {
var out []acp.Mention
_ = filepath.WalkDir(root, func(path string, entry fs.DirEntry, err error) error {
if err != nil || entry.IsDir() {
return skipGit(entry, err)
}
if entry.Type().IsRegular() {
out = appendFile(out, root, path)
}
if len(out) >= limit {
return filepath.SkipAll
}
return nil
})
return out
}
// skipGit tells the walk to leave .git alone, and to carry on past a directory
// it could not read rather than stop the whole listing for it.
func skipGit(entry fs.DirEntry, err error) error {
if err == nil && entry.Name() == ".git" {
return filepath.SkipDir
}
return nil
}
// appendFile adds one file to the list, named relative to the project with
// forward slashes whatever the platform, because that is how it is typed.
func appendFile(files []acp.Mention, root, path string) []acp.Mention {
relative, err := filepath.Rel(root, path)
if err != nil {
return files
}
return append(files, acp.Mention{Name: filepath.ToSlash(relative), Path: path})
}
|