Work on rendering results

This commit is contained in:
Kovid Goyal 2025-05-21 12:41:56 +05:30
parent cc500893f0
commit c2c9d2ceb2
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
4 changed files with 197 additions and 19 deletions

View file

@ -44,16 +44,16 @@ type Handler struct {
}
func (h *Handler) draw_screen() (err error) {
h.get_results()
matches, in_progress := h.get_results()
h.lp.StartAtomicUpdate()
defer h.lp.EndAtomicUpdate()
h.lp.ClearScreen()
y := 0
if dy, err := h.draw_search_bar(y); err != nil {
return err
} else {
y += dy
}
defer func() { // so that the cursor ends up in the right place
h.lp.MoveCursorTo(1, 1)
h.draw_search_bar(0)
}()
y := SEARCH_BAR_HEIGHT
y += h.draw_results(y, 4, matches, in_progress)
return
}

View file

@ -0,0 +1,172 @@
package choose_files
import (
"fmt"
"os"
"path/filepath"
"strings"
"unicode/utf8"
"github.com/kovidgoyal/kitty/tools/tui/loop"
"github.com/kovidgoyal/kitty/tools/utils"
"github.com/kovidgoyal/kitty/tools/utils/style"
"github.com/kovidgoyal/kitty/tools/wcswidth"
)
var _ = fmt.Print
func (h *Handler) draw_results_title() {
text := filepath.Clean(h.state.BaseDir())
home := filepath.Clean(utils.Expanduser("~"))
if strings.HasPrefix(text, home) {
text = "~" + text[len(home):]
}
available_width := h.screen_size.width - 9
if available_width < 2 {
return
}
tt := wcswidth.TruncateToVisualLength(text, available_width)
if len(tt) < len(text) {
text = wcswidth.TruncateToVisualLength(text, available_width-1)
}
text = ` 📁 ` + text + ` `
extra := available_width - wcswidth.Stringwidth(text)
x := 3
if extra > 1 {
x += extra / 2
}
h.lp.MoveCursorHorizontally(x)
h.lp.QueueWriteString(text)
}
func (h *Handler) draw_no_matches_message(in_progress bool) {
text := "Scanning filesystem, please wait…"
if !in_progress {
text = utils.IfElse(h.state.SearchText() == "", "No files present in this folder", "No matches found")
}
for _, line := range style.WrapTextAsLines(text, h.screen_size.width-2, style.WrapOptions{}) {
h.lp.QueueWriteString("\r")
h.lp.MoveCursorHorizontally(1)
h.lp.QueueWriteString(line)
h.lp.MoveCursorVertically(1)
}
}
func (h *Handler) draw_matching_result(r ResultItem) {
}
func (h *Handler) render_match_with_positions(text string, stop_at int, positions []int, scale int) {
prefix, suffix, _ := strings.Cut(h.lp.SprintStyled("fg=green", " "), " ")
write_chunk := func(text string, emphasize bool) {
if text == "" {
return
}
if emphasize {
h.lp.QueueWriteString(prefix)
defer func() {
h.lp.QueueWriteString(suffix)
}()
}
if scale > 1 {
h.lp.DrawSizedText(text, loop.SizedText{Scale: scale})
} else {
h.lp.QueueWriteString(text)
}
}
at := 0
limit := min(stop_at, len(text))
for _, p := range positions {
if p > limit || at > limit {
break
}
write_chunk(text[at:p], false)
at = p
if r, sz := utf8.DecodeRuneInString(text[p:]); r != utf8.RuneError {
write_chunk(string(r), true)
at += sz
}
}
if at < len(text) {
write_chunk(text[at:], false)
}
}
func icon_for(x os.DirEntry) string {
if x.IsDir() {
return `📁`
}
return "XX"
}
func (h *Handler) draw_column_of_matches(matches []ResultItem, x, available_width int, has_extra_matches bool) {
for i, m := range matches {
h.lp.QueueWriteString("\r")
h.lp.MoveCursorHorizontally(x)
text := ""
if has_extra_matches && i == len(matches)-1 {
text = "…"
} else {
text = m.text
tlen := len(text)
if wcswidth.Stringwidth(text) > available_width-3 {
text = wcswidth.TruncateToVisualLength(text, available_width-4) + "…"
tlen = len(text) - 1
}
h.lp.QueueWriteString(icon_for(m.dir_entry) + " ")
h.render_match_with_positions(text, tlen, m.positions, 1)
}
h.lp.MoveCursorVertically(1)
}
}
func (h *Handler) draw_list_of_results(matches []ResultItem, y, height int) {
if len(matches) == 0 || height < 2 {
return
}
available_width := h.screen_size.width - 2
col_width := available_width
num_cols := 1
if len(matches) > height {
col_width = 40
num_cols = available_width / col_width
for num_cols > 0 && height*(num_cols-1) >= len(matches) {
num_cols--
}
col_width = available_width / num_cols
}
x := 1
for i := range num_cols {
is_last := i == num_cols-1
chunk := matches[:min(len(matches), height)]
matches = matches[len(chunk):]
h.lp.MoveCursorTo(x, y)
has_extra_matches := is_last && len(matches) > 0
h.draw_column_of_matches(chunk, x, col_width-1, has_extra_matches)
x += col_width
}
}
func (h *Handler) draw_results(y, bottom_margin int, matches []ResultItem, in_progress bool) (height int) {
height = h.screen_size.height - y - bottom_margin
h.lp.MoveCursorTo(1, 1+y)
h.draw_frame(h.screen_size.width, height)
h.lp.MoveCursorTo(1, 1+y)
h.draw_results_title()
y += 2
h.lp.MoveCursorTo(1, y)
switch len(matches) {
case 0:
h.draw_no_matches_message(in_progress)
default:
switch h.state.SearchText() {
case "":
h.draw_list_of_results(matches, y, height-y)
default:
h.draw_matching_result(matches[0])
y += 2
h.draw_list_of_results(matches[1:], y, height-y)
}
}
return
}

