mirror of
https://github.com/ddworken/hishtory.git
synced 2025-08-14 17:18:46 +02:00
Quote initial commands to make it possible to easily use hishtory to find matching entries for already typed commands that contain flags
This commit is contained in:
@ -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,13 +984,20 @@ 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])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tokens = append(tokens, string(token))
|
if string(token) != "" {
|
||||||
|
tokens = append(tokens, string(token))
|
||||||
|
}
|
||||||
return tokens
|
return tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,8 +306,8 @@ 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"}},
|
||||||
{"''", ' ', -1, []string{""}},
|
{"''", ' ', -1, nil},
|
||||||
{"\"\"", ' ', -1, []string{""}},
|
{"\"\"", ' ', -1, nil},
|
||||||
{"\\\"", ' ', -1, []string{"\""}},
|
{"\\\"", ' ', -1, []string{"\""}},
|
||||||
{"\\'", ' ', -1, []string{"'"}},
|
{"\\'", ' ', -1, []string{"'"}},
|
||||||
// Tests the behavior of quotes with
|
// Tests the behavior of quotes with
|
||||||
@ -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)
|
||||||
|
@ -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"
|
||||||
@ -865,21 +866,55 @@ 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 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)
|
||||||
p := tea.NewProgram(initialModel(ctx, shellName, initialQuery), tea.WithOutput(os.Stderr))
|
p := tea.NewProgram(initialModel(ctx, shellName, initialQueryWithEscaping), tea.WithOutput(os.Stderr))
|
||||||
// Async: Get the initial set of rows
|
// Async: Get the initial set of rows
|
||||||
go func() {
|
go func() {
|
||||||
LAST_DISPATCHED_QUERY_ID++
|
LAST_DISPATCHED_QUERY_ID++
|
||||||
queryId := LAST_DISPATCHED_QUERY_ID
|
queryId := LAST_DISPATCHED_QUERY_ID
|
||||||
LAST_DISPATCHED_QUERY_TIMESTAMP = time.Now()
|
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, PADDED_NUM_ENTRIES)
|
rows, entries, err := getRows(ctx, conf.DisplayedColumns, shellName, conf.DefaultFilter, initialQueryWithEscaping, PADDED_NUM_ENTRIES)
|
||||||
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})
|
p.Send(asyncQueryFinishedMsg{queryId: queryId, rows: rows, entries: entries, searchErr: err, forceUpdateTable: true, maintainCursor: false, overriddenSearchQuery: nil})
|
||||||
} 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, PADDED_NUM_ENTRIES)
|
rows, entries, err := getRows(ctx, hctx.GetConf(ctx).DisplayedColumns, shellName, conf.DefaultFilter, emptyQuery, PADDED_NUM_ENTRIES)
|
||||||
p.Send(asyncQueryFinishedMsg{queryId: queryId, rows: rows, entries: entries, searchErr: err, forceUpdateTable: true, maintainCursor: false, overriddenSearchQuery: &emptyQuery})
|
p.Send(asyncQueryFinishedMsg{queryId: queryId, rows: rows, entries: entries, searchErr: err, forceUpdateTable: true, maintainCursor: false, overriddenSearchQuery: &emptyQuery})
|
||||||
@ -913,13 +948,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
|
||||||
|
Reference in New Issue
Block a user