mirror of
https://github.com/ddworken/hishtory.git
synced 2024-11-22 16:24:00 +01:00
* Add support for quoted searchs for exact matches, for #135 * Add support for quoting search queries * Fix spliteEscaped so that it works with escaping dashes and colons in search queries
This commit is contained in:
parent
1be8e2cb47
commit
1b3fa944bd
@ -38,7 +38,7 @@ Both support the same query format, see the below annotated queries:
|
||||
|---|---|
|
||||
| `psql` | Find all commands containing `psql` |
|
||||
| `psql db.example.com` | Find all commands containing `psql` and `db.example.com` |
|
||||
| `docker hostname:my-server` | Find all commands containing `docker` that were run on the computer with hostname `my-server` |
|
||||
| `"docker run" hostname:my-server` | Find all commands containing `docker run` that were run on the computer with hostname `my-server` |
|
||||
| `nano user:root` | Find all commands containing `nano` that were run as `root` |
|
||||
| `exit_code:127` | Find all commands that exited with code `127` |
|
||||
| `service before:2022-02-01` | Find all commands containing `service` run before February 1st 2022 |
|
||||
@ -74,7 +74,6 @@ If you would like to:
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>TUI key bindings</summary>
|
||||
The TUI (opened via `Control+R`) supports a number of key bindings:
|
||||
|
@ -1864,6 +1864,35 @@ func testTui_search(t *testing.T, onlineStatus OnlineStatus) {
|
||||
})
|
||||
out = stripTuiCommandPrefix(t, out)
|
||||
testutils.CompareGoldens(t, out, "TestTui-InvalidSearchBecomesValid")
|
||||
|
||||
// Record a couple commands that we can use to test for supporting quoted searches
|
||||
db := hctx.GetDb(hctx.MakeContext())
|
||||
require.NoError(t, db.Create(testutils.MakeFakeHistoryEntry("for i in 1")).Error)
|
||||
require.NoError(t, db.Create(testutils.MakeFakeHistoryEntry("for i in 2")).Error)
|
||||
require.NoError(t, db.Create(testutils.MakeFakeHistoryEntry("i for in")).Error)
|
||||
out = tester.RunInteractiveShell(t, `hishtory export`)
|
||||
testutils.CompareGoldens(t, out, "TestTui-ExportWithAdditionalEntries")
|
||||
|
||||
// Check the behavior when it is unquoted and fuzzy
|
||||
out = stripTuiCommandPrefix(t, captureTerminalOutput(t, tester, []string{
|
||||
"hishtory SPACE tquery ENTER",
|
||||
"for SPACE i SPACE in",
|
||||
}))
|
||||
testutils.CompareGoldens(t, out, "TestTui-SearchUnquoted")
|
||||
|
||||
// Check the behavior when it is quoted and exact
|
||||
out = stripTuiCommandPrefix(t, captureTerminalOutput(t, tester, []string{
|
||||
"hishtory SPACE tquery ENTER",
|
||||
"'\"'for SPACE i SPACE in'\"'",
|
||||
}))
|
||||
testutils.CompareGoldens(t, out, "TestTui-SearchQuoted")
|
||||
|
||||
// Check the behavior when it is backslashed
|
||||
out = stripTuiCommandPrefix(t, captureTerminalOutput(t, tester, []string{
|
||||
"hishtory SPACE tquery ENTER",
|
||||
"for\\\\ SPACE i\\\\ SPACE in",
|
||||
}))
|
||||
testutils.CompareGoldens(t, out, "TestTui-SearchBackslash")
|
||||
}
|
||||
|
||||
func testTui_general(t *testing.T, onlineStatus OnlineStatus) {
|
||||
|
5
client/lib/goldens/TestTui-ExportWithAdditionalEntries
Normal file
5
client/lib/goldens/TestTui-ExportWithAdditionalEntries
Normal file
@ -0,0 +1,5 @@
|
||||
ls ~/
|
||||
echo 'aaaaaa bbbb'
|
||||
for i in 1
|
||||
for i in 2
|
||||
i for in
|
27
client/lib/goldens/TestTui-SearchBackslash
Normal file
27
client/lib/goldens/TestTui-SearchBackslash
Normal file
@ -0,0 +1,27 @@
|
||||
Search Query: > for\ i\ in
|
||||
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Hostname CWD Timestamp Runtime Exit Code Command │
|
||||
│────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||
│ localhost /tmp/ Oct 17 2022 21:43:31 PDT 3s 2 for i in 2 │
|
||||
│ localhost /tmp/ Oct 17 2022 21:43:26 PDT 3s 2 for i in 1 │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
hiSHtory: Search your shell history • ctrl+h help
|
27
client/lib/goldens/TestTui-SearchQuoted
Normal file
27
client/lib/goldens/TestTui-SearchQuoted
Normal file
@ -0,0 +1,27 @@
|
||||
Search Query: > "for i in"
|
||||
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Hostname CWD Timestamp Runtime Exit Code Command │
|
||||
│────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||
│ localhost /tmp/ Oct 17 2022 21:43:31 PDT 3s 2 for i in 2 │
|
||||
│ localhost /tmp/ Oct 17 2022 21:43:26 PDT 3s 2 for i in 1 │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
hiSHtory: Search your shell history • ctrl+h help
|
27
client/lib/goldens/TestTui-SearchUnquoted
Normal file
27
client/lib/goldens/TestTui-SearchUnquoted
Normal file
@ -0,0 +1,27 @@
|
||||
Search Query: > for i in
|
||||
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Hostname CWD Timestamp Runtime Exit Code Command │
|
||||
│────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||
│ localhost /tmp/ Oct 17 2022 21:43:36 PDT 3s 2 i for in │
|
||||
│ localhost /tmp/ Oct 17 2022 21:43:31 PDT 3s 2 for i in 2 │
|
||||
│ localhost /tmp/ Oct 17 2022 21:43:26 PDT 3s 2 for i in 1 │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
hiSHtory: Search your shell history • ctrl+h help
|
@ -978,19 +978,29 @@ func tokenize(query string) []string {
|
||||
return splitEscaped(query, ' ', -1)
|
||||
}
|
||||
|
||||
// TODO: Maybe add support for searching for the backslash character itself?
|
||||
func splitEscaped(query string, separator rune, maxSplit int) []string {
|
||||
var token []rune
|
||||
var tokens []string
|
||||
splits := 1
|
||||
runeQuery := []rune(query)
|
||||
isInQuotedString := false
|
||||
for i := 0; i < len(runeQuery); i++ {
|
||||
if (maxSplit < 0 || splits < maxSplit) && runeQuery[i] == separator {
|
||||
if (maxSplit < 0 || splits < maxSplit) && runeQuery[i] == separator && !isInQuotedString {
|
||||
tokens = append(tokens, string(token))
|
||||
token = token[:0]
|
||||
splits++
|
||||
} else if runeQuery[i] == '\\' && i+1 < len(runeQuery) {
|
||||
token = append(token, runeQuery[i], runeQuery[i+1])
|
||||
if runeQuery[i+1] == '-' || runeQuery[i+1] == ':' || runeQuery[i+1] == '\\' {
|
||||
// Note that we need to keep the backslash before the dash to support searches like `ls \-Slah`.
|
||||
// And we need it before the colon so that we can search for things like `foo\:bar`
|
||||
// And we need it before the backslash so that we can search for literal backslashes.
|
||||
token = append(token, runeQuery[i])
|
||||
}
|
||||
i++
|
||||
token = append(token, runeQuery[i])
|
||||
} else if runeQuery[i] == '"' {
|
||||
isInQuotedString = !isInQuotedString
|
||||
} else {
|
||||
token = append(token, runeQuery[i])
|
||||
}
|
||||
|
@ -257,7 +257,7 @@ func TestContainsUnescaped(t *testing.T) {
|
||||
for _, tc := range testcases {
|
||||
actual := containsUnescaped(tc.input, tc.token)
|
||||
if !reflect.DeepEqual(actual, tc.expected) {
|
||||
t.Fatalf("containsUnescaped failure for containsUnescaped(%#v, %#v), actual=%#v", tc.input, tc.token, actual)
|
||||
t.Fatalf("failure for containsUnescaped(%#v, %#v), actual=%#v", tc.input, tc.token, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -274,16 +274,30 @@ func TestSplitEscaped(t *testing.T) {
|
||||
{"foo bar baz", ' ', 3, []string{"foo", "bar", "baz"}},
|
||||
{"foo bar baz", ' ', 1, []string{"foo bar baz"}},
|
||||
{"foo bar baz", ' ', -1, []string{"foo", "bar", "baz"}},
|
||||
{"foo\\ bar baz", ' ', -1, []string{"foo\\ bar", "baz"}},
|
||||
{"foo\\bar baz", ' ', -1, []string{"foo\\bar", "baz"}},
|
||||
{"foo\\bar baz foob", ' ', 2, []string{"foo\\bar", "baz foob"}},
|
||||
{"foo\\ bar\\ baz", ' ', -1, []string{"foo\\ bar\\ baz"}},
|
||||
{"foo\\ bar\\ baz", ' ', -1, []string{"foo\\ bar\\ ", "baz"}},
|
||||
{"foo\\ bar baz", ' ', -1, []string{"foo bar", "baz"}},
|
||||
{"foo\\bar baz", ' ', -1, []string{"foobar", "baz"}},
|
||||
{"foo\\bar baz foob", ' ', 2, []string{"foobar", "baz foob"}},
|
||||
{"foo\\ bar\\ baz", ' ', -1, []string{"foo bar baz"}},
|
||||
{"foo\\ bar\\ baz", ' ', -1, []string{"foo bar ", "baz"}},
|
||||
{"\"foo bar\"", ' ', -1, []string{"foo bar"}},
|
||||
{"\"foo bar\" \" \"", ' ', -1, []string{"foo bar", " "}},
|
||||
{"\"foo bar baz\" and", ' ', -1, []string{"foo bar baz", "and"}},
|
||||
{"\"foo bar baz\" and", ' ', -1, []string{"foo bar baz", "and"}},
|
||||
{"\"foo bar baz", ' ', -1, []string{"foo bar baz"}},
|
||||
{"\"foo bar baz\\\"\"", ' ', -1, []string{"foo bar baz\""}},
|
||||
{"cwd:\"foo bar :baz\\\"\"", ':', -1, []string{"cwd", "foo bar :baz\""}},
|
||||
{"cwd:\"foo bar :baz\\\"\"", ' ', -1, []string{"cwd:foo bar :baz\""}},
|
||||
{"ls \\-foo", ' ', -1, []string{"ls", "\\-foo"}},
|
||||
{"ls \\-foo \\a \\\\", ' ', -1, []string{"ls", "\\-foo", "a", "\\\\"}},
|
||||
{"foo:bar", ':', -1, []string{"foo", "bar"}},
|
||||
{"foo:bar", ' ', -1, []string{"foo:bar"}},
|
||||
{"foo\\:bar", ':', -1, []string{"foo\\:bar"}},
|
||||
{"foo\\:bar", ' ', -1, []string{"foo\\:bar"}},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
actual := splitEscaped(tc.input, tc.char, tc.limit)
|
||||
if !reflect.DeepEqual(actual, tc.expected) {
|
||||
t.Fatalf("containsUnescaped failure for splitEscaped(%#v, %#v, %#v), actual=%#v", tc.input, string(tc.char), tc.limit, actual)
|
||||
t.Fatalf("failure for splitEscaped(%#v, %#v, %#v), actual=%#v", tc.input, string(tc.char), tc.limit, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user