feat(config): add floating pane settings
- commit
- edb3828a76f61560a30f1c70766898286a232f62
- author
- brice · · 2026-07-09 23:21 UTC
- committer
- brice · · 2026-07-10 05:40 UTC
- parent
- 10e6267
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +151 -1
M internal/adapters/config/config.go +45 -1
| @@ -18,7 +18,10 @@ import ( | |||
| 18 | 18 | ||
| 19 | 19 | const pollInterval = 2 * time.Second | |
| 20 | 20 | ||
| 21 | - | var processNamePattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$`) | |
| 21 | + | var ( | |
| 22 | + | processNamePattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$`) | |
| 23 | + | percentagePattern = regexp.MustCompile(`^[0-9]{1,3}%$`) | |
| 24 | + | ) | |
| 22 | 25 | ||
| 23 | 26 | // Parse reads vev's flat action = value config format. Duplicate action keys | |
| 24 | 27 | // are accepted with a warning; the last value wins while first-seen action order | |
| @@ -27,6 +30,7 @@ func Parse(r io.Reader) (domain.Config, []domain.Warning, error) { | |||
| 27 | 30 | cfg := domain.Defaults() | |
| 28 | 31 | var warnings []domain.Warning | |
| 29 | 32 | seenBindingKeys := make(map[string]bool) | |
| 33 | + | seenFloatingKeys := make(map[string]bool) | |
| 30 | 34 | ||
| 31 | 35 | scanner := bufio.NewScanner(r) | |
| 32 | 36 | lineNo := 0 | |
| @@ -86,6 +90,30 @@ func Parse(r io.Reader) (domain.Config, []domain.Warning, error) { | |||
| 86 | 90 | interval = domain.MinBarInterval | |
| 87 | 91 | } | |
| 88 | 92 | cfg.Bar.Interval = interval | |
| 93 | + | case key == "floating.command": | |
| 94 | + | warnings = warnDuplicateFloatingKey(warnings, seenFloatingKeys, key, lineNo) | |
| 95 | + | command, ok := parseBarCommand(value) | |
| 96 | + | if !ok { | |
| 97 | + | warnings = append(warnings, domain.Warning{Line: lineNo, Msg: fmt.Sprintf("invalid floating.command %q", value)}) | |
| 98 | + | continue | |
| 99 | + | } | |
| 100 | + | cfg.Floating.Command = command | |
| 101 | + | case key == "floating.width": | |
| 102 | + | warnings = warnDuplicateFloatingKey(warnings, seenFloatingKeys, key, lineNo) | |
| 103 | + | width, ok := parsePercentage(value) | |
| 104 | + | if !ok { | |
| 105 | + | warnings = append(warnings, domain.Warning{Line: lineNo, Msg: fmt.Sprintf("invalid floating.width %q", value)}) | |
| 106 | + | continue | |
| 107 | + | } | |
| 108 | + | cfg.Floating.Width = width | |
| 109 | + | case key == "floating.height": | |
| 110 | + | warnings = warnDuplicateFloatingKey(warnings, seenFloatingKeys, key, lineNo) | |
| 111 | + | height, ok := parsePercentage(value) | |
| 112 | + | if !ok { | |
| 113 | + | warnings = append(warnings, domain.Warning{Line: lineNo, Msg: fmt.Sprintf("invalid floating.height %q", value)}) | |
| 114 | + | continue | |
| 115 | + | } | |
| 116 | + | cfg.Floating.Height = height | |
| 89 | 117 | case strings.HasPrefix(key, "code."): | |
| 90 | 118 | codeKey := strings.TrimPrefix(key, "code.") | |
| 91 | 119 | if codeKey == "" { | |
| @@ -189,6 +217,14 @@ func fileStamp(path string) (stamp, error) { | |||
| 189 | 217 | return stamp{modTime: st.ModTime(), size: st.Size(), exists: true}, nil | |
| 190 | 218 | } | |
| 191 | 219 | ||
| 220 | + | func warnDuplicateFloatingKey(warnings []domain.Warning, seen map[string]bool, key string, lineNo int) []domain.Warning { | |
| 221 | + | if seen[key] { | |
| 222 | + | warnings = append(warnings, domain.Warning{Line: lineNo, Msg: fmt.Sprintf("duplicate key %q", key)}) | |
| 223 | + | } | |
| 224 | + | seen[key] = true | |
| 225 | + | return warnings | |
| 226 | + | } | |
| 227 | + | ||
| 192 | 228 | func updateBindingEntry(entries []domain.ConfigEntry, key, value string) { | |
| 193 | 229 | for i := range entries { | |
| 194 | 230 | if entries[i].Key == key { | |
| @@ -224,6 +260,14 @@ func parseProcessList(value string, lineNo int) ([]string, []domain.Warning) { | |||
| 224 | 260 | return out, warnings | |
| 225 | 261 | } | |
| 226 | 262 | ||
| 263 | + | func parsePercentage(value string) (int, bool) { | |
| 264 | + | if !percentagePattern.MatchString(value) { | |
| 265 | + | return 0, false | |
| 266 | + | } | |
| 267 | + | percentage, err := strconv.Atoi(strings.TrimSuffix(value, "%")) | |
| 268 | + | return percentage, err == nil && percentage >= 1 && percentage <= 100 | |
| 269 | + | } | |
| 270 | + | ||
| 227 | 271 | func parseBarCommand(value string) (string, bool) { | |
| 228 | 272 | value = strings.TrimSpace(value) | |
| 229 | 273 | if len(value) >= 2 && value[0] == '"' && value[len(value)-1] == '"' { | |
| @@ -18,7 +18,10 @@ import ( | |||||
| 18 | 18 | ||||
| 19 | const pollInterval = 2 * time.Second | 19 | const pollInterval = 2 * time.Second | ||
| 20 | 20 | ||||
| 21 | - | var processNamePattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$`) | 21 | + | var ( |
| 22 | + | processNamePattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$`) | |||
| 23 | + | percentagePattern = regexp.MustCompile(`^[0-9]{1,3}%$`) | |||
| 24 | + | ) | |||
| 22 | 25 | ||||
| 23 | // Parse reads vev's flat action = value config format. Duplicate action keys | 26 | // Parse reads vev's flat action = value config format. Duplicate action keys | ||
| 24 | // are accepted with a warning; the last value wins while first-seen action order | 27 | // are accepted with a warning; the last value wins while first-seen action order | ||
| @@ -27,6 +30,7 @@ func Parse(r io.Reader) (domain.Config, []domain.Warning, error) { | |||||
| 27 | cfg := domain.Defaults() | 30 | cfg := domain.Defaults() | ||
| 28 | var warnings []domain.Warning | 31 | var warnings []domain.Warning | ||
| 29 | seenBindingKeys := make(map[string]bool) | 32 | seenBindingKeys := make(map[string]bool) | ||
| 33 | + | seenFloatingKeys := make(map[string]bool) | |||
| 30 | 34 | ||||
| 31 | scanner := bufio.NewScanner(r) | 35 | scanner := bufio.NewScanner(r) | ||
| 32 | lineNo := 0 | 36 | lineNo := 0 | ||
| @@ -86,6 +90,30 @@ func Parse(r io.Reader) (domain.Config, []domain.Warning, error) { | |||||
| 86 | interval = domain.MinBarInterval | 90 | interval = domain.MinBarInterval | ||
| 87 | } | 91 | } | ||
| 88 | cfg.Bar.Interval = interval | 92 | cfg.Bar.Interval = interval | ||
| 93 | + | case key == "floating.command": | |||
| 94 | + | warnings = warnDuplicateFloatingKey(warnings, seenFloatingKeys, key, lineNo) | |||
| 95 | + | command, ok := parseBarCommand(value) | |||
| 96 | + | if !ok { | |||
| 97 | + | warnings = append(warnings, domain.Warning{Line: lineNo, Msg: fmt.Sprintf("invalid floating.command %q", value)}) | |||
| 98 | + | continue | |||
| 99 | + | } | |||
| 100 | + | cfg.Floating.Command = command | |||
| 101 | + | case key == "floating.width": | |||
| 102 | + | warnings = warnDuplicateFloatingKey(warnings, seenFloatingKeys, key, lineNo) | |||
| 103 | + | width, ok := parsePercentage(value) | |||
| 104 | + | if !ok { | |||
| 105 | + | warnings = append(warnings, domain.Warning{Line: lineNo, Msg: fmt.Sprintf("invalid floating.width %q", value)}) | |||
| 106 | + | continue | |||
| 107 | + | } | |||
| 108 | + | cfg.Floating.Width = width | |||
| 109 | + | case key == "floating.height": | |||
| 110 | + | warnings = warnDuplicateFloatingKey(warnings, seenFloatingKeys, key, lineNo) | |||
| 111 | + | height, ok := parsePercentage(value) | |||
| 112 | + | if !ok { | |||
| 113 | + | warnings = append(warnings, domain.Warning{Line: lineNo, Msg: fmt.Sprintf("invalid floating.height %q", value)}) | |||
| 114 | + | continue | |||
| 115 | + | } | |||
| 116 | + | cfg.Floating.Height = height | |||
| 89 | case strings.HasPrefix(key, "code."): | 117 | case strings.HasPrefix(key, "code."): | ||
| 90 | codeKey := strings.TrimPrefix(key, "code.") | 118 | codeKey := strings.TrimPrefix(key, "code.") | ||
| 91 | if codeKey == "" { | 119 | if codeKey == "" { | ||
| @@ -189,6 +217,14 @@ func fileStamp(path string) (stamp, error) { | |||||
| 189 | return stamp{modTime: st.ModTime(), size: st.Size(), exists: true}, nil | 217 | return stamp{modTime: st.ModTime(), size: st.Size(), exists: true}, nil | ||
| 190 | } | 218 | } | ||
| 191 | 219 | ||||
| 220 | + | func warnDuplicateFloatingKey(warnings []domain.Warning, seen map[string]bool, key string, lineNo int) []domain.Warning { | |||
| 221 | + | if seen[key] { | |||
| 222 | + | warnings = append(warnings, domain.Warning{Line: lineNo, Msg: fmt.Sprintf("duplicate key %q", key)}) | |||
| 223 | + | } | |||
| 224 | + | seen[key] = true | |||
| 225 | + | return warnings | |||
| 226 | + | } | |||
| 227 | + | ||||
| 192 | func updateBindingEntry(entries []domain.ConfigEntry, key, value string) { | 228 | func updateBindingEntry(entries []domain.ConfigEntry, key, value string) { | ||
| 193 | for i := range entries { | 229 | for i := range entries { | ||
| 194 | if entries[i].Key == key { | 230 | if entries[i].Key == key { | ||
| @@ -224,6 +260,14 @@ func parseProcessList(value string, lineNo int) ([]string, []domain.Warning) { | |||||
| 224 | return out, warnings | 260 | return out, warnings | ||
| 225 | } | 261 | } | ||
| 226 | 262 | ||||
| 263 | + | func parsePercentage(value string) (int, bool) { | |||
| 264 | + | if !percentagePattern.MatchString(value) { | |||
| 265 | + | return 0, false | |||
| 266 | + | } | |||
| 267 | + | percentage, err := strconv.Atoi(strings.TrimSuffix(value, "%")) | |||
| 268 | + | return percentage, err == nil && percentage >= 1 && percentage <= 100 | |||
| 269 | + | } | |||
| 270 | + | ||||
| 227 | func parseBarCommand(value string) (string, bool) { | 271 | func parseBarCommand(value string) (string, bool) { | ||
| 228 | value = strings.TrimSpace(value) | 272 | value = strings.TrimSpace(value) | ||
| 229 | if len(value) >= 2 && value[0] == '"' && value[len(value)-1] == '"' { | 273 | if len(value) >= 2 && value[0] == '"' && value[len(value)-1] == '"' { | ||
M internal/adapters/config/config_test.go +94 -0
| @@ -91,6 +91,97 @@ func TestParse(t *testing.T) { | |||
| 91 | 91 | {Line: 3, Msg: "missing key"}, | |
| 92 | 92 | }, | |
| 93 | 93 | }, | |
| 94 | + | { | |
| 95 | + | name: "floating settings custom values", | |
| 96 | + | input: "floating.command = \"btop --utf-force\"\nfloating.width = 75%\nfloating.height = 60%\n", | |
| 97 | + | want: domain.Config{ | |
| 98 | + | Theme: domain.ThemeAuto, | |
| 99 | + | Floating: domain.FloatingConfig{ | |
| 100 | + | Command: "btop --utf-force", | |
| 101 | + | Width: 75, | |
| 102 | + | Height: 60, | |
| 103 | + | }, | |
| 104 | + | BindingEntries: []domain.ConfigEntry{}, | |
| 105 | + | Codes: map[string]string{}, | |
| 106 | + | }, | |
| 107 | + | }, | |
| 108 | + | { | |
| 109 | + | name: "floating command can be empty or quoted", | |
| 110 | + | input: "floating.command = echo ready\nfloating.command = \"\"\n", | |
| 111 | + | want: domain.Config{ | |
| 112 | + | Theme: domain.ThemeAuto, | |
| 113 | + | Floating: domain.FloatingConfig{ | |
| 114 | + | Width: 80, | |
| 115 | + | Height: 80, | |
| 116 | + | }, | |
| 117 | + | BindingEntries: []domain.ConfigEntry{}, | |
| 118 | + | Codes: map[string]string{}, | |
| 119 | + | }, | |
| 120 | + | wantWarnings: []domain.Warning{{Line: 2, Msg: "duplicate key \"floating.command\""}}, | |
| 121 | + | }, | |
| 122 | + | { | |
| 123 | + | name: "floating percentage endpoints", | |
| 124 | + | input: "floating.width = 1%\nfloating.height = 100%\n", | |
| 125 | + | want: domain.Config{ | |
| 126 | + | Theme: domain.ThemeAuto, | |
| 127 | + | Floating: domain.FloatingConfig{ | |
| 128 | + | Width: 1, | |
| 129 | + | Height: 100, | |
| 130 | + | }, | |
| 131 | + | BindingEntries: []domain.ConfigEntry{}, | |
| 132 | + | Codes: map[string]string{}, | |
| 133 | + | }, | |
| 134 | + | }, | |
| 135 | + | { | |
| 136 | + | name: "floating invalid percentages warn and preserve defaults", | |
| 137 | + | input: "floating.width = 80\nfloating.height = 0%\nfloating.width = 101%\nfloating.height = eighty%\n", | |
| 138 | + | want: domain.Config{ | |
| 139 | + | Theme: domain.ThemeAuto, | |
| 140 | + | Floating: domain.FloatingConfig{ | |
| 141 | + | Width: 80, | |
| 142 | + | Height: 80, | |
| 143 | + | }, | |
| 144 | + | BindingEntries: []domain.ConfigEntry{}, | |
| 145 | + | Codes: map[string]string{}, | |
| 146 | + | }, | |
| 147 | + | wantWarnings: []domain.Warning{ | |
| 148 | + | {Line: 1, Msg: "invalid floating.width \"80\""}, | |
| 149 | + | {Line: 2, Msg: "invalid floating.height \"0%\""}, | |
| 150 | + | {Line: 3, Msg: "duplicate key \"floating.width\""}, | |
| 151 | + | {Line: 3, Msg: "invalid floating.width \"101%\""}, | |
| 152 | + | {Line: 4, Msg: "duplicate key \"floating.height\""}, | |
| 153 | + | {Line: 4, Msg: "invalid floating.height \"eighty%\""}, | |
| 154 | + | }, | |
| 155 | + | }, | |
| 156 | + | { | |
| 157 | + | name: "floating duplicate keys warn and last valid value wins", | |
| 158 | + | input: "floating.command = btop\nfloating.command = lazygit\nfloating.width = 70%\nfloating.width = 90%\nfloating.height = 60%\nfloating.height = 85%\n", | |
| 159 | + | want: domain.Config{ | |
| 160 | + | Theme: domain.ThemeAuto, | |
| 161 | + | Floating: domain.FloatingConfig{ | |
| 162 | + | Command: "lazygit", | |
| 163 | + | Width: 90, | |
| 164 | + | Height: 85, | |
| 165 | + | }, | |
| 166 | + | BindingEntries: []domain.ConfigEntry{}, | |
| 167 | + | Codes: map[string]string{}, | |
| 168 | + | }, | |
| 169 | + | wantWarnings: []domain.Warning{ | |
| 170 | + | {Line: 2, Msg: "duplicate key \"floating.command\""}, | |
| 171 | + | {Line: 4, Msg: "duplicate key \"floating.width\""}, | |
| 172 | + | {Line: 6, Msg: "duplicate key \"floating.height\""}, | |
| 173 | + | }, | |
| 174 | + | }, | |
| 175 | + | { | |
| 176 | + | name: "floating malformed quoted command warns and keeps default", | |
| 177 | + | input: "floating.command = \"bad\\q\"\n", | |
| 178 | + | want: domain.Config{ | |
| 179 | + | Theme: domain.ThemeAuto, | |
| 180 | + | BindingEntries: []domain.ConfigEntry{}, | |
| 181 | + | Codes: map[string]string{}, | |
| 182 | + | }, | |
| 183 | + | wantWarnings: []domain.Warning{{Line: 1, Msg: "invalid floating.command \"\\\"bad\\\\q\\\"\""}}, | |
| 184 | + | }, | |
| 94 | 185 | { | |
| 95 | 186 | name: "bar settings custom values", | |
| 96 | 187 | input: "bar.top-right = custom-top\nbar.bottom-right = custom-bottom\nbar.interval = 2s\n", | |
| @@ -244,6 +335,9 @@ func TestParse(t *testing.T) { | |||
| 244 | 335 | if tt.want.Bar.Interval == 0 { | |
| 245 | 336 | tt.want.Bar = domain.Defaults().Bar | |
| 246 | 337 | } | |
| 338 | + | if tt.want.Floating.Width == 0 && tt.want.Floating.Height == 0 { | |
| 339 | + | tt.want.Floating = domain.Defaults().Floating | |
| 340 | + | } | |
| 247 | 341 | if tt.want.Snapshot.RestoreProcesses == nil && !tt.want.Snapshot.RestoreProcessesSet { | |
| 248 | 342 | tt.want.Snapshot.RestoreProcesses = append([]string(nil), domain.DefaultSnapshotRestoreProcesses()...) | |
| 249 | 343 | } | |
| @@ -91,6 +91,97 @@ func TestParse(t *testing.T) { | |||||
| 91 | {Line: 3, Msg: "missing key"}, | 91 | {Line: 3, Msg: "missing key"}, | ||
| 92 | }, | 92 | }, | ||
| 93 | }, | 93 | }, | ||
| 94 | + | { | |||
| 95 | + | name: "floating settings custom values", | |||
| 96 | + | input: "floating.command = \"btop --utf-force\"\nfloating.width = 75%\nfloating.height = 60%\n", | |||
| 97 | + | want: domain.Config{ | |||
| 98 | + | Theme: domain.ThemeAuto, | |||
| 99 | + | Floating: domain.FloatingConfig{ | |||
| 100 | + | Command: "btop --utf-force", | |||
| 101 | + | Width: 75, | |||
| 102 | + | Height: 60, | |||
| 103 | + | }, | |||
| 104 | + | BindingEntries: []domain.ConfigEntry{}, | |||
| 105 | + | Codes: map[string]string{}, | |||
| 106 | + | }, | |||
| 107 | + | }, | |||
| 108 | + | { | |||
| 109 | + | name: "floating command can be empty or quoted", | |||
| 110 | + | input: "floating.command = echo ready\nfloating.command = \"\"\n", | |||
| 111 | + | want: domain.Config{ | |||
| 112 | + | Theme: domain.ThemeAuto, | |||
| 113 | + | Floating: domain.FloatingConfig{ | |||
| 114 | + | Width: 80, | |||
| 115 | + | Height: 80, | |||
| 116 | + | }, | |||
| 117 | + | BindingEntries: []domain.ConfigEntry{}, | |||
| 118 | + | Codes: map[string]string{}, | |||
| 119 | + | }, | |||
| 120 | + | wantWarnings: []domain.Warning{{Line: 2, Msg: "duplicate key \"floating.command\""}}, | |||
| 121 | + | }, | |||
| 122 | + | { | |||
| 123 | + | name: "floating percentage endpoints", | |||
| 124 | + | input: "floating.width = 1%\nfloating.height = 100%\n", | |||
| 125 | + | want: domain.Config{ | |||
| 126 | + | Theme: domain.ThemeAuto, | |||
| 127 | + | Floating: domain.FloatingConfig{ | |||
| 128 | + | Width: 1, | |||
| 129 | + | Height: 100, | |||
| 130 | + | }, | |||
| 131 | + | BindingEntries: []domain.ConfigEntry{}, | |||
| 132 | + | Codes: map[string]string{}, | |||
| 133 | + | }, | |||
| 134 | + | }, | |||
| 135 | + | { | |||
| 136 | + | name: "floating invalid percentages warn and preserve defaults", | |||
| 137 | + | input: "floating.width = 80\nfloating.height = 0%\nfloating.width = 101%\nfloating.height = eighty%\n", | |||
| 138 | + | want: domain.Config{ | |||
| 139 | + | Theme: domain.ThemeAuto, | |||
| 140 | + | Floating: domain.FloatingConfig{ | |||
| 141 | + | Width: 80, | |||
| 142 | + | Height: 80, | |||
| 143 | + | }, | |||
| 144 | + | BindingEntries: []domain.ConfigEntry{}, | |||
| 145 | + | Codes: map[string]string{}, | |||
| 146 | + | }, | |||
| 147 | + | wantWarnings: []domain.Warning{ | |||
| 148 | + | {Line: 1, Msg: "invalid floating.width \"80\""}, | |||
| 149 | + | {Line: 2, Msg: "invalid floating.height \"0%\""}, | |||
| 150 | + | {Line: 3, Msg: "duplicate key \"floating.width\""}, | |||
| 151 | + | {Line: 3, Msg: "invalid floating.width \"101%\""}, | |||
| 152 | + | {Line: 4, Msg: "duplicate key \"floating.height\""}, | |||
| 153 | + | {Line: 4, Msg: "invalid floating.height \"eighty%\""}, | |||
| 154 | + | }, | |||
| 155 | + | }, | |||
| 156 | + | { | |||
| 157 | + | name: "floating duplicate keys warn and last valid value wins", | |||
| 158 | + | input: "floating.command = btop\nfloating.command = lazygit\nfloating.width = 70%\nfloating.width = 90%\nfloating.height = 60%\nfloating.height = 85%\n", | |||
| 159 | + | want: domain.Config{ | |||
| 160 | + | Theme: domain.ThemeAuto, | |||
| 161 | + | Floating: domain.FloatingConfig{ | |||
| 162 | + | Command: "lazygit", | |||
| 163 | + | Width: 90, | |||
| 164 | + | Height: 85, | |||
| 165 | + | }, | |||
| 166 | + | BindingEntries: []domain.ConfigEntry{}, | |||
| 167 | + | Codes: map[string]string{}, | |||
| 168 | + | }, | |||
| 169 | + | wantWarnings: []domain.Warning{ | |||
| 170 | + | {Line: 2, Msg: "duplicate key \"floating.command\""}, | |||
| 171 | + | {Line: 4, Msg: "duplicate key \"floating.width\""}, | |||
| 172 | + | {Line: 6, Msg: "duplicate key \"floating.height\""}, | |||
| 173 | + | }, | |||
| 174 | + | }, | |||
| 175 | + | { | |||
| 176 | + | name: "floating malformed quoted command warns and keeps default", | |||
| 177 | + | input: "floating.command = \"bad\\q\"\n", | |||
| 178 | + | want: domain.Config{ | |||
| 179 | + | Theme: domain.ThemeAuto, | |||
| 180 | + | BindingEntries: []domain.ConfigEntry{}, | |||
| 181 | + | Codes: map[string]string{}, | |||
| 182 | + | }, | |||
| 183 | + | wantWarnings: []domain.Warning{{Line: 1, Msg: "invalid floating.command \"\\\"bad\\\\q\\\"\""}}, | |||
| 184 | + | }, | |||
| 94 | { | 185 | { | ||
| 95 | name: "bar settings custom values", | 186 | name: "bar settings custom values", | ||
| 96 | input: "bar.top-right = custom-top\nbar.bottom-right = custom-bottom\nbar.interval = 2s\n", | 187 | input: "bar.top-right = custom-top\nbar.bottom-right = custom-bottom\nbar.interval = 2s\n", | ||
| @@ -244,6 +335,9 @@ func TestParse(t *testing.T) { | |||||
| 244 | if tt.want.Bar.Interval == 0 { | 335 | if tt.want.Bar.Interval == 0 { | ||
| 245 | tt.want.Bar = domain.Defaults().Bar | 336 | tt.want.Bar = domain.Defaults().Bar | ||
| 246 | } | 337 | } | ||
| 338 | + | if tt.want.Floating.Width == 0 && tt.want.Floating.Height == 0 { | |||
| 339 | + | tt.want.Floating = domain.Defaults().Floating | |||
| 340 | + | } | |||
| 247 | if tt.want.Snapshot.RestoreProcesses == nil && !tt.want.Snapshot.RestoreProcessesSet { | 341 | if tt.want.Snapshot.RestoreProcesses == nil && !tt.want.Snapshot.RestoreProcessesSet { | ||
| 248 | tt.want.Snapshot.RestoreProcesses = append([]string(nil), domain.DefaultSnapshotRestoreProcesses()...) | 342 | tt.want.Snapshot.RestoreProcesses = append([]string(nil), domain.DefaultSnapshotRestoreProcesses()...) | ||
| 249 | } | 343 | } | ||
M internal/domain/config.go +12 -0
| @@ -47,6 +47,13 @@ type BarConfig struct { | |||
| 47 | 47 | Interval time.Duration | |
| 48 | 48 | } | |
| 49 | 49 | ||
| 50 | + | // FloatingConfig contains launch and sizing settings for per-tab floating panes. | |
| 51 | + | type FloatingConfig struct { | |
| 52 | + | Command string | |
| 53 | + | Width int | |
| 54 | + | Height int | |
| 55 | + | } | |
| 56 | + | ||
| 50 | 57 | // Config is the user-editable vev configuration after parsing. Unknown binding | |
| 51 | 58 | // keys are preserved here (in BindingEntries, in file order) so the usecase | |
| 52 | 59 | // layer can decide which actions it understands. | |
| @@ -56,6 +63,7 @@ type Config struct { | |||
| 56 | 63 | BindingEntries []ConfigEntry | |
| 57 | 64 | Codes map[string]string | |
| 58 | 65 | Snapshot SnapshotConfig | |
| 66 | + | Floating FloatingConfig | |
| 59 | 67 | } | |
| 60 | 68 | ||
| 61 | 69 | // Warning describes a non-fatal config problem. Parsers and reloaders should | |
| @@ -79,5 +87,9 @@ func Defaults() Config { | |||
| 79 | 87 | Snapshot: SnapshotConfig{ | |
| 80 | 88 | RestoreProcesses: DefaultSnapshotRestoreProcesses(), | |
| 81 | 89 | }, | |
| 90 | + | Floating: FloatingConfig{ | |
| 91 | + | Width: 80, | |
| 92 | + | Height: 80, | |
| 93 | + | }, | |
| 82 | 94 | } | |
| 83 | 95 | } | |
| @@ -47,6 +47,13 @@ type BarConfig struct { | |||||
| 47 | Interval time.Duration | 47 | Interval time.Duration | ||
| 48 | } | 48 | } | ||
| 49 | 49 | ||||
| 50 | + | // FloatingConfig contains launch and sizing settings for per-tab floating panes. | |||
| 51 | + | type FloatingConfig struct { | |||
| 52 | + | Command string | |||
| 53 | + | Width int | |||
| 54 | + | Height int | |||
| 55 | + | } | |||
| 56 | + | ||||
| 50 | // Config is the user-editable vev configuration after parsing. Unknown binding | 57 | // Config is the user-editable vev configuration after parsing. Unknown binding | ||
| 51 | // keys are preserved here (in BindingEntries, in file order) so the usecase | 58 | // keys are preserved here (in BindingEntries, in file order) so the usecase | ||
| 52 | // layer can decide which actions it understands. | 59 | // layer can decide which actions it understands. | ||
| @@ -56,6 +63,7 @@ type Config struct { | |||||
| 56 | BindingEntries []ConfigEntry | 63 | BindingEntries []ConfigEntry | ||
| 57 | Codes map[string]string | 64 | Codes map[string]string | ||
| 58 | Snapshot SnapshotConfig | 65 | Snapshot SnapshotConfig | ||
| 66 | + | Floating FloatingConfig | |||
| 59 | } | 67 | } | ||
| 60 | 68 | ||||
| 61 | // Warning describes a non-fatal config problem. Parsers and reloaders should | 69 | // Warning describes a non-fatal config problem. Parsers and reloaders should | ||
| @@ -79,5 +87,9 @@ func Defaults() Config { | |||||
| 79 | Snapshot: SnapshotConfig{ | 87 | Snapshot: SnapshotConfig{ | ||
| 80 | RestoreProcesses: DefaultSnapshotRestoreProcesses(), | 88 | RestoreProcesses: DefaultSnapshotRestoreProcesses(), | ||
| 81 | }, | 89 | }, | ||
| 90 | + | Floating: FloatingConfig{ | |||
| 91 | + | Width: 80, | |||
| 92 | + | Height: 80, | |||
| 93 | + | }, | |||
| 82 | } | 94 | } | ||
| 83 | } | 95 | } | ||