mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-08 22:28:24 +02:00
Implement metadata based previews in choose-files
This commit is contained in:
@@ -12,7 +12,7 @@ The choose-files kitten is designed to allow you to select files, very fast,
|
|||||||
with just a few key strokes. It operates like `fzf
|
with just a few key strokes. It operates like `fzf
|
||||||
<https://github.com/junegunn/fzf/>`__ and similar fuzzy finders, except that
|
<https://github.com/junegunn/fzf/>`__ and similar fuzzy finders, except that
|
||||||
it is specialised for finding files. As such it supports features such as
|
it is specialised for finding files. As such it supports features such as
|
||||||
filtering by file type, file type icons, content previews (coming soon) and
|
filtering by file type, file type icons, content previews and
|
||||||
so on, out of the box. It can be used as a drop in (but much more efficient and
|
so on, out of the box. It can be used as a drop in (but much more efficient and
|
||||||
keyboard friendly) replacement for the :guilabel:`File open and save`
|
keyboard friendly) replacement for the :guilabel:`File open and save`
|
||||||
dialog boxes common to GUI programs. On Linux, with the help of the
|
dialog boxes common to GUI programs. On Linux, with the help of the
|
||||||
|
|||||||
@@ -97,8 +97,8 @@ func (m Mode) WindowTitle() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type render_state struct {
|
type render_state struct {
|
||||||
num_matches, num_of_slots, num_before, num_per_column, num_columns, num_shown int
|
num_matches, num_of_slots, num_before, num_per_column, num_columns, num_shown, preview_width int
|
||||||
first_idx CollectionIndex
|
first_idx CollectionIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
type State struct {
|
type State struct {
|
||||||
@@ -116,6 +116,7 @@ type State struct {
|
|||||||
filter_map map[string]Filter
|
filter_map map[string]Filter
|
||||||
filter_names []string
|
filter_names []string
|
||||||
show_hidden bool
|
show_hidden bool
|
||||||
|
show_preview bool
|
||||||
respect_ignores bool
|
respect_ignores bool
|
||||||
sort_by_last_modified bool
|
sort_by_last_modified bool
|
||||||
global_ignores ignorefiles.IgnoreFile
|
global_ignores ignorefiles.IgnoreFile
|
||||||
@@ -131,6 +132,7 @@ type State struct {
|
|||||||
|
|
||||||
func (s State) DisplayTitle() bool { return s.display_title }
|
func (s State) DisplayTitle() bool { return s.display_title }
|
||||||
func (s State) ShowHidden() bool { return s.show_hidden }
|
func (s State) ShowHidden() bool { return s.show_hidden }
|
||||||
|
func (s State) ShowPreview() bool { return s.show_preview }
|
||||||
func (s State) RespectIgnores() bool { return s.respect_ignores }
|
func (s State) RespectIgnores() bool { return s.respect_ignores }
|
||||||
func (s State) SortByLastModified() bool { return s.sort_by_last_modified }
|
func (s State) SortByLastModified() bool { return s.sort_by_last_modified }
|
||||||
func (s State) GlobalIgnores() ignorefiles.IgnoreFile { return s.global_ignores }
|
func (s State) GlobalIgnores() ignorefiles.IgnoreFile { return s.global_ignores }
|
||||||
@@ -209,6 +211,7 @@ type Handler struct {
|
|||||||
shortcut_tracker config.ShortcutTracker
|
shortcut_tracker config.ShortcutTracker
|
||||||
msg_printer *message.Printer
|
msg_printer *message.Printer
|
||||||
spinner *tui.Spinner
|
spinner *tui.Spinner
|
||||||
|
preview_manager *PreviewManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) draw_screen() (err error) {
|
func (h *Handler) draw_screen() (err error) {
|
||||||
@@ -482,6 +485,9 @@ func (h *Handler) dispatch_action(name, args string) (err error) {
|
|||||||
}
|
}
|
||||||
case "toggle":
|
case "toggle":
|
||||||
switch args {
|
switch args {
|
||||||
|
case "preview":
|
||||||
|
h.state.show_preview = !h.state.show_preview
|
||||||
|
return h.draw_screen()
|
||||||
case "dotfiles":
|
case "dotfiles":
|
||||||
h.state.show_hidden = !h.state.show_hidden
|
h.state.show_hidden = !h.state.show_hidden
|
||||||
h.result_manager.set_show_hidden()
|
h.result_manager.set_show_hidden()
|
||||||
@@ -577,6 +583,7 @@ func (h *Handler) OnText(text string, from_key_event, in_bracketed_paste bool) (
|
|||||||
|
|
||||||
type CachedValues struct {
|
type CachedValues struct {
|
||||||
Show_hidden bool `json:"show_hidden"`
|
Show_hidden bool `json:"show_hidden"`
|
||||||
|
Hide_preview bool `json:"hide_preview"`
|
||||||
Respect_ignores bool `json:"respect_ignores"`
|
Respect_ignores bool `json:"respect_ignores"`
|
||||||
Sort_by_last_modified bool `json:"sort_by_last_modified"`
|
Sort_by_last_modified bool `json:"sort_by_last_modified"`
|
||||||
}
|
}
|
||||||
@@ -593,7 +600,7 @@ var cached_values = sync.OnceValue(func() *CachedValues {
|
|||||||
})
|
})
|
||||||
|
|
||||||
func (s State) save_cached_values() {
|
func (s State) save_cached_values() {
|
||||||
c := CachedValues{Show_hidden: s.show_hidden, Respect_ignores: s.respect_ignores, Sort_by_last_modified: s.sort_by_last_modified}
|
c := CachedValues{Show_hidden: s.show_hidden, Respect_ignores: s.respect_ignores, Sort_by_last_modified: s.sort_by_last_modified, Hide_preview: !s.show_preview}
|
||||||
fname := filepath.Join(utils.CacheDir(), cache_filename)
|
fname := filepath.Join(utils.CacheDir(), cache_filename)
|
||||||
if data, err := json.Marshal(c); err == nil {
|
if data, err := json.Marshal(c); err == nil {
|
||||||
_ = os.WriteFile(fname, data, 0600)
|
_ = os.WriteFile(fname, data, 0600)
|
||||||
@@ -661,6 +668,8 @@ func (h *Handler) set_state_from_config(conf *Config, opts *Options) (err error)
|
|||||||
h.state.sort_by_last_modified = false
|
h.state.sort_by_last_modified = false
|
||||||
h.state.respect_ignores = true
|
h.state.respect_ignores = true
|
||||||
h.state.show_hidden = false
|
h.state.show_hidden = false
|
||||||
|
h.state.show_preview = true
|
||||||
|
|
||||||
switch conf.Show_hidden {
|
switch conf.Show_hidden {
|
||||||
case Show_hidden_true, Show_hidden_y, Show_hidden_yes:
|
case Show_hidden_true, Show_hidden_y, Show_hidden_yes:
|
||||||
h.state.show_hidden = true
|
h.state.show_hidden = true
|
||||||
@@ -685,6 +694,15 @@ func (h *Handler) set_state_from_config(conf *Config, opts *Options) (err error)
|
|||||||
case Sort_by_last_modified_last:
|
case Sort_by_last_modified_last:
|
||||||
h.state.sort_by_last_modified = cached_values().Sort_by_last_modified
|
h.state.sort_by_last_modified = cached_values().Sort_by_last_modified
|
||||||
}
|
}
|
||||||
|
switch conf.Show_preview {
|
||||||
|
case Show_preview_true, Show_preview_y, Show_preview_yes:
|
||||||
|
h.state.show_preview = true
|
||||||
|
case Show_preview_false, Show_preview_n, Show_preview_no:
|
||||||
|
h.state.show_preview = false
|
||||||
|
case Show_preview_last:
|
||||||
|
h.state.show_preview = !cached_values().Hide_preview
|
||||||
|
}
|
||||||
|
|
||||||
h.state.global_ignores = ignorefiles.NewGitignore()
|
h.state.global_ignores = ignorefiles.NewGitignore()
|
||||||
if err = h.state.global_ignores.LoadLines(conf.Ignore...); err != nil {
|
if err = h.state.global_ignores.LoadLines(conf.Ignore...); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -753,6 +771,7 @@ func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) {
|
|||||||
return 1, err
|
return 1, err
|
||||||
}
|
}
|
||||||
handler.result_manager = NewResultManager(handler.err_chan, &handler.state, lp.WakeupMainThread)
|
handler.result_manager = NewResultManager(handler.err_chan, &handler.state, lp.WakeupMainThread)
|
||||||
|
handler.preview_manager = NewPreviewManager(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 {
|
||||||
|
|||||||
@@ -40,6 +40,10 @@ Anchored patterns match with respect to whatever directory is currently being di
|
|||||||
Can be specified multiple times to use multiple patterns. Note that every pattern
|
Can be specified multiple times to use multiple patterns. Note that every pattern
|
||||||
has to be checked against every file, so use sparingly.
|
has to be checked against every file, so use sparingly.
|
||||||
''')
|
''')
|
||||||
|
|
||||||
|
opt('show_preview', 'last', choices=('last', 'yes', 'y', 'true', 'no', 'n', 'false'), long_text='''
|
||||||
|
Whether to show a preview of the current file/directory. The default value of :code:`last` means remember the last
|
||||||
|
used value. This setting can be toggled withing the program.''')
|
||||||
egr() # }}}
|
egr() # }}}
|
||||||
|
|
||||||
agr('shortcuts', 'Keyboard shortcuts') # {{{
|
agr('shortcuts', 'Keyboard shortcuts') # {{{
|
||||||
@@ -81,6 +85,7 @@ map('Previous filter', 'prev_filter alt+f -1')
|
|||||||
map('Toggle showing dotfiles', 'toggle_dotfiles alt+h toggle dotfiles')
|
map('Toggle showing dotfiles', 'toggle_dotfiles alt+h toggle dotfiles')
|
||||||
map('Toggle showing ignored files', 'toggle_ignorefiles alt+i toggle ignorefiles')
|
map('Toggle showing ignored files', 'toggle_ignorefiles alt+i toggle ignorefiles')
|
||||||
map('Toggle sorting by dates', 'toggle_sort_by_dates alt+d toggle sort_by_dates')
|
map('Toggle sorting by dates', 'toggle_sort_by_dates alt+d toggle sort_by_dates')
|
||||||
|
map('Toggle showing preview', 'toggle_preview alt+p toggle preview')
|
||||||
|
|
||||||
egr() # }}}
|
egr() # }}}
|
||||||
|
|
||||||
|
|||||||
201
kittens/choose_files/preview.go
Normal file
201
kittens/choose_files/preview.go
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
package choose_files
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/kovidgoyal/kitty/tools/icons"
|
||||||
|
"github.com/kovidgoyal/kitty/tools/utils"
|
||||||
|
"github.com/kovidgoyal/kitty/tools/utils/humanize"
|
||||||
|
"github.com/kovidgoyal/kitty/tools/utils/style"
|
||||||
|
"github.com/kovidgoyal/kitty/tools/wcswidth"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = fmt.Print
|
||||||
|
|
||||||
|
type Preview interface {
|
||||||
|
Render(h *Handler, x, y, width, height int)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PreviewManager struct {
|
||||||
|
report_errors chan error
|
||||||
|
settings Settings
|
||||||
|
WakeupMainThread func() bool
|
||||||
|
cache map[string]Preview
|
||||||
|
lock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPreviewManager(err_chan chan error, settings Settings, WakeupMainThread func() bool) *PreviewManager {
|
||||||
|
return &PreviewManager{
|
||||||
|
report_errors: err_chan, settings: settings, WakeupMainThread: WakeupMainThread,
|
||||||
|
cache: make(map[string]Preview),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *PreviewManager) cached_preview(path string) Preview {
|
||||||
|
pm.lock.Lock()
|
||||||
|
defer pm.lock.Unlock()
|
||||||
|
return pm.cache[path]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *PreviewManager) set_cached_preview(path string, val Preview) {
|
||||||
|
pm.lock.Lock()
|
||||||
|
defer pm.lock.Unlock()
|
||||||
|
pm.cache[path] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) render_wrapped_text_in_region(text string, x, y, width, height int, centered bool) int {
|
||||||
|
lines := style.WrapTextAsLines(text, width, style.WrapOptions{})
|
||||||
|
for i, line := range lines {
|
||||||
|
extra := 0
|
||||||
|
if centered {
|
||||||
|
extra = max(0, width-wcswidth.Stringwidth(line)) / 2
|
||||||
|
}
|
||||||
|
h.lp.MoveCursorTo(x+extra, y+i)
|
||||||
|
h.lp.QueueWriteString(line)
|
||||||
|
if i >= height {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessagePreview struct {
|
||||||
|
title string
|
||||||
|
msg string
|
||||||
|
trailers []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p MessagePreview) Render(h *Handler, x, y, width, height int) {
|
||||||
|
offset := 0
|
||||||
|
if p.title != "" {
|
||||||
|
offset += h.render_wrapped_text_in_region(p.title, x, y, width, height, true)
|
||||||
|
}
|
||||||
|
offset += h.render_wrapped_text_in_region(p.msg, x, y+offset, width, height-offset, false)
|
||||||
|
limit := height - offset
|
||||||
|
if limit > 1 {
|
||||||
|
for i, line := range p.trailers {
|
||||||
|
text := wcswidth.TruncateToVisualLength(line, width-1)
|
||||||
|
if len(text) < len(line) {
|
||||||
|
text += "…"
|
||||||
|
}
|
||||||
|
h.lp.MoveCursorTo(x, y+offset+i-1)
|
||||||
|
if i >= limit {
|
||||||
|
h.lp.QueueWriteString("…")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
h.lp.QueueWriteString(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewErrorPreview(err error) Preview {
|
||||||
|
sctx := style.Context{AllowEscapeCodes: true}
|
||||||
|
text := fmt.Sprintf("%s: %s", sctx.SprintFunc("fg=red")("Error"), err)
|
||||||
|
return &MessagePreview{msg: text}
|
||||||
|
}
|
||||||
|
|
||||||
|
func write_file_metadata(abspath string, metadata fs.FileInfo, entries []fs.DirEntry) (header string, trailers []string) {
|
||||||
|
buf := strings.Builder{}
|
||||||
|
buf.Grow(4096)
|
||||||
|
add := func(key, val string) { fmt.Fprintf(&buf, "%s: %s\n", key, val) }
|
||||||
|
ftype := metadata.Mode().Type()
|
||||||
|
const file_icon = " "
|
||||||
|
switch ftype {
|
||||||
|
case 0:
|
||||||
|
add("Size", humanize.Bytes(uint64(metadata.Size())))
|
||||||
|
case fs.ModeSymlink:
|
||||||
|
if tgt, err := os.Readlink(abspath); err == nil {
|
||||||
|
add("Target", tgt)
|
||||||
|
} else {
|
||||||
|
add("Target", err.Error())
|
||||||
|
}
|
||||||
|
case fs.ModeDir:
|
||||||
|
num_files, num_dirs := 0, 0
|
||||||
|
for _, e := range entries {
|
||||||
|
if e.IsDir() {
|
||||||
|
num_dirs++
|
||||||
|
} else {
|
||||||
|
num_files++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add("Children", fmt.Sprintf("%d %s %d %s", num_dirs, icons.IconForFileWithMode("dir", fs.ModeDir, false), num_files, file_icon))
|
||||||
|
}
|
||||||
|
add("Modified", humanize.Time(metadata.ModTime()))
|
||||||
|
add("Mode", metadata.Mode().String())
|
||||||
|
if len(entries) > 0 {
|
||||||
|
type entry struct {
|
||||||
|
lname string
|
||||||
|
ftype fs.FileMode
|
||||||
|
}
|
||||||
|
type_map := make(map[string]entry, len(entries))
|
||||||
|
for _, e := range entries {
|
||||||
|
type_map[e.Name()] = entry{strings.ToLower(e.Name()), e.Type()}
|
||||||
|
}
|
||||||
|
names := utils.Map(func(e fs.DirEntry) string { return e.Name() }, entries)
|
||||||
|
slices.SortFunc(names, func(a, b string) int { return strings.Compare(type_map[a].lname, type_map[b].lname) })
|
||||||
|
fmt.Fprintln(&buf, "Contents:")
|
||||||
|
for _, n := range names {
|
||||||
|
trailers = append(trailers, icons.IconForFileWithMode(n, type_map[n].ftype, false)+" "+n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.String(), trailers
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDirectoryPreview(abspath string, metadata fs.FileInfo) Preview {
|
||||||
|
entries, err := os.ReadDir(abspath)
|
||||||
|
if err != nil {
|
||||||
|
return NewErrorPreview(fmt.Errorf("failed to read the directory %s with error: %w", abspath, err))
|
||||||
|
}
|
||||||
|
title := icons.IconForFileWithMode("dir", fs.ModeDir, false) + " Directory\n"
|
||||||
|
header, extra := write_file_metadata(abspath, metadata, entries)
|
||||||
|
return &MessagePreview{title: title, msg: header, trailers: extra}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFileMetadataPreview(abspath string, metadata fs.FileInfo) Preview {
|
||||||
|
title := icons.IconForFileWithMode(filepath.Base(abspath), metadata.Mode().Type(), false) + " File"
|
||||||
|
h, t := write_file_metadata(abspath, metadata, nil)
|
||||||
|
return &MessagePreview{title: title, msg: h, trailers: t}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *PreviewManager) preview_for(abspath string, ftype fs.FileMode) (ans Preview) {
|
||||||
|
if ans = pm.cached_preview(abspath); ans != nil {
|
||||||
|
return ans
|
||||||
|
}
|
||||||
|
defer func() { pm.set_cached_preview(abspath, ans) }()
|
||||||
|
s, err := os.Lstat(abspath)
|
||||||
|
if err != nil {
|
||||||
|
return NewErrorPreview(err)
|
||||||
|
}
|
||||||
|
if s.IsDir() {
|
||||||
|
return NewDirectoryPreview(abspath, s)
|
||||||
|
}
|
||||||
|
if ftype&fs.ModeSymlink != 0 && ftype&SymlinkToDir != 0 {
|
||||||
|
s, err = os.Stat(abspath)
|
||||||
|
if err != nil {
|
||||||
|
return NewErrorPreview(err)
|
||||||
|
}
|
||||||
|
return NewDirectoryPreview(abspath, s)
|
||||||
|
}
|
||||||
|
return NewFileMetadataPreview(abspath, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) draw_preview_content(x, y, width, height int) {
|
||||||
|
matches, _ := h.get_results()
|
||||||
|
r := matches.At(h.state.CurrentIndex())
|
||||||
|
if r == nil {
|
||||||
|
h.render_wrapped_text_in_region("No preview available", x, y, width, height, false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
abspath := filepath.Join(h.state.CurrentDir(), r.text)
|
||||||
|
if p := h.preview_manager.preview_for(abspath, r.ftype); p == nil {
|
||||||
|
h.render_wrapped_text_in_region("No preview available", x, y, width, height, false)
|
||||||
|
} else {
|
||||||
|
p.Render(h, x, y, width, height)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -208,8 +208,22 @@ func (h *Handler) draw_column_of_matches(matches ResultsType, current_idx int, x
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) draw_list_of_results(matches *SortedResults, y, height int) (num_cols, num_shown int) {
|
func (h *Handler) draw_list_of_results(matches *SortedResults, y, height int) (num_cols, num_shown, preview_width int) {
|
||||||
|
const BASE_COL_WIDTH = 40
|
||||||
available_width := h.screen_size.width - 2
|
available_width := h.screen_size.width - 2
|
||||||
|
show_preview := h.state.ShowPreview()
|
||||||
|
if show_preview && available_width < BASE_COL_WIDTH+30 {
|
||||||
|
show_preview = false
|
||||||
|
}
|
||||||
|
if show_preview {
|
||||||
|
switch {
|
||||||
|
case available_width < BASE_COL_WIDTH*2:
|
||||||
|
preview_width = max(30, available_width/2)
|
||||||
|
default:
|
||||||
|
preview_width = BASE_COL_WIDTH
|
||||||
|
}
|
||||||
|
available_width -= preview_width
|
||||||
|
}
|
||||||
col_width := available_width
|
col_width := available_width
|
||||||
num_cols = 1
|
num_cols = 1
|
||||||
calc_num_cols := func(num_matches int) int {
|
calc_num_cols := func(num_matches int) int {
|
||||||
@@ -217,7 +231,7 @@ func (h *Handler) draw_list_of_results(matches *SortedResults, y, height int) (n
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
if num_matches > height {
|
if num_matches > height {
|
||||||
col_width = 40
|
col_width = BASE_COL_WIDTH
|
||||||
num_cols = available_width / col_width
|
num_cols = available_width / col_width
|
||||||
for num_cols > 0 && height*(num_cols-1) >= num_matches {
|
for num_cols > 0 && height*(num_cols-1) >= num_matches {
|
||||||
num_cols--
|
num_cols--
|
||||||
@@ -239,7 +253,7 @@ func (h *Handler) draw_list_of_results(matches *SortedResults, y, height int) (n
|
|||||||
num_shown += len(col)
|
num_shown += len(col)
|
||||||
x += col_width
|
x += col_width
|
||||||
}
|
}
|
||||||
return len(columns), num_shown
|
return len(columns), num_shown, preview_width
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) draw_num_of_matches(num_shown, y int, in_progress bool) {
|
func (h *Handler) draw_num_of_matches(num_shown, y int, in_progress bool) {
|
||||||
@@ -274,6 +288,23 @@ func (h *Handler) draw_num_of_matches(num_shown, y int, in_progress bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) draw_preview(y int) {
|
||||||
|
x := h.screen_size.width - h.state.last_render.preview_width
|
||||||
|
height := h.state.last_render.num_of_slots
|
||||||
|
buf := strings.Builder{}
|
||||||
|
buf.Grow(16 * height)
|
||||||
|
buf.WriteString(fmt.Sprintf(loop.MoveCursorToTemplate, y-1, x))
|
||||||
|
buf.WriteString("┬")
|
||||||
|
for i := range height {
|
||||||
|
buf.WriteString(fmt.Sprintf(loop.MoveCursorToTemplate, y+i, x))
|
||||||
|
buf.WriteString("│")
|
||||||
|
}
|
||||||
|
buf.WriteString(fmt.Sprintf(loop.MoveCursorToTemplate, y+height, x))
|
||||||
|
buf.WriteString("┴")
|
||||||
|
h.lp.QueueWriteString(buf.String())
|
||||||
|
h.draw_preview_content(x+1, y, h.state.last_render.preview_width-1, h.state.last_render.num_of_slots)
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) draw_results(y, bottom_margin int, matches *SortedResults, in_progress bool) (height int) {
|
func (h *Handler) draw_results(y, bottom_margin int, matches *SortedResults, 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)
|
||||||
@@ -286,15 +317,19 @@ func (h *Handler) draw_results(y, bottom_margin int, matches *SortedResults, in_
|
|||||||
num_cols := 0
|
num_cols := 0
|
||||||
num := matches.Len()
|
num := matches.Len()
|
||||||
num_shown := 0
|
num_shown := 0
|
||||||
|
h.state.last_render.preview_width = 0
|
||||||
switch num {
|
switch num {
|
||||||
case 0:
|
case 0:
|
||||||
h.draw_no_matches_message(in_progress)
|
h.draw_no_matches_message(in_progress)
|
||||||
default:
|
default:
|
||||||
num_cols, num_shown = h.draw_list_of_results(matches, y, h.state.last_render.num_of_slots)
|
num_cols, num_shown, h.state.last_render.preview_width = h.draw_list_of_results(matches, y, h.state.last_render.num_of_slots)
|
||||||
}
|
}
|
||||||
h.state.last_render.num_matches = num
|
h.state.last_render.num_matches = num
|
||||||
h.state.last_render.num_shown = num_shown
|
h.state.last_render.num_shown = num_shown
|
||||||
h.draw_num_of_matches(h.state.last_render.num_of_slots*num_cols, y+height-2, in_progress)
|
h.draw_num_of_matches(h.state.last_render.num_of_slots*num_cols, y+height-2, in_progress)
|
||||||
|
if h.state.last_render.preview_width > 0 {
|
||||||
|
h.draw_preview(y)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,9 @@ func (h *Handler) draw_controls(y int) (max_width int) {
|
|||||||
h.state.respect_ignores = !h.state.respect_ignores
|
h.state.respect_ignores = !h.state.respect_ignores
|
||||||
h.result_manager.set_respect_ignores()
|
h.result_manager.set_respect_ignores()
|
||||||
})
|
})
|
||||||
|
add_control(" ", utils.IfElse(h.state.ShowPreview(), "hide preview", "show preview"), func() {
|
||||||
|
h.state.show_preview = !h.state.show_preview
|
||||||
|
})
|
||||||
add_control(utils.IfElse(h.state.SortByLastModified(), " ", " "), utils.IfElse(h.state.SortByLastModified(), "sort names", "sort dates"), func() {
|
add_control(utils.IfElse(h.state.SortByLastModified(), " ", " "), utils.IfElse(h.state.SortByLastModified(), "sort names", "sort dates"), func() {
|
||||||
h.state.sort_by_last_modified = !h.state.sort_by_last_modified
|
h.state.sort_by_last_modified = !h.state.sort_by_last_modified
|
||||||
h.result_manager.set_sort_by_last_modified()
|
h.result_manager.set_sort_by_last_modified()
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ def completion(self: TestCompletion, tdir: str):
|
|||||||
env['PATH'] = os.path.join(tdir, 'bin')
|
env['PATH'] = os.path.join(tdir, 'bin')
|
||||||
env['HOME'] = os.path.join(tdir, 'sub')
|
env['HOME'] = os.path.join(tdir, 'sub')
|
||||||
env['KITTY_CONFIG_DIRECTORY'] = os.path.join(tdir, 'sub')
|
env['KITTY_CONFIG_DIRECTORY'] = os.path.join(tdir, 'sub')
|
||||||
|
print(1111111, all_argv)
|
||||||
cp = subprocess.run(
|
cp = subprocess.run(
|
||||||
[kitten(), '__complete__', 'json'],
|
[kitten(), '__complete__', 'json'],
|
||||||
check=True, stdout=subprocess.PIPE, cwd=tdir, input=json.dumps(all_argv).encode(), env=env
|
check=True, stdout=subprocess.PIPE, cwd=tdir, input=json.dumps(all_argv).encode(), env=env
|
||||||
|
|||||||
Reference in New Issue
Block a user