Add config option to filter out duplicate history entries as requested in #10

This commit is contained in:
David Dworken 2022-11-03 20:36:36 -07:00
parent 83ca625ad9
commit 6dea8a989e
No known key found for this signature in database
12 changed files with 162 additions and 8 deletions

View File

@ -71,7 +71,6 @@ hishtory config-set displayed-columns CWD Command
</details> </details>
<details> <details>
<summary>Custom Columns</summary> <summary>Custom Columns</summary>
@ -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 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 hishtory config-add displayed-columns git_remote
``` ```
</details> </details>
<details> <details>
@ -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`. 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`.
</details> </details>
<details>
<summary>Filtering duplicate entries</summary>
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
```
</details>
<details> <details>
<summary>Offline Install</summary> <summary>Offline Install</summary>
If you don't need the ability to sync your shell history, you can install hiSHtory in offline mode. If you don't need the ability to sync your shell history, you can install hiSHtory in offline mode.

View File

@ -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 { type deviceSet struct {
deviceMap *map[device]deviceOp deviceMap *map[device]deviceOp
currentDevice *device currentDevice *device

View File

@ -163,6 +163,8 @@ type ClientConfig struct {
CustomColumns []CustomColumnDefinition `json:"custom_columns"` CustomColumns []CustomColumnDefinition `json:"custom_columns"`
// Whether this is an offline instance of hishtory with no syncing // Whether this is an offline instance of hishtory with no syncing
IsOffline bool `json:"is_offline"` IsOffline bool `json:"is_offline"`
// Whether duplicate commands should be displayed
FilterDuplicateCommands bool `json:"filter_duplicate_commands"`
} }
type CustomColumnDefinition struct { type CustomColumnDefinition struct {

View File

@ -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

View File

@ -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

View File

@ -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 │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└───────────────────────────────────────────────────────────────────────────┘

View File

@ -0,0 +1,5 @@
echo foo
echo foo
echo baz
echo baz
echo foo

View File

@ -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

View File

@ -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 │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└───────────────────────────────────────────────────────────────────────────┘

View File

@ -461,7 +461,7 @@ func stringArrayToAnyArray(arr []string) []any {
return ret 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) config := hctx.GetConf(ctx)
headerFmt := color.New(color.FgGreen, color.Underline).SprintfFunc() 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 := table.New(columns...)
tbl.WithHeaderFormatter(headerFmt) tbl.WithHeaderFormatter(headerFmt)
for _, result := range results { lastCommand := ""
row, err := buildTableRow(ctx, config.DisplayedColumns, *result) 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 { if err != nil {
return err return err
} }
tbl.AddRow(stringArrayToAnyArray(row)...) tbl.AddRow(stringArrayToAnyArray(row)...)
numRows += 1
lastCommand = entry.Command
if numRows >= numResults {
break
}
} }
tbl.Print() tbl.Print()

View File

@ -20,7 +20,7 @@ import (
) )
const TABLE_HEIGHT = 20 const TABLE_HEIGHT = 20
const PADDED_NUM_ENTRIES = TABLE_HEIGHT * 3 const PADDED_NUM_ENTRIES = TABLE_HEIGHT * 5
var selectedRow string = "" 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) { func getRows(ctx *context.Context, columnNames []string, query string, numEntries int) ([]table.Row, int, error) {
db := hctx.GetDb(ctx) db := hctx.GetDb(ctx)
config := hctx.GetConf(ctx)
data, err := Search(ctx, db, query, numEntries) data, err := Search(ctx, db, query, numEntries)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
var rows []table.Row var rows []table.Row
lastCommand := ""
for i := 0; i < numEntries; i++ { for i := 0; i < numEntries; i++ {
if i < len(data) { if i < len(data) {
entry := data[i] entry := data[i]
if entry.Command == lastCommand && config.FilterDuplicateCommands {
continue
}
entry.Command = strings.ReplaceAll(entry.Command, "\n", " ") // TODO: handle multi-line commands better here entry.Command = strings.ReplaceAll(entry.Command, "\n", " ") // TODO: handle multi-line commands better here
row, err := buildTableRow(ctx, columnNames, *entry) row, err := buildTableRow(ctx, columnNames, *entry)
if err != nil { if err != nil {
return nil, 0, fmt.Errorf("failed to build row for entry=%#v: %v", entry, err) return nil, 0, fmt.Errorf("failed to build row for entry=%#v: %v", entry, err)
} }
rows = append(rows, row) rows = append(rows, row)
lastCommand = entry.Command
} else { } else {
rows = append(rows, table.Row{}) rows = append(rows, table.Row{})
} }

View File

@ -135,6 +135,8 @@ func main() {
switch key { switch key {
case "enable-control-r": case "enable-control-r":
fmt.Printf("%v", config.ControlRSearchEnabled) fmt.Printf("%v", config.ControlRSearchEnabled)
case "filter-duplicate-commands":
fmt.Printf("%v", config.FilterDuplicateCommands)
case "displayed-columns": case "displayed-columns":
for _, col := range config.DisplayedColumns { for _, col := range config.DisplayedColumns {
if strings.Contains(col, " ") { if strings.Contains(col, " ") {
@ -162,6 +164,13 @@ func main() {
} }
config.ControlRSearchEnabled = (val == "true") config.ControlRSearchEnabled = (val == "true")
lib.CheckFatalError(hctx.SetConfig(config)) 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": case "displayed-columns":
vals := os.Args[3:] vals := os.Args[3:]
config.DisplayedColumns = vals config.DisplayedColumns = vals
@ -313,9 +322,10 @@ func query(ctx *context.Context, query string) {
} }
} }
lib.CheckFatalError(displayBannerIfSet(ctx)) 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(err)
lib.CheckFatalError(lib.DisplayResults(ctx, data)) lib.CheckFatalError(lib.DisplayResults(ctx, data, numResults))
} }
func displayBannerIfSet(ctx *context.Context) error { func displayBannerIfSet(ctx *context.Context) error {