// // Copyright (c) 2019 Ted Unangst // // Permission to use, copy, modify, and distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. package main /* #include */ import "C" import ( "bufio" "fmt" "io/ioutil" "log" "os" "os/signal" "strings" ) func adminscreen() { log.SetOutput(ioutil.Discard) stdout := bufio.NewWriter(os.Stdout) esc := "\x1b" smcup := esc + "[?1049h" rmcup := esc + "[?1049l" var avatarColors string getconfig("avatarcolors", &avatarColors) messages := []*struct { name string label string text string }{ { name: "servermsg", label: "server", text: string(serverMsg), }, { name: "aboutmsg", label: "about", text: string(aboutMsg), }, { name: "loginmsg", label: "login", text: string(loginMsg), }, { name: "avatarcolors", label: "avatar colors (4 RGBA hex numbers)", text: string(avatarColors), }, } cursel := 0 hidecursor := func() { stdout.WriteString(esc + "[?25l") } showcursor := func() { stdout.WriteString(esc + "[?12;25h") } movecursor := func(x, y int) { stdout.WriteString(fmt.Sprintf(esc+"[%d;%dH", y, x)) } moveleft := func() { stdout.WriteString(esc + "[1D") } clearscreen := func() { stdout.WriteString(esc + "[2J") } //clearline := func() { stdout.WriteString(esc + "[2K") } colorfn := func(code int) func(string) string { return func(s string) string { return fmt.Sprintf(esc+"[%dm"+"%s"+esc+"[0m", code, s) } } reverse := colorfn(7) magenta := colorfn(35) readchar := func() byte { var buf [1]byte os.Stdin.Read(buf[:]) c := buf[0] return c } savedtio := new(C.struct_termios) C.tcgetattr(1, savedtio) restore := func() { stdout.WriteString(rmcup) showcursor() stdout.Flush() C.tcsetattr(1, C.TCSAFLUSH, savedtio) } defer restore() go func() { sig := make(chan os.Signal) signal.Notify(sig, os.Interrupt) <-sig restore() os.Exit(0) }() init := func() { tio := new(C.struct_termios) C.tcgetattr(1, tio) tio.c_lflag = tio.c_lflag & ^C.uint(C.ECHO|C.ICANON) C.tcsetattr(1, C.TCSADRAIN, tio) hidecursor() stdout.WriteString(smcup) clearscreen() movecursor(1, 1) stdout.Flush() } editing := false linecount := func(s string) int { lines := 1 for i := range s { if s[i] == '\n' { lines++ } } return lines } msglineno := func(idx int) int { off := 2 if idx == -1 { return off } for i, m := range messages { off += 2 if i == idx { return off } off += linecount(m.text) } off += 2 return off } forscreen := func(s string) string { return strings.Replace(s, "\n", "\n ", -1) } drawmessage := func(idx int) { line := msglineno(idx) movecursor(4, line) label := messages[idx].label if idx == cursel { label = reverse(label) if editing { label = magenta(label) } } text := forscreen(messages[idx].text) stdout.WriteString(fmt.Sprintf("%s\n %s", label, text)) } drawscreen := func() { clearscreen() movecursor(4, msglineno(-1)) stdout.WriteString(magenta(serverName + " admin panel")) for i := range messages { if !editing || i != cursel { drawmessage(i) } } movecursor(4, msglineno(len(messages))) dir := "j/k to move - q to quit - enter to edit" if editing { dir = "esc to end" } stdout.WriteString(magenta(dir)) if editing { drawmessage(cursel) } stdout.Flush() } selectnext := func() { if cursel < len(messages)-1 { movecursor(4, msglineno(cursel)) stdout.WriteString(messages[cursel].label) cursel++ movecursor(4, msglineno(cursel)) stdout.WriteString(reverse(messages[cursel].label)) stdout.Flush() } } selectprev := func() { if cursel > 0 { movecursor(4, msglineno(cursel)) stdout.WriteString(messages[cursel].label) cursel-- movecursor(4, msglineno(cursel)) stdout.WriteString(reverse(messages[cursel].label)) stdout.Flush() } } editsel := func() { editing = true showcursor() drawscreen() m := messages[cursel] loop: for { c := readchar() switch c { case '\x1b': break loop case '\n': m.text += "\n" drawscreen() case 127: if len(m.text) > 0 { last := m.text[len(m.text)-1] m.text = m.text[:len(m.text)-1] if last == '\n' { drawscreen() } else { moveleft() stdout.WriteString(" ") moveleft() } } default: m.text += string(c) stdout.WriteString(string(c)) } stdout.Flush() } editing = false setconfig(m.name, m.text) hidecursor() drawscreen() } init() drawscreen() for { c := readchar() switch c { case 'q': return case 'j': selectnext() case 'k': selectprev() case '\n': editsel() default: } } }