mocode-ai 0.4.4 → 0.4.6
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 +219 -209
- package/README.zh-CN.md +10 -0
- package/dist/agent/index.js +30 -13
- package/dist/config/index.js +13 -11
- package/dist/config/presets.js +177 -0
- package/dist/llm/capabilities.js +4 -0
- package/dist/llm/index.js +5 -1
- package/dist/memory/reflect.js +5 -2
- package/dist/repl/index.js +268 -10
- package/dist/tools/builtins/ask-human.js +71 -43
- package/dist/tools/builtins/codegraph.js +1 -1
- package/dist/ui/batch.js +235 -0
- package/dist/ui/content.js +51 -0
- package/dist/ui/diff.js +41 -4
- package/dist/ui/intervention.js +59 -20
- package/dist/ui/layout.js +449 -66
- package/dist/ui/prompt.js +99 -5
- package/dist/ui/render.js +22 -0
- package/dist/ui/theme.js +22 -0
- package/package.json +1 -1
package/dist/ui/prompt.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import readline from 'node:readline';
|
|
2
2
|
import { stdin, stdout } from 'node:process';
|
|
3
3
|
import { ui } from './theme.js';
|
|
4
|
-
import { displayWidth, padEndDisplay, truncateDisplay } from './render.js';
|
|
4
|
+
import { displayWidth, padEndDisplay, truncateDisplay, visColToCharCol, wrapByDisplayWidth } from './render.js';
|
|
5
5
|
import * as layout from './layout.js';
|
|
6
6
|
import * as mouse from './mouse.js';
|
|
7
7
|
// ── 粘贴检测(块级 + 时间窗)──
|
|
@@ -249,8 +249,105 @@ export async function promptWithSlashMenu(opts) {
|
|
|
249
249
|
menu: menuLines().length ? { lines: menuLines() } : null,
|
|
250
250
|
});
|
|
251
251
|
}
|
|
252
|
+
/** 鼠标点击输入框:layout 已把"屏 tap 位置"以 (flatIdx, inSegVis) 形式传进来
|
|
253
|
+
* (flatIdx = flat 中的绝对段号;inSegVis = 段内显示列)。
|
|
254
|
+
* 本函数在 prompt 自己的 (lines + chip) 上复刻 wrap/flat 还原 (cl, cc)→ redraw。
|
|
255
|
+
* chip 模式自动扣 chip prefix:
|
|
256
|
+
* - dispLines() 把 chipPre 末行 + chipPrefix + suffix[0] 拼成一行 "mergedRow"
|
|
257
|
+
* - flat 中对应的"合并行段"是 mergedRow 的折行段,段内 inChar 累加从 mergedRow 起
|
|
258
|
+
* - 要得到 lines[cl=0] 内的字符索引,扣掉 mergedRow 起始到 lines[0] 起始的字符数
|
|
259
|
+
* = chipPre 末行字符数 + chipPrefix 字符数。 */
|
|
260
|
+
function applyExternalCursor(flatIdx, inSegVis) {
|
|
261
|
+
const g = layout.getGeo();
|
|
262
|
+
const promptW = displayWidth(opts.prompt);
|
|
263
|
+
const W = Math.max(1, g.cols - promptW);
|
|
264
|
+
const dispLs = dispLines();
|
|
265
|
+
const lineVis = dispLs.map((l) => wrapByDisplayWidth(l, W));
|
|
266
|
+
const flat = [];
|
|
267
|
+
for (const lv of lineVis)
|
|
268
|
+
for (const r of lv)
|
|
269
|
+
flat.push(r);
|
|
270
|
+
let newCl;
|
|
271
|
+
let newCc;
|
|
272
|
+
if (flat.length === 0) {
|
|
273
|
+
newCl = 0;
|
|
274
|
+
newCc = 0;
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
const safeIdx = Math.max(0, Math.min(flatIdx, flat.length - 1));
|
|
278
|
+
const seg = flat[safeIdx];
|
|
279
|
+
const segW = displayWidth(seg);
|
|
280
|
+
const safeInSeg = Math.max(0, Math.min(inSegVis, segW));
|
|
281
|
+
const inChar = visColToCharCol(seg, safeInSeg);
|
|
282
|
+
// safeIdx → (dispLine, segInLine)
|
|
283
|
+
let dispLine = 0;
|
|
284
|
+
let segInLine = safeIdx;
|
|
285
|
+
while (dispLine < lineVis.length && segInLine >= lineVis[dispLine].length) {
|
|
286
|
+
segInLine -= lineVis[dispLine].length;
|
|
287
|
+
dispLine++;
|
|
288
|
+
}
|
|
289
|
+
if (dispLine >= lineVis.length) {
|
|
290
|
+
dispLine = lineVis.length - 1;
|
|
291
|
+
segInLine = lineVis[dispLine].length - 1;
|
|
292
|
+
}
|
|
293
|
+
// dispLine → cl(chip-aware)
|
|
294
|
+
if (chip) {
|
|
295
|
+
const preLen = chipPreLines().length;
|
|
296
|
+
if (dispLine < preLen - 1) {
|
|
297
|
+
// 落在 chipPre 内(非末行)→ 走出 chip 到 suffix 起点
|
|
298
|
+
newCl = 0;
|
|
299
|
+
}
|
|
300
|
+
else if (dispLine === preLen - 1) {
|
|
301
|
+
// 合并行 = suffix 第 0 行(cl=0)
|
|
302
|
+
newCl = 0;
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
newCl = dispLine - (preLen - 1);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
newCl = dispLine;
|
|
310
|
+
}
|
|
311
|
+
newCl = Math.max(0, Math.min(newCl, lines.length - 1));
|
|
312
|
+
const lineText = lines[newCl] ?? '';
|
|
313
|
+
// cc 必须在 lines[newCl] 内。flat 中 seg 是 dispLs[dispLine] 的某折行段,
|
|
314
|
+
// seg 内 inChar 是段内字符偏移,但 lines[newCl] 的字符是按 (dispLs 前段累加) + (本段 inChar) 算的。
|
|
315
|
+
// ——非 chip 模式下:dispLine = newCl, segStartChars = sum(lineVis[dispLine][0..segInLine-1].length)
|
|
316
|
+
// ccInLine = segStartChars + inChar
|
|
317
|
+
// ——chip 模式下 newCl=0(合并行):dispLine 是合并行 dispLine,segStartChars 同上,
|
|
318
|
+
// 但 lines[0] 在合并行中"起始处"在 (mergedRow.length - lines[0].length) 字符处,
|
|
319
|
+
// 故 ccInLine = segStartChars + inChar - chipOverheadChars。
|
|
320
|
+
let segStartChars = 0;
|
|
321
|
+
const segs = lineVis[dispLine];
|
|
322
|
+
for (let s = 0; s < segInLine; s++)
|
|
323
|
+
segStartChars += segs[s]?.length ?? 0;
|
|
324
|
+
let ccInLine = segStartChars + inChar;
|
|
325
|
+
if (chip && newCl === 0) {
|
|
326
|
+
const pre = chipPreLines();
|
|
327
|
+
const mergedRow = pre[pre.length - 1] ?? '';
|
|
328
|
+
const prefix = chipPrefix();
|
|
329
|
+
// mergedRow 起始到 lines[0] 起始的字符数 = (chipPreLast + chipPrefix) 字符数
|
|
330
|
+
const chipOverheadChars = mergedRow.length + prefix.length - lineText.length;
|
|
331
|
+
ccInLine = ccInLine - chipOverheadChars;
|
|
332
|
+
}
|
|
333
|
+
newCc = Math.max(0, Math.min(ccInLine, lineText.length));
|
|
334
|
+
}
|
|
335
|
+
cl = newCl;
|
|
336
|
+
cc = newCc;
|
|
337
|
+
// 收起菜单/过滤态:点击输入框关闭菜单,回到纯文本编辑。
|
|
338
|
+
if (menuOpen) {
|
|
339
|
+
menuOpen = false;
|
|
340
|
+
filtered = [];
|
|
341
|
+
selected = 0;
|
|
342
|
+
menuTop = 0;
|
|
343
|
+
}
|
|
344
|
+
justSawCR = false;
|
|
345
|
+
computeFiltered();
|
|
346
|
+
redraw();
|
|
347
|
+
}
|
|
252
348
|
function cleanup() {
|
|
253
349
|
layout.setPasteHandler(null);
|
|
350
|
+
layout.setCursorChangeHandler(null);
|
|
254
351
|
try {
|
|
255
352
|
stdin.setRawMode(false);
|
|
256
353
|
}
|
|
@@ -566,6 +663,7 @@ export async function promptWithSlashMenu(opts) {
|
|
|
566
663
|
return;
|
|
567
664
|
}
|
|
568
665
|
layout.setPasteHandler(onMousePaste); // 鼠标右键单击输入框(未拖动)→ 读剪贴板贴入;cleanup 时注销
|
|
666
|
+
layout.setCursorChangeHandler(applyExternalCursor); // 鼠标左键单击输入框(未拖动)→ 改 cl/cc 到点击位;cleanup 时注销
|
|
569
667
|
stdin.resume();
|
|
570
668
|
emitter.on('keypress', onKey);
|
|
571
669
|
computeFiltered();
|
|
@@ -615,7 +713,6 @@ export async function promptTurnPicker(items) {
|
|
|
615
713
|
cursorLine: 0,
|
|
616
714
|
cursorCol: displayWidth(hint),
|
|
617
715
|
menu: { lines: menuLines() },
|
|
618
|
-
caret: false, // 纯导航菜单(非文本输入):不画输入框块状光标,聚焦由菜单 ▸ 标记
|
|
619
716
|
});
|
|
620
717
|
}
|
|
621
718
|
function cleanup() {
|
|
@@ -756,7 +853,6 @@ export async function promptSessionPicker(items, recentCap = 10) {
|
|
|
756
853
|
cursorLine: 0,
|
|
757
854
|
cursorCol: displayWidth(h),
|
|
758
855
|
menu: { lines: menuLines() },
|
|
759
|
-
caret: false, // 纯导航菜单(非文本输入):不画输入框块状光标,聚焦由菜单 ▸ 标记
|
|
760
856
|
});
|
|
761
857
|
}
|
|
762
858
|
function cleanup() {
|
|
@@ -894,7 +990,6 @@ export async function promptThemePicker(items) {
|
|
|
894
990
|
cursorLine: 0,
|
|
895
991
|
cursorCol: displayWidth(hint),
|
|
896
992
|
menu: { lines: menuLines() },
|
|
897
|
-
caret: false, // 纯导航菜单:不画输入框块状光标,聚焦由菜单 ▸ 标记
|
|
898
993
|
});
|
|
899
994
|
}
|
|
900
995
|
function cleanup() {
|
|
@@ -1008,7 +1103,6 @@ export async function promptRevertChoice(fileCount) {
|
|
|
1008
1103
|
cursorLine: 0,
|
|
1009
1104
|
cursorCol: displayWidth(hint),
|
|
1010
1105
|
menu: { lines: menuLines() },
|
|
1011
|
-
caret: false, // 纯导航菜单:不画输入框块状光标,聚焦由菜单 ▸ 标记
|
|
1012
1106
|
});
|
|
1013
1107
|
}
|
|
1014
1108
|
function cleanup() {
|
package/dist/ui/render.js
CHANGED
|
@@ -99,6 +99,28 @@ export function sliceByDisplayCol(str, start, end) {
|
|
|
99
99
|
}
|
|
100
100
|
return out;
|
|
101
101
|
}
|
|
102
|
+
/** 把可视列(0-based)反推为 str 内的字符偏移(JS 字符串下标,UTF-16 code unit)。
|
|
103
|
+
* 点击落在宽字符的"列中段"时归到该字符之前的偏移(停在它左侧边界)——与终端光标行为一致。
|
|
104
|
+
* visCol 大于串的显示宽度时返回 str.length(点行末的"右边")。
|
|
105
|
+
* 输入框点击定位专用;与 sliceByDisplayCol 互为逆,但语义只关心单点、不关心区间。 */
|
|
106
|
+
export function visColToCharCol(str, visCol) {
|
|
107
|
+
if (visCol <= 0)
|
|
108
|
+
return 0;
|
|
109
|
+
let w = 0;
|
|
110
|
+
let i = 0;
|
|
111
|
+
for (const ch of str) {
|
|
112
|
+
const cw = charWidth(ch.codePointAt(0) ?? 0);
|
|
113
|
+
if (cw <= 0) {
|
|
114
|
+
i += ch.length;
|
|
115
|
+
continue;
|
|
116
|
+
} // 零宽(组合符):不占列
|
|
117
|
+
if (w + cw > visCol)
|
|
118
|
+
break; // 落在该字符显示区间内 → 停在其左侧
|
|
119
|
+
w += cw;
|
|
120
|
+
i += ch.length;
|
|
121
|
+
}
|
|
122
|
+
return i;
|
|
123
|
+
}
|
|
102
124
|
/** 带色串的可见显示宽度(先去 ANSI 再按 displayWidth 度量)。 */
|
|
103
125
|
export function ansiDisplayWidth(s) {
|
|
104
126
|
return displayWidth(stripAnsi(s));
|
package/dist/ui/theme.js
CHANGED
|
@@ -29,6 +29,9 @@ const DEFAULT = {
|
|
|
29
29
|
brightCyan: '\x1B[38;2;86;182;194m',
|
|
30
30
|
brightMagenta: '\x1B[38;2;198;120;221m',
|
|
31
31
|
userBg: '\x1B[48;2;44;49;60m', // One Dark current-line bg,深色终端上微妙可辨
|
|
32
|
+
// diff 行底色:One Dark bg #282c34 上加 ~14% 亮度的对应色,够辨识但不刺眼
|
|
33
|
+
addBg: '\x1B[48;2;44;62;42m', // 偏暗绿(One Dark green(152,195,121)暗化)
|
|
34
|
+
delBg: '\x1B[48;2;62;38;42m', // 偏暗红(One Dark red(224,108,117)暗化)
|
|
32
35
|
};
|
|
33
36
|
/**
|
|
34
37
|
* 内建主题表。default=One Dark、light=Solarized Light、solarized=Solarized Dark、
|
|
@@ -49,6 +52,10 @@ const THEMES = {
|
|
|
49
52
|
brightCyan: '\x1B[38;2;42;161;152m',
|
|
50
53
|
brightMagenta: '\x1B[38;2;108;113;196m',
|
|
51
54
|
userBg: '\x1B[48;2;238;232;213m',
|
|
55
|
+
// diff 行底色:Solarized Light base2(238,232,213)上贴同色族浅 tint,
|
|
56
|
+
// 比直接用 base1 更柔,跟深 fg(red/green)对比充足
|
|
57
|
+
addBg: '\x1B[48;2;220;235;205m',
|
|
58
|
+
delBg: '\x1B[48;2;245;218;215m',
|
|
52
59
|
},
|
|
53
60
|
solarized: {
|
|
54
61
|
red: '\x1B[38;2;220;50;47m',
|
|
@@ -61,6 +68,9 @@ const THEMES = {
|
|
|
61
68
|
brightCyan: '\x1B[38;2;147;161;161m',
|
|
62
69
|
brightMagenta: '\x1B[38;2;108;113;196m',
|
|
63
70
|
userBg: '\x1B[48;2;7;54;66m',
|
|
71
|
+
// diff 行底色:Solarized Dark base03(0,43,54)上加对应色族暗 tint
|
|
72
|
+
addBg: '\x1B[48;2;20;50;38m',
|
|
73
|
+
delBg: '\x1B[48;2;55;30;30m',
|
|
64
74
|
},
|
|
65
75
|
gruvbox: {
|
|
66
76
|
red: '\x1B[38;2;251;73;52m',
|
|
@@ -73,6 +83,9 @@ const THEMES = {
|
|
|
73
83
|
brightCyan: '\x1B[38;2;142;192;124m',
|
|
74
84
|
brightMagenta: '\x1B[38;2;211;134;155m',
|
|
75
85
|
userBg: '\x1B[48;2;40;40;40m',
|
|
86
|
+
// diff 行底色:Gruvbox dark bg(40,40,40)上贴暗 bg0_a 风格
|
|
87
|
+
addBg: '\x1B[48;2;40;55;30m',
|
|
88
|
+
delBg: '\x1B[48;2;70;35;30m',
|
|
76
89
|
},
|
|
77
90
|
nord: {
|
|
78
91
|
red: '\x1B[38;2;191;97;106m',
|
|
@@ -85,6 +98,9 @@ const THEMES = {
|
|
|
85
98
|
brightCyan: '\x1B[38;2;136;192;208m',
|
|
86
99
|
brightMagenta: '\x1B[38;2;180;142;173m',
|
|
87
100
|
userBg: '\x1B[48;2;67;76;94m',
|
|
101
|
+
// diff 行底色:Nord polar night(46,52,64)上贴对应色族暗 tint
|
|
102
|
+
addBg: '\x1B[48;2;46;66;52m',
|
|
103
|
+
delBg: '\x1B[48;2;72;46;52m',
|
|
88
104
|
},
|
|
89
105
|
};
|
|
90
106
|
let currentName = 'default';
|
|
@@ -155,4 +171,10 @@ export const ui = {
|
|
|
155
171
|
get userBg() {
|
|
156
172
|
return color('userBg');
|
|
157
173
|
},
|
|
174
|
+
get addBg() {
|
|
175
|
+
return color('addBg');
|
|
176
|
+
},
|
|
177
|
+
get delBg() {
|
|
178
|
+
return color('delBg');
|
|
179
|
+
},
|
|
158
180
|
};
|