dsh-neotui 0.1.26 → 0.1.28
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/package.json +1 -1
- package/src/panels.js +74 -29
package/package.json
CHANGED
package/src/panels.js
CHANGED
|
@@ -1818,6 +1818,63 @@ export class SettingsPanel extends Widget {
|
|
|
1818
1818
|
|
|
1819
1819
|
// ---- Model provider management (simple form buffer) ----
|
|
1820
1820
|
|
|
1821
|
+
/** A centered standalone edit buffer (ControlPanel-style): its own Input with
|
|
1822
|
+
* a visible caret, full key isolation from NORMAL/INSERT routing, and paste
|
|
1823
|
+
* support — Enter commits, Esc cancels. */
|
|
1824
|
+
export class EditPopup extends Popup {
|
|
1825
|
+
constructor(app, { title, value, onCommit }) {
|
|
1826
|
+
const w = Math.min(80, app.screen.w - 8);
|
|
1827
|
+
const h = Math.min(16, app.screen.h - 6);
|
|
1828
|
+
super({
|
|
1829
|
+
x: Math.floor((app.screen.w - w) / 2), y: Math.floor((app.screen.h - h) / 2),
|
|
1830
|
+
w, h, title,
|
|
1831
|
+
lines: [],
|
|
1832
|
+
buttons: [],
|
|
1833
|
+
onAction: () => {},
|
|
1834
|
+
});
|
|
1835
|
+
this.app = app;
|
|
1836
|
+
this.onCommit = onCommit;
|
|
1837
|
+
this.input = new Input({
|
|
1838
|
+
x: this.x + 2, y: this.y + h - 2, w: w - 4, h: 1,
|
|
1839
|
+
multi: true, maxLines: 4, app,
|
|
1840
|
+
prompt: "> ", placeholder: "输入值…(Ctrl+Shift+V 粘贴,Enter 确定,Esc 取消)",
|
|
1841
|
+
});
|
|
1842
|
+
this.input.setValue(String(value ?? ""), { select: true });
|
|
1843
|
+
this.#layout();
|
|
1844
|
+
}
|
|
1845
|
+
#layout() {
|
|
1846
|
+
const lines = [];
|
|
1847
|
+
lines.push([{ t: " 当前值预览:", fg: K.DIM, underline: true }]);
|
|
1848
|
+
const v = this.input.value;
|
|
1849
|
+
if (v === "") lines.push([{ t: "(空)", fg: K.FAINT }]);
|
|
1850
|
+
else for (const ln of v.split("\n").slice(0, 6)) lines.push([{ t: " " + truncate(ln, this.w - 6), fg: K.TXT }]);
|
|
1851
|
+
lines.push([{ t: "" }]);
|
|
1852
|
+
this.lines = lines;
|
|
1853
|
+
}
|
|
1854
|
+
render(screen) {
|
|
1855
|
+
super.render(screen);
|
|
1856
|
+
this.input.render(screen);
|
|
1857
|
+
}
|
|
1858
|
+
onKey(ev) {
|
|
1859
|
+
if (ev.type === "key" && ev.name === "escape") { this.app.closeOverlay(); this.app.focus(this.app.chat); return true; }
|
|
1860
|
+
if (ev.type === "key" && ev.name === "enter") {
|
|
1861
|
+
const v = this.input.value;
|
|
1862
|
+
this.app.closeOverlay();
|
|
1863
|
+
this.app.focus(this.app.chat);
|
|
1864
|
+
this.onCommit?.(v);
|
|
1865
|
+
return true;
|
|
1866
|
+
}
|
|
1867
|
+
const handled = this.input.onKey(ev);
|
|
1868
|
+
if (handled) this.#layout();
|
|
1869
|
+
this.app.redraw();
|
|
1870
|
+
return true;
|
|
1871
|
+
}
|
|
1872
|
+
onMouse(ev) {
|
|
1873
|
+
if (this.input.inside(ev.x, ev.y)) { this.input.onMouse(ev); this.app.redraw(); return true; }
|
|
1874
|
+
return super.onMouse(ev);
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1821
1878
|
export class ModelPanel extends Widget {
|
|
1822
1879
|
constructor(app) {
|
|
1823
1880
|
super({ x: 30, y: 0, w: app.screen.w - 30, h: app.screen.h - 1 });
|
|
@@ -1842,15 +1899,13 @@ export class ModelPanel extends Widget {
|
|
|
1842
1899
|
this.scanning = false;
|
|
1843
1900
|
const listW = 26;
|
|
1844
1901
|
this.listView = new ScrollView({ x: this.x + 1, y: this.y + 1, w: listW, h: this.h - 2, showScrollbar: true });
|
|
1845
|
-
this.formView = new ScrollView({ x: this.x + listW + 1, y: this.y + 1, w: this.w - listW - 2, h: this.h -
|
|
1846
|
-
this.input = new Input({ x: this.x + listW + 1, y: this.y + this.h - 2, w: this.w - listW - 2, h: 1, prompt: "值: ", placeholder: "Enter 提交 · Esc 取消" });
|
|
1902
|
+
this.formView = new ScrollView({ x: this.x + listW + 1, y: this.y + 1, w: this.w - listW - 2, h: this.h - 2, showScrollbar: true });
|
|
1847
1903
|
}
|
|
1848
1904
|
relayout(x, y, w, h) {
|
|
1849
1905
|
this.x = x; this.y = y; this.w = w; this.h = h;
|
|
1850
1906
|
const listW = 26;
|
|
1851
1907
|
this.listView.x = x + 1; this.listView.y = y + 1; this.listView.w = listW; this.listView.h = h - 2;
|
|
1852
|
-
this.formView.x = x + listW + 1; this.formView.y = y + 1; this.formView.w = w - listW - 2; this.formView.h = h -
|
|
1853
|
-
this.input.x = x + listW + 1; this.input.y = y + h - 2; this.input.w = w - listW - 2;
|
|
1908
|
+
this.formView.x = x + listW + 1; this.formView.y = y + 1; this.formView.w = w - listW - 2; this.formView.h = h - 2;
|
|
1854
1909
|
}
|
|
1855
1910
|
async load() {
|
|
1856
1911
|
try {
|
|
@@ -1988,22 +2043,17 @@ export class ModelPanel extends Widget {
|
|
|
1988
2043
|
screen.text(this.x + 1, this.y, " 模型供应商", { fg: K.DIM });
|
|
1989
2044
|
this.listView.render(screen);
|
|
1990
2045
|
this.formView.render(screen);
|
|
1991
|
-
if (this.editing) {
|
|
1992
|
-
screen.text(this.x + 28, this.y + this.h - 3, `编辑 ${this.editing.label}`, { fg: K.WARN, bold: true });
|
|
1993
|
-
this.input.render(screen);
|
|
1994
|
-
}
|
|
1995
2046
|
}
|
|
1996
2047
|
#startEdit(label, value, commit) {
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
this.app
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
this.
|
|
2005
|
-
|
|
2006
|
-
this.#rebuild();
|
|
2048
|
+
// a REAL standalone edit buffer in the middle of the window: own caret,
|
|
2049
|
+
// isolated from NORMAL/INSERT routing, paste supported
|
|
2050
|
+
const popup = new EditPopup(this.app, {
|
|
2051
|
+
title: `编辑 ${label}`,
|
|
2052
|
+
value,
|
|
2053
|
+
onCommit: (text) => { commit(text); this.#rebuild(); this.app.redraw(); },
|
|
2054
|
+
});
|
|
2055
|
+
this.app.overlay = popup;
|
|
2056
|
+
this.app.focus(popup.input);
|
|
2007
2057
|
this.app.redraw();
|
|
2008
2058
|
}
|
|
2009
2059
|
#activateItem() {
|
|
@@ -2206,20 +2256,14 @@ export class ModelPanel extends Widget {
|
|
|
2206
2256
|
}
|
|
2207
2257
|
onKey(ev) {
|
|
2208
2258
|
if (ev.type !== "key") return false;
|
|
2209
|
-
if (this.editing) {
|
|
2210
|
-
if (ev.name === "escape") { this.editing = null; this.#rebuild(); return true; }
|
|
2211
|
-
if (ev.name === "enter") { this.#commitEdit(); return true; }
|
|
2212
|
-
const handled = this.input.onKey(ev);
|
|
2213
|
-
if (handled) this.app.redraw();
|
|
2214
|
-
return true;
|
|
2215
|
-
}
|
|
2216
2259
|
if (this.scanMode) {
|
|
2217
2260
|
if (ev.name === "escape") { this.scanMode = false; this.#rebuild(); return true; }
|
|
2218
|
-
if (ev.name === "up") { this.scanCursor = Math.max(0, this.scanCursor - 1); this.app.redraw(); return true; }
|
|
2219
|
-
if (ev.name === "down") { this.scanCursor = Math.min(this.scanItems.length - 1, this.scanCursor + 1); this.app.redraw(); return true; }
|
|
2261
|
+
if (ev.name === "up") { this.scanCursor = Math.max(0, this.scanCursor - 1); this.#rebuild(); this.app.redraw(); return true; }
|
|
2262
|
+
if (ev.name === "down") { this.scanCursor = Math.min(this.scanItems.length - 1, this.scanCursor + 1); this.#rebuild(); this.app.redraw(); return true; }
|
|
2220
2263
|
if (ev.name === "char" && ev.key === " " && !ev.ctrl) {
|
|
2221
2264
|
const m = this.scanItems[this.scanCursor];
|
|
2222
2265
|
if (m) { if (this.scanSel.has(m.id)) this.scanSel.delete(m.id); else this.scanSel.add(m.id); }
|
|
2266
|
+
this.#rebuild();
|
|
2223
2267
|
this.app.redraw();
|
|
2224
2268
|
return true;
|
|
2225
2269
|
}
|
|
@@ -2231,18 +2275,20 @@ export class ModelPanel extends Widget {
|
|
|
2231
2275
|
if (ev.name === "escape") { this.sub = null; this.#rebuild(); return true; }
|
|
2232
2276
|
if (ev.name === "up" || (ev.name === "char" && ev.key === "k" && !ev.ctrl)) {
|
|
2233
2277
|
this.sub.cursor = Math.max(0, this.sub.cursor - 1);
|
|
2278
|
+
this.#rebuild();
|
|
2234
2279
|
this.app.redraw();
|
|
2235
2280
|
return true;
|
|
2236
2281
|
}
|
|
2237
2282
|
if (ev.name === "down" || (ev.name === "char" && ev.key === "j" && !ev.ctrl)) {
|
|
2238
2283
|
this.sub.cursor = Math.min(Math.max(0, this.#subItems().length - 1), this.sub.cursor + 1);
|
|
2284
|
+
this.#rebuild();
|
|
2239
2285
|
this.app.redraw();
|
|
2240
2286
|
return true;
|
|
2241
2287
|
}
|
|
2242
2288
|
if (ev.name === "enter") { this.#activateItem(); return true; }
|
|
2243
2289
|
return false;
|
|
2244
2290
|
}
|
|
2245
|
-
if (ev.name === "escape") {
|
|
2291
|
+
if (ev.name === "escape") { return false; } // App falls back to chat mode
|
|
2246
2292
|
// dual-focus navigation: ↑/↓ move the cursor INSIDE the focused region —
|
|
2247
2293
|
// the provider column in list focus, the option rows in form focus.
|
|
2248
2294
|
// → enters the form, ← returns to the list.
|
|
@@ -2277,7 +2323,6 @@ export class ModelPanel extends Widget {
|
|
|
2277
2323
|
if (ev.kind === "wheel-up") { this.formView.scroll(-3); return true; }
|
|
2278
2324
|
if (ev.kind === "wheel-down") { this.formView.scroll(3); return true; }
|
|
2279
2325
|
if (ev.kind !== "press" || ev.button !== 0) return false;
|
|
2280
|
-
if (this.editing && this.input.inside(ev.x, ev.y)) return this.input.onMouse(ev);
|
|
2281
2326
|
if (ev.x < this.x + 26) {
|
|
2282
2327
|
const idx = ev.y - this.listView.y + this.listView.scrollY;
|
|
2283
2328
|
// one click = select AND open (same as Enter)
|