v0.2.0 c616855

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

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