Make the keyboard shortcuts configurable

This commit is contained in:
Kovid Goyal
2025-07-10 16:20:45 +05:30
parent 27736af979
commit 3469e1b044
2 changed files with 126 additions and 63 deletions

View File

@@ -118,6 +118,7 @@ type State struct {
respect_ignores bool
sort_by_last_modified bool
global_ignores ignorefiles.IgnoreFile
keyboard_shortcuts []*config.KeyAction
save_file_cdir string
selections []string
@@ -186,12 +187,13 @@ type ScreenSize struct {
}
type Handler struct {
state State
screen_size ScreenSize
result_manager *ResultManager
lp *loop.Loop
rl *readline.Readline
err_chan chan error
state State
screen_size ScreenSize
result_manager *ResultManager
lp *loop.Loop
rl *readline.Readline
err_chan chan error
shortcut_tracker config.ShortcutTracker
}
func (h *Handler) draw_screen() (err error) {
@@ -231,7 +233,7 @@ func load_config(opts *Options) (ans *Config, err error) {
if err != nil {
return nil, err
}
// ans.KeyboardShortcuts = config.ResolveShortcuts(ans.KeyboardShortcuts)
ans.KeyboardShortcuts = config.ResolveShortcuts(ans.KeyboardShortcuts)
return ans, nil
}
@@ -343,27 +345,72 @@ func (h *Handler) change_filter(delta int) bool {
return true
}
func (h *Handler) OnKeyEvent(ev *loop.KeyEvent) (err error) {
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"):
func (h *Handler) dispatch_action(name, args string) (err error) {
switch name {
case "quit":
h.lp.Quit(1)
case "next":
if n, nerr := strconv.Atoi(args); nerr == nil {
h.next_result(n)
} else {
switch args {
case "":
h.next_result(1)
case "left":
h.move_sideways(true)
case "right":
h.move_sideways(false)
}
}
return h.draw_screen()
case "next_filter":
if n, nerr := strconv.Atoi(args); nerr == nil {
h.change_filter(n)
return h.draw_screen()
}
h.lp.Beep()
case "select":
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 "accept":
m := h.current_abspath()
if h.state.mode.SelectFiles() {
if m != "" {
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 {
if h.state.mode.CanSelectNonExistent() {
t := h.state.SearchText()
h.initialize_save_file_name(utils.IfElse(t == "", h.state.suggested_save_file_name, t))
return h.draw_screen()
} else {
h.lp.Beep()
}
}
case "cd":
switch args {
case "current":
return h.change_to_current_dir_if_possible()
case ev.MatchesPressOrRepeat("ctrl+f"):
if h.change_filter(1) {
return h.draw_screen()
}
h.lp.Beep()
case ev.MatchesPressOrRepeat("alt+f"):
if h.change_filter(-1) {
return h.draw_screen()
}
h.lp.Beep()
case ev.MatchesPressOrRepeat("shift+tab"):
case "up":
curr := h.state.CurrentDir()
switch curr {
case "/":
@@ -377,43 +424,34 @@ func (h *Handler) OnKeyEvent(ev *loop.KeyEvent) (err error) {
return h.draw_screen()
}
h.lp.Beep()
case ev.MatchesPressOrRepeat("shift+enter"):
if !h.toggle_selection() {
default:
args = utils.Expanduser(args)
if st, serr := os.Stat(args); serr != nil || !st.IsDir() {
h.lp.Beep()
} else {
if len(h.state.selections) > 0 && !h.state.mode.AllowsMultipleSelection() {
return h.finish_selection()
}
return
}
if absp, err := filepath.Abs(args); err == nil {
h.change_current_dir(absp)
return h.draw_screen()
}
case ev.MatchesPressOrRepeat("enter"):
m := h.current_abspath()
if h.state.mode.SelectFiles() {
if m != "" {
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 {
if h.state.mode.CanSelectNonExistent() {
t := h.state.SearchText()
h.initialize_save_file_name(utils.IfElse(t == "", h.state.suggested_save_file_name, t))
return h.draw_screen()
} else {
h.lp.Beep()
}
}
}
}
return
}
func (h *Handler) OnKeyEvent(ev *loop.KeyEvent) (err error) {
switch h.state.screen {
case NORMAL:
if h.handle_edit_keys(ev) {
ev.Handled = true
h.draw_screen()
}
ac := h.shortcut_tracker.Match(ev, h.state.keyboard_shortcuts)
if ac != nil {
ev.Handled = true
return h.dispatch_action(ac.Name, ac.Args)
}
case SAVE_FILE:
err = h.save_file_name_handle_key(ev)
@@ -570,6 +608,7 @@ func (h *Handler) set_state_from_config(conf *Config, opts *Options) (err error)
if err = h.state.global_ignores.LoadLines(conf.Ignore...); err != nil {
return err
}
h.state.keyboard_shortcuts = conf.KeyboardShortcuts
return
}

View File

@@ -17,7 +17,7 @@ opt = definition.add_option
map = definition.add_map
mma = definition.add_mouse_map
agr('Filesystem scanning')
agr('scanning', 'Filesystem scanning') # {{{
opt('show_hidden', 'last', choices=('last', 'yes', 'y', 'true', 'no', 'n', 'false'), long_text='''
Whether to show hidden files. The default value of :code:`last` means remember the last
@@ -40,8 +40,32 @@ 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
has to be checked against every file, so use sparingly.
''')
egr() # }}}
egr()
agr('shortcuts', 'Keyboard shortcuts') # {{{
map('Quit', 'quit esc quit')
map('Quit', 'quit ctrl+c quit')
map('Accept current result', 'accept enter accept')
map('Select current result', 'select shift+enter select')
map('Next result', 'next_result down next 1')
map('Previous result', 'prev_result up next -1')
map('Left result', 'left_result left next left')
map('Right result', 'right_result right next right')
map('Change to currently selected dir', 'cd_current tab cd current')
map('Change to parent directory', 'cd_parent shift+tab cd up')
map('Change to root directory', 'cd_root ctrl+/ cd /')
map('Change to home directory', 'cd_home ctrl+~ cd ~')
map('Change to home directory', 'cd_home ctrl+` cd ~')
map('Change to home directory', 'cd_home ctrl+shift+` cd ~')
map('Change to temp directory', 'cd_tmp ctrl+t cd /tmp')
map('Next filter', 'next_filter ctrl+f 1')
map('Previous filter', 'prev_filter alt+f -1')
egr() # }}}
def main(args: list[str]) -> None:
raise SystemExit('This must be run as kitten choose-files')