all repos — honk @ a504f3774072be850266c2ecfe95e583daba38ff

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>
 20void
 21clearecho(struct termios *tio)
 22{
 23	tio->c_lflag = tio->c_lflag & ~(ECHO|ICANON);
 24}
 25*/
 26import "C"
 27import (
 28	"bufio"
 29	"fmt"
 30	"io/ioutil"
 31	"log"
 32	"os"
 33	"os/signal"
 34	"strings"
 35)
 36
 37func adminscreen() {
 38	log.SetOutput(ioutil.Discard)
 39	stdout := bufio.NewWriter(os.Stdout)
 40	esc := "\x1b"
 41	smcup := esc + "[?1049h"
 42	rmcup := esc + "[?1049l"
 43
 44	var avatarColors string
 45	getconfig("avatarcolors", &avatarColors)
 46	loadLingo()
 47
 48	type adminfield struct {
 49		name    string
 50		label   string
 51		text    string
 52		oneline bool
 53	}
 54
 55	messages := []*adminfield{
 56		{
 57			name:  "servermsg",
 58			label: "server",
 59			text:  string(serverMsg),
 60		},
 61		{
 62			name:  "aboutmsg",
 63			label: "about",
 64			text:  string(aboutMsg),
 65		},
 66		{
 67			name:  "loginmsg",
 68			label: "login",
 69			text:  string(loginMsg),
 70		},
 71		{
 72			name:    "avatarcolors",
 73			label:   "avatar colors (4 RGBA hex numbers)",
 74			text:    string(avatarColors),
 75			oneline: true,
 76		},
 77	}
 78	for _, l := range []string{"honked", "bonked", "honked back", "qonked", "evented"} {
 79		messages = append(messages, &adminfield{
 80			name:    "lingo-" + strings.ReplaceAll(l, " ", ""),
 81			label:   "lingo for " + l,
 82			text:    relingo[l],
 83			oneline: true,
 84		})
 85	}
 86	cursel := 0
 87
 88	hidecursor := func() {
 89		stdout.WriteString(esc + "[?25l")
 90	}
 91	showcursor := func() {
 92		stdout.WriteString(esc + "[?12;25h")
 93	}
 94	movecursor := func(x, y int) {
 95		stdout.WriteString(fmt.Sprintf(esc+"[%d;%dH", y, x))
 96	}
 97	moveleft := func() {
 98		stdout.WriteString(esc + "[1D")
 99	}
100	clearscreen := func() {
101		stdout.WriteString(esc + "[2J")
102	}
103	//clearline := func() { stdout.WriteString(esc + "[2K") }
104	colorfn := func(code int) func(string) string {
105		return func(s string) string {
106			return fmt.Sprintf(esc+"[%dm"+"%s"+esc+"[0m", code, s)
107		}
108	}
109	reverse := colorfn(7)
110	magenta := colorfn(35)
111	readchar := func() byte {
112		var buf [1]byte
113		os.Stdin.Read(buf[:])
114		c := buf[0]
115		return c
116	}
117
118	savedtio := new(C.struct_termios)
119	C.tcgetattr(1, savedtio)
120	restore := func() {
121		stdout.WriteString(rmcup)
122		showcursor()
123		stdout.Flush()
124		C.tcsetattr(1, C.TCSAFLUSH, savedtio)
125	}
126	defer restore()
127	go func() {
128		sig := make(chan os.Signal)
129		signal.Notify(sig, os.Interrupt)
130		<-sig
131		restore()
132		os.Exit(0)
133	}()
134
135	init := func() {
136		tio := new(C.struct_termios)
137		C.tcgetattr(1, tio)
138		C.clearecho(tio)
139		C.tcsetattr(1, C.TCSADRAIN, tio)
140
141		hidecursor()
142		stdout.WriteString(smcup)
143		clearscreen()
144		movecursor(1, 1)
145		stdout.Flush()
146	}
147
148	editing := false
149
150	linecount := func(s string) int {
151		lines := 1
152		for i := range s {
153			if s[i] == '\n' {
154				lines++
155			}
156		}
157		return lines
158	}
159
160	msglineno := func(idx int) int {
161		off := 1
162		if idx == -1 {
163			return off
164		}
165		for i, m := range messages {
166			off += 1
167			if i == idx {
168				return off
169			}
170			if !m.oneline {
171				off += 1
172				off += linecount(m.text)
173			}
174		}
175		off += 2
176		return off
177	}
178
179	forscreen := func(s string) string {
180		return strings.Replace(s, "\n", "\n   ", -1)
181	}
182
183	drawmessage := func(idx int) {
184		line := msglineno(idx)
185		movecursor(4, line)
186		label := messages[idx].label
187		if idx == cursel {
188			label = reverse(label)
189		}
190		label = magenta(label)
191		text := forscreen(messages[idx].text)
192		if messages[idx].oneline {
193			stdout.WriteString(fmt.Sprintf("%s\t   %s", label, text))
194		} else {
195			stdout.WriteString(fmt.Sprintf("%s\n   %s", label, text))
196		}
197	}
198
199	drawscreen := func() {
200		clearscreen()
201		movecursor(4, msglineno(-1))
202		stdout.WriteString(magenta(serverName + " admin panel"))
203		for i := range messages {
204			if !editing || i != cursel {
205				drawmessage(i)
206			}
207		}
208		movecursor(4, msglineno(len(messages)))
209		dir := "j/k to move - q to quit - enter to edit"
210		if editing {
211			dir = "esc to end"
212		}
213		stdout.WriteString(magenta(dir))
214		if editing {
215			drawmessage(cursel)
216		}
217		stdout.Flush()
218	}
219
220	selectnext := func() {
221		if cursel < len(messages)-1 {
222			movecursor(4, msglineno(cursel))
223			stdout.WriteString(magenta(messages[cursel].label))
224			cursel++
225			movecursor(4, msglineno(cursel))
226			stdout.WriteString(reverse(magenta(messages[cursel].label)))
227			stdout.Flush()
228		}
229	}
230	selectprev := func() {
231		if cursel > 0 {
232			movecursor(4, msglineno(cursel))
233			stdout.WriteString(magenta(messages[cursel].label))
234			cursel--
235			movecursor(4, msglineno(cursel))
236			stdout.WriteString(reverse(magenta(messages[cursel].label)))
237			stdout.Flush()
238		}
239	}
240	editsel := func() {
241		editing = true
242		showcursor()
243		drawscreen()
244		m := messages[cursel]
245	loop:
246		for {
247			c := readchar()
248			switch c {
249			case '\x1b':
250				break loop
251			case '\n':
252				if m.oneline {
253					break loop
254				}
255				m.text += "\n"
256				drawscreen()
257			case 127:
258				if len(m.text) > 0 {
259					last := m.text[len(m.text)-1]
260					m.text = m.text[:len(m.text)-1]
261					if last == '\n' {
262						drawscreen()
263					} else {
264						moveleft()
265						stdout.WriteString(" ")
266						moveleft()
267					}
268				}
269			default:
270				m.text += string(c)
271				stdout.WriteString(string(c))
272			}
273			stdout.Flush()
274		}
275		editing = false
276		setconfig(m.name, m.text)
277		hidecursor()
278		drawscreen()
279	}
280
281	init()
282	drawscreen()
283
284	for {
285		c := readchar()
286		switch c {
287		case 'q':
288			return
289		case 'j':
290			selectnext()
291		case 'k':
292			selectprev()
293		case '\n':
294			editsel()
295		default:
296
297		}
298	}
299}