zettcode-tui 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. zettcode/tui/__init__.py +422 -0
  2. zettcode/tui/_compat.py +32 -0
  3. zettcode/tui/capabilities.py +107 -0
  4. zettcode/tui/clipboard.py +91 -0
  5. zettcode/tui/console.py +229 -0
  6. zettcode/tui/core/__init__.py +63 -0
  7. zettcode/tui/core/app.py +658 -0
  8. zettcode/tui/core/events.py +134 -0
  9. zettcode/tui/core/focus.py +85 -0
  10. zettcode/tui/core/geometry.py +212 -0
  11. zettcode/tui/core/host.py +66 -0
  12. zettcode/tui/core/keymap.py +218 -0
  13. zettcode/tui/core/scheduler.py +105 -0
  14. zettcode/tui/core/screen.py +80 -0
  15. zettcode/tui/core/theme.py +248 -0
  16. zettcode/tui/core/widget.py +204 -0
  17. zettcode/tui/gallery.py +588 -0
  18. zettcode/tui/glyphs.py +73 -0
  19. zettcode/tui/input/__init__.py +18 -0
  20. zettcode/tui/input/bridge.py +46 -0
  21. zettcode/tui/input/decoder.py +234 -0
  22. zettcode/tui/input/events.py +64 -0
  23. zettcode/tui/input/keys.py +289 -0
  24. zettcode/tui/input/reader.py +147 -0
  25. zettcode/tui/layout/__init__.py +24 -0
  26. zettcode/tui/layout/box.py +153 -0
  27. zettcode/tui/layout/overlay.py +164 -0
  28. zettcode/tui/layout/scroll.py +477 -0
  29. zettcode/tui/layout/solver.py +95 -0
  30. zettcode/tui/layout/spacing.py +137 -0
  31. zettcode/tui/py.typed +0 -0
  32. zettcode/tui/render/__init__.py +71 -0
  33. zettcode/tui/render/ansi.py +371 -0
  34. zettcode/tui/render/canvas.py +153 -0
  35. zettcode/tui/render/code.py +456 -0
  36. zettcode/tui/render/color.py +177 -0
  37. zettcode/tui/render/renderer.py +151 -0
  38. zettcode/tui/render/rich_text.py +80 -0
  39. zettcode/tui/render/style.py +121 -0
  40. zettcode/tui/render/sweep.py +87 -0
  41. zettcode/tui/render/text.py +319 -0
  42. zettcode/tui/runner.py +316 -0
  43. zettcode/tui/terminal.py +222 -0
  44. zettcode/tui/testing/__init__.py +6 -0
  45. zettcode/tui/testing/harness.py +220 -0
  46. zettcode/tui/testing/snapshot.py +64 -0
  47. zettcode/tui/theme_file.py +300 -0
  48. zettcode/tui/themes/ayu-mirage.toml +44 -0
  49. zettcode/tui/themes/catppuccin-latte.toml +44 -0
  50. zettcode/tui/themes/catppuccin-mocha.toml +44 -0
  51. zettcode/tui/themes/dracula.toml +44 -0
  52. zettcode/tui/themes/github-dimmed.toml +44 -0
  53. zettcode/tui/themes/github-light.toml +44 -0
  54. zettcode/tui/themes/gruvbox-dark.toml +44 -0
  55. zettcode/tui/themes/gruvbox-light.toml +44 -0
  56. zettcode/tui/themes/kanagawa.toml +44 -0
  57. zettcode/tui/themes/nord.toml +44 -0
  58. zettcode/tui/themes/one-dark.toml +44 -0
  59. zettcode/tui/themes/rose-pine-dawn.toml +44 -0
  60. zettcode/tui/themes/rose-pine.toml +44 -0
  61. zettcode/tui/themes/solarized-dark.toml +44 -0
  62. zettcode/tui/themes/solarized-light.toml +44 -0
  63. zettcode/tui/themes/tokyo-night.toml +44 -0
  64. zettcode/tui/widgets/__init__.py +121 -0
  65. zettcode/tui/widgets/collapsible.py +116 -0
  66. zettcode/tui/widgets/completion.py +211 -0
  67. zettcode/tui/widgets/console.py +442 -0
  68. zettcode/tui/widgets/dialog.py +165 -0
  69. zettcode/tui/widgets/diff.py +236 -0
  70. zettcode/tui/widgets/list.py +293 -0
  71. zettcode/tui/widgets/list_page.py +121 -0
  72. zettcode/tui/widgets/markdown.py +657 -0
  73. zettcode/tui/widgets/progress.py +166 -0
  74. zettcode/tui/widgets/rich_text.py +84 -0
  75. zettcode/tui/widgets/status.py +106 -0
  76. zettcode/tui/widgets/table.py +101 -0
  77. zettcode/tui/widgets/tasks.py +130 -0
  78. zettcode/tui/widgets/text.py +111 -0
  79. zettcode/tui/widgets/textarea.py +671 -0
  80. zettcode/tui/widgets/toast.py +137 -0
  81. zettcode_tui-0.4.0.dist-info/METADATA +20 -0
  82. zettcode_tui-0.4.0.dist-info/RECORD +83 -0
  83. zettcode_tui-0.4.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,300 @@
