@xingjisoft/neutrino 0.1.0 → 0.1.2
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 +35 -2
- package/dist/app.js +180 -2
- package/dist/clipboard.js +113 -0
- package/dist/colors.js +2 -0
- package/dist/config.js +19 -0
- package/dist/header.js +11 -6
- package/dist/markdown.js +630 -0
- package/dist/screens/agentConversation.js +12 -14
- package/dist/screens/agentHistory.js +76 -24
- package/dist/screens/agentSelect.js +54 -7
- package/dist/screens/chat.js +48 -22
- package/dist/screens/history.js +73 -20
- package/dist/screens/modelSelect.js +66 -7
- package/dist/screens/settings.js +64 -2
- package/dist/screens/start.js +153 -14
- package/dist/selection.js +75 -0
- package/dist/state.js +3 -0
- package/dist/terminal/buffer.js +63 -8
- package/dist/terminal/input.js +24 -6
- package/dist/terminal/renderer.js +13 -4
- package/dist/terminal/screen.js +5 -0
- package/dist/ui.js +93 -27
- package/package.json +2 -1
package/dist/ui.js
CHANGED
|
@@ -70,25 +70,12 @@ export function wrapText(text, width) {
|
|
|
70
70
|
}
|
|
71
71
|
return lines;
|
|
72
72
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const innerW = w - 4;
|
|
80
|
-
const innerY = y + 1;
|
|
81
|
-
if (active)
|
|
82
|
-
buf.fillRect(x, y, w, h, { bg: C.highlightBg });
|
|
83
|
-
const textStyle = dimmed
|
|
84
|
-
? { fg: C.dim, bg: C.highlightBg }
|
|
85
|
-
: active
|
|
86
|
-
? { fg: C.white, bg: C.highlightBg }
|
|
87
|
-
: { fg: C.dim };
|
|
88
|
-
if (value.length === 0) {
|
|
89
|
-
buf.writeText(innerX, innerY, truncateByWidth(placeholder, innerW), textStylePlaceholder(active));
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
73
|
+
/**
|
|
74
|
+
* Compute which part of the input value is visible inside the box, given the
|
|
75
|
+
* cursor position. Shared by `drawInputBox` and mouse click handling so the
|
|
76
|
+
* rendered text and click-to-position-cursor always agree.
|
|
77
|
+
*/
|
|
78
|
+
export function computeInputWindow(value, cursorPos, innerW) {
|
|
92
79
|
const chars = Array.from(value);
|
|
93
80
|
const widths = chars.map((ch) => wcwidth(ch.codePointAt(0) ?? 0));
|
|
94
81
|
const cursorCp = Math.min(Array.from(value.slice(0, cursorPos)).length, chars.length);
|
|
@@ -113,20 +100,99 @@ export function drawInputBox(buf, x, y, w, value, cursorPos, opts = {}) {
|
|
|
113
100
|
let prefixW = 0;
|
|
114
101
|
for (let i = 0; i < startCp; i++)
|
|
115
102
|
prefixW += widths[i];
|
|
116
|
-
let
|
|
103
|
+
let visibleCount = 0;
|
|
117
104
|
let acc = 0;
|
|
118
105
|
for (let i = startCp; i < chars.length; i++) {
|
|
119
|
-
if (acc + widths[i] > innerW)
|
|
120
|
-
endCp = i;
|
|
106
|
+
if (acc + widths[i] > innerW)
|
|
121
107
|
break;
|
|
122
|
-
}
|
|
123
108
|
acc += widths[i];
|
|
109
|
+
visibleCount++;
|
|
110
|
+
}
|
|
111
|
+
return { startCp, prefixW, visibleCount };
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Map a column inside the input box (0-based, relative to the inner area) to a
|
|
115
|
+
* cursor index in the full value.
|
|
116
|
+
*/
|
|
117
|
+
export function cursorFromColumn(value, col, win) {
|
|
118
|
+
const chars = Array.from(value);
|
|
119
|
+
if (col <= 0)
|
|
120
|
+
return win.startCp;
|
|
121
|
+
let acc = 0;
|
|
122
|
+
let i = win.startCp;
|
|
123
|
+
while (i < chars.length) {
|
|
124
|
+
const w = wcwidth(chars[i].codePointAt(0) ?? 0);
|
|
125
|
+
if (col < acc + w)
|
|
126
|
+
return i;
|
|
127
|
+
acc += w;
|
|
128
|
+
i++;
|
|
129
|
+
}
|
|
130
|
+
return i;
|
|
131
|
+
}
|
|
132
|
+
export function inRect(px, py, r) {
|
|
133
|
+
return px >= r.x && px < r.x + r.w && py >= r.y && py < r.y + r.h;
|
|
134
|
+
}
|
|
135
|
+
export function drawHint(buf, y, text) {
|
|
136
|
+
const max = buf.width;
|
|
137
|
+
const t = displayWidth(text) <= max ? text : truncateByWidth(text, max - 1) + "…";
|
|
138
|
+
buf.centerText(y, t, { fg: C.dim });
|
|
139
|
+
}
|
|
140
|
+
export function drawInputBox(buf, x, y, w, value, cursorPos, opts = {}) {
|
|
141
|
+
const active = opts.active ?? true;
|
|
142
|
+
const dimmed = opts.dimmed ?? false;
|
|
143
|
+
const placeholder = opts.placeholder ?? "";
|
|
144
|
+
const cursorOn = opts.cursorOn ?? true;
|
|
145
|
+
const h = 3;
|
|
146
|
+
const innerX = x + 2;
|
|
147
|
+
const innerW = w - 4;
|
|
148
|
+
const innerY = y + 1;
|
|
149
|
+
if (active)
|
|
150
|
+
buf.fillRect(x, y, w, h, { bg: C.highlightBg });
|
|
151
|
+
const textStyle = dimmed
|
|
152
|
+
? { fg: C.dim, bg: C.highlightBg }
|
|
153
|
+
: active
|
|
154
|
+
? { fg: C.white, bg: C.highlightBg }
|
|
155
|
+
: { fg: C.dim };
|
|
156
|
+
if (value.length === 0) {
|
|
157
|
+
buf.writeText(innerX, innerY, truncateByWidth(placeholder, innerW), textStylePlaceholder(active));
|
|
158
|
+
return;
|
|
124
159
|
}
|
|
125
|
-
const
|
|
160
|
+
const win = computeInputWindow(value, cursorPos, innerW);
|
|
161
|
+
const chars = Array.from(value);
|
|
162
|
+
// Codepoint index of the cursor (cursorPos is a UTF-16 offset).
|
|
163
|
+
const cursorCp = Math.min(Array.from(value.slice(0, cursorPos)).length, chars.length);
|
|
164
|
+
const visible = chars.slice(win.startCp, win.startCp + win.visibleCount).join("");
|
|
126
165
|
buf.writeText(innerX, innerY, visible, textStyle);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
166
|
+
// Blinking block cursor. While it is "on" the character underneath is drawn
|
|
167
|
+
// inside the block in the opposite colour so the text stays legible instead
|
|
168
|
+
// of being hidden by the cursor.
|
|
169
|
+
const cursorScreenCol = cursorColAt(chars, cursorCp, win);
|
|
170
|
+
if (active && cursorOn && cursorScreenCol >= 0 && cursorScreenCol < innerW) {
|
|
171
|
+
drawBlockCursor(buf, innerX + cursorScreenCol, innerY, chars, cursorCp, cursorScreenCol, innerW);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function cursorColAt(chars, cursorCp, win) {
|
|
175
|
+
let col = win.prefixW;
|
|
176
|
+
for (let i = win.startCp; i < cursorCp; i++) {
|
|
177
|
+
col += wcwidth(chars[i].codePointAt(0) ?? 0);
|
|
178
|
+
}
|
|
179
|
+
return col - win.prefixW;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Draw a single block cursor cell. The glyph under the cursor (the character
|
|
183
|
+
* immediately to its right) is rendered in the opposite colour — a dark glyph
|
|
184
|
+
* on the accent block — so it remains readable. Past the end of the text, or
|
|
185
|
+
* when the glyph would not fit, a solid block is drawn instead.
|
|
186
|
+
*/
|
|
187
|
+
function drawBlockCursor(buf, x, y, chars, cursorCp, col, innerW) {
|
|
188
|
+
const cursorStyle = { fg: C.black, bg: C.accent };
|
|
189
|
+
const ch = cursorCp < chars.length ? chars[cursorCp] : "";
|
|
190
|
+
const cw = ch ? wcwidth(ch.codePointAt(0) ?? 0) : 0;
|
|
191
|
+
if (ch && ch !== " " && cw >= 1 && col + cw <= innerW) {
|
|
192
|
+
buf.writeText(x, y, ch, cursorStyle);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
buf.set(x, y, " ", cursorStyle);
|
|
130
196
|
}
|
|
131
197
|
}
|
|
132
198
|
function textStylePlaceholder(active) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xingjisoft/neutrino",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "XINGJI Neutrino - a CLI coding agent in the terminal.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "tsc -p tsconfig.json",
|
|
20
|
+
"test": "npm run build && node scripts/test-mouse.mjs && node scripts/test-cursor.mjs && node scripts/test-selection.mjs && node scripts/test-render.mjs",
|
|
20
21
|
"prepare": "npm run build"
|
|
21
22
|
},
|
|
22
23
|
"keywords": [
|