mirror of
https://github.com/ddworken/hishtory.git
synced 2025-06-20 11:57:50 +02:00
* 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
This commit is contained in:
parent
81ed634a4b
commit
a3f1282368
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -26,7 +26,7 @@ var UNUSED_GOLDENS []string = []string{"TestTui-Exit", "testControlR-ControlC-ba
|
||||
"testCustomColumns-tquery-zsh", "testUninstall-post-uninstall-bash",
|
||||
"testUninstall-post-uninstall-zsh", "TestTui-ColoredOutput",
|
||||
"TestTui-ColoredOutputWithCustomColorScheme", "TestTui-ColoredOutputWithSearch", "TestTui-ColoredOutputWithSearch-Highlight",
|
||||
"TestTui-DefaultColorScheme"}
|
||||
"TestTui-DefaultColorScheme", "TestTui-ColoredOutputWithDefaultFilter"}
|
||||
|
||||
func main() {
|
||||
exportMetrics()
|
||||
|
2
client/testdata/TestTui-ColoredOutput
vendored
2
client/testdata/TestTui-ColoredOutput
vendored
@ -1,4 +1,4 @@
|
||||
Search Query: > [7ml[0m[90m[49ms
|
||||
Search Query: [90m> [7m[39ml[0m[90m[49ms
|
||||
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│[39m Hostname CWD Timestamp Runtime Exit Code Command [90m│
|
||||
|
@ -1,4 +1,4 @@
|
||||
Search Query: > ech[7m [0m[39m[49m
|
||||
Search Query: [90m> [39mech[7m [0m[39m[49m
|
||||
|
||||
[91m┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│[39m Hostname CWD Timestamp Runtime Exit Code Command [91m│
|
||||
|
27
client/testdata/TestTui-ColoredOutputWithDefaultFilter
vendored
Normal file
27
client/testdata/TestTui-ColoredOutputWithDefaultFilter
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
Search Query: [90m[exit_code:0] [39mech[7m [0m[39m[49m
|
||||
|
||||
[91m┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│[39m Hostname CWD Timestamp Runtime Exit Code Command [91m│
|
||||
│────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||
│[39m [91m│
|
||||
│[39m [91m│
|
||||
│[39m [91m│
|
||||
│[39m [91m│
|
||||
│[39m [91m│
|
||||
│[39m [91m│
|
||||
│[39m [91m│
|
||||
│[39m [91m│
|
||||
│[39m [91m│
|
||||
│[39m [91m│
|
||||
│[39m [91m│
|
||||
│[39m [91m│
|
||||
│[39m [91m│
|
||||
│[39m [91m│
|
||||
│[39m [91m│
|
||||
│[39m [91m│
|
||||
│[39m [91m│
|
||||
│[39m [91m│
|
||||
│[39m [91m│
|
||||
│[39m [91m│
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
[90mhiSHtory: Search your shell history[39m [90m • ctrl+h[39m [90mhelp
|
@ -1,4 +1,4 @@
|
||||
Search Query: > ech[7m [0m[39m[49m
|
||||
Search Query: [90m> [39mech[7m [0m[39m[49m
|
||||
|
||||
[90m┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│[39m Hostname CWD Timestamp Runtime Exit Code Command [90m│
|
||||
|
@ -1,4 +1,4 @@
|
||||
Search Query: > ech[7m [0m[39m[49m
|
||||
Search Query: [90m> [39mech[7m [0m[39m[49m
|
||||
|
||||
[90m┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│[39m Hostname CWD Timestamp Runtime Exit Code Command [90m│
|
||||
|
27
client/testdata/TestTui-DefaultFilter-Deleted
vendored
Normal file
27
client/testdata/TestTui-DefaultFilter-Deleted
vendored
Normal file
@ -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
|
27
client/testdata/TestTui-DefaultFilter-DeletedWithText
vendored
Normal file
27
client/testdata/TestTui-DefaultFilter-DeletedWithText
vendored
Normal file
@ -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
|
27
client/testdata/TestTui-DefaultFilter-Enabled
vendored
Normal file
27
client/testdata/TestTui-DefaultFilter-Enabled
vendored
Normal file
@ -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
|
27
client/testdata/TestTui-DefaultFilter-EnabledAdditionalQuery
vendored
Normal file
27
client/testdata/TestTui-DefaultFilter-EnabledAdditionalQuery
vendored
Normal file
@ -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
|
@ -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()
|
||||
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})
|
||||
}
|
||||
}()
|
||||
|
Loading…
x
Reference in New Issue
Block a user