mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-11 02:59:40 +02:00
Code to scan filesystem for the file picker
This commit is contained in:
@@ -39,10 +39,12 @@ type ScreenSize struct {
|
||||
type Handler struct {
|
||||
state State
|
||||
screen_size ScreenSize
|
||||
scan_cache ScanCache
|
||||
lp *loop.Loop
|
||||
}
|
||||
|
||||
func (h *Handler) draw_screen() (err error) {
|
||||
h.get_results()
|
||||
h.lp.StartAtomicUpdate()
|
||||
defer h.lp.EndAtomicUpdate()
|
||||
h.lp.ClearScreen()
|
||||
@@ -52,7 +54,6 @@ func (h *Handler) draw_screen() (err error) {
|
||||
} else {
|
||||
y += dy
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -81,7 +82,7 @@ func (h *Handler) OnKeyEvent(ev *loop.KeyEvent) (err error) {
|
||||
switch {
|
||||
case h.handle_edit_keys(ev):
|
||||
h.draw_screen()
|
||||
case ev.MatchesPressOrRepeat("esc"):
|
||||
case ev.MatchesPressOrRepeat("esc") || ev.MatchesPressOrRepeat("ctrl+c"):
|
||||
h.lp.Quit(1)
|
||||
}
|
||||
return
|
||||
@@ -118,6 +119,7 @@ func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) {
|
||||
}
|
||||
lp.OnKeyEvent = handler.OnKeyEvent
|
||||
lp.OnText = handler.OnText
|
||||
lp.OnWakeup = func() error { return handler.draw_screen() }
|
||||
err = lp.Run()
|
||||
if err != nil {
|
||||
return 1, err
|
||||
|
||||
145
kittens/choose_files/scan.go
Normal file
145
kittens/choose_files/scan.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package choose_files
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/kovidgoyal/kitty/tools/tui/subseq"
|
||||
"github.com/kovidgoyal/kitty/tools/utils"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
type ResultItem struct {
|
||||
text, abspath string
|
||||
dir_entry os.DirEntry
|
||||
positions []int // may be nil
|
||||
}
|
||||
|
||||
func (r ResultItem) String() string {
|
||||
return fmt.Sprintf("{text: %#v, abspath: %#v, is_dir: %v, positions: %#v}", r.text, r.abspath, r.dir_entry.IsDir(), r.positions)
|
||||
}
|
||||
|
||||
type dir_cache map[string][]ResultItem
|
||||
|
||||
type ScanCache struct {
|
||||
dir_entries dir_cache
|
||||
mutex sync.Mutex
|
||||
root_dir, search_text string
|
||||
in_progress bool
|
||||
matches []ResultItem
|
||||
}
|
||||
|
||||
func (sc *ScanCache) get_cached_entries(root_dir string) (ans []ResultItem, found bool) {
|
||||
sc.mutex.Lock()
|
||||
defer sc.mutex.Unlock()
|
||||
ans, found = sc.dir_entries[root_dir]
|
||||
return
|
||||
}
|
||||
|
||||
func (sc *ScanCache) set_cached_entries(root_dir string, e []ResultItem) {
|
||||
sc.mutex.Lock()
|
||||
defer sc.mutex.Unlock()
|
||||
sc.dir_entries[root_dir] = e
|
||||
}
|
||||
|
||||
func scan_dir(path, root_dir string) []ResultItem {
|
||||
if ans, err := os.ReadDir(path); err == nil {
|
||||
if rel, err := filepath.Rel(root_dir, path); err == nil {
|
||||
return utils.Map(func(x os.DirEntry) ResultItem {
|
||||
path := filepath.Join(root_dir, x.Name())
|
||||
return ResultItem{dir_entry: x, abspath: path, text: filepath.Join(rel, x.Name())}
|
||||
}, ans)
|
||||
}
|
||||
}
|
||||
return []ResultItem{}
|
||||
}
|
||||
|
||||
func (sc *ScanCache) fs_scan(root_dir, current_dir string, max_depth int) (ans []ResultItem) {
|
||||
var found bool
|
||||
if ans, found = sc.get_cached_entries(current_dir); !found {
|
||||
ans = scan_dir(current_dir, root_dir)
|
||||
sc.set_cached_entries(current_dir, ans)
|
||||
}
|
||||
for _, x := range ans {
|
||||
ans = append(ans, x)
|
||||
if x.dir_entry.IsDir() && max_depth > 0 {
|
||||
ans = append(ans, sc.fs_scan(root_dir, x.abspath, max_depth-1)...)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (sc *ScanCache) scan(root_dir, search_text string, max_depth int) (ans []ResultItem) {
|
||||
if strings.HasPrefix(search_text, "/") {
|
||||
root_dir = "/"
|
||||
}
|
||||
ans = slices.Clone(sc.fs_scan(root_dir, root_dir, max_depth))
|
||||
if search_text == "" {
|
||||
slices.SortFunc(ans, func(a, b ResultItem) int {
|
||||
switch a.dir_entry.IsDir() {
|
||||
case true:
|
||||
switch b.dir_entry.IsDir() {
|
||||
case true:
|
||||
return strings.Compare(strings.ToLower(a.text), strings.ToLower(b.text))
|
||||
case false:
|
||||
return -1
|
||||
}
|
||||
case false:
|
||||
switch b.dir_entry.IsDir() {
|
||||
case true:
|
||||
return 1
|
||||
case false:
|
||||
return strings.Compare(strings.ToLower(a.text), strings.ToLower(b.text))
|
||||
}
|
||||
}
|
||||
return 0
|
||||
})
|
||||
} else {
|
||||
pm := make(map[string]ResultItem, len(ans))
|
||||
for _, x := range ans {
|
||||
pm[x.text] = x
|
||||
}
|
||||
matches := subseq.ScoreItems(search_text, utils.Keys(pm), subseq.Options{})
|
||||
slices.SortFunc(matches, func(a, b *subseq.Match) int { return cmp.Compare(b.Score, a.Score) })
|
||||
ans = utils.Map(func(m *subseq.Match) ResultItem {
|
||||
x := pm[m.Text]
|
||||
x.positions = m.Positions
|
||||
return x
|
||||
}, matches)
|
||||
}
|
||||
return ans
|
||||
}
|
||||
|
||||
func (h *Handler) get_results() {
|
||||
sc := &h.scan_cache
|
||||
sc.mutex.Lock()
|
||||
defer sc.mutex.Unlock()
|
||||
if sc.dir_entries == nil {
|
||||
sc.dir_entries = make(dir_cache, 512)
|
||||
}
|
||||
if sc.root_dir == h.state.CurrentDir() && sc.search_text == h.state.SearchText() {
|
||||
return
|
||||
}
|
||||
sc.in_progress = true
|
||||
sc.matches = nil
|
||||
root_dir := h.state.CurrentDir()
|
||||
search_text := h.state.SearchText()
|
||||
sc.root_dir = root_dir
|
||||
sc.search_text = search_text
|
||||
go func() {
|
||||
results := sc.scan(root_dir, search_text, h.state.MaxDepth())
|
||||
sc.mutex.Lock()
|
||||
defer sc.mutex.Unlock()
|
||||
if root_dir == sc.root_dir && search_text == sc.search_text {
|
||||
sc.matches = results
|
||||
sc.in_progress = false
|
||||
h.lp.WakeupMainThread()
|
||||
}
|
||||
}()
|
||||
}
|
||||
Reference in New Issue
Block a user