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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/panels.js +74 -29
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-neotui",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "description": "Neo-TUI: mouse-driven terminal UI client for DeepSeek Harness (B-tier per dsh-tui-design.md)",
5
5
  "type": "module",
6
6
  "bin": {
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 - 3, showScrollbar: true });
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 - 3;
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
- this.editing = { label, commit };
1998
- this.input.setValue(value ?? "", { select: true });
1999
- this.app.redraw();
2000
- }
2001
- #commitEdit() {
2002
- if (!this.editing) return;
2003
- const { label, commit } = this.editing;
2004
- this.editing = null;
2005
- commit(this.input.value);
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") { this.editing = null; return false; } // App falls back to chat mode
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)