all repos — honk @ 25ffd152ac82710572424bd703ef1423f4fc4698

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