mirror of
https://github.com/ddworken/hishtory.git
synced 2024-11-25 09:43:20 +01:00
Quote initial commands to make it possible to easily use hishtory to find matching entries for already typed commands that contain flags (#251)
* Quote initial commands to make it possible to easily use hishtory to find matching entries for already typed commands that contain flags * Add test for quoting dashes * Fix test failures * More test fixes * Update goldens * Update goldens * Update goldens * Fix race condition * Fix test harness bug by swapping to splitn * Update goldens * Update golden * Update test
This commit is contained in:
parent
905afd91c3
commit
0023c72636
@ -2165,6 +2165,14 @@ func testTui_search(t *testing.T, onlineStatus OnlineStatus) {
|
|||||||
"'\"'foo:bar'\"'",
|
"'\"'foo:bar'\"'",
|
||||||
}))
|
}))
|
||||||
testutils.CompareGoldens(t, out, "TestTui-SearchColonDoubleQuoted")
|
testutils.CompareGoldens(t, out, "TestTui-SearchColonDoubleQuoted")
|
||||||
|
|
||||||
|
// And check that we can quote dashes
|
||||||
|
require.NoError(t, db.Create(testutils.MakeFakeHistoryEntry("foo --bar")).Error)
|
||||||
|
out = stripTuiCommandPrefix(t, captureTerminalOutput(t, tester, []string{
|
||||||
|
"hishtory SPACE tquery ENTER",
|
||||||
|
"'\"'--bar'\"'",
|
||||||
|
}))
|
||||||
|
testutils.CompareGoldens(t, out, "TestTui-SearchQuoteDash")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTui_general(t *testing.T, onlineStatus OnlineStatus) {
|
func testTui_general(t *testing.T, onlineStatus OnlineStatus) {
|
||||||
@ -2520,8 +2528,8 @@ echo bar`)
|
|||||||
tester.RunInteractiveShell(t, `hishtory config-set displayed-columns 'Exit Code' git_remote Command`)
|
tester.RunInteractiveShell(t, `hishtory config-set displayed-columns 'Exit Code' git_remote Command`)
|
||||||
out = tester.RunInteractiveShell(t, `hishtory query -pipefail`)
|
out = tester.RunInteractiveShell(t, `hishtory query -pipefail`)
|
||||||
testutils.CompareGoldens(t, out, fmt.Sprintf("testCustomColumns-query-isAction=%v", testutils.IsGithubAction()))
|
testutils.CompareGoldens(t, out, fmt.Sprintf("testCustomColumns-query-isAction=%v", testutils.IsGithubAction()))
|
||||||
out = captureTerminalOutput(t, tester, []string{"hishtory SPACE tquery SPACE -pipefail ENTER"})
|
out = captureTerminalOutput(t, tester, []string{"hishtory SPACE tquery SPACE ENTER", "-pipefail"})
|
||||||
out = stripRequiredPrefix(t, out, "hishtory tquery -pipefail")
|
out = stripRequiredPrefix(t, out, "hishtory tquery")
|
||||||
testName := "testCustomColumns-tquery-" + tester.ShellName()
|
testName := "testCustomColumns-tquery-" + tester.ShellName()
|
||||||
if testutils.IsGithubAction() {
|
if testutils.IsGithubAction() {
|
||||||
testName += "-isAction"
|
testName += "-isAction"
|
||||||
@ -2777,8 +2785,8 @@ func TestTimestampFormat(t *testing.T) {
|
|||||||
// And check that it is displayed in both the tui and the classic view
|
// And check that it is displayed in both the tui and the classic view
|
||||||
out := hishtoryQuery(t, tester, "-pipefail -tablesizing")
|
out := hishtoryQuery(t, tester, "-pipefail -tablesizing")
|
||||||
testutils.CompareGoldens(t, out, "TestTimestampFormat-query")
|
testutils.CompareGoldens(t, out, "TestTimestampFormat-query")
|
||||||
out = captureTerminalOutput(t, tester, []string{"hishtory SPACE tquery SPACE -pipefail SPACE -tablesizing ENTER"})
|
out = captureTerminalOutput(t, tester, []string{"hishtory SPACE tquery ENTER", "table_cmd SPACE -tquery"})
|
||||||
out = stripRequiredPrefix(t, out, "hishtory tquery -pipefail -tablesizing")
|
out = stripRequiredPrefix(t, out, "hishtory tquery")
|
||||||
testutils.CompareGoldens(t, out, "TestTimestampFormat-tquery")
|
testutils.CompareGoldens(t, out, "TestTimestampFormat-tquery")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2817,7 +2825,7 @@ func TestSortByConsistentTimezone(t *testing.T) {
|
|||||||
testutils.CompareGoldens(t, out, "TestSortByConsistentTimezone-query")
|
testutils.CompareGoldens(t, out, "TestSortByConsistentTimezone-query")
|
||||||
out = tester.RunInteractiveShell(t, `hishtory export -pipefail -tablesizing`)
|
out = tester.RunInteractiveShell(t, `hishtory export -pipefail -tablesizing`)
|
||||||
testutils.CompareGoldens(t, out, "TestSortByConsistentTimezone-export")
|
testutils.CompareGoldens(t, out, "TestSortByConsistentTimezone-export")
|
||||||
out = captureTerminalOutput(t, tester, []string{"hishtory SPACE tquery SPACE -pipefail SPACE -tablesizing ENTER"})
|
out = captureTerminalOutput(t, tester, []string{"hishtory SPACE tquery ENTER", "-pipefail SPACE -tablesizing"})
|
||||||
out = stripTuiCommandPrefix(t, out)
|
out = stripTuiCommandPrefix(t, out)
|
||||||
require.Regexp(t, regexp.MustCompile(`Timestamp[\s\S]*Command[\s\S]*Apr 16 2022 01:36:26 PDT[\s\S]*third_entry[\s\S]*Apr 16 2022 01:19:46 PDT[\s\S]*second_entry[\s\S]*Apr 16 2022 01:03:06 PDT[\s\S]*first_entry`), out)
|
require.Regexp(t, regexp.MustCompile(`Timestamp[\s\S]*Command[\s\S]*Apr 16 2022 01:36:26 PDT[\s\S]*third_entry[\s\S]*Apr 16 2022 01:19:46 PDT[\s\S]*second_entry[\s\S]*Apr 16 2022 01:03:06 PDT[\s\S]*first_entry`), out)
|
||||||
}
|
}
|
||||||
@ -2878,8 +2886,8 @@ echo foo`)
|
|||||||
tester.RunInteractiveShell(t, `hishtory config-set displayed-columns 'Exit Code' Command`)
|
tester.RunInteractiveShell(t, `hishtory config-set displayed-columns 'Exit Code' Command`)
|
||||||
out = tester.RunInteractiveShell(t, `hishtory query -pipefail`)
|
out = tester.RunInteractiveShell(t, `hishtory query -pipefail`)
|
||||||
testutils.CompareGoldens(t, out, "testRemoveDuplicateRows-query")
|
testutils.CompareGoldens(t, out, "testRemoveDuplicateRows-query")
|
||||||
out = captureTerminalOutput(t, tester, []string{"hishtory SPACE tquery SPACE -pipefail ENTER"})
|
out = captureTerminalOutput(t, tester, []string{"hishtory SPACE tquery ENTER", "-pipefail"})
|
||||||
out = stripRequiredPrefix(t, out, "hishtory tquery -pipefail")
|
out = stripRequiredPrefix(t, out, "hishtory tquery")
|
||||||
testutils.CompareGoldens(t, out, "testRemoveDuplicateRows-tquery")
|
testutils.CompareGoldens(t, out, "testRemoveDuplicateRows-tquery")
|
||||||
|
|
||||||
// And change the config to filter out duplicate rows
|
// And change the config to filter out duplicate rows
|
||||||
@ -2894,18 +2902,20 @@ echo foo`)
|
|||||||
testutils.CompareGoldens(t, out, "testRemoveDuplicateRows-enabled-query")
|
testutils.CompareGoldens(t, out, "testRemoveDuplicateRows-enabled-query")
|
||||||
|
|
||||||
// Check tquery
|
// Check tquery
|
||||||
out = captureTerminalOutput(t, tester, []string{"hishtory SPACE tquery SPACE -pipefail ENTER"})
|
out = captureTerminalOutput(t, tester, []string{"hishtory SPACE tquery ENTER", "-pipefail"})
|
||||||
out = stripRequiredPrefix(t, out, "hishtory tquery -pipefail")
|
out = stripRequiredPrefix(t, out, "hishtory tquery")
|
||||||
testutils.CompareGoldens(t, out, "testRemoveDuplicateRows-enabled-tquery")
|
testutils.CompareGoldens(t, out, "testRemoveDuplicateRows-enabled-tquery")
|
||||||
|
|
||||||
// Check actually selecting it with query
|
// Check actually selecting it with query
|
||||||
out = captureTerminalOutputWithComplexCommands(t, tester, []TmuxCommand{
|
out = captureTerminalOutputWithComplexCommands(t, tester, []TmuxCommand{
|
||||||
{Keys: "hishtory SPACE tquery SPACE -pipefail ENTER", ExtraDelay: 1.0},
|
{Keys: "hishtory SPACE tquery ENTER", ExtraDelay: 1.0},
|
||||||
{Keys: "Down Down"},
|
{Keys: "-pipefail", ExtraDelay: 1.0},
|
||||||
|
{Keys: "Down Down Down"},
|
||||||
{Keys: "ENTER", ExtraDelay: 1.0},
|
{Keys: "ENTER", ExtraDelay: 1.0},
|
||||||
})
|
})
|
||||||
out = stripTuiCommandPrefix(t, out)
|
out = stripTuiCommandPrefix(t, out)
|
||||||
require.Contains(t, out, "\necho foo\n")
|
require.Contains(t, out, "echo foo\n")
|
||||||
|
require.NotContains(t, out, "hishtory tquery")
|
||||||
require.NotContains(t, out, "echo baz")
|
require.NotContains(t, out, "echo baz")
|
||||||
require.NotContains(t, out, "config-set")
|
require.NotContains(t, out, "config-set")
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ var tqueryCmd = &cobra.Command{
|
|||||||
if os.Getenv("HISHTORY_SHELL_NAME") != "" {
|
if os.Getenv("HISHTORY_SHELL_NAME") != "" {
|
||||||
shellName = os.Getenv("HISHTORY_SHELL_NAME")
|
shellName = os.Getenv("HISHTORY_SHELL_NAME")
|
||||||
}
|
}
|
||||||
lib.CheckFatalError(tui.TuiQuery(ctx, shellName, strings.Join(args, " ")))
|
lib.CheckFatalError(tui.TuiQuery(ctx, shellName, args))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -965,7 +965,9 @@ func splitEscaped(query string, separator rune, maxSplit int) []string {
|
|||||||
isInSingleQuotedString := false
|
isInSingleQuotedString := false
|
||||||
for i := 0; i < len(runeQuery); i++ {
|
for i := 0; i < len(runeQuery); i++ {
|
||||||
if (maxSplit < 0 || splits < maxSplit) && runeQuery[i] == separator && !isInSingleQuotedString && !isInDoubleQuotedString {
|
if (maxSplit < 0 || splits < maxSplit) && runeQuery[i] == separator && !isInSingleQuotedString && !isInDoubleQuotedString {
|
||||||
tokens = append(tokens, string(token))
|
if string(token) != "" {
|
||||||
|
tokens = append(tokens, string(token))
|
||||||
|
}
|
||||||
token = token[:0]
|
token = token[:0]
|
||||||
splits++
|
splits++
|
||||||
} else if runeQuery[i] == '\\' && i+1 < len(runeQuery) {
|
} else if runeQuery[i] == '\\' && i+1 < len(runeQuery) {
|
||||||
@ -982,8 +984,13 @@ func splitEscaped(query string, separator rune, maxSplit int) []string {
|
|||||||
} else if runeQuery[i] == '\'' && !isInDoubleQuotedString && !heuristicIgnoreUnclosedQuote(isInSingleQuotedString, '\'', runeQuery, i) {
|
} else if runeQuery[i] == '\'' && !isInDoubleQuotedString && !heuristicIgnoreUnclosedQuote(isInSingleQuotedString, '\'', runeQuery, i) {
|
||||||
isInSingleQuotedString = !isInSingleQuotedString
|
isInSingleQuotedString = !isInSingleQuotedString
|
||||||
} else {
|
} else {
|
||||||
if (isInSingleQuotedString || isInDoubleQuotedString) && separator == ' ' && runeQuery[i] == ':' {
|
if (isInSingleQuotedString || isInDoubleQuotedString) && separator == ' ' {
|
||||||
token = append(token, '\\')
|
if runeQuery[i] == ':' {
|
||||||
|
token = append(token, '\\')
|
||||||
|
}
|
||||||
|
if runeQuery[i] == '-' && len(token) == 0 {
|
||||||
|
token = append(token, '\\')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
token = append(token, runeQuery[i])
|
token = append(token, runeQuery[i])
|
||||||
}
|
}
|
||||||
|
@ -324,6 +324,11 @@ func TestSplitEscaped(t *testing.T) {
|
|||||||
{"foo:bar", ' ', -1, []string{"foo:bar"}},
|
{"foo:bar", ' ', -1, []string{"foo:bar"}},
|
||||||
{"'foo:bar'", ' ', -1, []string{"foo\\:bar"}},
|
{"'foo:bar'", ' ', -1, []string{"foo\\:bar"}},
|
||||||
{"\"foo:bar\"", ' ', -1, []string{"foo\\:bar"}},
|
{"\"foo:bar\"", ' ', -1, []string{"foo\\:bar"}},
|
||||||
|
// Tests for quoting dashes
|
||||||
|
{"'-foo'", ' ', -1, []string{"\\-foo"}},
|
||||||
|
{"'--foo'", ' ', -1, []string{"\\--foo"}},
|
||||||
|
{"bar '--foo'", ' ', -1, []string{"bar", "\\--foo"}},
|
||||||
|
{"bar 'foo-baz'", ' ', -1, []string{"bar", "foo-baz"}},
|
||||||
}
|
}
|
||||||
for _, tc := range testcases {
|
for _, tc := range testcases {
|
||||||
actual := splitEscaped(tc.input, tc.char, tc.limit)
|
actual := splitEscaped(tc.input, tc.char, tc.limit)
|
||||||
|
8
client/testdata/TestTimestampFormat-tquery
vendored
8
client/testdata/TestTimestampFormat-tquery
vendored
@ -1,10 +1,10 @@
|
|||||||
Search Query: > -pipefail -tablesizing
|
Search Query: > table_cmd -tquery
|
||||||
|
|
||||||
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
│ Hostname CWD Timestamp Runtime Exit Code Command │
|
│ Hostname CWD Timestamp Runtime Exit Code Command │
|
||||||
│──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
│──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
│ localhost ~/foo/ 2022/Apr/16 01:03 24s 3 table_cmd2 │
|
│ localhost ~/foo/ 2022/Apr/16 01:03 24s 3 table_cmd2 │
|
||||||
│ localhost /tmp/ 2022/Apr/16 01:03 4s 2 table_cmd1 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa… │
|
│ localhost /tmp/ 2022/Apr/16 01:03 4s 2 table_cmd1 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa… │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
|
27
client/testdata/TestTui-SearchQuoteDash
vendored
Normal file
27
client/testdata/TestTui-SearchQuoteDash
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Search Query: > "--bar"
|
||||||
|
|
||||||
|
┌────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Hostname CWD Timestamp Runtime Exit Code Command │
|
||||||
|
│────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
|
│ localhost /tmp/ Oct 17 2022 21:43:46 PDT 3s 2 foo --bar │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
└────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
hiSHtory: Search your shell history • ctrl+h help
|
@ -3,6 +3,7 @@ Search Query: > -pipefail
|
|||||||
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
│ Exit Code git_remote Command │
|
│ Exit Code git_remote Command │
|
||||||
│────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
│────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
|
│ 0 https://github.com/ddworken/hishtory hishtory tquery │
|
||||||
│ 0 https://github.com/ddworken/hishtory hishtory config-set displayed-columns 'Exit Code' git_remote Command │
|
│ 0 https://github.com/ddworken/hishtory hishtory config-set displayed-columns 'Exit Code' git_remote Command │
|
||||||
│ 0 echo bar │
|
│ 0 echo bar │
|
||||||
│ 0 cd / │
|
│ 0 cd / │
|
||||||
@ -22,6 +23,5 @@ Search Query: > -pipefail
|
|||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
|
||||||
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
hiSHtory: Search your shell history • ctrl+h help
|
hiSHtory: Search your shell history • ctrl+h help
|
@ -3,6 +3,7 @@ Search Query: > -pipefail
|
|||||||
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
│ Exit Code git_remote Command │
|
│ Exit Code git_remote Command │
|
||||||
│────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
│────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
|
||||||
|
│ 0 https://github.com/ddworken/hishtory hishtory tquery │
|
||||||
│ 0 https://github.com/ddworken/hishtory hishtory config-set displayed-columns 'Exit Code' git_remote Command │
|
│ 0 https://github.com/ddworken/hishtory hishtory config-set displayed-columns 'Exit Code' git_remote Command │
|
||||||
│ 0 echo bar │
|
│ 0 echo bar │
|
||||||
│ 0 cd / │
|
│ 0 cd / │
|
||||||
@ -22,6 +23,5 @@ Search Query: > -pipefail
|
|||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
|
||||||
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
hiSHtory: Search your shell history • ctrl+h help
|
hiSHtory: Search your shell history • ctrl+h help
|
@ -4,4 +4,5 @@ echo baz
|
|||||||
echo baz
|
echo baz
|
||||||
echo foo
|
echo foo
|
||||||
hishtory config-set displayed-columns 'Exit Code' Command
|
hishtory config-set displayed-columns 'Exit Code' Command
|
||||||
|
hishtory tquery
|
||||||
hishtory config-set filter-duplicate-commands true
|
hishtory config-set filter-duplicate-commands true
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
Exit Code Command
|
Exit Code Command
|
||||||
0 hishtory config-set filter-duplicate-commands true
|
0 hishtory config-set filter-duplicate-commands true
|
||||||
|
0 hishtory tquery
|
||||||
0 hishtory config-set displayed-columns 'Exit Code' Command
|
0 hishtory config-set displayed-columns 'Exit Code' Command
|
||||||
0 echo foo
|
0 echo foo
|
||||||
0 echo baz
|
0 echo baz
|
||||||
|
@ -3,6 +3,7 @@ Search Query: > -pipefail
|
|||||||
┌───────────────────────────────────────────────────────────────────────────┐
|
┌───────────────────────────────────────────────────────────────────────────┐
|
||||||
│ Exit Code Command │
|
│ Exit Code Command │
|
||||||
│───────────────────────────────────────────────────────────────────────────│
|
│───────────────────────────────────────────────────────────────────────────│
|
||||||
|
│ 0 hishtory tquery │
|
||||||
│ 0 hishtory config-set filter-duplicate-commands true │
|
│ 0 hishtory config-set filter-duplicate-commands true │
|
||||||
│ 0 hishtory config-set displayed-columns 'Exit Code' Command │
|
│ 0 hishtory config-set displayed-columns 'Exit Code' Command │
|
||||||
│ 0 echo foo │
|
│ 0 echo foo │
|
||||||
@ -22,6 +23,5 @@ Search Query: > -pipefail
|
|||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
|
||||||
└───────────────────────────────────────────────────────────────────────────┘
|
└───────────────────────────────────────────────────────────────────────────┘
|
||||||
hiSHtory: Search your shell history • ctrl+h help
|
hiSHtory: Search your shell history • ctrl+h help
|
@ -3,6 +3,7 @@ Search Query: > -pipefail
|
|||||||
┌───────────────────────────────────────────────────────────────────────────┐
|
┌───────────────────────────────────────────────────────────────────────────┐
|
||||||
│ Exit Code Command │
|
│ Exit Code Command │
|
||||||
│───────────────────────────────────────────────────────────────────────────│
|
│───────────────────────────────────────────────────────────────────────────│
|
||||||
|
│ 0 hishtory tquery │
|
||||||
│ 0 hishtory config-set displayed-columns 'Exit Code' Command │
|
│ 0 hishtory config-set displayed-columns 'Exit Code' Command │
|
||||||
│ 0 echo foo │
|
│ 0 echo foo │
|
||||||
│ 0 echo baz │
|
│ 0 echo baz │
|
||||||
@ -22,6 +23,5 @@ Search Query: > -pipefail
|
|||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
|
||||||
└───────────────────────────────────────────────────────────────────────────┘
|
└───────────────────────────────────────────────────────────────────────────┘
|
||||||
hiSHtory: Search your shell history • ctrl+h help
|
hiSHtory: Search your shell history • ctrl+h help
|
@ -361,7 +361,7 @@ func stripShellPrefix(out string) string {
|
|||||||
|
|
||||||
func stripRequiredPrefix(t *testing.T, out, prefix string) string {
|
func stripRequiredPrefix(t *testing.T, out, prefix string) string {
|
||||||
require.Contains(t, out, prefix)
|
require.Contains(t, out, prefix)
|
||||||
return strings.TrimSpace(strings.Split(out, prefix)[1])
|
return strings.TrimSpace(strings.SplitN(out, prefix, 2)[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
func stripTuiCommandPrefix(t *testing.T, out string) string {
|
func stripTuiCommandPrefix(t *testing.T, out string) string {
|
||||||
|
@ -3,6 +3,7 @@ package tui
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
_ "embed" // for embedding config.sh
|
_ "embed" // for embedding config.sh
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -95,6 +96,9 @@ type model struct {
|
|||||||
|
|
||||||
// The currently executing shell. Defaults to bash if not specified. Used for more precise AI suggestions.
|
// The currently executing shell. Defaults to bash if not specified. Used for more precise AI suggestions.
|
||||||
shellName string
|
shellName string
|
||||||
|
|
||||||
|
// Whether we've finished the first load of results. If we haven't, we refuse to run additional queries to avoid race conditions with how we handle invalid initial queries.
|
||||||
|
hasFinishedFirstLoad bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@ -119,6 +123,8 @@ type asyncQueryFinishedMsg struct {
|
|||||||
maintainCursor bool
|
maintainCursor bool
|
||||||
// An updated search query. May be used for initial queries when they're invalid.
|
// An updated search query. May be used for initial queries when they're invalid.
|
||||||
overriddenSearchQuery *string
|
overriddenSearchQuery *string
|
||||||
|
|
||||||
|
isFirstQuery bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func initialModel(ctx context.Context, shellName, initialQuery string) model {
|
func initialModel(ctx context.Context, shellName, initialQuery string) model {
|
||||||
@ -148,7 +154,7 @@ func initialModel(ctx context.Context, shellName, initialQuery string) model {
|
|||||||
queryInput.SetValue(initialQuery)
|
queryInput.SetValue(initialQuery)
|
||||||
}
|
}
|
||||||
CURRENT_QUERY_FOR_HIGHLIGHTING = initialQuery
|
CURRENT_QUERY_FOR_HIGHLIGHTING = initialQuery
|
||||||
return model{ctx: ctx, spinner: s, isLoading: true, table: nil, tableEntries: []*data.HistoryEntry{}, runQuery: &initialQuery, queryInput: queryInput, help: help.New(), shellName: shellName}
|
return model{ctx: ctx, spinner: s, isLoading: true, table: nil, tableEntries: []*data.HistoryEntry{}, runQuery: &initialQuery, queryInput: queryInput, help: help.New(), shellName: shellName, hasFinishedFirstLoad: false}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m model) Init() tea.Cmd {
|
func (m model) Init() tea.Cmd {
|
||||||
@ -199,13 +205,14 @@ func preventTableOverscrolling(m model) {
|
|||||||
|
|
||||||
func runQueryAndUpdateTable(m model, forceUpdateTable, maintainCursor bool) tea.Cmd {
|
func runQueryAndUpdateTable(m model, forceUpdateTable, maintainCursor bool) tea.Cmd {
|
||||||
if (m.runQuery != nil && *m.runQuery != m.lastQuery) || forceUpdateTable || m.searchErr != nil {
|
if (m.runQuery != nil && *m.runQuery != m.lastQuery) || forceUpdateTable || m.searchErr != nil {
|
||||||
|
// if !m.hasFinishedFirstLoad {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
query := m.lastQuery
|
query := m.lastQuery
|
||||||
if m.runQuery != nil {
|
if m.runQuery != nil {
|
||||||
query = *m.runQuery
|
query = *m.runQuery
|
||||||
}
|
}
|
||||||
LAST_DISPATCHED_QUERY_ID++
|
queryId := allocateQueryId()
|
||||||
queryId := LAST_DISPATCHED_QUERY_ID
|
|
||||||
LAST_DISPATCHED_QUERY_TIMESTAMP = time.Now()
|
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
conf := hctx.GetConf(m.ctx)
|
conf := hctx.GetConf(m.ctx)
|
||||||
defaultFilter := conf.DefaultFilter
|
defaultFilter := conf.DefaultFilter
|
||||||
@ -214,7 +221,7 @@ func runQueryAndUpdateTable(m model, forceUpdateTable, maintainCursor bool) tea.
|
|||||||
defaultFilter = ""
|
defaultFilter = ""
|
||||||
}
|
}
|
||||||
rows, entries, searchErr := getRows(m.ctx, conf.DisplayedColumns, m.shellName, defaultFilter, query, getNumEntriesNeeded(m.ctx))
|
rows, entries, searchErr := getRows(m.ctx, conf.DisplayedColumns, m.shellName, defaultFilter, query, getNumEntriesNeeded(m.ctx))
|
||||||
return asyncQueryFinishedMsg{queryId, rows, entries, searchErr, forceUpdateTable, maintainCursor, nil}
|
return asyncQueryFinishedMsg{queryId, rows, entries, searchErr, forceUpdateTable, maintainCursor, nil, false}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -331,6 +338,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.queryInput.SetValue(*msg.overriddenSearchQuery)
|
m.queryInput.SetValue(*msg.overriddenSearchQuery)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if msg.isFirstQuery {
|
||||||
|
m.hasFinishedFirstLoad = true
|
||||||
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
default:
|
default:
|
||||||
var cmd tea.Cmd
|
var cmd tea.Cmd
|
||||||
@ -879,28 +889,72 @@ func configureColorProfile(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TuiQuery(ctx context.Context, shellName, initialQuery string) error {
|
func buildInitialQueryWithSearchEscaping(initialQueryArray []string) (string, error) {
|
||||||
|
var initialQuery string
|
||||||
|
|
||||||
|
for i, queryChunk := range initialQueryArray {
|
||||||
|
if i != 0 {
|
||||||
|
initialQuery += " "
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(queryChunk, "-") {
|
||||||
|
quoted, err := json.Marshal(queryChunk)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to marshal query chunk for escaping: %w", err)
|
||||||
|
}
|
||||||
|
initialQuery += string(quoted)
|
||||||
|
} else {
|
||||||
|
initialQuery += queryChunk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return initialQuery, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitQueryArray(initialQueryArray []string) []string {
|
||||||
|
var splitQueryArray []string
|
||||||
|
for _, queryChunk := range initialQueryArray {
|
||||||
|
splitQueryArray = append(splitQueryArray, strings.Split(queryChunk, " ")...)
|
||||||
|
}
|
||||||
|
return splitQueryArray
|
||||||
|
}
|
||||||
|
|
||||||
|
func allocateQueryId() int {
|
||||||
|
LAST_DISPATCHED_QUERY_ID++
|
||||||
|
LAST_DISPATCHED_QUERY_TIMESTAMP = time.Now()
|
||||||
|
return LAST_DISPATCHED_QUERY_ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func TuiQuery(ctx context.Context, shellName string, initialQueryArray []string) error {
|
||||||
|
initialQueryArray = splitQueryArray(initialQueryArray)
|
||||||
|
initialQueryWithEscaping, err := buildInitialQueryWithSearchEscaping(initialQueryArray)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
loadedKeyBindings = hctx.GetConf(ctx).KeyBindings.ToKeyMap()
|
loadedKeyBindings = hctx.GetConf(ctx).KeyBindings.ToKeyMap()
|
||||||
configureColorProfile(ctx)
|
configureColorProfile(ctx)
|
||||||
additionalOptions := []tea.ProgramOption{tea.WithOutput(os.Stderr)}
|
additionalOptions := []tea.ProgramOption{tea.WithOutput(os.Stderr)}
|
||||||
if hctx.GetConf(ctx).FullScreenRendering {
|
if hctx.GetConf(ctx).FullScreenRendering {
|
||||||
additionalOptions = append(additionalOptions, tea.WithAltScreen())
|
additionalOptions = append(additionalOptions, tea.WithAltScreen())
|
||||||
}
|
}
|
||||||
p := tea.NewProgram(initialModel(ctx, shellName, initialQuery), additionalOptions...)
|
p := tea.NewProgram(initialModel(ctx, shellName, initialQueryWithEscaping), additionalOptions...)
|
||||||
// Async: Get the initial set of rows
|
// Async: Get the initial set of rows
|
||||||
go func() {
|
go func() {
|
||||||
LAST_DISPATCHED_QUERY_ID++
|
queryId := allocateQueryId()
|
||||||
queryId := LAST_DISPATCHED_QUERY_ID
|
|
||||||
LAST_DISPATCHED_QUERY_TIMESTAMP = time.Now()
|
|
||||||
conf := hctx.GetConf(ctx)
|
conf := hctx.GetConf(ctx)
|
||||||
rows, entries, err := getRows(ctx, conf.DisplayedColumns, shellName, conf.DefaultFilter, initialQuery, getNumEntriesNeeded(ctx))
|
rows, entries, err := getRows(ctx, conf.DisplayedColumns, shellName, conf.DefaultFilter, initialQueryWithEscaping, getNumEntriesNeeded(ctx))
|
||||||
if err == nil || initialQuery == "" {
|
if err == nil || initialQueryWithEscaping == "" {
|
||||||
p.Send(asyncQueryFinishedMsg{queryId: queryId, rows: rows, entries: entries, searchErr: err, forceUpdateTable: true, maintainCursor: false, overriddenSearchQuery: nil})
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
p.Send(asyncQueryFinishedMsg{queryId: queryId, rows: rows, entries: entries, searchErr: err, forceUpdateTable: true, maintainCursor: false, overriddenSearchQuery: nil, isFirstQuery: true})
|
||||||
} else {
|
} else {
|
||||||
// initialQuery is likely invalid in some way, let's just drop it
|
// The initial query is likely invalid in some way, let's just drop it
|
||||||
emptyQuery := ""
|
emptyQuery := ""
|
||||||
rows, entries, err := getRows(ctx, hctx.GetConf(ctx).DisplayedColumns, shellName, conf.DefaultFilter, emptyQuery, getNumEntriesNeeded(ctx))
|
rows, entries, err := getRows(ctx, hctx.GetConf(ctx).DisplayedColumns, shellName, conf.DefaultFilter, emptyQuery, getNumEntriesNeeded(ctx))
|
||||||
p.Send(asyncQueryFinishedMsg{queryId: queryId, rows: rows, entries: entries, searchErr: err, forceUpdateTable: true, maintainCursor: false, overriddenSearchQuery: &emptyQuery})
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
p.Send(asyncQueryFinishedMsg{queryId: allocateQueryId(), rows: rows, entries: entries, searchErr: err, forceUpdateTable: true, maintainCursor: false, overriddenSearchQuery: &emptyQuery, isFirstQuery: true})
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
// Async: Retrieve additional entries from the backend
|
// Async: Retrieve additional entries from the backend
|
||||||
@ -931,13 +985,13 @@ func TuiQuery(ctx context.Context, shellName, initialQuery string) error {
|
|||||||
p.Send(bannerMsg{banner: string(banner)})
|
p.Send(bannerMsg{banner: string(banner)})
|
||||||
}()
|
}()
|
||||||
// Blocking: Start the TUI
|
// Blocking: Start the TUI
|
||||||
_, err := p.Run()
|
_, err = p.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if SELECTED_COMMAND == "" && os.Getenv("HISHTORY_TERM_INTEGRATION") != "" {
|
if SELECTED_COMMAND == "" && os.Getenv("HISHTORY_TERM_INTEGRATION") != "" {
|
||||||
// Print out the initialQuery instead so that we don't clear the terminal
|
// Print out the initialQuery instead so that we don't clear the terminal (note that we don't use the escaped one here)
|
||||||
SELECTED_COMMAND = initialQuery
|
SELECTED_COMMAND = strings.Join(initialQueryArray, " ")
|
||||||
}
|
}
|
||||||
fmt.Printf("%s\n", SELECTED_COMMAND)
|
fmt.Printf("%s\n", SELECTED_COMMAND)
|
||||||
return nil
|
return nil
|
||||||
@ -945,4 +999,3 @@ func TuiQuery(ctx context.Context, shellName, initialQuery string) error {
|
|||||||
|
|
||||||
// TODO: support custom key bindings
|
// TODO: support custom key bindings
|
||||||
// TODO: make the help page wrap
|
// TODO: make the help page wrap
|
||||||
// TODO: If the initial query contains dashes, maybe we should smartly escape them?
|
|
||||||
|
Loading…
Reference in New Issue
Block a user