detached ece7682

resize_mode.go RawHistoryFolder

1package daemon
2
3import (
4	"errors"
5
6	"github.com/bnema/vev/internal/domain"
7	"github.com/bnema/vev/internal/domain/layout"
8	"github.com/bnema/vev/internal/ports"
9	"github.com/bnema/vev/internal/usecase/keys"
10)
11
12const (
13	resizeStepCols = 2
14	resizeStepRows = 1
15)
16
17func (d *Daemon) resizePane(target daemonActionTarget, axis layout.Axis, delta int) error {
18	return d.mutateTargetLayout(target, true, func(candidate *layout.Tree, area domain.Rect) error {
19		originalFocus := candidate.Focus
20		candidate.Focus = target.pane.id
21		if err := candidate.ResizeFocus(axis, delta, area); err != nil {
22			return err
23		}
24		candidate.Focus = originalFocus
25		return nil
26	})
27}
28
29func (d *Daemon) equalizePanes(target daemonActionTarget) error {
30	return d.mutateTargetLayout(target, false, func(candidate *layout.Tree, area domain.Rect) error {
31		return candidate.Equalize(area)
32	})
33}
34
35func resizeUserError(err error) error {
36	switch {
37	case errors.Is(err, layout.ErrNotInSplit):
38		return domain.UserWarn(domain.NoticeLayoutTooSmall, "pane is not in a split", err)
39	case errors.Is(err, layout.ErrTooSmall):
40		return domain.UserWarn(domain.NoticeLayoutTooSmall, "pane cannot be resized further", err)
41	default:
42		return err
43	}
44}
45
46func (d *Daemon) enterResizeMode(sess *session, ac *attachedClient) error {
47	if sess == nil || ac == nil {
48		return layout.ErrNotFound
49	}
50	target := resolveDaemonActionTargetForAttachment(sess, ac)
51	if target.tab == nil || target.pane == nil {
52		return layout.ErrNotFound
53	}
54	target.tab.mu.Lock()
55	canResize := target.tab.tree != nil && target.tab.tree.CanResize()
56	target.tab.mu.Unlock()
57	if !canResize {
58		return resizeUserError(layout.ErrNotInSplit)
59	}
60	d.exitCopyMode(ac)
61	ac.initOverlays()
62	ac.overlays.resizeMu.Lock()
63	ac.overlays.resizeESC.stop()
64	ac.overlays.resizeActive = true
65	ac.overlays.resizePending = nil
66	ac.overlays.resizeMu.Unlock()
67	d.invalidateRender(sess, ac, true, "resize_mode.go")
68	return nil
69}
70
71func (d *Daemon) handleResizeInput(ac *attachedClient, data []byte) {
72	if ac == nil || ac.overlays == nil {
73		return
74	}
75	rt := ac.overlays
76	var requests []daemonActionRequest
77	exit := false
78	rt.resizeMu.Lock()
79	if !rt.resizeActive {
80		rt.resizePending = nil
81		rt.resizeESC.stop()
82		rt.resizeMu.Unlock()
83		return
84	}
85	hadPending := len(rt.resizePending) != 0
86	if rt.resizeESC.timer != nil {
87		rt.resizeESC.stop()
88	}
89	retainEscape := !hadPending && len(data) != 0 && data[len(data)-1] == keys.ESC
90	if retainEscape {
91		data = data[:len(data)-1]
92	}
93	appendResize := func(axis layout.Axis, delta int) {
94		requests = append(requests, daemonActionRequest{kind: daemonActionResizePane, axis: axis, delta: delta})
95	}
96	routeOverlayBytes(data, &rt.resizePending, overlayEvents{
97		rune: func(r rune) {
98			switch r {
99			case 'h':
100				appendResize(layout.Width, -resizeStepCols)
101			case 'l':
102				appendResize(layout.Width, resizeStepCols)
103			case 'k':
104				appendResize(layout.Height, -resizeStepRows)
105			case 'j':
106				appendResize(layout.Height, resizeStepRows)
107			case '=':
108				requests = append(requests, daemonActionRequest{kind: daemonActionEqualizePanes})
109			case 'q':
110				exit = true
111			}
112		},
113		left:   func() { appendResize(layout.Width, -resizeStepCols) },
114		right:  func() { appendResize(layout.Width, resizeStepCols) },
115		up:     func() { appendResize(layout.Height, -resizeStepRows) },
116		down:   func() { appendResize(layout.Height, resizeStepRows) },
117		cancel: func() { exit = true },
118		enter:  func() { exit = true },
119	})
120	if exit {
121		rt.resizeActive = false
122		rt.resizePending = nil
123		rt.resizeESC.stop()
124	} else if retainEscape {
125		d.retainResizeESCLocked(ac)
126	}
127	rt.resizeMu.Unlock()
128
129	sess := ac.currentSession()
130	if sess == nil {
131		return
132	}
133	for _, request := range requests {
134		if _, err := d.dispatchAction(daemonActionDispatch{
135			request:     request,
136			serialize:   sess,
137			resolveFrom: sess,
138			attachment:  ac,
139			producer:    "resize_mode.go",
140		}); err != nil {
141			d.reportError(sess, resizeUserError(err))
142		}
143	}
144	if exit {
145		d.invalidateRender(sess, ac, true, "resize_mode.go")
146	}
147}
148
149// retainResizeESCLocked requires ac.overlays.resizeMu, not d.mu.
150func (d *Daemon) retainResizeESCLocked(ac *attachedClient) {
151	rt := ac.overlays
152	rt.resizePending = append(rt.resizePending[:0], keys.ESC)
153	rt.resizeESC.retain(d.clock, keys.ESCDelay, func(timer ports.Timer) {
154		rt.resizeMu.Lock()
155		if rt.resizeESC.timer != timer {
156			rt.resizeMu.Unlock()
157			return
158		}
159		rt.resizePending = nil
160		rt.resizeESC.timer = nil
161		rt.resizeESC.done = nil
162		active := rt.resizeActive
163		rt.resizeActive = false
164		rt.resizeMu.Unlock()
165		if active {
166			if sess := ac.currentSession(); sess != nil {
167				d.invalidateRender(sess, ac, true, "resize_mode.go")
168			}
169		}
170	})
171}

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