mirror of
https://github.com/ddworken/hishtory.git
synced 2025-02-02 11:39:24 +01:00
* Add support for custom key bindings for #190 * Add tests for configuring custom key bindings * Simplify key bindings test * Add docs on custom key bindings + error message for unhandled actions
This commit is contained in:
parent
d6a60214a2
commit
c933cbc792
@ -121,6 +121,13 @@ You can customize hishtory's color scheme for the TUI. Run `hishtory config-set
|
||||
|
||||
</blockquote></details>
|
||||
|
||||
<details>
|
||||
<summary>Custom Key Bindings</summary><blockquote>
|
||||
|
||||
You can customize hishtory's key bindings for the TUI. Run `hishtory config-get key-bindings` to see the current key bindings. You can then run `hishtory config-set key-bindings $action $keybinding` to configure custom key bindings.
|
||||
|
||||
</blockquote></details>
|
||||
|
||||
<details>
|
||||
<summary>Disabling Control+R integration</summary><blockquote>
|
||||
|
||||
@ -195,7 +202,6 @@ Note that this uses [HTTP Basic Auth](https://en.wikipedia.org/wiki/Basic_access
|
||||
|
||||
</blockquote></details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Customizing the install folder</summary><blockquote>
|
||||
|
||||
|
@ -114,6 +114,7 @@ func TestParam(t *testing.T) {
|
||||
t.Run("testTui/delete", wrapTestForSharding(testTui_delete))
|
||||
t.Run("testTui/color", wrapTestForSharding(testTui_color))
|
||||
t.Run("testTui/errors", wrapTestForSharding(testTui_errors))
|
||||
t.Run("testTui/keybindings", wrapTestForSharding(testTui_keybindings))
|
||||
t.Run("testTui/ai", wrapTestForSharding(testTui_ai))
|
||||
t.Run("testTui/defaultFilter", wrapTestForSharding(testTui_defaultFilter))
|
||||
|
||||
@ -2140,6 +2141,46 @@ func testTui_general(t *testing.T, onlineStatus OnlineStatus) {
|
||||
assertNoLeakedConnections(t)
|
||||
}
|
||||
|
||||
func testTui_keybindings(t *testing.T) {
|
||||
// Setup
|
||||
defer testutils.BackupAndRestore(t)()
|
||||
tester, _, _ := setupTestTui(t, Online)
|
||||
|
||||
// Check the default config
|
||||
testutils.CompareGoldens(t,
|
||||
tester.RunInteractiveShell(t, `hishtory config-get key-bindings`),
|
||||
"TestTui-KeyBindings-Default",
|
||||
)
|
||||
|
||||
// Configure some custom key bindings
|
||||
tester.RunInteractiveShell(t, `hishtory config-set key-bindings down '?'`)
|
||||
tester.RunInteractiveShell(t, `hishtory config-set key-bindings help ctrl+j`)
|
||||
|
||||
// Check that they got configured
|
||||
testutils.CompareGoldens(t,
|
||||
tester.RunInteractiveShell(t, `hishtory config-get key-bindings`),
|
||||
"TestTui-KeyBindings-Configured",
|
||||
)
|
||||
|
||||
// Record a command and demo searching for it
|
||||
tester.RunInteractiveShell(t, `echo 1`)
|
||||
tester.RunInteractiveShell(t, `echo 2`)
|
||||
out := captureTerminalOutput(t, tester, []string{
|
||||
"hishtory SPACE tquery ENTER",
|
||||
"C-j",
|
||||
})
|
||||
out = stripTuiCommandPrefix(t, out)
|
||||
testutils.CompareGoldens(t, out, "TestTui-KeyBindings-Help")
|
||||
|
||||
// Use the custom key binding for scrolling down
|
||||
out = captureTerminalOutput(t, tester, []string{
|
||||
"hishtory SPACE tquery ENTER",
|
||||
"'?' Enter",
|
||||
})
|
||||
out = stripTuiCommandPrefix(t, out)
|
||||
require.Regexp(t, regexp.MustCompile(`^ls ~/\n`), out)
|
||||
}
|
||||
|
||||
func testTui_errors(t *testing.T) {
|
||||
// Setup
|
||||
defer testutils.BackupAndRestore(t)()
|
||||
|
90
client/cmd/configKeyBindings.go
Normal file
90
client/cmd/configKeyBindings.go
Normal file
@ -0,0 +1,90 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ddworken/hishtory/client/hctx"
|
||||
"github.com/ddworken/hishtory/client/lib"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var getKeyBindingsCmd = &cobra.Command{
|
||||
Use: "key-bindings",
|
||||
Short: "Get the currently configured key bindings for the TUI",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := hctx.MakeContext()
|
||||
config := hctx.GetConf(ctx)
|
||||
fmt.Println("up: \t\t\t" + strings.Join(config.KeyBindings.Up, " "))
|
||||
fmt.Println("down: \t\t\t" + strings.Join(config.KeyBindings.Down, " "))
|
||||
fmt.Println("page-up: \t\t" + strings.Join(config.KeyBindings.PageUp, " "))
|
||||
fmt.Println("page-down: \t\t" + strings.Join(config.KeyBindings.PageDown, " "))
|
||||
fmt.Println("select-entry: \t\t" + strings.Join(config.KeyBindings.SelectEntry, " "))
|
||||
fmt.Println("select-entry-and-cd: \t" + strings.Join(config.KeyBindings.SelectEntryAndChangeDir, " "))
|
||||
fmt.Println("left: \t\t\t" + strings.Join(config.KeyBindings.Left, " "))
|
||||
fmt.Println("right: \t\t\t" + strings.Join(config.KeyBindings.Right, " "))
|
||||
fmt.Println("table-left: \t\t" + strings.Join(config.KeyBindings.TableLeft, " "))
|
||||
fmt.Println("table-right: \t\t" + strings.Join(config.KeyBindings.TableRight, " "))
|
||||
fmt.Println("delete-entry: \t\t" + strings.Join(config.KeyBindings.DeleteEntry, " "))
|
||||
fmt.Println("help: \t\t\t" + strings.Join(config.KeyBindings.Help, " "))
|
||||
fmt.Println("quit: \t\t\t" + strings.Join(config.KeyBindings.Quit, " "))
|
||||
fmt.Println("jump-start-of-input: \t" + strings.Join(config.KeyBindings.JumpStartOfInput, " "))
|
||||
fmt.Println("jump-end-of-input: \t" + strings.Join(config.KeyBindings.JumpEndOfInput, " "))
|
||||
fmt.Println("word-left: \t\t" + strings.Join(config.KeyBindings.WordLeft, " "))
|
||||
fmt.Println("word-right: \t\t" + strings.Join(config.KeyBindings.WordRight, " "))
|
||||
},
|
||||
}
|
||||
|
||||
var setKeyBindingsCmd = &cobra.Command{
|
||||
Use: "key-bindings",
|
||||
Short: "Set custom key bindings for the TUI",
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := hctx.MakeContext()
|
||||
config := hctx.GetConf(ctx)
|
||||
switch args[0] {
|
||||
case "up":
|
||||
config.KeyBindings.Up = args[1:]
|
||||
case "down":
|
||||
config.KeyBindings.Down = args[1:]
|
||||
case "page-up":
|
||||
config.KeyBindings.PageUp = args[1:]
|
||||
case "page-down":
|
||||
config.KeyBindings.PageDown = args[1:]
|
||||
case "select-entry":
|
||||
config.KeyBindings.SelectEntry = args[1:]
|
||||
case "select-entry-and-cd":
|
||||
config.KeyBindings.SelectEntryAndChangeDir = args[1:]
|
||||
case "left":
|
||||
config.KeyBindings.Left = args[1:]
|
||||
case "right":
|
||||
config.KeyBindings.Right = args[1:]
|
||||
case "table-left":
|
||||
config.KeyBindings.TableLeft = args[1:]
|
||||
case "table-right":
|
||||
config.KeyBindings.TableRight = args[1:]
|
||||
case "delete-entry":
|
||||
config.KeyBindings.DeleteEntry = args[1:]
|
||||
case "help":
|
||||
config.KeyBindings.Help = args[1:]
|
||||
case "quit":
|
||||
config.KeyBindings.Quit = args[1:]
|
||||
case "jump-start-of-input":
|
||||
config.KeyBindings.JumpStartOfInput = args[1:]
|
||||
case "jump-end-of-input":
|
||||
config.KeyBindings.JumpEndOfInput = args[1:]
|
||||
case "word-left":
|
||||
config.KeyBindings.WordLeft = args[1:]
|
||||
case "word-right":
|
||||
config.KeyBindings.WordRight = args[1:]
|
||||
default:
|
||||
lib.CheckFatalError(fmt.Errorf("unknown action %q, run `hishtory config-get keybindings` to see the list of currently configured key bindings", args[0]))
|
||||
}
|
||||
lib.CheckFatalError(hctx.SetConfig(config))
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
configGetCmd.AddCommand(getKeyBindingsCmd)
|
||||
configSetCmd.AddCommand(setKeyBindingsCmd)
|
||||
}
|
@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ddworken/hishtory/client/data"
|
||||
"github.com/ddworken/hishtory/client/tui/keybindings"
|
||||
"github.com/ddworken/hishtory/shared"
|
||||
"github.com/google/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -208,6 +209,8 @@ type ClientConfig struct {
|
||||
DefaultFilter string `json:"default_filter"`
|
||||
// The endpoint to use for AI suggestions
|
||||
AiCompletionEndpoint string `json:"ai_completion_endpoint"`
|
||||
// Custom key bindings for the TUI
|
||||
KeyBindings keybindings.SerializableKeyMap `json:"key_bindings"`
|
||||
}
|
||||
|
||||
type ColorScheme struct {
|
||||
@ -260,6 +263,7 @@ func GetConfig() (ClientConfig, error) {
|
||||
if err != nil {
|
||||
return ClientConfig{}, fmt.Errorf("failed to parse config file: %w", err)
|
||||
}
|
||||
config.KeyBindings = config.KeyBindings.WithDefaults()
|
||||
if config.DisplayedColumns == nil || len(config.DisplayedColumns) == 0 {
|
||||
config.DisplayedColumns = []string{"Hostname", "CWD", "Timestamp", "Runtime", "Exit Code", "Command"}
|
||||
}
|
||||
|
17
client/testdata/TestTui-KeyBindings-Configured
vendored
Normal file
17
client/testdata/TestTui-KeyBindings-Configured
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
up: up alt+OA ctrl+p
|
||||
down: ?
|
||||
page-up: pgup
|
||||
page-down: pgdown
|
||||
select-entry: enter
|
||||
select-entry-and-cd: ctrl+x
|
||||
left: left
|
||||
right: right
|
||||
table-left: shift+left
|
||||
table-right: shift+right
|
||||
delete-entry: ctrl+k
|
||||
help: ctrl+j
|
||||
quit: esc ctrl+c ctrl+d
|
||||
jump-start-of-input: ctrl+a
|
||||
jump-end-of-input: ctrl+e
|
||||
word-left: ctrl+left
|
||||
word-right: ctrl+right
|
17
client/testdata/TestTui-KeyBindings-Default
vendored
Normal file
17
client/testdata/TestTui-KeyBindings-Default
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
up: up alt+OA ctrl+p
|
||||
down: down alt+OB ctrl+n
|
||||
page-up: pgup
|
||||
page-down: pgdown
|
||||
select-entry: enter
|
||||
select-entry-and-cd: ctrl+x
|
||||
left: left
|
||||
right: right
|
||||
table-left: shift+left
|
||||
table-right: shift+right
|
||||
delete-entry: ctrl+k
|
||||
help: ctrl+h
|
||||
quit: esc ctrl+c ctrl+d
|
||||
jump-start-of-input: ctrl+a
|
||||
jump-end-of-input: ctrl+e
|
||||
word-left: ctrl+left
|
||||
word-right: ctrl+right
|
31
client/testdata/TestTui-KeyBindings-Help
vendored
Normal file
31
client/testdata/TestTui-KeyBindings-Help
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
Search Query: > ls
|
||||
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Hostname CWD Timestamp Runtime Exit Code Command │
|
||||
│────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||
│ localhost /tmp/ Oct 17 2022 21:43:21 PDT 3s 2 echo 'aaaaaa bbbb' │
|
||||
│ localhost /tmp/ Oct 17 2022 21:43:16 PDT 3s 2 ls ~/ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
hiSHtory: Search your shell history
|
||||
↑ scroll up ? scroll down pgup page up pgdn page down
|
||||
← move left → move right shift+← scroll the table left shift+→ scroll the table right
|
||||
enter select an entry ctrl+k delete the highlighted entry esc exit hiSHtory ctrl+j help
|
||||
ctrl+x select an entry and cd into that directory
|
326
client/tui/keybindings/keybindings.go
Normal file
326
client/tui/keybindings/keybindings.go
Normal file
@ -0,0 +1,326 @@
|
||||
package keybindings
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
)
|
||||
|
||||
type SerializableKeyMap struct {
|
||||
Up []string
|
||||
Down []string
|
||||
PageUp []string
|
||||
PageDown []string
|
||||
SelectEntry []string
|
||||
SelectEntryAndChangeDir []string
|
||||
Left []string
|
||||
Right []string
|
||||
TableLeft []string
|
||||
TableRight []string
|
||||
DeleteEntry []string
|
||||
Help []string
|
||||
Quit []string
|
||||
JumpStartOfInput []string
|
||||
JumpEndOfInput []string
|
||||
WordLeft []string
|
||||
WordRight []string
|
||||
}
|
||||
|
||||
func prettifyKeyBinding(kb string) string {
|
||||
if kb == "up" {
|
||||
return "↑ "
|
||||
}
|
||||
if kb == "down" {
|
||||
return "↓ "
|
||||
}
|
||||
if kb == "left" {
|
||||
return "←"
|
||||
}
|
||||
if kb == "right" {
|
||||
return "→"
|
||||
}
|
||||
subs := [][]string{
|
||||
{"+left", "+← "},
|
||||
{"+right", "+→ "},
|
||||
{"+down", "+↓ "},
|
||||
{"+up", "+↑ "},
|
||||
{"pgdown", "pgdn"},
|
||||
}
|
||||
for _, sub := range subs {
|
||||
kb = strings.ReplaceAll(kb, sub[0], sub[1])
|
||||
}
|
||||
return kb
|
||||
}
|
||||
|
||||
func (s SerializableKeyMap) ToKeyMap() KeyMap {
|
||||
if len(s.Up) == 0 {
|
||||
panic(fmt.Sprintf("%#v", s))
|
||||
}
|
||||
return KeyMap{
|
||||
Up: key.NewBinding(
|
||||
key.WithKeys(s.Up...),
|
||||
key.WithHelp(prettifyKeyBinding(s.Up[0]), "scroll up "),
|
||||
),
|
||||
Down: key.NewBinding(
|
||||
key.WithKeys(s.Down...),
|
||||
key.WithHelp(prettifyKeyBinding(s.Down[0]), "scroll down "),
|
||||
),
|
||||
PageUp: key.NewBinding(
|
||||
key.WithKeys(s.PageUp...),
|
||||
key.WithHelp(prettifyKeyBinding(s.PageUp[0]), "page up "),
|
||||
),
|
||||
PageDown: key.NewBinding(
|
||||
key.WithKeys(s.PageDown...),
|
||||
key.WithHelp(prettifyKeyBinding(s.PageDown[0]), "page down "),
|
||||
),
|
||||
SelectEntry: key.NewBinding(
|
||||
key.WithKeys(s.SelectEntry...),
|
||||
key.WithHelp(prettifyKeyBinding(s.SelectEntry[0]), "select an entry "),
|
||||
),
|
||||
SelectEntryAndChangeDir: key.NewBinding(
|
||||
key.WithKeys(s.SelectEntryAndChangeDir...),
|
||||
key.WithHelp(prettifyKeyBinding(s.SelectEntryAndChangeDir[0]), "select an entry and cd into that directory"),
|
||||
),
|
||||
Left: key.NewBinding(
|
||||
key.WithKeys(s.Left...),
|
||||
key.WithHelp(prettifyKeyBinding(s.Left[0]), "move left "),
|
||||
),
|
||||
Right: key.NewBinding(
|
||||
key.WithKeys(s.Right...),
|
||||
key.WithHelp(prettifyKeyBinding(s.Right[0]), "move right "),
|
||||
),
|
||||
TableLeft: key.NewBinding(
|
||||
key.WithKeys(s.TableLeft...),
|
||||
key.WithHelp(prettifyKeyBinding(s.TableLeft[0]), "scroll the table left "),
|
||||
),
|
||||
TableRight: key.NewBinding(
|
||||
key.WithKeys(s.TableRight...),
|
||||
key.WithHelp(prettifyKeyBinding(s.TableRight[0]), "scroll the table right "),
|
||||
),
|
||||
DeleteEntry: key.NewBinding(
|
||||
key.WithKeys(s.DeleteEntry...),
|
||||
key.WithHelp(prettifyKeyBinding(s.DeleteEntry[0]), "delete the highlighted entry "),
|
||||
),
|
||||
Help: key.NewBinding(
|
||||
key.WithKeys(s.Help...),
|
||||
key.WithHelp(prettifyKeyBinding(s.Help[0]), "help "),
|
||||
),
|
||||
Quit: key.NewBinding(
|
||||
key.WithKeys(s.Quit...),
|
||||
key.WithHelp(prettifyKeyBinding(s.Quit[0]), "exit hiSHtory "),
|
||||
),
|
||||
JumpStartOfInput: key.NewBinding(
|
||||
key.WithKeys(s.JumpStartOfInput...),
|
||||
key.WithHelp(prettifyKeyBinding(s.JumpStartOfInput[0]), "jump to the start of the input "),
|
||||
),
|
||||
JumpEndOfInput: key.NewBinding(
|
||||
key.WithKeys(s.JumpEndOfInput...),
|
||||
key.WithHelp(prettifyKeyBinding(s.JumpEndOfInput[0]), "jump to the end of the input "),
|
||||
),
|
||||
WordLeft: key.NewBinding(
|
||||
key.WithKeys(s.WordLeft...),
|
||||
key.WithHelp(prettifyKeyBinding(s.WordLeft[0]), "jump left one word "),
|
||||
),
|
||||
WordRight: key.NewBinding(
|
||||
key.WithKeys(s.WordRight...),
|
||||
key.WithHelp(prettifyKeyBinding(s.WordRight[0]), "jump right one word "),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func (s SerializableKeyMap) WithDefaults() SerializableKeyMap {
|
||||
if len(s.Up) == 0 {
|
||||
s.Up = DefaultKeyMap.Up.Keys()
|
||||
}
|
||||
if len(s.Down) == 0 {
|
||||
s.Down = DefaultKeyMap.Down.Keys()
|
||||
}
|
||||
if len(s.PageUp) == 0 {
|
||||
s.PageUp = DefaultKeyMap.PageUp.Keys()
|
||||
}
|
||||
if len(s.PageDown) == 0 {
|
||||
s.PageDown = DefaultKeyMap.PageDown.Keys()
|
||||
}
|
||||
if len(s.SelectEntry) == 0 {
|
||||
s.SelectEntry = DefaultKeyMap.SelectEntry.Keys()
|
||||
}
|
||||
if len(s.SelectEntryAndChangeDir) == 0 {
|
||||
s.SelectEntryAndChangeDir = DefaultKeyMap.SelectEntryAndChangeDir.Keys()
|
||||
}
|
||||
if len(s.Left) == 0 {
|
||||
s.Left = DefaultKeyMap.Left.Keys()
|
||||
}
|
||||
if len(s.Right) == 0 {
|
||||
s.Right = DefaultKeyMap.Right.Keys()
|
||||
}
|
||||
if len(s.TableLeft) == 0 {
|
||||
s.TableLeft = DefaultKeyMap.TableLeft.Keys()
|
||||
}
|
||||
if len(s.TableRight) == 0 {
|
||||
s.TableRight = DefaultKeyMap.TableRight.Keys()
|
||||
}
|
||||
if len(s.DeleteEntry) == 0 {
|
||||
s.DeleteEntry = DefaultKeyMap.DeleteEntry.Keys()
|
||||
}
|
||||
if len(s.Help) == 0 {
|
||||
s.Help = DefaultKeyMap.Help.Keys()
|
||||
}
|
||||
if len(s.Quit) == 0 {
|
||||
s.Quit = DefaultKeyMap.Quit.Keys()
|
||||
}
|
||||
if len(s.JumpStartOfInput) == 0 {
|
||||
s.JumpStartOfInput = DefaultKeyMap.JumpStartOfInput.Keys()
|
||||
}
|
||||
if len(s.JumpEndOfInput) == 0 {
|
||||
s.JumpEndOfInput = DefaultKeyMap.JumpEndOfInput.Keys()
|
||||
}
|
||||
if len(s.WordLeft) == 0 {
|
||||
s.WordLeft = DefaultKeyMap.WordLeft.Keys()
|
||||
}
|
||||
if len(s.WordRight) == 0 {
|
||||
s.WordRight = DefaultKeyMap.WordRight.Keys()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
type KeyMap struct {
|
||||
Up key.Binding
|
||||
Down key.Binding
|
||||
PageUp key.Binding
|
||||
PageDown key.Binding
|
||||
SelectEntry key.Binding
|
||||
SelectEntryAndChangeDir key.Binding
|
||||
Left key.Binding
|
||||
Right key.Binding
|
||||
TableLeft key.Binding
|
||||
TableRight key.Binding
|
||||
DeleteEntry key.Binding
|
||||
Help key.Binding
|
||||
Quit key.Binding
|
||||
JumpStartOfInput key.Binding
|
||||
JumpEndOfInput key.Binding
|
||||
WordLeft key.Binding
|
||||
WordRight key.Binding
|
||||
}
|
||||
|
||||
func (k KeyMap) ToSerializable() SerializableKeyMap {
|
||||
return SerializableKeyMap{
|
||||
Up: k.Up.Keys(),
|
||||
Down: k.Down.Keys(),
|
||||
PageUp: k.PageUp.Keys(),
|
||||
PageDown: k.PageDown.Keys(),
|
||||
SelectEntry: k.SelectEntry.Keys(),
|
||||
SelectEntryAndChangeDir: k.SelectEntryAndChangeDir.Keys(),
|
||||
Left: k.Left.Keys(),
|
||||
Right: k.Right.Keys(),
|
||||
TableLeft: k.TableLeft.Keys(),
|
||||
TableRight: k.TableRight.Keys(),
|
||||
DeleteEntry: k.DeleteEntry.Keys(),
|
||||
Help: k.Help.Keys(),
|
||||
Quit: k.Quit.Keys(),
|
||||
JumpStartOfInput: k.JumpStartOfInput.Keys(),
|
||||
JumpEndOfInput: k.JumpEndOfInput.Keys(),
|
||||
WordLeft: k.WordLeft.Keys(),
|
||||
WordRight: k.WordRight.Keys(),
|
||||
}
|
||||
}
|
||||
|
||||
var fakeTitleKeyBinding key.Binding = key.NewBinding(
|
||||
key.WithKeys(""),
|
||||
key.WithHelp("hiSHtory: Search your shell history", ""),
|
||||
)
|
||||
|
||||
var fakeEmptyKeyBinding key.Binding = key.NewBinding(
|
||||
key.WithKeys(""),
|
||||
key.WithHelp("", ""),
|
||||
)
|
||||
|
||||
func (k KeyMap) ShortHelp() []key.Binding {
|
||||
return []key.Binding{fakeTitleKeyBinding, k.Help}
|
||||
}
|
||||
|
||||
func (k KeyMap) FullHelp() [][]key.Binding {
|
||||
return [][]key.Binding{
|
||||
{fakeTitleKeyBinding, k.Up, k.Left, k.SelectEntry, k.SelectEntryAndChangeDir},
|
||||
{fakeEmptyKeyBinding, k.Down, k.Right, k.DeleteEntry},
|
||||
{fakeEmptyKeyBinding, k.PageUp, k.TableLeft, k.Quit},
|
||||
{fakeEmptyKeyBinding, k.PageDown, k.TableRight, k.Help},
|
||||
}
|
||||
}
|
||||
|
||||
type Binding struct {
|
||||
Keys []string `json:"keys"`
|
||||
Help key.Help `json:"help"`
|
||||
}
|
||||
|
||||
var DefaultKeyMap = KeyMap{
|
||||
Up: key.NewBinding(
|
||||
key.WithKeys("up", "alt+OA", "ctrl+p"),
|
||||
key.WithHelp("↑ ", "scroll up "),
|
||||
),
|
||||
Down: key.NewBinding(
|
||||
key.WithKeys("down", "alt+OB", "ctrl+n"),
|
||||
key.WithHelp("↓ ", "scroll down "),
|
||||
),
|
||||
PageUp: key.NewBinding(
|
||||
key.WithKeys("pgup"),
|
||||
key.WithHelp("pgup", "page up "),
|
||||
),
|
||||
PageDown: key.NewBinding(
|
||||
key.WithKeys("pgdown"),
|
||||
key.WithHelp("pgdn", "page down "),
|
||||
),
|
||||
SelectEntry: key.NewBinding(
|
||||
key.WithKeys("enter"),
|
||||
key.WithHelp("enter", "select an entry "),
|
||||
),
|
||||
SelectEntryAndChangeDir: key.NewBinding(
|
||||
key.WithKeys("ctrl+x"),
|
||||
key.WithHelp("ctrl+x", "select an entry and cd into that directory"),
|
||||
),
|
||||
Left: key.NewBinding(
|
||||
key.WithKeys("left"),
|
||||
key.WithHelp("← ", "move left "),
|
||||
),
|
||||
Right: key.NewBinding(
|
||||
key.WithKeys("right"),
|
||||
key.WithHelp("→ ", "move right "),
|
||||
),
|
||||
TableLeft: key.NewBinding(
|
||||
key.WithKeys("shift+left"),
|
||||
key.WithHelp("shift+← ", "scroll the table left "),
|
||||
),
|
||||
TableRight: key.NewBinding(
|
||||
key.WithKeys("shift+right"),
|
||||
key.WithHelp("shift+→ ", "scroll the table right "),
|
||||
),
|
||||
DeleteEntry: key.NewBinding(
|
||||
key.WithKeys("ctrl+k"),
|
||||
key.WithHelp("ctrl+k", "delete the highlighted entry "),
|
||||
),
|
||||
Help: key.NewBinding(
|
||||
key.WithKeys("ctrl+h"),
|
||||
key.WithHelp("ctrl+h", "help "),
|
||||
),
|
||||
Quit: key.NewBinding(
|
||||
key.WithKeys("esc", "ctrl+c", "ctrl+d"),
|
||||
key.WithHelp("esc", "exit hiSHtory "),
|
||||
),
|
||||
JumpStartOfInput: key.NewBinding(
|
||||
key.WithKeys("ctrl+a"),
|
||||
key.WithHelp("ctrl+a", "jump to the start of the input "),
|
||||
),
|
||||
JumpEndOfInput: key.NewBinding(
|
||||
key.WithKeys("ctrl+e"),
|
||||
key.WithHelp("ctrl+e", "jump to the end of the input "),
|
||||
),
|
||||
WordLeft: key.NewBinding(
|
||||
key.WithKeys("ctrl+left"),
|
||||
key.WithHelp("ctrl+left", "jump left one word "),
|
||||
),
|
||||
WordRight: key.NewBinding(
|
||||
key.WithKeys("ctrl+right"),
|
||||
key.WithHelp("ctrl+right", "jump right one word "),
|
||||
),
|
||||
}
|
@ -23,6 +23,7 @@ import (
|
||||
"github.com/ddworken/hishtory/client/hctx"
|
||||
"github.com/ddworken/hishtory/client/lib"
|
||||
"github.com/ddworken/hishtory/client/table"
|
||||
"github.com/ddworken/hishtory/client/tui/keybindings"
|
||||
"github.com/ddworken/hishtory/shared"
|
||||
"github.com/muesli/termenv"
|
||||
"golang.org/x/term"
|
||||
@ -42,120 +43,6 @@ var LAST_DISPATCHED_QUERY_ID = 0
|
||||
var LAST_DISPATCHED_QUERY_TIMESTAMP time.Time
|
||||
var LAST_PROCESSED_QUERY_ID = -1
|
||||
|
||||
type keyMap struct {
|
||||
Up key.Binding
|
||||
Down key.Binding
|
||||
PageUp key.Binding
|
||||
PageDown key.Binding
|
||||
SelectEntry key.Binding
|
||||
SelectEntryAndChangeDir key.Binding
|
||||
Left key.Binding
|
||||
Right key.Binding
|
||||
TableLeft key.Binding
|
||||
TableRight key.Binding
|
||||
DeleteEntry key.Binding
|
||||
Help key.Binding
|
||||
Quit key.Binding
|
||||
JumpStartOfInput key.Binding
|
||||
JumpEndOfInput key.Binding
|
||||
JumpWordLeft key.Binding
|
||||
JumpWordRight key.Binding
|
||||
}
|
||||
|
||||
var fakeTitleKeyBinding key.Binding = key.NewBinding(
|
||||
key.WithKeys(""),
|
||||
key.WithHelp("hiSHtory: Search your shell history", ""),
|
||||
)
|
||||
|
||||
var fakeEmptyKeyBinding key.Binding = key.NewBinding(
|
||||
key.WithKeys(""),
|
||||
key.WithHelp("", ""),
|
||||
)
|
||||
|
||||
func (k keyMap) ShortHelp() []key.Binding {
|
||||
return []key.Binding{fakeTitleKeyBinding, k.Help}
|
||||
}
|
||||
|
||||
func (k keyMap) FullHelp() [][]key.Binding {
|
||||
return [][]key.Binding{
|
||||
{fakeTitleKeyBinding, k.Up, k.Left, k.SelectEntry, k.SelectEntryAndChangeDir},
|
||||
{fakeEmptyKeyBinding, k.Down, k.Right, k.DeleteEntry},
|
||||
{fakeEmptyKeyBinding, k.PageUp, k.TableLeft, k.Quit},
|
||||
{fakeEmptyKeyBinding, k.PageDown, k.TableRight, k.Help},
|
||||
}
|
||||
}
|
||||
|
||||
var keys = keyMap{
|
||||
Up: key.NewBinding(
|
||||
key.WithKeys("up", "alt+OA", "ctrl+p"),
|
||||
key.WithHelp("↑ ", "scroll up "),
|
||||
),
|
||||
Down: key.NewBinding(
|
||||
key.WithKeys("down", "alt+OB", "ctrl+n"),
|
||||
key.WithHelp("↓ ", "scroll down "),
|
||||
),
|
||||
PageUp: key.NewBinding(
|
||||
key.WithKeys("pgup"),
|
||||
key.WithHelp("pgup", "page up "),
|
||||
),
|
||||
PageDown: key.NewBinding(
|
||||
key.WithKeys("pgdown"),
|
||||
key.WithHelp("pgdn", "page down "),
|
||||
),
|
||||
SelectEntry: key.NewBinding(
|
||||
key.WithKeys("enter"),
|
||||
key.WithHelp("enter", "select an entry "),
|
||||
),
|
||||
SelectEntryAndChangeDir: key.NewBinding(
|
||||
key.WithKeys("ctrl+x"),
|
||||
key.WithHelp("ctrl+x", "select an entry and cd into that directory"),
|
||||
),
|
||||
Left: key.NewBinding(
|
||||
key.WithKeys("left"),
|
||||
key.WithHelp("← ", "move left "),
|
||||
),
|
||||
Right: key.NewBinding(
|
||||
key.WithKeys("right"),
|
||||
key.WithHelp("→ ", "move right "),
|
||||
),
|
||||
TableLeft: key.NewBinding(
|
||||
key.WithKeys("shift+left"),
|
||||
key.WithHelp("shift+← ", "scroll the table left "),
|
||||
),
|
||||
TableRight: key.NewBinding(
|
||||
key.WithKeys("shift+right"),
|
||||
key.WithHelp("shift+→ ", "scroll the table right "),
|
||||
),
|
||||
DeleteEntry: key.NewBinding(
|
||||
key.WithKeys("ctrl+k"),
|
||||
key.WithHelp("ctrl+k", "delete the highlighted entry "),
|
||||
),
|
||||
Help: key.NewBinding(
|
||||
key.WithKeys("ctrl+h"),
|
||||
key.WithHelp("ctrl+h", "help "),
|
||||
),
|
||||
Quit: key.NewBinding(
|
||||
key.WithKeys("esc", "ctrl+c", "ctrl+d"),
|
||||
key.WithHelp("esc", "exit hiSHtory "),
|
||||
),
|
||||
JumpStartOfInput: key.NewBinding(
|
||||
key.WithKeys("ctrl+a"),
|
||||
key.WithHelp("ctrl+a", "jump to the start of the input "),
|
||||
),
|
||||
JumpEndOfInput: key.NewBinding(
|
||||
key.WithKeys("ctrl+e"),
|
||||
key.WithHelp("ctrl+e", "jump to the end of the input "),
|
||||
),
|
||||
JumpWordLeft: key.NewBinding(
|
||||
key.WithKeys("ctrl+left"),
|
||||
key.WithHelp("ctrl+left", "jump left one word "),
|
||||
),
|
||||
JumpWordRight: key.NewBinding(
|
||||
key.WithKeys("ctrl+right"),
|
||||
key.WithHelp("ctrl+right", "jump right one word "),
|
||||
),
|
||||
}
|
||||
|
||||
type SelectStatus int64
|
||||
|
||||
const (
|
||||
@ -164,6 +51,8 @@ const (
|
||||
SelectedWithChangeDir
|
||||
)
|
||||
|
||||
var loadedKeyBindings keybindings.KeyMap = keybindings.DefaultKeyMap
|
||||
|
||||
type model struct {
|
||||
// context
|
||||
ctx context.Context
|
||||
@ -330,20 +219,20 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch {
|
||||
case key.Matches(msg, keys.Quit):
|
||||
case key.Matches(msg, loadedKeyBindings.Quit):
|
||||
m.quitting = true
|
||||
return m, tea.Quit
|
||||
case key.Matches(msg, keys.SelectEntry):
|
||||
case key.Matches(msg, loadedKeyBindings.SelectEntry):
|
||||
if len(m.tableEntries) != 0 && m.table != nil {
|
||||
m.selected = Selected
|
||||
}
|
||||
return m, tea.Quit
|
||||
case key.Matches(msg, keys.SelectEntryAndChangeDir):
|
||||
case key.Matches(msg, loadedKeyBindings.SelectEntryAndChangeDir):
|
||||
if len(m.tableEntries) != 0 && m.table != nil {
|
||||
m.selected = SelectedWithChangeDir
|
||||
}
|
||||
return m, tea.Quit
|
||||
case key.Matches(msg, keys.DeleteEntry):
|
||||
case key.Matches(msg, loadedKeyBindings.DeleteEntry):
|
||||
if m.table == nil {
|
||||
return m, nil
|
||||
}
|
||||
@ -355,16 +244,16 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
cmd := runQueryAndUpdateTable(m, true, true)
|
||||
preventTableOverscrolling(m)
|
||||
return m, cmd
|
||||
case key.Matches(msg, keys.Help):
|
||||
case key.Matches(msg, loadedKeyBindings.Help):
|
||||
m.help.ShowAll = !m.help.ShowAll
|
||||
return m, nil
|
||||
case key.Matches(msg, keys.JumpStartOfInput):
|
||||
case key.Matches(msg, loadedKeyBindings.JumpStartOfInput):
|
||||
m.queryInput.SetCursor(0)
|
||||
return m, nil
|
||||
case key.Matches(msg, keys.JumpEndOfInput):
|
||||
case key.Matches(msg, loadedKeyBindings.JumpEndOfInput):
|
||||
m.queryInput.SetCursor(len(m.queryInput.Value()))
|
||||
return m, nil
|
||||
case key.Matches(msg, keys.JumpWordLeft):
|
||||
case key.Matches(msg, loadedKeyBindings.WordLeft):
|
||||
wordBoundaries := calculateWordBoundaries(m.queryInput.Value())
|
||||
lastBoundary := 0
|
||||
for _, boundary := range wordBoundaries {
|
||||
@ -375,7 +264,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
lastBoundary = boundary
|
||||
}
|
||||
return m, nil
|
||||
case key.Matches(msg, keys.JumpWordRight):
|
||||
case key.Matches(msg, loadedKeyBindings.WordRight):
|
||||
wordBoundaries := calculateWordBoundaries(m.queryInput.Value())
|
||||
for _, boundary := range wordBoundaries {
|
||||
if boundary > m.queryInput.Position() {
|
||||
@ -509,7 +398,7 @@ func (m model) View() string {
|
||||
if isExtraCompactHeightMode() {
|
||||
additionalMessagesStr = "\n"
|
||||
}
|
||||
helpView := m.help.View(keys)
|
||||
helpView := m.help.View(loadedKeyBindings)
|
||||
if isExtraCompactHeightMode() {
|
||||
helpView = ""
|
||||
}
|
||||
@ -751,10 +640,10 @@ func makeTable(ctx context.Context, shellName string, rows []table.Row) (table.M
|
||||
return table.Model{}, err
|
||||
}
|
||||
km := table.KeyMap{
|
||||
LineUp: keys.Up,
|
||||
LineDown: keys.Down,
|
||||
PageUp: keys.PageUp,
|
||||
PageDown: keys.PageDown,
|
||||
LineUp: loadedKeyBindings.Up,
|
||||
LineDown: loadedKeyBindings.Down,
|
||||
PageUp: loadedKeyBindings.PageUp,
|
||||
PageDown: loadedKeyBindings.PageDown,
|
||||
GotoTop: key.NewBinding(
|
||||
key.WithKeys("home"),
|
||||
key.WithHelp("home", "go to start"),
|
||||
@ -763,8 +652,8 @@ func makeTable(ctx context.Context, shellName string, rows []table.Row) (table.M
|
||||
key.WithKeys("end"),
|
||||
key.WithHelp("end", "go to end"),
|
||||
),
|
||||
MoveLeft: keys.TableLeft,
|
||||
MoveRight: keys.TableRight,
|
||||
MoveLeft: loadedKeyBindings.TableLeft,
|
||||
MoveRight: loadedKeyBindings.TableRight,
|
||||
}
|
||||
_, terminalHeight, err := getTerminalSize()
|
||||
if err != nil {
|
||||
@ -954,6 +843,7 @@ func configureColorProfile(ctx context.Context) {
|
||||
}
|
||||
|
||||
func TuiQuery(ctx context.Context, shellName, initialQuery string) error {
|
||||
loadedKeyBindings = hctx.GetConf(ctx).KeyBindings.ToKeyMap()
|
||||
configureColorProfile(ctx)
|
||||
p := tea.NewProgram(initialModel(ctx, shellName, initialQuery), tea.WithOutput(os.Stderr))
|
||||
// Async: Get the initial set of rows
|
||||
|
Loading…
Reference in New Issue
Block a user