mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-28 11:11:47 +02:00
Move filtering into file system scanner
Greatest reduction in work, changing filtering is rare therefore, rescan is not too bad.
This commit is contained in:
@@ -63,14 +63,16 @@ type FileSystemScanner struct {
|
||||
mutex sync.Mutex
|
||||
collection *ResultCollection
|
||||
dir_reader func(path string) ([]fs.DirEntry, error)
|
||||
filter_func func(filename string) bool
|
||||
err error
|
||||
}
|
||||
|
||||
func NewFileSystemScanner(root_dir string, notify chan bool) (fss *FileSystemScanner) {
|
||||
func NewFileSystemScanner(root_dir string, notify chan bool, filter_func func(string) bool) (fss *FileSystemScanner) {
|
||||
ans := &FileSystemScanner{root_dir: root_dir, listeners: []chan bool{notify}, collection: NewResultCollection(4096)}
|
||||
ans.in_progress.Store(true)
|
||||
ans.keep_going.Store(true)
|
||||
ans.dir_reader = os.ReadDir
|
||||
ans.filter_func = utils.IfElse(filter_func == nil, accept_all, filter_func)
|
||||
return ans
|
||||
}
|
||||
|
||||
@@ -184,6 +186,8 @@ func as_lower(s string, output []byte) int {
|
||||
return pos
|
||||
}
|
||||
|
||||
func accept_all(filename string) bool { return true }
|
||||
|
||||
func (fss *FileSystemScanner) worker() {
|
||||
defer func() {
|
||||
fss.lock()
|
||||
@@ -228,24 +232,29 @@ func (fss *FileSystemScanner) worker() {
|
||||
sortable = make([]*sortable_dir_entry, 0, cap(arena))
|
||||
}
|
||||
arena = arena[:len(entries)]
|
||||
sortable = sortable[:len(entries)]
|
||||
sortable = sortable[:0]
|
||||
for i, e := range entries {
|
||||
arena[i].name = e.Name()
|
||||
name := e.Name()
|
||||
ftype := e.Type()
|
||||
is_dir := ftype&fs.ModeDir != 0
|
||||
if !is_dir && !fss.filter_func(name) {
|
||||
continue
|
||||
}
|
||||
arena[i].name = name
|
||||
if ftype&fs.ModeSymlink != 0 {
|
||||
if st, serr := os.Stat(dir + arena[i].name); serr == nil && st.IsDir() {
|
||||
ftype |= SymlinkToDir
|
||||
}
|
||||
}
|
||||
arena[i].ftype = ftype
|
||||
if ftype&fs.ModeDir != 0 {
|
||||
if is_dir {
|
||||
arena[i].buf[0] = '0'
|
||||
} else {
|
||||
arena[i].buf[0] = '1'
|
||||
}
|
||||
n := as_lower(arena[i].name, arena[i].buf[1:])
|
||||
arena[i].sort_key = arena[i].buf[:1+n]
|
||||
sortable[i] = &arena[i]
|
||||
sortable = append(sortable, &arena[i])
|
||||
}
|
||||
slices.SortFunc(sortable, func(a, b *sortable_dir_entry) int { return bytes.Compare(a.sort_key, b.sort_key) })
|
||||
fss.lock()
|
||||
@@ -285,6 +294,7 @@ type FileSystemScorer struct {
|
||||
on_results func(error, bool)
|
||||
current_worker_wait *sync.WaitGroup
|
||||
scorer *fzf.FuzzyMatcher
|
||||
dir_reader func(path string) ([]fs.DirEntry, error)
|
||||
}
|
||||
|
||||
func NewFileSystemScorer(root_dir, query string, filter Filter, only_dirs bool, on_results func(error, bool)) (ans *FileSystemScorer) {
|
||||
@@ -302,7 +312,9 @@ func (fss *FileSystemScorer) Start() {
|
||||
fss.is_complete.Store(false)
|
||||
fss.keep_going.Store(true)
|
||||
if fss.scanner == nil {
|
||||
fss.scanner = NewFileSystemScanner(fss.root_dir, on_results)
|
||||
sc := NewFileSystemScanner(fss.root_dir, on_results, fss.filter.Match)
|
||||
sc.dir_reader = fss.dir_reader
|
||||
fss.scanner = sc
|
||||
fss.scanner.Start()
|
||||
} else {
|
||||
fss.scanner.AddListener(on_results)
|
||||
@@ -318,6 +330,9 @@ func (fss *FileSystemScorer) Change_query(query string) {
|
||||
}
|
||||
fss.keep_going.Store(false)
|
||||
if fss.current_worker_wait != nil {
|
||||
if fss.scanner != nil {
|
||||
fss.scanner.Cancel()
|
||||
}
|
||||
fss.current_worker_wait.Wait()
|
||||
}
|
||||
fss.lock()
|
||||
@@ -333,11 +348,15 @@ func (fss *FileSystemScorer) Change_filter(filter Filter) {
|
||||
}
|
||||
fss.keep_going.Store(false)
|
||||
if fss.current_worker_wait != nil {
|
||||
if fss.scanner != nil {
|
||||
fss.scanner.Cancel()
|
||||
}
|
||||
fss.current_worker_wait.Wait()
|
||||
}
|
||||
fss.lock()
|
||||
fss.filter = filter
|
||||
fss.sorted_results.Clear()
|
||||
fss.scanner = nil
|
||||
fss.unlock()
|
||||
fss.Start()
|
||||
}
|
||||
|
||||
@@ -107,9 +107,9 @@ func (n node) ReadDir(name string) ([]fs.DirEntry, error) {
|
||||
|
||||
func TestChooseFilesScoring(t *testing.T) {
|
||||
root := node{name: string(os.PathSeparator), children: map[string]*node{
|
||||
"b": {name: "b"},
|
||||
"a": {name: "a"},
|
||||
"c": {name: "c"},
|
||||
"b": {name: "b"},
|
||||
"a": {name: "a"},
|
||||
"c.png": {name: "c.png"},
|
||||
"x": {name: "x", children: map[string]*node{
|
||||
"1": {"1", nil}, "2": {"2", nil}, "3": {"3", nil},
|
||||
"s": {"s", map[string]*node{
|
||||
@@ -122,15 +122,12 @@ func TestChooseFilesScoring(t *testing.T) {
|
||||
}}
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
s := NewFileSystemScorer(string(os.PathSeparator), "", false, func(err error, is_complete bool) {
|
||||
s := NewFileSystemScorer(string(os.PathSeparator), "", Filter{}, false, func(err error, is_complete bool) {
|
||||
if is_complete {
|
||||
wg.Done()
|
||||
}
|
||||
})
|
||||
sc := NewFileSystemScanner(s.root_dir, make(chan bool))
|
||||
s.scanner = sc
|
||||
sc.dir_reader = root.ReadDir
|
||||
s.scanner.Start()
|
||||
s.dir_reader = root.ReadDir
|
||||
s.Start()
|
||||
wg.Wait()
|
||||
results := func() (ans []string) {
|
||||
@@ -141,20 +138,33 @@ func TestChooseFilesScoring(t *testing.T) {
|
||||
return
|
||||
}
|
||||
ae := func(query string, expected ...string) {
|
||||
if query != "" {
|
||||
wg.Add(1)
|
||||
s.Change_query(query)
|
||||
wg.Wait()
|
||||
}
|
||||
wg.Add(1)
|
||||
s.Change_query(query)
|
||||
wg.Wait()
|
||||
if diff := cmp.Diff(expected, results()); diff != "" {
|
||||
t.Fatalf("Query less scoring failed\n%s", diff)
|
||||
}
|
||||
}
|
||||
ae("", "x", "y", "a", "b", "c", "x/s", "x/1", "x/2", "x/3", "y/3", "y/4", "y/5", "x/s/m", "x/s/n")
|
||||
ae("a", "a")
|
||||
ae("3", "x/3", "y/3")
|
||||
ae("s", "x/s", "x/s/m", "x/s/n")
|
||||
ae("sn", "x/s/n")
|
||||
ae("", "x", "y", "a", "b", "c.png", "x/s", "x/1", "x/2", "x/3", "y/3", "y/4", "y/5", "x/s/m", "x/s/n")
|
||||
|
||||
af := func(filter string, expected ...string) {
|
||||
f, _ := NewFilter(filter)
|
||||
wg.Add(1)
|
||||
s.Change_filter(*f)
|
||||
wg.Wait()
|
||||
if diff := cmp.Diff(expected, results()); diff != "" {
|
||||
t.Fatalf("filter %s failed\n%s", filter, diff)
|
||||
}
|
||||
}
|
||||
af("glob:a:A", "x", "y", "a", "x/s")
|
||||
af("glob:[ab]:A", "x", "y", "a", "b", "x/s")
|
||||
af("mime:image/png:A", "x", "y", "c.png", "x/s")
|
||||
af("mime:image/*:A", "x", "y", "c.png", "x/s")
|
||||
af("glob:*:All", "x", "y", "a", "b", "c.png", "x/s", "x/1", "x/2", "x/3", "y/3", "y/4", "y/5", "x/s/m", "x/s/n")
|
||||
}
|
||||
|
||||
func TestSortedResults(t *testing.T) {
|
||||
@@ -258,14 +268,12 @@ func run_scoring(b *testing.B, depth, breadth int, query string) {
|
||||
b.StopTimer()
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
s := NewFileSystemScorer(string(os.PathSeparator), query, false, func(err error, is_complete bool) {
|
||||
s := NewFileSystemScorer(string(os.PathSeparator), query, Filter{}, false, func(err error, is_complete bool) {
|
||||
if is_complete {
|
||||
wg.Done()
|
||||
}
|
||||
})
|
||||
sc := NewFileSystemScanner(s.root_dir, make(chan bool))
|
||||
s.scanner = sc
|
||||
sc.dir_reader = root.ReadDir
|
||||
s.dir_reader = root.ReadDir
|
||||
b.StartTimer()
|
||||
s.scanner.Start()
|
||||
s.Start()
|
||||
|
||||
Reference in New Issue
Block a user