diff --git a/cmd/cli/main.go b/cmd/cli/main.go index abbeb9c8..ba5402c8 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "context" "encoding/json" "errors" "fmt" @@ -105,6 +106,7 @@ func runReshCli(out *output.Output, config cfg.Config) (string, int) { st := state{ // lock sync.Mutex + gui: g, cliRecords: resp.Records, initialQuery: *query, } @@ -158,8 +160,9 @@ func runReshCli(out *output.Output, config cfg.Config) (string, int) { out.FatalE(errMsg, err) } - layout.UpdateData(*query) - layout.UpdateRawData(*query) + ctx := context.Background() + layout.updateData(ctx, *query) + layout.updateRawData(ctx, *query) err = g.MainLoop() if err != nil && !errors.Is(err, gocui.ErrQuit) { out.FatalE("Main application loop finished with error", err) @@ -168,8 +171,13 @@ func runReshCli(out *output.Output, config cfg.Config) (string, int) { } type state struct { - lock sync.Mutex - cliRecords []recordint.SearchApp + gui *gocui.Gui + + cliRecords []recordint.SearchApp + + lock sync.Mutex + + cancelUpdate *context.CancelFunc data []searchapp.Item rawData []searchapp.RawItem highlightedItem int @@ -244,7 +252,8 @@ func (m manager) AbortPaste(g *gocui.Gui, v *gocui.View) error { return nil } -func (m manager) UpdateData(input string) { +func (m manager) updateData(ctx context.Context, input string) { + timeStart := time.Now() sugar := m.out.Logger.Sugar() sugar.Debugw("Starting data update ...", "recordCount", len(m.s.cliRecords), @@ -253,9 +262,14 @@ func (m manager) UpdateData(input string) { query := searchapp.NewQueryFromString(sugar, input, m.host, m.pwd, m.gitOriginRemote, m.config.Debug) var data []searchapp.Item itemSet := make(map[string]int) - m.s.lock.Lock() - defer m.s.lock.Unlock() for _, rec := range m.s.cliRecords { + if shouldCancel(ctx) { + timeEnd := time.Now() + sugar.Infow("Update got canceled", + "duration", timeEnd.Sub(timeStart), + ) + return + } itm, err := searchapp.NewItemFromRecordForQuery(rec, query, m.config.Debug) if err != nil { // records didn't match the query @@ -282,6 +296,9 @@ func (m manager) UpdateData(input string) { sort.SliceStable(data, func(p, q int) bool { return data[p].Score > data[q].Score }) + + m.s.lock.Lock() + defer m.s.lock.Unlock() m.s.data = nil for _, itm := range data { if len(m.s.data) > 420 { @@ -290,13 +307,17 @@ func (m manager) UpdateData(input string) { m.s.data = append(m.s.data, itm) } m.s.highlightedItem = 0 + timeEnd := time.Now() sugar.Debugw("Done with data update", + "duration", timeEnd.Sub(timeStart), "recordCount", len(m.s.cliRecords), "itemCount", len(m.s.data), + "input", input, ) } -func (m manager) UpdateRawData(input string) { +func (m manager) updateRawData(ctx context.Context, input string) { + timeStart := time.Now() sugar := m.out.Logger.Sugar() sugar.Debugw("Starting RAW data update ...", "recordCount", len(m.s.cliRecords), @@ -305,9 +326,14 @@ func (m manager) UpdateRawData(input string) { query := searchapp.GetRawTermsFromString(input, m.config.Debug) var data []searchapp.RawItem itemSet := make(map[string]bool) - m.s.lock.Lock() - defer m.s.lock.Unlock() for _, rec := range m.s.cliRecords { + if shouldCancel(ctx) { + timeEnd := time.Now() + sugar.Debugw("Update got canceled", + "duration", timeEnd.Sub(timeStart), + ) + return + } itm, err := searchapp.NewRawItemFromRecordForQuery(rec, query, m.config.Debug) if err != nil { // records didn't match the query @@ -328,6 +354,8 @@ func (m manager) UpdateRawData(input string) { sort.SliceStable(data, func(p, q int) bool { return data[p].Score > data[q].Score }) + m.s.lock.Lock() + defer m.s.lock.Unlock() m.s.rawData = nil for _, itm := range data { if len(m.s.rawData) > 420 { @@ -336,18 +364,52 @@ func (m manager) UpdateRawData(input string) { m.s.rawData = append(m.s.rawData, itm) } m.s.highlightedItem = 0 + timeEnd := time.Now() sugar.Debugw("Done with RAW data update", + "duration", timeEnd.Sub(timeStart), "recordCount", len(m.s.cliRecords), "itemCount", len(m.s.data), ) } -func (m manager) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { - gocui.DefaultEditor.Edit(v, key, ch, mod) + +func shouldCancel(ctx context.Context) bool { + select { + case <-ctx.Done(): + return true + default: + return false + } +} + +func (m manager) getCtxAndCancel() context.Context { + m.s.lock.Lock() + defer m.s.lock.Unlock() + if m.s.cancelUpdate != nil { + (*m.s.cancelUpdate)() + } + ctx, cancel := context.WithCancel(context.Background()) + m.s.cancelUpdate = &cancel + return ctx +} + +func (m manager) update(input string) { + ctx := m.getCtxAndCancel() if m.s.rawMode { - m.UpdateRawData(v.Buffer()) - return + m.updateRawData(ctx, input) + } else { + m.updateData(ctx, input) } - m.UpdateData(v.Buffer()) + m.flush() +} + +func (m manager) flush() { + f := func(_ *gocui.Gui) error { return nil } + m.s.gui.Update(f) +} + +func (m manager) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { + gocui.DefaultEditor.Edit(v, key, ch, mod) + go m.update(v.Buffer()) } func (m manager) Next(g *gocui.Gui, v *gocui.View) error { @@ -373,11 +435,7 @@ func (m manager) SwitchModes(g *gocui.Gui, v *gocui.View) error { m.s.rawMode = !m.s.rawMode m.s.lock.Unlock() - if m.s.rawMode { - m.UpdateRawData(v.Buffer()) - return nil - } - m.UpdateData(v.Buffer()) + go m.update(v.Buffer()) return nil } diff --git a/cmd/control/cmd/update.go b/cmd/control/cmd/update.go index c74c555b..1d35dd86 100644 --- a/cmd/control/cmd/update.go +++ b/cmd/control/cmd/update.go @@ -23,6 +23,7 @@ var updateCmd = &cobra.Command{ execArgs = append(execArgs, "--beta") } execCmd := exec.Command("bash", execArgs...) + execCmd.Stdin = os.Stdin execCmd.Stdout = os.Stdout execCmd.Stderr = os.Stderr err = execCmd.Run() diff --git a/internal/device/device.go b/internal/device/device.go index 7c04f6fc..d5bb13b2 100644 --- a/internal/device/device.go +++ b/internal/device/device.go @@ -130,7 +130,7 @@ func promptForName(out *output.Output, fpath string) (string, error) { fmt.Printf("\nChoose a short name for this device (default: '%s'): ", hostStub) input, err := reader.ReadString('\n') name := strings.TrimRight(input, "\n") - if err != nil { + if err != nil && err.Error() != "EOF" { return "", fmt.Errorf("reader error: %w", err) } if name == "" { diff --git a/scripts/install.sh b/scripts/install.sh index 83569b87..d3a7bc59 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -88,7 +88,7 @@ if [ -z "${__RESH_VERSION-}" ]; then # But don't output anything ./scripts/resh-daemon-stop.sh -q ||: else - ./scripts/resh-daemon-stop.sh + ./scripts/resh-daemon-stop.sh ||: fi echo "Installing ..." diff --git a/scripts/rawinstall.sh b/scripts/rawinstall.sh index bfcffc28..33fad5fd 100755 --- a/scripts/rawinstall.sh +++ b/scripts/rawinstall.sh @@ -136,13 +136,18 @@ echo " * OK" if ! scripts/install.sh; then if [ $? != 130 ]; then echo - echo "INSTALLATION FAILED!" - echo "I'm sorry for the inconvenience." + printf '\e[31;1m' # red color on + printf '┌────────────────────────────┐\n' + printf '│ │\n' + printf '│ INSTALLATION FAILED! │\n' + printf '│ │\n' + printf '└────────────────────────────┘\n' + printf '\e[0m' # reset echo echo "Please create an issue: https://github.com/curusarn/resh/issues" fi echo - echo "You can rerun the installation by executing: (this will skip downloading)" + echo "Rerun the installation and skip downloading by running:" echo echo "cd $PWD && scripts/install.sh" echo diff --git a/troubleshooting.md b/troubleshooting.md index 60a45de0..e2cc1bbd 100644 --- a/troubleshooting.md +++ b/troubleshooting.md @@ -28,7 +28,7 @@ Your RESH history is saved in one of: - `~/.local/share/resh/history.reshjson` - `$XDG_DATA_HOME/resh/history.reshjson` -The format is JSON prefixed by version. Display it as json using: +Each line is one JSON record prefixed by version. Display it as JSON using: ```sh cat ~/.local/share/resh/history.reshjson | sed 's/^v[^{]*{/{/' | jq . @@ -47,7 +47,7 @@ RESH config is read from one of: Logs can be useful for troubleshooting issues. Find RESH logs in one of: -- `~/.local/share//resh/log.json` +- `~/.local/share/resh/log.json` - `$XDG_DATA_HOME/resh/log.json` ### Log verbosity