mirror of https://github.com/curusarn/resh
Merge pull request #18 from curusarn/arrow_widgets
Implement arrow widgets, add reshctlpull/30/head
commit
414a65ad36
@ -1,3 +1,6 @@ |
|||||||
[submodule "submodules/bash-preexec"] |
[submodule "submodules/bash-preexec"] |
||||||
path = submodules/bash-preexec |
path = submodules/bash-preexec |
||||||
url = https://github.com/rcaloras/bash-preexec.git |
url = https://github.com/rcaloras/bash-preexec.git |
||||||
|
[submodule "submodules/bash-zsh-compat-widgets"] |
||||||
|
path = submodules/bash-zsh-compat-widgets |
||||||
|
url = git@github.com:curusarn/bash-zsh-compat-widgets.git |
||||||
|
|||||||
@ -0,0 +1,48 @@ |
|||||||
|
package cmd |
||||||
|
|
||||||
|
import ( |
||||||
|
"os" |
||||||
|
|
||||||
|
"github.com/curusarn/resh/cmd/control/status" |
||||||
|
"github.com/spf13/cobra" |
||||||
|
) |
||||||
|
|
||||||
|
// completionCmd represents the completion command
|
||||||
|
var completionCmd = &cobra.Command{ |
||||||
|
Use: "completion", |
||||||
|
Short: "Generates bash/zsh completion scripts", |
||||||
|
Long: `To load completion run |
||||||
|
|
||||||
|
. <(reshctl completion bash)
|
||||||
|
|
||||||
|
OR
|
||||||
|
|
||||||
|
. <(reshctl completion zsh)
|
||||||
|
`, |
||||||
|
} |
||||||
|
|
||||||
|
var completionBashCmd = &cobra.Command{ |
||||||
|
Use: "bash", |
||||||
|
Short: "Generates bash completion scripts", |
||||||
|
Long: `To load completion run |
||||||
|
|
||||||
|
. <(reshctl completion bash)
|
||||||
|
`, |
||||||
|
Run: func(cmd *cobra.Command, args []string) { |
||||||
|
rootCmd.GenBashCompletion(os.Stdout) |
||||||
|
exitCode = status.Success |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
var completionZshCmd = &cobra.Command{ |
||||||
|
Use: "zsh", |
||||||
|
Short: "Generates zsh completion scripts", |
||||||
|
Long: `To load completion run |
||||||
|
|
||||||
|
. <(reshctl completion zsh)
|
||||||
|
`, |
||||||
|
Run: func(cmd *cobra.Command, args []string) { |
||||||
|
rootCmd.GenZshCompletion(os.Stdout) |
||||||
|
exitCode = status.Success |
||||||
|
}, |
||||||
|
} |
||||||
@ -0,0 +1,61 @@ |
|||||||
|
package cmd |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"io/ioutil" |
||||||
|
"os/user" |
||||||
|
"path/filepath" |
||||||
|
|
||||||
|
"github.com/curusarn/resh/cmd/control/status" |
||||||
|
"github.com/spf13/cobra" |
||||||
|
) |
||||||
|
|
||||||
|
var debugCmd = &cobra.Command{ |
||||||
|
Use: "debug", |
||||||
|
Short: "Debug utils for resh", |
||||||
|
Long: "Reloads resh rc files. Shows logs and output from last runs of resh", |
||||||
|
} |
||||||
|
|
||||||
|
var debugReloadCmd = &cobra.Command{ |
||||||
|
Use: "reload", |
||||||
|
Short: "Reload resh rc files", |
||||||
|
Long: "Reload resh rc files", |
||||||
|
Run: func(cmd *cobra.Command, args []string) { |
||||||
|
exitCode = status.ReloadRcFiles |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
var debugOutputCmd = &cobra.Command{ |
||||||
|
Use: "output", |
||||||
|
Short: "Shows output from last runs of resh", |
||||||
|
Long: "Shows output from last runs of resh", |
||||||
|
Run: func(cmd *cobra.Command, args []string) { |
||||||
|
files := []string{ |
||||||
|
"daemon_last_run_out.txt", |
||||||
|
"collect_last_run_out.txt", |
||||||
|
"postcollect_last_run_out.txt", |
||||||
|
"session_init_last_run_out.txt", |
||||||
|
"arrow_up_last_run_out.txt", |
||||||
|
"arrow_down_last_run_out.txt", |
||||||
|
} |
||||||
|
usr, _ := user.Current() |
||||||
|
dir := usr.HomeDir |
||||||
|
reshdir := filepath.Join(dir, ".resh") |
||||||
|
for _, fpath := range files { |
||||||
|
fpath := filepath.Join(reshdir, fpath) |
||||||
|
debugReadFile(fpath) |
||||||
|
} |
||||||
|
exitCode = status.Success |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
func debugReadFile(path string) { |
||||||
|
fmt.Println("============================================================") |
||||||
|
fmt.Println(" filepath:", path) |
||||||
|
fmt.Println("============================================================") |
||||||
|
dat, err := ioutil.ReadFile(path) |
||||||
|
if err != nil { |
||||||
|
fmt.Println("ERROR while reading file:", err) |
||||||
|
} |
||||||
|
fmt.Println(string(dat)) |
||||||
|
} |
||||||
@ -0,0 +1,24 @@ |
|||||||
|
package cmd |
||||||
|
|
||||||
|
import ( |
||||||
|
"github.com/curusarn/resh/cmd/control/status" |
||||||
|
"github.com/spf13/cobra" |
||||||
|
) |
||||||
|
|
||||||
|
var disableCmd = &cobra.Command{ |
||||||
|
Use: "disable", |
||||||
|
Short: "disable RESH features", |
||||||
|
Long: `Disables RESH bindings for arrows and C-R.`, |
||||||
|
Run: func(cmd *cobra.Command, args []string) { |
||||||
|
exitCode = status.DisableAll |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
// var disableRecallingCmd = &cobra.Command{
|
||||||
|
// Use: "keybind",
|
||||||
|
// Short: "Disables RESH bindings for arrows and C-R.",
|
||||||
|
// Long: `Disables RESH bindings for arrows and C-R.`,
|
||||||
|
// Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
// exitCode = status.DisableAll
|
||||||
|
// },
|
||||||
|
// }
|
||||||
@ -0,0 +1,24 @@ |
|||||||
|
package cmd |
||||||
|
|
||||||
|
import ( |
||||||
|
"github.com/curusarn/resh/cmd/control/status" |
||||||
|
"github.com/spf13/cobra" |
||||||
|
) |
||||||
|
|
||||||
|
var enableCmd = &cobra.Command{ |
||||||
|
Use: "enable", |
||||||
|
Short: "enable RESH features", |
||||||
|
Long: `Enables RESH bindings for arrows and C-R.`, |
||||||
|
Run: func(cmd *cobra.Command, args []string) { |
||||||
|
exitCode = status.EnableAll |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
// var enableRecallingCmd = &cobra.Command{
|
||||||
|
// Use: "keybind",
|
||||||
|
// Short: "Enables RESH bindings for arrows and C-R.",
|
||||||
|
// Long: `Enables RESH bindings for arrows and C-R.`,
|
||||||
|
// Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
// exitCode = status.EnableAll
|
||||||
|
// },
|
||||||
|
// }
|
||||||
@ -0,0 +1,38 @@ |
|||||||
|
package cmd |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
|
||||||
|
"github.com/curusarn/resh/cmd/control/status" |
||||||
|
"github.com/spf13/cobra" |
||||||
|
) |
||||||
|
|
||||||
|
var exitCode status.Code |
||||||
|
|
||||||
|
var rootCmd = &cobra.Command{ |
||||||
|
Use: "reshctl", |
||||||
|
Short: "Reshctl (RESH control) - enables you to enable/disable features and more.", |
||||||
|
Long: `Enables you to enable/disable RESH bindings for arrows and C-R.`, |
||||||
|
} |
||||||
|
|
||||||
|
// Execute reshctl
|
||||||
|
func Execute() status.Code { |
||||||
|
rootCmd.AddCommand(disableCmd) |
||||||
|
// disableCmd.AddCommand(disableRecallingCmd)
|
||||||
|
|
||||||
|
rootCmd.AddCommand(enableCmd) |
||||||
|
// enableCmd.AddCommand(enableRecallingCmd)
|
||||||
|
|
||||||
|
rootCmd.AddCommand(completionCmd) |
||||||
|
completionCmd.AddCommand(completionBashCmd) |
||||||
|
completionCmd.AddCommand(completionZshCmd) |
||||||
|
|
||||||
|
rootCmd.AddCommand(debugCmd) |
||||||
|
debugCmd.AddCommand(debugReloadCmd) |
||||||
|
debugCmd.AddCommand(debugOutputCmd) |
||||||
|
if err := rootCmd.Execute(); err != nil { |
||||||
|
fmt.Println(err) |
||||||
|
return status.Fail |
||||||
|
} |
||||||
|
return exitCode |
||||||
|
} |
||||||
@ -0,0 +1,17 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"os" |
||||||
|
|
||||||
|
"github.com/curusarn/resh/cmd/control/cmd" |
||||||
|
) |
||||||
|
|
||||||
|
// Version from git set during build
|
||||||
|
var Version string |
||||||
|
|
||||||
|
// Revision from git set during build
|
||||||
|
var Revision string |
||||||
|
|
||||||
|
func main() { |
||||||
|
os.Exit(int(cmd.Execute())) |
||||||
|
} |
||||||
@ -0,0 +1,17 @@ |
|||||||
|
package status |
||||||
|
|
||||||
|
// Code - exit code of the resh-control command
|
||||||
|
type Code int |
||||||
|
|
||||||
|
const ( |
||||||
|
// Success exit code
|
||||||
|
Success Code = 0 |
||||||
|
// Fail exit code
|
||||||
|
Fail = 1 |
||||||
|
// EnableAll exit code - tells reshctl() wrapper to enable_all
|
||||||
|
EnableAll = 100 |
||||||
|
// DisableAll exit code - tells reshctl() wrapper to disable_all
|
||||||
|
DisableAll = 110 |
||||||
|
// ReloadRcFiles exit code - tells reshctl() wrapper to reload shellrc resh file
|
||||||
|
ReloadRcFiles = 200 |
||||||
|
) |
||||||
@ -0,0 +1,48 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"io/ioutil" |
||||||
|
"log" |
||||||
|
"net/http" |
||||||
|
|
||||||
|
"github.com/curusarn/resh/pkg/collect" |
||||||
|
"github.com/curusarn/resh/pkg/records" |
||||||
|
"github.com/curusarn/resh/pkg/sesshist" |
||||||
|
) |
||||||
|
|
||||||
|
type recallHandler struct { |
||||||
|
sesshistDispatch *sesshist.Dispatch |
||||||
|
} |
||||||
|
|
||||||
|
func (h *recallHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
||||||
|
jsn, err := ioutil.ReadAll(r.Body) |
||||||
|
if err != nil { |
||||||
|
log.Println("Error reading the body", err) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
rec := records.Record{} |
||||||
|
err = json.Unmarshal(jsn, &rec) |
||||||
|
if err != nil { |
||||||
|
log.Println("Decoding error:", err) |
||||||
|
log.Println("Payload:", jsn) |
||||||
|
return |
||||||
|
} |
||||||
|
cmd, err := h.sesshistDispatch.Recall(rec.SessionID, rec.RecallHistno, rec.RecallPrefix) |
||||||
|
if err != nil { |
||||||
|
log.Println("/recall - sess id:", rec.SessionID, " - histno:", rec.RecallHistno, " -> ERROR") |
||||||
|
log.Println("Recall error:", err) |
||||||
|
return |
||||||
|
} |
||||||
|
resp := collect.SingleResponse{cmd} |
||||||
|
jsn, err = json.Marshal(&resp) |
||||||
|
if err != nil { |
||||||
|
log.Println("Encoding error:", err) |
||||||
|
log.Println("Response:", resp) |
||||||
|
return |
||||||
|
} |
||||||
|
log.Println(string(jsn)) |
||||||
|
w.Write(jsn) |
||||||
|
log.Println("/recall - sess id:", rec.SessionID, " - histno:", rec.RecallHistno, " -> ", cmd) |
||||||
|
} |
||||||
@ -0,0 +1,47 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"io/ioutil" |
||||||
|
"log" |
||||||
|
"net/http" |
||||||
|
|
||||||
|
"github.com/curusarn/resh/pkg/records" |
||||||
|
) |
||||||
|
|
||||||
|
type recordHandler struct { |
||||||
|
subscribers []chan records.Record |
||||||
|
} |
||||||
|
|
||||||
|
func (h *recordHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
||||||
|
w.Write([]byte("OK\n")) |
||||||
|
jsn, err := ioutil.ReadAll(r.Body) |
||||||
|
// run rest of the handler as goroutine to prevent any hangups
|
||||||
|
go func() { |
||||||
|
if err != nil { |
||||||
|
log.Println("Error reading the body", err) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
record := records.Record{} |
||||||
|
err = json.Unmarshal(jsn, &record) |
||||||
|
if err != nil { |
||||||
|
log.Println("Decoding error: ", err) |
||||||
|
log.Println("Payload: ", jsn) |
||||||
|
return |
||||||
|
} |
||||||
|
for _, sub := range h.subscribers { |
||||||
|
sub <- record |
||||||
|
} |
||||||
|
part := "2" |
||||||
|
if record.PartOne { |
||||||
|
part = "1" |
||||||
|
} |
||||||
|
log.Println("/record - ", record.CmdLine, " - part", part) |
||||||
|
}() |
||||||
|
|
||||||
|
// fmt.Println("cmd:", r.CmdLine)
|
||||||
|
// fmt.Println("pwd:", r.Pwd)
|
||||||
|
// fmt.Println("git:", r.GitWorkTree)
|
||||||
|
// fmt.Println("exit_code:", r.ExitCode)
|
||||||
|
} |
||||||
@ -0,0 +1,48 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"net/http" |
||||||
|
"strconv" |
||||||
|
|
||||||
|
"github.com/curusarn/resh/pkg/cfg" |
||||||
|
"github.com/curusarn/resh/pkg/histfile" |
||||||
|
"github.com/curusarn/resh/pkg/records" |
||||||
|
"github.com/curusarn/resh/pkg/sesshist" |
||||||
|
"github.com/curusarn/resh/pkg/sesswatch" |
||||||
|
) |
||||||
|
|
||||||
|
func runServer(config cfg.Config, historyPath string) { |
||||||
|
var recordSubscribers []chan records.Record |
||||||
|
var sessionInitSubscribers []chan records.Record |
||||||
|
var sessionDropSubscribers []chan string |
||||||
|
|
||||||
|
// sessshist
|
||||||
|
sesshistSessionsToInit := make(chan records.Record) |
||||||
|
sessionInitSubscribers = append(sessionInitSubscribers, sesshistSessionsToInit) |
||||||
|
sesshistSessionsToDrop := make(chan string) |
||||||
|
sessionDropSubscribers = append(sessionDropSubscribers, sesshistSessionsToDrop) |
||||||
|
sesshistRecords := make(chan records.Record) |
||||||
|
recordSubscribers = append(recordSubscribers, sesshistRecords) |
||||||
|
|
||||||
|
// histfile
|
||||||
|
histfileRecords := make(chan records.Record) |
||||||
|
recordSubscribers = append(recordSubscribers, histfileRecords) |
||||||
|
histfileSessionsToDrop := make(chan string) |
||||||
|
sessionDropSubscribers = append(sessionDropSubscribers, histfileSessionsToDrop) |
||||||
|
histfileBox := histfile.New(histfileRecords, historyPath, 10000, histfileSessionsToDrop) |
||||||
|
|
||||||
|
// sesshist New
|
||||||
|
sesshistDispatch := sesshist.NewDispatch(sesshistSessionsToInit, sesshistSessionsToDrop, sesshistRecords, histfileBox, config.SesshistInitHistorySize) |
||||||
|
|
||||||
|
// sesswatch
|
||||||
|
sesswatchSessionsToWatch := make(chan records.Record) |
||||||
|
sessionInitSubscribers = append(sessionInitSubscribers, sesswatchSessionsToWatch) |
||||||
|
sesswatch.Go(sesswatchSessionsToWatch, sessionDropSubscribers, config.SesswatchPeriodSeconds) |
||||||
|
|
||||||
|
// handlers
|
||||||
|
http.HandleFunc("/status", statusHandler) |
||||||
|
http.Handle("/record", &recordHandler{subscribers: recordSubscribers}) |
||||||
|
http.Handle("/session_init", &sessionInitHandler{subscribers: sessionInitSubscribers}) |
||||||
|
http.Handle("/recall", &recallHandler{sesshistDispatch: sesshistDispatch}) |
||||||
|
http.ListenAndServe(":"+strconv.Itoa(config.Port), nil) |
||||||
|
} |
||||||
@ -0,0 +1,38 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"io/ioutil" |
||||||
|
"log" |
||||||
|
"net/http" |
||||||
|
|
||||||
|
"github.com/curusarn/resh/pkg/records" |
||||||
|
) |
||||||
|
|
||||||
|
type sessionInitHandler struct { |
||||||
|
subscribers []chan records.Record |
||||||
|
} |
||||||
|
|
||||||
|
func (h *sessionInitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
||||||
|
w.Write([]byte("OK\n")) |
||||||
|
jsn, err := ioutil.ReadAll(r.Body) |
||||||
|
// run rest of the handler as goroutine to prevent any hangups
|
||||||
|
go func() { |
||||||
|
if err != nil { |
||||||
|
log.Println("Error reading the body", err) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
record := records.Record{} |
||||||
|
err = json.Unmarshal(jsn, &record) |
||||||
|
if err != nil { |
||||||
|
log.Println("Decoding error: ", err) |
||||||
|
log.Println("Payload: ", jsn) |
||||||
|
return |
||||||
|
} |
||||||
|
for _, sub := range h.subscribers { |
||||||
|
sub <- record |
||||||
|
} |
||||||
|
log.Println("/session_init - id:", record.SessionID, " - pid:", record.SessionPID) |
||||||
|
}() |
||||||
|
} |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import "fmt" |
||||||
|
|
||||||
|
func main() { |
||||||
|
fmt.Println("Hell world") |
||||||
|
} |
||||||
@ -0,0 +1,149 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"flag" |
||||||
|
"fmt" |
||||||
|
"log" |
||||||
|
"os" |
||||||
|
|
||||||
|
"github.com/BurntSushi/toml" |
||||||
|
"github.com/curusarn/resh/pkg/cfg" |
||||||
|
"github.com/curusarn/resh/pkg/collect" |
||||||
|
"github.com/curusarn/resh/pkg/records" |
||||||
|
|
||||||
|
// "os/exec"
|
||||||
|
"os/user" |
||||||
|
"path/filepath" |
||||||
|
"strconv" |
||||||
|
) |
||||||
|
|
||||||
|
// Version from git set during build
|
||||||
|
var Version string |
||||||
|
|
||||||
|
// Revision from git set during build
|
||||||
|
var Revision string |
||||||
|
|
||||||
|
func main() { |
||||||
|
usr, _ := user.Current() |
||||||
|
dir := usr.HomeDir |
||||||
|
configPath := filepath.Join(dir, "/.config/resh.toml") |
||||||
|
reshUUIDPath := filepath.Join(dir, "/.resh/resh-uuid") |
||||||
|
|
||||||
|
machineIDPath := "/etc/machine-id" |
||||||
|
|
||||||
|
var config cfg.Config |
||||||
|
if _, err := toml.DecodeFile(configPath, &config); err != nil { |
||||||
|
log.Fatal("Error reading config:", err) |
||||||
|
} |
||||||
|
showVersion := flag.Bool("version", false, "Show version and exit") |
||||||
|
showRevision := flag.Bool("revision", false, "Show git revision and exit") |
||||||
|
|
||||||
|
requireVersion := flag.String("requireVersion", "", "abort if version doesn't match") |
||||||
|
requireRevision := flag.String("requireRevision", "", "abort if revision doesn't match") |
||||||
|
|
||||||
|
cmdLine := flag.String("cmdLine", "", "command line") |
||||||
|
exitCode := flag.Int("exitCode", -1, "exit code") |
||||||
|
sessionID := flag.String("sessionId", "", "resh generated session id") |
||||||
|
shlvl := flag.Int("shlvl", -1, "$SHLVL") |
||||||
|
|
||||||
|
// posix variables
|
||||||
|
pwdAfter := flag.String("pwdAfter", "", "$PWD after command") |
||||||
|
|
||||||
|
// non-posix
|
||||||
|
// sessionPid := flag.Int("sessionPid", -1, "$$ at session start")
|
||||||
|
|
||||||
|
gitCdupAfter := flag.String("gitCdupAfter", "", "git rev-parse --show-cdup") |
||||||
|
gitRemoteAfter := flag.String("gitRemoteAfter", "", "git remote get-url origin") |
||||||
|
|
||||||
|
gitCdupExitCodeAfter := flag.Int("gitCdupExitCodeAfter", -1, "... $?") |
||||||
|
gitRemoteExitCodeAfter := flag.Int("gitRemoteExitCodeAfter", -1, "... $?") |
||||||
|
|
||||||
|
// before after
|
||||||
|
timezoneAfter := flag.String("timezoneAfter", "", "") |
||||||
|
|
||||||
|
rtb := flag.String("realtimeBefore", "-1", "before $EPOCHREALTIME") |
||||||
|
rta := flag.String("realtimeAfter", "-1", "after $EPOCHREALTIME") |
||||||
|
flag.Parse() |
||||||
|
|
||||||
|
if *showVersion == true { |
||||||
|
fmt.Println(Version) |
||||||
|
os.Exit(0) |
||||||
|
} |
||||||
|
if *showRevision == true { |
||||||
|
fmt.Println(Revision) |
||||||
|
os.Exit(0) |
||||||
|
} |
||||||
|
if *requireVersion != "" && *requireVersion != Version { |
||||||
|
fmt.Println("Please restart/reload this terminal session " + |
||||||
|
"(resh version: " + Version + |
||||||
|
"; resh version of this terminal session: " + *requireVersion + |
||||||
|
")") |
||||||
|
os.Exit(3) |
||||||
|
} |
||||||
|
if *requireRevision != "" && *requireRevision != Revision { |
||||||
|
fmt.Println("Please restart/reload this terminal session " + |
||||||
|
"(resh revision: " + Revision + |
||||||
|
"; resh revision of this terminal session: " + *requireRevision + |
||||||
|
")") |
||||||
|
os.Exit(3) |
||||||
|
} |
||||||
|
realtimeAfter, err := strconv.ParseFloat(*rta, 64) |
||||||
|
if err != nil { |
||||||
|
log.Fatal("Flag Parsing error (rta):", err) |
||||||
|
} |
||||||
|
realtimeBefore, err := strconv.ParseFloat(*rtb, 64) |
||||||
|
if err != nil { |
||||||
|
log.Fatal("Flag Parsing error (rtb):", err) |
||||||
|
} |
||||||
|
realtimeDuration := realtimeAfter - realtimeBefore |
||||||
|
|
||||||
|
timezoneAfterOffset := collect.GetTimezoneOffsetInSeconds(*timezoneAfter) |
||||||
|
realtimeAfterLocal := realtimeAfter + timezoneAfterOffset |
||||||
|
|
||||||
|
realPwdAfter, err := filepath.EvalSymlinks(*pwdAfter) |
||||||
|
if err != nil { |
||||||
|
log.Println("err while handling pwdAfter realpath:", err) |
||||||
|
realPwdAfter = "" |
||||||
|
} |
||||||
|
|
||||||
|
gitDirAfter, gitRealDirAfter := collect.GetGitDirs(*gitCdupAfter, *gitCdupExitCodeAfter, *pwdAfter) |
||||||
|
if *gitRemoteExitCodeAfter != 0 { |
||||||
|
*gitRemoteAfter = "" |
||||||
|
} |
||||||
|
|
||||||
|
rec := records.Record{ |
||||||
|
// core
|
||||||
|
BaseRecord: records.BaseRecord{ |
||||||
|
CmdLine: *cmdLine, |
||||||
|
ExitCode: *exitCode, |
||||||
|
SessionID: *sessionID, |
||||||
|
Shlvl: *shlvl, |
||||||
|
|
||||||
|
PwdAfter: *pwdAfter, |
||||||
|
|
||||||
|
// non-posix
|
||||||
|
RealPwdAfter: realPwdAfter, |
||||||
|
|
||||||
|
// before after
|
||||||
|
TimezoneAfter: *timezoneAfter, |
||||||
|
|
||||||
|
RealtimeBefore: realtimeBefore, |
||||||
|
RealtimeAfter: realtimeAfter, |
||||||
|
RealtimeAfterLocal: realtimeAfterLocal, |
||||||
|
|
||||||
|
RealtimeDuration: realtimeDuration, |
||||||
|
|
||||||
|
GitDirAfter: gitDirAfter, |
||||||
|
GitRealDirAfter: gitRealDirAfter, |
||||||
|
GitOriginRemoteAfter: *gitRemoteAfter, |
||||||
|
MachineID: collect.ReadFileContent(machineIDPath), |
||||||
|
|
||||||
|
PartOne: false, |
||||||
|
|
||||||
|
ReshUUID: collect.ReadFileContent(reshUUIDPath), |
||||||
|
ReshVersion: Version, |
||||||
|
ReshRevision: Revision, |
||||||
|
}, |
||||||
|
} |
||||||
|
collect.SendRecord(rec, strconv.Itoa(config.Port), "/record") |
||||||
|
} |
||||||
@ -0,0 +1,186 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"flag" |
||||||
|
"fmt" |
||||||
|
"log" |
||||||
|
"os" |
||||||
|
|
||||||
|
"github.com/BurntSushi/toml" |
||||||
|
"github.com/curusarn/resh/pkg/cfg" |
||||||
|
"github.com/curusarn/resh/pkg/collect" |
||||||
|
"github.com/curusarn/resh/pkg/records" |
||||||
|
|
||||||
|
"os/user" |
||||||
|
"path/filepath" |
||||||
|
"strconv" |
||||||
|
) |
||||||
|
|
||||||
|
// Version from git set during build
|
||||||
|
var Version string |
||||||
|
|
||||||
|
// Revision from git set during build
|
||||||
|
var Revision string |
||||||
|
|
||||||
|
func main() { |
||||||
|
usr, _ := user.Current() |
||||||
|
dir := usr.HomeDir |
||||||
|
configPath := filepath.Join(dir, "/.config/resh.toml") |
||||||
|
reshUUIDPath := filepath.Join(dir, "/.resh/resh-uuid") |
||||||
|
|
||||||
|
machineIDPath := "/etc/machine-id" |
||||||
|
|
||||||
|
var config cfg.Config |
||||||
|
if _, err := toml.DecodeFile(configPath, &config); err != nil { |
||||||
|
log.Fatal("Error reading config:", err) |
||||||
|
} |
||||||
|
showVersion := flag.Bool("version", false, "Show version and exit") |
||||||
|
showRevision := flag.Bool("revision", false, "Show git revision and exit") |
||||||
|
|
||||||
|
requireVersion := flag.String("requireVersion", "", "abort if version doesn't match") |
||||||
|
requireRevision := flag.String("requireRevision", "", "abort if revision doesn't match") |
||||||
|
|
||||||
|
shell := flag.String("shell", "", "actual shell") |
||||||
|
uname := flag.String("uname", "", "uname") |
||||||
|
sessionID := flag.String("sessionId", "", "resh generated session id") |
||||||
|
|
||||||
|
// posix variables
|
||||||
|
cols := flag.String("cols", "-1", "$COLUMNS") |
||||||
|
lines := flag.String("lines", "-1", "$LINES") |
||||||
|
home := flag.String("home", "", "$HOME") |
||||||
|
lang := flag.String("lang", "", "$LANG") |
||||||
|
lcAll := flag.String("lcAll", "", "$LC_ALL") |
||||||
|
login := flag.String("login", "", "$LOGIN") |
||||||
|
shellEnv := flag.String("shellEnv", "", "$SHELL") |
||||||
|
term := flag.String("term", "", "$TERM") |
||||||
|
|
||||||
|
// non-posix
|
||||||
|
pid := flag.Int("pid", -1, "$$") |
||||||
|
sessionPid := flag.Int("sessionPid", -1, "$$ at session start") |
||||||
|
shlvl := flag.Int("shlvl", -1, "$SHLVL") |
||||||
|
|
||||||
|
host := flag.String("host", "", "$HOSTNAME") |
||||||
|
hosttype := flag.String("hosttype", "", "$HOSTTYPE") |
||||||
|
ostype := flag.String("ostype", "", "$OSTYPE") |
||||||
|
machtype := flag.String("machtype", "", "$MACHTYPE") |
||||||
|
|
||||||
|
// before after
|
||||||
|
timezoneBefore := flag.String("timezoneBefore", "", "") |
||||||
|
|
||||||
|
osReleaseID := flag.String("osReleaseId", "", "/etc/os-release ID") |
||||||
|
osReleaseVersionID := flag.String("osReleaseVersionId", "", |
||||||
|
"/etc/os-release ID") |
||||||
|
osReleaseIDLike := flag.String("osReleaseIdLike", "", "/etc/os-release ID") |
||||||
|
osReleaseName := flag.String("osReleaseName", "", "/etc/os-release ID") |
||||||
|
osReleasePrettyName := flag.String("osReleasePrettyName", "", |
||||||
|
"/etc/os-release ID") |
||||||
|
|
||||||
|
rtb := flag.String("realtimeBefore", "-1", "before $EPOCHREALTIME") |
||||||
|
rtsess := flag.String("realtimeSession", "-1", |
||||||
|
"on session start $EPOCHREALTIME") |
||||||
|
rtsessboot := flag.String("realtimeSessSinceBoot", "-1", |
||||||
|
"on session start $EPOCHREALTIME") |
||||||
|
flag.Parse() |
||||||
|
|
||||||
|
if *showVersion == true { |
||||||
|
fmt.Println(Version) |
||||||
|
os.Exit(0) |
||||||
|
} |
||||||
|
if *showRevision == true { |
||||||
|
fmt.Println(Revision) |
||||||
|
os.Exit(0) |
||||||
|
} |
||||||
|
if *requireVersion != "" && *requireVersion != Version { |
||||||
|
fmt.Println("Please restart/reload this terminal session " + |
||||||
|
"(resh version: " + Version + |
||||||
|
"; resh version of this terminal session: " + *requireVersion + |
||||||
|
")") |
||||||
|
os.Exit(3) |
||||||
|
} |
||||||
|
if *requireRevision != "" && *requireRevision != Revision { |
||||||
|
fmt.Println("Please restart/reload this terminal session " + |
||||||
|
"(resh revision: " + Revision + |
||||||
|
"; resh revision of this terminal session: " + *requireRevision + |
||||||
|
")") |
||||||
|
os.Exit(3) |
||||||
|
} |
||||||
|
realtimeBefore, err := strconv.ParseFloat(*rtb, 64) |
||||||
|
if err != nil { |
||||||
|
log.Fatal("Flag Parsing error (rtb):", err) |
||||||
|
} |
||||||
|
realtimeSessionStart, err := strconv.ParseFloat(*rtsess, 64) |
||||||
|
if err != nil { |
||||||
|
log.Fatal("Flag Parsing error (rt sess):", err) |
||||||
|
} |
||||||
|
realtimeSessSinceBoot, err := strconv.ParseFloat(*rtsessboot, 64) |
||||||
|
if err != nil { |
||||||
|
log.Fatal("Flag Parsing error (rt sess boot):", err) |
||||||
|
} |
||||||
|
realtimeSinceSessionStart := realtimeBefore - realtimeSessionStart |
||||||
|
realtimeSinceBoot := realtimeSessSinceBoot + realtimeSinceSessionStart |
||||||
|
|
||||||
|
timezoneBeforeOffset := collect.GetTimezoneOffsetInSeconds(*timezoneBefore) |
||||||
|
realtimeBeforeLocal := realtimeBefore + timezoneBeforeOffset |
||||||
|
|
||||||
|
if *osReleaseID == "" { |
||||||
|
*osReleaseID = "linux" |
||||||
|
} |
||||||
|
if *osReleaseName == "" { |
||||||
|
*osReleaseName = "Linux" |
||||||
|
} |
||||||
|
if *osReleasePrettyName == "" { |
||||||
|
*osReleasePrettyName = "Linux" |
||||||
|
} |
||||||
|
|
||||||
|
rec := records.Record{ |
||||||
|
// posix
|
||||||
|
Cols: *cols, |
||||||
|
Lines: *lines, |
||||||
|
// core
|
||||||
|
BaseRecord: records.BaseRecord{ |
||||||
|
Shell: *shell, |
||||||
|
Uname: *uname, |
||||||
|
SessionID: *sessionID, |
||||||
|
|
||||||
|
// posix
|
||||||
|
Home: *home, |
||||||
|
Lang: *lang, |
||||||
|
LcAll: *lcAll, |
||||||
|
Login: *login, |
||||||
|
// Path: *path,
|
||||||
|
ShellEnv: *shellEnv, |
||||||
|
Term: *term, |
||||||
|
|
||||||
|
// non-posix
|
||||||
|
Pid: *pid, |
||||||
|
SessionPID: *sessionPid, |
||||||
|
Host: *host, |
||||||
|
Hosttype: *hosttype, |
||||||
|
Ostype: *ostype, |
||||||
|
Machtype: *machtype, |
||||||
|
Shlvl: *shlvl, |
||||||
|
|
||||||
|
// before after
|
||||||
|
TimezoneBefore: *timezoneBefore, |
||||||
|
|
||||||
|
RealtimeBefore: realtimeBefore, |
||||||
|
RealtimeBeforeLocal: realtimeBeforeLocal, |
||||||
|
|
||||||
|
RealtimeSinceSessionStart: realtimeSinceSessionStart, |
||||||
|
RealtimeSinceBoot: realtimeSinceBoot, |
||||||
|
|
||||||
|
MachineID: collect.ReadFileContent(machineIDPath), |
||||||
|
|
||||||
|
OsReleaseID: *osReleaseID, |
||||||
|
OsReleaseVersionID: *osReleaseVersionID, |
||||||
|
OsReleaseIDLike: *osReleaseIDLike, |
||||||
|
OsReleaseName: *osReleaseName, |
||||||
|
OsReleasePrettyName: *osReleasePrettyName, |
||||||
|
|
||||||
|
ReshUUID: collect.ReadFileContent(reshUUIDPath), |
||||||
|
ReshVersion: Version, |
||||||
|
ReshRevision: Revision, |
||||||
|
}, |
||||||
|
} |
||||||
|
collect.SendRecord(rec, strconv.Itoa(config.Port), "/session_init") |
||||||
|
} |
||||||
@ -1 +1,3 @@ |
|||||||
port = 2627 |
port = 2627 |
||||||
|
sesswatchPeriodSeconds = 120 |
||||||
|
sesshistInitHistorySize = 1000 |
||||||
|
|||||||
@ -1,19 +1,51 @@ |
|||||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= |
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= |
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= |
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= |
||||||
|
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= |
||||||
|
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= |
||||||
|
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= |
||||||
|
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= |
||||||
|
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= |
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= |
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= |
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= |
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= |
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= |
||||||
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= |
||||||
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= |
||||||
github.com/jpillora/longestcommon v0.0.0-20161227235612-adb9d91ee629 h1:1dSBUfGlorLAua2CRx0zFN7kQsTpE2DQSmr7rrTNgY8= |
github.com/jpillora/longestcommon v0.0.0-20161227235612-adb9d91ee629 h1:1dSBUfGlorLAua2CRx0zFN7kQsTpE2DQSmr7rrTNgY8= |
||||||
github.com/jpillora/longestcommon v0.0.0-20161227235612-adb9d91ee629/go.mod h1:mb5nS4uRANwOJSZj8rlCWAfAcGi72GGMIXx+xGOjA7M= |
github.com/jpillora/longestcommon v0.0.0-20161227235612-adb9d91ee629/go.mod h1:mb5nS4uRANwOJSZj8rlCWAfAcGi72GGMIXx+xGOjA7M= |
||||||
|
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= |
||||||
github.com/mattn/go-shellwords v1.0.6 h1:9Jok5pILi5S1MnDirGVTufYGtksUs/V2BWUP3ZkeUUI= |
github.com/mattn/go-shellwords v1.0.6 h1:9Jok5pILi5S1MnDirGVTufYGtksUs/V2BWUP3ZkeUUI= |
||||||
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= |
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= |
||||||
github.com/mb-14/gomarkov v0.0.0-20190125094512-044dd0dcb5e7 h1:VsJjhYhufMGXICLwLYr8mFVMp8/A+YqmagMHnG/BA/4= |
github.com/mb-14/gomarkov v0.0.0-20190125094512-044dd0dcb5e7 h1:VsJjhYhufMGXICLwLYr8mFVMp8/A+YqmagMHnG/BA/4= |
||||||
github.com/mb-14/gomarkov v0.0.0-20190125094512-044dd0dcb5e7/go.mod h1:zQmHoMvvVJb7cxyt1wGT77lqUaeOFXlogOppOr4uHVo= |
github.com/mb-14/gomarkov v0.0.0-20190125094512-044dd0dcb5e7/go.mod h1:zQmHoMvvVJb7cxyt1wGT77lqUaeOFXlogOppOr4uHVo= |
||||||
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= |
||||||
|
github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b h1:9+ke9YJ9KGWw5ANXK6ozjoK47uI3uNbXv4YVINBnGm8= |
||||||
|
github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= |
||||||
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= |
||||||
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= |
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
||||||
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= |
||||||
github.com/schollz/progressbar v1.0.0 h1:gbyFReLHDkZo8mxy/dLWMr+Mpb1MokGJ1FqCiqacjZM= |
github.com/schollz/progressbar v1.0.0 h1:gbyFReLHDkZo8mxy/dLWMr+Mpb1MokGJ1FqCiqacjZM= |
||||||
github.com/schollz/progressbar v1.0.0/go.mod h1:/l9I7PC3L3erOuz54ghIRKUEFcosiWfLvJv+Eq26UMs= |
github.com/schollz/progressbar v1.0.0/go.mod h1:/l9I7PC3L3erOuz54ghIRKUEFcosiWfLvJv+Eq26UMs= |
||||||
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= |
||||||
|
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= |
||||||
|
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= |
||||||
|
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= |
||||||
|
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= |
||||||
|
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= |
||||||
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= |
||||||
|
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= |
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= |
||||||
|
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= |
||||||
github.com/wcharczuk/go-chart v2.0.1+incompatible h1:0pz39ZAycJFF7ju/1mepnk26RLVLBCWz1STcD3doU0A= |
github.com/wcharczuk/go-chart v2.0.1+incompatible h1:0pz39ZAycJFF7ju/1mepnk26RLVLBCWz1STcD3doU0A= |
||||||
github.com/wcharczuk/go-chart v2.0.1+incompatible/go.mod h1:PF5tmL4EIx/7Wf+hEkpCqYi5He4u90sw+0+6FhrryuE= |
github.com/wcharczuk/go-chart v2.0.1+incompatible/go.mod h1:PF5tmL4EIx/7Wf+hEkpCqYi5He4u90sw+0+6FhrryuE= |
||||||
github.com/whilp/git-urls v0.0.0-20160530060445-31bac0d230fa h1:rW+Lu6281ed/4XGuVIa4/YebTRNvoUJlfJ44ktEVwZk= |
github.com/whilp/git-urls v0.0.0-20160530060445-31bac0d230fa h1:rW+Lu6281ed/4XGuVIa4/YebTRNvoUJlfJ44ktEVwZk= |
||||||
github.com/whilp/git-urls v0.0.0-20160530060445-31bac0d230fa/go.mod h1:2rx5KE5FLD0HRfkkpyn8JwbVLBdhgeiOb2D2D9LLKM4= |
github.com/whilp/git-urls v0.0.0-20160530060445-31bac0d230fa/go.mod h1:2rx5KE5FLD0HRfkkpyn8JwbVLBdhgeiOb2D2D9LLKM4= |
||||||
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= |
||||||
|
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= |
||||||
golang.org/x/image v0.0.0-20190902063713-cb417be4ba39 h1:4dQcAORh9oYBwVSBVIkP489LUPC+f1HBkTYXgmqfR+o= |
golang.org/x/image v0.0.0-20190902063713-cb417be4ba39 h1:4dQcAORh9oYBwVSBVIkP489LUPC+f1HBkTYXgmqfR+o= |
||||||
golang.org/x/image v0.0.0-20190902063713-cb417be4ba39/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= |
golang.org/x/image v0.0.0-20190902063713-cb417be4ba39/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= |
||||||
|
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
||||||
|
|||||||
@ -0,0 +1,118 @@ |
|||||||
|
package collect |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"encoding/json" |
||||||
|
"io/ioutil" |
||||||
|
"log" |
||||||
|
"net/http" |
||||||
|
"path/filepath" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/curusarn/resh/pkg/records" |
||||||
|
) |
||||||
|
|
||||||
|
// SingleResponse json struct
|
||||||
|
type SingleResponse struct { |
||||||
|
CmdLine string `json:"cmdline"` |
||||||
|
} |
||||||
|
|
||||||
|
// SendRecallRequest to daemon
|
||||||
|
func SendRecallRequest(r records.Record, port string) string { |
||||||
|
recJSON, err := json.Marshal(r) |
||||||
|
if err != nil { |
||||||
|
log.Fatal("send err 1", err) |
||||||
|
} |
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", "http://localhost:"+port+"/recall", |
||||||
|
bytes.NewBuffer(recJSON)) |
||||||
|
if err != nil { |
||||||
|
log.Fatal("send err 2", err) |
||||||
|
} |
||||||
|
req.Header.Set("Content-Type", "application/json") |
||||||
|
|
||||||
|
client := &http.Client{} |
||||||
|
resp, err := client.Do(req) |
||||||
|
if err != nil { |
||||||
|
log.Fatal("resh-daemon is not running :(") |
||||||
|
} |
||||||
|
|
||||||
|
defer resp.Body.Close() |
||||||
|
body, err := ioutil.ReadAll(resp.Body) |
||||||
|
if err != nil { |
||||||
|
log.Fatal("read response error") |
||||||
|
} |
||||||
|
log.Println(string(body)) |
||||||
|
response := SingleResponse{} |
||||||
|
err = json.Unmarshal(body, &response) |
||||||
|
if err != nil { |
||||||
|
log.Fatal("unmarshal resp error: ", err) |
||||||
|
} |
||||||
|
log.Println(response) |
||||||
|
return response.CmdLine |
||||||
|
} |
||||||
|
|
||||||
|
// SendRecord to daemon
|
||||||
|
func SendRecord(r records.Record, port, path string) { |
||||||
|
recJSON, err := json.Marshal(r) |
||||||
|
if err != nil { |
||||||
|
log.Fatal("send err 1", err) |
||||||
|
} |
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", "http://localhost:"+port+path, |
||||||
|
bytes.NewBuffer(recJSON)) |
||||||
|
if err != nil { |
||||||
|
log.Fatal("send err 2", err) |
||||||
|
} |
||||||
|
req.Header.Set("Content-Type", "application/json") |
||||||
|
|
||||||
|
client := &http.Client{} |
||||||
|
_, err = client.Do(req) |
||||||
|
if err != nil { |
||||||
|
log.Fatal("resh-daemon is not running :(") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ReadFileContent and return it as a string
|
||||||
|
func ReadFileContent(path string) string { |
||||||
|
dat, err := ioutil.ReadFile(path) |
||||||
|
if err != nil { |
||||||
|
return "" |
||||||
|
//log.Fatal("failed to open " + path)
|
||||||
|
} |
||||||
|
return strings.TrimSuffix(string(dat), "\n") |
||||||
|
} |
||||||
|
|
||||||
|
// GetGitDirs based on result of git "cdup" command
|
||||||
|
func GetGitDirs(cdup string, exitCode int, pwd string) (string, string) { |
||||||
|
if exitCode != 0 { |
||||||
|
return "", "" |
||||||
|
} |
||||||
|
abspath := filepath.Clean(filepath.Join(pwd, cdup)) |
||||||
|
realpath, err := filepath.EvalSymlinks(abspath) |
||||||
|
if err != nil { |
||||||
|
log.Println("err while handling git dir paths:", err) |
||||||
|
return "", "" |
||||||
|
} |
||||||
|
return abspath, realpath |
||||||
|
} |
||||||
|
|
||||||
|
// GetTimezoneOffsetInSeconds based on zone returned by date command
|
||||||
|
func GetTimezoneOffsetInSeconds(zone string) float64 { |
||||||
|
// date +%z -> "+0200"
|
||||||
|
hoursStr := zone[:3] |
||||||
|
minsStr := zone[3:] |
||||||
|
hours, err := strconv.Atoi(hoursStr) |
||||||
|
if err != nil { |
||||||
|
log.Println("err while parsing hours in timezone offset:", err) |
||||||
|
return -1 |
||||||
|
} |
||||||
|
mins, err := strconv.Atoi(minsStr) |
||||||
|
if err != nil { |
||||||
|
log.Println("err while parsing mins in timezone offset:", err) |
||||||
|
return -1 |
||||||
|
} |
||||||
|
secs := ((hours * 60) + mins) * 60 |
||||||
|
return float64(secs) |
||||||
|
} |
||||||
@ -0,0 +1,137 @@ |
|||||||
|
package histfile |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"log" |
||||||
|
"os" |
||||||
|
"strconv" |
||||||
|
"sync" |
||||||
|
|
||||||
|
"github.com/curusarn/resh/pkg/records" |
||||||
|
) |
||||||
|
|
||||||
|
// Histfile writes records to histfile
|
||||||
|
type Histfile struct { |
||||||
|
sessionsMutex sync.Mutex |
||||||
|
sessions map[string]records.Record |
||||||
|
historyPath string |
||||||
|
|
||||||
|
recentMutex sync.Mutex |
||||||
|
recentRecords []records.Record |
||||||
|
recentCmdLines []string // deduplicated
|
||||||
|
cmdLinesLastIndex map[string]int |
||||||
|
} |
||||||
|
|
||||||
|
// New creates new histfile and runs two gorutines on it
|
||||||
|
func New(input chan records.Record, historyPath string, initHistSize int, sessionsToDrop chan string) *Histfile { |
||||||
|
hf := Histfile{ |
||||||
|
sessions: map[string]records.Record{}, |
||||||
|
historyPath: historyPath, |
||||||
|
cmdLinesLastIndex: map[string]int{}, |
||||||
|
} |
||||||
|
go hf.loadHistory(initHistSize) |
||||||
|
go hf.writer(input) |
||||||
|
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
|
||||||
|
func (h *Histfile) sessionGC(sessionsToDrop chan string) { |
||||||
|
for { |
||||||
|
func() { |
||||||
|
session := <-sessionsToDrop |
||||||
|
log.Println("histfile: got session to drop", session) |
||||||
|
h.sessionsMutex.Lock() |
||||||
|
defer h.sessionsMutex.Unlock() |
||||||
|
if part1, found := h.sessions[session]; found == true { |
||||||
|
log.Println("histfile: Dropping session:", session) |
||||||
|
delete(h.sessions, session) |
||||||
|
go writeRecord(part1, h.historyPath) |
||||||
|
} else { |
||||||
|
log.Println("histfile: No hanging parts for session:", session) |
||||||
|
} |
||||||
|
}() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// writer reads records from channel, merges them and writes them to file
|
||||||
|
func (h *Histfile) writer(input chan records.Record) { |
||||||
|
for { |
||||||
|
func() { |
||||||
|
record := <-input |
||||||
|
h.sessionsMutex.Lock() |
||||||
|
defer h.sessionsMutex.Unlock() |
||||||
|
|
||||||
|
// allows nested sessions to merge records properly
|
||||||
|
mergeID := record.SessionID + "_" + strconv.Itoa(record.Shlvl) |
||||||
|
if record.PartOne { |
||||||
|
if _, found := h.sessions[mergeID]; found { |
||||||
|
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[mergeID] = record |
||||||
|
} else { |
||||||
|
if part1, found := h.sessions[mergeID]; found == false { |
||||||
|
log.Println("histfile ERROR: Got second part of records and nothing to merge it with - ignoring! (mergeID:", mergeID, ")") |
||||||
|
} else { |
||||||
|
delete(h.sessions, mergeID) |
||||||
|
go h.mergeAndWriteRecord(part1, record) |
||||||
|
} |
||||||
|
} |
||||||
|
}() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (h *Histfile) mergeAndWriteRecord(part1, part2 records.Record) { |
||||||
|
err := part1.Merge(part2) |
||||||
|
if err != nil { |
||||||
|
log.Println("Error while merging", err) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
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) { |
||||||
|
recJSON, err := json.Marshal(rec) |
||||||
|
if err != nil { |
||||||
|
log.Println("Marshalling error", err) |
||||||
|
return |
||||||
|
} |
||||||
|
f, err := os.OpenFile(outputPath, |
||||||
|
os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) |
||||||
|
if err != nil { |
||||||
|
log.Println("Could not open file", err) |
||||||
|
return |
||||||
|
} |
||||||
|
defer f.Close() |
||||||
|
_, err = f.Write(append(recJSON, []byte("\n")...)) |
||||||
|
if err != nil { |
||||||
|
log.Printf("Error while writing: %v, %s\n", rec, err) |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// GetRecentCmdLines returns recent cmdLines
|
||||||
|
func (h *Histfile) GetRecentCmdLines(limit int) []string { |
||||||
|
return h.recentCmdLines |
||||||
|
} |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
package sess |
||||||
|
|
||||||
|
// Session represents a session, used for sennding through channels when more than just ID is needed
|
||||||
|
type Session struct { |
||||||
|
ID string |
||||||
|
PID int |
||||||
|
} |
||||||
@ -0,0 +1,201 @@ |
|||||||
|
package sesshist |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"log" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
"sync" |
||||||
|
|
||||||
|
"github.com/curusarn/resh/pkg/histfile" |
||||||
|
"github.com/curusarn/resh/pkg/records" |
||||||
|
) |
||||||
|
|
||||||
|
// Dispatch Recall() calls to an apropriate session history (sesshist)
|
||||||
|
type Dispatch struct { |
||||||
|
sessions map[string]*sesshist |
||||||
|
mutex sync.RWMutex |
||||||
|
|
||||||
|
history *histfile.Histfile |
||||||
|
historyInitSize int |
||||||
|
} |
||||||
|
|
||||||
|
// NewDispatch creates a new sesshist.Dispatch and starts necessary gorutines
|
||||||
|
func NewDispatch(sessionsToInit chan records.Record, sessionsToDrop chan string, |
||||||
|
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.sessionDropper(sessionsToDrop) |
||||||
|
go s.recordAdder(recordsToAdd) |
||||||
|
return &s |
||||||
|
} |
||||||
|
|
||||||
|
func (s *Dispatch) sessionInitializer(sessionsToInit chan records.Record) { |
||||||
|
for { |
||||||
|
record := <-sessionsToInit |
||||||
|
log.Println("sesshist: got session to init - " + record.SessionID) |
||||||
|
s.initSession(record.SessionID) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *Dispatch) sessionDropper(sessionsToDrop chan string) { |
||||||
|
for { |
||||||
|
sessionID := <-sessionsToDrop |
||||||
|
log.Println("sesshist: got session to drop - " + sessionID) |
||||||
|
s.dropSession(sessionID) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *Dispatch) recordAdder(recordsToAdd chan records.Record) { |
||||||
|
for { |
||||||
|
record := <-recordsToAdd |
||||||
|
if record.PartOne { |
||||||
|
log.Println("sesshist: got record to add - " + record.CmdLine) |
||||||
|
s.addRecentRecord(record.SessionID, record) |
||||||
|
} |
||||||
|
// TODO: we will need to handle part2 as well eventually
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// InitSession struct
|
||||||
|
func (s *Dispatch) initSession(sessionID string) error { |
||||||
|
log.Println("sesshist: initializing session - " + sessionID) |
||||||
|
s.mutex.RLock() |
||||||
|
_, found := s.sessions[sessionID] |
||||||
|
s.mutex.RUnlock() |
||||||
|
|
||||||
|
if found == true { |
||||||
|
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() |
||||||
|
defer s.mutex.Unlock() |
||||||
|
// 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 |
||||||
|
} |
||||||
|
|
||||||
|
// DropSession struct
|
||||||
|
func (s *Dispatch) dropSession(sessionID string) error { |
||||||
|
s.mutex.RLock() |
||||||
|
_, found := s.sessions[sessionID] |
||||||
|
s.mutex.RUnlock() |
||||||
|
|
||||||
|
if found == false { |
||||||
|
return errors.New("sesshist ERROR: Can't DROP not existing session " + sessionID) |
||||||
|
} |
||||||
|
|
||||||
|
s.mutex.Lock() |
||||||
|
defer s.mutex.Unlock() |
||||||
|
delete(s.sessions, sessionID) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// AddRecent record to session
|
||||||
|
func (s *Dispatch) addRecentRecord(sessionID string, record records.Record) error { |
||||||
|
s.mutex.RLock() |
||||||
|
session, found := s.sessions[sessionID] |
||||||
|
s.mutex.RUnlock() |
||||||
|
|
||||||
|
if found == false { |
||||||
|
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() |
||||||
|
defer session.mutex.Unlock() |
||||||
|
session.recentRecords = append(session.recentRecords, record) |
||||||
|
// remove previous occurance of record
|
||||||
|
cmdLine := record.CmdLine |
||||||
|
idx, found := session.cmdLinesLastIndex[cmdLine] |
||||||
|
if found { |
||||||
|
session.recentCmdLines = append(session.recentCmdLines[:idx], session.recentCmdLines[idx+1:]...) |
||||||
|
} |
||||||
|
session.cmdLinesLastIndex[cmdLine] = len(session.recentCmdLines) |
||||||
|
// append new record
|
||||||
|
session.recentCmdLines = append(session.recentCmdLines, cmdLine) |
||||||
|
log.Println("sesshist: record:", record.CmdLine, "; added to session:", sessionID, |
||||||
|
"; session len:", len(session.recentCmdLines), "; session len w/ dups:", len(session.recentRecords)) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Recall command from recent session history
|
||||||
|
func (s *Dispatch) Recall(sessionID string, histno int, prefix string) (string, error) { |
||||||
|
s.mutex.RLock() |
||||||
|
session, found := s.sessions[sessionID] |
||||||
|
s.mutex.RUnlock() |
||||||
|
|
||||||
|
if found == false { |
||||||
|
// go s.initSession(sessionID)
|
||||||
|
return "", errors.New("sesshist ERROR: No session history for SessionID " + sessionID + " - should we create one?") |
||||||
|
} |
||||||
|
if prefix == "" { |
||||||
|
session.mutex.Lock() |
||||||
|
defer session.mutex.Unlock() |
||||||
|
return session.getRecordByHistno(histno) |
||||||
|
} |
||||||
|
session.mutex.Lock() |
||||||
|
defer session.mutex.Unlock() |
||||||
|
return session.searchRecordByPrefix(prefix, histno) |
||||||
|
} |
||||||
|
|
||||||
|
type sesshist struct { |
||||||
|
recentRecords []records.Record |
||||||
|
recentCmdLines []string // deduplicated
|
||||||
|
cmdLinesLastIndex map[string]int |
||||||
|
mutex sync.Mutex |
||||||
|
} |
||||||
|
|
||||||
|
func (s *sesshist) getRecordByHistno(histno int) (string, error) { |
||||||
|
// addRecords() appends records to the end of the slice
|
||||||
|
// -> this func handles the indexing
|
||||||
|
if histno == 0 { |
||||||
|
return "", errors.New("sesshist ERROR: 'histno == 0' is not a record from history") |
||||||
|
} |
||||||
|
if histno < 0 { |
||||||
|
return "", errors.New("sesshist ERROR: 'histno < 0' is a command from future (not supperted yet)") |
||||||
|
} |
||||||
|
index := len(s.recentCmdLines) - histno |
||||||
|
if index < 0 { |
||||||
|
return "", errors.New("sesshist ERROR: 'histno > number of commands in the session' (" + strconv.Itoa(len(s.recentCmdLines)) + ")") |
||||||
|
} |
||||||
|
return s.recentCmdLines[index], nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *sesshist) searchRecordByPrefix(prefix string, histno int) (string, error) { |
||||||
|
if histno == 0 { |
||||||
|
return "", errors.New("sesshist ERROR: 'histno == 0' is not a record from history") |
||||||
|
} |
||||||
|
if histno < 0 { |
||||||
|
return "", errors.New("sesshist ERROR: 'histno < 0' is a command from future (not supperted yet)") |
||||||
|
} |
||||||
|
index := len(s.recentCmdLines) - histno |
||||||
|
if index < 0 { |
||||||
|
return "", errors.New("sesshist ERROR: 'histno > number of commands in the session' (" + strconv.Itoa(len(s.recentCmdLines)) + ")") |
||||||
|
} |
||||||
|
cmdLines := []string{} |
||||||
|
for i := len(s.recentCmdLines) - 1; i >= 0; i-- { |
||||||
|
if strings.HasPrefix(s.recentCmdLines[i], prefix) { |
||||||
|
cmdLines = append(cmdLines, s.recentCmdLines[i]) |
||||||
|
if len(cmdLines) >= histno { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if len(cmdLines) < histno { |
||||||
|
return "", errors.New("sesshist ERROR: 'histno > number of commands matching with given prefix' (" + strconv.Itoa(len(cmdLines)) + ")") |
||||||
|
} |
||||||
|
return cmdLines[histno-1], nil |
||||||
|
} |
||||||
@ -0,0 +1,64 @@ |
|||||||
|
package sesswatch |
||||||
|
|
||||||
|
import ( |
||||||
|
"log" |
||||||
|
"sync" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/curusarn/resh/pkg/records" |
||||||
|
"github.com/mitchellh/go-ps" |
||||||
|
) |
||||||
|
|
||||||
|
type sesswatch struct { |
||||||
|
sessionsToDrop []chan string |
||||||
|
sleepSeconds uint |
||||||
|
|
||||||
|
watchedSessions map[string]bool |
||||||
|
mutex sync.Mutex |
||||||
|
} |
||||||
|
|
||||||
|
// Go runs the session watcher - watches sessions and sends
|
||||||
|
func Go(sessionsToWatch chan records.Record, sessionsToDrop []chan string, sleepSeconds uint) { |
||||||
|
sw := sesswatch{sessionsToDrop: sessionsToDrop, sleepSeconds: sleepSeconds, watchedSessions: map[string]bool{}} |
||||||
|
go sw.waiter(sessionsToWatch) |
||||||
|
} |
||||||
|
|
||||||
|
func (s *sesswatch) waiter(sessionsToWatch chan records.Record) { |
||||||
|
for { |
||||||
|
func() { |
||||||
|
record := <-sessionsToWatch |
||||||
|
id := record.SessionID |
||||||
|
pid := record.SessionPID |
||||||
|
s.mutex.Lock() |
||||||
|
defer s.mutex.Unlock() |
||||||
|
if s.watchedSessions[id] == false { |
||||||
|
log.Println("sesswatch: start watching NEW session ~ pid:", id, "~", pid) |
||||||
|
s.watchedSessions[id] = true |
||||||
|
go s.watcher(id, pid) |
||||||
|
} |
||||||
|
}() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *sesswatch) watcher(sessionID string, sessionPID int) { |
||||||
|
for { |
||||||
|
time.Sleep(time.Duration(s.sleepSeconds) * time.Second) |
||||||
|
proc, err := ps.FindProcess(sessionPID) |
||||||
|
if err != nil { |
||||||
|
log.Println("sesswatch ERROR: error while finding process:", sessionPID) |
||||||
|
} else if proc == nil { |
||||||
|
log.Println("sesswatch: Dropping session ~ pid:", sessionID, "~", sessionPID) |
||||||
|
func() { |
||||||
|
s.mutex.Lock() |
||||||
|
defer s.mutex.Unlock() |
||||||
|
s.watchedSessions[sessionID] = false |
||||||
|
}() |
||||||
|
for _, ch := range s.sessionsToDrop { |
||||||
|
log.Println("sesswatch: sending 'drop session' message ...") |
||||||
|
ch <- sessionID |
||||||
|
log.Println("sesswatch: sending 'drop session' message DONE") |
||||||
|
} |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,161 @@ |
|||||||
|
|
||||||
|
__resh_reset_variables() { |
||||||
|
__RESH_HISTNO=0 |
||||||
|
__RESH_HISTNO_ZERO_LINE="" |
||||||
|
__RESH_HIST_PREV_LINE="" |
||||||
|
__RESH_HIST_RECALL_ACTIONS="" |
||||||
|
__RESH_HIST_NO_PREFIX_MODE=0 |
||||||
|
__RESH_HIST_RECALL_STRATEGY="" |
||||||
|
} |
||||||
|
|
||||||
|
__resh_preexec() { |
||||||
|
# core |
||||||
|
__RESH_COLLECT=1 |
||||||
|
__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" \ |
||||||
|
--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)" |
||||||
|
} |
||||||
|
|
||||||
|
# used for collect and collect --recall |
||||||
|
__resh_collect() { |
||||||
|
# posix |
||||||
|
local __RESH_COLS="$COLUMNS" |
||||||
|
local __RESH_LANG="$LANG" |
||||||
|
local __RESH_LC_ALL="$LC_ALL" |
||||||
|
# other LC ? |
||||||
|
local __RESH_LINES="$LINES" |
||||||
|
# __RESH_PATH="$PATH" |
||||||
|
local __RESH_PWD="$PWD" |
||||||
|
|
||||||
|
# non-posix |
||||||
|
local __RESH_SHLVL="$SHLVL" |
||||||
|
local __RESH_GIT_CDUP; __RESH_GIT_CDUP="$(git rev-parse --show-cdup 2>/dev/null)" |
||||||
|
local __RESH_GIT_CDUP_EXIT_CODE=$? |
||||||
|
local __RESH_GIT_REMOTE; __RESH_GIT_REMOTE="$(git remote get-url origin 2>/dev/null)" |
||||||
|
local __RESH_GIT_REMOTE_EXIT_CODE=$? |
||||||
|
#__RESH_GIT_TOPLEVEL="$(git rev-parse --show-toplevel)" |
||||||
|
#__RESH_GIT_TOPLEVEL_EXIT_CODE=$? |
||||||
|
|
||||||
|
if [ -n "$ZSH_VERSION" ]; then |
||||||
|
# assume Zsh |
||||||
|
local __RESH_PID="$$" # current pid |
||||||
|
elif [ -n "$BASH_VERSION" ]; then |
||||||
|
# assume Bash |
||||||
|
local __RESH_PID="$BASHPID" # current pid |
||||||
|
fi |
||||||
|
# time |
||||||
|
local __RESH_TZ_BEFORE; __RESH_TZ_BEFORE=$(date +%z) |
||||||
|
# __RESH_RT_BEFORE="$EPOCHREALTIME" |
||||||
|
__RESH_RT_BEFORE=$(__resh_get_epochrealtime) |
||||||
|
|
||||||
|
if [ "$__RESH_VERSION" != "$(resh-collect -version)" ]; then |
||||||
|
# shellcheck source=shellrc.sh |
||||||
|
source ~/.resh/shellrc |
||||||
|
if [ "$__RESH_VERSION" != "$(resh-collect -version)" ]; then |
||||||
|
echo "RESH WARNING: You probably just updated RESH - PLEASE RESTART OR RELOAD THIS TERMINAL SESSION (resh version: $(resh-collect -version); resh version of this terminal session: ${__RESH_VERSION})" |
||||||
|
else |
||||||
|
echo "RESH INFO: New RESH shellrc script was loaded - if you encounter any issues please restart this terminal session." |
||||||
|
fi |
||||||
|
elif [ "$__RESH_REVISION" != "$(resh-collect -revision)" ]; then |
||||||
|
# shellcheck source=shellrc.sh |
||||||
|
source ~/.resh/shellrc |
||||||
|
if [ "$__RESH_REVISION" != "$(resh-collect -revision)" ]; then |
||||||
|
echo "RESH WARNING: You probably just updated RESH - PLEASE RESTART OR RELOAD THIS TERMINAL SESSION (resh revision: $(resh-collect -revision); resh revision of this terminal session: ${__RESH_REVISION})" |
||||||
|
fi |
||||||
|
fi |
||||||
|
if [ "$__RESH_VERSION" = "$(resh-collect -version)" ] && [ "$__RESH_REVISION" = "$(resh-collect -revision)" ]; then |
||||||
|
resh-collect -requireVersion "$__RESH_VERSION" \ |
||||||
|
-requireRevision "$__RESH_REVISION" \ |
||||||
|
-shell "$__RESH_SHELL" \ |
||||||
|
-uname "$__RESH_UNAME" \ |
||||||
|
-sessionId "$__RESH_SESSION_ID" \ |
||||||
|
-cols "$__RESH_COLS" \ |
||||||
|
-home "$__RESH_HOME" \ |
||||||
|
-lang "$__RESH_LANG" \ |
||||||
|
-lcAll "$__RESH_LC_ALL" \ |
||||||
|
-lines "$__RESH_LINES" \ |
||||||
|
-login "$__RESH_LOGIN" \ |
||||||
|
-pwd "$__RESH_PWD" \ |
||||||
|
-shellEnv "$__RESH_SHELL_ENV" \ |
||||||
|
-term "$__RESH_TERM" \ |
||||||
|
-pid "$__RESH_PID" \ |
||||||
|
-sessionPid "$__RESH_SESSION_PID" \ |
||||||
|
-host "$__RESH_HOST" \ |
||||||
|
-hosttype "$__RESH_HOSTTYPE" \ |
||||||
|
-ostype "$__RESH_OSTYPE" \ |
||||||
|
-machtype "$__RESH_MACHTYPE" \ |
||||||
|
-shlvl "$__RESH_SHLVL" \ |
||||||
|
-gitCdup "$__RESH_GIT_CDUP" \ |
||||||
|
-gitCdupExitCode "$__RESH_GIT_CDUP_EXIT_CODE" \ |
||||||
|
-gitRemote "$__RESH_GIT_REMOTE" \ |
||||||
|
-gitRemoteExitCode "$__RESH_GIT_REMOTE_EXIT_CODE" \ |
||||||
|
-realtimeBefore "$__RESH_RT_BEFORE" \ |
||||||
|
-realtimeSession "$__RESH_RT_SESSION" \ |
||||||
|
-realtimeSessSinceBoot "$__RESH_RT_SESS_SINCE_BOOT" \ |
||||||
|
-timezoneBefore "$__RESH_TZ_BEFORE" \ |
||||||
|
-osReleaseId "$__RESH_OS_RELEASE_ID" \ |
||||||
|
-osReleaseVersionId "$__RESH_OS_RELEASE_VERSION_ID" \ |
||||||
|
-osReleaseIdLike "$__RESH_OS_RELEASE_ID_LIKE" \ |
||||||
|
-osReleaseName "$__RESH_OS_RELEASE_NAME" \ |
||||||
|
-osReleasePrettyName "$__RESH_OS_RELEASE_PRETTY_NAME" \ |
||||||
|
-histno "$__RESH_HISTNO" \ |
||||||
|
"$@" |
||||||
|
fi |
||||||
|
} |
||||||
|
|
||||||
|
__resh_precmd() { |
||||||
|
local __RESH_EXIT_CODE=$? |
||||||
|
local __RESH_RT_AFTER |
||||||
|
local __RESH_TZ_AFTER |
||||||
|
local __RESH_PWD_AFTER |
||||||
|
local __RESH_GIT_CDUP_AFTER |
||||||
|
local __RESH_GIT_CDUP_EXIT_CODE_AFTER |
||||||
|
local __RESH_GIT_REMOTE_AFTER |
||||||
|
local __RESH_GIT_REMOTE_EXIT_CODE_AFTER |
||||||
|
local __RESH_SHLVL="$SHLVL" |
||||||
|
__RESH_RT_AFTER=$(__resh_get_epochrealtime) |
||||||
|
__RESH_TZ_AFTER=$(date +%z) |
||||||
|
__RESH_PWD_AFTER="$PWD" |
||||||
|
__RESH_GIT_CDUP_AFTER="$(git rev-parse --show-cdup 2>/dev/null)" |
||||||
|
__RESH_GIT_CDUP_EXIT_CODE_AFTER=$? |
||||||
|
__RESH_GIT_REMOTE_AFTER="$(git remote get-url origin 2>/dev/null)" |
||||||
|
__RESH_GIT_REMOTE_EXIT_CODE_AFTER=$? |
||||||
|
if [ -n "${__RESH_COLLECT}" ]; then |
||||||
|
if [ "$__RESH_VERSION" != "$(resh-postcollect -version)" ]; then |
||||||
|
# shellcheck source=shellrc.sh |
||||||
|
source ~/.resh/shellrc |
||||||
|
if [ "$__RESH_VERSION" != "$(resh-postcollect -version)" ]; then |
||||||
|
echo "RESH WARNING: You probably just updated RESH - PLEASE RESTART OR RELOAD THIS TERMINAL SESSION (resh version: $(resh-collect -version); resh version of this terminal session: ${__RESH_VERSION})" |
||||||
|
else |
||||||
|
echo "RESH INFO: New RESH shellrc script was loaded - if you encounter any issues please restart this terminal session." |
||||||
|
fi |
||||||
|
elif [ "$__RESH_REVISION" != "$(resh-postcollect -revision)" ]; then |
||||||
|
# shellcheck source=shellrc.sh |
||||||
|
source ~/.resh/shellrc |
||||||
|
if [ "$__RESH_REVISION" != "$(resh-postcollect -revision)" ]; then |
||||||
|
echo "RESH WARNING: You probably just updated RESH - PLEASE RESTART OR RELOAD THIS TERMINAL SESSION (resh revision: $(resh-collect -revision); resh revision of this terminal session: ${__RESH_REVISION})" |
||||||
|
fi |
||||||
|
fi |
||||||
|
if [ "$__RESH_VERSION" = "$(resh-postcollect -version)" ] && [ "$__RESH_REVISION" = "$(resh-postcollect -revision)" ]; then |
||||||
|
resh-postcollect -requireVersion "$__RESH_VERSION" \ |
||||||
|
-requireRevision "$__RESH_REVISION" \ |
||||||
|
-cmdLine "$__RESH_CMDLINE" \ |
||||||
|
-realtimeBefore "$__RESH_RT_BEFORE" \ |
||||||
|
-exitCode "$__RESH_EXIT_CODE" \ |
||||||
|
-sessionId "$__RESH_SESSION_ID" \ |
||||||
|
-shlvl "$__RESH_SHLVL" \ |
||||||
|
-pwdAfter "$__RESH_PWD_AFTER" \ |
||||||
|
-gitCdupAfter "$__RESH_GIT_CDUP_AFTER" \ |
||||||
|
-gitCdupExitCodeAfter "$__RESH_GIT_CDUP_EXIT_CODE_AFTER" \ |
||||||
|
-gitRemoteAfter "$__RESH_GIT_REMOTE_AFTER" \ |
||||||
|
-gitRemoteExitCodeAfter "$__RESH_GIT_REMOTE_EXIT_CODE_AFTER" \ |
||||||
|
-realtimeAfter "$__RESH_RT_AFTER" \ |
||||||
|
-timezoneAfter "$__RESH_TZ_AFTER" \ |
||||||
|
&>~/.resh/postcollect_last_run_out.txt || echo "resh-postcollect ERROR: $(head -n 1 ~/.resh/postcollect_last_run_out.txt)" |
||||||
|
fi |
||||||
|
__resh_reset_variables |
||||||
|
fi |
||||||
|
unset __RESH_COLLECT |
||||||
|
} |
||||||
@ -0,0 +1,70 @@ |
|||||||
|
|
||||||
|
# shellcheck source=../submodules/bash-zsh-compat-widgets/bindfunc.sh |
||||||
|
. ~/.resh/bindfunc.sh |
||||||
|
# shellcheck source=widgets.sh |
||||||
|
. ~/.resh/widgets.sh |
||||||
|
|
||||||
|
__resh_bind_arrows() { |
||||||
|
bindfunc '\e[A' __resh_widget_arrow_up_compat |
||||||
|
bindfunc '\e[B' __resh_widget_arrow_down_compat |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
__resh_bind_control_R() { |
||||||
|
echo "bindfunc __resh_widget_control_R_compat" |
||||||
|
return 0 |
||||||
|
} |
||||||
|
__resh_unbind_arrows() { |
||||||
|
echo "\ bindfunc __resh_widget_arrow_up_compat" |
||||||
|
echo "\ bindfunc __resh_widget_arrow_down_compat" |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
__resh_unbind_control_R() { |
||||||
|
echo "\ bindfunc __resh_widget_control_R_compat" |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
__resh_bind_all() { |
||||||
|
__resh_bind_arrows |
||||||
|
__resh_bind_control_R |
||||||
|
} |
||||||
|
|
||||||
|
__resh_unbind_all() { |
||||||
|
__resh_unbind_arrows |
||||||
|
__resh_unbind_control_R |
||||||
|
} |
||||||
|
|
||||||
|
reshctl() { |
||||||
|
# run resh-control aka the real reshctl |
||||||
|
resh-control "$@" |
||||||
|
# modify current shell session based on exit status |
||||||
|
local _status=$? |
||||||
|
case "$_status" in |
||||||
|
0|1) |
||||||
|
# success | fail |
||||||
|
return "$_status" |
||||||
|
;; |
||||||
|
# enable |
||||||
|
100) |
||||||
|
# enable all |
||||||
|
__resh_bind_all |
||||||
|
return 0 |
||||||
|
;; |
||||||
|
# disable |
||||||
|
110) |
||||||
|
# disable all |
||||||
|
__resh_unbind_all |
||||||
|
return 0 |
||||||
|
;; |
||||||
|
200) |
||||||
|
# reload rc files |
||||||
|
. ~/.resh/shellrc |
||||||
|
return 0 |
||||||
|
;; |
||||||
|
*) |
||||||
|
echo "reshctl() FATAL ERROR: unknown status" >&2 |
||||||
|
return "$_status" |
||||||
|
;; |
||||||
|
esac |
||||||
|
} |
||||||
@ -0,0 +1,136 @@ |
|||||||
|
# util.sh - resh utility functions |
||||||
|
__resh_get_uuid() { |
||||||
|
cat /proc/sys/kernel/random/uuid 2>/dev/null || resh-uuid |
||||||
|
} |
||||||
|
|
||||||
|
__resh_get_pid() { |
||||||
|
if [ -n "$ZSH_VERSION" ]; then |
||||||
|
# assume Zsh |
||||||
|
local __RESH_PID="$$" # current pid |
||||||
|
elif [ -n "$BASH_VERSION" ]; then |
||||||
|
# assume Bash |
||||||
|
local __RESH_PID="$BASHPID" # current pid |
||||||
|
fi |
||||||
|
echo "$__RESH_PID" |
||||||
|
} |
||||||
|
|
||||||
|
__resh_get_epochrealtime() { |
||||||
|
if date +%s.%N | grep -vq 'N'; then |
||||||
|
# GNU date |
||||||
|
date +%s.%N |
||||||
|
elif gdate --version >/dev/null && gdate +%s.%N | grep -vq 'N'; then |
||||||
|
# GNU date take 2 |
||||||
|
gdate +%s.%N |
||||||
|
elif [ -n "$ZSH_VERSION" ]; then |
||||||
|
# zsh fallback using $EPOCHREALTIME |
||||||
|
if [ -z "${__RESH_ZSH_LOADED_DATETIME+x}" ]; then |
||||||
|
zmodload zsh/datetime |
||||||
|
__RESH_ZSH_LOADED_DATETIME=1 |
||||||
|
fi |
||||||
|
echo "$EPOCHREALTIME" |
||||||
|
else |
||||||
|
# dumb date |
||||||
|
# XXX: we lost precison beyond seconds |
||||||
|
date +%s |
||||||
|
if [ -z "${__RESH_DATE_WARN+x}" ]; then |
||||||
|
echo "resh WARN: can't get precise time - consider installing GNU date!" |
||||||
|
__RESH_DATE_WARN=1 |
||||||
|
fi |
||||||
|
fi |
||||||
|
} |
||||||
|
|
||||||
|
__resh_run_daemon() { |
||||||
|
if [ -n "$ZSH_VERSION" ]; then |
||||||
|
setopt LOCAL_OPTIONS NO_NOTIFY NO_MONITOR |
||||||
|
fi |
||||||
|
nohup resh-daemon &>~/.resh/daemon_last_run_out.txt & disown |
||||||
|
} |
||||||
|
|
||||||
|
__resh_bash_completion_init() { |
||||||
|
local bash_completion_dir=~/.resh/bash_completion.d |
||||||
|
# source user completion directory definitions |
||||||
|
# taken from /usr/share/bash-completion/bash_completion |
||||||
|
if [[ -d $bash_completion_dir && -r $bash_completion_dir && \ |
||||||
|
-x $bash_completion_dir ]]; then |
||||||
|
for i in $(LC_ALL=C command ls "$bash_completion_dir"); do |
||||||
|
i=$bash_completion_dir/$i |
||||||
|
# shellcheck disable=SC2154 |
||||||
|
# shellcheck source=/dev/null |
||||||
|
[[ ${i##*/} != @($_backup_glob|Makefile*|$_blacklist_glob) \ |
||||||
|
&& -f $i && -r $i ]] && . "$i" |
||||||
|
done |
||||||
|
fi |
||||||
|
} |
||||||
|
|
||||||
|
__resh_zsh_completion_init() { |
||||||
|
# shellcheck disable=SC2206 |
||||||
|
fpath=(~/.resh/zsh_completion.d $fpath) |
||||||
|
} |
||||||
|
|
||||||
|
__resh_session_init() { |
||||||
|
# posix |
||||||
|
local __RESH_COLS="$COLUMNS" |
||||||
|
local __RESH_LANG="$LANG" |
||||||
|
local __RESH_LC_ALL="$LC_ALL" |
||||||
|
# other LC ? |
||||||
|
local __RESH_LINES="$LINES" |
||||||
|
local __RESH_PWD="$PWD" |
||||||
|
|
||||||
|
# non-posix |
||||||
|
local __RESH_SHLVL="$SHLVL" |
||||||
|
|
||||||
|
# pid |
||||||
|
local __RESH_PID; __RESH_PID=$(__resh_get_pid) |
||||||
|
|
||||||
|
# time |
||||||
|
local __RESH_TZ_BEFORE; __RESH_TZ_BEFORE=$(date +%z) |
||||||
|
local __RESH_RT_BEFORE; __RESH_RT_BEFORE=$(__resh_get_epochrealtime) |
||||||
|
|
||||||
|
if [ "$__RESH_VERSION" != "$(resh-session-init -version)" ]; then |
||||||
|
# shellcheck source=shellrc.sh |
||||||
|
source ~/.resh/shellrc |
||||||
|
if [ "$__RESH_VERSION" != "$(resh-session-init -version)" ]; then |
||||||
|
echo "RESH WARNING: You probably just updated RESH - PLEASE RESTART OR RELOAD THIS TERMINAL SESSION (resh version: $(resh-session-init -version); resh version of this terminal session: ${__RESH_VERSION})" |
||||||
|
else |
||||||
|
echo "RESH INFO: New RESH shellrc script was loaded - if you encounter any issues please restart this terminal session." |
||||||
|
fi |
||||||
|
elif [ "$__RESH_REVISION" != "$(resh-session-init -revision)" ]; then |
||||||
|
# shellcheck source=shellrc.sh |
||||||
|
source ~/.resh/shellrc |
||||||
|
if [ "$__RESH_REVISION" != "$(resh-session-init -revision)" ]; then |
||||||
|
echo "RESH WARNING: You probably just updated RESH - PLEASE RESTART OR RELOAD THIS TERMINAL SESSION (resh revision: $(resh-session-init -revision); resh revision of this terminal session: ${__RESH_REVISION})" |
||||||
|
fi |
||||||
|
fi |
||||||
|
if [ "$__RESH_VERSION" = "$(resh-session-init -version)" ] && [ "$__RESH_REVISION" = "$(resh-session-init -revision)" ]; then |
||||||
|
resh-session-init -requireVersion "$__RESH_VERSION" \ |
||||||
|
-requireRevision "$__RESH_REVISION" \ |
||||||
|
-shell "$__RESH_SHELL" \ |
||||||
|
-uname "$__RESH_UNAME" \ |
||||||
|
-sessionId "$__RESH_SESSION_ID" \ |
||||||
|
-cols "$__RESH_COLS" \ |
||||||
|
-home "$__RESH_HOME" \ |
||||||
|
-lang "$__RESH_LANG" \ |
||||||
|
-lcAll "$__RESH_LC_ALL" \ |
||||||
|
-lines "$__RESH_LINES" \ |
||||||
|
-login "$__RESH_LOGIN" \ |
||||||
|
-shellEnv "$__RESH_SHELL_ENV" \ |
||||||
|
-term "$__RESH_TERM" \ |
||||||
|
-pid "$__RESH_PID" \ |
||||||
|
-sessionPid "$__RESH_SESSION_PID" \ |
||||||
|
-host "$__RESH_HOST" \ |
||||||
|
-hosttype "$__RESH_HOSTTYPE" \ |
||||||
|
-ostype "$__RESH_OSTYPE" \ |
||||||
|
-machtype "$__RESH_MACHTYPE" \ |
||||||
|
-shlvl "$__RESH_SHLVL" \ |
||||||
|
-realtimeBefore "$__RESH_RT_BEFORE" \ |
||||||
|
-realtimeSession "$__RESH_RT_SESSION" \ |
||||||
|
-realtimeSessSinceBoot "$__RESH_RT_SESS_SINCE_BOOT" \ |
||||||
|
-timezoneBefore "$__RESH_TZ_BEFORE" \ |
||||||
|
-osReleaseId "$__RESH_OS_RELEASE_ID" \ |
||||||
|
-osReleaseVersionId "$__RESH_OS_RELEASE_VERSION_ID" \ |
||||||
|
-osReleaseIdLike "$__RESH_OS_RELEASE_ID_LIKE" \ |
||||||
|
-osReleaseName "$__RESH_OS_RELEASE_NAME" \ |
||||||
|
-osReleasePrettyName "$__RESH_OS_RELEASE_PRETTY_NAME" \ |
||||||
|
&>~/.resh/session_init_last_run_out.txt || echo "resh-session-init ERROR: $(head -n 1 ~/.resh/session_init_last_run_out.txt)" |
||||||
|
fi |
||||||
|
} |
||||||
@ -0,0 +1,93 @@ |
|||||||
|
|
||||||
|
# shellcheck source=hooks.sh |
||||||
|
. ~/.resh/hooks.sh |
||||||
|
|
||||||
|
__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 |
||||||
|
__RESH_PREFIX=${BUFFER:0:$CURSOR} |
||||||
|
# cursor not at the end of the line => end "NO_PREFIX_MODE" |
||||||
|
[ "$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" |
||||||
|
[ "$BUFFER" != "$__RESH_HIST_PREV_LINE" ] && __RESH_HISTNO=0 && __RESH_HIST_NO_PREFIX_MODE=0 |
||||||
|
# "NO_PREFIX_MODE" => set prefix to empty string |
||||||
|
[ "$__RESH_HIST_NO_PREFIX_MODE" -eq 1 ] && __RESH_PREFIX="" |
||||||
|
# histno == 0 => save current line |
||||||
|
[ "$__RESH_HISTNO" -eq 0 ] && __RESH_HISTNO_ZERO_LINE=$BUFFER |
||||||
|
} |
||||||
|
__resh_helper_arrow_post() { |
||||||
|
# cursor at the beginning of the line => activate "NO_PREFIX_MODE" |
||||||
|
[ "$CURSOR" -eq 0 ] && __RESH_HIST_NO_PREFIX_MODE=1 |
||||||
|
# "NO_PREFIX_MODE" => move cursor to the end of the line |
||||||
|
[ "$__RESH_HIST_NO_PREFIX_MODE" -eq 1 ] && CURSOR=${#BUFFER} |
||||||
|
# save current line so we can spot user edits next time |
||||||
|
__RESH_HIST_PREV_LINE=$BUFFER |
||||||
|
} |
||||||
|
|
||||||
|
__resh_widget_arrow_up() { |
||||||
|
# run helper function |
||||||
|
__resh_helper_arrow_pre |
||||||
|
# append curent recall action |
||||||
|
__RESH_HIST_RECALL_ACTIONS="$__RESH_HIST_RECALL_ACTIONS;arrow_up:$__RESH_PREFIX" |
||||||
|
# increment histno |
||||||
|
__RESH_HISTNO=$((__RESH_HISTNO+1)) |
||||||
|
# back at histno == 0 => restore original line |
||||||
|
if [ "$__RESH_HISTNO" -eq 0 ]; then |
||||||
|
BUFFER=$__RESH_HISTNO_ZERO_LINE |
||||||
|
else |
||||||
|
# run recall |
||||||
|
local NEW_BUFFER |
||||||
|
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 |
||||||
|
# shellcheck disable=SC2015 |
||||||
|
[ "${#NEW_BUFFER}" -gt 0 ] && BUFFER=$NEW_BUFFER || __RESH_HISTNO=$((__RESH_HISTNO-1)) |
||||||
|
fi |
||||||
|
# run post helper |
||||||
|
__resh_helper_arrow_post |
||||||
|
} |
||||||
|
__resh_widget_arrow_down() { |
||||||
|
# run helper function |
||||||
|
__resh_helper_arrow_pre |
||||||
|
# append curent recall action |
||||||
|
__RESH_HIST_RECALL_ACTIONS="$__RESH_HIST_RECALL_ACTIONS;arrow_down:$__RESH_PREFIX" |
||||||
|
# increment histno |
||||||
|
__RESH_HISTNO=$((__RESH_HISTNO-1)) |
||||||
|
# prevent HISTNO from getting negative (for now) |
||||||
|
[ "$__RESH_HISTNO" -lt 0 ] && __RESH_HISTNO=0 |
||||||
|
# back at histno == 0 => restore original line |
||||||
|
if [ "$__RESH_HISTNO" -eq 0 ]; then |
||||||
|
BUFFER=$__RESH_HISTNO_ZERO_LINE |
||||||
|
else |
||||||
|
# run recall |
||||||
|
local NEW_BUFFER |
||||||
|
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 |
||||||
|
# shellcheck disable=SC2015 |
||||||
|
[ "${#NEW_BUFFER}" -gt 0 ] && BUFFER=$NEW_BUFFER || (( __RESH_HISTNO++ )) |
||||||
|
fi |
||||||
|
__resh_helper_arrow_post |
||||||
|
} |
||||||
|
__resh_widget_control_R() { |
||||||
|
local __RESH_PREFIX=${BUFFER:0:CURSOR} |
||||||
|
__RESH_HIST_RECALL_ACTIONS="$__RESH_HIST_RECALL_ACTIONS;control_R:$__RESH_PREFIX" |
||||||
|
# resh-collect --hstr |
||||||
|
hstr |
||||||
|
} |
||||||
|
|
||||||
|
__resh_widget_arrow_up_compat() { |
||||||
|
__bindfunc_compat_wrapper __resh_widget_arrow_up |
||||||
|
} |
||||||
|
|
||||||
|
__resh_widget_arrow_down_compat() { |
||||||
|
__bindfunc_compat_wrapper __resh_widget_arrow_down |
||||||
|
} |
||||||
|
|
||||||
|
__resh_widget_control_R_compat() { |
||||||
|
__bindfunc_compat_wrapper __resh_widget_control_R |
||||||
|
} |
||||||
@ -0,0 +1 @@ |
|||||||
|
Subproject commit 7dde81eaa09cbed11ebc70ea892bcae24ea1606c |
||||||
Loading…
Reference in new issue