mirror of https://github.com/curusarn/resh
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
734 lines
17 KiB
734 lines
17 KiB
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
"github.com/awesome-gocui/gocui"
|
|
"github.com/curusarn/resh/pkg/cfg"
|
|
"github.com/curusarn/resh/pkg/msg"
|
|
"github.com/curusarn/resh/pkg/records"
|
|
|
|
"os/user"
|
|
"path/filepath"
|
|
"strconv"
|
|
)
|
|
|
|
// version from git set during build
|
|
var version string
|
|
|
|
// commit from git set during build
|
|
var commit string
|
|
|
|
// special constant recognized by RESH wrappers
|
|
const exitCodeExecute = 111
|
|
|
|
var debug bool
|
|
|
|
func main() {
|
|
output, exitCode := runReshCli()
|
|
fmt.Print(output)
|
|
os.Exit(exitCode)
|
|
}
|
|
|
|
func runReshCli() (string, int) {
|
|
usr, _ := user.Current()
|
|
dir := usr.HomeDir
|
|
configPath := filepath.Join(dir, "/.config/resh.toml")
|
|
logPath := filepath.Join(dir, ".resh/cli.log")
|
|
|
|
f, err := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
|
|
if err != nil {
|
|
log.Fatal("Error opening file:", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
log.SetOutput(f)
|
|
|
|
var config cfg.Config
|
|
if _, err := toml.DecodeFile(configPath, &config); err != nil {
|
|
log.Fatal("Error reading config:", err)
|
|
}
|
|
if config.Debug {
|
|
debug = true
|
|
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
|
|
log.Println("DEBUG is ON")
|
|
}
|
|
|
|
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 == "" {
|
|
log.Println("Error: you need to specify sessionId")
|
|
}
|
|
if *host == "" {
|
|
log.Println("Error: you need to specify HOST")
|
|
}
|
|
if *pwd == "" {
|
|
log.Println("Error: you need to specify PWD")
|
|
}
|
|
if *gitOriginRemote == "DEFAULT" {
|
|
log.Println("Error: you need to specify gitOriginRemote")
|
|
}
|
|
|
|
log.Printf("gitRemoteOrigin: %s\n", *gitOriginRemote)
|
|
g, err := gocui.NewGui(gocui.OutputNormal, false)
|
|
if err != nil {
|
|
log.Panicln(err)
|
|
}
|
|
defer g.Close()
|
|
|
|
g.Cursor = true
|
|
g.SelFgColor = gocui.ColorGreen
|
|
// g.SelBgColor = gocui.ColorGreen
|
|
g.Highlight = true
|
|
|
|
mess := msg.DumpMsg{
|
|
SessionID: *sessionID,
|
|
PWD: *pwd,
|
|
}
|
|
resp := SendDumpMsg(mess, strconv.Itoa(config.Port))
|
|
|
|
st := state{
|
|
// lock sync.Mutex
|
|
fullRecords: resp.FullRecords,
|
|
initialQuery: *query,
|
|
}
|
|
|
|
layout := manager{
|
|
sessionID: *sessionID,
|
|
host: *host,
|
|
pwd: *pwd,
|
|
gitOriginRemote: *gitOriginRemote,
|
|
config: config,
|
|
s: &st,
|
|
}
|
|
g.SetManager(layout)
|
|
|
|
if err := g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, layout.Next); err != nil {
|
|
log.Panicln(err)
|
|
}
|
|
if err := g.SetKeybinding("", gocui.KeyArrowDown, gocui.ModNone, layout.Next); err != nil {
|
|
log.Panicln(err)
|
|
}
|
|
if err := g.SetKeybinding("", gocui.KeyArrowUp, gocui.ModNone, layout.Prev); err != nil {
|
|
log.Panicln(err)
|
|
}
|
|
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
|
log.Panicln(err)
|
|
}
|
|
if err := g.SetKeybinding("", gocui.KeyCtrlG, 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.KeyEnter, gocui.ModNone, layout.SelectExecute); err != nil {
|
|
log.Panicln(err)
|
|
}
|
|
if err := g.SetKeybinding("", gocui.KeyArrowRight, gocui.ModNone, layout.SelectPaste); err != nil {
|
|
log.Panicln(err)
|
|
}
|
|
|
|
layout.UpdateData(*query)
|
|
err = g.MainLoop()
|
|
if err != nil && gocui.IsQuit(err) == false {
|
|
log.Panicln(err)
|
|
}
|
|
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"
|
|
replace := []string{invert, 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"
|
|
invert := "\033[32;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 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
|
|
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,
|
|
}
|
|
}
|
|
|
|
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 (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) {
|
|
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
|
|
}
|
|
|
|
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
|
|
highlightedItem int
|
|
|
|
initialQuery string
|
|
|
|
output string
|
|
exitCode int
|
|
}
|
|
|
|
type manager struct {
|
|
sessionID string
|
|
host string
|
|
pwd string
|
|
gitOriginRemote string
|
|
config cfg.Config
|
|
|
|
s *state
|
|
}
|
|
|
|
func (m manager) SelectExecute(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 = m.s.data[m.s.highlightedItem].cmdLine
|
|
m.s.exitCode = exitCodeExecute
|
|
return gocui.ErrQuit
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m manager) SelectPaste(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 = m.s.data[m.s.highlightedItem].cmdLine
|
|
m.s.exitCode = 0 // success
|
|
return gocui.ErrQuit
|
|
}
|
|
return nil
|
|
}
|
|
|
|
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.host, m.pwd, m.gitOriginRemote)
|
|
var data []item
|
|
itemSet := make(map[string]bool)
|
|
m.s.lock.Lock()
|
|
defer m.s.lock.Unlock()
|
|
for _, rec := range m.s.fullRecords {
|
|
itm, err := newItemFromRecordForQuery(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.data = nil
|
|
for _, itm := range data {
|
|
if len(m.s.data) > 420 {
|
|
break
|
|
}
|
|
m.s.data = append(m.s.data, 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)
|
|
m.UpdateData(v.Buffer())
|
|
}
|
|
|
|
func (m manager) Next(g *gocui.Gui, v *gocui.View) error {
|
|
_, y := g.Size()
|
|
m.s.lock.Lock()
|
|
defer m.s.lock.Unlock()
|
|
if m.s.highlightedItem < y {
|
|
m.s.highlightedItem++
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m manager) Prev(g *gocui.Gui, v *gocui.View) error {
|
|
m.s.lock.Lock()
|
|
defer m.s.lock.Unlock()
|
|
if m.s.highlightedItem > 0 {
|
|
m.s.highlightedItem--
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m manager) Layout(g *gocui.Gui) error {
|
|
var b byte
|
|
maxX, maxY := g.Size()
|
|
|
|
v, err := g.SetView("input", 0, 0, maxX-1, 2, b)
|
|
if err != nil && gocui.IsUnknownView(err) == false {
|
|
log.Panicln(err.Error())
|
|
}
|
|
|
|
v.Editable = true
|
|
v.Editor = m
|
|
v.Title = "resh cli"
|
|
|
|
g.SetCurrentView("input")
|
|
|
|
m.s.lock.Lock()
|
|
defer m.s.lock.Unlock()
|
|
if len(m.s.initialQuery) > 0 {
|
|
v.WriteString(m.s.initialQuery)
|
|
v.SetCursor(len(m.s.initialQuery), 0)
|
|
m.s.initialQuery = ""
|
|
}
|
|
|
|
v, err = g.SetView("body", 0, 2, maxX-1, maxY, b)
|
|
if err != nil && gocui.IsUnknownView(err) == false {
|
|
log.Panicln(err.Error())
|
|
}
|
|
v.Frame = false
|
|
v.Autoscroll = false
|
|
v.Clear()
|
|
v.Rewind()
|
|
|
|
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 quit(g *gocui.Gui, v *gocui.View) error {
|
|
return gocui.ErrQuit
|
|
}
|
|
|
|
// SendDumpMsg to daemon
|
|
func SendDumpMsg(m msg.DumpMsg, port string) msg.DumpResponse {
|
|
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.DumpResponse{}
|
|
err = json.Unmarshal(body, &response)
|
|
if err != nil {
|
|
log.Fatal("unmarshal resp error: ", err)
|
|
}
|
|
return response
|
|
}
|
|
|