summaryrefslogtreecommitdiff
path: root/ui
diff options
context:
space:
mode:
Diffstat (limited to 'ui')
-rw-r--r--ui/buffers.go153
-rw-r--r--ui/editor.go46
-rw-r--r--ui/style.go8
-rw-r--r--ui/ui.go69
4 files changed, 107 insertions, 169 deletions
diff --git a/ui/buffers.go b/ui/buffers.go
index c1426f9..606a7ee 100644
--- a/ui/buffers.go
+++ b/ui/buffers.go
@@ -2,7 +2,6 @@ package ui
import (
"fmt"
- "hash/fnv"
"math"
"strings"
"time"
@@ -31,55 +30,28 @@ type point struct {
}
type Line struct {
- at time.Time
- head string
- body string
-
- isStatus bool
- isHighlight bool
+ At time.Time
+ Head string
+ Body string
+ HeadColor int
+ Highlight bool
+ Mergeable bool
splitPoints []point
width int
newLines []int
}
-func NewLine(at time.Time, head string, body string, isStatus bool, isHighlight bool) Line {
- l := Line{
- at: at,
- head: head,
- body: body,
- isStatus: isStatus,
- isHighlight: isHighlight,
- splitPoints: []point{},
- newLines: []int{},
- }
- l.computeSplitPoints()
- return l
-}
-
-func NewLineNow(head, body string) Line {
- return NewLine(time.Now(), head, body, false, false)
-}
-
-func LineFromIRCMessage(at time.Time, nick string, content string, isNotice bool, isHighlight bool) Line {
- if strings.HasPrefix(content, "\x01ACTION") {
- c := ircColorCode(identColor(nick))
- content = fmt.Sprintf("%s%s\x0F%s", c, nick, content[7:])
- nick = "*"
- } else if isNotice {
- c := ircColorCode(identColor(nick))
- content = fmt.Sprintf("(%s%s\x0F: %s)", c, nick, content)
- nick = "*"
+func (l *Line) computeSplitPoints() {
+ if l.splitPoints == nil {
+ l.splitPoints = []point{}
}
- return NewLine(at, nick, content, false, isHighlight)
-}
-func (l *Line) computeSplitPoints() {
var wb widthBuffer
lastWasSplit := false
l.splitPoints = l.splitPoints[:0]
- for i, r := range l.body {
+ for i, r := range l.Body {
curIsSplit := IsSplitRune(r)
if i == 0 || lastWasSplit != curIsSplit {
@@ -97,7 +69,7 @@ func (l *Line) computeSplitPoints() {
if !lastWasSplit {
l.splitPoints = append(l.splitPoints, point{
X: wb.Width(),
- I: len(l.body),
+ I: len(l.Body),
Split: true,
})
}
@@ -113,6 +85,9 @@ func (l *Line) NewLines(width int) []int {
if l.width == width {
return l.newLines
}
+ if l.newLines == nil {
+ l.newLines = []int{}
+ }
l.newLines = l.newLines[:0]
l.width = width
@@ -158,7 +133,7 @@ func (l *Line) NewLines(width int) []int {
// the word.
var wb widthBuffer
h := 1
- for j, r := range l.body[sp1.I:sp2.I] {
+ for j, r := range l.Body[sp1.I:sp2.I] {
wb.WriteRune(r)
if h*width < x+wb.Width() {
l.newLines = append(l.newLines, sp1.I+j)
@@ -185,7 +160,7 @@ func (l *Line) NewLines(width int) []int {
}
}
- if 0 < len(l.newLines) && l.newLines[len(l.newLines)-1] == len(l.body) {
+ if 0 < len(l.newLines) && l.newLines[len(l.newLines)-1] == len(l.Body) {
// DROP any newline that is placed at the end of the string because we
// don't care about those.
l.newLines = l.newLines[:len(l.newLines)-1]
@@ -229,20 +204,19 @@ func (b *buffer) DrawLines(screen tcell.Screen, width, height, nickColWidth int)
continue
}
- if i == 0 || b.lines[i-1].at.Truncate(time.Minute) != line.at.Truncate(time.Minute) {
- printTime(screen, 0, y0, st.Bold(true), line.at.Local())
+ if i == 0 || b.lines[i-1].At.Truncate(time.Minute) != line.At.Truncate(time.Minute) {
+ printTime(screen, 0, y0, st.Bold(true), line.At.Local())
}
- head := truncate(line.head, nickColWidth, "\u2026")
+ head := truncate(line.Head, nickColWidth, "\u2026")
x := 7 + nickColWidth - StringWidth(head)
- c := identColor(line.head)
- st = st.Foreground(colorFromCode(c))
- if line.isHighlight {
+ st = st.Foreground(colorFromCode(line.HeadColor))
+ if line.Highlight {
st = st.Reverse(true)
}
screen.SetContent(x-1, y0, ' ', nil, st)
screen.SetContent(7+nickColWidth, y0, ' ', nil, st)
- printString(screen, &x, y0, st.Foreground(colorFromCode(c)), head)
+ printString(screen, &x, y0, st, head)
st = st.Reverse(false).Foreground(tcell.ColorDefault)
x = x0
@@ -250,7 +224,7 @@ func (b *buffer) DrawLines(screen tcell.Screen, width, height, nickColWidth int)
var sb StyleBuffer
sb.Reset()
- for i, r := range line.body {
+ for i, r := range line.Body {
if 0 < len(nls) && i == nls[0] {
x = x0
y++
@@ -280,7 +254,7 @@ func (b *buffer) DrawLines(screen tcell.Screen, width, height, nickColWidth int)
b.isAtTop = 0 <= y0
}
-type bufferList struct {
+type BufferList struct {
list []buffer
current int
@@ -289,8 +263,8 @@ type bufferList struct {
nickColWidth int
}
-func newBufferList(width, height, nickColWidth int) bufferList {
- return bufferList{
+func NewBufferList(width, height, nickColWidth int) BufferList {
+ return BufferList{
list: []buffer{},
width: width,
height: height,
@@ -298,24 +272,24 @@ func newBufferList(width, height, nickColWidth int) bufferList {
}
}
-func (bs *bufferList) Resize(width, height int) {
+func (bs *BufferList) Resize(width, height int) {
bs.width = width
bs.height = height
}
-func (bs *bufferList) Next() {
+func (bs *BufferList) Next() {
bs.current = (bs.current + 1) % len(bs.list)
bs.list[bs.current].highlights = 0
bs.list[bs.current].unread = false
}
-func (bs *bufferList) Previous() {
+func (bs *BufferList) Previous() {
bs.current = (bs.current - 1 + len(bs.list)) % len(bs.list)
bs.list[bs.current].highlights = 0
bs.list[bs.current].unread = false
}
-func (bs *bufferList) Add(title string) (ok bool) {
+func (bs *BufferList) Add(title string) (ok bool) {
lTitle := strings.ToLower(title)
for _, b := range bs.list {
if strings.ToLower(b.title) == lTitle {
@@ -328,7 +302,7 @@ func (bs *bufferList) Add(title string) (ok bool) {
return
}
-func (bs *bufferList) Remove(title string) (ok bool) {
+func (bs *BufferList) Remove(title string) (ok bool) {
lTitle := strings.ToLower(title)
for i, b := range bs.list {
if strings.ToLower(b.title) == lTitle {
@@ -343,7 +317,7 @@ func (bs *bufferList) Remove(title string) (ok bool) {
return
}
-func (bs *bufferList) AddLine(title string, line Line) {
+func (bs *BufferList) AddLine(title string, highlight bool, line Line) {
idx := bs.idx(title)
if idx < 0 {
return
@@ -351,29 +325,30 @@ func (bs *bufferList) AddLine(title string, line Line) {
b := &bs.list[idx]
n := len(b.lines)
- line.body = strings.TrimRight(line.body, "\t ")
+ line.Body = strings.TrimRight(line.Body, "\t ")
- if line.isStatus && n != 0 && b.lines[n-1].isStatus {
+ if line.Mergeable && n != 0 && b.lines[n-1].Mergeable {
l := &b.lines[n-1]
- l.body += " " + line.body
+ l.Body += " " + line.Body
l.computeSplitPoints()
l.width = 0
} else {
+ line.computeSplitPoints()
b.lines = append(b.lines, line)
if idx == bs.current && 0 < b.scrollAmt {
b.scrollAmt += len(line.NewLines(bs.width-9-bs.nickColWidth)) + 1
}
}
- if !line.isStatus && idx != bs.current {
+ if !line.Mergeable && idx != bs.current {
b.unread = true
}
- if line.isHighlight && idx != bs.current {
+ if highlight && idx != bs.current {
b.highlights++
}
}
-func (bs *bufferList) AddLines(title string, lines []Line) {
+func (bs *BufferList) AddLines(title string, lines []Line) {
idx := bs.idx(title)
if idx < 0 {
return
@@ -383,19 +358,22 @@ func (bs *bufferList) AddLines(title string, lines []Line) {
limit := len(lines)
if 0 < len(b.lines) {
- firstLineTime := b.lines[0].at.Unix()
+ firstLineTime := b.lines[0].At.Unix()
for i, l := range lines {
- if firstLineTime < l.at.Unix() {
+ if firstLineTime < l.At.Unix() {
limit = i
break
}
}
}
+ for i := 0; i < limit; i++ {
+ lines[i].computeSplitPoints()
+ }
b.lines = append(lines[:limit], b.lines...)
}
-func (bs *bufferList) TypingStart(title, nick string) {
+func (bs *BufferList) TypingStart(title, nick string) {
idx := bs.idx(title)
if idx < 0 {
return
@@ -411,7 +389,7 @@ func (bs *bufferList) TypingStart(title, nick string) {
b.typings = append(b.typings, nick)
}
-func (bs *bufferList) TypingStop(title, nick string) {
+func (bs *BufferList) TypingStop(title, nick string) {
idx := bs.idx(title)
if idx < 0 {
return
@@ -427,19 +405,19 @@ func (bs *bufferList) TypingStop(title, nick string) {
}
}
-func (bs *bufferList) Current() (title string) {
+func (bs *BufferList) Current() (title string) {
return bs.list[bs.current].title
}
-func (bs *bufferList) CurrentOldestTime() (t *time.Time) {
+func (bs *BufferList) CurrentOldestTime() (t *time.Time) {
ls := bs.list[bs.current].lines
if 0 < len(ls) {
- t = &ls[0].at
+ t = &ls[0].At
}
return
}
-func (bs *bufferList) ScrollUp() {
+func (bs *BufferList) ScrollUp() {
b := &bs.list[bs.current]
if b.isAtTop {
return
@@ -447,7 +425,7 @@ func (bs *bufferList) ScrollUp() {
b.scrollAmt += bs.height / 2
}
-func (bs *bufferList) ScrollDown() {
+func (bs *BufferList) ScrollDown() {
b := &bs.list[bs.current]
b.scrollAmt -= bs.height / 2
@@ -456,12 +434,12 @@ func (bs *bufferList) ScrollDown() {
}
}
-func (bs *bufferList) IsAtTop() bool {
+func (bs *BufferList) IsAtTop() bool {
b := &bs.list[bs.current]
return b.isAtTop
}
-func (bs *bufferList) idx(title string) int {
+func (bs *BufferList) idx(title string) int {
if title == "" {
return bs.current
}
@@ -475,13 +453,13 @@ func (bs *bufferList) idx(title string) int {
return -1
}
-func (bs *bufferList) Draw(screen tcell.Screen) {
+func (bs *BufferList) Draw(screen tcell.Screen) {
bs.list[bs.current].DrawLines(screen, bs.width, bs.height-3, bs.nickColWidth)
bs.drawStatusBar(screen, bs.height-3)
bs.drawTitleList(screen, bs.height-1)
}
-func (bs *bufferList) drawStatusBar(screen tcell.Screen, y int) {
+func (bs *BufferList) drawStatusBar(screen tcell.Screen, y int) {
st := tcell.StyleDefault.Dim(true)
nicks := bs.list[bs.current].typings
verb := " is typing..."
@@ -517,7 +495,7 @@ func (bs *bufferList) drawStatusBar(screen tcell.Screen, y int) {
}
}
-func (bs *bufferList) drawTitleList(screen tcell.Screen, y int) {
+func (bs *BufferList) drawTitleList(screen tcell.Screen, y int) {
var widths []int
for _, b := range bs.list {
width := StringWidth(b.title)
@@ -604,24 +582,7 @@ func printTime(screen tcell.Screen, x int, y int, st tcell.Style, t time.Time) {
screen.SetContent(x+4, y, mn1, nil, st)
}
-// see <https://modern.ircdocs.horse/formatting.html>
-var identColorBlacklist = []int{1, 8, 16, 27, 28, 88, 89, 90, 91}
-
-func identColor(s string) (code int) {
- h := fnv.New32()
- _, _ = h.Write([]byte(s))
-
- code = int(h.Sum32()) % (99 - len(identColorBlacklist))
- for _, c := range identColorBlacklist {
- if c <= code {
- code++
- }
- }
-
- return
-}
-
-func ircColorCode(code int) string {
+func IrcColorCode(code int) string {
var c [3]rune
c[0] = 0x03
c[1] = rune(code/10) + '0'
diff --git a/ui/editor.go b/ui/editor.go
index 33cf736..1b30c53 100644
--- a/ui/editor.go
+++ b/ui/editor.go
@@ -9,8 +9,8 @@ type Completion struct {
CursorIdx int
}
-// editor is the text field where the user writes messages and commands.
-type editor struct {
+// Editor is the text field where the user writes messages and commands.
+type Editor struct {
// text contains the written runes. An empty slice means no text is written.
text [][]rune
@@ -38,8 +38,8 @@ type editor struct {
completeIdx int
}
-func newEditor(width int, autoComplete func(cursorIdx int, text []rune) []Completion) editor {
- return editor{
+func NewEditor(width int, autoComplete func(cursorIdx int, text []rune) []Completion) Editor {
+ return Editor{
text: [][]rune{{}},
textWidth: []int{0},
width: width,
@@ -47,7 +47,7 @@ func newEditor(width int, autoComplete func(cursorIdx int, text []rune) []Comple
}
}
-func (e *editor) Resize(width int) {
+func (e *Editor) Resize(width int) {
if width < e.width {
e.cursorIdx = 0
e.offsetIdx = 0
@@ -56,21 +56,21 @@ func (e *editor) Resize(width int) {
e.width = width
}
-func (e *editor) IsCommand() bool {
+func (e *Editor) IsCommand() bool {
return len(e.text[e.lineIdx]) != 0 && e.text[e.lineIdx][0] == '/'
}
-func (e *editor) TextLen() int {
+func (e *Editor) TextLen() int {
return len(e.text[e.lineIdx])
}
-func (e *editor) PutRune(r rune) {
+func (e *Editor) PutRune(r rune) {
e.putRune(r)
e.Right()
e.autoCache = nil
}
-func (e *editor) putRune(r rune) {
+func (e *Editor) putRune(r rune) {
e.text[e.lineIdx] = append(e.text[e.lineIdx], ' ')
copy(e.text[e.lineIdx][e.cursorIdx+1:], e.text[e.lineIdx][e.cursorIdx:])
e.text[e.lineIdx][e.cursorIdx] = r
@@ -83,7 +83,7 @@ func (e *editor) putRune(r rune) {
}
}
-func (e *editor) RemRune() (ok bool) {
+func (e *Editor) RemRune() (ok bool) {
ok = 0 < e.cursorIdx
if !ok {
return
@@ -94,7 +94,7 @@ func (e *editor) RemRune() (ok bool) {
return
}
-func (e *editor) RemRuneForward() (ok bool) {
+func (e *Editor) RemRuneForward() (ok bool) {
ok = e.cursorIdx < len(e.text[e.lineIdx])
if !ok {
return
@@ -104,7 +104,7 @@ func (e *editor) RemRuneForward() (ok bool) {
return
}
-func (e *editor) remRuneAt(idx int) {
+func (e *Editor) remRuneAt(idx int) {
// TODO avoid looping twice
rw := e.textWidth[idx+1] - e.textWidth[idx]
for i := idx + 1; i < len(e.textWidth); i++ {
@@ -117,7 +117,7 @@ func (e *editor) remRuneAt(idx int) {
e.text[e.lineIdx] = e.text[e.lineIdx][:len(e.text[e.lineIdx])-1]
}
-func (e *editor) Flush() (content string) {
+func (e *Editor) Flush() (content string) {
content = string(e.text[e.lineIdx])
if len(e.text[len(e.text)-1]) == 0 {
e.lineIdx = len(e.text) - 1
@@ -132,12 +132,12 @@ func (e *editor) Flush() (content string) {
return
}
-func (e *editor) Right() {
+func (e *Editor) Right() {
e.right()
e.autoCache = nil
}
-func (e *editor) right() {
+func (e *Editor) right() {
if e.cursorIdx == len(e.text[e.lineIdx]) {
return
}
@@ -151,7 +151,7 @@ func (e *editor) right() {
}
}
-func (e *editor) Left() {
+func (e *Editor) Left() {
if e.cursorIdx == 0 {
return
}
@@ -165,7 +165,7 @@ func (e *editor) Left() {
e.autoCache = nil
}
-func (e *editor) Home() {
+func (e *Editor) Home() {
if e.cursorIdx == 0 {
return
}
@@ -174,7 +174,7 @@ func (e *editor) Home() {
e.autoCache = nil
}
-func (e *editor) End() {
+func (e *Editor) End() {
if e.cursorIdx == len(e.text[e.lineIdx]) {
return
}
@@ -185,7 +185,7 @@ func (e *editor) End() {
e.autoCache = nil
}
-func (e *editor) Up() {
+func (e *Editor) Up() {
if e.lineIdx == 0 {
return
}
@@ -197,7 +197,7 @@ func (e *editor) Up() {
e.End()
}
-func (e *editor) Down() {
+func (e *Editor) Down() {
if e.lineIdx == len(e.text)-1 {
if len(e.text[e.lineIdx]) == 0 {
return
@@ -213,7 +213,7 @@ func (e *editor) Down() {
e.End()
}
-func (e *editor) AutoComplete() (ok bool) {
+func (e *Editor) AutoComplete() (ok bool) {
if e.autoCache == nil {
e.autoCache = e.autoComplete(e.cursorIdx, e.text[e.lineIdx])
if e.autoCache == nil {
@@ -234,7 +234,7 @@ func (e *editor) AutoComplete() (ok bool) {
return true
}
-func (e *editor) computeTextWidth() {
+func (e *Editor) computeTextWidth() {
e.textWidth = e.textWidth[:1]
rw := 0
for _, r := range e.text[e.lineIdx] {
@@ -243,7 +243,7 @@ func (e *editor) computeTextWidth() {
}
}
-func (e *editor) Draw(screen tcell.Screen, y int) {
+func (e *Editor) Draw(screen tcell.Screen, y int) {
st := tcell.StyleDefault
x := 0
diff --git a/ui/style.go b/ui/style.go
index f6e8c39..8672535 100644
--- a/ui/style.go
+++ b/ui/style.go
@@ -169,6 +169,14 @@ func (cb *colorBuffer) WriteRune(r rune) (ok int) {
return
}
+const (
+ ColorWhite = iota
+ ColorBlack
+ ColorBlue
+ ColorGreen
+ ColorRed
+)
+
var ansiCodes = []tcell.Color{
// Taken from <https://modern.ircdocs.horse/formatting.html>
tcell.ColorWhite, tcell.ColorBlack, tcell.ColorBlue, tcell.ColorGreen,
diff --git a/ui/ui.go b/ui/ui.go
index 50967b0..8c8d0ec 100644
--- a/ui/ui.go
+++ b/ui/ui.go
@@ -19,8 +19,8 @@ type UI struct {
exit atomic.Value // bool
config Config
- bs bufferList
- e editor
+ bs BufferList
+ e Editor
}
func New(config Config) (ui *UI, err error) {
@@ -52,11 +52,15 @@ func New(config Config) (ui *UI, err error) {
ui.exit.Store(false)
hmIdx := rand.Intn(len(homeMessages))
- ui.bs = newBufferList(w, h, ui.config.NickColWidth)
+ ui.bs = NewBufferList(w, h, ui.config.NickColWidth)
ui.bs.Add(Home)
- ui.bs.AddLine("", NewLineNow("--", homeMessages[hmIdx]))
+ ui.bs.AddLine("", false, Line{
+ At: time.Now(),
+ Head: "--",
+ Body: homeMessages[hmIdx],
+ })
- ui.e = newEditor(w, ui.config.AutoComplete)
+ ui.e = NewEditor(w, ui.config.AutoComplete)
ui.Resize()
@@ -85,22 +89,18 @@ func (ui *UI) CurrentBufferOldestTime() (t *time.Time) {
func (ui *UI) NextBuffer() {
ui.bs.Next()
- ui.draw()
}
func (ui *UI) PreviousBuffer() {
ui.bs.Previous()
- ui.draw()
}
func (ui *UI) ScrollUp() {
ui.bs.ScrollUp()
- ui.draw()
}
func (ui *UI) ScrollDown() {
ui.bs.ScrollDown()
- ui.draw()
}
func (ui *UI) IsAtTop() bool {
@@ -108,37 +108,27 @@ func (ui *UI) IsAtTop() bool {
}
func (ui *UI) AddBuffer(title string) {
- ok := ui.bs.Add(title)
- if ok {
- ui.draw()
- }
+ _ = ui.bs.Add(title)
}
func (ui *UI) RemoveBuffer(title string) {
- ok := ui.bs.Remove(title)
- if ok {
- ui.draw()
- }
+ _ = ui.bs.Remove(title)
}
-func (ui *UI) AddLine(buffer string, line Line) {
- ui.bs.AddLine(buffer, line)
- ui.draw()
+func (ui *UI) AddLine(buffer string, highlight bool, line Line) {
+ ui.bs.AddLine(buffer, highlight, line)
}
func (ui *UI) AddLines(buffer string, lines []Line) {
ui.bs.AddLines(buffer, lines)
- ui.draw()
}
func (ui *UI) TypingStart(buffer, nick string) {
ui.bs.TypingStart(buffer, nick)
- ui.draw()
}
func (ui *UI) TypingStop(buffer, nick string) {
ui.bs.TypingStop(buffer, nick)
- ui.draw()
}
func (ui *UI) InputIsCommand() bool {
@@ -151,77 +141,56 @@ func (ui *UI) InputLen() int {
func (ui *UI) InputRune(r rune) {
ui.e.PutRune(r)
- ui.draw()
}
func (ui *UI) InputRight() {
ui.e.Right()
- ui.draw()
}
func (ui *UI) InputLeft() {
ui.e.Left()
- ui.draw()
}
func (ui *UI) InputHome() {
ui.e.Home()
- ui.draw()
}
func (ui *UI) InputEnd() {
ui.e.End()
- ui.draw()
}
func (ui *UI) InputUp() {
ui.e.Up()
- ui.draw()
}
func (ui *UI) InputDown() {
ui.e.Down()
- ui.draw()
}
func (ui *UI) InputBackspace() (ok bool) {
- ok = ui.e.RemRune()
- if ok {
- ui.draw()
- }
- return
+ return ui.e.RemRune()
}
func (ui *UI) InputDelete() (ok bool) {
- ok = ui.e.RemRuneForward()
- if ok {
- ui.draw()
- }
- return
+ return ui.e.RemRuneForward()
}
func (ui *UI) InputAutoComplete() (ok bool) {
- ok = ui.e.AutoComplete()
- if ok {
- ui.draw()
- }
- return
+ return ui.e.AutoComplete()
}
func (ui *UI) InputEnter() (content string) {
- content = ui.e.Flush()
- ui.draw()
- return
+ return ui.e.Flush()
}
func (ui *UI) Resize() {
w, h := ui.screen.Size()
ui.e.Resize(w)
ui.bs.Resize(w, h)
- ui.draw()
+ ui.Draw()
}
-func (ui *UI) draw() {
+func (ui *UI) Draw() {
_, h := ui.screen.Size()
ui.e.Draw(ui.screen, h-2)
ui.bs.Draw(ui.screen)