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 {