v0.3.95 beceda1

resize_windows.go RawHistoryFolder

1//go:build windows
2
3package tui
4
5import (
6	"os"
7	"time"
8	"unsafe"
9
10	"golang.org/x/sys/windows"
11)
12
13// resizePollInterval bounds how long a size change goes unnoticed.
14const resizePollInterval = 100 * time.Millisecond
15
16// Windows has no SIGWINCH. Console resize events arrive as input records,
17// but stdin is read as a VT byte stream, so those records never reach us.
18// Poll the console size instead and fire the callbacks on every change.
19// This matters most under ConPTY hosts such as multiplexers, where the
20// pane width changes without any user input.
21func (p *ProcTerm) installResizeHandler() {
22	p.resizeOnce.Do(func() {
23		go func() {
24			lastW, lastH := p.Size()
25			ticker := time.NewTicker(resizePollInterval)
26			defer ticker.Stop()
27			for range ticker.C {
28				w, h := p.Size()
29				if w == lastW && h == lastH {
30					continue
31				}
32				lastW, lastH = w, h
33				for _, cb := range p.resizeCBs {
34					cb()
35				}
36			}
37		}()
38	})
39}
40
41func (p *ProcTerm) SetNonblock(enable bool) error { return nil }
42
43var (
44	kernel32                = windows.NewLazySystemDLL("kernel32.dll")
45	procPeekConsoleInputW   = kernel32.NewProc("PeekConsoleInputW")
46	procReadConsoleInputW   = kernel32.NewProc("ReadConsoleInputW")
47	procGetNumberOfConsoleE = kernel32.NewProc("GetNumberOfConsoleInputEvents")
48)
49
50// inputRecord mirrors the Win32 INPUT_RECORD layout (20 bytes). Only
51// key events are decoded; the union is kept as raw bytes.
52type inputRecord struct {
53	eventType uint16
54	_         uint16
55	event     [16]byte
56}
57
58const keyEvent = 0x0001
59
60// yieldsByte reports whether a key-down can produce input in VT mode.
61// Navigation and function keys emit escape sequences even when their
62// UnicodeChar is zero. Key-ups and non-key records produce no bytes.
63func (r *inputRecord) yieldsByte() bool {
64	if r.eventType != keyEvent {
65		return false
66	}
67	keyDown := *(*int32)(unsafe.Pointer(&r.event[0]))
68	if keyDown == 0 {
69		return false
70	}
71	char := *(*uint16)(unsafe.Pointer(&r.event[10]))
72	if char != 0 {
73		return true
74	}
75	key := *(*uint16)(unsafe.Pointer(&r.event[4]))
76	// Win32 virtual-key codes for PageUp/Down, End/Home, arrows,
77	// Insert/Delete and F1-F24. ReadConsole translates these into VT
78	// sequences, despite the input record having no Unicode character.
79	return key >= 0x21 && key <= 0x28 || key == 0x2d || key == 0x2e || key >= 0x70 && key <= 0x87
80}
81
82// consoleHasByte checks pending console input without consuming bytes.
83// Leading records that would not produce a byte are discarded so the
84// handle stops signalling for them; otherwise a lone focus event would
85// make every wait return immediately while ReadFile still blocks.
86func consoleHasByte(h windows.Handle) (bool, error) {
87	for {
88		var count uint32
89		if r, _, err := procGetNumberOfConsoleE.Call(uintptr(h), uintptr(unsafe.Pointer(&count))); r == 0 {
90			return false, err
91		}
92		if count == 0 {
93			return false, nil
94		}
95		records := make([]inputRecord, count)
96		var read uint32
97		if r, _, err := procPeekConsoleInputW.Call(uintptr(h), uintptr(unsafe.Pointer(&records[0])), uintptr(count), uintptr(unsafe.Pointer(&read))); r == 0 {
98			return false, err
99		}
100		skip := uint32(0)
101		for skip < read && !records[skip].yieldsByte() {
102			skip++
103		}
104		if skip < read {
105			return true, nil
106		}
107		if skip == 0 {
108			return false, nil
109		}
110		if r, _, err := procReadConsoleInputW.Call(uintptr(h), uintptr(unsafe.Pointer(&records[0])), uintptr(skip), uintptr(unsafe.Pointer(&read))); r == 0 {
111			return false, err
112		}
113	}
114}
115
116// peekStdin waits up to d for one byte of console input. It uses the
117// console handle's wait state plus PeekConsoleInput, so a missing reply
118// (for example an OSC 11 background query the host never answers) or a
119// bare Esc times out instead of blocking until the next keystroke.
120// Non-console stdin falls back to a blocking read.
121func peekStdin(in *os.File, d time.Duration) (byte, bool, error) {
122	h := windows.Handle(in.Fd())
123	var mode uint32
124	if windows.GetConsoleMode(h, &mode) != nil {
125		var b [1]byte
126		if _, err := in.Read(b[:]); err != nil {
127			return 0, false, err
128		}
129		return b[0], true, nil
130	}
131	deadline := time.Now().Add(d)
132	for {
133		ready, err := consoleHasByte(h)
134		if err != nil {
135			return 0, false, err
136		}
137		if ready {
138			var b [1]byte
139			if _, err := in.Read(b[:]); err != nil {
140				return 0, false, err
141			}
142			return b[0], true, nil
143		}
144		remaining := time.Until(deadline)
145		if remaining <= 0 {
146			return 0, false, nil
147		}
148		event, err := windows.WaitForSingleObject(h, uint32(remaining.Milliseconds())+1)
149		if err != nil {
150			return 0, false, err
151		}
152		if event == uint32(windows.WAIT_TIMEOUT) {
153			return 0, false, nil
154		}
155	}
156}

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 1

Tags 476

↑↓ moveenter openesc close