commit 06d3d2c

delthas  ·  2024-07-03 15:00:16 +0000 UTC
parent 1a988df
Add CTRL+K as a shortcut to buffer; filter buffer view by name

This makes CTRL+K behave as a real channel switcher, akin to
Discord's CTRL+K.

Fixes: https://todo.sr.ht/~delthas/senpai/83
6 files changed,  +138, -39
M app.go
M app.go
+9, -3
 1@@ -546,7 +546,7 @@ func (app *App) handleMouseEvent(ev vaxis.Mouse) {
 2 				app.win.ClickChannelCol(true)
 3 				app.win.SetMouseShape(vaxis.MouseShapeResizeHorizontal)
 4 			} else if x < app.win.ChannelWidth() {
 5-				app.win.ClickBuffer(y + app.win.ChannelOffset())
 6+				app.win.ClickBuffer(app.win.VerticalBufferOffset(y))
 7 			} else if app.win.ChannelWidth() == 0 && y == h-1 {
 8 				app.win.ClickBuffer(app.win.HorizontalBufferOffset(x))
 9 			} else if x == w-app.win.MemberWidth() {
10@@ -561,7 +561,7 @@ func (app *App) handleMouseEvent(ev vaxis.Mouse) {
11 		if ev.Button == vaxis.MouseMiddleButton {
12 			i := -1
13 			if x < app.win.ChannelWidth() {
14-				i = y + app.win.ChannelOffset()
15+				i = app.win.VerticalBufferOffset(y)
16 			} else if app.win.ChannelWidth() == 0 && y == h-1 {
17 				i = app.win.HorizontalBufferOffset(x)
18 			}
19@@ -578,12 +578,14 @@ func (app *App) handleMouseEvent(ev vaxis.Mouse) {
20 	}
21 	if ev.EventType == vaxis.EventRelease {
22 		if x < app.win.ChannelWidth()-1 {
23-			if i := y + app.win.ChannelOffset(); i == app.win.ClickedBuffer() {
24+			if i := app.win.VerticalBufferOffset(y); i == app.win.ClickedBuffer() {
25 				app.win.GoToBufferNo(i)
26+				app.clearBufferCommand()
27 			}
28 		} else if app.win.ChannelWidth() == 0 && y == h-1 {
29 			if i := app.win.HorizontalBufferOffset(x); i >= 0 && i == app.win.ClickedBuffer() {
30 				app.win.GoToBufferNo(i)
31+				app.clearBufferCommand()
32 			}
33 		} else if x > w-app.win.MemberWidth() {
34 			if i := y - 2 + app.win.MemberOffset(); i >= 0 && i == app.win.ClickedMember() {
35@@ -680,6 +682,10 @@ func (app *App) handleKeyEvent(ev vaxis.Key) {
36 		if len(app.win.InputContent()) == 0 {
37 			app.win.InputSet("/search ")
38 		}
39+	} else if keyMatches(ev, 'k', vaxis.ModCtrl) {
40+		if len(app.win.InputContent()) == 0 {
41+			app.win.InputSet("/buffer ")
42+		}
43 	} else if keyMatches(ev, 'a', vaxis.ModCtrl) {
44 		app.win.InputHome()
45 	} else if keyMatches(ev, 'e', vaxis.ModCtrl) {
+2, -2
 1@@ -165,8 +165,8 @@ func init() {
 2 			AllowHome: true,
 3 			MinArgs:   1,
 4 			MaxArgs:   1,
 5-			Usage:     "<name>",
 6-			Desc:      "switch to the buffer containing a substring",
 7+			Usage:     "<index|name>",
 8+			Desc:      "switch to the buffer at the position or containing a substring",
 9 			Handle:    commandDoBuffer,
10 		},
11 		"WHOIS": {
+7, -2
 1@@ -99,6 +99,9 @@ will be shown, which can be closed by clicking again anywhere in the interface.
 2 *CTRL-F*
 3 	Prepare for search: add /search to input line.
 4 
 5+*CTRL-K*
 6+	Prepare for jumping to a buffer: add /buffer to input line.
 7+
 8 *CTRL-U*, *PgUp*
 9 	Go up in the timeline.
10 
11@@ -206,8 +209,10 @@ _name_ is matched case-insensitively.  It can be one of the following:
12 *LIST* [pattern]
13 	List public channels, optionally matching the specified pattern.
14 
15-*BUFFER* <name>
16-	Switch to the buffer containing _name_.
17+*BUFFER* <index|name>
18+	Switch to the buffer at the _index_ position, or containing _name_.
19+	The buffer list will be filtered according to the passed name; entering the
20+	command will select the first buffer in the list.
21 
22 *WHOIS* <nickname>
23 	Get information about someone who is connected.
+85, -16
  1@@ -234,7 +234,8 @@ type BufferList struct {
  2 	tlHeight     int
  3 	textWidth    int
  4 
  5-	showBufferNumbers bool
  6+	filterBuffers      bool
  7+	filterBuffersQuery string // lowercased
  8 }
  9 
 10 // NewBufferList returns a new BufferList.
 11@@ -298,8 +299,9 @@ func (bs *BufferList) To(i int) bool {
 12 	return false
 13 }
 14 
 15-func (bs *BufferList) ShowBufferNumbers(enabled bool) {
 16-	bs.showBufferNumbers = enabled
 17+func (bs *BufferList) FilterBuffers(enable bool, query string) {
 18+	bs.filterBuffers = enable
 19+	bs.filterBuffersQuery = strings.ToLower(query)
 20 }
 21 
 22 func (bs *BufferList) Next() {
 23@@ -684,16 +686,20 @@ func (bs *BufferList) DrawVerticalBufferList(vx *Vaxis, x0, y0, width, height in
 24 			*offset = 0
 25 		}
 26 	}
 27+	off := bs.VerticalBufferOffset(0, *offset)
 28+	if off < 0 {
 29+		off = len(bs.list)
 30+	}
 31 
 32 	width--
 33 	drawVerticalLine(vx, x0+width, y0, height)
 34 	clearArea(vx, x0, y0, width, height)
 35 
 36 	indexPadding := 1 + int(math.Ceil(math.Log10(float64(len(bs.list)))))
 37-	for i, b := range bs.list[*offset:] {
 38-		bi := *offset + i
 39+	y := y0
 40+	for i, b := range bs.list[off:] {
 41+		bi := off + i
 42 		x := x0
 43-		y := y0 + i
 44 		var st vaxis.Style
 45 		if b.unread {
 46 			st.Attribute |= vaxis.AttrBold
 47@@ -702,7 +708,18 @@ func (bs *BufferList) DrawVerticalBufferList(vx *Vaxis, x0, y0, width, height in
 48 		if bi == bs.current || bi == bs.clicked {
 49 			st.Attribute |= vaxis.AttrReverse
 50 		}
 51-		if bs.showBufferNumbers {
 52+
 53+		var title string
 54+		if b.title == "" {
 55+			title = b.netName
 56+		} else {
 57+			title = b.title
 58+		}
 59+
 60+		if bs.filterBuffers {
 61+			if !strings.Contains(strings.ToLower(title), bs.filterBuffersQuery) {
 62+				continue
 63+			}
 64 			indexSt := st
 65 			indexSt.Foreground = ColorGray
 66 			indexText := fmt.Sprintf("%d:", bi+1)
 67@@ -710,10 +727,7 @@ func (bs *BufferList) DrawVerticalBufferList(vx *Vaxis, x0, y0, width, height in
 68 			x = x0 + indexPadding
 69 		}
 70 
 71-		var title string
 72-		if b.title == "" {
 73-			title = b.netName
 74-		} else {
 75+		if b.title != "" {
 76 			if bi == bs.current || bi == bs.clicked {
 77 				st := vaxis.Style{
 78 					Attribute: vaxis.AttrReverse,
 79@@ -722,7 +736,6 @@ func (bs *BufferList) DrawVerticalBufferList(vx *Vaxis, x0, y0, width, height in
 80 				setCell(vx, x+1, y, ' ', st)
 81 			}
 82 			x += 2
 83-			title = b.title
 84 		}
 85 		title = truncate(vx, title, width-(x-x0), "\u2026")
 86 		printString(vx, &x, y, Styled(title, st))
 87@@ -746,11 +759,28 @@ func (bs *BufferList) DrawVerticalBufferList(vx *Vaxis, x0, y0, width, height in
 88 			x = x0 + width - len(highlightText)
 89 			printString(vx, &x, y, Styled(highlightText, highlightSt))
 90 		}
 91+
 92+		y++
 93 	}
 94 }
 95 
 96 func (bs *BufferList) HorizontalBufferOffset(x int, offset int) int {
 97-	for i, b := range bs.list[offset:] {
 98+	if bs.filterBuffers {
 99+		offset = 0
100+	}
101+	i := 0
102+	for bi, b := range bs.list[offset:] {
103+		if bs.filterBuffers {
104+			var title string
105+			if b.title == "" {
106+				title = b.netName
107+			} else {
108+				title = b.title
109+			}
110+			if !strings.Contains(strings.ToLower(title), bs.filterBuffersQuery) {
111+				continue
112+			}
113+		}
114 		if i > 0 {
115 			x--
116 			if x < 0 {
117@@ -759,8 +789,35 @@ func (bs *BufferList) HorizontalBufferOffset(x int, offset int) int {
118 		}
119 		x -= bs.bufferWidth(&b)
120 		if x < 0 {
121-			return offset + i
122+			return offset + bi
123+		}
124+		i++
125+	}
126+	return -1
127+}
128+
129+func (bs *BufferList) VerticalBufferOffset(y int, offset int) int {
130+	if !bs.filterBuffers {
131+		return offset + y
132+	}
133+
134+	for i, b := range bs.list {
135+		var title string
136+		if b.title == "" {
137+			title = b.netName
138+		} else {
139+			title = b.title
140 		}
141+
142+		if bs.filterBuffers {
143+			if !strings.Contains(strings.ToLower(title), bs.filterBuffersQuery) {
144+				continue
145+			}
146+		}
147+		if y == 0 {
148+			return i
149+		}
150+		y--
151 	}
152 	return -1
153 }
154@@ -814,8 +871,13 @@ func (bs *BufferList) DrawHorizontalBufferList(vx *Vaxis, x0, y0, width int, off
155 	}
156 	x = x0
157 
158-	for i, b := range bs.list[*offset:] {
159-		i := i + *offset
160+	off := bs.HorizontalBufferOffset(0, *offset)
161+	if off < 0 {
162+		off = len(bs.list)
163+	}
164+
165+	for i, b := range bs.list[off:] {
166+		i := i + off
167 		if width <= x-x0 {
168 			break
169 		}
170@@ -837,6 +899,13 @@ func (bs *BufferList) DrawHorizontalBufferList(vx *Vaxis, x0, y0, width int, off
171 		} else {
172 			title = b.title
173 		}
174+
175+		if bs.filterBuffers {
176+			if !strings.Contains(strings.ToLower(title), bs.filterBuffersQuery) {
177+				continue
178+			}
179+		}
180+
181 		title = truncate(vx, title, width-x, "\u2026")
182 		printString(vx, &x, y0, Styled(title, st))
183 
+12, -6
 1@@ -174,7 +174,7 @@ func (ui *UI) ClickedBuffer() int {
 2 }
 3 
 4 func (ui *UI) ClickBuffer(i int) {
 5-	if i < len(ui.bs.list) {
 6+	if i >= 0 && i < len(ui.bs.list) {
 7 		ui.bs.clicked = i
 8 	}
 9 }
10@@ -221,8 +221,8 @@ func (ui *UI) GoToBufferNo(i int) {
11 	}
12 }
13 
14-func (ui *UI) ShowBufferNumbers(enable bool) {
15-	ui.bs.ShowBufferNumbers(enable)
16+func (ui *UI) FilterBuffers(enable bool, query string) {
17+	ui.bs.FilterBuffers(enable, query)
18 }
19 
20 func (ui *UI) ClickedMember() int {
21@@ -284,8 +284,8 @@ func (ui *UI) HorizontalBufferOffset(x int) int {
22 	return ui.bs.HorizontalBufferOffset(x, ui.channelOffset)
23 }
24 
25-func (ui *UI) ChannelOffset() int {
26-	return ui.channelOffset
27+func (ui *UI) VerticalBufferOffset(y int) int {
28+	return ui.bs.VerticalBufferOffset(y, ui.channelOffset)
29 }
30 
31 func (ui *UI) MemberOffset() int {
32@@ -393,7 +393,13 @@ func (ui *UI) AddLines(netID, buffer string, before, after []Line) {
33 func (ui *UI) JumpBuffer(sub string) bool {
34 	subLower := strings.ToLower(sub)
35 	for i, b := range ui.bs.list {
36-		if strings.Contains(strings.ToLower(b.title), subLower) {
37+		var title string
38+		if b.title == "" {
39+			title = b.netName
40+		} else {
41+			title = b.title
42+		}
43+		if strings.Contains(strings.ToLower(title), subLower) {
44 			if ui.bs.To(i) {
45 				ui.memberOffset = 0
46 			}
+23, -10
 1@@ -1,6 +1,7 @@
 2 package senpai
 3 
 4 import (
 5+	"strconv"
 6 	"strings"
 7 	"time"
 8 
 9@@ -79,17 +80,29 @@ func (app *App) setStatus() {
10 func (app *App) setBufferNumbers() {
11 	input := app.win.InputContent()
12 	if !isCommand(input) {
13-		app.win.ShowBufferNumbers(false)
14+		app.win.FilterBuffers(false, "")
15 		return
16 	}
17-	commandEnd := len(input)
18-	for i := 1; i < len(input); i++ {
19-		if input[i] == ' ' {
20-			commandEnd = i
21-			break
22-		}
23+	cmd, arg, _ := strings.Cut(string(input[1:]), " ")
24+	if cmd == "" || !strings.HasPrefix("buffer", cmd) {
25+		app.win.FilterBuffers(false, "")
26+		return
27+	}
28+	if _, err := strconv.Atoi(arg); err == nil {
29+		// Do not filter buffers if we are passing a buffer index
30+		arg = ""
31+	}
32+	app.win.FilterBuffers(true, arg)
33+}
34+
35+func (app *App) clearBufferCommand() {
36+	input := app.win.InputContent()
37+	if !isCommand(input) {
38+		return
39+	}
40+	cmd, _, _ := strings.Cut(string(input[1:]), " ")
41+	if cmd == "" || !strings.HasPrefix("buffer", cmd) {
42+		return
43 	}
44-	command := string(input[1:commandEnd])
45-	showBufferNumbers := len(command) != 0 && strings.HasPrefix("buffer", command)
46-	app.win.ShowBufferNumbers(showBufferNumbers)
47+	app.win.InputClear()
48 }