feat/spike 551649b

config.go RawHistoryFolder

1// Package config loads the bar's TOML configuration.
2package config
3
4import (
5	"fmt"
6	"os"
7	"path/filepath"
8
9	"github.com/BurntSushi/toml"
10)
11
12// Bar holds the global bar settings.
13type Bar struct {
14	Font string  `toml:"font"`
15	Size float64 `toml:"size"` // font size in logical pixels
16	// Scale multiplies the monitor scale (1.0 follows the monitor).
17	Scale float64 `toml:"scale"`
18	// Output is the wl_output name; empty lets the compositor choose.
19	Output string `toml:"output"`
20	// Theme is "auto" (the colors of the terminal in use) or the path of a
21	// terminal theme or config file: kitty, foot, ghostty or alacritty.
22	Theme string `toml:"theme"`
23	// Accent is the number (0-15) of the theme's ANSI color the bundled
24	// scripts use for highlights. Scripts read it as $NEFERBAR_ACCENT.
25	Accent int `toml:"accent"`
26	// Background and Foreground, as #rrggbb, override the theme's.
27	Background string `toml:"background"`
28	Foreground string `toml:"foreground"`
29}
30
31// Module is one script.
32type Module struct {
33	Name string `toml:"name"`
34	Zone string `toml:"zone"` // left | center | right
35	Exec string `toml:"exec"`
36}
37
38// Config is the whole file.
39type Config struct {
40	Bar    Bar      `toml:"bar"`
41	Module []Module `toml:"module"`
42}
43
44// Default returns the built-in settings.
45func Default() Config {
46	return Config{Bar: Bar{
47		Font: "JetBrainsMono Nerd Font Mono", Size: 14, Scale: 1,
48		Theme: "auto", Accent: 4,
49	}}
50}
51
52// DefaultPath is $XDG_CONFIG_HOME/neferbar/config.toml.
53func DefaultPath() string {
54	dir := os.Getenv("XDG_CONFIG_HOME")
55	if dir == "" {
56		home, _ := os.UserHomeDir()
57		dir = filepath.Join(home, ".config")
58	}
59	return filepath.Join(dir, "neferbar", "config.toml")
60}
61
62// Load reads path over the defaults. A missing file at the default path is
63// not an error.
64func Load(path string, explicit bool) (Config, error) {
65	c := Default()
66	md, err := toml.DecodeFile(path, &c)
67	if err != nil {
68		if !explicit && os.IsNotExist(err) {
69			return c, nil
70		}
71		return c, fmt.Errorf("config: %w", err)
72	}
73	if u := md.Undecoded(); len(u) > 0 {
74		return c, fmt.Errorf("config: unknown key %s", u[0])
75	}
76	return c, c.Validate()
77}
78
79// Validate checks every field.
80func (c *Config) Validate() error {
81	b := &c.Bar
82	if b.Font == "" {
83		return fmt.Errorf("config: bar.font is empty")
84	}
85	if b.Size < 6 || b.Size > 128 {
86		return fmt.Errorf("config: bar.size %v out of range 6..128", b.Size)
87	}
88	if b.Scale < 0.5 || b.Scale > 8 {
89		return fmt.Errorf("config: bar.scale %v out of range 0.5..8", b.Scale)
90	}
91	for _, col := range []string{b.Background, b.Foreground} {
92		if col == "" {
93			continue // taken from the theme
94		}
95		if _, err := ParseColor(col); err != nil {
96			return err
97		}
98	}
99	if b.Accent < 0 || b.Accent > 15 {
100		return fmt.Errorf("config: bar.accent %d must be a color number from 0 to 15", b.Accent)
101	}
102	if b.Theme == "" {
103		return fmt.Errorf("config: bar.theme is empty; use \"auto\" or the path of a theme file")
104	}
105	seen := map[string]bool{}
106	for i := range c.Module {
107		m := &c.Module[i]
108		if m.Exec == "" {
109			return fmt.Errorf("config: module %d has no exec", i+1)
110		}
111		if m.Name == "" {
112			m.Name = fmt.Sprintf("module%d", i+1)
113		}
114		if seen[m.Name] {
115			return fmt.Errorf("config: two modules are named %q", m.Name)
116		}
117		seen[m.Name] = true
118		switch m.Zone {
119		case "left", "center", "right":
120		default:
121			return fmt.Errorf("config: module %q: zone must be left, center or right", m.Name)
122		}
123	}
124	return nil
125}
126
127// ParseColor parses #rrggbb.
128func ParseColor(s string) ([3]uint8, error) {
129	var r, g, b uint8
130	if len(s) != 7 || s[0] != '#' {
131		return [3]uint8{}, fmt.Errorf("config: color %q must be #rrggbb", s)
132	}
133	if _, err := fmt.Sscanf(s[1:], "%02x%02x%02x", &r, &g, &b); err != nil {
134		return [3]uint8{}, fmt.Errorf("config: color %q must be #rrggbb", s)
135	}
136	return [3]uint8{r, g, b}, nil
137}

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 5

Tags 4

↑↓ moveenter openesc close