pi-zentui 0.1.9 → 0.1.11
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.
- package/README.md +112 -117
- package/extensions/zentui/config.ts +75 -26
- package/extensions/zentui/footer.ts +4 -2
- package/extensions/zentui/index.ts +4 -10
- package/extensions/zentui/settings-command.ts +3 -3
- package/extensions/zentui/style.ts +106 -6
- package/extensions/zentui/ui.ts +75 -13
- package/extensions/zentui/user-message.ts +36 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,9 +25,9 @@ Zentui brings two popular aesthetics to Pi:
|
|
|
25
25
|
|
|
26
26
|
### Editor (Opencode-inspired)
|
|
27
27
|
|
|
28
|
-
- Bordered input box with
|
|
28
|
+
- Bordered input box with configurable accent rail and border colors
|
|
29
29
|
- Model name and provider displayed inside the editor frame
|
|
30
|
-
-
|
|
30
|
+
- Configurable model, provider, and thinking-level indicator colors
|
|
31
31
|
- Prompt-box-style user messages matching the ZentUI input chrome
|
|
32
32
|
|
|
33
33
|
### Git Status Icons
|
|
@@ -49,65 +49,65 @@ Zentui brings two popular aesthetics to Pi:
|
|
|
49
49
|
|
|
50
50
|
Detects Starship Nerd Font runtime/language modules, uses the Starship Nerd Font symbols, and keeps Starship-style defaults such as `bold green` for Node.js. By default Zentui maps those styles through your active Pi theme; switch the Starship/footer color source to `terminal` in `/zentui` if you want your terminal colorscheme to supply the exact ANSI colors.
|
|
51
51
|
|
|
52
|
-
| Runtime/language | Detection examples
|
|
53
|
-
| ---------------- |
|
|
54
|
-
| Buf | `buf.yaml`, `buf.gen.yaml`, `buf.work.yaml`
|
|
55
|
-
| Bun | `bun.lock`, `bun.lockb`
|
|
56
|
-
| C | `.c`, `.h` files
|
|
57
|
-
| C++ | `.cpp`, `.cc`, `.cxx`, `.hpp` files
|
|
58
|
-
| CMake | `CMakeLists.txt`, `CMakeCache.txt`
|
|
59
|
-
| COBOL | `.cbl`, `.cob` files
|
|
60
|
-
| Conda | `CONDA_DEFAULT_ENV` environment
|
|
61
|
-
| Crystal | `.cr` files, `shard.yml`
|
|
62
|
-
| Dart | `.dart` files, `pubspec.yaml`, `.dart_tool/`
|
|
63
|
-
| Deno | `deno.json`, `deno.jsonc`, `deno.lock`
|
|
64
|
-
| .NET | `.csproj`, `.fsproj`, `global.json`, `Directory.Build.*`
|
|
65
|
-
| Elixir | `mix.exs`
|
|
66
|
-
| Elm | `.elm` files, `elm.json`, `elm-stuff/`
|
|
67
|
-
| Erlang | `rebar.config`, `erlang.mk`
|
|
68
|
-
| Fennel | `.fnl` files
|
|
69
|
-
| Fortran | `.f`, `.f90`, `.f95`, `.f03`, `.f08`, `.f18`, `fpm.toml`
|
|
70
|
-
| Gleam | `.gleam` files, `gleam.toml`
|
|
71
|
-
| Go | `go.mod`
|
|
72
|
-
| Gradle | `build.gradle`, `build.gradle.kts`, `gradle/`
|
|
73
|
-
| Guix shell | `GUIX_ENVIRONMENT` environment
|
|
74
|
-
| Haskell | `.hs`, `.cabal`, `stack.yaml`, `cabal.project`
|
|
75
|
-
| Haxe | `.hx`, `.hxml`, `haxelib.json`, `.haxerc`
|
|
76
|
-
| Helm | `helmfile.yaml`, `Chart.yaml`
|
|
77
|
-
| Java | `.java-version`
|
|
78
|
-
| Julia | `.jl` files, `Project.toml`, `Manifest.toml`
|
|
79
|
-
| Kotlin | `.kt`, `.kts` files
|
|
80
|
-
| Lua | `.lua` files, `stylua.toml`, `.luarc.json`, `lua/` dir
|
|
81
|
-
| Maven | `pom.xml`
|
|
82
|
-
| Meson | `MESON_DEVENV=1` and `MESON_PROJECT_NAME` environment
|
|
83
|
-
| Mojo | `.mojo` files
|
|
84
|
-
| Nim | `.nim`, `.nims`, `.nimble`, `nim.cfg`
|
|
85
|
-
| Nix shell | `IN_NIX_SHELL=pure` or `IN_NIX_SHELL=impure` environment
|
|
86
|
-
| Node.js | `package.json`, `.nvmrc`, `.node-version`
|
|
87
|
-
| OCaml | `.opam`, `.ml`, `.mli`, `dune`, `_opam/`, `esy.lock/`
|
|
88
|
-
| Odin | `.odin` files
|
|
89
|
-
| OPA/Rego | `.rego` files
|
|
90
|
-
| Perl | `.pl`, `.pm`, `Makefile.PL`, `cpanfile`, `META.*`
|
|
91
|
-
| PHP | `composer.json`
|
|
52
|
+
| Runtime/language | Detection examples |
|
|
53
|
+
| ---------------- | ------------------------------------------------------------- |
|
|
54
|
+
| Buf | `buf.yaml`, `buf.gen.yaml`, `buf.work.yaml` |
|
|
55
|
+
| Bun | `bun.lock`, `bun.lockb` |
|
|
56
|
+
| C | `.c`, `.h` files |
|
|
57
|
+
| C++ | `.cpp`, `.cc`, `.cxx`, `.hpp` files |
|
|
58
|
+
| CMake | `CMakeLists.txt`, `CMakeCache.txt` |
|
|
59
|
+
| COBOL | `.cbl`, `.cob` files |
|
|
60
|
+
| Conda | `CONDA_DEFAULT_ENV` environment |
|
|
61
|
+
| Crystal | `.cr` files, `shard.yml` |
|
|
62
|
+
| Dart | `.dart` files, `pubspec.yaml`, `.dart_tool/` |
|
|
63
|
+
| Deno | `deno.json`, `deno.jsonc`, `deno.lock` |
|
|
64
|
+
| .NET | `.csproj`, `.fsproj`, `global.json`, `Directory.Build.*` |
|
|
65
|
+
| Elixir | `mix.exs` |
|
|
66
|
+
| Elm | `.elm` files, `elm.json`, `elm-stuff/` |
|
|
67
|
+
| Erlang | `rebar.config`, `erlang.mk` |
|
|
68
|
+
| Fennel | `.fnl` files |
|
|
69
|
+
| Fortran | `.f`, `.f90`, `.f95`, `.f03`, `.f08`, `.f18`, `fpm.toml` |
|
|
70
|
+
| Gleam | `.gleam` files, `gleam.toml` |
|
|
71
|
+
| Go | `go.mod` |
|
|
72
|
+
| Gradle | `build.gradle`, `build.gradle.kts`, `gradle/` |
|
|
73
|
+
| Guix shell | `GUIX_ENVIRONMENT` environment |
|
|
74
|
+
| Haskell | `.hs`, `.cabal`, `stack.yaml`, `cabal.project` |
|
|
75
|
+
| Haxe | `.hx`, `.hxml`, `haxelib.json`, `.haxerc` |
|
|
76
|
+
| Helm | `helmfile.yaml`, `Chart.yaml` |
|
|
77
|
+
| Java | `.java-version` |
|
|
78
|
+
| Julia | `.jl` files, `Project.toml`, `Manifest.toml` |
|
|
79
|
+
| Kotlin | `.kt`, `.kts` files |
|
|
80
|
+
| Lua | `.lua` files, `stylua.toml`, `.luarc.json`, `lua/` dir |
|
|
81
|
+
| Maven | `pom.xml` |
|
|
82
|
+
| Meson | `MESON_DEVENV=1` and `MESON_PROJECT_NAME` environment |
|
|
83
|
+
| Mojo | `.mojo` files |
|
|
84
|
+
| Nim | `.nim`, `.nims`, `.nimble`, `nim.cfg` |
|
|
85
|
+
| Nix shell | `IN_NIX_SHELL=pure` or `IN_NIX_SHELL=impure` environment |
|
|
86
|
+
| Node.js | `package.json`, `.nvmrc`, `.node-version` |
|
|
87
|
+
| OCaml | `.opam`, `.ml`, `.mli`, `dune`, `_opam/`, `esy.lock/` |
|
|
88
|
+
| Odin | `.odin` files |
|
|
89
|
+
| OPA/Rego | `.rego` files |
|
|
90
|
+
| Perl | `.pl`, `.pm`, `Makefile.PL`, `cpanfile`, `META.*` |
|
|
91
|
+
| PHP | `composer.json` |
|
|
92
92
|
| Pixi | `pixi.toml`, `pixi.lock`, `PIXI_ENVIRONMENT_NAME` environment |
|
|
93
|
-
| Pulumi | `Pulumi.yaml`, `Pulumi.yml`
|
|
94
|
-
| PureScript | `.purs` files, `spago.dhall`, `spago.yaml`, `spago.lock`
|
|
95
|
-
| Python | `pyproject.toml`, `requirements.txt`, `setup.py`, `Pipfile`
|
|
96
|
-
| R | `.R`, `.Rmd`, `.Rproj`, `DESCRIPTION`, `.Rproj.user/`
|
|
97
|
-
| Raku | `.raku`, `.rakumod`, `.p6`, `.pm6`, `META6.json`
|
|
98
|
-
| Red | `.red`, `.reds` files
|
|
99
|
-
| Ruby | `Gemfile`, `.ruby-version`
|
|
93
|
+
| Pulumi | `Pulumi.yaml`, `Pulumi.yml` |
|
|
94
|
+
| PureScript | `.purs` files, `spago.dhall`, `spago.yaml`, `spago.lock` |
|
|
95
|
+
| Python | `pyproject.toml`, `requirements.txt`, `setup.py`, `Pipfile` |
|
|
96
|
+
| R | `.R`, `.Rmd`, `.Rproj`, `DESCRIPTION`, `.Rproj.user/` |
|
|
97
|
+
| Raku | `.raku`, `.rakumod`, `.p6`, `.pm6`, `META6.json` |
|
|
98
|
+
| Red | `.red`, `.reds` files |
|
|
99
|
+
| Ruby | `Gemfile`, `.ruby-version` |
|
|
100
100
|
| Rust | `Cargo.toml` |
|
|
101
|
-
| Scala | `.scala`, `.sbt`, `build.sbt`, `.metals/`
|
|
102
|
-
| Solidity | `.sol` files
|
|
103
|
-
| Spack | `SPACK_ENV` environment
|
|
104
|
-
| Swift | `.swift` files, `Package.swift`
|
|
105
|
-
| Terraform | `.tf`, `.tfplan`, `.tfstate`, `.terraform/`
|
|
106
|
-
| Typst | `.typ` files, `template.typ`
|
|
107
|
-
| Vagrant | `Vagrantfile`
|
|
108
|
-
| V | `.v` files, `v.mod`, `vpkg.json`
|
|
109
|
-
| Xmake | `xmake.lua`
|
|
110
|
-
| Zig | `.zig` files, `build.zig`
|
|
101
|
+
| Scala | `.scala`, `.sbt`, `build.sbt`, `.metals/` |
|
|
102
|
+
| Solidity | `.sol` files |
|
|
103
|
+
| Spack | `SPACK_ENV` environment |
|
|
104
|
+
| Swift | `.swift` files, `Package.swift` |
|
|
105
|
+
| Terraform | `.tf`, `.tfplan`, `.tfstate`, `.terraform/` |
|
|
106
|
+
| Typst | `.typ` files, `template.typ` |
|
|
107
|
+
| Vagrant | `Vagrantfile` |
|
|
108
|
+
| V | `.v` files, `v.mod`, `vpkg.json` |
|
|
109
|
+
| Xmake | `xmake.lua` |
|
|
110
|
+
| Zig | `.zig` files, `build.zig` |
|
|
111
111
|
|
|
112
112
|
## Install
|
|
113
113
|
|
|
@@ -121,71 +121,66 @@ pi install git:github.com/lmilojevicc/pi-zentui
|
|
|
121
121
|
|
|
122
122
|
## Config
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
User config lives at `~/.pi/agent/zentui.json`. The file is optional: missing or invalid known values fall back to Zentui defaults, unknown keys are ignored at runtime, and `/zentui` currently patches only color-source settings.
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
~/.pi/agent/zentui.json
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### Default config
|
|
126
|
+
Default config values — copy this and change any value you want:
|
|
131
127
|
|
|
132
128
|
```json
|
|
133
129
|
{
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
130
|
+
"projectRefreshIntervalMs": 30000,
|
|
131
|
+
"icons": {
|
|
132
|
+
"cwd": "",
|
|
133
|
+
"git": "",
|
|
134
|
+
"ahead": "↑",
|
|
135
|
+
"behind": "↓",
|
|
136
|
+
"diverged": "⇕",
|
|
137
|
+
"conflicted": "=",
|
|
138
|
+
"untracked": "?",
|
|
139
|
+
"stashed": "$",
|
|
140
|
+
"modified": "!",
|
|
141
|
+
"staged": "+",
|
|
142
|
+
"renamed": "»",
|
|
143
|
+
"deleted": "✘",
|
|
144
|
+
"typechanged": "T"
|
|
145
|
+
},
|
|
146
|
+
"colors": {
|
|
147
|
+
"cwd": "bold cyan",
|
|
148
|
+
"gitBranch": "bold purple",
|
|
149
|
+
"gitStatus": "bold red",
|
|
150
|
+
"contextNormal": "bright-black",
|
|
151
|
+
"contextWarning": "bold yellow",
|
|
152
|
+
"contextError": "bold red",
|
|
153
|
+
"tokens": "bright-black",
|
|
154
|
+
"cost": "bold green",
|
|
155
|
+
"separator": "bright-black",
|
|
156
|
+
"runtimePrefix": "",
|
|
157
|
+
"editorAccent": "accent",
|
|
158
|
+
"editorBorder": "borderMuted",
|
|
159
|
+
"editorModel": "accent",
|
|
160
|
+
"editorProvider": "text",
|
|
161
|
+
"editorThinking": "muted",
|
|
162
|
+
"editorThinkingMinimal": "thinkingMinimal",
|
|
163
|
+
"editorThinkingLow": "thinkingLow",
|
|
164
|
+
"editorThinkingMedium": "thinkingMedium",
|
|
165
|
+
"editorThinkingHigh": "thinkingHigh",
|
|
166
|
+
"editorThinkingXhigh": "thinkingXhigh"
|
|
167
|
+
},
|
|
168
|
+
"colorSources": {
|
|
169
|
+
"starship": "theme",
|
|
170
|
+
"editor": "theme",
|
|
171
|
+
"userMessages": "theme"
|
|
172
|
+
}
|
|
167
173
|
}
|
|
168
174
|
```
|
|
169
175
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
- `
|
|
177
|
-
- `
|
|
178
|
-
|
|
179
|
-
Both settings default to `theme`. The config still stores editor and previous user-message sources separately as `editor` and `userMessages`, but `/zentui` changes them together so the prompt chrome stays consistent.
|
|
180
|
-
|
|
181
|
-
Color values can use terminal-palette style strings, hex colors, or Pi theme color tokens:
|
|
182
|
-
|
|
183
|
-
- Named terminal colors: `bold purple`, `yellow`, `bright-cyan`
|
|
184
|
-
- 256-color / hex: `bold 149`, `fg:202`, `#89b4fa`
|
|
185
|
-
- Backgrounds and modifiers: `bg:blue fg:bright-green`, `underline bg:#bf5700`
|
|
186
|
-
- Pi theme tokens: `accent`, `borderMuted`, `syntaxKeyword`
|
|
187
|
-
|
|
188
|
-
Use the `colors` config key for these values.
|
|
176
|
+
- Style values can be Starship/terminal strings (`bold purple`, `fg:202`, `#89b4fa`, `bg:blue fg:bright-green`) or Pi theme tokens (`accent`, `borderMuted`, `thinkingHigh`).
|
|
177
|
+
- `projectRefreshIntervalMs`: project status polling interval; `0` disables polling.
|
|
178
|
+
- `icons`: every shown icon key is configurable; omit any key to use the Zentui default.
|
|
179
|
+
- `colorSources`: `theme` maps styles through Pi theme tokens; `terminal` emits terminal colors. `/zentui` switches these sources; manual JSON controls specific style values.
|
|
180
|
+
- The shown `editor*` values match the default `theme` source. Omit those keys to keep Zentui's source-aware defaults when switching between `theme` and `terminal`.
|
|
181
|
+
- `editorAccent` styles the active editor rail and previous user-message rail.
|
|
182
|
+
- `editorBorder` styles the active editor and previous user-message top/bottom border color only; the border glyph stays `─`.
|
|
183
|
+
- `editorModel`, `editorProvider`, and `editorThinking*` style the editor metadata. `editorThinking` applies to every non-`off` thinking level unless a level-specific key is set.
|
|
189
184
|
|
|
190
185
|
## Requirements
|
|
191
186
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { isSupportedColorSpec } from "./style";
|
|
4
5
|
|
|
5
6
|
export type ColorSpec = string;
|
|
6
7
|
export type ColorSource = "theme" | "terminal";
|
|
@@ -42,6 +43,16 @@ export type PolishedTuiConfig = {
|
|
|
42
43
|
cost: ColorSpec;
|
|
43
44
|
separator: ColorSpec;
|
|
44
45
|
runtimePrefix: ColorSpec;
|
|
46
|
+
editorAccent?: ColorSpec;
|
|
47
|
+
editorBorder?: ColorSpec;
|
|
48
|
+
editorModel?: ColorSpec;
|
|
49
|
+
editorProvider?: ColorSpec;
|
|
50
|
+
editorThinking?: ColorSpec;
|
|
51
|
+
editorThinkingMinimal?: ColorSpec;
|
|
52
|
+
editorThinkingLow?: ColorSpec;
|
|
53
|
+
editorThinkingMedium?: ColorSpec;
|
|
54
|
+
editorThinkingHigh?: ColorSpec;
|
|
55
|
+
editorThinkingXhigh?: ColorSpec;
|
|
45
56
|
};
|
|
46
57
|
colorSources: ColorSourcesConfig;
|
|
47
58
|
};
|
|
@@ -69,12 +80,12 @@ export const defaultConfig: PolishedTuiConfig = {
|
|
|
69
80
|
cwd: "bold cyan",
|
|
70
81
|
gitBranch: "bold purple",
|
|
71
82
|
gitStatus: "bold red",
|
|
72
|
-
contextNormal: "
|
|
83
|
+
contextNormal: "bright-black",
|
|
73
84
|
contextWarning: "bold yellow",
|
|
74
85
|
contextError: "bold red",
|
|
75
|
-
tokens: "
|
|
86
|
+
tokens: "bright-black",
|
|
76
87
|
cost: "bold green",
|
|
77
|
-
separator: "
|
|
88
|
+
separator: "bright-black",
|
|
78
89
|
runtimePrefix: "",
|
|
79
90
|
},
|
|
80
91
|
colorSources: {
|
|
@@ -84,6 +95,22 @@ export const defaultConfig: PolishedTuiConfig = {
|
|
|
84
95
|
},
|
|
85
96
|
};
|
|
86
97
|
|
|
98
|
+
const iconKeys = [
|
|
99
|
+
"cwd",
|
|
100
|
+
"git",
|
|
101
|
+
"ahead",
|
|
102
|
+
"behind",
|
|
103
|
+
"diverged",
|
|
104
|
+
"conflicted",
|
|
105
|
+
"untracked",
|
|
106
|
+
"stashed",
|
|
107
|
+
"modified",
|
|
108
|
+
"staged",
|
|
109
|
+
"renamed",
|
|
110
|
+
"deleted",
|
|
111
|
+
"typechanged",
|
|
112
|
+
] as const satisfies readonly (keyof PolishedTuiConfig["icons"])[];
|
|
113
|
+
|
|
87
114
|
type ConfigRecord = Record<string, unknown>;
|
|
88
115
|
|
|
89
116
|
function isRecord(value: unknown): value is ConfigRecord {
|
|
@@ -107,6 +134,11 @@ function stringValue(record: Record<string, unknown>, key: string): string | und
|
|
|
107
134
|
return typeof value === "string" ? value : undefined;
|
|
108
135
|
}
|
|
109
136
|
|
|
137
|
+
function colorValue(record: Record<string, unknown>, key: string): string | undefined {
|
|
138
|
+
const value = stringValue(record, key);
|
|
139
|
+
return value !== undefined && isSupportedColorSpec(value) ? value : undefined;
|
|
140
|
+
}
|
|
141
|
+
|
|
110
142
|
function colorSourceValue(
|
|
111
143
|
record: Record<string, unknown>,
|
|
112
144
|
key: keyof ColorSourcesConfig,
|
|
@@ -125,18 +157,37 @@ function definedColors(
|
|
|
125
157
|
) as Partial<PolishedTuiConfig["colors"]>;
|
|
126
158
|
}
|
|
127
159
|
|
|
160
|
+
function normalizeIcons(record: Record<string, unknown>): Partial<PolishedTuiConfig["icons"]> {
|
|
161
|
+
return Object.fromEntries(
|
|
162
|
+
iconKeys.flatMap((key) => {
|
|
163
|
+
const value = stringValue(record, key);
|
|
164
|
+
return value === undefined ? [] : [[key, value]];
|
|
165
|
+
}),
|
|
166
|
+
) as Partial<PolishedTuiConfig["icons"]>;
|
|
167
|
+
}
|
|
168
|
+
|
|
128
169
|
function normalizeColors(record: Record<string, unknown>): Partial<PolishedTuiConfig["colors"]> {
|
|
129
170
|
return definedColors({
|
|
130
|
-
cwd:
|
|
131
|
-
gitBranch:
|
|
132
|
-
gitStatus:
|
|
133
|
-
contextNormal:
|
|
134
|
-
contextWarning:
|
|
135
|
-
contextError:
|
|
136
|
-
tokens:
|
|
137
|
-
cost:
|
|
138
|
-
separator:
|
|
139
|
-
runtimePrefix:
|
|
171
|
+
cwd: colorValue(record, "cwd") ?? colorValue(record, "cwdText"),
|
|
172
|
+
gitBranch: colorValue(record, "gitBranch") ?? colorValue(record, "git"),
|
|
173
|
+
gitStatus: colorValue(record, "gitStatus"),
|
|
174
|
+
contextNormal: colorValue(record, "contextNormal"),
|
|
175
|
+
contextWarning: colorValue(record, "contextWarning"),
|
|
176
|
+
contextError: colorValue(record, "contextError"),
|
|
177
|
+
tokens: colorValue(record, "tokens"),
|
|
178
|
+
cost: colorValue(record, "cost"),
|
|
179
|
+
separator: colorValue(record, "separator"),
|
|
180
|
+
runtimePrefix: colorValue(record, "runtimePrefix"),
|
|
181
|
+
editorAccent: colorValue(record, "editorAccent"),
|
|
182
|
+
editorBorder: colorValue(record, "editorBorder"),
|
|
183
|
+
editorModel: colorValue(record, "editorModel"),
|
|
184
|
+
editorProvider: colorValue(record, "editorProvider"),
|
|
185
|
+
editorThinking: colorValue(record, "editorThinking"),
|
|
186
|
+
editorThinkingMinimal: colorValue(record, "editorThinkingMinimal"),
|
|
187
|
+
editorThinkingLow: colorValue(record, "editorThinkingLow"),
|
|
188
|
+
editorThinkingMedium: colorValue(record, "editorThinkingMedium"),
|
|
189
|
+
editorThinkingHigh: colorValue(record, "editorThinkingHigh"),
|
|
190
|
+
editorThinkingXhigh: colorValue(record, "editorThinkingXhigh"),
|
|
140
191
|
});
|
|
141
192
|
}
|
|
142
193
|
|
|
@@ -172,18 +223,17 @@ function readConfigRecord(path = configPath): ConfigRecord {
|
|
|
172
223
|
}
|
|
173
224
|
|
|
174
225
|
export function ensureConfigExists(): void {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
} catch {
|
|
180
|
-
// Ignore config bootstrap failures; extension will fall back to defaults.
|
|
181
|
-
}
|
|
226
|
+
// Intentionally left as a no-op. Zentui config is user-owned and
|
|
227
|
+
// compatibility-sensitive: runtime defaults come from `mergeConfig({})`, and
|
|
228
|
+
// the extension should not persist opinionated defaults unless the user
|
|
229
|
+
// explicitly changes a setting.
|
|
182
230
|
}
|
|
183
231
|
|
|
184
232
|
export function mergeConfig(parsed: unknown): PolishedTuiConfig {
|
|
185
233
|
const config = isRecord(parsed) ? parsed : {};
|
|
186
|
-
const icons = isRecord(config.icons)
|
|
234
|
+
const icons = isRecord(config.icons)
|
|
235
|
+
? normalizeIcons(config.icons as Record<string, unknown>)
|
|
236
|
+
: {};
|
|
187
237
|
const colors = isRecord(config.colors)
|
|
188
238
|
? normalizeColors(config.colors as Record<string, unknown>)
|
|
189
239
|
: {};
|
|
@@ -200,16 +250,16 @@ export function mergeConfig(parsed: unknown): PolishedTuiConfig {
|
|
|
200
250
|
...defaultConfig.colors,
|
|
201
251
|
...colors,
|
|
202
252
|
},
|
|
203
|
-
colorSources,
|
|
253
|
+
colorSources: { ...colorSources },
|
|
204
254
|
};
|
|
205
255
|
}
|
|
206
256
|
|
|
207
257
|
export function loadConfig(): PolishedTuiConfig {
|
|
208
258
|
try {
|
|
209
|
-
if (!existsSync(configPath)) return
|
|
259
|
+
if (!existsSync(configPath)) return mergeConfig({});
|
|
210
260
|
return mergeConfig(JSON.parse(readFileSync(configPath, "utf8")));
|
|
211
261
|
} catch {
|
|
212
|
-
return
|
|
262
|
+
return mergeConfig({});
|
|
213
263
|
}
|
|
214
264
|
}
|
|
215
265
|
|
|
@@ -219,10 +269,9 @@ export function saveColorSourcesPatch(
|
|
|
219
269
|
): PolishedTuiConfig {
|
|
220
270
|
const record = readConfigRecord(path);
|
|
221
271
|
const existing = isRecord(record.colorSources)
|
|
222
|
-
?
|
|
272
|
+
? { ...(record.colorSources as Record<string, unknown>) }
|
|
223
273
|
: {};
|
|
224
274
|
record.colorSources = {
|
|
225
|
-
...defaultConfig.colorSources,
|
|
226
275
|
...existing,
|
|
227
276
|
...validColorSourceEntries(patch),
|
|
228
277
|
};
|
|
@@ -53,7 +53,7 @@ export function installFooter(
|
|
|
53
53
|
renderStyleForSource(theme, colorSource, config.colors.gitBranch, text);
|
|
54
54
|
const gitStatusColor = (text: string) =>
|
|
55
55
|
renderStyleForSource(theme, colorSource, config.colors.gitStatus, text);
|
|
56
|
-
const gitIcon = gitColor(config.icons.git);
|
|
56
|
+
const gitIcon = config.icons.git ? gitColor(config.icons.git) : "";
|
|
57
57
|
const allStatus = [
|
|
58
58
|
state.conflicted > 0 ? config.icons.conflicted : "",
|
|
59
59
|
state.stashed ? config.icons.stashed : "",
|
|
@@ -75,7 +75,9 @@ export function installFooter(
|
|
|
75
75
|
const statusBlock =
|
|
76
76
|
allStatus || aheadBehind ? gitStatusColor(`[${allStatus}${aheadBehind}]`) : "";
|
|
77
77
|
const branchLabel = branch
|
|
78
|
-
?
|
|
78
|
+
? [...["on", gitIcon, gitColor(branch)].filter(Boolean), statusBlock]
|
|
79
|
+
.filter(Boolean)
|
|
80
|
+
.join(" ")
|
|
79
81
|
: "";
|
|
80
82
|
const runtimeLabel = formatRuntimeSegment(
|
|
81
83
|
theme,
|
|
@@ -19,7 +19,6 @@ import { readRuntimeInfo } from "./runtime";
|
|
|
19
19
|
import { installSelectorBorderStyle } from "./selector-border";
|
|
20
20
|
import { registerZentuiSettingsCommand } from "./settings-command";
|
|
21
21
|
import { type FooterState, createInitialState, syncState } from "./state";
|
|
22
|
-
import { renderAccentLine } from "./style";
|
|
23
22
|
import { PolishedEditor } from "./ui";
|
|
24
23
|
import { installUserMessageStyle } from "./user-message";
|
|
25
24
|
|
|
@@ -81,15 +80,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
81
80
|
keybindings,
|
|
82
81
|
ctx.ui.theme,
|
|
83
82
|
getCurrentConfig,
|
|
84
|
-
() =>
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
getCurrentConfig().colorSources.editor,
|
|
89
|
-
state.modelLabel,
|
|
90
|
-
),
|
|
91
|
-
ctx.ui.theme.fg("text", state.providerLabel),
|
|
92
|
-
].join(ctx.ui.theme.fg("borderMuted", " ")),
|
|
83
|
+
() => ({
|
|
84
|
+
modelLabel: state.modelLabel,
|
|
85
|
+
providerLabel: state.providerLabel,
|
|
86
|
+
}),
|
|
93
87
|
getThinkingLevel,
|
|
94
88
|
),
|
|
95
89
|
);
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
truncateToWidth,
|
|
8
8
|
} from "@earendil-works/pi-tui";
|
|
9
9
|
import type { ColorSource, ColorSourcesConfig, PolishedTuiConfig } from "./config";
|
|
10
|
-
import { EDITOR_BORDER_STYLE, renderChromeBorder } from "./style";
|
|
10
|
+
import { EDITOR_BORDER_STYLE, renderChromeBorder, safeThemeFg } from "./style";
|
|
11
11
|
|
|
12
12
|
const colorSourceValues: ColorSource[] = ["theme", "terminal"];
|
|
13
13
|
|
|
@@ -97,8 +97,8 @@ export function registerZentuiSettingsCommand(pi: ExtensionAPI, deps: SettingsCo
|
|
|
97
97
|
EDITOR_BORDER_STYLE,
|
|
98
98
|
"─".repeat(Math.max(0, width)),
|
|
99
99
|
);
|
|
100
|
-
const header = theme
|
|
101
|
-
const hint = theme
|
|
100
|
+
const header = safeThemeFg(theme, "accent", theme.bold("Zentui settings"));
|
|
101
|
+
const hint = safeThemeFg(theme, "muted", "Enter/Space cycles values · Esc closes");
|
|
102
102
|
return [
|
|
103
103
|
truncateToWidth(border, width, ""),
|
|
104
104
|
truncateToWidth(header, width, ""),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
1
2
|
import type { ColorSource, ColorSpec } from "./config";
|
|
2
3
|
|
|
3
4
|
type ThemeLike = {
|
|
@@ -12,6 +13,21 @@ export type { ThemeLike };
|
|
|
12
13
|
export const EDITOR_ACCENT_STYLE = "blue";
|
|
13
14
|
export const EDITOR_BORDER_STYLE = "bright-black";
|
|
14
15
|
|
|
16
|
+
export type SourceStyleFallback = {
|
|
17
|
+
theme: ColorSpec;
|
|
18
|
+
terminal: ColorSpec;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const EDITOR_ACCENT_FALLBACK: SourceStyleFallback = {
|
|
22
|
+
theme: "accent",
|
|
23
|
+
terminal: EDITOR_ACCENT_STYLE,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const EDITOR_BORDER_FALLBACK: SourceStyleFallback = {
|
|
27
|
+
theme: "borderMuted",
|
|
28
|
+
terminal: EDITOR_BORDER_STYLE,
|
|
29
|
+
};
|
|
30
|
+
|
|
15
31
|
function isHexColor(value: string): boolean {
|
|
16
32
|
return /^#(?:[0-9a-fA-F]{6})$/.test(value);
|
|
17
33
|
}
|
|
@@ -72,6 +88,54 @@ const themeColorNameMap = new Map([
|
|
|
72
88
|
|
|
73
89
|
const themeStyleModifiers = new Set(["bold", "italic", "underline"]);
|
|
74
90
|
|
|
91
|
+
const themeColorTokens = new Set<ThemeColor>([
|
|
92
|
+
"accent",
|
|
93
|
+
"border",
|
|
94
|
+
"borderAccent",
|
|
95
|
+
"borderMuted",
|
|
96
|
+
"success",
|
|
97
|
+
"error",
|
|
98
|
+
"warning",
|
|
99
|
+
"muted",
|
|
100
|
+
"dim",
|
|
101
|
+
"text",
|
|
102
|
+
"thinkingText",
|
|
103
|
+
"userMessageText",
|
|
104
|
+
"customMessageText",
|
|
105
|
+
"customMessageLabel",
|
|
106
|
+
"toolTitle",
|
|
107
|
+
"toolOutput",
|
|
108
|
+
"mdHeading",
|
|
109
|
+
"mdLink",
|
|
110
|
+
"mdLinkUrl",
|
|
111
|
+
"mdCode",
|
|
112
|
+
"mdCodeBlock",
|
|
113
|
+
"mdCodeBlockBorder",
|
|
114
|
+
"mdQuote",
|
|
115
|
+
"mdQuoteBorder",
|
|
116
|
+
"mdHr",
|
|
117
|
+
"mdListBullet",
|
|
118
|
+
"toolDiffAdded",
|
|
119
|
+
"toolDiffRemoved",
|
|
120
|
+
"toolDiffContext",
|
|
121
|
+
"syntaxComment",
|
|
122
|
+
"syntaxKeyword",
|
|
123
|
+
"syntaxFunction",
|
|
124
|
+
"syntaxVariable",
|
|
125
|
+
"syntaxString",
|
|
126
|
+
"syntaxNumber",
|
|
127
|
+
"syntaxType",
|
|
128
|
+
"syntaxOperator",
|
|
129
|
+
"syntaxPunctuation",
|
|
130
|
+
"thinkingOff",
|
|
131
|
+
"thinkingMinimal",
|
|
132
|
+
"thinkingLow",
|
|
133
|
+
"thinkingMedium",
|
|
134
|
+
"thinkingHigh",
|
|
135
|
+
"thinkingXhigh",
|
|
136
|
+
"bashMode",
|
|
137
|
+
]);
|
|
138
|
+
|
|
75
139
|
function terminalColorToAnsi(color: string, isBackground = false): string | undefined {
|
|
76
140
|
const normalized = color.toLowerCase();
|
|
77
141
|
const colorCode = terminalColorCodes.get(normalized);
|
|
@@ -92,6 +156,26 @@ function isExplicitTerminalColorToken(token: string): boolean {
|
|
|
92
156
|
return /^(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/.test(normalized);
|
|
93
157
|
}
|
|
94
158
|
|
|
159
|
+
function isSupportedStyleToken(token: string): boolean {
|
|
160
|
+
const normalized = token.toLowerCase();
|
|
161
|
+
if (terminalStyleModifiers.has(normalized)) return true;
|
|
162
|
+
if (terminalColorToAnsi(normalized) !== undefined) return true;
|
|
163
|
+
|
|
164
|
+
const isForeground = normalized.startsWith("fg:");
|
|
165
|
+
const isBackground = normalized.startsWith("bg:");
|
|
166
|
+
if (isForeground || isBackground) {
|
|
167
|
+
return terminalColorToAnsi(normalized.slice(3), isBackground) !== undefined;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return themeColorTokens.has(token as ThemeColor);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function isSupportedColorSpec(style: ColorSpec): boolean {
|
|
174
|
+
const trimmed = style.trim();
|
|
175
|
+
if (trimmed === "") return true;
|
|
176
|
+
return trimmed.split(/\s+/).every(isSupportedStyleToken);
|
|
177
|
+
}
|
|
178
|
+
|
|
95
179
|
function applyThemeModifiers(theme: ThemeLike, styleTokens: string[], text: string): string {
|
|
96
180
|
let rendered = text;
|
|
97
181
|
for (const token of styleTokens) {
|
|
@@ -103,7 +187,7 @@ function applyThemeModifiers(theme: ThemeLike, styleTokens: string[], text: stri
|
|
|
103
187
|
return rendered;
|
|
104
188
|
}
|
|
105
189
|
|
|
106
|
-
function
|
|
190
|
+
export function safeThemeFg(theme: ThemeLike, color: string, text: string): string {
|
|
107
191
|
try {
|
|
108
192
|
return theme.fg(color, text);
|
|
109
193
|
} catch {
|
|
@@ -137,7 +221,7 @@ export function colorize(theme: ThemeLike, color: ColorSpec, text: string): stri
|
|
|
137
221
|
if (isHexColor(color)) {
|
|
138
222
|
return `${hexToAnsi(color)}${text}\x1b[39m`;
|
|
139
223
|
}
|
|
140
|
-
return
|
|
224
|
+
return safeThemeFg(theme, color, text);
|
|
141
225
|
}
|
|
142
226
|
|
|
143
227
|
/**
|
|
@@ -184,7 +268,7 @@ export function renderThemeStyle(theme: ThemeLike, style: ColorSpec, text: strin
|
|
|
184
268
|
if (tokens.some(isExplicitTerminalColorToken)) return renderTerminalStyle(style, text);
|
|
185
269
|
|
|
186
270
|
const color = mapThemeColor(tokens) ?? "text";
|
|
187
|
-
return
|
|
271
|
+
return safeThemeFg(theme, color, applyThemeModifiers(theme, tokens, text));
|
|
188
272
|
}
|
|
189
273
|
|
|
190
274
|
export function renderStyleForSource(
|
|
@@ -198,6 +282,17 @@ export function renderStyleForSource(
|
|
|
198
282
|
: renderThemeStyle(theme, style, text);
|
|
199
283
|
}
|
|
200
284
|
|
|
285
|
+
export function renderStyleForSourceOrFallback(
|
|
286
|
+
theme: ThemeLike,
|
|
287
|
+
source: ColorSource,
|
|
288
|
+
style: ColorSpec | undefined,
|
|
289
|
+
fallback: ColorSpec | SourceStyleFallback,
|
|
290
|
+
text: string,
|
|
291
|
+
): string {
|
|
292
|
+
const fallbackStyle = typeof fallback === "string" ? fallback : fallback[source];
|
|
293
|
+
return renderStyleForSource(theme, source, style ?? fallbackStyle, text);
|
|
294
|
+
}
|
|
295
|
+
|
|
201
296
|
export function renderEditorAccent(text: string): string {
|
|
202
297
|
return renderTerminalStyle(EDITOR_ACCENT_STYLE, text);
|
|
203
298
|
}
|
|
@@ -207,7 +302,7 @@ export function renderEditorBorder(text: string): string {
|
|
|
207
302
|
}
|
|
208
303
|
|
|
209
304
|
export function renderAccentLine(theme: ThemeLike, source: ColorSource, text: string): string {
|
|
210
|
-
return
|
|
305
|
+
return renderStyleForSourceOrFallback(theme, source, undefined, EDITOR_ACCENT_FALLBACK, text);
|
|
211
306
|
}
|
|
212
307
|
|
|
213
308
|
export function renderChromeBorder(
|
|
@@ -216,6 +311,11 @@ export function renderChromeBorder(
|
|
|
216
311
|
terminalFallbackStyle: ColorSpec,
|
|
217
312
|
text: string,
|
|
218
313
|
): string {
|
|
219
|
-
|
|
220
|
-
|
|
314
|
+
return renderStyleForSourceOrFallback(
|
|
315
|
+
theme,
|
|
316
|
+
source,
|
|
317
|
+
undefined,
|
|
318
|
+
{ theme: "borderMuted", terminal: terminalFallbackStyle },
|
|
319
|
+
text,
|
|
320
|
+
);
|
|
221
321
|
}
|
package/extensions/zentui/ui.ts
CHANGED
|
@@ -7,20 +7,30 @@ import {
|
|
|
7
7
|
visibleWidth,
|
|
8
8
|
} from "@earendil-works/pi-tui";
|
|
9
9
|
import type { PolishedTuiConfig } from "./config";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
EDITOR_ACCENT_FALLBACK,
|
|
12
|
+
EDITOR_BORDER_FALLBACK,
|
|
13
|
+
renderStyleForSourceOrFallback,
|
|
14
|
+
safeThemeFg,
|
|
15
|
+
} from "./style";
|
|
11
16
|
|
|
12
17
|
type AutocompleteEditorInternals = {
|
|
13
18
|
autocompleteList?: Pick<Component, "render">;
|
|
14
19
|
isShowingAutocomplete?: () => boolean;
|
|
15
20
|
};
|
|
16
21
|
|
|
22
|
+
type EditorMeta = {
|
|
23
|
+
modelLabel: string;
|
|
24
|
+
providerLabel: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
17
27
|
function clampRenderedLines(lines: string[], width: number): string[] {
|
|
18
28
|
const maxWidth = Math.max(0, width);
|
|
19
29
|
return lines.map((line) => truncateToWidth(line, maxWidth, ""));
|
|
20
30
|
}
|
|
21
31
|
|
|
22
32
|
export class PolishedEditor extends CustomEditor {
|
|
23
|
-
private readonly getModelMeta: () =>
|
|
33
|
+
private readonly getModelMeta: () => EditorMeta;
|
|
24
34
|
private readonly getThinkingLevel: () => string | undefined;
|
|
25
35
|
private readonly getConfig: () => PolishedTuiConfig;
|
|
26
36
|
private readonly uiTheme: Theme;
|
|
@@ -32,11 +42,11 @@ export class PolishedEditor extends CustomEditor {
|
|
|
32
42
|
keybindings: KeybindingsManager,
|
|
33
43
|
uiTheme: Theme,
|
|
34
44
|
getConfig: () => PolishedTuiConfig,
|
|
35
|
-
getModelMeta: () =>
|
|
45
|
+
getModelMeta: () => EditorMeta,
|
|
36
46
|
getThinkingLevel: () => string | undefined,
|
|
37
47
|
) {
|
|
38
48
|
super(tui, theme, keybindings, { paddingX: 0 });
|
|
39
|
-
this.borderColor = (text: string) => uiTheme
|
|
49
|
+
this.borderColor = (text: string) => safeThemeFg(uiTheme, "border", text);
|
|
40
50
|
this.uiTheme = uiTheme;
|
|
41
51
|
this.getConfig = getConfig;
|
|
42
52
|
this.getModelMeta = getModelMeta;
|
|
@@ -49,6 +59,23 @@ export class PolishedEditor extends CustomEditor {
|
|
|
49
59
|
return `${truncated}${pad}`;
|
|
50
60
|
}
|
|
51
61
|
|
|
62
|
+
private editorThinkingStyle(config: PolishedTuiConfig, level: string): string | undefined {
|
|
63
|
+
switch (level.toLowerCase()) {
|
|
64
|
+
case "minimal":
|
|
65
|
+
return config.colors.editorThinkingMinimal ?? config.colors.editorThinking;
|
|
66
|
+
case "low":
|
|
67
|
+
return config.colors.editorThinkingLow ?? config.colors.editorThinking;
|
|
68
|
+
case "medium":
|
|
69
|
+
return config.colors.editorThinkingMedium ?? config.colors.editorThinking;
|
|
70
|
+
case "high":
|
|
71
|
+
return config.colors.editorThinkingHigh ?? config.colors.editorThinking;
|
|
72
|
+
case "xhigh":
|
|
73
|
+
return config.colors.editorThinkingXhigh ?? config.colors.editorThinking;
|
|
74
|
+
default:
|
|
75
|
+
return config.colors.editorThinking;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
52
79
|
render(width: number): string[] {
|
|
53
80
|
if (width <= 2) {
|
|
54
81
|
return clampRenderedLines(super.render(width), width);
|
|
@@ -84,26 +111,61 @@ export class PolishedEditor extends CustomEditor {
|
|
|
84
111
|
return clampRenderedLines(rendered, width);
|
|
85
112
|
}
|
|
86
113
|
|
|
114
|
+
const config = this.getConfig();
|
|
115
|
+
const colorSource = config.colorSources.editor;
|
|
87
116
|
const editorLines = editorFrame.slice(1, -1);
|
|
88
|
-
const
|
|
117
|
+
const { modelLabel, providerLabel } = this.getModelMeta();
|
|
118
|
+
const model = renderStyleForSourceOrFallback(
|
|
119
|
+
this.uiTheme,
|
|
120
|
+
colorSource,
|
|
121
|
+
config.colors.editorModel,
|
|
122
|
+
EDITOR_ACCENT_FALLBACK,
|
|
123
|
+
modelLabel,
|
|
124
|
+
);
|
|
125
|
+
const provider = renderStyleForSourceOrFallback(
|
|
126
|
+
this.uiTheme,
|
|
127
|
+
colorSource,
|
|
128
|
+
config.colors.editorProvider,
|
|
129
|
+
"text",
|
|
130
|
+
providerLabel,
|
|
131
|
+
);
|
|
132
|
+
const modelMeta = [model, provider]
|
|
133
|
+
.filter(Boolean)
|
|
134
|
+
.join(safeThemeFg(this.uiTheme, "borderMuted", " "));
|
|
135
|
+
const metaParts = [modelMeta];
|
|
89
136
|
const thinkingLevel = this.getThinkingLevel();
|
|
90
137
|
if (thinkingLevel && thinkingLevel !== "off") {
|
|
91
|
-
metaParts.push(
|
|
138
|
+
metaParts.push(
|
|
139
|
+
renderStyleForSourceOrFallback(
|
|
140
|
+
this.uiTheme,
|
|
141
|
+
colorSource,
|
|
142
|
+
this.editorThinkingStyle(config, thinkingLevel),
|
|
143
|
+
"muted",
|
|
144
|
+
thinkingLevel,
|
|
145
|
+
),
|
|
146
|
+
);
|
|
92
147
|
}
|
|
93
|
-
const meta = metaParts.filter(Boolean).join(this.uiTheme
|
|
148
|
+
const meta = metaParts.filter(Boolean).join(safeThemeFg(this.uiTheme, "border", " "));
|
|
94
149
|
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
150
|
+
const rail = `${renderStyleForSourceOrFallback(
|
|
151
|
+
this.uiTheme,
|
|
152
|
+
colorSource,
|
|
153
|
+
config.colors.editorAccent,
|
|
154
|
+
EDITOR_ACCENT_FALLBACK,
|
|
155
|
+
"│",
|
|
156
|
+
)}${this.reset} `;
|
|
157
|
+
const top = renderStyleForSourceOrFallback(
|
|
98
158
|
this.uiTheme,
|
|
99
159
|
colorSource,
|
|
100
|
-
|
|
160
|
+
config.colors.editorBorder,
|
|
161
|
+
EDITOR_BORDER_FALLBACK,
|
|
101
162
|
"─".repeat(width),
|
|
102
163
|
);
|
|
103
|
-
const bottom =
|
|
164
|
+
const bottom = renderStyleForSourceOrFallback(
|
|
104
165
|
this.uiTheme,
|
|
105
166
|
colorSource,
|
|
106
|
-
|
|
167
|
+
config.colors.editorBorder,
|
|
168
|
+
EDITOR_BORDER_FALLBACK,
|
|
107
169
|
"─".repeat(width),
|
|
108
170
|
);
|
|
109
171
|
const lines = ["", ...editorLines, "", meta];
|
|
@@ -6,7 +6,11 @@ import {
|
|
|
6
6
|
visibleWidth,
|
|
7
7
|
} from "@earendil-works/pi-tui";
|
|
8
8
|
import type { PolishedTuiConfig } from "./config";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
EDITOR_ACCENT_FALLBACK,
|
|
11
|
+
EDITOR_BORDER_FALLBACK,
|
|
12
|
+
renderStyleForSourceOrFallback,
|
|
13
|
+
} from "./style";
|
|
10
14
|
|
|
11
15
|
const OSC133_ZONE_START = "\x1b]133;A\x07";
|
|
12
16
|
const OSC133_ZONE_END = "\x1b]133;B\x07";
|
|
@@ -52,7 +56,12 @@ function findMarkdownText(value: unknown): string | undefined {
|
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
function themeFg(theme: Theme | undefined, color: ThemeColor, text: string): string {
|
|
55
|
-
|
|
59
|
+
if (!theme) return text;
|
|
60
|
+
try {
|
|
61
|
+
return theme.fg(color, text);
|
|
62
|
+
} catch {
|
|
63
|
+
return text;
|
|
64
|
+
}
|
|
56
65
|
}
|
|
57
66
|
|
|
58
67
|
function makeMarkdownTheme(theme: Theme | undefined): MarkdownTheme {
|
|
@@ -87,7 +96,17 @@ function renderPromptBoxLine(
|
|
|
87
96
|
config: PolishedTuiConfig,
|
|
88
97
|
): string {
|
|
89
98
|
if (width <= 0) return "";
|
|
90
|
-
const rail = `${
|
|
99
|
+
const rail = `${
|
|
100
|
+
theme
|
|
101
|
+
? renderStyleForSourceOrFallback(
|
|
102
|
+
theme,
|
|
103
|
+
config.colorSources.userMessages,
|
|
104
|
+
config.colors.editorAccent,
|
|
105
|
+
EDITOR_ACCENT_FALLBACK,
|
|
106
|
+
"│",
|
|
107
|
+
)
|
|
108
|
+
: "│"
|
|
109
|
+
} `;
|
|
91
110
|
const contentWidth = Math.max(0, width - visibleWidth(rail));
|
|
92
111
|
return truncateToWidth(`${rail}${fillLine(line, contentWidth)}`, width, "");
|
|
93
112
|
}
|
|
@@ -103,7 +122,17 @@ function renderZentuiUserMessage(
|
|
|
103
122
|
if (width <= 0) return [""];
|
|
104
123
|
|
|
105
124
|
const railWidth = visibleWidth(
|
|
106
|
-
`${
|
|
125
|
+
`${
|
|
126
|
+
theme
|
|
127
|
+
? renderStyleForSourceOrFallback(
|
|
128
|
+
theme,
|
|
129
|
+
config.colorSources.userMessages,
|
|
130
|
+
config.colors.editorAccent,
|
|
131
|
+
EDITOR_ACCENT_FALLBACK,
|
|
132
|
+
"│",
|
|
133
|
+
)
|
|
134
|
+
: "│"
|
|
135
|
+
} `,
|
|
107
136
|
);
|
|
108
137
|
const contentWidth = Math.max(1, width - railWidth);
|
|
109
138
|
const renderer = new Markdown(text, 0, 0, makeMarkdownTheme(theme), {
|
|
@@ -112,10 +141,11 @@ function renderZentuiUserMessage(
|
|
|
112
141
|
const body = renderer.render(contentWidth);
|
|
113
142
|
const contentLines = body.length > 0 ? body : [""];
|
|
114
143
|
const border = theme
|
|
115
|
-
?
|
|
144
|
+
? renderStyleForSourceOrFallback(
|
|
116
145
|
theme,
|
|
117
146
|
config.colorSources.userMessages,
|
|
118
|
-
|
|
147
|
+
config.colors.editorBorder,
|
|
148
|
+
EDITOR_BORDER_FALLBACK,
|
|
119
149
|
"─".repeat(width),
|
|
120
150
|
)
|
|
121
151
|
: "─".repeat(width);
|