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