Merge pull request #137 from curusarn/thesis_release

Big bold beatiful RESH-CLI update
pull/143/head
Šimon Let 6 years ago committed by GitHub
commit 09dff916df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 27
      cmd/cli/highlight.go
  2. 328
      cmd/cli/item.go
  3. 207
      cmd/cli/main.go
  4. 2
      cmd/cli/query.go
  5. 181
      cmd/cli/time.go
  6. 2
      cmd/collect/main.go
  7. 3
      cmd/postcollect/main.go
  8. 2
      conf/config.toml
  9. 7
      pkg/histcli/histcli.go
  10. 10
      pkg/histfile/histfile.go
  11. 17
      pkg/records/records.go
  12. 3
      scripts/hooks.sh

@ -27,6 +27,19 @@ func cleanHighlight(str string) string {
return str
}
func highlightHeader(str string) string {
underline := "\033[4m"
end := "\033[0m"
// no clean highlight
return underline + str + end
}
func highlightStatus(str string) string {
invert := "\033[7;1m"
end := "\033[0m"
return invert + cleanHighlight(str) + end
}
func highlightSelected(str string) string {
// template "\033[3%d;%dm"
// invertGreen := "\033[32;7;1m"
@ -35,6 +48,13 @@ func highlightSelected(str string) string {
return invert + cleanHighlight(str) + end
}
func highlightDate(str string) string {
// template "\033[3%d;%dm"
yellowNormal := "\033[33m"
end := "\033[0m"
return yellowNormal + cleanHighlight(str) + end
}
func highlightHost(str string) string {
// template "\033[3%d;%dm"
redNormal := "\033[31m"
@ -71,6 +91,13 @@ func highlightGit(str string) string {
return greenBold + cleanHighlight(str) + end
}
func doHighlightHeader(str string, minLength int) string {
if len(str) < minLength {
str = str + strings.Repeat(" ", minLength-len(str))
}
return highlightHeader(str)
}
func doHighlightString(str string, minLength int) string {
if len(str) < minLength {
str = str + strings.Repeat(" ", minLength-len(str))

@ -6,17 +6,49 @@ import (
"log"
"strconv"
"strings"
"time"
"github.com/curusarn/resh/pkg/records"
)
const itemLocationLenght = 30
type item struct {
// dateWithColor string
// date string
isRaw bool
realtimeBefore float64
// [host:]pwd
differentHost bool
host string
home string
samePwd bool
pwd string
// [G] [E#]
sameGitRepo bool
exitCode int
cmdLineWithColor string
cmdLine string
score float64
key string
// cmdLineRaw string
}
type itemColumns struct {
dateWithColor string
date string
// [host:]pwd
locationWithColor string
location string
hostWithColor string
host string
pwdTilde string
samePwd bool
//locationWithColor string
//location string
// [G] [E#]
flagsWithColor string
@ -25,7 +57,7 @@ type item struct {
cmdLineWithColor string
cmdLine string
score float64
// score float64
key string
// cmdLineRaw string
@ -36,27 +68,166 @@ func (i item) less(i2 item) bool {
return i.score > i2.score
}
func (i item) produceLine(flagLength int) (string, int) {
func splitStatusLineToLines(statusLine string, printedLineLength, realLineLength int) []string {
var statusLineSlice []string
// status line
var idxSt, idxEnd int
var nextLine bool
tab := " "
tabSize := len(tab)
for idxSt < len(statusLine) {
idxEnd = idxSt + printedLineLength
if nextLine {
idxEnd -= tabSize
}
if idxEnd > len(statusLine) {
idxEnd = len(statusLine)
}
str := statusLine[idxSt:idxEnd]
indent := " "
if nextLine {
indent += tab
}
statusLineSlice = append(statusLineSlice, highlightStatus(rightCutPadString(indent+str, realLineLength))+"\n")
idxSt += printedLineLength
nextLine = true
}
return statusLineSlice
}
func (i item) drawStatusLine(compactRendering bool, printedLineLength, realLineLength int) []string {
if i.isRaw {
return splitStatusLineToLines(i.cmdLine, printedLineLength, realLineLength)
}
secs := int64(i.realtimeBefore)
nsecs := int64((i.realtimeBefore - float64(secs)) * 1e9)
tm := time.Unix(secs, nsecs)
const timeFormat = "2006-01-02 15:04:05"
timeString := tm.Format(timeFormat)
pwdTilde := strings.Replace(i.pwd, i.home, "~", 1)
separator := " "
stLine := timeString + separator + i.host + ":" + pwdTilde + separator + i.cmdLine
return splitStatusLineToLines(stLine, printedLineLength, realLineLength)
}
func (i item) drawItemColumns(compactRendering bool) itemColumns {
if i.isRaw {
notAvailable := "n/a"
return itemColumns{
date: notAvailable + " ",
dateWithColor: notAvailable + " ",
// dateWithColor: highlightDate(notAvailable) + " ",
host: "",
hostWithColor: "",
pwdTilde: notAvailable,
cmdLine: i.cmdLine,
cmdLineWithColor: i.cmdLineWithColor,
// score: i.score,
key: i.key,
}
}
// DISPLAY
// DISPLAY > date
secs := int64(i.realtimeBefore)
nsecs := int64((i.realtimeBefore - float64(secs)) * 1e9)
tm := time.Unix(secs, nsecs)
var date string
if compactRendering {
date = formatTimeRelativeShort(tm) + " "
} else {
date = formatTimeRelativeLong(tm) + " "
}
dateWithColor := highlightDate(date)
// DISPLAY > location
// DISPLAY > location > host
host := ""
hostWithColor := ""
if i.differentHost {
host += i.host + ":"
hostWithColor += highlightHost(i.host) + ":"
}
// DISPLAY > location > directory
pwdTilde := strings.Replace(i.pwd, i.home, "~", 1)
// DISPLAY > flags
flags := ""
flagsWithColor := ""
if debug {
hitsStr := fmt.Sprintf("%.1f", i.score)
flags += " S" + hitsStr
flagsWithColor += " S" + hitsStr
}
if i.sameGitRepo {
flags += " G"
flagsWithColor += " " + highlightGit("G")
}
if i.exitCode != 0 {
flags += " E" + strconv.Itoa(i.exitCode)
flagsWithColor += " " + highlightWarn("E"+strconv.Itoa(i.exitCode))
}
// NOTE: you can debug arbitrary metadata like this
// flags += " <" + record.GitOriginRemote + ">"
// flagsWithColor += " <" + record.GitOriginRemote + ">"
return itemColumns{
date: date,
dateWithColor: dateWithColor,
host: host,
hostWithColor: hostWithColor,
pwdTilde: pwdTilde,
samePwd: i.samePwd,
flags: flags,
flagsWithColor: flagsWithColor,
cmdLine: i.cmdLine,
cmdLineWithColor: i.cmdLineWithColor,
// score: i.score,
key: i.key,
}
}
func (ic itemColumns) produceLine(dateLength int, locationLength int, flagLength int, header bool, showDate bool) (string, int) {
line := ""
line += i.locationWithColor
line += i.flagsWithColor
flags := i.flags
if flagLength < len(i.flags) {
log.Printf("produceLine can't specify line w/ flags shorter than the actual size. - len(flags) %v, requested %v\n", len(i.flags), flagLength)
if showDate {
date := ic.date
for len(date) < dateLength {
line += " "
date += " "
}
// TODO: use strings.Repeat
line += ic.dateWithColor
}
// LOCATION
locationWithColor := ic.hostWithColor
pwdLength := locationLength - len(ic.host)
if ic.samePwd {
locationWithColor += highlightPwd(leftCutPadString(ic.pwdTilde, pwdLength))
} else {
locationWithColor += leftCutPadString(ic.pwdTilde, pwdLength)
}
line += locationWithColor
line += ic.flagsWithColor
flags := ic.flags
if flagLength < len(ic.flags) {
log.Printf("produceLine can't specify line w/ flags shorter than the actual size. - len(flags) %v, requested %v\n", len(ic.flags), flagLength)
}
for len(flags) < flagLength {
line += " "
flags += " "
}
spacer := " "
if flagLength > 5 {
if flagLength > 5 || header {
// use shorter spacer
// because there is likely a long flag like E130 in the view
spacer = " "
}
line += spacer + i.cmdLineWithColor
line += spacer + ic.cmdLineWithColor
length := len(i.location) + flagLength + len(spacer) + len(i.cmdLine)
length := dateLength + locationLength + flagLength + len(spacer) + len(ic.cmdLine)
return line, length
}
@ -94,15 +265,26 @@ func properMatch(str, term, padChar string) bool {
// newItemFromRecordForQuery creates new item from record based on given query
// returns error if the query doesn't match the record
func newItemFromRecordForQuery(record records.CliRecord, query query, debug bool) (item, error) {
const hitScore = 1.0
const hitScoreConsecutive = 0.1
const properMatchScore = 0.3
const actualPwdScore = 0.9
const nonZeroExitCodeScorePenalty = 0.5
const sameGitRepoScore = 0.7
// const sameGitRepoScoreExtra = 0.0
const differentHostScorePenalty = 0.2
// Use numbers that won't add up to same score for any number of query words
const hitScore = 1.307
const properMatchScore = 0.603
const hitScoreConsecutive = 0.002
// Host penalty
var actualPwdScore = 0.9
var sameGitRepoScore = 0.8
var nonZeroExitCodeScorePenalty = 0.4
var differentHostScorePenalty = 0.2
reduceHostPenalty := false
if reduceHostPenalty {
actualPwdScore = 0.9
sameGitRepoScore = 0.7
nonZeroExitCodeScorePenalty = 0.4
differentHostScorePenalty = 0.1
}
const timeScoreCoef = 1e-13
// nonZeroExitCodeScorePenalty + differentHostScorePenalty
score := 0.0
@ -114,7 +296,8 @@ func newItemFromRecordForQuery(record records.CliRecord, query query, debug bool
anyHit = true
if termHit == false {
score += hitScore
} else {
} else if len(term) > 1 {
// only count consecutive matches for queries longer than 1
score += hitScoreConsecutive
}
termHit = true
@ -122,9 +305,33 @@ func newItemFromRecordForQuery(record records.CliRecord, query query, debug bool
score += properMatchScore
}
cmd = strings.ReplaceAll(cmd, term, highlightMatch(term))
// NO continue
}
}
// DISPLAY > cmdline
// cmd := "<" + strings.ReplaceAll(record.CmdLine, "\n", ";") + ">"
cmdLine := strings.ReplaceAll(record.CmdLine, "\n", ";")
cmdLineWithColor := strings.ReplaceAll(cmd, "\n", ";")
// KEY for deduplication
key := record.CmdLine
// NOTE: since we import standard history we need a compatible key without metadata
/*
unlikelySeparator := "|||||"
key := record.CmdLine + unlikelySeparator + record.Pwd + unlikelySeparator +
record.GitOriginRemote + unlikelySeparator + record.Host
*/
if record.IsRaw {
return item{
isRaw: true,
cmdLine: cmdLine,
cmdLineWithColor: cmdLineWithColor,
score: score,
key: key,
}, nil
}
// actual pwd matches
// N terms can only produce:
// -> N matches against the command
@ -149,74 +356,27 @@ func newItemFromRecordForQuery(record records.CliRecord, query query, debug bool
differentHost = true
score -= differentHostScorePenalty
}
errorExitStatus := false
// errorExitStatus := false
if record.ExitCode != 0 {
errorExitStatus = true
// errorExitStatus = true
score -= nonZeroExitCodeScorePenalty
}
if score <= 0 && !anyHit {
return item{}, errors.New("no match for given record and query")
}
score += record.RealtimeBefore * timeScoreCoef
// KEY for deduplication
unlikelySeparator := "|||||"
key := record.CmdLine + unlikelySeparator + record.Pwd + unlikelySeparator +
record.GitOriginRemote + unlikelySeparator + record.Host
// + strconv.Itoa(record.ExitCode) + unlikelySeparator
// DISPLAY
// DISPLAY > date
// TODO
// DISPLAY > location
location := ""
locationWithColor := ""
if differentHost {
location += record.Host + ":"
locationWithColor += highlightHost(record.Host) + ":"
}
const locationLenght = 30
// const locationLenght = 20 // small screenshots
pwdLength := locationLenght - len(location)
pwdTilde := strings.Replace(record.Pwd, record.Home, "~", 1)
location += leftCutPadString(pwdTilde, pwdLength)
if samePwd {
locationWithColor += highlightPwd(leftCutPadString(pwdTilde, pwdLength))
} else {
locationWithColor += leftCutPadString(pwdTilde, pwdLength)
}
// DISPLAY > flags
flags := ""
flagsWithColor := ""
if debug {
hitsStr := fmt.Sprintf("%.1f", score)
flags += " S" + hitsStr
}
if sameGitRepo {
flags += " G"
flagsWithColor += " " + highlightGit("G")
}
if errorExitStatus {
flags += " E" + strconv.Itoa(record.ExitCode)
flagsWithColor += " " + highlightWarn("E"+strconv.Itoa(record.ExitCode))
}
// NOTE: you can debug arbitrary metadata like this
// flags += " <" + record.GitOriginRemote + ">"
// flagsWithColor += " <" + record.GitOriginRemote + ">"
// DISPLAY > cmdline
it := item{
realtimeBefore: record.RealtimeBefore,
// cmd := "<" + strings.ReplaceAll(record.CmdLine, "\n", ";") + ">"
cmdLine := strings.ReplaceAll(record.CmdLine, "\n", ";")
cmdLineWithColor := strings.ReplaceAll(cmd, "\n", ";")
differentHost: differentHost,
host: record.Host,
home: record.Home,
samePwd: samePwd,
pwd: record.Pwd,
it := item{
location: location,
locationWithColor: locationWithColor,
flags: flags,
flagsWithColor: flagsWithColor,
sameGitRepo: sameGitRepo,
exitCode: record.ExitCode,
cmdLine: cmdLine,
cmdLineWithColor: cmdLineWithColor,
score: score,

@ -124,24 +124,32 @@ func runReshCli() (string, int) {
if err := g.SetKeybinding("", gocui.KeyArrowDown, gocui.ModNone, layout.Next); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyArrowUp, gocui.ModNone, layout.Prev); err != nil {
if err := g.SetKeybinding("", gocui.KeyCtrlN, gocui.ModNone, layout.Next); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
if err := g.SetKeybinding("", gocui.KeyArrowUp, gocui.ModNone, layout.Prev); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyCtrlG, gocui.ModNone, quit); err != nil {
if err := g.SetKeybinding("", gocui.KeyCtrlP, gocui.ModNone, layout.Prev); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyCtrlD, gocui.ModNone, quit); err != nil {
if err := g.SetKeybinding("", gocui.KeyArrowRight, gocui.ModNone, layout.SelectPaste); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyEnter, gocui.ModNone, layout.SelectExecute); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyArrowRight, gocui.ModNone, layout.SelectPaste); err != nil {
if err := g.SetKeybinding("", gocui.KeyCtrlG, gocui.ModNone, layout.AbortPaste); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyCtrlD, gocui.ModNone, quit); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding("", gocui.KeyCtrlR, gocui.ModNone, layout.SwitchModes); err != nil {
log.Panicln(err)
}
@ -161,6 +169,7 @@ type state struct {
data []item
rawData []rawItem
highlightedItem int
displayedItemsCount int
rawMode bool
@ -202,6 +211,17 @@ func (m manager) SelectPaste(g *gocui.Gui, v *gocui.View) error {
return nil
}
func (m manager) AbortPaste(g *gocui.Gui, v *gocui.View) error {
m.s.lock.Lock()
defer m.s.lock.Unlock()
if m.s.highlightedItem < len(m.s.data) {
m.s.output = v.Buffer()
m.s.exitCode = 0 // success
return gocui.ErrQuit
}
return nil
}
type dedupRecord struct {
dataIndex int
score float32
@ -316,10 +336,9 @@ func (m manager) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier)
}
func (m manager) Next(g *gocui.Gui, v *gocui.View) error {
_, y := g.Size()
m.s.lock.Lock()
defer m.s.lock.Unlock()
if m.s.highlightedItem < y {
if m.s.highlightedItem < m.s.displayedItemsCount-1 {
m.s.highlightedItem++
}
return nil
@ -393,64 +412,109 @@ func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
}
// SendCliMsg to daemon
func SendCliMsg(m msg.CliMsg, port string) msg.CliResponse {
recJSON, err := json.Marshal(m)
if err != nil {
log.Fatal("send err 1", err)
func getHeader(compactRendering bool) itemColumns {
date := "TIME "
host := "HOST:"
dir := "DIRECTORY"
if compactRendering {
dir = "DIR"
}
req, err := http.NewRequest("POST", "http://localhost:"+port+"/dump",
bytes.NewBuffer(recJSON))
if err != nil {
log.Fatal("send err 2", err)
flags := " FLAGS"
cmdLine := "COMMAND-LINE"
return itemColumns{
date: date,
dateWithColor: date,
host: host,
hostWithColor: host,
pwdTilde: dir,
samePwd: false,
flags: flags,
flagsWithColor: flags,
cmdLine: cmdLine,
cmdLineWithColor: cmdLine,
// score: i.score,
key: "_HEADERS_",
}
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 := msg.CliResponse{}
err = json.Unmarshal(body, &response)
if err != nil {
log.Fatal("unmarshal resp error: ", err)
}
return response
}
const smallTerminalTresholdWidth = 110
func (m manager) normalMode(g *gocui.Gui, v *gocui.View) error {
maxX, maxY := g.Size()
longestFlagsLen := 2 // at least 2
compactRenderingMode := false
if maxX < smallTerminalTresholdWidth {
compactRenderingMode = true
}
data := []itemColumns{}
header := getHeader(compactRenderingMode)
longestDateLen := len(header.date)
longestLocationLen := len(header.host) + len(header.pwdTilde)
longestFlagsLen := 2
maxPossibleMainViewHeight := maxY - 3 - 1 - 1 - 1 // - top box - header - status - help
for i, itm := range m.s.data {
if i == maxY {
break
}
if len(itm.flags) > longestFlagsLen {
longestFlagsLen = len(itm.flags)
ic := itm.drawItemColumns(compactRenderingMode)
data = append(data, ic)
if i > maxPossibleMainViewHeight {
// do not stretch columns because of results that will end up outside of the page
continue
}
if len(ic.date) > longestDateLen {
longestDateLen = len(ic.date)
}
if len(ic.host)+len(ic.pwdTilde) > longestLocationLen {
longestLocationLen = len(ic.host) + len(ic.pwdTilde)
}
if len(ic.flags) > longestFlagsLen {
longestFlagsLen = len(ic.flags)
}
}
maxLocationLen := maxX/7 + 8
if longestLocationLen > maxLocationLen {
longestLocationLen = maxLocationLen
}
for i, itm := range m.s.data {
if i == maxY {
if debug {
log.Println(maxY)
if m.s.highlightedItem >= len(m.s.data) {
m.s.highlightedItem = len(m.s.data) - 1
}
// status line
topBoxHeight := 3 // size of the query box up top
topBoxHeight++ // headers
realLineLength := maxX - 2
printedLineLength := maxX - 4
statusLine := m.s.data[m.s.highlightedItem].drawStatusLine(compactRenderingMode, printedLineLength, realLineLength)
var statusLineHeight int = len(statusLine) + 1 // help line
helpLineHeight := 1
const helpLine = "HELP: type to search, UP/DOWN to select, RIGHT to edit, ENTER to execute, CTRL+G to abort, CTRL+C/D to quit; " +
"TIP: when resh-cli is launched command line is used as initial search query"
mainViewHeight := maxY - topBoxHeight - statusLineHeight - helpLineHeight
m.s.displayedItemsCount = mainViewHeight
// header
// header := getHeader()
dispStr, _ := header.produceLine(longestDateLen, longestLocationLen, longestFlagsLen, true, true)
dispStr = doHighlightHeader(dispStr, maxX*2)
v.WriteString(dispStr + "\n")
var index int
for index < len(data) {
itm := data[index]
if index == mainViewHeight {
// page is full
break
}
displayStr, _ := itm.produceLine(longestFlagsLen)
if m.s.highlightedItem == i {
// use actual min requried length instead of 420 constant
displayStr = doHighlightString(displayStr, maxX*2)
displayStr, _ := itm.produceLine(longestDateLen, longestLocationLen, longestFlagsLen, false, true)
if m.s.highlightedItem == index {
// maxX * 2 because there are escape sequences that make it hard to tell the real string lenght
displayStr = doHighlightString(displayStr, maxX*3)
if debug {
log.Println("### HightlightedItem string :", displayStr)
}
@ -465,10 +529,17 @@ func (m manager) normalMode(g *gocui.Gui, v *gocui.View) error {
}
}
v.WriteString(displayStr + "\n")
// if m.s.highlightedItem == i {
// v.SetHighlight(m.s.highlightedItem, true)
// }
index++
}
// push the status line to the bottom of the page
for index < mainViewHeight {
v.WriteString("\n")
index++
}
for _, line := range statusLine {
v.WriteString(line)
}
v.WriteString(helpLine)
if debug {
log.Println("len(data) =", len(m.s.data))
log.Println("highlightedItem =", m.s.highlightedItem)
@ -478,6 +549,8 @@ func (m manager) normalMode(g *gocui.Gui, v *gocui.View) error {
func (m manager) rawMode(g *gocui.Gui, v *gocui.View) error {
maxX, maxY := g.Size()
topBoxSize := 3
m.s.displayedItemsCount = maxY - topBoxSize
for i, itm := range m.s.rawData {
if i == maxY {
@ -514,3 +587,37 @@ func (m manager) rawMode(g *gocui.Gui, v *gocui.View) error {
}
return nil
}
// SendCliMsg to daemon
func SendCliMsg(m msg.CliMsg, port string) msg.CliResponse {
recJSON, err := json.Marshal(m)
if err != nil {
log.Fatal("send err 1", err)
}
req, err := http.NewRequest("POST", "http://localhost:"+port+"/dump",
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 := msg.CliResponse{}
err = json.Unmarshal(body, &response)
if err != nil {
log.Fatal("unmarshal resp error: ", err)
}
return response
}

@ -2,6 +2,7 @@ package main
import (
"log"
"sort"
"strings"
)
@ -54,6 +55,7 @@ func newQueryFromString(queryInput string, host string, pwd string, gitOriginRem
log.Println("QUERY filtered terms =" + logStr)
log.Println("QUERY pwd =" + pwd)
}
sort.SliceStable(terms, func(i, j int) bool { return len(terms[i]) < len(terms[j]) })
return query{
terms: terms,
host: host,

@ -0,0 +1,181 @@
package main
import (
"strconv"
"time"
)
func formatTimeRelativeLongest(tm time.Time) string {
tmSince := time.Since(tm)
hrs := tmSince.Hours()
yrs := int(hrs / (365 * 24))
if yrs > 0 {
if yrs == 1 {
return "1 year ago"
}
return strconv.Itoa(yrs) + " years ago"
}
months := int(hrs / (30 * 24))
if months > 0 {
if months == 1 {
return "1 month ago"
}
return strconv.Itoa(months) + " months ago"
}
days := int(hrs / 24)
if days > 0 {
if days == 1 {
return "1 day ago"
}
return strconv.Itoa(days) + " days ago"
}
hrsInt := int(hrs)
if hrsInt > 0 {
if hrsInt == 1 {
return "1 hour ago"
}
return strconv.Itoa(hrsInt) + " hours ago"
}
mins := int(hrs*60) % 60
if mins > 0 {
if mins == 1 {
return "1 min ago"
}
return strconv.Itoa(mins) + " mins ago"
}
secs := int(hrs*60*60) % 60
if secs > 0 {
if secs == 1 {
return "1 sec ago"
}
return strconv.Itoa(secs) + " secs ago"
}
return "now"
}
func formatTimeRelativeLong(tm time.Time) string {
tmSince := time.Since(tm)
hrs := tmSince.Hours()
yrs := int(hrs / (365 * 24))
if yrs > 0 {
if yrs == 1 {
return "1 year"
}
return strconv.Itoa(yrs) + " years"
}
months := int(hrs / (30 * 24))
if months > 0 {
if months == 1 {
return "1 month"
}
return strconv.Itoa(months) + " months"
}
days := int(hrs / 24)
if days > 0 {
if days == 1 {
return "1 day"
}
return strconv.Itoa(days) + " days"
}
hrsInt := int(hrs)
if hrsInt > 0 {
if hrsInt == 1 {
return "1 hour"
}
return strconv.Itoa(hrsInt) + " hours"
}
mins := int(hrs*60) % 60
if mins > 0 {
if mins == 1 {
return "1 min"
}
return strconv.Itoa(mins) + " mins"
}
secs := int(hrs*60*60) % 60
if secs > 0 {
if secs == 1 {
return "1 sec"
}
return strconv.Itoa(secs) + " secs"
}
return "now"
}
func formatTimeMixedLongest(tm time.Time) string {
tmSince := time.Since(tm)
hrs := tmSince.Hours()
yrs := int(hrs / (365 * 24))
if yrs > 0 {
if yrs == 1 {
return "1 year ago"
}
return strconv.Itoa(yrs) + " years ago"
}
months := int(hrs / (30 * 24))
if months > 0 {
if months == 1 {
return "1 month ago"
}
return strconv.Itoa(months) + " months ago"
}
days := int(hrs / 24)
if days > 0 {
if days == 1 {
return "1 day ago"
}
return strconv.Itoa(days) + " days ago"
}
hrsInt := int(hrs)
mins := int(hrs*60) % 60
return strconv.Itoa(hrsInt) + ":" + strconv.Itoa(mins)
}
func formatTimeRelativeShort(tm time.Time) string {
tmSince := time.Since(tm)
hrs := tmSince.Hours()
yrs := int(hrs / (365 * 24))
if yrs > 0 {
return strconv.Itoa(yrs) + " Y"
}
months := int(hrs / (30 * 24))
if months > 0 {
return strconv.Itoa(months) + " M"
}
days := int(hrs / 24)
if days > 0 {
return strconv.Itoa(days) + " D"
}
hrsInt := int(hrs)
if hrsInt > 0 {
return strconv.Itoa(hrsInt) + " h"
}
mins := int(hrs*60) % 60
if mins > 0 {
return strconv.Itoa(mins) + " m"
}
secs := int(hrs*60*60) % 60
if secs > 0 {
return strconv.Itoa(secs) + " s"
}
return "now"
}
func formatTimeMixedShort(tm time.Time) string {
tmSince := time.Since(tm)
hrs := tmSince.Hours()
yrs := int(hrs / (365 * 24))
if yrs > 0 {
return strconv.Itoa(yrs) + " Y"
}
months := int(hrs / (30 * 24))
if months > 0 {
return strconv.Itoa(months) + " M"
}
days := int(hrs / 24)
if days > 0 {
return strconv.Itoa(days) + " D"
}
hrsInt := int(hrs)
mins := int(hrs*60) % 60
return strconv.Itoa(hrsInt) + ":" + strconv.Itoa(mins)
}

@ -53,6 +53,7 @@ func main() {
shell := flag.String("shell", "", "actual shell")
uname := flag.String("uname", "", "uname")
sessionID := flag.String("sessionId", "", "resh generated session id")
recordID := flag.String("recordId", "", "resh generated record id")
// recall metadata
recallActions := flag.String("recall-actions", "", "recall actions that took place before executing the command")
@ -195,6 +196,7 @@ func main() {
Shell: *shell,
Uname: *uname,
SessionID: *sessionID,
RecordID: *recordID,
// posix
Home: *home,

@ -44,6 +44,8 @@ func main() {
cmdLine := flag.String("cmdLine", "", "command line")
exitCode := flag.Int("exitCode", -1, "exit code")
sessionID := flag.String("sessionId", "", "resh generated session id")
recordID := flag.String("recordId", "", "resh generated record id")
shlvl := flag.Int("shlvl", -1, "$SHLVL")
shell := flag.String("shell", "", "actual shell")
@ -118,6 +120,7 @@ func main() {
CmdLine: *cmdLine,
ExitCode: *exitCode,
SessionID: *sessionID,
RecordID: *recordID,
Shlvl: *shlvl,
Shell: *shell,

@ -4,4 +4,4 @@ sesshistInitHistorySize = 1000
debug = false
bindArrowKeysBash = false
bindArrowKeysZsh = true
bindControlR = false
bindControlR = true

@ -22,3 +22,10 @@ func (h *Histcli) AddRecord(record records.Record) {
h.List = append(h.List, cli)
}
// AddCmdLine to the histcli
func (h *Histcli) AddCmdLine(cmdline string) {
cli := records.NewCliRecordFromCmdLine(cmdline)
h.List = append(h.List, cli)
}

@ -50,7 +50,13 @@ func New(input chan records.Record, sessionsToDrop chan string,
}
// load records from resh history, reverse, enrich and save
func (h *Histfile) loadFullRecords(recs []records.Record) {
func (h *Histfile) loadCliRecords(recs []records.Record) {
for _, cmdline := range h.bashCmdLines.List {
h.cliRecords.AddCmdLine(cmdline)
}
for _, cmdline := range h.zshCmdLines.List {
h.cliRecords.AddCmdLine(cmdline)
}
for i := len(recs) - 1; i >= 0; i-- {
rec := recs[i]
h.cliRecords.AddRecord(rec)
@ -82,7 +88,7 @@ func (h *Histfile) loadHistory(bashHistoryPath, zshHistoryPath string, maxInitHi
}
log.Println("histfile: Loading resh history from file ...")
history := records.LoadFromFile(h.historyPath, math.MaxInt32)
go h.loadFullRecords(history)
go h.loadCliRecords(history)
// NOTE: keeping this weird interface for now because we might use it in the future
// when we only load bash or zsh history
reshCmdLines := loadCmdLines(history)

@ -23,6 +23,7 @@ type BaseRecord struct {
Shell string `json:"shell"`
Uname string `json:"uname"`
SessionID string `json:"sessionId"`
RecordID string `json:"recordId"`
// posix
Home string `json:"home"`
@ -149,6 +150,7 @@ type SlimRecord struct {
// CliRecord used for sending records to RESH-CLI
type CliRecord struct {
IsRaw bool `json:"isRaw"`
SessionID string `json:"sessionId"`
CmdLine string `json:"cmdLine"`
@ -158,14 +160,23 @@ type CliRecord struct {
GitOriginRemote string `json:"gitOriginRemote"`
ExitCode int `json:"exitCode"`
// RealtimeBefore float64 `json:"realtimeBefore"`
RealtimeBefore float64 `json:"realtimeBefore"`
// RealtimeAfter float64 `json:"realtimeAfter"`
// RealtimeDuration float64 `json:"realtimeDuration"`
}
// NewCliRecordFromCmdLine from EnrichedRecord
func NewCliRecordFromCmdLine(cmdLine string) CliRecord {
return CliRecord{
IsRaw: true,
CmdLine: cmdLine,
}
}
// NewCliRecord from EnrichedRecord
func NewCliRecord(r EnrichedRecord) CliRecord {
return CliRecord{
IsRaw: false,
SessionID: r.SessionID,
CmdLine: r.CmdLine,
Host: r.Host,
@ -173,6 +184,7 @@ func NewCliRecord(r EnrichedRecord) CliRecord {
Home: r.Home,
GitOriginRemote: r.GitOriginRemote,
ExitCode: r.ExitCode,
RealtimeBefore: r.RealtimeBefore,
}
}
@ -231,6 +243,9 @@ func (r *Record) Merge(r2 Record) error {
if r.CmdLine != r2.CmdLine {
return errors.New("Records to merge are not parts of the same records - r1:" + r.CmdLine + " r2:" + r2.CmdLine)
}
if r.RecordID != r2.RecordID {
return errors.New("Records to merge do not have the same ID - r1:" + r.RecordID + " r2:" + r2.RecordID)
}
// r.RealtimeBefore != r2.RealtimeBefore - can't be used because of bash-preexec runs when it's not supposed to
r.ExitCode = r2.ExitCode
r.PwdAfter = r2.PwdAfter

@ -9,6 +9,7 @@ __resh_reset_variables() {
__RESH_HIST_RECALL_ACTIONS=""
__RESH_HIST_NO_PREFIX_MODE=0
__RESH_HIST_RECALL_STRATEGY=""
__RESH_RECORD_ID=$(__resh_get_uuid)
}
__resh_preexec() {
@ -81,6 +82,7 @@ __resh_collect() {
-shell "$__RESH_SHELL" \
-uname "$__RESH_UNAME" \
-sessionId "$__RESH_SESSION_ID" \
-recordId "$__RESH_RECORD_ID" \
-cols "$__RESH_COLS" \
-home "$__RESH_HOME" \
-lang "$__RESH_LANG" \
@ -157,6 +159,7 @@ __resh_precmd() {
-realtimeBefore "$__RESH_RT_BEFORE" \
-exitCode "$__RESH_EXIT_CODE" \
-sessionId "$__RESH_SESSION_ID" \
-recordId "$__RESH_RECORD_ID" \
-shell "$__RESH_SHELL" \
-shlvl "$__RESH_SHLVL" \
-pwdAfter "$__RESH_PWD_AFTER" \

Loading…
Cancel
Save