|
@@ -50,13 +50,28 @@ func ConfigurePicker(model *picker.Model, cfg *config.Config, title string) {
|
|
|
// mode. Return (cmd, true) if handled, (nil, false) to fall through.
|
|
// mode. Return (cmd, true) if handled, (nil, false) to fall through.
|
|
|
type browseKeyHandler func(event *tview.KeyEvent) (tview.Command, bool)
|
|
type browseKeyHandler func(event *tview.KeyEvent) (tview.Command, bool)
|
|
|
|
|
|
|
|
-// pickerBrowseHandleKey implements a two-phase ESC for overlay pickers.
|
|
|
|
|
-// First ESC enters browse mode (j/k navigate, i returns to input).
|
|
|
|
|
-// Second ESC calls closeFn to close the picker.
|
|
|
|
|
-// Returns (command, handled). If handled is false, the caller should
|
|
|
|
|
-// fall through to the normal picker event handling.
|
|
|
|
|
-// The optional extra handlers are checked before the default swallow-all,
|
|
|
|
|
-// allowing pickers to add custom browse-mode keys (e.g. favorite toggle).
|
|
|
|
|
|
|
+// pickerBrowseHandleKey implements a two-phase ESC interaction model for
|
|
|
|
|
+// overlay pickers (emoji, search, channels, attachments).
|
|
|
|
|
+//
|
|
|
|
|
+// When browseMode is false, keys pass through to the picker's input field for
|
|
|
|
|
+// filtering/searching. The first ESC press sets browseMode to true instead of
|
|
|
|
|
+// closing the picker.
|
|
|
|
|
+//
|
|
|
|
|
+// When browseMode is true, keys are intercepted before reaching the input field:
|
|
|
|
|
+//
|
|
|
|
|
+// j / k — select next / previous item
|
|
|
|
|
+// g / G — jump to top / bottom of list
|
|
|
|
|
+// i — return to input mode (browseMode = false)
|
|
|
|
|
+// Enter — confirm the selected item
|
|
|
|
|
+// ESC — close the picker via closeFn
|
|
|
|
|
+//
|
|
|
|
|
+// The optional extra handlers are checked before the default catch-all,
|
|
|
|
|
+// allowing pickers to add custom browse-mode keys (e.g. emoji picker's 'f'
|
|
|
|
|
+// for favorite toggle). All other keys are swallowed in browse mode to prevent
|
|
|
|
|
+// them from reaching the input field or triggering global keybinds.
|
|
|
|
|
+//
|
|
|
|
|
+// Returns (command, handled). If handled is false, the caller should fall
|
|
|
|
|
+// through to the normal picker event handling.
|
|
|
func pickerBrowseHandleKey(event *tview.KeyEvent, browseMode *bool, model *picker.Model, closeFn func(), extra ...browseKeyHandler) (tview.Command, bool) {
|
|
func pickerBrowseHandleKey(event *tview.KeyEvent, browseMode *bool, model *picker.Model, closeFn func(), extra ...browseKeyHandler) (tview.Command, bool) {
|
|
|
if !*browseMode {
|
|
if !*browseMode {
|
|
|
if event.Key() == tcell.KeyEsc {
|
|
if event.Key() == tcell.KeyEsc {
|
|
@@ -125,6 +140,7 @@ func atomicSaveJSON(path string, v any) {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
if err := os.Rename(tmpPath, path); err != nil {
|
|
if err := os.Rename(tmpPath, path); err != nil {
|
|
|
|
|
+ os.Remove(tmpPath)
|
|
|
slog.Error("failed to rename JSON file", "path", path, "err", err)
|
|
slog.Error("failed to rename JSON file", "path", path, "err", err)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|