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. 326
      cmd/cli/item.go
  3. 209
      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 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 { func highlightSelected(str string) string {
// template "\033[3%d;%dm" // template "\033[3%d;%dm"
// invertGreen := "\033[32;7;1m" // invertGreen := "\033[32;7;1m"
@ -35,6 +48,13 @@ func highlightSelected(str string) string {
return invert + cleanHighlight(str) + end 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 { func highlightHost(str string) string {
// template "\033[3%d;%dm" // template "\033[3%d;%dm"
redNormal := "\033[31m" redNormal := "\033[31m"
@ -71,6 +91,13 @@ func highlightGit(str string) string {
return greenBold + cleanHighlight(str) + end 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 { func doHighlightString(str string, minLength int) string {
if len(str) < minLength { if len(str) < minLength {
str = str + strings.Repeat(" ", minLength-len(str)) str = str + strings.Repeat(" ", minLength-len(str))

@ -6,17 +6,49 @@ import (
"log" "log"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/curusarn/resh/pkg/records" "github.com/curusarn/resh/pkg/records"
) )
const itemLocationLenght = 30
type item struct { type item struct {
// dateWithColor string isRaw bool
// date string
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 // [host:]pwd
locationWithColor string hostWithColor string
location string host string
pwdTilde string
samePwd bool
//locationWithColor string
//location string
// [G] [E#] // [G] [E#]
flagsWithColor string flagsWithColor string
@ -25,7 +57,7 @@ type item struct {
cmdLineWithColor string cmdLineWithColor string
cmdLine string cmdLine string
score float64 // score float64
key string key string
// cmdLineRaw string // cmdLineRaw string
@ -36,27 +68,166 @@ func (i item) less(i2 item) bool {
return i.score > i2.score 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 := ""
line += i.locationWithColor if showDate {
line += i.flagsWithColor date := ic.date
flags := i.flags for len(date) < dateLength {
if flagLength < len(i.flags) { line += " "
log.Printf("produceLine can't specify line w/ flags shorter than the actual size. - len(flags) %v, requested %v\n", len(i.flags), flagLength) 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 { for len(flags) < flagLength {
line += " " line += " "
flags += " " flags += " "
} }
spacer := " " spacer := " "
if flagLength > 5 { if flagLength > 5 || header {
// use shorter spacer // use shorter spacer
// because there is likely a long flag like E130 in the view // because there is likely a long flag like E130 in the view
spacer = " " 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 return line, length
} }
@ -94,15 +265,26 @@ func properMatch(str, term, padChar string) bool {
// newItemFromRecordForQuery creates new item from record based on given query // newItemFromRecordForQuery creates new item from record based on given query
// returns error if the query doesn't match the record // returns error if the query doesn't match the record
func newItemFromRecordForQuery(record records.CliRecord, query query, debug bool) (item, error) { func newItemFromRecordForQuery(record records.CliRecord, query query, debug bool) (item, error) {
const hitScore = 1.0 // Use numbers that won't add up to same score for any number of query words
const hitScoreConsecutive = 0.1 const hitScore = 1.307
const properMatchScore = 0.3 const properMatchScore = 0.603
const actualPwdScore = 0.9 const hitScoreConsecutive = 0.002
const nonZeroExitCodeScorePenalty = 0.5
const sameGitRepoScore = 0.7 // Host penalty
// const sameGitRepoScoreExtra = 0.0 var actualPwdScore = 0.9
const differentHostScorePenalty = 0.2 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 // nonZeroExitCodeScorePenalty + differentHostScorePenalty
score := 0.0 score := 0.0
@ -114,7 +296,8 @@ func newItemFromRecordForQuery(record records.CliRecord, query query, debug bool
anyHit = true anyHit = true
if termHit == false { if termHit == false {
score += hitScore score += hitScore
} else { } else if len(term) > 1 {
// only count consecutive matches for queries longer than 1
score += hitScoreConsecutive score += hitScoreConsecutive
} }
termHit = true termHit = true
@ -122,9 +305,33 @@ func newItemFromRecordForQuery(record records.CliRecord, query query, debug bool
score += properMatchScore score += properMatchScore
} }
cmd = strings.ReplaceAll(cmd, term, highlightMatch(term)) 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 // actual pwd matches
// N terms can only produce: // N terms can only produce:
// -> N matches against the command // -> N matches against the command
@ -149,74 +356,27 @@ func newItemFromRecordForQuery(record records.CliRecord, query query, debug bool
differentHost = true differentHost = true
score -= differentHostScorePenalty score -= differentHostScorePenalty
} }
errorExitStatus := false // errorExitStatus := false
if record.ExitCode != 0 { if record.ExitCode != 0 {
errorExitStatus = true // errorExitStatus = true
score -= nonZeroExitCodeScorePenalty score -= nonZeroExitCodeScorePenalty
} }
if score <= 0 && !anyHit { if score <= 0 && !anyHit {
return item{}, errors.New("no match for given record and query") return item{}, errors.New("no match for given record and query")
} }
score += record.RealtimeBefore * timeScoreCoef
// KEY for deduplication it := item{
realtimeBefore: record.RealtimeBefore,
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
// cmd := "<" + strings.ReplaceAll(record.CmdLine, "\n", ";") + ">" differentHost: differentHost,
cmdLine := strings.ReplaceAll(record.CmdLine, "\n", ";") host: record.Host,
cmdLineWithColor := strings.ReplaceAll(cmd, "\n", ";") home: record.Home,
samePwd: samePwd,
pwd: record.Pwd,
it := item{ sameGitRepo: sameGitRepo,
location: location, exitCode: record.ExitCode,
locationWithColor: locationWithColor,
flags: flags,
flagsWithColor: flagsWithColor,
cmdLine: cmdLine, cmdLine: cmdLine,
cmdLineWithColor: cmdLineWithColor, cmdLineWithColor: cmdLineWithColor,
score: score, score: score,

@ -124,24 +124,32 @@ func runReshCli() (string, int) {
if err := g.SetKeybinding("", gocui.KeyArrowDown, gocui.ModNone, layout.Next); err != nil { if err := g.SetKeybinding("", gocui.KeyArrowDown, gocui.ModNone, layout.Next); err != nil {
log.Panicln(err) 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) 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) 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) 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) log.Panicln(err)
} }
if err := g.SetKeybinding("", gocui.KeyEnter, gocui.ModNone, layout.SelectExecute); err != nil { if err := g.SetKeybinding("", gocui.KeyEnter, gocui.ModNone, layout.SelectExecute); err != nil {
log.Panicln(err) 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) 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 { if err := g.SetKeybinding("", gocui.KeyCtrlR, gocui.ModNone, layout.SwitchModes); err != nil {
log.Panicln(err) log.Panicln(err)
} }
@ -161,6 +169,7 @@ type state struct {
data []item data []item
rawData []rawItem rawData []rawItem
highlightedItem int highlightedItem int
displayedItemsCount int
rawMode bool rawMode bool
@ -202,6 +211,17 @@ func (m manager) SelectPaste(g *gocui.Gui, v *gocui.View) error {
return nil 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 { type dedupRecord struct {
dataIndex int dataIndex int
score float32 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 { func (m manager) Next(g *gocui.Gui, v *gocui.View) error {
_, y := g.Size()
m.s.lock.Lock() m.s.lock.Lock()
defer m.s.lock.Unlock() defer m.s.lock.Unlock()
if m.s.highlightedItem < y { if m.s.highlightedItem < m.s.displayedItemsCount-1 {
m.s.highlightedItem++ m.s.highlightedItem++
} }
return nil return nil
@ -393,64 +412,109 @@ func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit return gocui.ErrQuit
} }
// SendCliMsg to daemon func getHeader(compactRendering bool) itemColumns {
func SendCliMsg(m msg.CliMsg, port string) msg.CliResponse { date := "TIME "
recJSON, err := json.Marshal(m) host := "HOST:"
if err != nil { dir := "DIRECTORY"
log.Fatal("send err 1", err) if compactRendering {
dir = "DIR"
}
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, err := http.NewRequest("POST", "http://localhost:"+port+"/dump", const smallTerminalTresholdWidth = 110
bytes.NewBuffer(recJSON))
if err != nil {
log.Fatal("send err 2", err)
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{} func (m manager) normalMode(g *gocui.Gui, v *gocui.View) error {
resp, err := client.Do(req) maxX, maxY := g.Size()
if err != nil {
log.Fatal("resh-daemon is not running :(")
}
defer resp.Body.Close() compactRenderingMode := false
body, err := ioutil.ReadAll(resp.Body) if maxX < smallTerminalTresholdWidth {
if err != nil { compactRenderingMode = true
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
}
func (m manager) normalMode(g *gocui.Gui, v *gocui.View) error { data := []itemColumns{}
maxX, maxY := g.Size()
longestFlagsLen := 2 // at least 2 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 { for i, itm := range m.s.data {
if i == maxY { if i == maxY {
break break
} }
if len(itm.flags) > longestFlagsLen { ic := itm.drawItemColumns(compactRenderingMode)
longestFlagsLen = len(itm.flags) 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 m.s.highlightedItem >= len(m.s.data) {
if i == maxY { m.s.highlightedItem = len(m.s.data) - 1
if debug {
log.Println(maxY)
} }
// 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 break
} }
displayStr, _ := itm.produceLine(longestFlagsLen)
if m.s.highlightedItem == i { displayStr, _ := itm.produceLine(longestDateLen, longestLocationLen, longestFlagsLen, false, true)
// use actual min requried length instead of 420 constant if m.s.highlightedItem == index {
displayStr = doHighlightString(displayStr, maxX*2) // maxX * 2 because there are escape sequences that make it hard to tell the real string lenght
displayStr = doHighlightString(displayStr, maxX*3)
if debug { if debug {
log.Println("### HightlightedItem string :", displayStr) log.Println("### HightlightedItem string :", displayStr)
} }
@ -465,10 +529,17 @@ func (m manager) normalMode(g *gocui.Gui, v *gocui.View) error {
} }
} }
v.WriteString(displayStr + "\n") v.WriteString(displayStr + "\n")
// if m.s.highlightedItem == i { index++
// v.SetHighlight(m.s.highlightedItem, true) }
// } // 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 { if debug {
log.Println("len(data) =", len(m.s.data)) log.Println("len(data) =", len(m.s.data))
log.Println("highlightedItem =", m.s.highlightedItem) 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 { func (m manager) rawMode(g *gocui.Gui, v *gocui.View) error {
maxX, maxY := g.Size() maxX, maxY := g.Size()
topBoxSize := 3
m.s.displayedItemsCount = maxY - topBoxSize
for i, itm := range m.s.rawData { for i, itm := range m.s.rawData {
if i == maxY { if i == maxY {
@ -514,3 +587,37 @@ func (m manager) rawMode(g *gocui.Gui, v *gocui.View) error {
} }
return nil 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 ( import (
"log" "log"
"sort"
"strings" "strings"
) )
@ -54,6 +55,7 @@ func newQueryFromString(queryInput string, host string, pwd string, gitOriginRem
log.Println("QUERY filtered terms =" + logStr) log.Println("QUERY filtered terms =" + logStr)
log.Println("QUERY pwd =" + pwd) log.Println("QUERY pwd =" + pwd)
} }
sort.SliceStable(terms, func(i, j int) bool { return len(terms[i]) < len(terms[j]) })
return query{ return query{
terms: terms, terms: terms,
host: host, 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") shell := flag.String("shell", "", "actual shell")
uname := flag.String("uname", "", "uname") uname := flag.String("uname", "", "uname")
sessionID := flag.String("sessionId", "", "resh generated session id") sessionID := flag.String("sessionId", "", "resh generated session id")
recordID := flag.String("recordId", "", "resh generated record id")
// recall metadata // recall metadata
recallActions := flag.String("recall-actions", "", "recall actions that took place before executing the command") recallActions := flag.String("recall-actions", "", "recall actions that took place before executing the command")
@ -195,6 +196,7 @@ func main() {
Shell: *shell, Shell: *shell,
Uname: *uname, Uname: *uname,
SessionID: *sessionID, SessionID: *sessionID,
RecordID: *recordID,
// posix // posix
Home: *home, Home: *home,

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

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

@ -22,3 +22,10 @@ func (h *Histcli) AddRecord(record records.Record) {
h.List = append(h.List, cli) 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 // 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-- { for i := len(recs) - 1; i >= 0; i-- {
rec := recs[i] rec := recs[i]
h.cliRecords.AddRecord(rec) h.cliRecords.AddRecord(rec)
@ -82,7 +88,7 @@ func (h *Histfile) loadHistory(bashHistoryPath, zshHistoryPath string, maxInitHi
} }
log.Println("histfile: Loading resh history from file ...") log.Println("histfile: Loading resh history from file ...")
history := records.LoadFromFile(h.historyPath, math.MaxInt32) 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 // NOTE: keeping this weird interface for now because we might use it in the future
// when we only load bash or zsh history // when we only load bash or zsh history
reshCmdLines := loadCmdLines(history) reshCmdLines := loadCmdLines(history)

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

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

Loading…
Cancel
Save