Make every xterm resource a config value with today's value as default; tools read the rendered resources (issue 1656)
mesh/delivery-group group fix/1656-xterm-config rejected: a member's own check failed
mesh/merge-gate waits for its group's composed check
mesh/repo-check head f178aab641dc merged into trunk 3259765d46bc beside mesh-controller#420@69a67876 — fail: its merge-check.sh failed: files asking for a …
mesh/delivery superseded: a newer head of the same pull request

This commit is contained in:
2026-10-21 12:51:40 +02:00
parent b5dbfbb784
commit f178aab641
7 changed files with 571 additions and 90 deletions
+12 -5
View File
@@ -21,17 +21,24 @@ They are those both workstations ran, with three changes:
- **scrollback:** `saveLines` 10000 instead of 2000.
The palette (background, foreground and the sixteen colours) is today's, unchanged. So are the
clipboard keys: Ctrl+Shift+C copies to the clipboard and Ctrl+Shift+V pastes from it. The bundle's
`Resources()` is the one source of these values. The manifest test holds the manifest to it, and
`xterm_colours` compares against it.
clipboard keys: Ctrl+Shift+C copies to the clipboard and Ctrl+Shift+V pastes from it.
Every one of these values is a **config value** (ADR 0348; issue 1656) with today's value as its default:
`face`, `size` (the predefined `font-size`, 6 to 72), `save-lines`, `term-name`, `render-font`, `bold-mode`,
`always-bold-mode`, `allow-bold-fonts`, `background`, `foreground`, `color0` to `color15` (each `#rrggbb`),
and the two clipboard bindings `copy-binding` and `paste-binding`. `files/xresources-normal` holds only
`${config:<key>}` placeholders, so a machine changes its font size through mesh-config. The manifest test holds
the file to the keys, and the file filled with the defaults to the text every machine had before.
The tools keep no copy of the values: `xterm_font` and `xterm_colours` read the `XTerm*` lines of the rendered
resources file `xorg` writes (`~/.config/xorg/xresources`) as the module's value, and say so when it is not there.
## Tools
| tool | | what |
|---|---|---|
| `node-terminal-emulator.open` | d | a terminal in the session: the login shell, or a command run through it (`$SHELL -lc`), in a directory (the home by default), with an optional title, and `hold` to keep it open. It starts as a transient unit of the account's service manager (`systemd-run --user`), so it outlives the call and every runtime restart |
| `xterm_font` | r/a | the face and size in force and the installed font they resolve to (`fc-match`); set face or size for terminals opened from now on. Only installed faces are accepted |
| `xterm_colours` | r | background, foreground, cursor and the sixteen colours as `#rrggbb`, each beside the module's default |
| `xterm_font` | r/a | the face and size in force beside the module's value (from the rendered resources), and the installed font they resolve to (`fc-match`); set face or size for terminals opened from now on. Only installed faces are accepted |
| `xterm_colours` | r | background, foreground, cursor and the sixteen colours as `#rrggbb`, each beside the module's value from the rendered resources |
## What it leaves found
@@ -0,0 +1,274 @@
package main
import (
"context"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"testing"
"xterm/internal/desktop"
)
// todaysResources is the file every machine had before the module took config values (novox/hq issue 1656).
var todaysResources = strings.Join([]string{
"! xterm: the face, the palette and the clipboard keys. Scoped to the XTerm class, so they",
"! outrank any `*name` resource of yours and touch no other X program.",
"XTerm*termName: xterm-256color",
"XTerm*saveLines: 10000",
"XTerm*renderFont: true",
"XTerm*faceName: JetBrainsMono Nerd Font",
"XTerm*faceSize: 12",
"XTerm*boldMode: false",
"XTerm*alwaysBoldMode: false",
"XTerm*allowBoldFonts: true",
"XTerm*background: #000000",
"XTerm*foreground: #a8a8a8",
"XTerm*color0: #000000",
"XTerm*color8: #666666",
"XTerm*color1: #9e1828",
"XTerm*color9: #bc5766",
"XTerm*color2: #008800",
"XTerm*color10: #61a171",
"XTerm*color3: #d2bb4b",
"XTerm*color11: #e7db52",
"XTerm*color4: #414171",
"XTerm*color12: #5085af",
"XTerm*color5: #963c59",
"XTerm*color13: #a97a99",
"XTerm*color6: #418179",
"XTerm*color14: #6ba4a4",
"XTerm*color7: #bebebe",
"XTerm*color15: #ffffff",
"XTerm*VT100.Translations: #override \\n\\",
" Ctrl Shift ~Alt <Key> C: copy-selection(CLIPBOARD) \\n\\",
" Ctrl Shift ~Alt <Key> V: insert-selection(CLIPBOARD)",
"",
}, "\n")
var placeholder = regexp.MustCompile(`\$\{config:([a-z0-9][a-z0-9_.-]*)\}`)
// asText is a default as the controller writes a value into a file: a string as it is, a number without a
// needless fraction, a boolean as true or false.
func asText(t *testing.T, v any) string {
t.Helper()
switch v := v.(type) {
case string:
return v
case float64:
return strconv.FormatFloat(v, 'f', -1, 64)
case bool:
return strconv.FormatBool(v)
}
t.Fatalf("a default of no type: %v", v)
return ""
}
func template(t *testing.T) string {
t.Helper()
m := readManifest(t)
if len(m.Shell) != 1 {
t.Fatalf("%+v", m.Shell)
}
return m.Shell[0].Code
}
// 1656/AC1: every value the resources set is a config value with today's value as its default.
func TestEveryResourceValueIsAConfigValueWithTodaysDefault(t *testing.T) {
m := readManifest(t)
want := map[string]any{
"face": "JetBrainsMono Nerd Font", "size": 12.0, "save-lines": 10000.0, "term-name": "xterm-256color",
"render-font": true, "bold-mode": false, "always-bold-mode": false, "allow-bold-fonts": true,
"background": "#000000", "foreground": "#a8a8a8",
"color0": "#000000", "color1": "#9e1828", "color2": "#008800", "color3": "#d2bb4b",
"color4": "#414171", "color5": "#963c59", "color6": "#418179", "color7": "#bebebe",
"color8": "#666666", "color9": "#bc5766", "color10": "#61a171", "color11": "#e7db52",
"color12": "#5085af", "color13": "#a97a99", "color14": "#6ba4a4", "color15": "#ffffff",
"copy-binding": "Ctrl Shift ~Alt <Key> C", "paste-binding": "Ctrl Shift ~Alt <Key> V",
}
if len(m.Config) != len(want) {
t.Errorf("declares %d config values, want %d", len(m.Config), len(want))
}
for key, def := range want {
c, ok := m.Config[key]
if !ok {
t.Errorf("%s: not declared", key)
continue
}
if c.Default != def || c.Required || strings.TrimSpace(c.Description) == "" {
t.Errorf("%s: %+v, want default %v and a description", key, c, def)
}
wantType := map[bool]string{true: "string"}[true]
switch def.(type) {
case float64:
wantType = "number"
case bool:
wantType = "boolean"
}
if c.Type != wantType {
t.Errorf("%s: type %q, want %q", key, c.Type, wantType)
}
}
size := m.Config["size"]
if size.Predefined != "font-size" || size.Minimum == nil || *size.Minimum != 6 || size.Maximum == nil || *size.Maximum != 72 {
t.Errorf("size: %+v, want the predefined font-size from 6 to 72", size)
}
names := []string{"background", "foreground"}
for i := 0; i < 16; i++ {
names = append(names, "color"+strconv.Itoa(i))
}
for _, key := range names {
c := m.Config[key]
re, err := regexp.Compile("^(?:" + c.Pattern + ")$")
if err != nil || c.Pattern == "" {
t.Errorf("%s: pattern %q", key, c.Pattern)
continue
}
if !re.MatchString("#0a1B2c") || re.MatchString("red") || re.MatchString("#12345") || re.MatchString("#1234567") {
t.Errorf("%s: pattern %q is not #rrggbb", key, c.Pattern)
}
}
for _, key := range []string{"copy-binding", "paste-binding"} {
re := regexp.MustCompile("^(?:" + m.Config[key].Pattern + ")$")
if !re.MatchString("Ctrl Shift ~Alt <Key> C") || re.MatchString("a: b") || re.MatchString("a\nb") || re.MatchString(`a\`) {
t.Errorf("%s: pattern %q lets a value end the resource", key, m.Config[key].Pattern)
}
}
}
// 1656/AC2: no literal value is left in the resources, and every declared key is used.
func TestEveryResourceValueIsAPlaceholderOfADeclaredKeyAndEveryKeyIsUsed(t *testing.T) {
m := readManifest(t)
text := template(t)
used := map[string]bool{}
for _, l := range strings.Split(text, "\n") {
switch {
case l == "", strings.HasPrefix(l, "!"):
continue
case strings.HasPrefix(l, "XTerm*VT100.Translations: #override"):
continue
}
_, value, ok := strings.Cut(l, ": ")
if strings.HasPrefix(l, " ") {
// a translation line: `<binding>: <action> \n\`
value, _, _ = strings.Cut(strings.TrimSpace(l), ": ")
ok = true
}
if !ok {
t.Errorf("not a resource: %q", l)
continue
}
f := placeholder.FindStringSubmatch(value)
if f == nil || f[0] != value {
t.Errorf("a literal value, not a ${config:<key>}: %q", l)
continue
}
if _, declared := m.Config[f[1]]; !declared {
t.Errorf("%q names a key the module does not declare", l)
}
used[f[1]] = true
}
for key := range m.Config {
if !used[key] {
t.Errorf("%s is declared and no resource uses it", key)
}
}
}
// 1656/AC3: filled with the defaults, the resources are the file every machine had.
func TestFilledWithTheDefaultsTheResourcesAreTodaysFileByteForByte(t *testing.T) {
m := readManifest(t)
got := placeholder.ReplaceAllStringFunc(template(t), func(ref string) string {
c, ok := m.Config[placeholder.FindStringSubmatch(ref)[1]]
if !ok {
t.Fatalf("%s: no such key", ref)
}
return asText(t, c.Default)
})
if got != todaysResources {
t.Fatalf("filled with the defaults:\n%s\nwant:\n%s", got, todaysResources)
}
}
// renderedWith is the resources file xorg writes with this module's piece filled from the defaults, and
// the given values changed.
func renderedWith(t *testing.T, changed map[string]string) string {
t.Helper()
m := readManifest(t)
text := placeholder.ReplaceAllStringFunc(template(t), func(ref string) string {
key := placeholder.FindStringSubmatch(ref)[1]
if v, ok := changed[key]; ok {
return v
}
return asText(t, m.Config[key].Default)
})
return "! The mesh's X resources (module xorg)\nXft.dpi: 96\n# xterm\n" + text + "Xft.rgba: rgb\n"
}
func deskWithResources(t *testing.T, rendered string, f *fake) xterm {
t.Helper()
path := filepath.Join(t.TempDir(), "xresources")
if rendered != "" {
if err := os.WriteFile(path, []byte(rendered), 0o644); err != nil {
t.Fatal(err)
}
}
return xterm{d: f.desk(true), resources: path}
}
// 1656/AC4: the tools' defaults are what the module's resources were rendered with, not constants.
func TestFontReportsTheDefaultTheModuleRenderedNotAConstant(t *testing.T) {
f := &fake{query: "XTerm*faceName:\tInter\nXTerm*faceSize:\t13\n"}
x := deskWithResources(t, renderedWith(t, map[string]string{"size": "10", "face": "Fira Code"}), f)
got, err := x.font(context.Background(), desktop.Args{})
if err != nil {
t.Fatal(err)
}
if j := asJSON(got); !strings.Contains(j, `"default":{"face":"Fira Code","size":10}`) {
t.Fatalf("%s", j)
}
}
// 1656/AC4
func TestColoursReportTheDefaultTheModuleRenderedNotAConstant(t *testing.T) {
f := &fake{query: "XTerm*color1:\t#aa0000\nXTerm*foreground:\t#112233\nXTerm*color2:\trgb:00/88/01\n"}
x := deskWithResources(t, renderedWith(t, map[string]string{"color1": "#aa0000", "foreground": "#112233"}), f)
got, err := x.colours(context.Background(), desktop.Args{})
if err != nil {
t.Fatal(err)
}
j := asJSON(got)
for _, want := range []string{`{"name":"color1","value":"#aa0000","default":"#aa0000"}`,
`{"name":"foreground","value":"#112233","default":"#112233"}`,
`{"name":"color2","value":"#008801","default":"#008800","differs":true}`} {
if !strings.Contains(j, want) {
t.Errorf("lacks %s in %s", want, j)
}
}
}
// 1656/AC4: without the rendered file there is no default to report, said loudly.
func TestWithoutTheRenderedResourcesTheToolsSayWhy(t *testing.T) {
x := deskWithResources(t, "", &fake{})
if _, err := x.font(context.Background(), desktop.Args{}); err == nil || !strings.Contains(err.Error(), "xresources") {
t.Fatalf("font: %v", err)
}
if _, err := x.colours(context.Background(), desktop.Args{}); err == nil || !strings.Contains(err.Error(), "xresources") {
t.Fatalf("colours: %v", err)
}
}
// 1656/AC4: tools.go holds no copy of the defaults.
func TestTheToolsHoldNoCopyOfTheDefaults(t *testing.T) {
src, err := os.ReadFile("tools.go")
if err != nil {
t.Fatal(err)
}
for _, lit := range []string{"JetBrainsMono", "#a8a8a8", "#9e1828", "xterm-256color", "Face =", "Size =", "func Resources"} {
if strings.Contains(string(src), lit) {
t.Errorf("tools.go holds %q", lit)
}
}
}
+3 -2
View File
@@ -9,6 +9,7 @@ import (
"context"
"fmt"
"os"
"path/filepath"
"time"
stdio "git.novox.be/novox/mesh-sdk/go"
@@ -17,7 +18,7 @@ import (
)
func main() {
if err := stdio.Serve("", tools(xterm{d: desktop.Machine("i3", "sway")})); err != nil {
if err := stdio.Serve("", tools(xterm{d: desktop.Machine("i3", "sway"), resources: filepath.Join(desktop.Home(), ResourcesFile)})); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
@@ -53,7 +54,7 @@ func tools(x xterm) []stdio.Tool {
"resources set the default again at the next login.",
Input: desktop.Schema(map[string]any{
"face": desktop.Str("a font family, as fc-list names it"),
"size": desktop.Num("points, 6 to 48"),
"size": desktop.Num("points, 6 to 72"),
}),
Run: call(x.font),
},
+59 -44
View File
@@ -11,47 +11,50 @@ import (
"xterm/internal/desktop"
)
type xterm struct{ d desktop.Desk }
// The module's defaults: the face research 026/04 chose and both workstations' palette.
const (
Face = "JetBrainsMono Nerd Font"
Size = 12
)
// Palette is the colours both workstations ran, by resource name, in the order they are written.
var Palette = [][2]string{
{"background", "#000000"}, {"foreground", "#a8a8a8"},
{"color0", "#000000"}, {"color8", "#666666"},
{"color1", "#9e1828"}, {"color9", "#bc5766"},
{"color2", "#008800"}, {"color10", "#61a171"},
{"color3", "#d2bb4b"}, {"color11", "#e7db52"},
{"color4", "#414171"}, {"color12", "#5085af"},
{"color5", "#963c59"}, {"color13", "#a97a99"},
{"color6", "#418179"}, {"color14", "#6ba4a4"},
{"color7", "#bebebe"}, {"color15", "#ffffff"},
type xterm struct {
d desktop.Desk
resources string
}
// Resources is the module's contribution to the X resources (the xresources slot `normal`), as the
// manifest carries it; the manifest test holds the two to one text.
func Resources() string {
var b strings.Builder
b.WriteString("! xterm: the face, the palette and the clipboard keys. Scoped to the XTerm class, so they\n")
b.WriteString("! outrank any `*name` resource of yours and touch no other X program.\n")
for _, kv := range [][2]string{
{"termName", "xterm-256color"}, {"saveLines", "10000"}, {"renderFont", "true"},
{"faceName", Face}, {"faceSize", strconv.Itoa(Size)},
{"boldMode", "false"}, {"alwaysBoldMode", "false"}, {"allowBoldFonts", "true"},
} {
fmt.Fprintf(&b, "XTerm*%s: %s\n", kv[0], kv[1])
// PaletteNames are the colour resources xterm_colours reports, in the order the resources write them.
// Their values are the module's config values, read from the rendered resources (Rendered), never held here.
var PaletteNames = []string{
"background", "foreground",
"color0", "color8", "color1", "color9", "color2", "color10", "color3", "color11",
"color4", "color12", "color5", "color13", "color6", "color14", "color7", "color15",
}
// ResourcesFile is where xorg writes the X resources, under the account's home: this module's resources are
// its contribution to the `normal` slot, filled with the module's config values.
const ResourcesFile = ".config/xorg/xresources"
// Rendered is the XTerm* resources of a rendered resources file, by name: `XTerm*faceSize: 12` is faceSize 12.
// The translations, which span lines, and every resource of another program are left out.
func Rendered(text string) map[string]string {
out := map[string]string{}
for _, l := range strings.Split(text, "\n") {
k, v, ok := strings.Cut(l, ":")
name, scoped := strings.CutPrefix(strings.TrimSpace(k), "XTerm*")
if ok && scoped && !strings.Contains(name, ".") {
out[name] = strings.TrimSpace(v)
}
}
for _, kv := range Palette {
fmt.Fprintf(&b, "XTerm*%s: %s\n", kv[0], kv[1])
return out
}
// defaults are the resources the module's config values rendered, as xorg placed them: what the module sets, so
// what a terminal has unless the session's own resources say otherwise.
func (x xterm) defaults() (map[string]string, error) {
text, err := os.ReadFile(x.resources)
if err != nil {
return nil, fmt.Errorf("the module's resources are not where xorg places them (%s): is xorg assigned to this machine, "+
"and has its xresources file been written? %w", x.resources, err)
}
b.WriteString("XTerm*VT100.Translations: #override \\n\\\n")
b.WriteString(" Ctrl Shift ~Alt <Key> C: copy-selection(CLIPBOARD) \\n\\\n")
b.WriteString(" Ctrl Shift ~Alt <Key> V: insert-selection(CLIPBOARD)\n")
return b.String()
got := Rendered(string(text))
if len(got) == 0 {
return nil, fmt.Errorf("%s holds no XTerm* resource: the xterm module's resources are not in it", x.resources)
}
return got, nil
}
// openScript runs in the new terminal: into the directory, then the login shell, or a command
@@ -124,6 +127,10 @@ func (x xterm) font(ctx context.Context, a desktop.Args) (any, error) {
if err != nil {
return nil, err
}
def, err := x.defaults()
if err != nil {
return nil, err
}
env := s.Env(x.d.Base)
face := a.Opt("face", "")
size, sized, err := a.Number("size")
@@ -142,8 +149,8 @@ func (x xterm) font(ctx context.Context, a desktop.Args) (any, error) {
res = append(res, "XTerm*faceName: "+face)
}
if sized {
if size < 6 || size > 48 {
return nil, fmt.Errorf("size is from 6 to 48 points")
if size < 6 || size > 72 {
return nil, fmt.Errorf("size is from 6 to 72 points")
}
res = append(res, "XTerm*faceSize: "+strconv.FormatFloat(size, 'f', -1, 64))
}
@@ -156,9 +163,13 @@ func (x xterm) font(ctx context.Context, a desktop.Args) (any, error) {
return nil, q.Err()
}
inForce := Effective(q.Stdout, "faceName")
var points any = def["faceSize"]
if n, err := strconv.ParseFloat(def["faceSize"], 64); err == nil {
points = n
}
answer := map[string]any{
"session": s.Display, "face": inForce, "size": Effective(q.Stdout, "faceSize"),
"default": map[string]any{"face": Face, "size": Size},
"default": map[string]any{"face": def["faceName"], "size": points},
}
if inForce != "" {
m := x.d.Run(ctx, env, nil, "fc-match", "-f", "%{family[0]}|%{style[0]}|%{file}", inForce)
@@ -175,7 +186,7 @@ func (x xterm) font(ctx context.Context, a desktop.Args) (any, error) {
var rgbSpec = regexp.MustCompile(`^rgb:([0-9a-fA-F]{1,4})/([0-9a-fA-F]{1,4})/([0-9a-fA-F]{1,4})$`)
// Hex writes an X colour (`rgb:9e/18/28`, `#9e1828`) as #rrggbb; anything else (a colour name) as it is.
// Hex writes an X colour (`rgb:9e/18/28`, `#rrggbb`) as #rrggbb; anything else (a colour name) as it is.
func Hex(v string) string {
v = strings.TrimSpace(v)
if m := rgbSpec.FindStringSubmatch(v); m != nil {
@@ -191,6 +202,10 @@ func Hex(v string) string {
}
func (x xterm) colours(ctx context.Context, a desktop.Args) (any, error) {
def, err := x.defaults()
if err != nil {
return nil, err
}
res, s, err := x.d.InSession(ctx, "xrdb", "-query")
if err != nil {
return nil, err
@@ -205,9 +220,9 @@ func (x xterm) colours(ctx context.Context, a desktop.Args) (any, error) {
Differs bool `json:"differs,omitempty"`
}
var out []colour
for _, kv := range Palette {
v := Hex(Effective(res.Stdout, kv[0]))
out = append(out, colour{kv[0], v, kv[1], v != kv[1]})
for _, name := range PaletteNames {
v, d := Hex(Effective(res.Stdout, name)), Hex(def[name])
out = append(out, colour{name, v, d, v != d})
}
cursor := Hex(Effective(res.Stdout, "cursorColor"))
return map[string]any{"session": s.Display, "colours": out, "cursor": cursor}, nil
+26 -11
View File
@@ -19,7 +19,8 @@ type manifest struct {
Name string `json:"name"`
Serves []string `json:"serves"`
} `json:"claims"`
Tools []string `json:"tools"`
Tools []string `json:"tools"`
Config map[string]configKey `json:"config"`
Environment struct {
Variables map[string]string `json:"variables"`
} `json:"environment"`
@@ -37,6 +38,18 @@ type manifest struct {
} `json:"build"`
}
type configKey struct {
Type string `json:"type"`
Description string `json:"description"`
Pattern string `json:"pattern"`
Minimum *float64 `json:"minimum"`
Maximum *float64 `json:"maximum"`
Predefined string `json:"predefined"`
Default any `json:"default"`
Required bool `json:"required"`
Trusted bool `json:"trusted"`
}
func readManifest(t *testing.T) manifest {
raw, err := os.ReadFile(filepath.Join("..", "..", "module.json"))
if err != nil {
@@ -99,10 +112,8 @@ func TestItsResourcesAreOneContributionInTheNormalSlotScopedToXTerm(t *testing.T
if len(m.Shell) != 1 || m.Shell[0].For != "xresources" || m.Shell[0].Slot != "normal" {
t.Fatalf("%+v", m.Shell)
}
if m.Shell[0].Code != Resources() {
t.Fatal("the manifest's resources are not the ones the bundle describes: regenerate the manifest from Resources()")
}
for _, l := range strings.Split(strings.TrimSpace(Resources()), "\n") {
code := m.Shell[0].Code
for _, l := range strings.Split(strings.TrimSpace(code), "\n") {
switch {
case strings.HasPrefix(l, "!"), strings.HasPrefix(l, " "):
case strings.HasPrefix(l, "#"):
@@ -111,8 +122,8 @@ func TestItsResourcesAreOneContributionInTheNormalSlotScopedToXTerm(t *testing.T
t.Errorf("a resource of another program, or of every one: %q", l)
}
}
if !strings.Contains(Resources(), "XTerm*faceName: JetBrainsMono Nerd Font") || !strings.Contains(Resources(), "copy-selection(CLIPBOARD)") {
t.Fatal("the chosen face and the clipboard keys")
if !strings.Contains(code, "XTerm*faceName: ${config:face}") || !strings.Contains(code, "copy-selection(CLIPBOARD)") {
t.Fatal("the face and the clipboard keys")
}
}
@@ -202,7 +213,11 @@ func TestFontAndColoursAreReadAsAnXTermSeesThem(t *testing.T) {
}
}
f := &fake{query: q}
got, err := xterm{d: f.desk(true)}.colours(context.Background(), desktop.Args{})
rendered := filepath.Join(t.TempDir(), "xresources")
if err := os.WriteFile(rendered, []byte(renderedWith(t, nil)), 0o644); err != nil {
t.Fatal(err)
}
got, err := xterm{d: f.desk(true), resources: rendered}.colours(context.Background(), desktop.Args{})
if err != nil {
t.Fatal(err)
}
@@ -210,14 +225,14 @@ func TestFontAndColoursAreReadAsAnXTermSeesThem(t *testing.T) {
if !strings.Contains(j, `{"name":"color1","value":"#ff0000","default":"#9e1828","differs":true}`) || !strings.Contains(j, `{"name":"foreground","value":"#a8a8a8","default":"#a8a8a8"}`) {
t.Fatalf("%s", j)
}
if _, err := (xterm{d: f.desk(true)}).font(context.Background(), desktop.Args{"face": "Missing Font"}); err == nil {
if _, err := (xterm{d: f.desk(true), resources: rendered}).font(context.Background(), desktop.Args{"face": "Missing Font"}); err == nil {
t.Fatal("a face that is not installed")
}
if _, err := (xterm{d: f.desk(true)}).font(context.Background(), desktop.Args{"size": 99.0}); err == nil {
if _, err := (xterm{d: f.desk(true), resources: rendered}).font(context.Background(), desktop.Args{"size": 99.0}); err == nil {
t.Fatal("size out of range")
}
f.ran, f.stdin = nil, nil
got, err = xterm{d: f.desk(true)}.font(context.Background(), desktop.Args{"face": "Inter", "size": 13.0})
got, err = xterm{d: f.desk(true), resources: rendered}.font(context.Background(), desktop.Args{"face": "Inter", "size": 13.0})
if err != nil {
t.Fatal(err)
}
+28 -28
View File
@@ -1,31 +1,31 @@
! xterm: the face, the palette and the clipboard keys. Scoped to the XTerm class, so they
! outrank any `*name` resource of yours and touch no other X program.
XTerm*termName: xterm-256color
XTerm*saveLines: 10000
XTerm*renderFont: true
XTerm*faceName: JetBrainsMono Nerd Font
XTerm*faceSize: 12
XTerm*boldMode: false
XTerm*alwaysBoldMode: false
XTerm*allowBoldFonts: true
XTerm*background: #000000
XTerm*foreground: #a8a8a8
XTerm*color0: #000000
XTerm*color8: #666666
XTerm*color1: #9e1828
XTerm*color9: #bc5766
XTerm*color2: #008800
XTerm*color10: #61a171
XTerm*color3: #d2bb4b
XTerm*color11: #e7db52
XTerm*color4: #414171
XTerm*color12: #5085af
XTerm*color5: #963c59
XTerm*color13: #a97a99
XTerm*color6: #418179
XTerm*color14: #6ba4a4
XTerm*color7: #bebebe
XTerm*color15: #ffffff
XTerm*termName: ${config:term-name}
XTerm*saveLines: ${config:save-lines}
XTerm*renderFont: ${config:render-font}
XTerm*faceName: ${config:face}
XTerm*faceSize: ${config:size}
XTerm*boldMode: ${config:bold-mode}
XTerm*alwaysBoldMode: ${config:always-bold-mode}
XTerm*allowBoldFonts: ${config:allow-bold-fonts}
XTerm*background: ${config:background}
XTerm*foreground: ${config:foreground}
XTerm*color0: ${config:color0}
XTerm*color8: ${config:color8}
XTerm*color1: ${config:color1}
XTerm*color9: ${config:color9}
XTerm*color2: ${config:color2}
XTerm*color10: ${config:color10}
XTerm*color3: ${config:color3}
XTerm*color11: ${config:color11}
XTerm*color4: ${config:color4}
XTerm*color12: ${config:color12}
XTerm*color5: ${config:color5}
XTerm*color13: ${config:color13}
XTerm*color6: ${config:color6}
XTerm*color14: ${config:color14}
XTerm*color7: ${config:color7}
XTerm*color15: ${config:color15}
XTerm*VT100.Translations: #override \n\
Ctrl Shift ~Alt <Key> C: copy-selection(CLIPBOARD) \n\
Ctrl Shift ~Alt <Key> V: insert-selection(CLIPBOARD)
${config:copy-binding}: copy-selection(CLIPBOARD) \n\
${config:paste-binding}: insert-selection(CLIPBOARD)
+169
View File
@@ -21,6 +21,175 @@
"xterm_font",
"xterm_colours"
],
"config": {
"face": {
"type": "string",
"description": "The font family xterm draws with, as fc-list names it: JetBrainsMono Nerd Font, because research 026/04 chose it for its glyphs the prompt uses. A family the machine does not have falls back to the nearest installed face (xterm_font says which).",
"pattern": "[A-Za-z0-9 ._-]{1,64}",
"default": "JetBrainsMono Nerd Font"
},
"size": {
"type": "number",
"description": "The size of xterm's text, in points: 12 reads well on a screen of about 100 DPI, and a denser screen needs a larger size.",
"minimum": 6,
"maximum": 72,
"predefined": "font-size",
"default": 12
},
"save-lines": {
"type": "number",
"description": "How many lines of scrollback xterm keeps per terminal: 10000 holds a long build log, and each line costs memory in every open terminal.",
"minimum": 0,
"maximum": 1000000,
"default": 10000
},
"term-name": {
"type": "string",
"description": "The TERM value xterm sets for the programs it runs: xterm-256color, because the palette has 256 colours and every program knows the name.",
"pattern": "[A-Za-z0-9._-]{1,64}",
"default": "xterm-256color"
},
"render-font": {
"type": "boolean",
"description": "Whether xterm draws with fontconfig faces (true) or the old X core fonts (false): true, because the face is a fontconfig family and core fonts cannot show it.",
"default": true
},
"bold-mode": {
"type": "boolean",
"description": "Whether xterm draws bold text by overstriking the normal face (true): false, because the face has a real bold style and overstriking blurs it.",
"default": false
},
"always-bold-mode": {
"type": "boolean",
"description": "Whether xterm overstrikes bold even where the face has a bold style (true): false, for the same reason as bold-mode.",
"default": false
},
"allow-bold-fonts": {
"type": "boolean",
"description": "Whether xterm may use the face's bold style for bold text (true): true, so bold text is drawn with the real bold weight.",
"default": true
},
"background": {
"type": "string",
"description": "The terminal's background colour, as #rrggbb: black, as both workstations ran it.",
"pattern": "#[0-9a-fA-F]{6}",
"default": "#000000"
},
"foreground": {
"type": "string",
"description": "The terminal's text colour, as #rrggbb: a light grey, easier on the eyes than white on black.",
"pattern": "#[0-9a-fA-F]{6}",
"default": "#a8a8a8"
},
"color0": {
"type": "string",
"description": "Palette entry 0, the normal black, as #rrggbb: #000000, as both workstations ran it.",
"pattern": "#[0-9a-fA-F]{6}",
"default": "#000000"
},
"color1": {
"type": "string",
"description": "Palette entry 1, the normal red, as #rrggbb: #9e1828, as both workstations ran it.",
"pattern": "#[0-9a-fA-F]{6}",
"default": "#9e1828"
},
"color2": {
"type": "string",
"description": "Palette entry 2, the normal green, as #rrggbb: #008800, as both workstations ran it.",
"pattern": "#[0-9a-fA-F]{6}",
"default": "#008800"
},
"color3": {
"type": "string",
"description": "Palette entry 3, the normal yellow, as #rrggbb: #d2bb4b, as both workstations ran it.",
"pattern": "#[0-9a-fA-F]{6}",
"default": "#d2bb4b"
},
"color4": {
"type": "string",
"description": "Palette entry 4, the normal blue, as #rrggbb: #414171, as both workstations ran it.",
"pattern": "#[0-9a-fA-F]{6}",
"default": "#414171"
},
"color5": {
"type": "string",
"description": "Palette entry 5, the normal magenta, as #rrggbb: #963c59, as both workstations ran it.",
"pattern": "#[0-9a-fA-F]{6}",
"default": "#963c59"
},
"color6": {
"type": "string",
"description": "Palette entry 6, the normal cyan, as #rrggbb: #418179, as both workstations ran it.",
"pattern": "#[0-9a-fA-F]{6}",
"default": "#418179"
},
"color7": {
"type": "string",
"description": "Palette entry 7, the normal white, as #rrggbb: #bebebe, as both workstations ran it.",
"pattern": "#[0-9a-fA-F]{6}",
"default": "#bebebe"
},
"color8": {
"type": "string",
"description": "Palette entry 8, the bright black, as #rrggbb: #666666, as both workstations ran it.",
"pattern": "#[0-9a-fA-F]{6}",
"default": "#666666"
},
"color9": {
"type": "string",
"description": "Palette entry 9, the bright red, as #rrggbb: #bc5766, as both workstations ran it.",
"pattern": "#[0-9a-fA-F]{6}",
"default": "#bc5766"
},
"color10": {
"type": "string",
"description": "Palette entry 10, the bright green, as #rrggbb: #61a171, as both workstations ran it.",
"pattern": "#[0-9a-fA-F]{6}",
"default": "#61a171"
},
"color11": {
"type": "string",
"description": "Palette entry 11, the bright yellow, as #rrggbb: #e7db52, as both workstations ran it.",
"pattern": "#[0-9a-fA-F]{6}",
"default": "#e7db52"
},
"color12": {
"type": "string",
"description": "Palette entry 12, the bright blue, as #rrggbb: #5085af, as both workstations ran it.",
"pattern": "#[0-9a-fA-F]{6}",
"default": "#5085af"
},
"color13": {
"type": "string",
"description": "Palette entry 13, the bright magenta, as #rrggbb: #a97a99, as both workstations ran it.",
"pattern": "#[0-9a-fA-F]{6}",
"default": "#a97a99"
},
"color14": {
"type": "string",
"description": "Palette entry 14, the bright cyan, as #rrggbb: #6ba4a4, as both workstations ran it.",
"pattern": "#[0-9a-fA-F]{6}",
"default": "#6ba4a4"
},
"color15": {
"type": "string",
"description": "Palette entry 15, the bright white, as #rrggbb: #ffffff, as both workstations ran it.",
"pattern": "#[0-9a-fA-F]{6}",
"default": "#ffffff"
},
"copy-binding": {
"type": "string",
"description": "The key combination that copies the selection to the clipboard, in X translation syntax: Ctrl Shift ~Alt <Key> C, so a plain Ctrl+C still interrupts the program. It holds no colon, backslash or line break, which would end the resource.",
"pattern": "[^:\\\\\\n]+",
"default": "Ctrl Shift ~Alt <Key> C"
},
"paste-binding": {
"type": "string",
"description": "The key combination that pastes the clipboard, in X translation syntax: Ctrl Shift ~Alt <Key> V, the pair of copy-binding. It holds no colon, backslash or line break, which would end the resource.",
"pattern": "[^:\\\\\\n]+",
"default": "Ctrl Shift ~Alt <Key> V"
}
},
"items": [
{
"collection": "environment",