mirror of
https://github.com/ddworken/hishtory.git
synced 2024-12-26 16:58:55 +01:00
Add support for full-screen rendering (#258)
This commit is contained in:
parent
5996b832a3
commit
389515ae28
13
README.md
13
README.md
@ -199,6 +199,19 @@ You can configure a custom timestamp format for hiSHtory via `hishtory config-se
|
||||
|
||||
</blockquote></details>
|
||||
|
||||
<details>
|
||||
<summary>Custom rendering</summary><blockquote>
|
||||
|
||||
By default, hiHStory tries to render the TUI in a reasonable way that balances terminal space consumption and TUI usability. If you find that you wish to customize this behavior, there are two config options that you can experiment with enabling:
|
||||
|
||||
```
|
||||
hishtory config-set compact-mode true # Renders the TUI in "compact mode" with less whitespace
|
||||
hishtory config-set full-screen true # Renders the TUI in "full-screen mode" so that it uses the entire terminal
|
||||
```
|
||||
|
||||
</blockquote></details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Web UI for sharing</summary><blockquote>
|
||||
|
||||
|
@ -119,6 +119,7 @@ func TestParam(t *testing.T) {
|
||||
t.Run("testTui/ai", wrapTestForSharding(testTui_ai))
|
||||
t.Run("testTui/defaultFilter", wrapTestForSharding(testTui_defaultFilter))
|
||||
t.Run("testTui/escaping", wrapTestForSharding(testTui_escaping))
|
||||
t.Run("testTui/fullscreen", wrapTestForSharding(testTui_fullscreen))
|
||||
|
||||
// Assert there are no leaked connections
|
||||
assertNoLeakedConnections(t)
|
||||
@ -1871,6 +1872,54 @@ func testTui_escaping(t *testing.T) {
|
||||
testutils.CompareGoldens(t, out, "TestTui-Escaping")
|
||||
}
|
||||
|
||||
func testTui_fullscreen(t *testing.T) {
|
||||
// Setup
|
||||
defer testutils.BackupAndRestore(t)()
|
||||
tester, _, _ := setupTestTui(t, Online)
|
||||
|
||||
// By default full-screen mode is disabled
|
||||
require.Equal(t, "false", strings.TrimSpace(tester.RunInteractiveShell(t, `hishtory config-get full-screen`)))
|
||||
require.Equal(t, "false", strings.TrimSpace(tester.RunInteractiveShell(t, `hishtory config-get compact-mode`)))
|
||||
|
||||
// Test that we can enable it
|
||||
tester.RunInteractiveShell(t, `hishtory config-set full-screen true`)
|
||||
require.Equal(t, "true", strings.TrimSpace(tester.RunInteractiveShell(t, `hishtory config-get full-screen`)))
|
||||
|
||||
// Test that it renders in full-screen mode taking up the entire terminal
|
||||
out := captureTerminalOutput(t, tester, []string{
|
||||
"echo foo ENTER",
|
||||
"hishtory SPACE tquery ENTER",
|
||||
})
|
||||
testutils.CompareGoldens(t, out, "TestTui-FullScreenRender")
|
||||
|
||||
// Test that it clears full-screen mode and restores the original terminal state
|
||||
out = captureTerminalOutput(t, tester, []string{
|
||||
"echo SPACE foo ENTER",
|
||||
"hishtory SPACE tquery ENTER",
|
||||
"Escape",
|
||||
})
|
||||
require.Contains(t, out, "echo foo\n")
|
||||
require.Contains(t, out, "hishtory tquery\n")
|
||||
require.NotContains(t, out, "Search Query")
|
||||
require.True(t, len(strings.Split(out, "\n")) <= 7)
|
||||
|
||||
// Test that it renders the help page fine
|
||||
out = captureTerminalOutput(t, tester, []string{
|
||||
"echo SPACE foo ENTER",
|
||||
"hishtory SPACE tquery ENTER",
|
||||
"C-h",
|
||||
})
|
||||
testutils.CompareGoldens(t, out, "TestTui-FullScreenHelp")
|
||||
|
||||
// Test that it renders fine in full-screen mode and compact-mode
|
||||
tester.RunInteractiveShell(t, `hishtory config-set compact-mode true`)
|
||||
out = captureTerminalOutput(t, tester, []string{
|
||||
"echo foo ENTER",
|
||||
"hishtory SPACE tquery ENTER",
|
||||
})
|
||||
testutils.CompareGoldens(t, out, "TestTui-FullScreenCompactRender")
|
||||
}
|
||||
|
||||
func testTui_defaultFilter(t *testing.T) {
|
||||
// Setup
|
||||
defer testutils.BackupAndRestore(t)()
|
||||
|
@ -184,6 +184,7 @@ func init() {
|
||||
configGetCmd.AddCommand(getAiCompletionEndpoint)
|
||||
configGetCmd.AddCommand(getCompactMode)
|
||||
configGetCmd.AddCommand(getLogLevelCmd)
|
||||
configGetCmd.AddCommand(getFullScreenCmd)
|
||||
}
|
||||
|
||||
var getLogLevelCmd = &cobra.Command{
|
||||
@ -195,3 +196,13 @@ var getLogLevelCmd = &cobra.Command{
|
||||
fmt.Println(config.LogLevel.String())
|
||||
},
|
||||
}
|
||||
|
||||
var getFullScreenCmd = &cobra.Command{
|
||||
Use: "full-screen",
|
||||
Short: "Get whether or not hishtory is configured to run in full-screen mode",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := hctx.MakeContext()
|
||||
config := hctx.GetConf(ctx)
|
||||
fmt.Println(config.FullScreenRendering)
|
||||
},
|
||||
}
|
||||
|
@ -262,6 +262,19 @@ var setLogLevelCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
var setFullScreenCmd = &cobra.Command{
|
||||
Use: "full-screen",
|
||||
Short: "Configure whether or not hishtory is configured to run in full-screen mode",
|
||||
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
||||
ValidArgs: []string{"true", "false"},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := hctx.MakeContext()
|
||||
config := hctx.GetConf(ctx)
|
||||
config.FullScreenRendering = args[0] == "true"
|
||||
lib.CheckFatalError(hctx.SetConfig(config))
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(configSetCmd)
|
||||
configSetCmd.AddCommand(setEnableControlRCmd)
|
||||
@ -277,6 +290,7 @@ func init() {
|
||||
configSetCmd.AddCommand(setAiCompletionEndpoint)
|
||||
configSetCmd.AddCommand(compactMode)
|
||||
configSetCmd.AddCommand(setLogLevelCmd)
|
||||
configSetCmd.AddCommand(setFullScreenCmd)
|
||||
setColorSchemeCmd.AddCommand(setColorSchemeSelectedText)
|
||||
setColorSchemeCmd.AddCommand(setColorSchemeSelectedBackground)
|
||||
setColorSchemeCmd.AddCommand(setColorSchemeBorderColor)
|
||||
|
@ -221,6 +221,8 @@ type ClientConfig struct {
|
||||
KeyBindings keybindings.SerializableKeyMap `json:"key_bindings"`
|
||||
// The log level for hishtory (e.g., "debug", "info", "warn", "error")
|
||||
LogLevel logrus.Level `json:"log_level"`
|
||||
// Whether the TUI should render in full-screen mode
|
||||
FullScreenRendering bool `json:"full_screen_rendering"`
|
||||
}
|
||||
|
||||
type ColorScheme struct {
|
||||
|
40
client/testdata/TestTui-FullScreenCompactRender
vendored
Normal file
40
client/testdata/TestTui-FullScreenCompactRender
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
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 ~/ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
46
client/testdata/TestTui-FullScreenHelp
vendored
Normal file
46
client/testdata/TestTui-FullScreenHelp
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
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+h help
|
||||
ctrl+x select an entry and cd into that directory
|
42
client/testdata/TestTui-FullScreenRender
vendored
Normal file
42
client/testdata/TestTui-FullScreenRender
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
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 • ctrl+h help
|
@ -29,11 +29,6 @@ import (
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
const (
|
||||
TABLE_HEIGHT = 20
|
||||
PADDED_NUM_ENTRIES = TABLE_HEIGHT * 5
|
||||
)
|
||||
|
||||
var (
|
||||
CURRENT_QUERY_FOR_HIGHLIGHTING string = ""
|
||||
SELECTED_COMMAND string = ""
|
||||
@ -218,7 +213,7 @@ func runQueryAndUpdateTable(m model, forceUpdateTable, maintainCursor bool) tea.
|
||||
// The default filter was cleared for this session, so don't apply it
|
||||
defaultFilter = ""
|
||||
}
|
||||
rows, entries, searchErr := getRows(m.ctx, conf.DisplayedColumns, m.shellName, defaultFilter, query, PADDED_NUM_ENTRIES)
|
||||
rows, entries, searchErr := getRows(m.ctx, conf.DisplayedColumns, m.shellName, defaultFilter, query, getNumEntriesNeeded(m.ctx))
|
||||
return asyncQueryFinishedMsg{queryId, rows, entries, searchErr, forceUpdateTable, maintainCursor, nil}
|
||||
}
|
||||
}
|
||||
@ -457,7 +452,7 @@ func getBaseStyle(config hctx.ClientConfig) lipgloss.Style {
|
||||
|
||||
func renderNullableTable(m model, helpText string) string {
|
||||
if m.table == nil {
|
||||
return strings.Repeat("\n", TABLE_HEIGHT+3)
|
||||
return strings.Repeat("\n", getTableHeight(m.ctx)+3)
|
||||
}
|
||||
helpTextLen := strings.Count(helpText, "\n")
|
||||
baseStyle := getBaseStyle(*hctx.GetConf(m.ctx))
|
||||
@ -656,6 +651,25 @@ func min(a, b int) int {
|
||||
return b
|
||||
}
|
||||
|
||||
func getTableHeight(ctx context.Context) int {
|
||||
config := hctx.GetConf(ctx)
|
||||
if config.FullScreenRendering {
|
||||
_, terminalHeight, err := getTerminalSize()
|
||||
if err != nil {
|
||||
// A reasonable guess at a default if for some reason we fail to retrieve the terminal size
|
||||
return 30
|
||||
}
|
||||
return max(terminalHeight-15, 20)
|
||||
}
|
||||
// Default to 20 when not full-screen since we want to balance showing a large table with not using the entire screen
|
||||
return 20
|
||||
}
|
||||
|
||||
func getNumEntriesNeeded(ctx context.Context) int {
|
||||
// Get more than table height since the TUI filters some out (e.g. duplicate entries)
|
||||
return getTableHeight(ctx) * 5
|
||||
}
|
||||
|
||||
func makeTable(ctx context.Context, shellName string, rows []table.Row) (table.Model, error) {
|
||||
config := hctx.GetConf(ctx)
|
||||
columns, err := makeTableColumns(ctx, shellName, config.DisplayedColumns, rows)
|
||||
@ -689,7 +703,7 @@ func makeTable(ctx context.Context, shellName string, rows []table.Row) (table.M
|
||||
if isExtraCompactHeightMode(ctx) {
|
||||
tuiSize -= 3
|
||||
}
|
||||
tableHeight := min(TABLE_HEIGHT, terminalHeight-tuiSize)
|
||||
tableHeight := min(getTableHeight(ctx), terminalHeight-tuiSize)
|
||||
t := table.New(
|
||||
table.WithColumns(columns),
|
||||
table.WithRows(rows),
|
||||
@ -868,20 +882,24 @@ 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))
|
||||
additionalOptions := []tea.ProgramOption{tea.WithOutput(os.Stderr)}
|
||||
if hctx.GetConf(ctx).FullScreenRendering {
|
||||
additionalOptions = append(additionalOptions, tea.WithAltScreen())
|
||||
}
|
||||
p := tea.NewProgram(initialModel(ctx, shellName, initialQuery), additionalOptions...)
|
||||
// Async: Get the initial set of rows
|
||||
go func() {
|
||||
LAST_DISPATCHED_QUERY_ID++
|
||||
queryId := LAST_DISPATCHED_QUERY_ID
|
||||
LAST_DISPATCHED_QUERY_TIMESTAMP = time.Now()
|
||||
conf := hctx.GetConf(ctx)
|
||||
rows, entries, err := getRows(ctx, conf.DisplayedColumns, shellName, conf.DefaultFilter, initialQuery, PADDED_NUM_ENTRIES)
|
||||
rows, entries, err := getRows(ctx, conf.DisplayedColumns, shellName, conf.DefaultFilter, initialQuery, getNumEntriesNeeded(ctx))
|
||||
if err == nil || initialQuery == "" {
|
||||
p.Send(asyncQueryFinishedMsg{queryId: queryId, rows: rows, entries: entries, searchErr: err, forceUpdateTable: true, maintainCursor: false, overriddenSearchQuery: nil})
|
||||
} else {
|
||||
// initialQuery is likely invalid in some way, let's just drop it
|
||||
emptyQuery := ""
|
||||
rows, entries, err := getRows(ctx, hctx.GetConf(ctx).DisplayedColumns, shellName, conf.DefaultFilter, emptyQuery, PADDED_NUM_ENTRIES)
|
||||
rows, entries, err := getRows(ctx, hctx.GetConf(ctx).DisplayedColumns, shellName, conf.DefaultFilter, emptyQuery, getNumEntriesNeeded(ctx))
|
||||
p.Send(asyncQueryFinishedMsg{queryId: queryId, rows: rows, entries: entries, searchErr: err, forceUpdateTable: true, maintainCursor: false, overriddenSearchQuery: &emptyQuery})
|
||||
}
|
||||
}()
|
||||
|
Loading…
Reference in New Issue
Block a user