v0.3.0 ea9abc7

interactive.go RawHistoryFolder

1package app
2
3import (
4	"context"
5	"errors"
6	"fmt"
7
8	"github.com/bnema/nefercap/internal/adapters/cli"
9	"github.com/bnema/nefercap/internal/adapters/config"
10	"github.com/bnema/nefercap/internal/adapters/control"
11	"github.com/bnema/nefercap/internal/adapters/destination"
12	"github.com/bnema/nefercap/internal/adapters/selection"
13	"github.com/bnema/nefercap/internal/adapters/uierrors"
14	"github.com/bnema/nefercap/internal/adapters/wayland"
15	"github.com/bnema/nefercap/internal/ports"
16)
17
18// chooseInteractive offers workspaces only when the compositor lists them.
19// Monitor and region captures work with the standard protocols.
20func chooseInteractive(ctx context.Context, source *wayland.Source, outputs []ports.Output, options cli.Options, settings config.Settings) (ports.Selection, bool, error) {
21	mode := ports.Screenshot
22	if options.Command == cli.Rec {
23		mode = ports.Record
24	}
25	if options.Output != "" {
26		chosen, err := resolveOutput(outputs, options.Output)
27		if err != nil {
28			return ports.Selection{}, false, err
29		}
30		outputs = []ports.Output{chosen}
31	}
32	workspaces, err := source.Workspaces(ctx)
33	if err != nil {
34		if ctx.Err() != nil && uierrors.IsCancellation(err) {
35			return ports.Selection{}, false, ctx.Err()
36		}
37		return ports.Selection{}, false, fmt.Errorf("list workspace capture targets: %w", err)
38	}
39	if err := checkDirs(options, settings); err != nil {
40		return ports.Selection{}, false, err
41	}
42	selector := selection.New(mode, options.Video, workspaces, settings.Grid).WithCapabilities(source.Capabilities())
43	if options.Command == cli.AllInOne {
44		selector.AllowToggle()
45	}
46	sel, accepted, err := selector.Select(ctx, outputs)
47	if err != nil || !accepted {
48		return ports.Selection{}, false, err
49	}
50	sel.Duration = options.Duration
51	if err := assignDestination(&sel, options, settings); err != nil {
52		return ports.Selection{}, false, err
53	}
54	return sel, true, nil
55}
56
57// checkDirs creates and checks the default directories a confirmed capture
58// can use, so a bad screenshot.dir or video.dir fails before the selector
59// opens. All-in-one can reach both modes.
60func checkDirs(options cli.Options, settings config.Settings) error {
61	if options.Path != "" {
62		return nil
63	}
64	all := options.Command == cli.AllInOne
65	if all || options.Command == cli.Rec {
66		if _, err := destination.VideoIn(settings.VideoDir); err != nil {
67			return fmt.Errorf("video.dir: %w", err)
68		}
69	}
70	if (all || options.Command == cli.Shot) && !options.Clipboard && settings.Output != config.OutputClipboard {
71		if _, err := destination.ScreenshotIn(settings.ScreenshotDir); err != nil {
72			return fmt.Errorf("screenshot.dir: %w", err)
73		}
74	}
75	return nil
76}
77
78// assignDestination resolves where an interactive capture goes. Flags win over
79// the config: -file alone is a file only, -clipboard alone is clipboard only,
80// both save and copy. Without flags a screenshot follows screenshot.output.
81// Recordings always write a file; only screenshots are copied.
82func assignDestination(sel *ports.Selection, options cli.Options, settings config.Settings) (err error) {
83	if sel.Mode == ports.Record {
84		if sel.Path = options.Path; sel.Path == "" {
85			sel.Path, err = destination.VideoIn(settings.VideoDir)
86		}
87		return err
88	}
89	if options.Path != "" || options.Clipboard {
90		sel.Path, sel.Clipboard = options.Path, options.Clipboard
91		return nil
92	}
93	sel.Clipboard = settings.Output != config.OutputFile
94	if settings.Output != config.OutputClipboard {
95		sel.Path, err = destination.ScreenshotIn(settings.ScreenshotDir)
96	}
97	return err
98}
99
100func rejectCompetingSelector(ctx context.Context) error {
101	active, err := control.Query(ctx)
102	if err != nil {
103		return err
104	}
105	if active {
106		// A selector on another capture connection is not part of the
107		// recording's excluded set: it would show up in the video.
108		return errors.New("recording is active; stop it before opening a capture selector")
109	}
110	return nil
111}

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 2

Tags 9

↑↓ moveenter openesc close