main 7a3b92c

config.go RawHistoryFolder

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

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