View file

@ -66,10 +66,13 @@ func (sc *ScanCache) fs_scan(root_dir, current_dir string, max_depth int) (ans [
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)...)
ans = slices.Clone(ans)
// now recurse into directories
if max_depth > 0 {
for _, x := range ans {
if x.dir_entry.IsDir() {
ans = append(ans, sc.fs_scan(root_dir, x.abspath, max_depth-1)...)
}
}
}
return
@ -79,7 +82,7 @@ func (sc *ScanCache) scan(root_dir, search_text string, max_depth int) (ans []Re
if strings.HasPrefix(search_text, "/") {
root_dir = "/"
}
ans = slices.Clone(sc.fs_scan(root_dir, root_dir, max_depth))
ans = 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() {
@ -105,7 +108,9 @@ func (sc *ScanCache) scan(root_dir, search_text string, max_depth int) (ans []Re
for _, x := range ans {
pm[x.text] = x
}
matches := subseq.ScoreItems(search_text, utils.Keys(pm), subseq.Options{})
matches := utils.Filter(subseq.ScoreItems(search_text, utils.Keys(pm), subseq.Options{}), func(x *subseq.Match) bool {
return x.Score > 0
})
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]
@ -116,7 +121,7 @@ func (sc *ScanCache) scan(root_dir, search_text string, max_depth int) (ans []Re
return ans
}
func (h *Handler) get_results() {
func (h *Handler) get_results() (ans []ResultItem, in_progress bool) {
sc := &h.scan_cache
sc.mutex.Lock()
defer sc.mutex.Unlock()
@ -124,7 +129,7 @@ func (h *Handler) get_results() {
sc.dir_entries = make(dir_cache, 512)
}
if sc.root_dir == h.state.CurrentDir() && sc.search_text == h.state.SearchText() {
return
return sc.matches, sc.in_progress
}
sc.in_progress = true
sc.matches = nil
@ -142,4 +147,5 @@ func (h *Handler) get_results() {
h.lp.WakeupMainThread()
}
}()
return sc.matches, sc.in_progress
}

View file

@ -46,15 +46,15 @@ func (h *Handler) draw_search_text(available_width int) {
h.lp.MoveCursorHorizontally(-2)
}
func (h *Handler) draw_search_bar(y int) (height int, err error) {
const SEARCH_BAR_HEIGHT = 4
func (h *Handler) draw_search_bar(y int) {
left_margin, right_margin := 5, 5
height = 4
h.lp.MoveCursorTo(1+left_margin, 1+y)
available_width := h.screen_size.width - left_margin - right_margin
h.draw_frame(available_width, height)
h.draw_frame(available_width, SEARCH_BAR_HEIGHT)
h.lp.MoveCursorTo(1+left_margin+1, 2+y)
h.draw_search_text(available_width - 2)
return
}