From a3f1282368e0a500d4425e48ec69d33707fead2b Mon Sep 17 00:00:00 2001 From: David Dworken Date: Sun, 7 Jan 2024 21:06:22 -0800 Subject: [PATCH] Add ability to configure a default filter (for #76) (#161) * Add ability to configure a default filter (for #76) * Add test for color of default filter * Add basic test for default filter * Add goldens for tests * Add more tests for default filters * Update goldens * Add another golden * Update goldens * Remove debug log * Add golden to allowlist * Update goldens --- client/client_test.go | 56 +++++++++++++++++++ client/cmd/configGet.go | 10 ++++ client/cmd/configSet.go | 13 +++++ client/hctx/hctx.go | 2 + client/posttest/main.go | 4 +- client/testdata/TestTui-ColoredOutput | 2 +- ...TestTui-ColoredOutputWithCustomColorScheme | 2 +- .../TestTui-ColoredOutputWithDefaultFilter | 27 +++++++++ .../testdata/TestTui-ColoredOutputWithSearch | 2 +- .../TestTui-ColoredOutputWithSearch-Highlight | 2 +- client/testdata/TestTui-DefaultFilter-Deleted | 27 +++++++++ .../TestTui-DefaultFilter-DeletedWithText | 27 +++++++++ client/testdata/TestTui-DefaultFilter-Enabled | 27 +++++++++ ...stTui-DefaultFilter-EnabledAdditionalQuery | 27 +++++++++ client/tui/tui.go | 38 ++++++++++--- 15 files changed, 251 insertions(+), 15 deletions(-) create mode 100644 client/testdata/TestTui-ColoredOutputWithDefaultFilter create mode 100644 client/testdata/TestTui-DefaultFilter-Deleted create mode 100644 client/testdata/TestTui-DefaultFilter-DeletedWithText create mode 100644 client/testdata/TestTui-DefaultFilter-Enabled create mode 100644 client/testdata/TestTui-DefaultFilter-EnabledAdditionalQuery diff --git a/client/client_test.go b/client/client_test.go index 9ad7ad6..1daa008 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -113,6 +113,7 @@ func TestParam(t *testing.T) { t.Run("testTui/color", testTui_color) t.Run("testTui/errors", testTui_errors) t.Run("testTui/ai", testTui_ai) + t.Run("testTui/defaultFilter", testTui_defaultFilter) // Assert there are no leaked connections assertNoLeakedConnections(t) @@ -1752,6 +1753,53 @@ func testTui_scroll(t *testing.T) { assertNoLeakedConnections(t) } +func testTui_defaultFilter(t *testing.T) { + // Setup + defer testutils.BackupAndRestore(t)() + tester, userSecret, _ := setupTestTui(t, Online) + db := hctx.GetDb(hctx.MakeContext()) + e1 := testutils.MakeFakeHistoryEntry("exit 0") + e1.ExitCode = 0 + require.NoError(t, db.Create(e1).Error) + manuallySubmitHistoryEntry(t, userSecret, e1) + e2 := testutils.MakeFakeHistoryEntry("exit 1") + e2.ExitCode = 1 + require.NoError(t, db.Create(e2).Error) + manuallySubmitHistoryEntry(t, userSecret, e2) + + // Configure a default filter + require.Equal(t, "\"\"", strings.TrimSpace(tester.RunInteractiveShell(t, `hishtory config-get default-filter`))) + tester.RunInteractiveShell(t, `hishtory config-set default-filter "exit_code:0"`) + require.Equal(t, "\"exit_code:0\"", strings.TrimSpace(tester.RunInteractiveShell(t, `hishtory config-get default-filter`))) + + // Run a search query with no additional query + out := stripTuiCommandPrefix(t, captureTerminalOutput(t, tester, []string{ + "hishtory SPACE tquery ENTER", + })) + testutils.CompareGoldens(t, out, "TestTui-DefaultFilter-Enabled") + + // Run a search query with an additional query + out = stripTuiCommandPrefix(t, captureTerminalOutput(t, tester, []string{ + "hishtory SPACE tquery ENTER", + "exit", + })) + testutils.CompareGoldens(t, out, "TestTui-DefaultFilter-EnabledAdditionalQuery") + + // Run a search query and delete the default filter + out = stripTuiCommandPrefix(t, captureTerminalOutput(t, tester, []string{ + "hishtory SPACE tquery ENTER", + "BSpace", + })) + testutils.CompareGoldens(t, out, "TestTui-DefaultFilter-Deleted") + + // Run a search query, type something, and then delete the default filter + out = stripTuiCommandPrefix(t, captureTerminalOutput(t, tester, []string{ + "hishtory SPACE tquery ENTER", + "exit Left Left Left Left Left BSpace BSpace", + })) + testutils.CompareGoldens(t, out, "TestTui-DefaultFilter-DeletedWithText") +} + func testTui_color(t *testing.T) { if runtime.GOOS == "linux" { // For some reason, this test fails on linux. Since this test isn't critical and is expected to be @@ -1790,6 +1838,14 @@ func testTui_color(t *testing.T) { out = captureTerminalOutputComplex(t, TmuxCaptureConfig{tester: tester, complexCommands: []TmuxCommand{{Keys: "hishtory SPACE tquery ENTER"}, {Keys: "ech"}}, includeEscapeSequences: true}) out = stripTuiCommandPrefix(t, out) testutils.CompareGoldens(t, out, "TestTui-ColoredOutputWithCustomColorScheme") + + // And one more time with a default filter + require.Equal(t, "\"\"", strings.TrimSpace(tester.RunInteractiveShell(t, `hishtory config-get default-filter`))) + tester.RunInteractiveShell(t, `hishtory config-set default-filter "exit_code:0"`) + require.Equal(t, "\"exit_code:0\"", strings.TrimSpace(tester.RunInteractiveShell(t, `hishtory config-get default-filter`))) + out = captureTerminalOutputComplex(t, TmuxCaptureConfig{tester: tester, complexCommands: []TmuxCommand{{Keys: "hishtory SPACE tquery ENTER"}, {Keys: "ech"}}, includeEscapeSequences: true}) + out = stripTuiCommandPrefix(t, out) + testutils.CompareGoldens(t, out, "TestTui-ColoredOutputWithDefaultFilter") } func testTui_delete(t *testing.T) { diff --git a/client/cmd/configGet.go b/client/cmd/configGet.go index fda08d7..41ea858 100644 --- a/client/cmd/configGet.go +++ b/client/cmd/configGet.go @@ -41,6 +41,15 @@ var getHighlightMatchesCmd = &cobra.Command{ fmt.Println(config.HighlightMatches) }, } +var getDefaultFilterCmd = &cobra.Command{ + Use: "default-filter", + Short: "The default filter that is applied to all search queries", + Run: func(cmd *cobra.Command, args []string) { + ctx := hctx.MakeContext() + config := hctx.GetConf(ctx) + fmt.Printf("%#v", config.DefaultFilter) + }, +} var getFilterDuplicateCommandsCmd = &cobra.Command{ Use: "filter-duplicate-commands", @@ -149,4 +158,5 @@ func init() { configGetCmd.AddCommand(getEnableAiCompletion) configGetCmd.AddCommand(getPresavingCmd) configGetCmd.AddCommand(getColorScheme) + configGetCmd.AddCommand(getDefaultFilterCmd) } diff --git a/client/cmd/configSet.go b/client/cmd/configSet.go index b586bb9..27e688b 100644 --- a/client/cmd/configSet.go +++ b/client/cmd/configSet.go @@ -73,6 +73,18 @@ var setBetaModeCommand = &cobra.Command{ }, } +var setDefaultFilterCommand = &cobra.Command{ + Use: "default-filter", + Short: "Add a default filter that will be applied to all search queries (e.g. `exit_code:0` to filter to only commands that executed successfully)", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + ctx := hctx.MakeContext() + config := hctx.GetConf(ctx) + config.DefaultFilter = args[0] + lib.CheckFatalError(hctx.SetConfig(config)) + }, +} + var setEnableAiCompletionCmd = &cobra.Command{ Use: "ai-completion", Short: "Enable AI completion for searches starting with '?'", @@ -216,6 +228,7 @@ func init() { configSetCmd.AddCommand(setEnableAiCompletionCmd) configSetCmd.AddCommand(setPresavingCmd) configSetCmd.AddCommand(setColorSchemeCmd) + configSetCmd.AddCommand(setDefaultFilterCommand) setColorSchemeCmd.AddCommand(setColorSchemeSelectedText) setColorSchemeCmd.AddCommand(setColorSchemeSelectedBackground) setColorSchemeCmd.AddCommand(setColorSchemeBorderColor) diff --git a/client/hctx/hctx.go b/client/hctx/hctx.go index 75d020e..316a03b 100644 --- a/client/hctx/hctx.go +++ b/client/hctx/hctx.go @@ -203,6 +203,8 @@ type ClientConfig struct { EnablePresaving bool `json:"enable_presaving"` // The current color scheme for the TUI ColorScheme ColorScheme `json:"color_scheme"` + // A default filter that will be applied to all search queries + DefaultFilter string `json:"default_filter"` } type ColorScheme struct { diff --git a/client/posttest/main.go b/client/posttest/main.go index df37cfa..93c7b93 100644 --- a/client/posttest/main.go +++ b/client/posttest/main.go @@ -24,9 +24,9 @@ var UNUSED_GOLDENS []string = []string{"TestTui-Exit", "testControlR-ControlC-ba "testControlR-SelectMultiline-zsh", "testControlR-bash-Disabled", "testControlR-fish-Disabled", "testControlR-zsh-Disabled", "testCustomColumns-query-isAction=false", "testCustomColumns-tquery-bash", "testCustomColumns-tquery-zsh", "testUninstall-post-uninstall-bash", - "testUninstall-post-uninstall-zsh", "TestTui-ColoredOutput", + "testUninstall-post-uninstall-zsh", "TestTui-ColoredOutput", "TestTui-ColoredOutputWithCustomColorScheme", "TestTui-ColoredOutputWithSearch", "TestTui-ColoredOutputWithSearch-Highlight", - "TestTui-DefaultColorScheme"} + "TestTui-DefaultColorScheme", "TestTui-ColoredOutputWithDefaultFilter"} func main() { exportMetrics() diff --git a/client/testdata/TestTui-ColoredOutput b/client/testdata/TestTui-ColoredOutput index b8ca946..6e869be 100644 --- a/client/testdata/TestTui-ColoredOutput +++ b/client/testdata/TestTui-ColoredOutput @@ -1,4 +1,4 @@ -Search Query: > ls +Search Query: > ls ┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Hostname CWD Timestamp Runtime Exit Code Command │ diff --git a/client/testdata/TestTui-ColoredOutputWithCustomColorScheme b/client/testdata/TestTui-ColoredOutputWithCustomColorScheme index 300d8ec..8aeac6f 100644 --- a/client/testdata/TestTui-ColoredOutputWithCustomColorScheme +++ b/client/testdata/TestTui-ColoredOutputWithCustomColorScheme @@ -1,4 +1,4 @@ -Search Query: > ech  +Search Query: > ech  ┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Hostname CWD Timestamp Runtime Exit Code Command │ diff --git a/client/testdata/TestTui-ColoredOutputWithDefaultFilter b/client/testdata/TestTui-ColoredOutputWithDefaultFilter new file mode 100644 index 0000000..781034b --- /dev/null +++ b/client/testdata/TestTui-ColoredOutputWithDefaultFilter @@ -0,0 +1,27 @@ +Search Query: [exit_code:0] ech  + +┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Hostname CWD Timestamp Runtime Exit Code Command │ +│────────────────────────────────────────────────────────────────────────────────────────────────────────│ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +hiSHtory: Search your shell history  • ctrl+h help \ No newline at end of file diff --git a/client/testdata/TestTui-ColoredOutputWithSearch b/client/testdata/TestTui-ColoredOutputWithSearch index e56e5b3..490d4a1 100644 --- a/client/testdata/TestTui-ColoredOutputWithSearch +++ b/client/testdata/TestTui-ColoredOutputWithSearch @@ -1,4 +1,4 @@ -Search Query: > ech  +Search Query: > ech  ┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Hostname CWD Timestamp Runtime Exit Code Command │ diff --git a/client/testdata/TestTui-ColoredOutputWithSearch-Highlight b/client/testdata/TestTui-ColoredOutputWithSearch-Highlight index c34f136..22715b7 100644 --- a/client/testdata/TestTui-ColoredOutputWithSearch-Highlight +++ b/client/testdata/TestTui-ColoredOutputWithSearch-Highlight @@ -1,4 +1,4 @@ -Search Query: > ech  +Search Query: > ech  ┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Hostname CWD Timestamp Runtime Exit Code Command │ diff --git a/client/testdata/TestTui-DefaultFilter-Deleted b/client/testdata/TestTui-DefaultFilter-Deleted new file mode 100644 index 0000000..c9e941a --- /dev/null +++ b/client/testdata/TestTui-DefaultFilter-Deleted @@ -0,0 +1,27 @@ +Search Query: + +┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Hostname CWD Timestamp Runtime Exit Code Command │ +│────────────────────────────────────────────────────────────────────────────────────────────────────────│ +│ localhost /tmp/ Oct 17 2022 21:43:31 PDT 3s 1 exit 1 │ +│ localhost /tmp/ Oct 17 2022 21:43:26 PDT 3s 0 exit 0 │ +│ 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 \ No newline at end of file diff --git a/client/testdata/TestTui-DefaultFilter-DeletedWithText b/client/testdata/TestTui-DefaultFilter-DeletedWithText new file mode 100644 index 0000000..1c0b882 --- /dev/null +++ b/client/testdata/TestTui-DefaultFilter-DeletedWithText @@ -0,0 +1,27 @@ +Search Query: [exit_code:0] exit + +┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Hostname CWD Timestamp Runtime Exit Code Command │ +│────────────────────────────────────────────────────────────────────────────────────────────────────────│ +│ localhost /tmp/ Oct 17 2022 21:43:26 PDT 3s 0 exit 0 │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +hiSHtory: Search your shell history • ctrl+h help \ No newline at end of file diff --git a/client/testdata/TestTui-DefaultFilter-Enabled b/client/testdata/TestTui-DefaultFilter-Enabled new file mode 100644 index 0000000..1b9626f --- /dev/null +++ b/client/testdata/TestTui-DefaultFilter-Enabled @@ -0,0 +1,27 @@ +Search Query: [exit_code:0] + +┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Hostname CWD Timestamp Runtime Exit Code Command │ +│────────────────────────────────────────────────────────────────────────────────────────────────────────│ +│ localhost /tmp/ Oct 17 2022 21:43:26 PDT 3s 0 exit 0 │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +hiSHtory: Search your shell history • ctrl+h help \ No newline at end of file diff --git a/client/testdata/TestTui-DefaultFilter-EnabledAdditionalQuery b/client/testdata/TestTui-DefaultFilter-EnabledAdditionalQuery new file mode 100644 index 0000000..1c0b882 --- /dev/null +++ b/client/testdata/TestTui-DefaultFilter-EnabledAdditionalQuery @@ -0,0 +1,27 @@ +Search Query: [exit_code:0] exit + +┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +│ Hostname CWD Timestamp Runtime Exit Code Command │ +│────────────────────────────────────────────────────────────────────────────────────────────────────────│ +│ localhost /tmp/ Oct 17 2022 21:43:26 PDT 3s 0 exit 0 │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└────────────────────────────────────────────────────────────────────────────────────────────────────────┘ +hiSHtory: Search your shell history • ctrl+h help \ No newline at end of file diff --git a/client/tui/tui.go b/client/tui/tui.go index b18428c..112fddc 100644 --- a/client/tui/tui.go +++ b/client/tui/tui.go @@ -210,7 +210,14 @@ func initialModel(ctx context.Context, initialQuery string) model { s.Spinner = spinner.Dot s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205")) queryInput := textinput.New() - queryInput.Placeholder = "ls" + defaultFilter := hctx.GetConf(ctx).DefaultFilter + if defaultFilter != "" { + queryInput.Prompt = "[" + defaultFilter + "] " + } + queryInput.PromptStyle = queryInput.PlaceholderStyle + if defaultFilter == "" { + queryInput.Placeholder = "ls" + } queryInput.Focus() queryInput.CharLimit = 200 width, _, err := getTerminalSize() @@ -286,7 +293,13 @@ func runQueryAndUpdateTable(m model, forceUpdateTable, maintainCursor bool) tea. queryId := LAST_DISPATCHED_QUERY_ID LAST_DISPATCHED_QUERY_TIMESTAMP = time.Now() return func() tea.Msg { - rows, entries, searchErr := getRows(m.ctx, hctx.GetConf(m.ctx).DisplayedColumns, query, PADDED_NUM_ENTRIES) + conf := hctx.GetConf(m.ctx) + defaultFilter := conf.DefaultFilter + if m.queryInput.Prompt == "" { + // The default filter was cleared for this session, so don't apply it + defaultFilter = "" + } + rows, entries, searchErr := getRows(m.ctx, conf.DisplayedColumns, defaultFilter, query, PADDED_NUM_ENTRIES) return asyncQueryFinishedMsg{queryId, rows, entries, searchErr, forceUpdateTable, maintainCursor, nil} } } @@ -335,12 +348,18 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } pendingCommands = tea.Batch(pendingCommands, cmd1) } + forceUpdateTable := false + if msg.String() == "backspace" && (m.queryInput.Value() == "" || m.queryInput.Position() == 0) { + // Handle deleting the default filter just for this TUI instance + m.queryInput.Prompt = "" + forceUpdateTable = true + } i, cmd2 := m.queryInput.Update(msg) m.queryInput = i searchQuery := m.queryInput.Value() m.runQuery = &searchQuery CURRENT_QUERY_FOR_HIGHLIGHTING = searchQuery - cmd3 := runQueryAndUpdateTable(m, false, false) + cmd3 := runQueryAndUpdateTable(m, forceUpdateTable, false) preventTableOverscrolling(m) return m, tea.Batch(pendingCommands, cmd2, cmd3) } @@ -505,13 +524,13 @@ func getRowsFromAiSuggestions(ctx context.Context, columnNames []string, query s return rows, entries, nil } -func getRows(ctx context.Context, columnNames []string, query string, numEntries int) ([]table.Row, []*data.HistoryEntry, error) { +func getRows(ctx context.Context, columnNames []string, defaultFilter, query string, numEntries int) ([]table.Row, []*data.HistoryEntry, error) { db := hctx.GetDb(ctx) config := hctx.GetConf(ctx) if config.AiCompletion && !config.IsOffline && strings.HasPrefix(query, "?") && len(query) > 1 { return getRowsFromAiSuggestions(ctx, columnNames, query) } - searchResults, err := lib.Search(ctx, db, query, numEntries) + searchResults, err := lib.Search(ctx, db, defaultFilter+" "+query, numEntries) if err != nil { return nil, nil, err } @@ -564,7 +583,7 @@ var bigQueryResults []table.Row func makeTableColumns(ctx context.Context, columnNames []string, rows []table.Row) ([]table.Column, error) { // Handle an initial query with no results if len(rows) == 0 || len(rows[0]) == 0 { - allRows, _, err := getRows(ctx, columnNames, "", 25) + allRows, _, err := getRows(ctx, columnNames, hctx.GetConf(ctx).DefaultFilter, "", 25) if err != nil { return nil, err } @@ -590,7 +609,7 @@ func makeTableColumns(ctx context.Context, columnNames []string, rows []table.Ro // Calculate the maximum column width that is useful for each column if we search for the empty string if bigQueryResults == nil { - bigRows, _, err := getRows(ctx, columnNames, "", 1000) + bigRows, _, err := getRows(ctx, columnNames, "", "", 1000) if err != nil { return nil, err } @@ -868,13 +887,14 @@ func TuiQuery(ctx context.Context, initialQuery string) error { LAST_DISPATCHED_QUERY_ID++ queryId := LAST_DISPATCHED_QUERY_ID LAST_DISPATCHED_QUERY_TIMESTAMP = time.Now() - rows, entries, err := getRows(ctx, hctx.GetConf(ctx).DisplayedColumns, initialQuery, PADDED_NUM_ENTRIES) + conf := hctx.GetConf(ctx) + rows, entries, err := getRows(ctx, conf.DisplayedColumns, conf.DefaultFilter, initialQuery, PADDED_NUM_ENTRIES) 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, emptyQuery, PADDED_NUM_ENTRIES) + rows, entries, err := getRows(ctx, hctx.GetConf(ctx).DisplayedColumns, conf.DefaultFilter, emptyQuery, PADDED_NUM_ENTRIES) p.Send(asyncQueryFinishedMsg{queryId: queryId, rows: rows, entries: entries, searchErr: err, forceUpdateTable: true, maintainCursor: false, overriddenSearchQuery: &emptyQuery}) } }()