all repos — honk @ 2b081dc63590ac40e5cce10dd5a69cfe889f3bbf

my fork of honk

admin.go (view raw)

  1//
  2// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
  3//
  4// Permission to use, copy, modify, and distribute this software for any
  5// purpose with or without fee is hereby granted, provided that the above
  6// copyright notice and this permission notice appear in all copies.
  7//
  8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 15
 16package main
 17
 18/*
 19#include <termios.h>
 20*/
 21import "C"
 22import (
 23	"bufio"
 24	"fmt"
 25	"io/ioutil"
 26	"log"
 27	"os"
 28	"os/signal"
 29	"strings"
 30)
 31
 32func adminscreen() {
 33	log.SetOutput(ioutil.Discard)
 34	stdout := bufio.NewWriter(os.Stdout)
 35	esc := "\x1b"
 36	smcup := esc + "[?1049h"
 37	rmcup := esc + "[?1049l"
 38
 39	messages := []*struct {
 40		name  string
 41		label string
 42		text  string
 43	}{
 44		{
 45			name:  "servermsg",
 46			label: "server",
 47			text:  string(serverMsg),
 48		},
 49		{
 50			name:  "aboutmsg",
 51			label: "about",
 52			text:  string(aboutMsg),
 53		},
 54		{
 55			name:  "loginmsg",
 56			label: "login",
 57			text:  string(loginMsg),
 58		},
 59	}
 60	cursel := 0
 61
 62	hidecursor := func() {
 63		stdout.WriteString(esc + "[?25l")
 64	}
 65	showcursor := func() {
 66		stdout.WriteString(esc + "[?12;25h")
 67	}
 68	movecursor := func(x, y int) {
 69		stdout.WriteString(fmt.Sprintf(esc+"[%d;%dH", y, x))
 70	}
 71	moveleft := func() {
 72		stdout.WriteString(esc + "[1D")
 73	}
 74	clearscreen := func() {
 75		stdout.WriteString(esc + "[2J")
 76	}
 77	//clearline := func() { stdout.WriteString(esc + "[2K") }
 78	colorfn := func(code int) func(string) string {
 79		return func(s string) string {
 80			return fmt.Sprintf(esc+"[%dm"+"%s"+esc+"[0m", code, s)
 81		}
 82	}
 83	reverse := colorfn(7)
 84	magenta := colorfn(35)
 85	readchar := func() byte {
 86		var buf [1]byte
 87		os.Stdin.Read(buf[:])
 88		c := buf[0]
 89		return c
 90	}
 91
 92	savedtio := new(C.struct_termios)
 93	C.tcgetattr(1, savedtio)
 94	restore := func() {
 95		stdout.WriteString(rmcup)
 96		showcursor()
 97		stdout.Flush()
 98		C.tcsetattr(1, C.TCSAFLUSH, savedtio)
 99	}
100	defer restore()
101	go func() {
102		sig := make(chan os.Signal)
103		signal.Notify(sig, os.Interrupt)
104		<-sig
105		restore()
106		os.Exit(0)
107	}()
108
109	init := func() {
110		tio := new(C.struct_termios)
111		C.tcgetattr(1, tio)
112		tio.c_lflag = tio.c_lflag & ^C.uint(C.ECHO|C.ICANON)
113		C.tcsetattr(1, C.TCSADRAIN, tio)
114
115		hidecursor()
116		stdout.WriteString(smcup)
117		clearscreen()
118		movecursor(1, 1)
119		stdout.Flush()
120	}
121
122	editing := false
123
124	linecount := func(s string) int {
125		lines := 1
126		for i := range s {
127			if s[i] == '\n' {
128				lines++
129			}
130		}
131		return lines
132	}
133
134	msglineno := func(idx int) int {
135		off := 2
136		if idx == -1 {
137			return off
138		}
139		for i, m := range messages {
140			off += 2
141			if i == idx {
142				return off
143			}
144			off += linecount(m.text)
145		}
146		off += 2
147		return off
148	}
149
150	forscreen := func(s string) string {
151		return strings.Replace(s, "\n", "\n   ", -1)
152	}
153
154	drawmessage := func(idx int) {
155		line := msglineno(idx)
156		movecursor(4, line)
157		label := messages[idx].label
158		if idx == cursel {
159			label = reverse(label)
160			if editing {
161				label = magenta(label)
162			}
163		}
164		text := forscreen(messages[idx].text)
165		stdout.WriteString(fmt.Sprintf("%s\n   %s", label, text))
166	}
167
168	drawscreen := func() {
169		clearscreen()
170		movecursor(4, msglineno(-1))
171		stdout.WriteString(magenta(serverName + " admin panel"))
172		for i := range messages {
173			if !editing || i != cursel {
174				drawmessage(i)
175			}
176		}
177		movecursor(4, msglineno(len(messages)))
178		dir := "j/k to move - q to quit - enter to edit"
179		if editing {
180			dir = "esc to end"
181		}
182		stdout.WriteString(magenta(dir))
183		if editing {
184			drawmessage(cursel)
185		}
186		stdout.Flush()
187	}
188
189	selectnext := func() {
190		if cursel < len(messages)-1 {
191			movecursor(4, msglineno(cursel))
192			stdout.WriteString(messages[cursel].label)
193			cursel++
194			movecursor(4, msglineno(cursel))
195			stdout.WriteString(reverse(messages[cursel].label))
196			stdout.Flush()
197		}
198	}
199	selectprev := func() {
200		if cursel > 0 {
201			movecursor(4, msglineno(cursel))
202			stdout.WriteString(messages[cursel].label)
203			cursel--
204			movecursor(4, msglineno(cursel))
205			stdout.WriteString(reverse(messages[cursel].label))
206			stdout.Flush()
207		}
208	}
209	editsel := func() {
210		editing = true
211		showcursor()
212		drawscreen()
213		m := messages[cursel]
214	loop:
215		for {
216			c := readchar()
217			switch c {
218			case '\x1b':
219				break loop
220			case '\n':
221				m.text += "\n"
222				drawscreen()
223			case 127:
224				if len(m.text) > 0 {
225					last := m.text[len(m.text)-1]
226					m.text = m.text[:len(m.text)-1]
227					if last == '\n' {
228						drawscreen()
229					} else {
230						moveleft()
231						stdout.WriteString(" ")
232						moveleft()
233					}
234				}
235			default:
236				m.text += string(c)
237				stdout.WriteString(string(c))
238			}
239			stdout.Flush()
240		}
241		editing = false
242		updateconfig(m.name, m.text)
243		hidecursor()
244		drawscreen()
245	}
246
247	init()
248	drawscreen()
249
250	for {
251		c := readchar()
252		switch c {
253		case 'q':
254			return
255		case 'j':
256			selectnext()
257		case 'k':
258			selectprev()
259		case '\n':
260			editsel()
261		default:
262
263		}
264	}
265}