feat/data-dnd e05cb15

terminal.go RawHistoryFolder

1package core
2
3import (
4	"strings"
5	"time"
6
7	"github.com/bnema/neferwl/internal/ports"
8)
9
10// Automatic terminal requests use a SlotEnv token to place their windows
11// on the intended workspace even if focus changes before mapping.
12
13// termRetry is the least time between two terminals for one workspace: a
14// terminal that fails, or maps and exits at once, is not respawned in a loop.
15const termRetry = 5 * time.Second
16
17// keepsTerminal reports whether empty workspaces get a terminal.
18func (c *Core) keepsTerminal() bool {
19	return c.opts.Terminal && c.cfg.Terminal.AutoOpen != "off" && len(c.cfg.Terminal.Command) > 0
20}
21
22// fillEmpty returns the terminals to spawn for empty workspaces on screen.
23// Workspaces with slots are left to them.
24func (c *Core) fillEmpty() []ports.SpawnRequest {
25	now := c.now()
26	c.placement.expireWorkspaceClaims(now)
27	if !c.keepsTerminal() {
28		return nil
29	}
30	var reqs []ports.SpawnRequest
31	for _, s := range c.screens {
32		w := s.mon.Current()
33		if c.cfg.Terminal.AutoOpen == "first" {
34			if c.firstTerminalResolved || s != c.cur() || s.name() == "" {
35				continue
36			}
37			c.firstTerminalResolved = true
38			if w != s.mon.Workspaces[0] || !w.empty() || c.hasSlots(w) {
39				continue
40			}
41		}
42		if s.name() == "" || !w.empty() || c.hasSlots(w) || c.placement.workspacePending(w, spawnTerminal) || now.Sub(w.termAt) < termRetry {
43			continue
44		}
45		c.firstTerminalResolved = true
46		w.termAt = now
47		reqs = append(reqs, c.placement.request(c.cfg.Terminal.Command, spawnTarget{kind: spawnTerminal, ws: w, at: now}))
48	}
49	return reqs
50}
51
52// cancelClaim removes a workspace claim minted for a send suppressed by the
53// gate. A terminal may be requested again at once.
54func (c *Core) cancelClaim(req ports.SpawnRequest) {
55	for _, env := range req.Env {
56		if token, ok := strings.CutPrefix(env, ports.SlotEnv+"="); ok {
57			if target, ok := c.placement.pending[token]; ok && target.kind != spawnSlot {
58				if target.kind == spawnTerminal {
59					target.ws.termAt = time.Time{}
60				}
61				delete(c.placement.pending, token)
62			}
63		}
64	}
65}
66
67func (c *Core) hasSlots(w *Workspace) bool {
68	for key := range c.slots {
69		if w.Name != "" && key.workspace == w.Name {
70			return true
71		}
72	}
73	return false
74}

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 25

Tags 10

↑↓ moveenter openesc close