detached 3f6dcc6

fix(config): remove remote configuration

Signed-off-by: brice <b@bnema.dev>
commit
3f6dcc6bf606e4c68c7b7becefa9758e89050a41
author
brice · · 2026-07-30 08:33 UTC
parent
a806195
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
8 files · +2 -328
M docs/configuration.md +1 -13
@@ -75,21 +75,9 @@ code.toggle-floating-pane = FLT
7575 code.rename-session = RNS
7676 code.rename-tab = RNT
7777 code.detach = DET
78-
79-[remote]
80-enabled = true
81-remember = true
8278 ```
8379
84-Invalid values log a warning and resolve that setting to its default on both initial load and reload. A leftover `[remote] hosts = ...` assignment fails closed as unsupported; host lists are not stored in config.
85-
86-## Remote
87-
88-The optional `[remote]` section accepts only scalar booleans: `enabled` and `remember` (TOML-style `true`/`false`). Both default to `true`.
89-
90-`enabled = false` is a kill-switch for unified remote listing and host management. Direct `vev attach user@host[:session]` still works. `remember = false` stops learning hosts on successful attach.
91-
92-Host lists are not part of config. A `hosts` key under `[remote]` is a hard load error. Unrecognized remote keys log an `unknown key` warning. An unrecognized section header logs an `unknown section` warning and ignores its keys until the next section header.
80+Invalid values log a warning and resolve that setting to its default on both initial load and reload.
9381
9482 ## Logs and durable state
9583
@@ -75,21 +75,9 @@ code.toggle-floating-pane = FLT
75 code.rename-session = RNS75 code.rename-session = RNS
76 code.rename-tab = RNT76 code.rename-tab = RNT
77 code.detach = DET77 code.detach = DET
78-
79-[remote]
80-enabled = true
81-remember = true
82 ```78 ```
83 79
84-Invalid values log a warning and resolve that setting to its default on both initial load and reload. A leftover `[remote] hosts = ...` assignment fails closed as unsupported; host lists are not stored in config.80+Invalid values log a warning and resolve that setting to its default on both initial load and reload.
85-
86-## Remote
87-
88-The optional `[remote]` section accepts only scalar booleans: `enabled` and `remember` (TOML-style `true`/`false`). Both default to `true`.
89-
90-`enabled = false` is a kill-switch for unified remote listing and host management. Direct `vev attach user@host[:session]` still works. `remember = false` stops learning hosts on successful attach.
91-
92-Host lists are not part of config. A `hosts` key under `[remote]` is a hard load error. Unrecognized remote keys log an `unknown key` warning. An unrecognized section header logs an `unknown section` warning and ignores its keys until the next section header.
93 81
94 ## Logs and durable state82 ## Logs and durable state
95 83
M internal/adapters/config/config.go +1 -31
@@ -21,14 +21,11 @@ const pollInterval = 2 * time.Second
2121 var (
2222 processNamePattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$`)
2323 percentagePattern = regexp.MustCompile(`^[0-9]{1,3}%$`)
24- sectionPattern = regexp.MustCompile(`^\[([^\]]+)\]$`)
2524 )
2625
2726 // Parse reads vev's flat action = value config format. Duplicate action keys
2827 // are accepted with a warning; the last value wins while first-seen action order
29-// is preserved for binding conflict resolution. An optional [remote] section
30-// accepts enabled/remember with TOML-style true/false values. A leftover hosts
31-// assignment fails closed as unsupported.
28+// is preserved for binding conflict resolution.
3229 func Parse(r io.Reader) (domain.Config, []domain.Warning, error) {
3330 cfg := domain.Defaults()
3431 var warnings []domain.Warning
@@ -38,8 +35,6 @@ func Parse(r io.Reader) (domain.Config, []domain.Warning, error) {
3835 seenPaletteKeys := make(map[string]bool)
3936 seenNavKeys := make(map[string]bool)
4037 seenTabsKeys := make(map[string]bool)
41- seenRemoteKeys := make(map[string]bool)
42- section := ""
4338
4439 scanner := bufio.NewScanner(r)
4540 lineNo := 0
@@ -54,17 +49,6 @@ func Parse(r io.Reader) (domain.Config, []domain.Warning, error) {
5449 continue
5550 }
5651
57- if matches := sectionPattern.FindStringSubmatch(line); matches != nil {
58- name := matches[1]
59- if name == "remote" {
60- section = "remote"
61- } else {
62- section = "unknown:" + name
63- warnings = append(warnings, domain.Warning{Line: lineNo, Msg: fmt.Sprintf("unknown section %q", name)})
64- }
65- continue
66- }
67-
6852 key, value, ok := strings.Cut(line, "=")
6953 if !ok {
7054 warnings = append(warnings, domain.Warning{Line: lineNo, Msg: "missing '='"})
@@ -77,20 +61,6 @@ func Parse(r io.Reader) (domain.Config, []domain.Warning, error) {
7761 continue
7862 }
7963
80- if strings.HasPrefix(section, "unknown:") {
81- continue
82- }
83- if section == "remote" {
84- var remoteWarnings []domain.Warning
85- var remoteErr error
86- cfg.Remote, remoteWarnings, remoteErr = parseRemoteKey(cfg.Remote, seenRemoteKeys, key, value, lineNo)
87- warnings = append(warnings, remoteWarnings...)
88- if remoteErr != nil {
89- return cfg, warnings, remoteErr
90- }
91- continue
92- }
93-
9464 switch {
9565 case key == "theme":
9666 mode, ok := parseTheme(value)
@@ -21,14 +21,11 @@ const pollInterval = 2 * time.Second
21 var (21 var (
22 processNamePattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$`)22 processNamePattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$`)
23 percentagePattern = regexp.MustCompile(`^[0-9]{1,3}%$`)23 percentagePattern = regexp.MustCompile(`^[0-9]{1,3}%$`)
24- sectionPattern = regexp.MustCompile(`^\[([^\]]+)\]$`)
25 )24 )
26 25
27 // Parse reads vev's flat action = value config format. Duplicate action keys26 // Parse reads vev's flat action = value config format. Duplicate action keys
28 // are accepted with a warning; the last value wins while first-seen action order27 // are accepted with a warning; the last value wins while first-seen action order
29-// is preserved for binding conflict resolution. An optional [remote] section28+// is preserved for binding conflict resolution.
30-// accepts enabled/remember with TOML-style true/false values. A leftover hosts
31-// assignment fails closed as unsupported.
32 func Parse(r io.Reader) (domain.Config, []domain.Warning, error) {29 func Parse(r io.Reader) (domain.Config, []domain.Warning, error) {
33 cfg := domain.Defaults()30 cfg := domain.Defaults()
34 var warnings []domain.Warning31 var warnings []domain.Warning
@@ -38,8 +35,6 @@ func Parse(r io.Reader) (domain.Config, []domain.Warning, error) {
38 seenPaletteKeys := make(map[string]bool)35 seenPaletteKeys := make(map[string]bool)
39 seenNavKeys := make(map[string]bool)36 seenNavKeys := make(map[string]bool)
40 seenTabsKeys := make(map[string]bool)37 seenTabsKeys := make(map[string]bool)
41- seenRemoteKeys := make(map[string]bool)
42- section := ""
43 38
44 scanner := bufio.NewScanner(r)39 scanner := bufio.NewScanner(r)
45 lineNo := 040 lineNo := 0
@@ -54,17 +49,6 @@ func Parse(r io.Reader) (domain.Config, []domain.Warning, error) {
54 continue49 continue
55 }50 }
56 51
57- if matches := sectionPattern.FindStringSubmatch(line); matches != nil {
58- name := matches[1]
59- if name == "remote" {
60- section = "remote"
61- } else {
62- section = "unknown:" + name
63- warnings = append(warnings, domain.Warning{Line: lineNo, Msg: fmt.Sprintf("unknown section %q", name)})
64- }
65- continue
66- }
67-
68 key, value, ok := strings.Cut(line, "=")52 key, value, ok := strings.Cut(line, "=")
69 if !ok {53 if !ok {
70 warnings = append(warnings, domain.Warning{Line: lineNo, Msg: "missing '='"})54 warnings = append(warnings, domain.Warning{Line: lineNo, Msg: "missing '='"})
@@ -77,20 +61,6 @@ func Parse(r io.Reader) (domain.Config, []domain.Warning, error) {
77 continue61 continue
78 }62 }
79 63
80- if strings.HasPrefix(section, "unknown:") {
81- continue
82- }
83- if section == "remote" {
84- var remoteWarnings []domain.Warning
85- var remoteErr error
86- cfg.Remote, remoteWarnings, remoteErr = parseRemoteKey(cfg.Remote, seenRemoteKeys, key, value, lineNo)
87- warnings = append(warnings, remoteWarnings...)
88- if remoteErr != nil {
89- return cfg, warnings, remoteErr
90- }
91- continue
92- }
93-
94 switch {64 switch {
95 case key == "theme":65 case key == "theme":
96 mode, ok := parseTheme(value)66 mode, ok := parseTheme(value)
M internal/adapters/config/config_test.go +0 -15
@@ -29,7 +29,6 @@ func TestParse(t *testing.T) {
2929 input string
3030 want domain.Config
3131 wantWarnings []domain.Warning
32- checkRemote bool
3332 }{
3433 {
3534 name: "comments and inline comments",
@@ -52,17 +51,6 @@ func TestParse(t *testing.T) {
5251 Codes: map[string]string{"detach": "dt"},
5352 },
5453 },
55- {
56- name: "remote can disable enabled and remember",
57- input: "[remote]\nenabled = false\nremember = false\n",
58- want: domain.Config{
59- Theme: domain.ThemeAuto,
60- BindingEntries: []domain.ConfigEntry{},
61- Codes: map[string]string{},
62- Remote: domain.RemoteConfig{Enabled: false, Remember: false},
63- },
64- checkRemote: true,
65- },
6654 {
6755 name: "invalid theme warns and keeps default",
6856 input: "theme = blue\n",
@@ -370,9 +358,6 @@ func TestParse(t *testing.T) {
370358 if !tt.want.Palette.AnchorSet {
371359 tt.want.Palette = domain.Defaults().Palette
372360 }
373- if !tt.checkRemote {
374- tt.want.Remote = domain.Defaults().Remote
375- }
376361 got, warnings, err := Parse(strings.NewReader(tt.input))
377362 if err != nil {
378363 t.Fatalf("Parse() error = %v", err)
@@ -29,7 +29,6 @@ func TestParse(t *testing.T) {
29 input string29 input string
30 want domain.Config30 want domain.Config
31 wantWarnings []domain.Warning31 wantWarnings []domain.Warning
32- checkRemote bool
33 }{32 }{
34 {33 {
35 name: "comments and inline comments",34 name: "comments and inline comments",
@@ -52,17 +51,6 @@ func TestParse(t *testing.T) {
52 Codes: map[string]string{"detach": "dt"},51 Codes: map[string]string{"detach": "dt"},
53 },52 },
54 },53 },
55- {
56- name: "remote can disable enabled and remember",
57- input: "[remote]\nenabled = false\nremember = false\n",
58- want: domain.Config{
59- Theme: domain.ThemeAuto,
60- BindingEntries: []domain.ConfigEntry{},
61- Codes: map[string]string{},
62- Remote: domain.RemoteConfig{Enabled: false, Remember: false},
63- },
64- checkRemote: true,
65- },
66 {54 {
67 name: "invalid theme warns and keeps default",55 name: "invalid theme warns and keeps default",
68 input: "theme = blue\n",56 input: "theme = blue\n",
@@ -370,9 +358,6 @@ func TestParse(t *testing.T) {
370 if !tt.want.Palette.AnchorSet {358 if !tt.want.Palette.AnchorSet {
371 tt.want.Palette = domain.Defaults().Palette359 tt.want.Palette = domain.Defaults().Palette
372 }360 }
373- if !tt.checkRemote {
374- tt.want.Remote = domain.Defaults().Remote
375- }
376 got, warnings, err := Parse(strings.NewReader(tt.input))361 got, warnings, err := Parse(strings.NewReader(tt.input))
377 if err != nil {362 if err != nil {
378 t.Fatalf("Parse() error = %v", err)363 t.Fatalf("Parse() error = %v", err)
D internal/adapters/config/remote.go +0 -50
@@ -1,50 +0,0 @@
1-package config
2-
3-import (
4- "errors"
5- "fmt"
6- "strings"
7-
8- "github.com/bnema/vev/internal/domain"
9-)
10-
11-// ErrUnsupportedRemoteHosts indicates a legacy remote hosts assignment.
12-var ErrUnsupportedRemoteHosts = errors.New("unsupported remote hosts assignment")
13-
14-func parseRemoteKey(remote domain.RemoteConfig, seen map[string]bool, key, value string, lineNo int) (domain.RemoteConfig, []domain.Warning, error) {
15- var warnings []domain.Warning
16- switch key {
17- case "enabled":
18- warnings = warnDuplicateKey(warnings, seen, key, lineNo)
19- on, ok := parseTrueFalse(value)
20- if !ok {
21- warnings = append(warnings, domain.Warning{Line: lineNo, Msg: fmt.Sprintf("invalid enabled %q", value)})
22- return remote, warnings, nil
23- }
24- remote.Enabled = on
25- case "remember":
26- warnings = warnDuplicateKey(warnings, seen, key, lineNo)
27- on, ok := parseTrueFalse(value)
28- if !ok {
29- warnings = append(warnings, domain.Warning{Line: lineNo, Msg: fmt.Sprintf("invalid remember %q", value)})
30- return remote, warnings, nil
31- }
32- remote.Remember = on
33- case "hosts":
34- return remote, warnings, fmt.Errorf("line %d: %w", lineNo, ErrUnsupportedRemoteHosts)
35- default:
36- warnings = append(warnings, domain.Warning{Line: lineNo, Msg: fmt.Sprintf("unknown key %q", key)})
37- }
38- return remote, warnings, nil
39-}
40-
41-func parseTrueFalse(value string) (bool, bool) {
42- switch strings.TrimSpace(value) {
43- case "true":
44- return true, true
45- case "false":
46- return false, true
47- default:
48- return false, false
49- }
50-}
@@ -1,50 +0,0 @@
1-package config
2-
3-import (
4- "errors"
5- "fmt"
6- "strings"
7-
8- "github.com/bnema/vev/internal/domain"
9-)
10-
11-// ErrUnsupportedRemoteHosts indicates a legacy remote hosts assignment.
12-var ErrUnsupportedRemoteHosts = errors.New("unsupported remote hosts assignment")
13-
14-func parseRemoteKey(remote domain.RemoteConfig, seen map[string]bool, key, value string, lineNo int) (domain.RemoteConfig, []domain.Warning, error) {
15- var warnings []domain.Warning
16- switch key {
17- case "enabled":
18- warnings = warnDuplicateKey(warnings, seen, key, lineNo)
19- on, ok := parseTrueFalse(value)
20- if !ok {
21- warnings = append(warnings, domain.Warning{Line: lineNo, Msg: fmt.Sprintf("invalid enabled %q", value)})
22- return remote, warnings, nil
23- }
24- remote.Enabled = on
25- case "remember":
26- warnings = warnDuplicateKey(warnings, seen, key, lineNo)
27- on, ok := parseTrueFalse(value)
28- if !ok {
29- warnings = append(warnings, domain.Warning{Line: lineNo, Msg: fmt.Sprintf("invalid remember %q", value)})
30- return remote, warnings, nil
31- }
32- remote.Remember = on
33- case "hosts":
34- return remote, warnings, fmt.Errorf("line %d: %w", lineNo, ErrUnsupportedRemoteHosts)
35- default:
36- warnings = append(warnings, domain.Warning{Line: lineNo, Msg: fmt.Sprintf("unknown key %q", key)})
37- }
38- return remote, warnings, nil
39-}
40-
41-func parseTrueFalse(value string) (bool, bool) {
42- switch strings.TrimSpace(value) {
43- case "true":
44- return true, true
45- case "false":
46- return false, true
47- default:
48- return false, false
49- }
50-}
D internal/adapters/config/remote_test.go +0 -195
@@ -1,195 +0,0 @@
1-package config
2-
3-import (
4- "strings"
5- "testing"
6-
7- "github.com/bnema/vev/internal/domain"
8- "github.com/stretchr/testify/require"
9-)
10-
11-func TestParseRemote(t *testing.T) {
12- t.Parallel()
13-
14- tests := []struct {
15- name string
16- input string
17- want domain.RemoteConfig
18- wantErr string
19- wantErrIs error
20- wantWarnings []domain.Warning
21- wantTheme domain.ThemeMode
22- checkTheme bool
23- wantBindingEntries []domain.ConfigEntry
24- checkBindings bool
25- }{
26- {
27- name: "defaults when section absent",
28- want: domain.RemoteConfig{Enabled: true, Remember: true},
29- },
30- {
31- name: "complete remote block",
32- input: `[remote]
33-enabled = true
34-remember = true
35-`,
36- want: domain.RemoteConfig{
37- Enabled: true,
38- Remember: true,
39- },
40- },
41- {
42- name: "enabled false",
43- input: `[remote]
44-enabled = false
45-`,
46- want: domain.RemoteConfig{Enabled: false, Remember: true},
47- },
48- {
49- name: "remember false",
50- input: `[remote]
51-remember = false
52-`,
53- want: domain.RemoteConfig{Enabled: true, Remember: false},
54- },
55- {
56- name: "legacy on off rejected and defaults retained",
57- input: `[remote]
58-enabled = off
59-remember = on
60-`,
61- want: domain.RemoteConfig{Enabled: true, Remember: true},
62- wantWarnings: []domain.Warning{
63- {Line: 2, Msg: `invalid enabled "off"`},
64- {Line: 3, Msg: `invalid remember "on"`},
65- },
66- },
67- {
68- name: "duplicate keys warn and last valid wins",
69- input: `[remote]
70-enabled = false
71-enabled = true
72-remember = false
73-remember = true
74-`,
75- want: domain.RemoteConfig{Enabled: true, Remember: true},
76- wantWarnings: []domain.Warning{
77- {Line: 3, Msg: `duplicate key "enabled"`},
78- {Line: 5, Msg: `duplicate key "remember"`},
79- },
80- },
81- {
82- name: "invalid booleans warn and retain defaults",
83- input: `[remote]
84-enabled = yes
85-remember = True
86-`,
87- want: domain.RemoteConfig{Enabled: true, Remember: true},
88- wantWarnings: []domain.Warning{
89- {Line: 2, Msg: `invalid enabled "yes"`},
90- {Line: 3, Msg: `invalid remember "True"`},
91- },
92- },
93- {
94- name: "hosts assignment fails closed",
95- input: `[remote]
96-hosts = ["arch", "build@mule"]
97-`,
98- wantErr: `line 2: unsupported remote hosts assignment`,
99- wantErrIs: ErrUnsupportedRemoteHosts,
100- wantWarnings: nil,
101- },
102- {
103- name: "empty hosts array fails closed",
104- input: `[remote]
105-hosts = []
106-`,
107- wantErr: `line 2: unsupported remote hosts assignment`,
108- wantErrIs: ErrUnsupportedRemoteHosts,
109- wantWarnings: nil,
110- },
111- {
112- name: "malformed hosts assignment fails closed",
113- input: `[remote]
114-hosts = not-an-array
115-`,
116- wantErr: `line 2: unsupported remote hosts assignment`,
117- wantErrIs: ErrUnsupportedRemoteHosts,
118- wantWarnings: nil,
119- },
120- {
121- name: "hosts cannot fall through as a binding",
122- input: `theme = light
123-[remote]
124-hosts = ["arch"]
125-new-tab = alt+t
126-`,
127- wantErr: `line 3: unsupported remote hosts assignment`,
128- wantErrIs: ErrUnsupportedRemoteHosts,
129- wantWarnings: nil,
130- },
131- {
132- name: "unknown remote key warns",
133- input: `[remote]
134-proxy = true
135-enabled = false
136-`,
137- want: domain.RemoteConfig{Enabled: false, Remember: true},
138- wantWarnings: []domain.Warning{
139- {Line: 2, Msg: `unknown key "proxy"`},
140- },
141- },
142- {
143- name: "unknown section warns and ignores its keys",
144- input: `theme = dark
145-[other]
146-enabled = false
147-[remote]
148-enabled = false
149-`,
150- want: domain.RemoteConfig{Enabled: false, Remember: true},
151- wantWarnings: []domain.Warning{
152- {Line: 2, Msg: `unknown section "other"`},
153- },
154- wantTheme: domain.ThemeDark,
155- checkTheme: true,
156- },
157- {
158- name: "flat keys preserved outside remote section",
159- input: `theme = light
160-new-tab = alt+t
161-[remote]
162-enabled = true
163-`,
164- want: domain.RemoteConfig{Enabled: true, Remember: true},
165- wantTheme: domain.ThemeLight,
166- checkTheme: true,
167- wantBindingEntries: []domain.ConfigEntry{{Key: "new-tab", Value: "alt+t"}},
168- checkBindings: true,
169- },
170- }
171-
172- for _, tt := range tests {
173- t.Run(tt.name, func(t *testing.T) {
174- t.Parallel()
175-
176- cfg, warnings, err := Parse(strings.NewReader(tt.input))
177- if tt.wantErr != "" {
178- require.EqualError(t, err, tt.wantErr)
179- require.ErrorIs(t, err, tt.wantErrIs)
180- require.Equal(t, tt.wantWarnings, warnings)
181- require.Empty(t, cfg.BindingEntries)
182- return
183- }
184- require.NoError(t, err)
185- require.Equal(t, tt.want, cfg.Remote)
186- require.Equal(t, tt.wantWarnings, warnings)
187- if tt.checkTheme {
188- require.Equal(t, tt.wantTheme, cfg.Theme)
189- }
190- if tt.checkBindings {
191- require.Equal(t, tt.wantBindingEntries, cfg.BindingEntries)
192- }
193- })
194- }
195-}
@@ -1,195 +0,0 @@
1-package config
2-
3-import (
4- "strings"
5- "testing"
6-
7- "github.com/bnema/vev/internal/domain"
8- "github.com/stretchr/testify/require"
9-)
10-
11-func TestParseRemote(t *testing.T) {
12- t.Parallel()
13-
14- tests := []struct {
15- name string
16- input string
17- want domain.RemoteConfig
18- wantErr string
19- wantErrIs error
20- wantWarnings []domain.Warning
21- wantTheme domain.ThemeMode
22- checkTheme bool
23- wantBindingEntries []domain.ConfigEntry
24- checkBindings bool
25- }{
26- {
27- name: "defaults when section absent",
28- want: domain.RemoteConfig{Enabled: true, Remember: true},
29- },
30- {
31- name: "complete remote block",
32- input: `[remote]
33-enabled = true
34-remember = true
35-`,
36- want: domain.RemoteConfig{
37- Enabled: true,
38- Remember: true,
39- },
40- },
41- {
42- name: "enabled false",
43- input: `[remote]
44-enabled = false
45-`,
46- want: domain.RemoteConfig{Enabled: false, Remember: true},
47- },
48- {
49- name: "remember false",
50- input: `[remote]
51-remember = false
52-`,
53- want: domain.RemoteConfig{Enabled: true, Remember: false},
54- },
55- {
56- name: "legacy on off rejected and defaults retained",
57- input: `[remote]
58-enabled = off
59-remember = on
60-`,
61- want: domain.RemoteConfig{Enabled: true, Remember: true},
62- wantWarnings: []domain.Warning{
63- {Line: 2, Msg: `invalid enabled "off"`},
64- {Line: 3, Msg: `invalid remember "on"`},
65- },
66- },
67- {
68- name: "duplicate keys warn and last valid wins",
69- input: `[remote]
70-enabled = false
71-enabled = true
72-remember = false
73-remember = true
74-`,
75- want: domain.RemoteConfig{Enabled: true, Remember: true},
76- wantWarnings: []domain.Warning{
77- {Line: 3, Msg: `duplicate key "enabled"`},
78- {Line: 5, Msg: `duplicate key "remember"`},
79- },
80- },
81- {
82- name: "invalid booleans warn and retain defaults",
83- input: `[remote]
84-enabled = yes
85-remember = True
86-`,
87- want: domain.RemoteConfig{Enabled: true, Remember: true},
88- wantWarnings: []domain.Warning{
89- {Line: 2, Msg: `invalid enabled "yes"`},
90- {Line: 3, Msg: `invalid remember "True"`},
91- },
92- },
93- {
94- name: "hosts assignment fails closed",
95- input: `[remote]
96-hosts = ["arch", "build@mule"]
97-`,
98- wantErr: `line 2: unsupported remote hosts assignment`,
99- wantErrIs: ErrUnsupportedRemoteHosts,
100- wantWarnings: nil,
101- },
102- {
103- name: "empty hosts array fails closed",
104- input: `[remote]
105-hosts = []
106-`,
107- wantErr: `line 2: unsupported remote hosts assignment`,
108- wantErrIs: ErrUnsupportedRemoteHosts,
109- wantWarnings: nil,
110- },
111- {
112- name: "malformed hosts assignment fails closed",
113- input: `[remote]
114-hosts = not-an-array
115-`,
116- wantErr: `line 2: unsupported remote hosts assignment`,
117- wantErrIs: ErrUnsupportedRemoteHosts,
118- wantWarnings: nil,
119- },
120- {
121- name: "hosts cannot fall through as a binding",
122- input: `theme = light
123-[remote]
124-hosts = ["arch"]
125-new-tab = alt+t
126-`,
127- wantErr: `line 3: unsupported remote hosts assignment`,
128- wantErrIs: ErrUnsupportedRemoteHosts,
129- wantWarnings: nil,
130- },
131- {
132- name: "unknown remote key warns",
133- input: `[remote]
134-proxy = true
135-enabled = false
136-`,
137- want: domain.RemoteConfig{Enabled: false, Remember: true},
138- wantWarnings: []domain.Warning{
139- {Line: 2, Msg: `unknown key "proxy"`},
140- },
141- },
142- {
143- name: "unknown section warns and ignores its keys",
144- input: `theme = dark
145-[other]
146-enabled = false
147-[remote]
148-enabled = false
149-`,
150- want: domain.RemoteConfig{Enabled: false, Remember: true},
151- wantWarnings: []domain.Warning{
152- {Line: 2, Msg: `unknown section "other"`},
153- },
154- wantTheme: domain.ThemeDark,
155- checkTheme: true,
156- },
157- {
158- name: "flat keys preserved outside remote section",
159- input: `theme = light
160-new-tab = alt+t
161-[remote]
162-enabled = true
163-`,
164- want: domain.RemoteConfig{Enabled: true, Remember: true},
165- wantTheme: domain.ThemeLight,
166- checkTheme: true,
167- wantBindingEntries: []domain.ConfigEntry{{Key: "new-tab", Value: "alt+t"}},
168- checkBindings: true,
169- },
170- }
171-
172- for _, tt := range tests {
173- t.Run(tt.name, func(t *testing.T) {
174- t.Parallel()
175-
176- cfg, warnings, err := Parse(strings.NewReader(tt.input))
177- if tt.wantErr != "" {
178- require.EqualError(t, err, tt.wantErr)
179- require.ErrorIs(t, err, tt.wantErrIs)
180- require.Equal(t, tt.wantWarnings, warnings)
181- require.Empty(t, cfg.BindingEntries)
182- return
183- }
184- require.NoError(t, err)
185- require.Equal(t, tt.want, cfg.Remote)
186- require.Equal(t, tt.wantWarnings, warnings)
187- if tt.checkTheme {
188- require.Equal(t, tt.wantTheme, cfg.Theme)
189- }
190- if tt.checkBindings {
191- require.Equal(t, tt.wantBindingEntries, cfg.BindingEntries)
192- }
193- })
194- }
195-}
M internal/domain/config.go +0 -5
@@ -98,7 +98,6 @@ type Config struct {
9898 Floating FloatingConfig
9999 Nav NavConfig
100100 Tabs TabsConfig
101- Remote RemoteConfig
102101 }
103102
104103 // Warning describes a non-fatal config problem. Parsers and reloaders should
@@ -136,9 +135,5 @@ func Defaults() Config {
136135 Tabs: TabsConfig{
137136 TerminalTitle: true,
138137 },
139- Remote: RemoteConfig{
140- Enabled: true,
141- Remember: true,
142- },
143138 }
144139 }
@@ -98,7 +98,6 @@ type Config struct {
98 Floating FloatingConfig98 Floating FloatingConfig
99 Nav NavConfig99 Nav NavConfig
100 Tabs TabsConfig100 Tabs TabsConfig
101- Remote RemoteConfig
102 }101 }
103 102
104 // Warning describes a non-fatal config problem. Parsers and reloaders should103 // Warning describes a non-fatal config problem. Parsers and reloaders should
@@ -136,9 +135,5 @@ func Defaults() Config {
136 Tabs: TabsConfig{135 Tabs: TabsConfig{
137 TerminalTitle: true,136 TerminalTitle: true,
138 },137 },
139- Remote: RemoteConfig{
140- Enabled: true,
141- Remember: true,
142- },
143 }138 }
144 }139 }
M internal/domain/remote_host.go +0 -6
@@ -6,12 +6,6 @@ import (
66 "unicode"
77 )
88
9-// RemoteConfig contains user-configurable remote scalar settings.
10-type RemoteConfig struct {
11- Enabled bool
12- Remember bool
13-}
14-
159 // RemoteHost is a merged remote host entry with its source markers.
1610 type RemoteHost struct {
1711 Target string
@@ -6,12 +6,6 @@ import (
6 "unicode"6 "unicode"
7 )7 )
8 8
9-// RemoteConfig contains user-configurable remote scalar settings.
10-type RemoteConfig struct {
11- Enabled bool
12- Remember bool
13-}
14-
15 // RemoteHost is a merged remote host entry with its source markers.9 // RemoteHost is a merged remote host entry with its source markers.
16 type RemoteHost struct {10 type RemoteHost struct {
17 Target string11 Target string
M internal/domain/remote_host_test.go +0 -13
@@ -71,16 +71,3 @@ func TestRemoteHostUniqueTargets(t *testing.T) {
7171 })
7272 }
7373 }
74-
75-func TestRemoteHostConfigDefaults(t *testing.T) {
76- cfg := Defaults()
77- if !cfg.Remote.Enabled {
78- t.Fatal("Defaults().Remote.Enabled = false, want true")
79- }
80- if !cfg.Remote.Remember {
81- t.Fatal("Defaults().Remote.Remember = false, want true")
82- }
83- if cfg.Remote != (RemoteConfig{Enabled: true, Remember: true}) {
84- t.Fatalf("Defaults().Remote = %#v, want Enabled=true Remember=true only", cfg.Remote)
85- }
86-}
@@ -71,16 +71,3 @@ func TestRemoteHostUniqueTargets(t *testing.T) {
71 })71 })
72 }72 }
73 }73 }
74-
75-func TestRemoteHostConfigDefaults(t *testing.T) {
76- cfg := Defaults()
77- if !cfg.Remote.Enabled {
78- t.Fatal("Defaults().Remote.Enabled = false, want true")
79- }
80- if !cfg.Remote.Remember {
81- t.Fatal("Defaults().Remote.Remember = false, want true")
82- }
83- if cfg.Remote != (RemoteConfig{Enabled: true, Remember: true}) {
84- t.Fatalf("Defaults().Remote = %#v, want Enabled=true Remember=true only", cfg.Remote)
85- }
86-}

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