1
+ """Load a theme palette from TOML so colours do not have to be edited in code.
2
+
3
+ A palette is a file, and a palette's name is the file's name. The ones that ship with the program are TOML files in
4
+ ``themes/`` beside this module, and the reader's own are ``<name>.toml`` files in a directory of theirs
5
+ (``~/.zettcode/themes``); both are read by the same code, so adding a palette is writing one file and nothing else.
6
+ Only the *names* are listed up front (a directory listing); a palette's file is read when it is asked for, so a
7
+ start pays for no palette it does not use.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import re
13
+ from collections.abc import Callable, Mapping, Sequence
14
+ from dataclasses import dataclass, fields, replace
15
+ from functools import partial
16
+ from importlib import resources
17
+ from pathlib import Path
18
+
19
+ from ._compat import tomllib
20
+ from .core.theme import DARK, LIGHT, Theme, ToolTheme, theme_named
21
+ from .render.code import CodeTheme
22
+ from .render.color import parse_hex
23
+
24
+ #: The palettes that ship as files, in the order a menu lists them: the dark ones, then the light ones.
25
+ BUNDLED = (
26
+ "catppuccin-mocha",
27
+ "tokyo-night",
28
+ "nord",
29
+ "gruvbox-dark",
30
+ "rose-pine",
31
+ "kanagawa",
32
+ "dracula",
33
+ "one-dark",
34
+ "solarized-dark",
35
+ "github-dimmed",
36
+ "ayu-mirage",
37
+ "catppuccin-latte",
38
+ "rose-pine-dawn",
39
+ "gruvbox-light",
40
+ "solarized-light",
41
+ "github-light",
42
+ )
43
+
44
+ #: What a palette's name may be. It is typed after ``/theme`` as one word, so it holds no space.
45
+ THEME_NAME = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]*")
46
+
47
+ #: Names the program reserves: they are what the terminal's own light or dark is mapped to, and a file cannot
48
+ #: take them over.
49
+ RESERVED_THEMES = (DARK.name, LIGHT.name)
50
+
51
+ #: The page the two built-in palettes paint when they are chosen by name.
52
+ PAGES = {DARK.name: "#232a2e", LIGHT.name: "#f7f8f5"}
53
+
54
+ EXAMPLE = """\
55
+ base = "dark" # dark | light
56
+ description = "my palette" # shown beside its name in the /theme menu
57
+
58
+ [ui]
59
+ accent = "#a7c080"
60
+ error = "#e67e80"
61
+ # diff_added = "#a7c080" # default: the accent
62
+ # diff_removed = "#e67e80" # default: the error colour
63
+ surface_side = "#393a44"
64
+ surface_peer = "#313244"
65
+
66
+ [tools]
67
+ read = "#89b4fa"
68
+ shell = "#fab387"
69
+
70
+ [code]
71
+ keyword = "#e58fa8"
72
+ string = "#9bddad"
73
+ comment = "#6d7a70"
74
+ number = "#d8b46a"
75
+ function = "#7fb7d8"
76
+ builtin = "#b8a6e0"
77
+ inline = "#9bddad"
78
+ """
79
+
80
+
81
+ class ThemeFileError(ValueError):
82
+ """Raised when a theme file cannot be understood."""
83
+
84
+
85
+ def theme_from_toml(text: str, *, base: Theme | None = None) -> Theme:
86
+ """Build a theme from TOML text, starting from its base palette.
87
+
88
+ Args:
89
+ text: TOML source; ``base`` selects ``dark`` or ``light`` and the ``ui``,
90
+ ``tools``, and ``code`` tables override individual roles, so a file
91
+ only names the colours it wants to change.
92
+ base: Palette to override directly; when omitted the file's ``base`` key
93
+ (default ``dark``) is resolved through ``theme_named``.
94
+ """
95
+ try:
96
+ data = tomllib.loads(text)
97
+ except tomllib.TOMLDecodeError as error:
98
+ raise ThemeFileError(f"Invalid theme file: {error}") from error
99
+ if not isinstance(data, dict):
100
+ raise ThemeFileError("Theme file must be a table")
101
+
102
+ palette = base
103
+ if palette is None:
104
+ requested = data.get("base", DARK.name)
105
+ try:
106
+ palette = theme_named(str(requested))
107
+ except ValueError as error:
108
+ raise ThemeFileError(str(error)) from error
109
+
110
+ ui = _select(data.get("ui", {}), {field.name for field in fields(Theme)} - {"code", "tools"})
111
+ tools = _select(
112
+ data.get("tools", {}),
113
+ {field.name for field in fields(ToolTheme)},
114
+ hyphenated={"web-search": "web_search", "ask-user": "ask_user"},
115
+ )
116
+ code = _select(data.get("code", {}), {field.name for field in fields(CodeTheme)})
117
+ return replace(palette, **ui, tools=replace(palette.tools, **tools), code=replace(palette.code, **code))
118
+
119
+
120
+ def load_theme(path: Path, *, base: Theme | None = None) -> Theme:
121
+ """Read a theme file from disk.
122
+
123
+ Args:
124
+ path: File to read; a missing or unreadable file raises
125
+ ``ThemeFileError`` rather than an ``OSError``.
126
+ base: Palette to override directly, bypassing the file's ``base`` key.
127
+ """
128
+ try:
129
+ text = path.read_text(encoding="utf-8")
130
+ except OSError as error:
131
+ raise ThemeFileError(f"Cannot read {path}: {error}") from error
132
+ return theme_from_toml(text, base=base)
133
+
134
+
135
+ @dataclass(frozen=True, slots=True)
136
+ class ThemeSource:
137
+ """A palette that something offers by name: the program's own files, or a plugin.
138
+
139
+ Attributes:
140
+ name: What ``/theme`` is typed with; one word.
141
+ load: Builds the palette. Called when it is asked for, never before, so a source costs nothing until picked.
142
+ description: The one-line note the menu shows beside the name, or a function that returns it (called only
143
+ when a menu is opened, for the same reason).
144
+ """
145
+
146
+ name: str
147
+ load: Callable[[], Theme]
148
+ description: str | Callable[[], str] = ""
149
+
150
+
151
+ def _bundled_text(name: str) -> str:
152
+ """Return the text of one palette file that ships with the program."""
153
+ return (resources.files(__package__) / "themes" / f"{name}.toml").read_text(encoding="utf-8")
154
+
155
+
156
+ def _load_bundled(name: str) -> Theme:
157
+ return theme_from_toml(_bundled_text(name))
158
+
159
+
160
+ def _bundled_note(name: str) -> str:
161
+ note = tomllib.loads(_bundled_text(name)).get("description")
162
+ return note if isinstance(note, str) else ""
163
+
164
+
165
+ def _file_note(path: Path) -> str:
166
+ note = tomllib.loads(path.read_text(encoding="utf-8")).get("description")
167
+ return note if isinstance(note, str) else ""
168
+
169
+
170
+ def file_theme(name: str, path: Path) -> ThemeSource:
171
+ """Return a source for a palette kept in a TOML file, read when it is asked for and not before."""
172
+ return ThemeSource(name, partial(load_theme, path), partial(_file_note, path))
173
+
174
+
175
+ def bundled_themes() -> tuple[ThemeSource, ...]:
176
+ """Return the palettes that ship as files, as sources: the built-in plugin registers these.
177
+
178
+ Nothing is read here; each file is opened when its palette is asked for, or its note when a menu is.
179
+ """
180
+ return tuple(ThemeSource(name, partial(_load_bundled, name), partial(_bundled_note, name)) for name in BUNDLED)
181
+
182
+
183
+ def _user_file(name: str, directory: Path | None) -> Path | None:
184
+ """Return the reader's file for a palette name, or ``None``. A name that is not one word is never a file."""
185
+ if directory is None or name in RESERVED_THEMES or not THEME_NAME.fullmatch(name):
186
+ return None
187
+ path = directory / f"{name}.toml"
188
+ return path if path.is_file() else None
189
+
190
+
191
+ def _user_names(directory: Path | None) -> tuple[str, ...]:
192
+ """Return the names of the reader's palettes: the ``.toml`` files in their directory, by name."""
193
+ if directory is None:
194
+ return ()
195
+ try:
196
+ stems = sorted(path.stem for path in directory.glob("*.toml") if path.is_file())
197
+ except OSError:
198
+ return ()
199
+ return tuple(stem for stem in stems if THEME_NAME.fullmatch(stem) and stem not in RESERVED_THEMES)
200
+
201
+
202
+ class ThemeCatalog:
203
+ """Every palette that can be asked for by name, and where each comes from.
204
+
205
+ There are three places, in this order: the two the terminal's own light or dark maps to (``dark`` and ``light``,
206
+ which nothing can replace), the *sources* (the built-in plugin's files, then any other plugin's, in the order
207
+ they loaded), and the reader's own ``<name>.toml`` files. A reader's file shadows a source of the same name,
208
+ which is how a palette that ships is adjusted: copy it, change what is wrong with it, keep the name. A source
209
+ whose name is taken is ignored, the first one winning.
210
+
211
+ Only names are listed up front. A palette is built when it is asked for, so a catalog of sixty costs a start
212
+ nothing.
213
+
214
+ Args:
215
+ sources: What plugins and the program offer.
216
+ directory: The reader's palette directory; one that is missing is the same as an empty one.
217
+ """
218
+
219
+ def __init__(self, sources: Sequence[ThemeSource] = (), directory: Path | None = None) -> None:
220
+ chosen: dict[str, ThemeSource] = {}
221
+ for source in sources:
222
+ if source.name not in RESERVED_THEMES and source.name not in chosen and THEME_NAME.fullmatch(source.name):
223
+ chosen[source.name] = source
224
+ self._sources = chosen
225
+ self.directory = directory
226
+
227
+ def names(self) -> tuple[str, ...]:
228
+ """Return the names in the order a menu lists them. Nothing is parsed."""
229
+ own = [name for name in _user_names(self.directory) if name not in self._sources]
230
+ return (*RESERVED_THEMES, *self._sources, *own)
231
+
232
+ def load(self, name: str) -> Theme:
233
+ """Return the palette called ``name``.
234
+
235
+ Raises:
236
+ ThemeFileError: There is no such palette, or it cannot be built or understood. The message names what is
237
+ available, so a typo is answered by the list.
238
+ """
239
+ if name in RESERVED_THEMES:
240
+ # Chosen by name, they paint their own page like every other palette: a light one over a dark terminal is
241
+ # light, not dark text on the terminal's black. Only the *automatic* pick leaves the page alone.
242
+ return replace(theme_named(name), background=PAGES[name])
243
+ path = _user_file(name, self.directory)
244
+ if path is not None:
245
+ loaded = load_theme(path)
246
+ elif name in self._sources:
247
+ try:
248
+ loaded = self._sources[name].load()
249
+ except ThemeFileError:
250
+ raise
251
+ except Exception as error: # noqa: BLE001 - a plugin's palette is its author's code
252
+ raise ThemeFileError(f"Theme {name!r} could not be built: {error}") from error
253
+ else:
254
+ raise ThemeFileError(f"Unknown theme: {name!r}. Available: {', '.join(self.names())}")
255
+ return replace(loaded, name=name, code=replace(loaded.code, name=name))
256
+
257
+ def notes(self) -> dict[str, str]:
258
+ """Return the one-line note each palette carries, by name; a palette with none is left out.
259
+
260
+ Read from the files and the plugins, so this is for a menu that is being opened and not for the start.
261
+ Whatever cannot be read has no note, and is not an error here: it is the error of the person who picks it.
262
+ """
263
+ notes: dict[str, str] = {}
264
+ for name in self.names():
265
+ if name in RESERVED_THEMES:
266
+ continue
267
+ path = _user_file(name, self.directory)
268
+ try:
269
+ if path is not None:
270
+ data = tomllib.loads(path.read_text(encoding="utf-8")).get("description")
271
+ else:
272
+ described = self._sources[name].description
273
+ data = described() if callable(described) else described
274
+ except Exception: # noqa: BLE001 - see above
275
+ continue
276
+ if isinstance(data, str) and data.strip():
277
+ notes[name] = data.strip()
278
+ return notes
279
+
280
+
281
+ def _select(values: object, allowed: set[str], *, hyphenated: Mapping[str, str] | None = None) -> dict[str, str]:
282
+ """Validate one palette table against the keys the theme knows.
283
+
284
+ A role whose name is a word rather than an identifier is written with a hyphen in the file (``web-search``)
285
+ and kept as the field it names (``web_search``); ``hyphenated`` is that spelling, when a table has one.
286
+ """
287
+ if not isinstance(values, dict):
288
+ raise ThemeFileError("Palette sections must be tables")
289
+ spelled = hyphenated or {}
290
+ chosen: dict[str, str] = {}
291
+ for key, value in values.items():
292
+ name = spelled.get(key, key)
293
+ if name not in allowed:
294
+ raise ThemeFileError(f"Unknown theme key: {key!r}")
295
+ if not isinstance(value, str):
296
+ raise ThemeFileError(f"{key} must be a string colour")
297
+ if name != "name" and parse_hex(value) is None:
298
+ raise ThemeFileError(f"{key} must be a #rrggbb colour, got {value!r}")
299
+ chosen[name] = value
300
+ return chosen
@@ -0,0 +1,44 @@
1
+ description = "blue-grey with warm gold"
2
+ base = "dark"
3
+
4
+ [ui]
5
+ background = "#1f2430"
6
+ surface = "#242936"
7
+ surface_alt = "#2d3446"
8
+ surface_side = "#344458"
9
+ surface_peer = "#44465e"
10
+ text = "#cccac2"
11
+ muted = "#8d95a3"
12
+ subtle = "#a7aebc"
13
+ accent = "#ffcc66"
14
+ accent_bright = "#ffd173"
15
+ warning = "#ffcc66"
16
+ error = "#f28779"
17
+ border = "#33415e"
18
+ focus = "#ffd173"
19
+ selection = "#34455a"
20
+ diff_added = "#87d96c"
21
+ diff_removed = "#f28779"
22
+
23
+ [tools]
24
+ read = "#ffcc66"
25
+ search = "#73d0ff"
26
+ image = "#73d0ff"
27
+ write = "#ffad66"
28
+ delete = "#f28779"
29
+ shell = "#73d0ff"
30
+ plan = "#73d0ff"
31
+ subagent = "#5ccfe6"
32
+ message = "#f29e74"
33
+
34
+ [code]
35
+ inline = "#87d96c"
36
+ text = "#cccac2"
37
+ keyword = "#ffad66"
38
+ string = "#87d96c"
39
+ comment = "#8d95a3"
40
+ number = "#ffad66"
41
+ function = "#73d0ff"
42
+ builtin = "#dfbfff"
43
+ operator = "#a7aebc"
44
+ punctuator = "#a7aebc"
@@ -0,0 +1,44 @@
1
+ description = "creamy lavender-grey, saturated accents"
2
+ base = "light"
3
+
4
+ [ui]
5
+ background = "#eff1f5"
6
+ surface = "#f7f8fb"
7
+ surface_alt = "#e6e9ef"
8
+ surface_side = "#d2dcf0"
9
+ surface_peer = "#dad2ef"
10
+ text = "#484b64"
11
+ muted = "#6c6f81"
12
+ subtle = "#575a71"
13
+ accent = "#8839ef"
14
+ accent_bright = "#5b6cca"
15
+ warning = "#a26715"
16
+ error = "#d20f39"
17
+ border = "#ccd0da"
18
+ focus = "#5b6cca"
19
+ selection = "#ccd0da"
20
+ diff_added = "#358423"
21
+ diff_removed = "#d20f39"
22
+
23
+ [tools]
24
+ read = "#a26715"
25
+ search = "#5b6cca"
26
+ image = "#5b6cca"
27
+ write = "#c54e09"
28
+ delete = "#d20f39"
29
+ shell = "#5b6cca"
30
+ plan = "#5b6cca"
31
+ subagent = "#148086"
32
+ message = "#aa5693"
33
+
34
+ [code]
35
+ inline = "#358423"
36
+ text = "#484b64"
37
+ keyword = "#8839ef"
38
+ string = "#358423"
39
+ comment = "#6c6f81"
40
+ number = "#c54e09"
41
+ function = "#1e66f5"
42
+ builtin = "#8839ef"
43
+ operator = "#575a71"
44
+ punctuator = "#575a71"
@@ -0,0 +1,44 @@
1
+ description = "soft purple-grey, pastel accents"
2
+ base = "dark"
3
+
4
+ [ui]
5
+ background = "#1e1e2e"
6
+ surface = "#262637"
7
+ surface_alt = "#313244"
8
+ surface_side = "#3a3f56"
9
+ surface_peer = "#45415b"
10
+ text = "#cdd6f4"
11
+ muted = "#8f93a8"
12
+ subtle = "#a6adc8"
13
+ accent = "#cba6f7"
14
+ accent_bright = "#b4befe"
15
+ warning = "#f9e2af"
16
+ error = "#f38ba8"
17
+ border = "#45475a"
18
+ focus = "#b4befe"
19
+ selection = "#45475a"
20
+ diff_added = "#a6e3a1"
21
+ diff_removed = "#f38ba8"
22
+
23
+ [tools]
24
+ read = "#f9e2af"
25
+ search = "#b4befe"
26
+ image = "#b4befe"
27
+ write = "#fab387"
28
+ delete = "#f38ba8"
29
+ shell = "#b4befe"
30
+ plan = "#b4befe"
31
+ subagent = "#94e2d5"
32
+ message = "#f5c2e7"
33
+
34
+ [code]
35
+ inline = "#a6e3a1"
36
+ text = "#cdd6f4"
37
+ keyword = "#cba6f7"
38
+ string = "#a6e3a1"
39
+ comment = "#8f93a8"
40
+ number = "#fab387"
41
+ function = "#89b4fa"
42
+ builtin = "#cba6f7"
43
+ operator = "#a6adc8"
44
+ punctuator = "#a6adc8"
@@ -0,0 +1,44 @@
1
+ description = "vivid purple, pink and green"
2
+ base = "dark"
3
+
4
+ [ui]
5
+ background = "#282a36"
6
+ surface = "#2f3140"
7
+ surface_alt = "#343746"
8
+ surface_side = "#3d4958"
9
+ surface_peer = "#46435d"
10
+ text = "#f8f8f2"
11
+ muted = "#8d99bd"
12
+ subtle = "#bdc0d6"
13
+ accent = "#bd93f9"
14
+ accent_bright = "#ff79c6"
15
+ warning = "#f1fa8c"
16
+ error = "#ff5555"
17
+ border = "#44475a"
18
+ focus = "#ff79c6"
19
+ selection = "#44475a"
20
+ diff_added = "#50fa7b"
21
+ diff_removed = "#ff5555"
22
+
23
+ [tools]
24
+ read = "#f1fa8c"
25
+ search = "#8be9fd"
26
+ image = "#8be9fd"
27
+ write = "#ffb86c"
28
+ delete = "#ff5555"
29
+ shell = "#8be9fd"
30
+ plan = "#8be9fd"
31
+ subagent = "#8be9fd"
32
+ message = "#ff79c6"
33
+
34
+ [code]
35
+ inline = "#50fa7b"
36
+ text = "#f8f8f2"
37
+ keyword = "#ff79c6"
38
+ string = "#50fa7b"
39
+ comment = "#8d99bd"
40
+ number = "#ffb86c"
41
+ function = "#8be9fd"
42
+ builtin = "#bd93f9"
43
+ operator = "#bdc0d6"
44
+ punctuator = "#bdc0d6"
@@ -0,0 +1,44 @@
1
+ description = "GitHub's dimmed dark, restrained"
2
+ base = "dark"
3
+
4
+ [ui]
5
+ background = "#22272e"
6
+ surface = "#2d333b"
7
+ surface_alt = "#373e47"
8
+ surface_side = "#3a4758"
9
+ surface_peer = "#44445b"
10
+ text = "#cad2db"
11
+ muted = "#959fa9"
12
+ subtle = "#b1bac4"
13
+ accent = "#539bf5"
14
+ accent_bright = "#6cb6ff"
15
+ warning = "#c69026"
16
+ error = "#e5534b"
17
+ border = "#444c56"
18
+ focus = "#6cb6ff"
19
+ selection = "#2f4766"
20
+ diff_added = "#57ab5a"
21
+ diff_removed = "#e5534b"
22
+
23
+ [tools]
24
+ read = "#c69026"
25
+ search = "#539bf5"
26
+ image = "#539bf5"
27
+ write = "#cc6b2c"
28
+ delete = "#e5534b"
29
+ shell = "#539bf5"
30
+ plan = "#539bf5"
31
+ subagent = "#39c5cf"
32
+ message = "#e275ad"
33
+
34
+ [code]
35
+ inline = "#57ab5a"
36
+ text = "#cad2db"
37
+ keyword = "#f47067"
38
+ string = "#57ab5a"
39
+ comment = "#959fa9"
40
+ number = "#cc6b2c"
41
+ function = "#539bf5"
42
+ builtin = "#986ee2"
43
+ operator = "#b1bac4"
44
+ punctuator = "#b1bac4"
@@ -0,0 +1,44 @@
1
+ description = "plain white, standard blue/green/red"
2
+ base = "light"
3
+
4
+ [ui]
5
+ background = "#ffffff"
6
+ surface = "#f6f8fa"
7
+ surface_alt = "#eaeef2"
8
+ surface_side = "#d4e1f0"
9
+ surface_peer = "#dcd9f0"
10
+ text = "#1f2328"
11
+ muted = "#656d76"
12
+ subtle = "#555e67"
13
+ accent = "#0969da"
14
+ accent_bright = "#0550ae"
15
+ warning = "#9a6700"
16
+ error = "#cf222e"
17
+ border = "#d0d7de"
18
+ focus = "#0550ae"
19
+ selection = "#ddf4ff"
20
+ diff_added = "#1a7f37"
21
+ diff_removed = "#cf222e"
22
+
23
+ [tools]
24
+ read = "#9a6700"
25
+ search = "#0969da"
26
+ image = "#0969da"
27
+ write = "#bc4c00"
28
+ delete = "#cf222e"
29
+ shell = "#0969da"
30
+ plan = "#0969da"
31
+ subagent = "#1b7c83"
32
+ message = "#bf3989"
33
+
34
+ [code]
35
+ inline = "#1a7f37"
36
+ text = "#1f2328"
37
+ keyword = "#cf222e"
38
+ string = "#1a7f37"
39
+ comment = "#656d76"
40
+ number = "#bc4c00"
41
+ function = "#0969da"
42
+ builtin = "#8250df"
43
+ operator = "#555e67"
44
+ punctuator = "#555e67"
@@ -0,0 +1,44 @@
1
+ description = "warm brown-yellow, retro paper"
2
+ base = "dark"
3
+
4
+ [ui]
5
+ background = "#282828"
6
+ surface = "#32302f"
7
+ surface_alt = "#3c3836"
8
+ surface_side = "#434340"
9
+ surface_peer = "#504243"
10
+ text = "#ebdbb2"
11
+ muted = "#a29689"
12
+ subtle = "#c0b298"
13
+ accent = "#fe8019"
14
+ accent_bright = "#fabd2f"
15
+ warning = "#d79921"
16
+ error = "#fb4934"
17
+ border = "#504945"
18
+ focus = "#fabd2f"
19
+ selection = "#504945"
20
+ diff_added = "#b8bb26"
21
+ diff_removed = "#fb4934"
22
+
23
+ [tools]
24
+ read = "#d79921"
25
+ search = "#83a598"
26
+ image = "#83a598"
27
+ write = "#fe8019"
28
+ delete = "#fb4934"
29
+ shell = "#83a598"
30
+ plan = "#83a598"
31
+ subagent = "#8ec07c"
32
+ message = "#d3869b"
33
+
34
+ [code]
35
+ inline = "#b8bb26"
36
+ text = "#ebdbb2"
37
+ keyword = "#fb4934"
38
+ string = "#b8bb26"
39
+ comment = "#a29689"
40
+ number = "#fe8019"
41
+ function = "#83a598"
42
+ builtin = "#d3869b"
43
+ operator = "#c0b298"
44
+ punctuator = "#c0b298"
@@ -0,0 +1,44 @@
1
+ description = "cream paper, brown text"
2
+ base = "light"
3
+
4
+ [ui]
5
+ background = "#fbf1c7"
6
+ surface = "#f9f5d7"
7
+ surface_alt = "#ebdbb2"
8
+ surface_side = "#d4cfac"
9
+ surface_peer = "#dfc7aa"
10
+ text = "#3c3836"
11
+ muted = "#73675c"
12
+ subtle = "#504945"
13
+ accent = "#af3a03"
14
+ accent_bright = "#9e6712"
15
+ warning = "#9e6712"
16
+ error = "#9d0006"
17
+ border = "#d5c4a1"
18
+ focus = "#9e6712"
19
+ selection = "#d5c4a1"
20
+ diff_added = "#79740e"
21
+ diff_removed = "#9d0006"
22
+
23
+ [tools]
24
+ read = "#9e6712"
25
+ search = "#076678"
26
+ image = "#076678"
27
+ write = "#af3a03"
28
+ delete = "#9d0006"
29
+ shell = "#076678"
30
+ plan = "#076678"
31
+ subagent = "#427b58"
32
+ message = "#8f3f71"
33
+
34
+ [code]
35
+ inline = "#79740e"
36
+ text = "#3c3836"
37
+ keyword = "#9d0006"
38
+ string = "#79740e"
39
+ comment = "#73675c"
40
+ number = "#af3a03"
41
+ function = "#076678"
42
+ builtin = "#8f3f71"
43
+ operator = "#504945"
44
+ punctuator = "#504945"