mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-05-13 08:26:56 +00:00
Work on save file name mode
This commit is contained in:
parent
a023a0db09
commit
6880ecaa28
4 changed files with 260 additions and 54 deletions
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/kovidgoyal/kitty/tools/config"
|
||||
"github.com/kovidgoyal/kitty/tools/tty"
|
||||
"github.com/kovidgoyal/kitty/tools/tui/loop"
|
||||
"github.com/kovidgoyal/kitty/tools/tui/readline"
|
||||
"github.com/kovidgoyal/kitty/tools/utils"
|
||||
)
|
||||
|
||||
|
|
@ -27,6 +28,13 @@ type ScorePattern struct {
|
|||
val float64
|
||||
}
|
||||
|
||||
type Screen int
|
||||
|
||||
const (
|
||||
NORMAL Screen = iota
|
||||
SAVE_FILE
|
||||
)
|
||||
|
||||
type Mode int
|
||||
|
||||
const (
|
||||
|
|
@ -39,6 +47,14 @@ const (
|
|||
SELECT_SAVE_DIR_FOR_FILES // select a dir for saving one or more pre-sent filenames, must be an existing one
|
||||
)
|
||||
|
||||
func (m Mode) CanSelectNonExistent() bool {
|
||||
switch m {
|
||||
case SELECT_SAVE_FILE, SELECT_SAVE_DIR:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m Mode) AllowsMultipleSelection() bool {
|
||||
switch m {
|
||||
case SELECT_MULTIPLE_FILES, SELECT_MULTIPLE_DIRS:
|
||||
|
|
@ -55,6 +71,14 @@ func (m Mode) OnlyDirs() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (m Mode) SelectFiles() bool {
|
||||
switch m {
|
||||
case SELECT_SINGLE_FILE, SELECT_MULTIPLE_FILES, SELECT_SAVE_FILE:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m Mode) WindowTitle() string {
|
||||
switch m {
|
||||
case SELECT_SINGLE_FILE:
|
||||
|
|
@ -76,15 +100,18 @@ func (m Mode) WindowTitle() string {
|
|||
}
|
||||
|
||||
type State struct {
|
||||
base_dir string
|
||||
current_dir string
|
||||
select_dirs bool
|
||||
multiselect bool
|
||||
score_patterns []ScorePattern
|
||||
search_text string
|
||||
mode Mode
|
||||
window_title string
|
||||
base_dir string
|
||||
current_dir string
|
||||
select_dirs bool
|
||||
multiselect bool
|
||||
score_patterns []ScorePattern
|
||||
search_text string
|
||||
mode Mode
|
||||
suggested_save_file_name string
|
||||
window_title string
|
||||
screen Screen
|
||||
|
||||
save_file_cdir string
|
||||
selections []string
|
||||
current_idx int
|
||||
num_of_matches_at_last_render int
|
||||
|
|
@ -124,9 +151,19 @@ func (s State) WindowTitle() string {
|
|||
}
|
||||
return s.window_title
|
||||
}
|
||||
func (s *State) AddSelection(abspath string) {
|
||||
func (s *State) AddSelection(abspath string) bool {
|
||||
if !slices.Contains(s.selections, abspath) {
|
||||
s.selections = append(s.selections, abspath)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *State) ToggleSelection(abspath string) {
|
||||
before := len(s.selections)
|
||||
s.selections = slices.DeleteFunc(s.selections, func(x string) bool { return x == abspath })
|
||||
if len(s.selections) == before {
|
||||
s.selections = append(s.selections, abspath)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -139,20 +176,26 @@ type Handler struct {
|
|||
screen_size ScreenSize
|
||||
scan_cache ScanCache
|
||||
lp *loop.Loop
|
||||
rl *readline.Readline
|
||||
}
|
||||
|
||||
func (h *Handler) draw_screen() (err error) {
|
||||
matches, in_progress := h.get_results()
|
||||
h.lp.SetWindowTitle(h.state.WindowTitle())
|
||||
h.lp.StartAtomicUpdate()
|
||||
defer h.lp.EndAtomicUpdate()
|
||||
h.lp.ClearScreen()
|
||||
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, 2, matches, in_progress)
|
||||
switch h.state.screen {
|
||||
case NORMAL:
|
||||
matches, in_progress := h.get_results()
|
||||
h.lp.SetWindowTitle(h.state.WindowTitle())
|
||||
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, 2, matches, in_progress)
|
||||
case SAVE_FILE:
|
||||
err = h.draw_save_file_name_screen()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -174,6 +217,7 @@ func (h *Handler) init_sizes(new_size loop.ScreenSize) {
|
|||
h.screen_size.cell_height = int(new_size.CellHeight)
|
||||
h.screen_size.width_px = int(new_size.WidthPx)
|
||||
h.screen_size.height_px = int(new_size.HeightPx)
|
||||
h.rl.ClearCachedScreenSize()
|
||||
}
|
||||
|
||||
func (h *Handler) OnInitialize() (ans string, err error) {
|
||||
|
|
@ -184,6 +228,7 @@ func (h *Handler) OnInitialize() (ans string, err error) {
|
|||
}
|
||||
h.lp.AllowLineWrapping(false)
|
||||
h.lp.SetCursorShape(loop.BAR_CURSOR, true)
|
||||
h.lp.StartBracketedPaste()
|
||||
h.draw_screen()
|
||||
return
|
||||
}
|
||||
|
|
@ -199,57 +244,119 @@ func (h *Handler) current_abspath() string {
|
|||
|
||||
}
|
||||
|
||||
func (h *Handler) add_selection_if_possible() {
|
||||
func (h *Handler) add_selection_if_possible() bool {
|
||||
m := h.current_abspath()
|
||||
if m != "" {
|
||||
h.state.AddSelection(m)
|
||||
return h.state.AddSelection(m)
|
||||
}
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *Handler) toggle_selection() bool {
|
||||
m := h.current_abspath()
|
||||
if m != "" {
|
||||
h.state.ToggleSelection(m)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *Handler) change_to_current_dir_if_possible() error {
|
||||
matches, in_progress := h.get_results()
|
||||
if len(matches) > 0 && !in_progress {
|
||||
m := h.current_abspath()
|
||||
if st, err := os.Stat(m); err == nil {
|
||||
if !st.IsDir() {
|
||||
m = filepath.Dir(m)
|
||||
}
|
||||
h.state.SetCurrentDir(m)
|
||||
return h.draw_screen()
|
||||
}
|
||||
}
|
||||
h.lp.Beep()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) finish_selection() error {
|
||||
if h.state.mode.CanSelectNonExistent() {
|
||||
h.initialize_save_file_name()
|
||||
return h.draw_screen()
|
||||
}
|
||||
h.lp.Quit(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) OnKeyEvent(ev *loop.KeyEvent) (err error) {
|
||||
switch {
|
||||
case h.handle_edit_keys(ev), h.handle_result_list_keys(ev):
|
||||
h.draw_screen()
|
||||
case ev.MatchesPressOrRepeat("esc") || ev.MatchesPressOrRepeat("ctrl+c"):
|
||||
h.lp.Quit(1)
|
||||
case ev.MatchesPressOrRepeat("tab"):
|
||||
matches, in_progress := h.get_results()
|
||||
if len(matches) > 0 && !in_progress {
|
||||
m := h.current_abspath()
|
||||
if st, err := os.Stat(m); err == nil {
|
||||
if !st.IsDir() {
|
||||
m = filepath.Dir(m)
|
||||
switch h.state.screen {
|
||||
case NORMAL:
|
||||
switch {
|
||||
case h.handle_edit_keys(ev), h.handle_result_list_keys(ev):
|
||||
h.draw_screen()
|
||||
case ev.MatchesPressOrRepeat("esc") || ev.MatchesPressOrRepeat("ctrl+c"):
|
||||
h.lp.Quit(1)
|
||||
case ev.MatchesPressOrRepeat("tab"):
|
||||
return h.change_to_current_dir_if_possible()
|
||||
case ev.MatchesPressOrRepeat("shift+tab"):
|
||||
curr := h.state.CurrentDir()
|
||||
switch curr {
|
||||
case "/":
|
||||
case ".":
|
||||
if curr, err = os.Getwd(); err == nil && curr != "/" {
|
||||
h.state.SetCurrentDir(filepath.Dir(curr))
|
||||
return h.draw_screen()
|
||||
}
|
||||
h.state.SetCurrentDir(m)
|
||||
return h.draw_screen()
|
||||
}
|
||||
}
|
||||
h.lp.Beep()
|
||||
case ev.MatchesPressOrRepeat("shift+tab"):
|
||||
curr := h.state.CurrentDir()
|
||||
switch curr {
|
||||
case "/":
|
||||
case ".":
|
||||
if curr, err = os.Getwd(); err == nil && curr != "/" {
|
||||
default:
|
||||
h.state.SetCurrentDir(filepath.Dir(curr))
|
||||
return h.draw_screen()
|
||||
}
|
||||
default:
|
||||
h.state.SetCurrentDir(filepath.Dir(curr))
|
||||
return h.draw_screen()
|
||||
h.lp.Beep()
|
||||
case ev.MatchesPressOrRepeat("shift+enter"):
|
||||
if !h.toggle_selection() {
|
||||
h.lp.Beep()
|
||||
} else {
|
||||
if len(h.state.selections) > 0 && !h.state.mode.AllowsMultipleSelection() {
|
||||
return h.finish_selection()
|
||||
}
|
||||
return h.draw_screen()
|
||||
}
|
||||
case ev.MatchesPressOrRepeat("enter"):
|
||||
if h.state.mode.SelectFiles() {
|
||||
m := h.current_abspath()
|
||||
var s os.FileInfo
|
||||
if s, err = os.Stat(m); err != nil {
|
||||
h.lp.Beep()
|
||||
return nil
|
||||
}
|
||||
if s.IsDir() {
|
||||
return h.change_to_current_dir_if_possible()
|
||||
}
|
||||
}
|
||||
if h.add_selection_if_possible() {
|
||||
if len(h.state.selections) > 0 {
|
||||
return h.finish_selection()
|
||||
}
|
||||
return h.draw_screen()
|
||||
} else {
|
||||
h.lp.Beep()
|
||||
}
|
||||
}
|
||||
h.lp.Beep()
|
||||
case ev.MatchesPressOrRepeat("enter"):
|
||||
h.add_selection_if_possible()
|
||||
h.lp.Quit(0)
|
||||
case SAVE_FILE:
|
||||
err = h.save_file_name_handle_key(ev)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (h *Handler) OnText(text string, from_key_event, in_bracketed_paste bool) (err error) {
|
||||
h.state.search_text += text
|
||||
return h.draw_screen()
|
||||
switch h.state.screen {
|
||||
case NORMAL:
|
||||
h.state.search_text += text
|
||||
return h.draw_screen()
|
||||
case SAVE_FILE:
|
||||
if err = h.rl.OnText(text, from_key_event, in_bracketed_paste); err == nil {
|
||||
err = h.draw_screen()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func mult(a, b float64) float64 { return a * b }
|
||||
|
|
@ -296,6 +403,19 @@ func (h *Handler) set_state_from_config(conf *Config, opts *Options) (err error)
|
|||
default:
|
||||
h.state.mode = SELECT_SINGLE_FILE
|
||||
}
|
||||
h.state.suggested_save_file_name = opts.SuggestedSaveFileName
|
||||
if opts.SuggestedSaveFilePath != "" {
|
||||
switch h.state.mode {
|
||||
case SELECT_SAVE_FILE, SELECT_SAVE_DIR, SELECT_SAVE_DIR_FOR_FILES:
|
||||
if s, err := os.Stat(opts.SuggestedSaveFilePath); err == nil {
|
||||
if (s.IsDir() && h.state.mode != SELECT_SAVE_FILE) || (!s.IsDir() && h.state.mode == SELECT_SAVE_FILE) {
|
||||
if h.state.AddSelection(opts.SuggestedSaveFileName) {
|
||||
return h.finish_selection()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -310,7 +430,9 @@ func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) {
|
|||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
handler := Handler{lp: lp}
|
||||
handler := Handler{lp: lp, rl: readline.New(lp, readline.RlInit{
|
||||
Prompt: "> ", ContinuationPrompt: ". ",
|
||||
})}
|
||||
if err = handler.set_state_from_config(conf, opts); err != nil {
|
||||
return 1, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,14 @@ type=choices
|
|||
choices=file,files,save-file,dir,save-dir,dirs,dir-for-files
|
||||
default=file
|
||||
The type of object(s) to select
|
||||
|
||||
|
||||
--suggested-save-file-name
|
||||
A suggested name when picking a save file.
|
||||
|
||||
|
||||
--suggested-save-file-path
|
||||
Path to an existing file to use as the save file.
|
||||
'''.format(config_help=CONFIG_HELP.format(conf_name='diff', appname=appname)).format
|
||||
|
||||
|
||||
|
|
|
|||
72
kittens/choose_files/save-file.go
Normal file
72
kittens/choose_files/save-file.go
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
package choose_files
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/kovidgoyal/kitty/tools/tui/loop"
|
||||
"github.com/kovidgoyal/kitty/tools/utils"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
func (h *Handler) current_save_file_path() string {
|
||||
ans := h.rl.AllText()
|
||||
if ans != "" {
|
||||
ans = utils.Expanduser(ans)
|
||||
if !filepath.IsAbs(ans) {
|
||||
ans = filepath.Join(h.state.save_file_cdir, ans)
|
||||
}
|
||||
}
|
||||
return ans
|
||||
}
|
||||
|
||||
func (h *Handler) save_file_name_handle_key(ev *loop.KeyEvent) (err error) {
|
||||
switch {
|
||||
case ev.MatchesPressOrRepeat("esc") || ev.MatchesPressOrRepeat("ctrl+c"):
|
||||
h.state.selections = nil
|
||||
h.state.screen = NORMAL
|
||||
err = h.draw_screen()
|
||||
case ev.MatchesPressOrRepeat("enter"):
|
||||
if p := h.current_save_file_path(); p != "" {
|
||||
h.state.selections = []string{p}
|
||||
h.lp.Quit(0)
|
||||
} else {
|
||||
h.lp.Beep()
|
||||
}
|
||||
default:
|
||||
if err = h.rl.OnKeyEvent(ev); err == nil {
|
||||
err = h.draw_screen()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (h *Handler) initialize_save_file_name() {
|
||||
h.state.screen = SAVE_FILE
|
||||
h.rl.ResetText()
|
||||
cdir := h.state.CurrentDir()
|
||||
fname := h.state.suggested_save_file_name
|
||||
if len(h.state.selections) > 0 {
|
||||
if q, err := filepath.Abs(h.state.selections[0]); err == nil {
|
||||
if s, err := os.Stat(q); err == nil {
|
||||
if s.IsDir() && h.state.mode.OnlyDirs() {
|
||||
cdir = filepath.Dir(q)
|
||||
fname = filepath.Base(q)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
h.rl.SetText(fname)
|
||||
h.state.save_file_cdir = cdir
|
||||
}
|
||||
|
||||
func (h *Handler) draw_save_file_name_screen() (err error) {
|
||||
desc := utils.IfElse(h.state.mode == SELECT_SAVE_FILE, "file", "directory")
|
||||
h.lp.Println("Enter the name of the", desc, "below, relative to:")
|
||||
h.lp.Println(h.lp.SprintStyled("fg=green", h.state.save_file_cdir))
|
||||
h.lp.Println()
|
||||
h.rl.RedrawNonAtomic()
|
||||
return
|
||||
}
|
||||
|
|
@ -277,8 +277,12 @@ func (self *Readline) CursorAtEndOfLine() bool {
|
|||
return self.input_state.cursor.X >= len(self.input_state.lines[self.input_state.cursor.Y])
|
||||
}
|
||||
|
||||
func (self *Readline) OnResize(old_size loop.ScreenSize, new_size loop.ScreenSize) error {
|
||||
func (self *Readline) ClearCachedScreenSize() {
|
||||
self.screen_width, self.screen_height = 0, 0
|
||||
}
|
||||
|
||||
func (self *Readline) OnResize(old_size loop.ScreenSize, new_size loop.ScreenSize) error {
|
||||
self.ClearCachedScreenSize()
|
||||
self.Redraw()
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue