mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-08 14:18:26 +02:00
More work on choose files kitten
This commit is contained in:
@@ -123,6 +123,7 @@ func (s State) SelectDirs() bool { return s.select_dirs }
|
|||||||
func (s State) Multiselect() bool { return s.multiselect }
|
func (s State) Multiselect() bool { return s.multiselect }
|
||||||
func (s State) String() string { return utils.Repr(s) }
|
func (s State) String() string { return utils.Repr(s) }
|
||||||
func (s State) SearchText() string { return s.search_text }
|
func (s State) SearchText() string { return s.search_text }
|
||||||
|
func (s State) OnlyDirs() bool { return s.mode.OnlyDirs() }
|
||||||
func (s *State) SetSearchText(val string) {
|
func (s *State) SetSearchText(val string) {
|
||||||
if s.search_text != val {
|
if s.search_text != val {
|
||||||
s.search_text = val
|
s.search_text = val
|
||||||
@@ -174,9 +175,10 @@ type ScreenSize struct {
|
|||||||
type Handler struct {
|
type Handler struct {
|
||||||
state State
|
state State
|
||||||
screen_size ScreenSize
|
screen_size ScreenSize
|
||||||
scan_cache ScanCache
|
result_manager *ResultManager
|
||||||
lp *loop.Loop
|
lp *loop.Loop
|
||||||
rl *readline.Readline
|
rl *readline.Readline
|
||||||
|
err_chan chan error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) draw_screen() (err error) {
|
func (h *Handler) draw_screen() (err error) {
|
||||||
@@ -229,6 +231,7 @@ func (h *Handler) OnInitialize() (ans string, err error) {
|
|||||||
h.lp.AllowLineWrapping(false)
|
h.lp.AllowLineWrapping(false)
|
||||||
h.lp.SetCursorShape(loop.BAR_CURSOR, true)
|
h.lp.SetCursorShape(loop.BAR_CURSOR, true)
|
||||||
h.lp.StartBracketedPaste()
|
h.lp.StartBracketedPaste()
|
||||||
|
h.result_manager.set_root_dir(h.state.CurrentDir())
|
||||||
h.draw_screen()
|
h.draw_screen()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -270,6 +273,7 @@ func (h *Handler) change_to_current_dir_if_possible() error {
|
|||||||
m = filepath.Dir(m)
|
m = filepath.Dir(m)
|
||||||
}
|
}
|
||||||
h.state.SetCurrentDir(m)
|
h.state.SetCurrentDir(m)
|
||||||
|
h.result_manager.set_root_dir(h.state.CurrentDir())
|
||||||
return h.draw_screen()
|
return h.draw_screen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -303,10 +307,12 @@ func (h *Handler) OnKeyEvent(ev *loop.KeyEvent) (err error) {
|
|||||||
case ".":
|
case ".":
|
||||||
if curr, err = os.Getwd(); err == nil && curr != "/" {
|
if curr, err = os.Getwd(); err == nil && curr != "/" {
|
||||||
h.state.SetCurrentDir(filepath.Dir(curr))
|
h.state.SetCurrentDir(filepath.Dir(curr))
|
||||||
|
h.result_manager.set_root_dir(h.state.CurrentDir())
|
||||||
return h.draw_screen()
|
return h.draw_screen()
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
h.state.SetCurrentDir(filepath.Dir(curr))
|
h.state.SetCurrentDir(filepath.Dir(curr))
|
||||||
|
h.result_manager.set_root_dir(h.state.CurrentDir())
|
||||||
return h.draw_screen()
|
return h.draw_screen()
|
||||||
}
|
}
|
||||||
h.lp.Beep()
|
h.lp.Beep()
|
||||||
@@ -430,12 +436,13 @@ func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 1, err
|
return 1, err
|
||||||
}
|
}
|
||||||
handler := Handler{lp: lp, rl: readline.New(lp, readline.RlInit{
|
handler := Handler{lp: lp, err_chan: make(chan error), rl: readline.New(lp, readline.RlInit{
|
||||||
Prompt: "> ", ContinuationPrompt: ". ",
|
Prompt: "> ", ContinuationPrompt: ". ",
|
||||||
})}
|
})}
|
||||||
if err = handler.set_state_from_config(conf, opts); err != nil {
|
if err = handler.set_state_from_config(conf, opts); err != nil {
|
||||||
return 1, err
|
return 1, err
|
||||||
}
|
}
|
||||||
|
handler.result_manager = NewResultManager(handler.err_chan, &handler.state, lp.WakeupMainThread)
|
||||||
switch len(args) {
|
switch len(args) {
|
||||||
case 0:
|
case 0:
|
||||||
if default_cwd, err = os.Getwd(); err != nil {
|
if default_cwd, err = os.Getwd(); err != nil {
|
||||||
@@ -457,7 +464,14 @@ func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) {
|
|||||||
}
|
}
|
||||||
lp.OnKeyEvent = handler.OnKeyEvent
|
lp.OnKeyEvent = handler.OnKeyEvent
|
||||||
lp.OnText = handler.OnText
|
lp.OnText = handler.OnText
|
||||||
lp.OnWakeup = func() error { return handler.draw_screen() }
|
lp.OnWakeup = func() (err error) {
|
||||||
|
select {
|
||||||
|
case err = <-handler.err_chan:
|
||||||
|
default:
|
||||||
|
err = handler.draw_screen()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
err = lp.Run()
|
err = lp.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 1, err
|
return 1, err
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ func icon_for(path string, x os.FileMode) string {
|
|||||||
return ans
|
return ans
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) draw_column_of_matches(matches []*ResultItem, current_idx int, x, available_width int) {
|
func (h *Handler) draw_column_of_matches(matches []ResultItem, current_idx int, x, available_width int) {
|
||||||
for i, m := range matches {
|
for i, m := range matches {
|
||||||
h.lp.QueueWriteString("\r")
|
h.lp.QueueWriteString("\r")
|
||||||
h.lp.MoveCursorHorizontally(x)
|
h.lp.MoveCursorHorizontally(x)
|
||||||
@@ -139,7 +139,7 @@ func (h *Handler) draw_column_of_matches(matches []*ResultItem, current_idx int,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) draw_list_of_results(matches []*ResultItem, y, height int) int {
|
func (h *Handler) draw_list_of_results(matches []ResultItem, y, height int) int {
|
||||||
if len(matches) == 0 || height < 2 {
|
if len(matches) == 0 || height < 2 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -198,7 +198,7 @@ func (h *Handler) draw_num_of_matches(num_shown, y int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) draw_results(y, bottom_margin int, matches []*ResultItem, in_progress bool) (height int) {
|
func (h *Handler) draw_results(y, bottom_margin int, matches []ResultItem, in_progress bool) (height int) {
|
||||||
height = h.screen_size.height - y - bottom_margin
|
height = h.screen_size.height - y - bottom_margin
|
||||||
h.lp.MoveCursorTo(1, 1+y)
|
h.lp.MoveCursorTo(1, 1+y)
|
||||||
h.draw_frame(h.screen_size.width, height)
|
h.draw_frame(h.screen_size.width, height)
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
package choose_files
|
package choose_files
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cmp"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/kovidgoyal/kitty/tools/fzf"
|
"github.com/kovidgoyal/kitty/tools/fzf"
|
||||||
"github.com/kovidgoyal/kitty/tools/tui/subseq"
|
|
||||||
"github.com/kovidgoyal/kitty/tools/utils"
|
"github.com/kovidgoyal/kitty/tools/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -43,6 +41,25 @@ func (r *ResultItem) sorted_positions() []int {
|
|||||||
return r.positions
|
return r.positions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func get_modified_score(abspath string, score float64, score_patterns []ScorePattern) float64 {
|
||||||
|
for _, sp := range score_patterns {
|
||||||
|
if sp.pat.MatchString(abspath) {
|
||||||
|
score = sp.op(score, sp.val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return score
|
||||||
|
}
|
||||||
|
|
||||||
|
func count_uppercase(s string) int {
|
||||||
|
count := 0
|
||||||
|
for _, r := range s {
|
||||||
|
if unicode.IsUpper(r) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
type ScanRequest struct {
|
type ScanRequest struct {
|
||||||
root_dir string
|
root_dir string
|
||||||
}
|
}
|
||||||
@@ -66,6 +83,13 @@ type ScoreResult struct {
|
|||||||
items []ResultItem
|
items []ResultItem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Settings interface {
|
||||||
|
OnlyDirs() bool
|
||||||
|
ScorePatterns() []ScorePattern
|
||||||
|
CurrentDir() string
|
||||||
|
SearchText() string
|
||||||
|
}
|
||||||
|
|
||||||
type ResultManager struct {
|
type ResultManager struct {
|
||||||
current_root_dir string
|
current_root_dir string
|
||||||
current_root_dir_scan_complete bool
|
current_root_dir_scan_complete bool
|
||||||
@@ -84,18 +108,21 @@ type ResultManager struct {
|
|||||||
|
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
scorer *fzf.FuzzyMatcher
|
scorer *fzf.FuzzyMatcher
|
||||||
state *State
|
settings Settings
|
||||||
|
|
||||||
|
WakeupMainThread func() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResultManager(err_chan chan error, state *State) *ResultManager {
|
func NewResultManager(err_chan chan error, settings Settings, WakeupMainThread func() bool) *ResultManager {
|
||||||
ans := &ResultManager{
|
ans := &ResultManager{
|
||||||
scan_requests: make(chan ScanRequest),
|
scan_requests: make(chan ScanRequest, 256),
|
||||||
scan_results: make(chan ScanResult),
|
scan_results: make(chan ScanResult, 256),
|
||||||
score_queries: make(chan ScoreRequest),
|
score_queries: make(chan ScoreRequest, 256),
|
||||||
score_results: make(chan ScoreResult),
|
score_results: make(chan ScoreResult, 256),
|
||||||
report_errors: err_chan,
|
report_errors: err_chan,
|
||||||
scorer: fzf.NewFuzzyMatcher(fzf.PATH_SCHEME),
|
scorer: fzf.NewFuzzyMatcher(fzf.PATH_SCHEME),
|
||||||
state: state,
|
settings: settings,
|
||||||
|
WakeupMainThread: WakeupMainThread,
|
||||||
}
|
}
|
||||||
go ans.scan_worker()
|
go ans.scan_worker()
|
||||||
go ans.scan_result_handler()
|
go ans.scan_result_handler()
|
||||||
@@ -104,6 +131,14 @@ func NewResultManager(err_chan chan error, state *State) *ResultManager {
|
|||||||
return ans
|
return ans
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *ResultManager) lock() {
|
||||||
|
m.mutex.Lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ResultManager) unlock() {
|
||||||
|
m.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
func (m *ResultManager) scan(dir, root_dir string, level int) (err error) {
|
func (m *ResultManager) scan(dir, root_dir string, level int) (err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
@@ -119,7 +154,7 @@ func (m *ResultManager) scan(dir, root_dir string, level int) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
m.scan_results <- ScanResult{root_dir: root_dir, items: utils.Map(func(x os.DirEntry) ResultItem {
|
m.scan_results <- ScanResult{root_dir: root_dir, items: utils.Map(func(x os.DirEntry) ResultItem {
|
||||||
return ResultItem{abspath: filepath.Join(dir, x.Name())}
|
return ResultItem{abspath: filepath.Join(dir, x.Name()), ftype: x.Type()}
|
||||||
}, items)}
|
}, items)}
|
||||||
for _, x := range items {
|
for _, x := range items {
|
||||||
if x.IsDir() {
|
if x.IsDir() {
|
||||||
@@ -136,33 +171,42 @@ func (m *ResultManager) scan(dir, root_dir string, level int) (err error) {
|
|||||||
|
|
||||||
func (m *ResultManager) scan_worker() {
|
func (m *ResultManager) scan_worker() {
|
||||||
for r := range m.scan_requests {
|
for r := range m.scan_requests {
|
||||||
|
st := time.Now()
|
||||||
if err := m.scan(r.root_dir, r.root_dir, 0); err == nil {
|
if err := m.scan(r.root_dir, r.root_dir, 0); err == nil {
|
||||||
m.scan_results <- ScanResult{root_dir: r.root_dir, is_finished: true}
|
m.scan_results <- ScanResult{root_dir: r.root_dir, is_finished: true}
|
||||||
}
|
}
|
||||||
|
debugprintln(111111111, time.Now().Sub(st), len(m.results_for_current_root_dir))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ResultManager) scan_result_handler() {
|
func (m *ResultManager) create_score_query(items []ResultItem, is_finished bool) ScoreRequest {
|
||||||
one := func(r ScanResult) {
|
return ScoreRequest{root_dir: m.current_root_dir, query: m.current_query, items: utils.Map(
|
||||||
m.mutex.Lock()
|
|
||||||
defer m.mutex.Unlock()
|
|
||||||
if !m.is_root_dir_current(r.root_dir) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(r.items) > 0 {
|
|
||||||
m.results_for_current_root_dir = append(m.results_for_current_root_dir, r.items...)
|
|
||||||
m.score_queries <- ScoreRequest{root_dir: m.current_root_dir, query: m.current_query, items: utils.Map(
|
|
||||||
func(r ResultItem) ResultItem {
|
func(r ResultItem) ResultItem {
|
||||||
text, err := filepath.Rel(m.current_root_dir, r.abspath)
|
text, err := filepath.Rel(m.current_root_dir, r.abspath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
text = r.abspath
|
text = r.abspath
|
||||||
}
|
}
|
||||||
return ResultItem{abspath: r.abspath, text: text, ltext: strings.ToLower(text), ftype: r.ftype}
|
return ResultItem{abspath: r.abspath, text: text, ltext: strings.ToLower(text), ftype: r.ftype}
|
||||||
}, r.items), is_last_for_current_root_dir: r.is_finished}
|
}, items), is_last_for_current_root_dir: is_finished}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ResultManager) scan_result_handler() {
|
||||||
|
one := func(r ScanResult) {
|
||||||
|
if !m.is_root_dir_current(r.root_dir) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
var sqr ScoreRequest
|
||||||
|
has_items := len(r.items) > 0
|
||||||
|
m.lock()
|
||||||
|
if has_items {
|
||||||
|
m.results_for_current_root_dir = append(m.results_for_current_root_dir, r.items...)
|
||||||
|
}
|
||||||
|
sqr = m.create_score_query(r.items, r.is_finished)
|
||||||
if r.is_finished {
|
if r.is_finished {
|
||||||
m.current_root_dir_scan_complete = true
|
m.current_root_dir_scan_complete = true
|
||||||
}
|
}
|
||||||
|
m.unlock()
|
||||||
|
m.score_queries <- sqr
|
||||||
}
|
}
|
||||||
for r := range m.scan_results {
|
for r := range m.scan_results {
|
||||||
if r.err != nil {
|
if r.err != nil {
|
||||||
@@ -175,10 +219,10 @@ func (m *ResultManager) scan_result_handler() {
|
|||||||
|
|
||||||
func (m *ResultManager) score(r ScoreRequest) (err error) {
|
func (m *ResultManager) score(r ScoreRequest) (err error) {
|
||||||
items := r.items
|
items := r.items
|
||||||
m.mutex.Lock()
|
m.lock()
|
||||||
only_dirs := m.state.mode.OnlyDirs()
|
only_dirs := m.settings.OnlyDirs()
|
||||||
sp := m.state.ScorePatterns()
|
sp := m.settings.ScorePatterns()
|
||||||
m.mutex.Unlock()
|
m.unlock()
|
||||||
if only_dirs {
|
if only_dirs {
|
||||||
items = utils.Filter(items, func(r ResultItem) bool { return r.ftype.IsDir() })
|
items = utils.Filter(items, func(r ResultItem) bool { return r.ftype.IsDir() })
|
||||||
}
|
}
|
||||||
@@ -281,38 +325,49 @@ func merge_sorted_slices(a, b []ResultItem, cmp func(a, b ResultItem) int) []Res
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
result = append(result, a[i:]...)
|
result = append(result, a[i:]...)
|
||||||
result = append(result, b[j:]...)
|
return append(result, b[j:]...)
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ResultManager) add_score_results(r ScoreResult) (err error) {
|
func (m *ResultManager) add_score_results(r ScoreResult) (err error) {
|
||||||
cmp := utils.IfElse(r.query == "", cmp_without_score, cmp_with_score)
|
cmp := utils.IfElse(r.query == "", cmp_without_score, cmp_with_score)
|
||||||
slices.SortStableFunc(r.items, cmp)
|
slices.SortStableFunc(r.items, cmp)
|
||||||
m.mutex.Lock()
|
m.lock()
|
||||||
defer func() {
|
defer func() {
|
||||||
m.mutex.Unlock()
|
m.unlock()
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
st, qerr := utils.Format_stacktrace_on_panic(r)
|
st, qerr := utils.Format_stacktrace_on_panic(r)
|
||||||
err = fmt.Errorf("%w\n%s", qerr, st)
|
err = fmt.Errorf("%w\n%s", qerr, st)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
merge_sorted_slices(m.renderable_results, r.items, cmp)
|
m.renderable_results = merge_sorted_slices(m.renderable_results, r.items, cmp)
|
||||||
|
if r.is_last_for_current_root_dir {
|
||||||
|
m.current_query_scoring_complete = true
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ResultManager) sort_worker() {
|
func (m *ResultManager) sort_worker() {
|
||||||
|
last_wakeup_at := time.Now()
|
||||||
for r := range m.score_results {
|
for r := range m.score_results {
|
||||||
if m.is_query_current(r.query, r.root_dir) {
|
if m.is_query_current(r.query, r.root_dir) {
|
||||||
if err := m.add_score_results(r); err != nil {
|
if err := m.add_score_results(r); err != nil {
|
||||||
m.report_errors <- err
|
m.report_errors <- err
|
||||||
|
} else {
|
||||||
|
m.lock()
|
||||||
|
is_complete := m.current_root_dir_scan_complete && m.current_query_scoring_complete
|
||||||
|
m.unlock()
|
||||||
|
if is_complete || time.Now().Sub(last_wakeup_at) > time.Millisecond*50 {
|
||||||
|
m.WakeupMainThread()
|
||||||
|
last_wakeup_at = time.Now()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ResultManager) is_root_dir_current(root_dir string) bool {
|
func (m *ResultManager) is_root_dir_current(root_dir string) bool {
|
||||||
m.mutex.Lock()
|
m.lock()
|
||||||
defer m.mutex.Unlock()
|
defer m.unlock()
|
||||||
return root_dir == m.current_root_dir
|
return root_dir == m.current_root_dir
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,8 +382,8 @@ func (m *ResultManager) set_root_dir(root_dir string) {
|
|||||||
if root_dir, err = filepath.Abs(root_dir); err != nil {
|
if root_dir, err = filepath.Abs(root_dir); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
m.mutex.Lock()
|
m.lock()
|
||||||
defer m.mutex.Unlock()
|
defer m.unlock()
|
||||||
if m.current_root_dir == root_dir {
|
if m.current_root_dir == root_dir {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -342,14 +397,20 @@ func (m *ResultManager) set_root_dir(root_dir string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *ResultManager) is_query_current(query, root_dir string) bool {
|
func (m *ResultManager) is_query_current(query, root_dir string) bool {
|
||||||
m.mutex.Lock()
|
m.lock()
|
||||||
defer m.mutex.Unlock()
|
defer m.unlock()
|
||||||
return root_dir == m.current_root_dir && query == m.current_query
|
return root_dir == m.current_root_dir && query == m.current_query
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ResultManager) set_query(query string) {
|
func (m *ResultManager) set_query(query string) {
|
||||||
m.mutex.Lock()
|
var sqr *ScoreRequest
|
||||||
defer m.mutex.Unlock()
|
m.lock()
|
||||||
|
defer func() {
|
||||||
|
m.unlock()
|
||||||
|
if sqr != nil {
|
||||||
|
m.score_queries <- *sqr
|
||||||
|
}
|
||||||
|
}()
|
||||||
if query == m.current_query {
|
if query == m.current_query {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -357,224 +418,18 @@ func (m *ResultManager) set_query(query string) {
|
|||||||
m.matches_for_current_query = nil
|
m.matches_for_current_query = nil
|
||||||
m.renderable_results = nil
|
m.renderable_results = nil
|
||||||
m.current_query_scoring_complete = false
|
m.current_query_scoring_complete = false
|
||||||
|
if m.results_for_current_root_dir != nil {
|
||||||
|
s := m.create_score_query(m.results_for_current_root_dir, m.current_root_dir_scan_complete)
|
||||||
|
sqr = &s
|
||||||
|
} else if m.current_root_dir_scan_complete {
|
||||||
|
m.current_query_scoring_complete = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type dir_cache map[string][]os.DirEntry
|
func (h *Handler) get_results() (ans []ResultItem, in_progress bool) {
|
||||||
|
h.result_manager.lock()
|
||||||
type ScanCache struct {
|
defer h.result_manager.unlock()
|
||||||
dir_entries dir_cache
|
ans = h.result_manager.renderable_results
|
||||||
mutex sync.Mutex
|
in_progress = !h.result_manager.current_query_scoring_complete || !h.result_manager.current_root_dir_scan_complete
|
||||||
root_dir, search_text string
|
|
||||||
in_progress bool
|
|
||||||
only_dirs bool
|
|
||||||
matches []*ResultItem
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sc *ScanCache) get_cached_entries(root_dir string) (ans []os.DirEntry, found bool) {
|
|
||||||
sc.mutex.Lock()
|
|
||||||
defer sc.mutex.Unlock()
|
|
||||||
ans, found = sc.dir_entries[root_dir]
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *ScanCache) set_cached_entries(root_dir string, e []os.DirEntry) {
|
|
||||||
sc.mutex.Lock()
|
|
||||||
defer sc.mutex.Unlock()
|
|
||||||
sc.dir_entries[root_dir] = e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sc *ScanCache) readdir(current_dir string) (ans []os.DirEntry) {
|
|
||||||
var found bool
|
|
||||||
if ans, found = sc.get_cached_entries(current_dir); !found {
|
|
||||||
ans, _ = os.ReadDir(current_dir)
|
|
||||||
sc.set_cached_entries(current_dir, ans)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func sort_items_without_search_text(items []*ResultItem) (ans []*ResultItem) {
|
|
||||||
type s struct {
|
|
||||||
ltext string
|
|
||||||
num_of_slashes int
|
|
||||||
is_dir bool
|
|
||||||
is_hidden bool
|
|
||||||
r *ResultItem
|
|
||||||
}
|
|
||||||
hidden_pat := regexp.MustCompile(`(^|/)\.[^/]+(/|$)`)
|
|
||||||
d := utils.Map(func(x *ResultItem) s {
|
|
||||||
return s{strings.ToLower(x.text), strings.Count(x.text, "/"), x.ftype.IsDir(), hidden_pat.MatchString(x.abspath), x}
|
|
||||||
}, items)
|
|
||||||
sort.SliceStable(d, func(i, j int) bool {
|
|
||||||
a, b := d[i], d[j]
|
|
||||||
if a.num_of_slashes == b.num_of_slashes {
|
|
||||||
if a.is_dir == b.is_dir {
|
|
||||||
if a.is_hidden == b.is_hidden {
|
|
||||||
if a.ltext == b.ltext {
|
|
||||||
return count_uppercase(a.r.text) < count_uppercase(b.r.text)
|
|
||||||
}
|
|
||||||
return a.ltext < b.ltext
|
|
||||||
}
|
|
||||||
return b.is_hidden
|
|
||||||
}
|
|
||||||
return a.is_dir
|
|
||||||
}
|
|
||||||
return a.num_of_slashes < b.num_of_slashes
|
|
||||||
})
|
|
||||||
return utils.Map(func(s s) *ResultItem { return s.r }, d)
|
|
||||||
}
|
|
||||||
|
|
||||||
func get_modified_score(abspath string, score float64, score_patterns []ScorePattern) float64 {
|
|
||||||
for _, sp := range score_patterns {
|
|
||||||
if sp.pat.MatchString(abspath) {
|
|
||||||
score = sp.op(score, sp.val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return score
|
|
||||||
}
|
|
||||||
|
|
||||||
func count_uppercase(s string) int {
|
|
||||||
count := 0
|
|
||||||
for _, r := range s {
|
|
||||||
if unicode.IsUpper(r) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
type pos_in_name struct {
|
|
||||||
name string
|
|
||||||
positions []int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ResultItem) finalize(positions []pos_in_name) {
|
|
||||||
buf := strings.Builder{}
|
|
||||||
buf.Grow(256)
|
|
||||||
pos := 0
|
|
||||||
for i, x := range positions {
|
|
||||||
before := buf.Len()
|
|
||||||
buf.WriteString(x.name)
|
|
||||||
if i != len(positions)-1 {
|
|
||||||
buf.WriteRune(os.PathSeparator)
|
|
||||||
}
|
|
||||||
for _, p := range x.positions {
|
|
||||||
r.positions = append(r.positions, p+pos)
|
|
||||||
}
|
|
||||||
pos += buf.Len() - before
|
|
||||||
}
|
|
||||||
r.text = buf.String()
|
|
||||||
if r.text == "" {
|
|
||||||
r.text = string(os.PathSeparator)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sc *ScanCache) scan_dir(abspath string, patterns []string, positions []pos_in_name, score float64) (ans []*ResultItem) {
|
|
||||||
if entries := sc.readdir(abspath); len(entries) > 0 {
|
|
||||||
npos := make([]pos_in_name, len(positions)+1)
|
|
||||||
copy(npos, positions)
|
|
||||||
if sc.only_dirs {
|
|
||||||
entries = utils.Filter(entries, func(e os.DirEntry) bool { return e.IsDir() })
|
|
||||||
}
|
|
||||||
names := make([]string, len(entries))
|
|
||||||
for i, e := range entries {
|
|
||||||
names[i] = e.Name()
|
|
||||||
}
|
|
||||||
var scores []*subseq.Match
|
|
||||||
pattern := ""
|
|
||||||
if len(patterns) > 0 {
|
|
||||||
pattern = patterns[0]
|
|
||||||
}
|
|
||||||
if pattern != "" {
|
|
||||||
scores = subseq.ScoreItems(pattern, names, subseq.Options{})
|
|
||||||
} else {
|
|
||||||
null := subseq.Match{}
|
|
||||||
scores = slices.Repeat([]*subseq.Match{&null}, len(names))
|
|
||||||
}
|
|
||||||
is_last := pattern == "" || len(patterns) <= 1
|
|
||||||
for i, n := range names {
|
|
||||||
e := entries[i]
|
|
||||||
child_abspath := filepath.Join(abspath, n)
|
|
||||||
if pattern == "" || scores[i].Score > 0 {
|
|
||||||
npos[len(positions)] = pos_in_name{name: n, positions: scores[i].Positions}
|
|
||||||
if is_last {
|
|
||||||
r := &ResultItem{score: score + scores[i].Score, ftype: entries[i].Type(), abspath: child_abspath}
|
|
||||||
r.finalize(npos)
|
|
||||||
ans = append(ans, r)
|
|
||||||
} else if e.IsDir() {
|
|
||||||
ans = append(ans, sc.scan_dir(child_abspath, patterns[1:], npos, scores[i].Score+score)...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sc *ScanCache) scan(root_dir, search_text string, score_patterns []ScorePattern) (ans []*ResultItem) {
|
|
||||||
var patterns []string
|
|
||||||
switch search_text {
|
|
||||||
case "", "/":
|
|
||||||
default:
|
|
||||||
patterns = strings.Split(filepath.Clean(search_text), string(os.PathSeparator))
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(search_text, "/") {
|
|
||||||
root_dir = "/"
|
|
||||||
if len(patterns) > 0 {
|
|
||||||
patterns = patterns[1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ans = sc.scan_dir(root_dir, patterns, nil, 0)
|
|
||||||
for _, ri := range ans {
|
|
||||||
ri.score = get_modified_score(ri.abspath, ri.score, score_patterns)
|
|
||||||
}
|
|
||||||
has_search_text := search_text != "" && search_text != "/"
|
|
||||||
if !has_search_text {
|
|
||||||
return sort_items_without_search_text(ans)
|
|
||||||
}
|
|
||||||
slices.SortStableFunc(ans, func(a, b *ResultItem) int {
|
|
||||||
ans := cmp.Compare(b.score, a.score)
|
|
||||||
if ans == 0 {
|
|
||||||
ans = cmp.Compare(len(a.text), len(b.text))
|
|
||||||
if ans == 0 {
|
|
||||||
ans = cmp.Compare(count_uppercase(a.text), count_uppercase(b.text))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ans
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handler) get_results() (ans []*ResultItem, in_progress bool) {
|
|
||||||
sc := &h.scan_cache
|
|
||||||
sc.mutex.Lock()
|
|
||||||
defer sc.mutex.Unlock()
|
|
||||||
if sc.dir_entries == nil {
|
|
||||||
sc.dir_entries = make(dir_cache, 512)
|
|
||||||
}
|
|
||||||
cd := h.state.CurrentDir()
|
|
||||||
st := h.state.SearchText()
|
|
||||||
only_dirs := h.state.mode.OnlyDirs()
|
|
||||||
if st != "" {
|
|
||||||
st = filepath.Clean(st)
|
|
||||||
}
|
|
||||||
if sc.root_dir == cd && sc.search_text == st && sc.only_dirs == only_dirs {
|
|
||||||
return sc.matches, sc.in_progress
|
|
||||||
}
|
|
||||||
sc.in_progress = true
|
|
||||||
sc.matches = nil
|
|
||||||
sc.root_dir = cd
|
|
||||||
sc.search_text = st
|
|
||||||
sc.only_dirs = only_dirs
|
|
||||||
sp := h.state.ScorePatterns()
|
|
||||||
go func() {
|
|
||||||
defer h.lp.RecoverFromPanicInGoRoutine()
|
|
||||||
results := sc.scan(cd, st, sp)
|
|
||||||
sc.mutex.Lock()
|
|
||||||
defer sc.mutex.Unlock()
|
|
||||||
if cd == sc.root_dir && st == sc.search_text && sc.only_dirs == only_dirs {
|
|
||||||
sc.matches = results
|
|
||||||
sc.in_progress = false
|
|
||||||
h.lp.WakeupMainThread()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return sc.matches, sc.in_progress
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ func (h *Handler) handle_edit_keys(ev *loop.KeyEvent) bool {
|
|||||||
} else {
|
} else {
|
||||||
g := wcswidth.SplitIntoGraphemes(h.state.search_text)
|
g := wcswidth.SplitIntoGraphemes(h.state.search_text)
|
||||||
h.state.SetSearchText(strings.Join(g[:len(g)-1], ""))
|
h.state.SetSearchText(strings.Join(g[:len(g)-1], ""))
|
||||||
|
h.result_manager.set_query(h.state.SearchText())
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user