From 6dea8a989e6ec22d086ddca306f90e56f301df48 Mon Sep 17 00:00:00 2001 From: David Dworken Date: Thu, 3 Nov 2022 20:36:36 -0700 Subject: [PATCH] Add config option to filter out duplicate history entries as requested in #10 --- README.md | 11 +++++-- client/client_test.go | 32 +++++++++++++++++++ client/hctx/hctx.go | 2 ++ .../testRemoveDuplicateRows-enabled-export | 8 +++++ .../testRemoveDuplicateRows-enabled-query | 7 ++++ .../testRemoveDuplicateRows-enabled-tquery | 30 +++++++++++++++++ .../goldens/testRemoveDuplicateRows-export | 5 +++ .../lib/goldens/testRemoveDuplicateRows-query | 7 ++++ .../goldens/testRemoveDuplicateRows-tquery | 30 +++++++++++++++++ client/lib/lib.go | 16 ++++++++-- client/lib/tui.go | 8 ++++- hishtory.go | 14 ++++++-- 12 files changed, 162 insertions(+), 8 deletions(-) create mode 100644 client/lib/goldens/testRemoveDuplicateRows-enabled-export create mode 100644 client/lib/goldens/testRemoveDuplicateRows-enabled-query create mode 100644 client/lib/goldens/testRemoveDuplicateRows-enabled-tquery create mode 100644 client/lib/goldens/testRemoveDuplicateRows-export create mode 100644 client/lib/goldens/testRemoveDuplicateRows-query create mode 100644 client/lib/goldens/testRemoveDuplicateRows-tquery diff --git a/README.md b/README.md index 230bbbf..4fc4c82 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,6 @@ hishtory config-set displayed-columns CWD Command -
Custom Columns @@ -81,7 +80,6 @@ You can create custom column definitions that are populated from arbitrary comma hishtory config-add custom-column git_remote '(git remote -v 2>/dev/null | grep origin 1>/dev/null ) && git remote get-url origin || true' hishtory config-add displayed-columns git_remote ``` -
@@ -89,6 +87,15 @@ hishtory config-add displayed-columns git_remote If you'd like to disable the control-R integration in your shell, you can do so by running `hishtory config-set enable-control-r false`.
+
+Filtering duplicate entries +By default, hishtory query will show all results even if this includes duplicate history entries. This helps you keep track of how many times you've run a command and in what contexts. If you'd rather disable this so that hiSHtory won't show duplicate entries, you can run: + +``` +hishtory config-set filter-duplicate-commands true +``` +
+
Offline Install If you don't need the ability to sync your shell history, you can install hiSHtory in offline mode. diff --git a/client/client_test.go b/client/client_test.go index 5fe984b..d580e93 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -2069,6 +2069,38 @@ echo bar`) } } +func TestRemoveDuplicateRows(t *testing.T) { + // Setup + tester := bashTester{} + defer testutils.BackupAndRestore(t)() + installHishtory(t, tester, "") + + // Record a few commands and check that they get recorded and all are displayed in a table + tester.RunInteractiveShell(t, `echo foo +echo foo +echo baz +echo baz +echo foo`) + out := tester.RunInteractiveShell(t, `hishtory export -pipefail`) + compareGoldens(t, out, "testRemoveDuplicateRows-export") + tester.RunInteractiveShell(t, `hishtory config-set displayed-columns 'Exit Code' Command`) + out = tester.RunInteractiveShell(t, `hishtory query -pipefail`) + compareGoldens(t, out, "testRemoveDuplicateRows-query") + out = captureTerminalOutput(t, tester, []string{"hishtory SPACE tquery SPACE -pipefail ENTER"}) + out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1]) + compareGoldens(t, out, "testRemoveDuplicateRows-tquery") + + // And change the config to filter out duplicate rows + tester.RunInteractiveShell(t, `hishtory config-set filter-duplicate-commands true`) + out = tester.RunInteractiveShell(t, `hishtory export -pipefail`) + compareGoldens(t, out, "testRemoveDuplicateRows-enabled-export") + out = tester.RunInteractiveShell(t, `hishtory query -pipefail`) + compareGoldens(t, out, "testRemoveDuplicateRows-enabled-query") + out = captureTerminalOutput(t, tester, []string{"hishtory SPACE tquery SPACE -pipefail ENTER"}) + out = strings.TrimSpace(strings.Split(out, "hishtory tquery")[1]) + compareGoldens(t, out, "testRemoveDuplicateRows-enabled-tquery") +} + type deviceSet struct { deviceMap *map[device]deviceOp currentDevice *device diff --git a/client/hctx/hctx.go b/client/hctx/hctx.go index 6bb4495..37c7430 100644 --- a/client/hctx/hctx.go +++ b/client/hctx/hctx.go @@ -163,6 +163,8 @@ type ClientConfig struct { CustomColumns []CustomColumnDefinition `json:"custom_columns"` // Whether this is an offline instance of hishtory with no syncing IsOffline bool `json:"is_offline"` + // Whether duplicate commands should be displayed + FilterDuplicateCommands bool `json:"filter_duplicate_commands"` } type CustomColumnDefinition struct { diff --git a/client/lib/goldens/testRemoveDuplicateRows-enabled-export b/client/lib/goldens/testRemoveDuplicateRows-enabled-export new file mode 100644 index 0000000..c300a9a --- /dev/null +++ b/client/lib/goldens/testRemoveDuplicateRows-enabled-export @@ -0,0 +1,8 @@ +echo foo +echo foo +echo baz +echo baz +echo foo +hishtory config-set displayed-columns 'Exit Code' Command +source /Users/david/.bashrc +hishtory config-set filter-duplicate-commands true diff --git a/client/lib/goldens/testRemoveDuplicateRows-enabled-query b/client/lib/goldens/testRemoveDuplicateRows-enabled-query new file mode 100644 index 0000000..3ec0baa --- /dev/null +++ b/client/lib/goldens/testRemoveDuplicateRows-enabled-query @@ -0,0 +1,7 @@ +Exit Code Command +0 hishtory config-set filter-duplicate-commands true +0 source /Users/david/.bashrc +0 hishtory config-set displayed-columns 'Exit Code' Command +0 echo foo +0 echo baz +0 echo foo diff --git a/client/lib/goldens/testRemoveDuplicateRows-enabled-tquery b/client/lib/goldens/testRemoveDuplicateRows-enabled-tquery new file mode 100644 index 0000000..dad32fa --- /dev/null +++ b/client/lib/goldens/testRemoveDuplicateRows-enabled-tquery @@ -0,0 +1,30 @@ +-pipefail + + + +Search Query: > -pipefail + +┌───────────────────────────────────────────────────────────────────────────┐ +│ Exit Code Command │ +│───────────────────────────────────────────────────────────────────────────│ +│ 0 source /Users/david/.bashrc │ +│ 0 hishtory config-set filter-duplicate-commands true │ +│ 0 source /Users/david/.bashrc │ +│ 0 hishtory config-set displayed-columns 'Exit Code' Command │ +│ 0 echo foo │ +│ 0 echo baz │ +│ 0 echo foo │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└───────────────────────────────────────────────────────────────────────────┘ \ No newline at end of file diff --git a/client/lib/goldens/testRemoveDuplicateRows-export b/client/lib/goldens/testRemoveDuplicateRows-export new file mode 100644 index 0000000..bf1a9bf --- /dev/null +++ b/client/lib/goldens/testRemoveDuplicateRows-export @@ -0,0 +1,5 @@ +echo foo +echo foo +echo baz +echo baz +echo foo diff --git a/client/lib/goldens/testRemoveDuplicateRows-query b/client/lib/goldens/testRemoveDuplicateRows-query new file mode 100644 index 0000000..2650c8c --- /dev/null +++ b/client/lib/goldens/testRemoveDuplicateRows-query @@ -0,0 +1,7 @@ +Exit Code Command +0 hishtory config-set displayed-columns 'Exit Code' Command +0 echo foo +0 echo baz +0 echo baz +0 echo foo +0 echo foo diff --git a/client/lib/goldens/testRemoveDuplicateRows-tquery b/client/lib/goldens/testRemoveDuplicateRows-tquery new file mode 100644 index 0000000..1f75890 --- /dev/null +++ b/client/lib/goldens/testRemoveDuplicateRows-tquery @@ -0,0 +1,30 @@ +-pipefail + + + +Search Query: > -pipefail + +┌───────────────────────────────────────────────────────────────────────────┐ +│ Exit Code Command │ +│───────────────────────────────────────────────────────────────────────────│ +│ 0 source /Users/david/.bashrc │ +│ 0 hishtory config-set displayed-columns 'Exit Code' Command │ +│ 0 echo foo │ +│ 0 echo baz │ +│ 0 echo baz │ +│ 0 echo foo │ +│ 0 echo foo │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└───────────────────────────────────────────────────────────────────────────┘ \ No newline at end of file diff --git a/client/lib/lib.go b/client/lib/lib.go index a2b93ed..742063e 100644 --- a/client/lib/lib.go +++ b/client/lib/lib.go @@ -461,7 +461,7 @@ func stringArrayToAnyArray(arr []string) []any { return ret } -func DisplayResults(ctx *context.Context, results []*data.HistoryEntry) error { +func DisplayResults(ctx *context.Context, results []*data.HistoryEntry, numResults int) error { config := hctx.GetConf(ctx) headerFmt := color.New(color.FgGreen, color.Underline).SprintfFunc() @@ -472,12 +472,22 @@ func DisplayResults(ctx *context.Context, results []*data.HistoryEntry) error { tbl := table.New(columns...) tbl.WithHeaderFormatter(headerFmt) - for _, result := range results { - row, err := buildTableRow(ctx, config.DisplayedColumns, *result) + lastCommand := "" + numRows := 0 + for _, entry := range results { + if entry != nil && entry.Command == lastCommand && config.FilterDuplicateCommands { + continue + } + row, err := buildTableRow(ctx, config.DisplayedColumns, *entry) if err != nil { return err } tbl.AddRow(stringArrayToAnyArray(row)...) + numRows += 1 + lastCommand = entry.Command + if numRows >= numResults { + break + } } tbl.Print() diff --git a/client/lib/tui.go b/client/lib/tui.go index 55b7e80..b1f5e3c 100644 --- a/client/lib/tui.go +++ b/client/lib/tui.go @@ -20,7 +20,7 @@ import ( ) const TABLE_HEIGHT = 20 -const PADDED_NUM_ENTRIES = TABLE_HEIGHT * 3 +const PADDED_NUM_ENTRIES = TABLE_HEIGHT * 5 var selectedRow string = "" @@ -212,20 +212,26 @@ func (m model) View() string { func getRows(ctx *context.Context, columnNames []string, query string, numEntries int) ([]table.Row, int, error) { db := hctx.GetDb(ctx) + config := hctx.GetConf(ctx) data, err := Search(ctx, db, query, numEntries) if err != nil { return nil, 0, err } var rows []table.Row + lastCommand := "" for i := 0; i < numEntries; i++ { if i < len(data) { entry := data[i] + if entry.Command == lastCommand && config.FilterDuplicateCommands { + continue + } entry.Command = strings.ReplaceAll(entry.Command, "\n", " ") // TODO: handle multi-line commands better here row, err := buildTableRow(ctx, columnNames, *entry) if err != nil { return nil, 0, fmt.Errorf("failed to build row for entry=%#v: %v", entry, err) } rows = append(rows, row) + lastCommand = entry.Command } else { rows = append(rows, table.Row{}) } diff --git a/hishtory.go b/hishtory.go index 9b038e0..0101de4 100644 --- a/hishtory.go +++ b/hishtory.go @@ -135,6 +135,8 @@ func main() { switch key { case "enable-control-r": fmt.Printf("%v", config.ControlRSearchEnabled) + case "filter-duplicate-commands": + fmt.Printf("%v", config.FilterDuplicateCommands) case "displayed-columns": for _, col := range config.DisplayedColumns { if strings.Contains(col, " ") { @@ -162,6 +164,13 @@ func main() { } config.ControlRSearchEnabled = (val == "true") lib.CheckFatalError(hctx.SetConfig(config)) + case "filter-duplicate-commands": + val := os.Args[3] + if val != "true" && val != "false" { + log.Fatalf("Unexpected config value %s, must be one of: true, false", val) + } + config.FilterDuplicateCommands = (val == "true") + lib.CheckFatalError(hctx.SetConfig(config)) case "displayed-columns": vals := os.Args[3:] config.DisplayedColumns = vals @@ -313,9 +322,10 @@ func query(ctx *context.Context, query string) { } } lib.CheckFatalError(displayBannerIfSet(ctx)) - data, err := lib.Search(ctx, db, query, 25) + numResults := 25 + data, err := lib.Search(ctx, db, query, numResults*5) lib.CheckFatalError(err) - lib.CheckFatalError(lib.DisplayResults(ctx, data)) + lib.CheckFatalError(lib.DisplayResults(ctx, data, numResults)) } func displayBannerIfSet(ctx *context.Context) error {