work around bash-preexec issues, load history on init

bash-preexec doesn't get along with `bind -x` 'widgets'

history gets loaded from file on daemon start
recent history is used to populate session history on session start

add some metadata about recalls
pull/18/head
Simon Let 6 years ago
parent ca660a0e9b
commit 1ef128895b
  1. 2
      VERSION
  2. 8
      cmd/collect/main.go
  3. 4
      cmd/daemon/main.go
  4. 8
      cmd/daemon/run-server.go
  5. 1
      conf/config.toml
  6. 1
      pkg/cfg/cfg.go
  7. 75
      pkg/histfile/histfile.go
  8. 57
      pkg/records/records.go
  9. 45
      pkg/sesshist/sesshist.go
  10. 8
      scripts/hooks.sh
  11. 4
      scripts/reshctl.sh
  12. 29
      scripts/widgets.sh

@ -1 +1 @@
1.1.6 1.2.1

@ -56,6 +56,7 @@ func main() {
// recall metadata // recall metadata
recallActions := flag.String("recall-actions", "", "recall actions that took place before executing the command") recallActions := flag.String("recall-actions", "", "recall actions that took place before executing the command")
recallStrategy := flag.String("recall-strategy", "", "recall strategy used during recall actions")
// posix variables // posix variables
cols := flag.String("cols", "-1", "$COLUMNS") cols := flag.String("cols", "-1", "$COLUMNS")
@ -124,10 +125,6 @@ func main() {
")") ")")
os.Exit(3) os.Exit(3)
} }
if *recallHistno != 0 && *recall == false {
log.Println("Option '--recall' only works with '--histno' option - exiting!")
os.Exit(4)
}
if *recallPrefix != "" && *recall == false { if *recallPrefix != "" && *recall == false {
log.Println("Option '--prefix-search' only works with '--recall' option - exiting!") log.Println("Option '--prefix-search' only works with '--recall' option - exiting!")
os.Exit(4) os.Exit(4)
@ -232,8 +229,9 @@ func main() {
ReshVersion: Version, ReshVersion: Version,
ReshRevision: Revision, ReshRevision: Revision,
RecallActions: []string{*recallActions}, RecallActionsRaw: *recallActions,
RecallPrefix: *recallPrefix, RecallPrefix: *recallPrefix,
RecallStrategy: *recallStrategy,
}, },
} }
if *recall { if *recall {

@ -31,7 +31,7 @@ func main() {
dir := usr.HomeDir dir := usr.HomeDir
pidfilePath := filepath.Join(dir, ".resh/resh.pid") pidfilePath := filepath.Join(dir, ".resh/resh.pid")
configPath := filepath.Join(dir, ".config/resh.toml") configPath := filepath.Join(dir, ".config/resh.toml")
outputPath := filepath.Join(dir, ".resh_history.json") historyPath := filepath.Join(dir, ".resh_history.json")
logPath := filepath.Join(dir, ".resh/daemon.log") logPath := filepath.Join(dir, ".resh/daemon.log")
f, err := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) f, err := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
@ -69,7 +69,7 @@ func main() {
if err != nil { if err != nil {
log.Fatal("Could not create pidfile", err) log.Fatal("Could not create pidfile", err)
} }
runServer(config, outputPath) runServer(config, historyPath)
err = os.Remove(pidfilePath) err = os.Remove(pidfilePath)
if err != nil { if err != nil {
log.Println("Could not delete pidfile", err) log.Println("Could not delete pidfile", err)

@ -11,7 +11,7 @@ import (
"github.com/curusarn/resh/pkg/sesswatch" "github.com/curusarn/resh/pkg/sesswatch"
) )
func runServer(config cfg.Config, outputPath string) { func runServer(config cfg.Config, historyPath string) {
var recordSubscribers []chan records.Record var recordSubscribers []chan records.Record
var sessionInitSubscribers []chan records.Record var sessionInitSubscribers []chan records.Record
var sessionDropSubscribers []chan string var sessionDropSubscribers []chan string
@ -23,14 +23,16 @@ func runServer(config cfg.Config, outputPath string) {
sessionDropSubscribers = append(sessionDropSubscribers, sesshistSessionsToDrop) sessionDropSubscribers = append(sessionDropSubscribers, sesshistSessionsToDrop)
sesshistRecords := make(chan records.Record) sesshistRecords := make(chan records.Record)
recordSubscribers = append(recordSubscribers, sesshistRecords) recordSubscribers = append(recordSubscribers, sesshistRecords)
sesshistDispatch := sesshist.NewDispatch(sesshistSessionsToInit, sesshistSessionsToDrop, sesshistRecords)
// histfile // histfile
histfileRecords := make(chan records.Record) histfileRecords := make(chan records.Record)
recordSubscribers = append(recordSubscribers, histfileRecords) recordSubscribers = append(recordSubscribers, histfileRecords)
histfileSessionsToDrop := make(chan string) histfileSessionsToDrop := make(chan string)
sessionDropSubscribers = append(sessionDropSubscribers, histfileSessionsToDrop) sessionDropSubscribers = append(sessionDropSubscribers, histfileSessionsToDrop)
histfile.Go(histfileRecords, outputPath, histfileSessionsToDrop) histfileBox := histfile.New(histfileRecords, historyPath, 10000, histfileSessionsToDrop)
// sesshist New
sesshistDispatch := sesshist.NewDispatch(sesshistSessionsToInit, sesshistSessionsToDrop, sesshistRecords, histfileBox, config.SesshistInitHistorySize)
// sesswatch // sesswatch
sesswatchSessionsToWatch := make(chan records.Record) sesswatchSessionsToWatch := make(chan records.Record)

@ -1,2 +1,3 @@
port = 2627 port = 2627
sesswatchPeriodSeconds = 120 sesswatchPeriodSeconds = 120
sesshistInitHistorySize = 1000

@ -4,4 +4,5 @@ package cfg
type Config struct { type Config struct {
Port int Port int
SesswatchPeriodSeconds uint SesswatchPeriodSeconds uint
SesshistInitHistorySize int
} }

@ -9,31 +9,49 @@ import (
"github.com/curusarn/resh/pkg/records" "github.com/curusarn/resh/pkg/records"
) )
type histfile struct { // Histfile writes records to histfile
mutex sync.Mutex type Histfile struct {
sessionsMutex sync.Mutex
sessions map[string]records.Record sessions map[string]records.Record
outputPath string historyPath string
recentMutex sync.Mutex
recentRecords []records.Record
recentCmdLines []string // deduplicated
cmdLinesLastIndex map[string]int
} }
// Go creates histfile and runs two gorutines on it // New creates new histfile and runs two gorutines on it
func Go(input chan records.Record, outputPath string, sessionsToDrop chan string) { func New(input chan records.Record, historyPath string, initHistSize int, sessionsToDrop chan string) *Histfile {
hf := histfile{sessions: map[string]records.Record{}, outputPath: outputPath} hf := Histfile{
sessions: map[string]records.Record{},
historyPath: historyPath,
cmdLinesLastIndex: map[string]int{},
}
go hf.loadHistory(initHistSize)
go hf.writer(input) go hf.writer(input)
go hf.sessionGC(sessionsToDrop) go hf.sessionGC(sessionsToDrop)
return &hf
}
func (h *Histfile) loadHistory(initHistSize int) {
h.recentMutex.Lock()
defer h.recentMutex.Unlock()
h.recentCmdLines = records.LoadCmdLinesFromFile(h.historyPath, initHistSize)
} }
// sessionGC reads sessionIDs from channel and deletes them from histfile struct // sessionGC reads sessionIDs from channel and deletes them from histfile struct
func (h *histfile) sessionGC(sessionsToDrop chan string) { func (h *Histfile) sessionGC(sessionsToDrop chan string) {
for { for {
func() { func() {
session := <-sessionsToDrop session := <-sessionsToDrop
log.Println("histfile: got session to drop", session) log.Println("histfile: got session to drop", session)
h.mutex.Lock() h.sessionsMutex.Lock()
defer h.mutex.Unlock() defer h.sessionsMutex.Unlock()
if part1, found := h.sessions[session]; found == true { if part1, found := h.sessions[session]; found == true {
log.Println("histfile: Dropping session:", session) log.Println("histfile: Dropping session:", session)
delete(h.sessions, session) delete(h.sessions, session)
go writeRecord(part1, h.outputPath) go writeRecord(part1, h.historyPath)
} else { } else {
log.Println("histfile: No hanging parts for session:", session) log.Println("histfile: No hanging parts for session:", session)
} }
@ -42,38 +60,52 @@ func (h *histfile) sessionGC(sessionsToDrop chan string) {
} }
// writer reads records from channel, merges them and writes them to file // writer reads records from channel, merges them and writes them to file
func (h *histfile) writer(input chan records.Record) { func (h *Histfile) writer(input chan records.Record) {
for { for {
func() { func() {
record := <-input record := <-input
h.mutex.Lock() h.sessionsMutex.Lock()
defer h.mutex.Unlock() defer h.sessionsMutex.Unlock()
if record.PartOne { if record.PartOne {
if _, found := h.sessions[record.SessionID]; found { if _, found := h.sessions[record.SessionID]; found {
log.Println("histfile ERROR: Got another first part of the records before merging the previous one - overwriting!") log.Println("histfile WARN: Got another first part of the records before merging the previous one - overwriting! " +
"(this happens in bash because bash-preexec runs when it's not supposed to)")
} }
h.sessions[record.SessionID] = record h.sessions[record.SessionID] = record
} else { } else {
part1, found := h.sessions[record.SessionID] if part1, found := h.sessions[record.SessionID]; found == false {
if found == false {
log.Println("histfile ERROR: Got second part of records and nothing to merge it with - ignoring!") log.Println("histfile ERROR: Got second part of records and nothing to merge it with - ignoring!")
} else { } else {
delete(h.sessions, record.SessionID) delete(h.sessions, record.SessionID)
go mergeAndWriteRecord(part1, record, h.outputPath) go h.mergeAndWriteRecord(part1, record)
} }
} }
}() }()
} }
} }
func mergeAndWriteRecord(part1, part2 records.Record, outputPath string) { func (h *Histfile) mergeAndWriteRecord(part1, part2 records.Record) {
err := part1.Merge(part2) err := part1.Merge(part2)
if err != nil { if err != nil {
log.Println("Error while merging", err) log.Println("Error while merging", err)
return return
} }
writeRecord(part1, outputPath)
func() {
h.recentMutex.Lock()
defer h.recentMutex.Unlock()
h.recentRecords = append(h.recentRecords, part1)
cmdLine := part1.CmdLine
idx, found := h.cmdLinesLastIndex[cmdLine]
if found {
h.recentCmdLines = append(h.recentCmdLines[:idx], h.recentCmdLines[idx+1:]...)
}
h.cmdLinesLastIndex[cmdLine] = len(h.recentCmdLines)
h.recentCmdLines = append(h.recentCmdLines, cmdLine)
}()
writeRecord(part1, h.historyPath)
} }
func writeRecord(rec records.Record, outputPath string) { func writeRecord(rec records.Record, outputPath string) {
@ -95,3 +127,8 @@ func writeRecord(rec records.Record, outputPath string) {
return return
} }
} }
// GetRecentCmdLines returns recent cmdLines
func (h *Histfile) GetRecentCmdLines(limit int) []string {
return h.recentCmdLines
}

@ -1,10 +1,12 @@
package records package records
import ( import (
"bufio"
"encoding/json" "encoding/json"
"errors" "errors"
"log" "log"
"math" "math"
"os"
"strconv" "strconv"
"strings" "strings"
@ -84,6 +86,7 @@ type BaseRecord struct {
Recalled bool `json:"recalled"` Recalled bool `json:"recalled"`
RecallHistno int `json:"recallHistno,omitempty"` RecallHistno int `json:"recallHistno,omitempty"`
RecallStrategy string `json:"recallStrategy,omitempty"` RecallStrategy string `json:"recallStrategy,omitempty"`
RecallActionsRaw string `json:"recallActionsRaw,omitempty"`
RecallActions []string `json:"recallActions,omitempty"` RecallActions []string `json:"recallActions,omitempty"`
// recall command // recall command
@ -177,11 +180,10 @@ func (r *Record) Merge(r2 Record) error {
if r.SessionID != r2.SessionID { if r.SessionID != r2.SessionID {
return errors.New("Records to merge are not from the same sesion - r1:" + r.SessionID + " r2:" + r2.SessionID) return errors.New("Records to merge are not from the same sesion - r1:" + r.SessionID + " r2:" + r2.SessionID)
} }
if r.CmdLine != r2.CmdLine || r.RealtimeBefore != r2.RealtimeBefore { if r.CmdLine != r2.CmdLine {
return errors.New("Records to merge are not parts of the same records - r1:" + return errors.New("Records to merge are not parts of the same records - r1:" + r.CmdLine + " r2:" + r2.CmdLine)
r.CmdLine + "(" + strconv.FormatFloat(r.RealtimeBefore, 'f', -1, 64) + ") r2:" +
r2.CmdLine + "(" + strconv.FormatFloat(r2.RealtimeBefore, 'f', -1, 64) + ")")
} }
// r.RealtimeBefore != r2.RealtimeBefore - can't be used because of bash-preexec runs when it's not supposed to
r.ExitCode = r2.ExitCode r.ExitCode = r2.ExitCode
r.PwdAfter = r2.PwdAfter r.PwdAfter = r2.PwdAfter
r.RealPwdAfter = r2.RealPwdAfter r.RealPwdAfter = r2.RealPwdAfter
@ -436,3 +438,50 @@ func (r *EnrichedRecord) DistanceTo(r2 EnrichedRecord, p DistParams) float64 {
return dist return dist
} }
// LoadCmdLinesFromFile loads limit cmdlines from file
func LoadCmdLinesFromFile(fname string, limit int) []string {
recs := LoadFromFile(fname, limit*3) // assume that at least 1/3 of commands is unique
var cmdLines []string
cmdLinesSet := map[string]bool{}
for i := len(recs) - 1; i >= 0; i-- {
cmdLine := recs[i].CmdLine
if cmdLinesSet[cmdLine] {
continue
}
cmdLinesSet[cmdLine] = true
cmdLines = append([]string{cmdLine}, cmdLines...)
if len(cmdLines) > limit {
break
}
}
return cmdLines
}
// LoadFromFile loads at most 'limit' records from 'fname' file
func LoadFromFile(fname string, limit int) []Record {
file, err := os.Open(fname)
if err != nil {
log.Fatal("Open() resh history file error:", err)
}
defer file.Close()
var recs []Record
scanner := bufio.NewScanner(file)
for scanner.Scan() {
record := Record{}
fallbackRecord := FallbackRecord{}
line := scanner.Text()
err = json.Unmarshal([]byte(line), &record)
if err != nil {
err = json.Unmarshal([]byte(line), &fallbackRecord)
if err != nil {
log.Println("Line:", line)
log.Fatal("Decoding error:", err)
}
record = Convert(&fallbackRecord)
}
recs = append(recs, record)
}
return recs
}

@ -7,6 +7,7 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/curusarn/resh/pkg/histfile"
"github.com/curusarn/resh/pkg/records" "github.com/curusarn/resh/pkg/records"
) )
@ -14,11 +15,20 @@ import (
type Dispatch struct { type Dispatch struct {
sessions map[string]*sesshist sessions map[string]*sesshist
mutex sync.RWMutex mutex sync.RWMutex
history *histfile.Histfile
historyInitSize int
} }
// NewDispatch creates a new sesshist.Dispatch and starts necessary gorutines // NewDispatch creates a new sesshist.Dispatch and starts necessary gorutines
func NewDispatch(sessionsToInit chan records.Record, sessionsToDrop chan string, recordsToAdd chan records.Record) *Dispatch { func NewDispatch(sessionsToInit chan records.Record, sessionsToDrop chan string,
s := Dispatch{sessions: map[string]*sesshist{}} recordsToAdd chan records.Record, history *histfile.Histfile, historyInitSize int) *Dispatch {
s := Dispatch{
sessions: map[string]*sesshist{},
history: history,
historyInitSize: historyInitSize,
}
go s.sessionInitializer(sessionsToInit) go s.sessionInitializer(sessionsToInit)
go s.sessionDropper(sessionsToDrop) go s.sessionDropper(sessionsToDrop)
go s.recordAdder(recordsToAdd) go s.recordAdder(recordsToAdd)
@ -54,6 +64,7 @@ func (s *Dispatch) recordAdder(recordsToAdd chan records.Record) {
// InitSession struct // InitSession struct
func (s *Dispatch) initSession(sessionID string) error { func (s *Dispatch) initSession(sessionID string) error {
log.Println("sesshist: initializing session - " + sessionID)
s.mutex.RLock() s.mutex.RLock()
_, found := s.sessions[sessionID] _, found := s.sessions[sessionID]
s.mutex.RUnlock() s.mutex.RUnlock()
@ -62,9 +73,17 @@ func (s *Dispatch) initSession(sessionID string) error {
return errors.New("sesshist ERROR: Can't INIT already existing session " + sessionID) return errors.New("sesshist ERROR: Can't INIT already existing session " + sessionID)
} }
log.Println("sesshist: loading history to populate session - " + sessionID)
historyCmdLines := s.history.GetRecentCmdLines(s.historyInitSize)
s.mutex.Lock() s.mutex.Lock()
defer s.mutex.Unlock() defer s.mutex.Unlock()
s.sessions[sessionID] = &sesshist{} // init sesshist and populate it with history loaded from file
s.sessions[sessionID] = &sesshist{
recentCmdLines: historyCmdLines,
cmdLinesLastIndex: map[string]int{},
}
log.Println("sesshist: session init done - " + sessionID)
return nil return nil
} }
@ -91,19 +110,22 @@ func (s *Dispatch) addRecentRecord(sessionID string, record records.Record) erro
s.mutex.RUnlock() s.mutex.RUnlock()
if found == false { if found == false {
return errors.New("sesshist ERROR: No session history for SessionID " + sessionID + " (should we create one?)") log.Println("sesshist ERROR: addRecontRecord(): No session history for SessionID " + sessionID + " - creating session history.")
s.initSession(sessionID)
return s.addRecentRecord(sessionID, record)
} }
session.mutex.Lock() session.mutex.Lock()
defer session.mutex.Unlock() defer session.mutex.Unlock()
session.recentRecords = append(session.recentRecords, record) session.recentRecords = append(session.recentRecords, record)
// remove previous occurance of record // remove previous occurance of record
for i := len(session.recentCmdLines) - 1; i >= 0; i-- { cmdLine := record.CmdLine
if session.recentCmdLines[i] == record.CmdLine { idx, found := session.cmdLinesLastIndex[cmdLine]
session.recentCmdLines = append(session.recentCmdLines[:i], session.recentCmdLines[i+1:]...) if found {
} session.recentCmdLines = append(session.recentCmdLines[:idx], session.recentCmdLines[idx+1:]...)
} }
session.cmdLinesLastIndex[cmdLine] = len(session.recentCmdLines)
// append new record // append new record
session.recentCmdLines = append(session.recentCmdLines, record.CmdLine) session.recentCmdLines = append(session.recentCmdLines, cmdLine)
log.Println("sesshist: record:", record.CmdLine, "; added to session:", sessionID, log.Println("sesshist: record:", record.CmdLine, "; added to session:", sessionID,
"; session len:", len(session.recentCmdLines), "; session len w/ dups:", len(session.recentRecords)) "; session len:", len(session.recentCmdLines), "; session len w/ dups:", len(session.recentRecords))
return nil return nil
@ -116,7 +138,8 @@ func (s *Dispatch) Recall(sessionID string, histno int, prefix string) (string,
s.mutex.RUnlock() s.mutex.RUnlock()
if found == false { if found == false {
return "", errors.New("sesshist ERROR: No session history for SessionID " + sessionID) // go s.initSession(sessionID)
return "", errors.New("sesshist ERROR: No session history for SessionID " + sessionID + " - should we create one?")
} }
if prefix == "" { if prefix == "" {
session.mutex.Lock() session.mutex.Lock()
@ -131,7 +154,7 @@ func (s *Dispatch) Recall(sessionID string, histno int, prefix string) (string,
type sesshist struct { type sesshist struct {
recentRecords []records.Record recentRecords []records.Record
recentCmdLines []string // deduplicated recentCmdLines []string // deduplicated
// cmdLines map[string]int cmdLinesLastIndex map[string]int
mutex sync.Mutex mutex sync.Mutex
} }

@ -5,16 +5,17 @@ __resh_reset_variables() {
__RESH_HIST_PREV_LINE="" __RESH_HIST_PREV_LINE=""
__RESH_HIST_RECALL_ACTIONS="" __RESH_HIST_RECALL_ACTIONS=""
__RESH_HIST_NO_PREFIX_MODE=0 __RESH_HIST_NO_PREFIX_MODE=0
__RESH_HIST_RECALL_STRATEGY=""
} }
__resh_preexec() { __resh_preexec() {
# core # core
__RESH_COLLECT=1 __RESH_COLLECT=1
__RESH_CMDLINE="$1" # not local to preserve it for postcollect (useful as sanity check) __RESH_CMDLINE="$1" # not local to preserve it for postcollect (useful as sanity check)
__resh_collect --cmdLine "$__RESH_CMDLINE" --recall-actions "$__RESH_HIST_RECALL_ACTIONS" \ __resh_collect --cmdLine "$__RESH_CMDLINE" \
--recall-actions "$__RESH_HIST_RECALL_ACTIONS" \
--recall-strategy "$__RESH_HIST_RECALL_STRATEGY" \
&>~/.resh/collect_last_run_out.txt || echo "resh-collect ERROR: $(head -n 1 ~/.resh/collect_last_run_out.txt)" &>~/.resh/collect_last_run_out.txt || echo "resh-collect ERROR: $(head -n 1 ~/.resh/collect_last_run_out.txt)"
__resh_reset_variables
} }
# used for collect and collect --recall # used for collect and collect --recall
@ -152,6 +153,7 @@ __resh_precmd() {
-timezoneAfter "$__RESH_TZ_AFTER" \ -timezoneAfter "$__RESH_TZ_AFTER" \
&>~/.resh/postcollect_last_run_out.txt || echo "resh-postcollect ERROR: $(head -n 1 ~/.resh/postcollect_last_run_out.txt)" &>~/.resh/postcollect_last_run_out.txt || echo "resh-postcollect ERROR: $(head -n 1 ~/.resh/postcollect_last_run_out.txt)"
fi fi
__resh_reset_variables
fi fi
unset __RESH_COLLECT unset __RESH_COLLECT
} }

@ -5,8 +5,8 @@
. ~/.resh/widgets.sh . ~/.resh/widgets.sh
__resh_bind_arrows() { __resh_bind_arrows() {
bindfunc '\C-k' __resh_widget_arrow_up_compat bindfunc '\e[A' __resh_widget_arrow_up_compat
bindfunc '\C-j' __resh_widget_arrow_down_compat bindfunc '\e[B' __resh_widget_arrow_down_compat
return 0 return 0
} }

@ -3,16 +3,21 @@
. ~/.resh/hooks.sh . ~/.resh/hooks.sh
__resh_helper_arrow_pre() { __resh_helper_arrow_pre() {
# this is a very bad workaround
# force bash-preexec to run repeatedly because otherwise premature run of bash-preexec overshadows the next poper run
# I honestly think that it's impossible to make widgets work in bash without hacks like this
# shellcheck disable=2034
__bp_preexec_interactive_mode="on"
# set recall strategy
__RESH_HIST_RECALL_STRATEGY="bash_recent - history-search-{backward,forward}"
# set prefix # set prefix
__RESH_PREFIX=${BUFFER:0:CURSOR} __RESH_PREFIX=${BUFFER:0:$CURSOR}
# cursor not at the end of the line => end "NO_PREFIX_MODE" # cursor not at the end of the line => end "NO_PREFIX_MODE"
[ "$CURSOR" -ne "${#BUFFER}" ] && __RESH_HIST_NO_PREFIX_MODE=0 [ "$CURSOR" -ne "${#BUFFER}" ] && __RESH_HIST_NO_PREFIX_MODE=0
# if user made any edits from last recall action => restart histno AND deactivate "NO_PREFIX_MODE" # if user made any edits from last recall action => restart histno AND deactivate "NO_PREFIX_MODE"
[ "$BUFFER" != "$__RESH_HIST_PREV_LINE" ] && __RESH_HISTNO=0 && __RESH_HIST_NO_PREFIX_MODE=0 [ "$BUFFER" != "$__RESH_HIST_PREV_LINE" ] && __RESH_HISTNO=0 && __RESH_HIST_NO_PREFIX_MODE=0
# "NO_PREFIX_MODE" => set prefix to empty string # "NO_PREFIX_MODE" => set prefix to empty string
[ "$__RESH_HIST_NO_PREFIX_MODE" -eq 1 ] && __RESH_PREFIX="" [ "$__RESH_HIST_NO_PREFIX_MODE" -eq 1 ] && __RESH_PREFIX=""
# append curent recall action
__RESH_HIST_RECALL_ACTIONS="$__RESH_HIST_RECALL_ACTIONS;arrow_up:$__RESH_PREFIX"
# histno == 0 => save current line # histno == 0 => save current line
[ $__RESH_HISTNO -eq 0 ] && __RESH_HISTNO_ZERO_LINE=$BUFFER [ $__RESH_HISTNO -eq 0 ] && __RESH_HISTNO_ZERO_LINE=$BUFFER
} }
@ -28,18 +33,20 @@ __resh_helper_arrow_post() {
__resh_widget_arrow_up() { __resh_widget_arrow_up() {
# run helper function # run helper function
__resh_helper_arrow_pre __resh_helper_arrow_pre
# append curent recall action
__RESH_HIST_RECALL_ACTIONS="$__RESH_HIST_RECALL_ACTIONS;arrow_up:$__RESH_PREFIX"
# increment histno # increment histno
(( __RESH_HISTNO++ )) __RESH_HISTNO=$((__RESH_HISTNO+1))
# back at histno == 0 => restore original line # back at histno == 0 => restore original line
if [ $__RESH_HISTNO -eq 0 ]; then if [ $__RESH_HISTNO -eq 0 ]; then
BUFFER=$__RESH_HISTNO_ZERO_LINE BUFFER=$__RESH_HISTNO_ZERO_LINE
else else
# run recall # run recall
local NEW_BUFFER local NEW_BUFFER
NEW_BUFFER="$(__resh_collect --recall --histno "$__RESH_HISTNO" --prefix-search "$__RESH_PREFIX" 2> ~/.resh/arrow_up_last_run_out.txt)" NEW_BUFFER="$(__resh_collect --recall --prefix-search "$__RESH_PREFIX" 2> ~/.resh/arrow_up_last_run_out.txt)"
# IF new buffer in non-empty THEN use the new buffer ELSE revert histno change # IF new buffer in non-empty THEN use the new buffer ELSE revert histno change
# shellcheck disable=SC2015 # shellcheck disable=SC2015
[ ${#NEW_BUFFER} -gt 0 ] && BUFFER=$NEW_BUFFER || (( __RESH_HISTNO-- )) [ ${#NEW_BUFFER} -gt 0 ] && BUFFER=$NEW_BUFFER || __RESH_HISTNO=$((__RESH_HISTNO-1))
fi fi
# run post helper # run post helper
__resh_helper_arrow_post __resh_helper_arrow_post
@ -47,8 +54,10 @@ __resh_widget_arrow_up() {
__resh_widget_arrow_down() { __resh_widget_arrow_down() {
# run helper function # run helper function
__resh_helper_arrow_pre __resh_helper_arrow_pre
# append curent recall action
__RESH_HIST_RECALL_ACTIONS="$__RESH_HIST_RECALL_ACTIONS;arrow_down:$__RESH_PREFIX"
# increment histno # increment histno
(( __RESH_HISTNO-- )) __RESH_HISTNO=$((__RESH_HISTNO-1))
# prevent HISTNO from getting negative (for now) # prevent HISTNO from getting negative (for now)
[ $__RESH_HISTNO -lt 0 ] && __RESH_HISTNO=0 [ $__RESH_HISTNO -lt 0 ] && __RESH_HISTNO=0
# back at histno == 0 => restore original line # back at histno == 0 => restore original line
@ -57,7 +66,7 @@ __resh_widget_arrow_down() {
else else
# run recall # run recall
local NEW_BUFFER local NEW_BUFFER
NEW_BUFFER="$(__resh_collect --recall --histno "$__RESH_HISTNO" --prefix-search "$__RESH_PREFIX" 2> ~/.resh/arrow_down_last_run_out.txt)" NEW_BUFFER="$(__resh_collect --recall --prefix-search "$__RESH_PREFIX" 2> ~/.resh/arrow_down_last_run_out.txt)"
# IF new buffer in non-empty THEN use the new buffer ELSE revert histno change # IF new buffer in non-empty THEN use the new buffer ELSE revert histno change
# shellcheck disable=SC2015 # shellcheck disable=SC2015
[ ${#NEW_BUFFER} -gt 0 ] && BUFFER=$NEW_BUFFER || (( __RESH_HISTNO++ )) [ ${#NEW_BUFFER} -gt 0 ] && BUFFER=$NEW_BUFFER || (( __RESH_HISTNO++ ))
@ -65,8 +74,8 @@ __resh_widget_arrow_down() {
__resh_helper_arrow_post __resh_helper_arrow_post
} }
__resh_widget_control_R() { __resh_widget_control_R() {
local __RESH_LBUFFER=${BUFFER:0:CURSOR} local __RESH_PREFIX=${BUFFER:0:CURSOR}
__RESH_HIST_RECALL_ACTIONS="$__RESH_HIST_RECALL_ACTIONS;control_R:$__RESH_LBUFFER" __RESH_HIST_RECALL_ACTIONS="$__RESH_HIST_RECALL_ACTIONS;control_R:$__RESH_PREFIX"
# resh-collect --hstr # resh-collect --hstr
hstr hstr
} }

Loading…
Cancel
Save