v0.7.0 4c16dd8

terminal_probe_test.go RawHistoryFolder

1package client
2
3import (
4	"bytes"
5	"context"
6	"io"
7	"testing"
8	"time"
9
10	"github.com/bnema/vev-vt/protocol/terminalquery"
11	"github.com/stretchr/testify/require"
12
13	portsmocks "github.com/bnema/vev/internal/ports/mocks"
14)
15
16func TestProbeTerminalCapabilities(t *testing.T) {
17	const query = terminalquery.KittyGraphicsQuery + terminalquery.KittyKeyboardQuery + terminalquery.DeviceAttributesQuery
18	tests := []struct {
19		name     string
20		reply    string
21		timeout  bool
22		want     terminalCapabilities
23		wantRest string
24	}{
25		{name: "graphics and keyboard", reply: "\x1b_Gi=31;OK\x1b\\\x1b[?0u\x1b[?62;c", want: terminalCapabilities{KittyGraphics: true, KittyKeyboard: true}},
26		{name: "keyboard only", reply: "\x1b[?0u\x1b[?62;22c", want: terminalCapabilities{KittyKeyboard: true}},
27		{name: "neither", reply: "\x1b[?62;22c"},
28		{name: "typed input is kept in order", reply: "ab\x1b[?1u\x1b[?62;22ccd", want: terminalCapabilities{KittyKeyboard: true}, wantRest: "abcd"},
29		{name: "no DA1 means nothing", reply: "\x1b[?0u", timeout: true, wantRest: ""},
30		{name: "silent terminal times out", timeout: true},
31	}
32	for _, tt := range tests {
33		t.Run(tt.name, func(t *testing.T) {
34			inR, inW := io.Pipe()
35			defer func() { _ = inW.Close() }()
36			pump := newTerminalInputPump(inR)
37			pump.start()
38			defer pump.stop()
39
40			var out bytes.Buffer
41			term := portsmocks.NewMockTerminal(t)
42			term.EXPECT().Out().Return(&out).Once()
43			written := make(chan struct{})
44			term.EXPECT().Flush().RunAndReturn(func() error {
45				go func() {
46					defer close(written)
47					if tt.reply != "" {
48						_, _ = inW.Write([]byte(tt.reply))
49					}
50				}()
51				return nil
52			}).Once()
53			clk := &pcFakeClock{}
54
55			got := make(chan terminalCapabilities, 1)
56			go func() { got <- probeTerminalCapabilities(context.Background(), term, clk, pump) }()
57			if tt.timeout {
58				require.Eventually(t, func() bool {
59					clk.mu.Lock()
60					defer clk.mu.Unlock()
61					return len(clk.timers) == 1
62				}, timeoutForTest, pollForTest)
63				// A pipe write returns once the pump read it, so a partial
64				// reply is in before the deadline fires.
65				<-written
66				require.Eventually(t, func() bool { return pumpDrained(pump) }, timeoutForTest, pollForTest)
67				clk.fireLast()
68			}
69			caps := <-got
70			require.Equal(t, query, out.String())
71			require.Equal(t, tt.want, caps)
72
73			consumer, ok := pump.tryClaim()
74			require.True(t, ok)
75			defer pump.revoke(consumer)
76			if tt.wantRest == "" {
77				_, ok := pump.take(context.Background(), consumer)
78				require.False(t, ok, "no byte may be left for the next consumer")
79				return
80			}
81			result, ok := pump.take(context.Background(), consumer)
82			require.True(t, ok)
83			require.Equal(t, tt.wantRest, string(result.data))
84		})
85	}
86}
87
88const (
89	timeoutForTest = 2 * time.Second
90	pollForTest    = time.Millisecond
91)
92
93// pumpDrained reports that the probe consumed every read the pump delivered.
94func pumpDrained(p *terminalInputPump) bool {
95	p.mu.Lock()
96	defer p.mu.Unlock()
97	return p.pending == nil && p.delivering == 0
98}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close