Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for custom key bindings for #190 #209

Merged
merged 4 commits into from
Apr 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down Expand Up @@ -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>

Expand Down
41 changes: 41 additions & 0 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down Expand Up @@ -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)()
Expand Down
90 changes: 90 additions & 0 deletions client/cmd/configKeyBindings.go
Original file line number Diff line number Diff line change
@@ -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)
}
4 changes: 4 additions & 0 deletions client/hctx/hctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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"}
}
Expand Down
17 changes: 17 additions & 0 deletions client/testdata/TestTui-KeyBindings-Configured
Original file line number Diff line number Diff line change
@@ -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 changes: 17 additions & 0 deletions client/testdata/TestTui-KeyBindings-Default
Original file line number Diff line number Diff line change
@@ -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 changes: 31 additions & 0 deletions client/testdata/TestTui-KeyBindings-Help
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading