mirror of https://github.com/curusarn/resh
parent
282f3bd759
commit
78d9d51484
@ -0,0 +1,79 @@ |
|||||||
|
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) |
||||||
|
} |
||||||
@ -0,0 +1,278 @@ |
|||||||
|
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 |
||||||
|
|
||||||
|
hits float64 |
||||||
|
|
||||||
|
key string |
||||||
|
// cmdLineRaw string
|
||||||
|
} |
||||||
|
|
||||||
|
func (i item) less(i2 item) bool { |
||||||
|
// reversed order
|
||||||
|
return i.hits > i2.hits |
||||||
|
} |
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
hits := 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 { |
||||||
|
hits += hitScore |
||||||
|
} else { |
||||||
|
hits += hitScoreConsecutive |
||||||
|
} |
||||||
|
termHit = true |
||||||
|
if properMatch(cmd, term, " ") { |
||||||
|
hits += 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 |
||||||
|
hits += actualPwdScore |
||||||
|
} else if sameGitRepo { |
||||||
|
anyHit = true |
||||||
|
hits += sameGitRepoScore |
||||||
|
} |
||||||
|
|
||||||
|
differentHost := false |
||||||
|
if record.Host != query.host { |
||||||
|
differentHost = true |
||||||
|
hits -= differentHostScorePenalty |
||||||
|
} |
||||||
|
errorExitStatus := false |
||||||
|
if record.ExitCode != 0 { |
||||||
|
errorExitStatus = true |
||||||
|
hits -= nonZeroExitCodeScorePenalty |
||||||
|
} |
||||||
|
if hits <= 0 && !anyHit { |
||||||
|
return item{}, errors.New("no match for given record and query") |
||||||
|
} |
||||||
|
|
||||||
|
// KEY for deduplication
|
||||||
|
|
||||||
|
unlikelySeparator := "|||||" |
||||||
|
key := record.CmdLine + unlikelySeparator + record.Pwd + |
||||||
|
unlikelySeparator + strconv.Itoa(record.ExitCode) + unlikelySeparator + |
||||||
|
record.GitOriginRemote + unlikelySeparator + record.Host |
||||||
|
|
||||||
|
// DISPLAY
|
||||||
|
// DISPLAY > date
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
// DISPLAY > location
|
||||||
|
location := "" |
||||||
|
locationWithColor := "" |
||||||
|
if differentHost { |
||||||
|
location += record.Host + ":" |
||||||
|
locationWithColor += highlightHost(record.Host) + ":" |
||||||
|
} |
||||||
|
const locationLenght = 30 |
||||||
|
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", hits) |
||||||
|
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)) |
||||||
|
} |
||||||
|
|
||||||
|
// 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, |
||||||
|
hits: hits, |
||||||
|
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 |
||||||
|
} |
||||||
@ -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 |
||||||
|
} |
||||||
Loading…
Reference in new issue