diff --git a/cmd/cli/highlight.go b/cmd/cli/highlight.go new file mode 100644 index 0000000..eabe539 --- /dev/null +++ b/cmd/cli/highlight.go @@ -0,0 +1,88 @@ +package main + +import ( + "strconv" + "strings" +) + +func cleanHighlight(str string) string { + prefix := "\033[" + + invert := "\033[7;1m" + invertGreen := "\033[32;7;1m" + end := "\033[0m" + replace := []string{invert, invertGreen, end} + for i := 30; i < 48; i++ { + base := prefix + strconv.Itoa(i) + normal := base + "m" + bold := base + ";1m" + replace = append(replace, normal, bold) + } + if strings.Contains(str, prefix) == false { + return str + } + for _, escSeq := range replace { + str = strings.ReplaceAll(str, escSeq, "") + } + return str +} + +func highlightSelected(str string) string { + // template "\033[3%d;%dm" + // invertGreen := "\033[32;7;1m" + invert := "\033[7;1m" + end := "\033[0m" + return invert + cleanHighlight(str) + end +} + +func highlightHost(str string) string { + // template "\033[3%d;%dm" + redNormal := "\033[31m" + end := "\033[0m" + return redNormal + cleanHighlight(str) + end +} + +func highlightPwd(str string) string { + // template "\033[3%d;%dm" + blueBold := "\033[34;1m" + end := "\033[0m" + return blueBold + cleanHighlight(str) + end +} + +func highlightMatch(str string) string { + // template "\033[3%d;%dm" + magentaBold := "\033[35;1m" + end := "\033[0m" + return magentaBold + cleanHighlight(str) + end +} + +func highlightWarn(str string) string { + // template "\033[3%d;%dm" + // orangeBold := "\033[33;1m" + redBold := "\033[31;1m" + end := "\033[0m" + return redBold + cleanHighlight(str) + end +} + +func highlightGit(str string) string { + // template "\033[3%d;%dm" + greenBold := "\033[32;1m" + end := "\033[0m" + return greenBold + cleanHighlight(str) + end +} + +func doHighlightString(str string, minLength int) string { + if len(str) < minLength { + str = str + strings.Repeat(" ", minLength-len(str)) + } + return highlightSelected(str) +} + +// EXTRAS + +func highlightModeTitle(str string) string { + // template "\033[3%d;%dm" + greenNormal := "\033[32;1m" + end := "\033[0m" + return greenNormal + cleanHighlight(str) + end +} diff --git a/cmd/cli/item.go b/cmd/cli/item.go new file mode 100644 index 0000000..54e9fa3 --- /dev/null +++ b/cmd/cli/item.go @@ -0,0 +1,282 @@ +package main + +import ( + "errors" + "fmt" + "log" + "strconv" + "strings" + + "github.com/curusarn/resh/pkg/records" +) + +type item struct { + // dateWithColor string + // date string + + // [host:]pwd + locationWithColor string + location string + + // [G] [E#] + flagsWithColor string + flags string + + cmdLineWithColor string + cmdLine string + + score float64 + + key string + // cmdLineRaw string +} + +func (i item) less(i2 item) bool { + // reversed order + return i.score > i2.score +} + +func (i item) produceLine(flagLength int) (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) + } + for len(flags) < flagLength { + line += " " + flags += " " + } + spacer := " " + if flagLength > 5 { + // use shorter spacer + // because there is likely a long flag like E130 in the view + spacer = " " + } + line += spacer + i.cmdLineWithColor + + length := len(i.location) + flagLength + len(spacer) + len(i.cmdLine) + return line, length +} + +func leftCutPadString(str string, newLen int) string { + dots := "…" + strLen := len(str) + if newLen > strLen { + return strings.Repeat(" ", newLen-strLen) + str + } else if newLen < strLen { + return dots + str[strLen-newLen+1:] + } + return str +} + +func rightCutPadString(str string, newLen int) string { + dots := "…" + strLen := len(str) + if newLen > strLen { + return str + strings.Repeat(" ", newLen-strLen) + } else if newLen < strLen { + return str[:newLen-1] + dots + } + return str +} + +// proper match for path is when whole directory is matched +// proper match for command is when term matches word delimeted by whitespace +func properMatch(str, term, padChar string) bool { + if strings.Contains(padChar+str+padChar, padChar+term+padChar) { + return true + } + return false +} + +// newItemFromRecordForQuery creates new item from record based on given query +// returns error if the query doesn't match the record +func newItemFromRecordForQuery(record records.EnrichedRecord, 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 + + // nonZeroExitCodeScorePenalty + differentHostScorePenalty + + score := 0.0 + anyHit := false + cmd := record.CmdLine + for _, term := range query.terms { + termHit := false + if strings.Contains(record.CmdLine, term) { + anyHit = true + if termHit == false { + score += hitScore + } else { + score += hitScoreConsecutive + } + termHit = true + if properMatch(cmd, term, " ") { + score += properMatchScore + } + cmd = strings.ReplaceAll(cmd, term, highlightMatch(term)) + // NO continue + } + } + // actual pwd matches + // N terms can only produce: + // -> N matches against the command + // -> 1 extra match for the actual directory match + sameGitRepo := false + if query.gitOriginRemote != "" && query.gitOriginRemote == record.GitOriginRemote { + sameGitRepo = true + } + + samePwd := false + if record.Pwd == query.pwd { + anyHit = true + samePwd = true + score += actualPwdScore + } else if sameGitRepo { + anyHit = true + score += sameGitRepoScore + } + + differentHost := false + if record.Host != query.host { + differentHost = true + score -= differentHostScorePenalty + } + errorExitStatus := false + if record.ExitCode != 0 { + errorExitStatus = true + score -= nonZeroExitCodeScorePenalty + } + if score <= 0 && !anyHit { + return item{}, errors.New("no match for given record and query") + } + + // 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 = 35 + // 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", ";") + ">" + cmdLine := strings.ReplaceAll(record.CmdLine, "\n", ";") + cmdLineWithColor := strings.ReplaceAll(cmd, "\n", ";") + + it := item{ + location: location, + locationWithColor: locationWithColor, + flags: flags, + flagsWithColor: flagsWithColor, + cmdLine: cmdLine, + cmdLineWithColor: cmdLineWithColor, + score: score, + key: key, + } + return it, nil +} + +type rawItem struct { + cmdLineWithColor string + cmdLine string + + hits float64 + + key string + // cmdLineRaw string +} + +// newRawItemFromRecordForQuery creates new item from record based on given query +// returns error if the query doesn't match the record +func newRawItemFromRecordForQuery(record records.EnrichedRecord, terms []string, debug bool) (rawItem, error) { + const hitScore = 1.0 + const hitScoreConsecutive = 0.1 + const properMatchScore = 0.3 + + hits := 0.0 + anyHit := false + cmd := record.CmdLine + for _, term := range terms { + termHit := false + if strings.Contains(record.CmdLine, term) { + anyHit = true + if termHit == false { + hits += hitScore + } else { + hits += hitScoreConsecutive + } + termHit = true + if properMatch(cmd, term, " ") { + hits += properMatchScore + } + cmd = strings.ReplaceAll(cmd, term, highlightMatch(term)) + // NO continue + } + } + _ = anyHit + // KEY for deduplication + key := record.CmdLine + + // DISPLAY > cmdline + + // cmd := "<" + strings.ReplaceAll(record.CmdLine, "\n", ";") + ">" + cmdLine := strings.ReplaceAll(record.CmdLine, "\n", ";") + cmdLineWithColor := strings.ReplaceAll(cmd, "\n", ";") + + it := rawItem{ + cmdLine: cmdLine, + cmdLineWithColor: cmdLineWithColor, + hits: hits, + key: key, + } + return it, nil +} diff --git a/cmd/cli/main.go b/cmd/cli/main.go index 9513e75..f61b5ad 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -3,7 +3,6 @@ package main import ( "bytes" "encoding/json" - "errors" "flag" "fmt" "io/ioutil" @@ -67,15 +66,23 @@ func runReshCli() (string, int) { } sessionID := flag.String("sessionID", "", "resh generated session id") + host := flag.String("host", "", "host") pwd := flag.String("pwd", "", "present working directory") + gitOriginRemote := flag.String("gitOriginRemote", "DEFAULT", "git origin remote") query := flag.String("query", "", "search query") flag.Parse() if *sessionID == "" { - fmt.Println("Error: you need to specify sessionId") + log.Println("Error: you need to specify sessionId") + } + if *host == "" { + log.Println("Error: you need to specify HOST") } if *pwd == "" { - fmt.Println("Error: you need to specify PWD") + log.Println("Error: you need to specify PWD") + } + if *gitOriginRemote == "DEFAULT" { + log.Println("Error: you need to specify gitOriginRemote") } g, err := gocui.NewGui(gocui.OutputNormal, false) @@ -85,7 +92,7 @@ func runReshCli() (string, int) { defer g.Close() g.Cursor = true - g.SelFgColor = gocui.ColorGreen + // g.SelFgColor = gocui.ColorGreen // g.SelBgColor = gocui.ColorGreen g.Highlight = true @@ -102,10 +109,12 @@ func runReshCli() (string, int) { } layout := manager{ - sessionID: *sessionID, - pwd: *pwd, - config: config, - s: &st, + sessionID: *sessionID, + host: *host, + pwd: *pwd, + gitOriginRemote: records.NormalizeGitRemote(*gitOriginRemote), + config: config, + s: &st, } g.SetManager(layout) @@ -133,8 +142,12 @@ func runReshCli() (string, int) { if err := g.SetKeybinding("", gocui.KeyArrowRight, gocui.ModNone, layout.SelectPaste); err != nil { log.Panicln(err) } + if err := g.SetKeybinding("", gocui.KeyCtrlR, gocui.ModNone, layout.SwitchModes); err != nil { + log.Panicln(err) + } layout.UpdateData(*query) + layout.UpdateRawData(*query) err = g.MainLoop() if err != nil && gocui.IsQuit(err) == false { log.Panicln(err) @@ -142,289 +155,15 @@ func runReshCli() (string, int) { return layout.s.output, layout.s.exitCode } -func leftCutPadString(str string, newLen int) string { - dots := "…" - strLen := len(str) - if newLen > strLen { - return strings.Repeat(" ", newLen-strLen) + str - } else if newLen < strLen { - return dots + str[strLen-newLen+1:] - } - return str -} - -func rightCutPadString(str string, newLen int) string { - dots := "…" - strLen := len(str) - if newLen > strLen { - return str + strings.Repeat(" ", newLen-strLen) - } else if newLen < strLen { - return str[:newLen-1] + dots - } - return str -} - -func cleanHighlight(str string) string { - prefix := "\033[" - - invert := "\033[32;7;1m" - end := "\033[0m" - blueBold := "\033[34;1m" - redBold := "\033[31;1m" - repace := []string{invert, end, blueBold, redBold} - if strings.Contains(str, prefix) == false { - return str - } - for _, escSeq := range repace { - str = strings.ReplaceAll(str, escSeq, "") - } - return str -} - -func highlightSelected(str string) string { - // template "\033[3%d;%dm" - invert := "\033[32;7;1m" - end := "\033[0m" - return invert + cleanHighlight(str) + end -} - -func highlightMatchAlternative(str string) string { - // template "\033[3%d;%dm" - blueBold := "\033[34;1m" - end := "\033[0m" - return blueBold + cleanHighlight(str) + end -} - -func highlightMatch(str string) string { - // template "\033[3%d;%dm" - redBold := "\033[31;1m" - end := "\033[0m" - return redBold + cleanHighlight(str) + end -} - -func toString(record records.EnrichedRecord, lineLength int) string { - dirColWidth := 24 // make this dynamic somehow - return leftCutPadString(strings.Replace(record.Pwd, record.Home, "~", 1), dirColWidth) + " " + - rightCutPadString(strings.ReplaceAll(record.CmdLine, "\n", "; "), lineLength-dirColWidth-3) + "\n" -} - -type query struct { - terms []string - pwd string - // pwdTilde string -} - -func isValidTerm(term string) bool { - if len(term) == 0 { - return false - } - if strings.Contains(term, " ") { - return false - } - return true -} - -func filterTerms(terms []string) []string { - var newTerms []string - for _, term := range terms { - if isValidTerm(term) { - newTerms = append(newTerms, term) - } - } - return newTerms -} - -func newQueryFromString(queryInput string, pwd string) query { - if debug { - log.Println("QUERY input = <" + queryInput + ">") - } - terms := strings.Fields(queryInput) - var logStr string - for _, term := range terms { - logStr += " <" + term + ">" - } - if debug { - log.Println("QUERY raw terms =" + logStr) - } - terms = filterTerms(terms) - logStr = "" - for _, term := range terms { - logStr += " <" + term + ">" - } - if debug { - log.Println("QUERY filtered terms =" + logStr) - log.Println("QUERY pwd =" + pwd) - } - return query{terms: terms, pwd: pwd} -} - -type item struct { - // record records.EnrichedRecord - display string - displayNoColor string - cmdLine string - pwd string - pwdTilde string - hits float64 -} - -func (i item) less(i2 item) bool { - // reversed order - return i.hits > i2.hits -} - -// used for deduplication -func (i item) key() string { - unlikelySeparator := "|||||" - return i.cmdLine + unlikelySeparator + i.pwd -} - -// func (i item) equals(i2 item) bool { -// return i.cmdLine == i2.cmdLine && i.pwd == i2.pwd -// } - -// proper match for path is when whole directory is matched -// proper match for command is when term matches word delimeted by whitespace -func properMatch(str, term, padChar string) bool { - if strings.Contains(padChar+str+padChar, padChar+term+padChar) { - return true - } - return false -} - -// newItemFromRecordForQuery creates new item from record based on given query -// returns error if the query doesn't match the record -func newItemFromRecordForQuery(record records.EnrichedRecord, query query, debug bool) (item, error) { - // TODO: use color to highlight matches - const hitScore = 1.0 - const hitScoreConsecutive = 0.1 - const properMatchScore = 0.3 - const actualPwdScore = 0.9 - const actualPwdScoreExtra = 0.2 // this + hitScore > actualPwdScore - const nonZeroExitCodeScorePenalty = 0.8 // this < min(hitScore, actualPwdScore) - - hits := 0.0 - if record.ExitCode != 0 { - hits -= nonZeroExitCodeScorePenalty - } - cmd := record.CmdLine - pwdTilde := strings.Replace(record.Pwd, record.Home, "~", 1) - pwdDisp := leftCutPadString(pwdTilde, 25) - pwdRawDisp := leftCutPadString(record.Pwd, 25) - var useRawPwd bool - var dirHit bool - for _, term := range query.terms { - termHit := false - if strings.Contains(record.CmdLine, term) { - if termHit == false { - hits += hitScore - } else { - hits += hitScoreConsecutive - } - termHit = true - if properMatch(cmd, term, " ") { - hits += properMatchScore - } - cmd = strings.ReplaceAll(cmd, term, highlightMatch(term)) - // NO continue - } - if strings.Contains(pwdTilde, term) { - if termHit == false { - hits += hitScore - } else { - hits += hitScoreConsecutive - } - termHit = true - if properMatch(pwdTilde, term, "/") { - hits += properMatchScore - } - pwdDisp = strings.ReplaceAll(pwdDisp, term, highlightMatch(term)) - pwdRawDisp = strings.ReplaceAll(pwdRawDisp, term, highlightMatch(term)) - dirHit = true - } else if strings.Contains(record.Pwd, term) { - if termHit == false { - hits += hitScore - } else { - hits += hitScoreConsecutive - } - termHit = true - if properMatch(pwdTilde, term, "/") { - hits += properMatchScore - } - pwdRawDisp = strings.ReplaceAll(pwdRawDisp, term, highlightMatch(term)) - dirHit = true - useRawPwd = true - } - // if strings.Contains(record.GitOriginRemote, term) { - // hits++ - // } - } - // actual pwd matches - // only use if there was no directory match on any of the terms - // N terms can only produce: - // -> N matches against the command - // -> N matches against the directory - // -> 1 extra match for the actual directory match - if record.Pwd == query.pwd { - if dirHit { - hits += actualPwdScoreExtra - } else { - hits += actualPwdScore - } - pwdDisp = highlightMatchAlternative(pwdDisp) - // pwdRawDisp = highlightMatchAlternative(pwdRawDisp) - useRawPwd = false - } - if hits <= 0 { - return item{}, errors.New("no match for given record and query") - } - display := "" - // pwd := leftCutPadString("<"+pwdTilde+">", 20) - if useRawPwd { - display += pwdRawDisp - } else { - display += pwdDisp - } - if debug { - hitsStr := fmt.Sprintf("%.1f", hits) - hitsDisp := " " + hitsStr + " " - display += hitsDisp - } else { - display += " " - } - // cmd := "<" + strings.ReplaceAll(record.CmdLine, "\n", ";") + ">" - cmd = strings.ReplaceAll(cmd, "\n", ";") - display += cmd - // itDummy := item{ - // cmdLine: record.CmdLine, - // pwd: record.Pwd, - // } - // + " #K:<" + itDummy.key() + ">" - - it := item{ - display: display, - displayNoColor: display, - cmdLine: record.CmdLine, - pwd: record.Pwd, - pwdTilde: pwdTilde, - hits: hits, - } - return it, nil -} - -func doHighlightString(str string, minLength int) string { - if len(str) < minLength { - str = str + strings.Repeat(" ", minLength-len(str)) - } - return highlightSelected(str) -} - type state struct { lock sync.Mutex fullRecords []records.EnrichedRecord data []item + rawData []rawItem highlightedItem int + rawMode bool + initialQuery string output string @@ -432,9 +171,11 @@ type state struct { } type manager struct { - sessionID string - pwd string - config cfg.Config + sessionID string + host string + pwd string + gitOriginRemote string + config cfg.Config s *state } @@ -461,15 +202,20 @@ func (m manager) SelectPaste(g *gocui.Gui, v *gocui.View) error { return nil } +type dedupRecord struct { + dataIndex int + score float32 +} + func (m manager) UpdateData(input string) { if debug { log.Println("EDIT start") log.Println("len(fullRecords) =", len(m.s.fullRecords)) log.Println("len(data) =", len(m.s.data)) } - query := newQueryFromString(input, m.pwd) + query := newQueryFromString(input, m.host, m.pwd, m.gitOriginRemote) var data []item - itemSet := make(map[string]bool) + itemSet := make(map[string]int) m.s.lock.Lock() defer m.s.lock.Unlock() for _, rec := range m.s.fullRecords { @@ -479,19 +225,25 @@ func (m manager) UpdateData(input string) { // log.Println(" * continue (no match)", rec.Pwd) continue } - if itemSet[itm.key()] { - // log.Println(" * continue (already present)", itm.key(), itm.pwd) + if idx, ok := itemSet[itm.key]; ok { + // duplicate found + if data[idx].score >= itm.score { + // skip duplicate item + continue + } + // update duplicate item + data[idx] = itm continue } - itemSet[itm.key()] = true + // add new item + itemSet[itm.key] = len(data) data = append(data, itm) - // log.Println("DATA =", itm.display) } if debug { log.Println("len(tmpdata) =", len(data)) } sort.SliceStable(data, func(p, q int) bool { - return data[p].hits > data[q].hits + return data[p].score > data[q].score }) m.s.data = nil for _, itm := range data { @@ -508,8 +260,58 @@ func (m manager) UpdateData(input string) { } } +func (m manager) UpdateRawData(input string) { + if debug { + log.Println("EDIT start") + log.Println("len(fullRecords) =", len(m.s.fullRecords)) + log.Println("len(data) =", len(m.s.data)) + } + query := getRawTermsFromString(input) + var data []rawItem + itemSet := make(map[string]bool) + m.s.lock.Lock() + defer m.s.lock.Unlock() + for _, rec := range m.s.fullRecords { + itm, err := newRawItemFromRecordForQuery(rec, query, m.config.Debug) + if err != nil { + // records didn't match the query + // log.Println(" * continue (no match)", rec.Pwd) + continue + } + if itemSet[itm.key] { + // log.Println(" * continue (already present)", itm.key(), itm.pwd) + continue + } + itemSet[itm.key] = true + data = append(data, itm) + // log.Println("DATA =", itm.display) + } + if debug { + log.Println("len(tmpdata) =", len(data)) + } + sort.SliceStable(data, func(p, q int) bool { + return data[p].hits > data[q].hits + }) + m.s.rawData = nil + for _, itm := range data { + if len(m.s.rawData) > 420 { + break + } + m.s.rawData = append(m.s.rawData, itm) + } + m.s.highlightedItem = 0 + if debug { + log.Println("len(fullRecords) =", len(m.s.fullRecords)) + log.Println("len(data) =", len(m.s.data)) + log.Println("EDIT end") + } +} func (m manager) Edit(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { gocui.DefaultEditor.Edit(v, key, ch, mod) + if m.s.rawMode { + m.UpdateRawData(v.Buffer()) + return + } m.UpdateData(v.Buffer()) } @@ -532,6 +334,19 @@ func (m manager) Prev(g *gocui.Gui, v *gocui.View) error { return nil } +func (m manager) SwitchModes(g *gocui.Gui, v *gocui.View) error { + m.s.lock.Lock() + m.s.rawMode = !m.s.rawMode + m.s.lock.Unlock() + + if m.s.rawMode { + m.UpdateRawData(v.Buffer()) + return nil + } + m.UpdateData(v.Buffer()) + return nil +} + func (m manager) Layout(g *gocui.Gui) error { var b byte maxX, maxY := g.Size() @@ -543,7 +358,11 @@ func (m manager) Layout(g *gocui.Gui) error { v.Editable = true v.Editor = m - v.Title = "resh cli" + if m.s.rawMode { + v.Title = " RESH CLI - NON-CONTEXTUAL \"RAW\" MODE - (CTRL+R to switch BACK) " + } else { + v.Title = " RESH CLI - CONTEXTUAL MODE - (CTRL+R to switch to RAW MODE) " + } g.SetCurrentView("input") @@ -564,40 +383,10 @@ func (m manager) Layout(g *gocui.Gui) error { v.Clear() v.Rewind() - for i, itm := range m.s.data { - if i == maxY { - if debug { - log.Println(maxY) - } - break - } - displayStr := itm.display - if m.s.highlightedItem == i { - // use actual min requried length instead of 420 constant - displayStr = doHighlightString(displayStr, 420) - if debug { - log.Println("### HightlightedItem string :", displayStr) - } - } else if debug { - log.Println(displayStr) - } - if strings.Contains(displayStr, "\n") { - log.Println("display string contained \\n") - displayStr = strings.ReplaceAll(displayStr, "\n", "#") - if debug { - log.Println("display string contained \\n") - } - } - v.WriteString(displayStr + "\n") - // if m.s.highlightedItem == i { - // v.SetHighlight(m.s.highlightedItem, true) - // } + if m.s.rawMode { + return m.rawMode(g, v) } - if debug { - log.Println("len(data) =", len(m.s.data)) - log.Println("highlightedItem =", m.s.highlightedItem) - } - return nil + return m.normalMode(g, v) } func quit(g *gocui.Gui, v *gocui.View) error { @@ -637,3 +426,91 @@ func SendDumpMsg(m msg.DumpMsg, port string) msg.DumpResponse { } return response } + +func (m manager) normalMode(g *gocui.Gui, v *gocui.View) error { + maxX, maxY := g.Size() + + longestFlagsLen := 2 // at least 2 + for i, itm := range m.s.data { + if i == maxY { + break + } + if len(itm.flags) > longestFlagsLen { + longestFlagsLen = len(itm.flags) + } + } + + for i, itm := range m.s.data { + if i == maxY { + if debug { + log.Println(maxY) + } + break + } + displayStr, _ := itm.produceLine(longestFlagsLen) + if m.s.highlightedItem == i { + // use actual min requried length instead of 420 constant + displayStr = doHighlightString(displayStr, maxX*2) + if debug { + log.Println("### HightlightedItem string :", displayStr) + } + } else if debug { + log.Println(displayStr) + } + if strings.Contains(displayStr, "\n") { + log.Println("display string contained \\n") + displayStr = strings.ReplaceAll(displayStr, "\n", "#") + if debug { + log.Println("display string contained \\n") + } + } + v.WriteString(displayStr + "\n") + // if m.s.highlightedItem == i { + // v.SetHighlight(m.s.highlightedItem, true) + // } + } + if debug { + log.Println("len(data) =", len(m.s.data)) + log.Println("highlightedItem =", m.s.highlightedItem) + } + return nil +} + +func (m manager) rawMode(g *gocui.Gui, v *gocui.View) error { + maxX, maxY := g.Size() + + for i, itm := range m.s.rawData { + if i == maxY { + if debug { + log.Println(maxY) + } + break + } + displayStr := itm.cmdLineWithColor + if m.s.highlightedItem == i { + // use actual min requried length instead of 420 constant + displayStr = doHighlightString(displayStr, maxX*2) + if debug { + log.Println("### HightlightedItem string :", displayStr) + } + } else if debug { + log.Println(displayStr) + } + if strings.Contains(displayStr, "\n") { + log.Println("display string contained \\n") + displayStr = strings.ReplaceAll(displayStr, "\n", "#") + if debug { + log.Println("display string contained \\n") + } + } + v.WriteString(displayStr + "\n") + // if m.s.highlightedItem == i { + // v.SetHighlight(m.s.highlightedItem, true) + // } + } + if debug { + log.Println("len(data) =", len(m.s.data)) + log.Println("highlightedItem =", m.s.highlightedItem) + } + return nil +} diff --git a/cmd/cli/query.go b/cmd/cli/query.go new file mode 100644 index 0000000..df72873 --- /dev/null +++ b/cmd/cli/query.go @@ -0,0 +1,83 @@ +package main + +import ( + "log" + "strings" +) + +type query struct { + terms []string + host string + pwd string + gitOriginRemote string + // pwdTilde string +} + +func isValidTerm(term string) bool { + if len(term) == 0 { + return false + } + if strings.Contains(term, " ") { + return false + } + return true +} + +func filterTerms(terms []string) []string { + var newTerms []string + for _, term := range terms { + if isValidTerm(term) { + newTerms = append(newTerms, term) + } + } + return newTerms +} + +func newQueryFromString(queryInput string, host string, pwd string, gitOriginRemote string) query { + if debug { + log.Println("QUERY input = <" + queryInput + ">") + } + terms := strings.Fields(queryInput) + var logStr string + for _, term := range terms { + logStr += " <" + term + ">" + } + if debug { + log.Println("QUERY raw terms =" + logStr) + } + terms = filterTerms(terms) + logStr = "" + for _, term := range terms { + logStr += " <" + term + ">" + } + if debug { + log.Println("QUERY filtered terms =" + logStr) + log.Println("QUERY pwd =" + pwd) + } + return query{ + terms: terms, + host: host, + pwd: pwd, + gitOriginRemote: gitOriginRemote, + } +} + +func getRawTermsFromString(queryInput string) []string { + if debug { + log.Println("QUERY input = <" + queryInput + ">") + } + terms := strings.Fields(queryInput) + var logStr string + for _, term := range terms { + logStr += " <" + term + ">" + } + if debug { + log.Println("QUERY raw terms =" + logStr) + } + terms = filterTerms(terms) + logStr = "" + for _, term := range terms { + logStr += " <" + term + ">" + } + return terms +} diff --git a/pkg/records/records.go b/pkg/records/records.go index 15d5ee5..0d0be91 100644 --- a/pkg/records/records.go +++ b/pkg/records/records.go @@ -169,25 +169,26 @@ func (r EnrichedRecord) ToString() (string, error) { // Enriched - returnd enriched record func Enriched(r Record) EnrichedRecord { record := EnrichedRecord{Record: r} + // normlize git remote + record.GitOriginRemote = NormalizeGitRemote(record.GitOriginRemote) + record.GitOriginRemoteAfter = NormalizeGitRemote(record.GitOriginRemoteAfter) // Get command/first word from commandline var err error - record.Command, record.FirstWord, err = GetCommandAndFirstWord(r.CmdLine) + err = r.Validate() if err != nil { - record.Errors = append(record.Errors, "GetCommandAndFirstWord error:"+err.Error()) + record.Errors = append(record.Errors, "Validate error:"+err.Error()) // rec, _ := record.ToString() // log.Println("Invalid command:", rec) record.Invalid = true - return record } - err = r.Validate() + record.Command, record.FirstWord, err = GetCommandAndFirstWord(r.CmdLine) if err != nil { - record.Errors = append(record.Errors, "Validate error:"+err.Error()) + record.Errors = append(record.Errors, "GetCommandAndFirstWord error:"+err.Error()) // rec, _ := record.ToString() // log.Println("Invalid command:", rec) - record.Invalid = true + record.Invalid = true // should this be really invalid ? } return record - // TODO: Detect and mark simple commands r.Simple } // Merge two records (part1 - collect + part2 - postcollect) @@ -327,6 +328,14 @@ func GetCommandAndFirstWord(cmdLine string) (string, string, error) { return "ERROR", "ERROR", errors.New("this should not happen - contact developer ;)") } +// NormalizeGitRemote func +func NormalizeGitRemote(gitRemote string) string { + if strings.HasSuffix(gitRemote, ".git") { + return gitRemote[:len(gitRemote)-4] + } + return gitRemote +} + // DistParams is used to supply params to Enrichedrecords.DistanceTo() type DistParams struct { ExitCode float64 diff --git a/scripts/reshctl.sh b/scripts/reshctl.sh index ef4809e..750fc6d 100644 --- a/scripts/reshctl.sh +++ b/scripts/reshctl.sh @@ -108,7 +108,8 @@ __resh_unbind_all() { # wrapper for resh-cli for calling resh directly resh() { local buffer - buffer=$(resh-cli --sessionID "$__RESH_SESSION_ID" --pwd "$PWD") + local git_remote; git_remote="$(git remote get-url origin 2>/dev/null)" + buffer=$(resh-cli --sessionID "$__RESH_SESSION_ID" --host "$HOST" --pwd "$PWD" --gitOriginRemote "$git_remote") status_code=$? if [ $status_code = 111 ]; then # execute diff --git a/scripts/widgets.sh b/scripts/widgets.sh index 79e2cd8..b66cd31 100644 --- a/scripts/widgets.sh +++ b/scripts/widgets.sh @@ -100,7 +100,8 @@ __resh_widget_control_R() { __RESH_HIST_RECALL_ACTIONS="$__RESH_HIST_RECALL_ACTIONS|||control_R:$BUFFER" local status_code - BUFFER=$(resh-cli --sessionID "$__RESH_SESSION_ID" --pwd "$PWD" --query "$BUFFER") + local git_remote; git_remote="$(git remote get-url origin 2>/dev/null)" + BUFFER=$(resh-cli --sessionID "$__RESH_SESSION_ID" --host "$HOST" --pwd "$PWD" --gitOriginRemote "$git_remote" --query "$BUFFER") status_code=$? if [ $status_code = 111 ]; then # execute