mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-05-13 16:37:27 +00:00
Work on rendering results
This commit is contained in:
parent
cc500893f0
commit
c2c9d2ceb2
4 changed files with 197 additions and 19 deletions
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
172
kittens/choose_files/results.go
Normal file
172
kittens/choose_files/results.go
Normal 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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue