@ridit/lens 0.3.6 → 0.3.8
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/CLAUDE.md +50 -0
- package/dist/index.mjs +1967 -1396
- package/package.json +1 -1
- package/src/commands/chat.tsx +14 -20
- package/src/components/chat/ChatMessage.tsx +46 -4
- package/src/components/chat/ChatOverlays.tsx +27 -22
- package/src/components/chat/ChatRunner.tsx +55 -15
- package/src/components/chat/TextArea.tsx +177 -0
- package/src/components/chat/hooks/useChat.ts +417 -226
- package/src/components/chat/hooks/useCommandHandlers.ts +0 -4
- package/src/components/repo/RepoAnalysis.tsx +5 -5
- package/src/components/task/TaskRunner.tsx +3 -3
- package/src/components/timeline/TimelineRunner.tsx +2 -2
- package/src/components/watch/RunRunner.tsx +2 -1
- package/src/index.tsx +13 -2
- package/src/prompts/fewshot.ts +18 -0
- package/src/prompts/system.ts +30 -17
- package/src/types/chat.ts +3 -1
- package/src/utils/chat.ts +73 -8
- package/src/utils/intentClassifier.ts +0 -15
- package/src/utils/memory.ts +103 -26
- package/src/utils/thinking.tsx +64 -0
- package/src/utils/tools/builtins.ts +47 -6
package/package.json
CHANGED
package/src/commands/chat.tsx
CHANGED
|
@@ -1,23 +1,17 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Box
|
|
3
|
-
import figures from "figures";
|
|
4
|
-
import { existsSync } from "fs";
|
|
5
|
-
import path from "path";
|
|
2
|
+
import { Box } from "ink";
|
|
6
3
|
import { ChatRunner } from "../components/chat/ChatRunner";
|
|
7
|
-
import { ACCENT } from "../colors";
|
|
8
4
|
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return <ChatRunner repoPath={resolvedPath} />;
|
|
23
|
-
};
|
|
5
|
+
export function ChatCommand({
|
|
6
|
+
path,
|
|
7
|
+
autoForce = false,
|
|
8
|
+
}: {
|
|
9
|
+
path: string;
|
|
10
|
+
autoForce?: boolean;
|
|
11
|
+
}) {
|
|
12
|
+
return (
|
|
13
|
+
<Box flexDirection="column">
|
|
14
|
+
<ChatRunner repoPath={path} autoForce={autoForce} />
|
|
15
|
+
</Box>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
+
import figures from "figures";
|
|
3
4
|
import { ACCENT, GREEN, RED } from "../../colors";
|
|
4
5
|
import type { Message } from "../../types/chat";
|
|
6
|
+
import type { DiffLine } from "../repo/DiffViewer";
|
|
5
7
|
|
|
6
8
|
function InlineText({ text }: { text: string }) {
|
|
7
9
|
const parts = text.split(/(`[^`]+`|\*\*[^*]+\*\*)/g);
|
|
@@ -170,10 +172,12 @@ export function StaticMessage({ msg }: { msg: Message }) {
|
|
|
170
172
|
if (msg.type === "plan") {
|
|
171
173
|
return (
|
|
172
174
|
<Box flexDirection="column" marginBottom={1}>
|
|
173
|
-
|
|
174
|
-
<
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
{msg.content ? (
|
|
176
|
+
<Box gap={1}>
|
|
177
|
+
<Text color={ACCENT}>*</Text>
|
|
178
|
+
<MessageBody content={msg.content} />
|
|
179
|
+
</Box>
|
|
180
|
+
) : null}
|
|
177
181
|
<Box marginLeft={2} gap={1}>
|
|
178
182
|
<Text color={msg.applied ? GREEN : "gray"}>
|
|
179
183
|
{msg.applied ? "✓" : "·"}
|
|
@@ -182,6 +186,44 @@ export function StaticMessage({ msg }: { msg: Message }) {
|
|
|
182
186
|
{msg.applied ? "changes applied" : "changes skipped"}
|
|
183
187
|
</Text>
|
|
184
188
|
</Box>
|
|
189
|
+
{msg.applied && msg.diffLines && msg.diffLines.length > 0 && (
|
|
190
|
+
<Box flexDirection="column" marginLeft={2} marginTop={0}>
|
|
191
|
+
{msg.patches.map((patch, fi) => (
|
|
192
|
+
<Box key={patch.path} flexDirection="column">
|
|
193
|
+
<Text bold color={fi % 2 === 0 ? "cyan" : "magenta"}>
|
|
194
|
+
{figures.bullet} {patch.path}
|
|
195
|
+
{patch.isNew ? " (new)" : ""}
|
|
196
|
+
</Text>
|
|
197
|
+
{(msg.diffLines![fi] ?? []).map((line: DiffLine, li: number) => {
|
|
198
|
+
const prefix =
|
|
199
|
+
line.type === "added"
|
|
200
|
+
? "+"
|
|
201
|
+
: line.type === "removed"
|
|
202
|
+
? "-"
|
|
203
|
+
: " ";
|
|
204
|
+
const color =
|
|
205
|
+
line.type === "added"
|
|
206
|
+
? "green"
|
|
207
|
+
: line.type === "removed"
|
|
208
|
+
? "red"
|
|
209
|
+
: "gray";
|
|
210
|
+
const lineNumStr =
|
|
211
|
+
line.lineNum === -1
|
|
212
|
+
? " "
|
|
213
|
+
: String(line.lineNum).padStart(3, " ");
|
|
214
|
+
return (
|
|
215
|
+
<Box key={li}>
|
|
216
|
+
<Text color="gray">{lineNumStr} </Text>
|
|
217
|
+
<Text color={color}>
|
|
218
|
+
{prefix} {line.content}
|
|
219
|
+
</Text>
|
|
220
|
+
</Box>
|
|
221
|
+
);
|
|
222
|
+
})}
|
|
223
|
+
</Box>
|
|
224
|
+
))}
|
|
225
|
+
</Box>
|
|
226
|
+
)}
|
|
185
227
|
</Box>
|
|
186
228
|
);
|
|
187
229
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Box, Static, Text } from "ink";
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { Box, Static, Text, useStdout } from "ink";
|
|
3
3
|
import Spinner from "ink-spinner";
|
|
4
|
-
import
|
|
4
|
+
import { TextArea } from "./TextArea";
|
|
5
5
|
import { ACCENT, GREEN, RED } from "../../colors";
|
|
6
6
|
import { DiffViewer } from "../repo/DiffViewer";
|
|
7
7
|
import { StaticMessage } from "./ChatMessage";
|
|
@@ -24,11 +24,6 @@ function Hint({ text }: { text: string }) {
|
|
|
24
24
|
);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
// ── PermissionPrompt ──────────────────────────────────────────────────────────
|
|
28
|
-
//
|
|
29
|
-
// Works with both the old explicit ToolCall union and the new generic
|
|
30
|
-
// { type, _label, _display } shape produced by the plugin system.
|
|
31
|
-
|
|
32
27
|
export function PermissionPrompt({
|
|
33
28
|
tool,
|
|
34
29
|
onDecide,
|
|
@@ -40,7 +35,6 @@ export function PermissionPrompt({
|
|
|
40
35
|
let label: string;
|
|
41
36
|
let value: string;
|
|
42
37
|
|
|
43
|
-
// Generic plugin tool shape
|
|
44
38
|
if ("_label" in tool) {
|
|
45
39
|
const iconMap: Record<string, string> = {
|
|
46
40
|
run: "$",
|
|
@@ -61,7 +55,6 @@ export function PermissionPrompt({
|
|
|
61
55
|
label = tool._label;
|
|
62
56
|
value = tool._display;
|
|
63
57
|
} else {
|
|
64
|
-
// Legacy explicit ToolCall union
|
|
65
58
|
if (tool.type === "shell") {
|
|
66
59
|
icon = "$";
|
|
67
60
|
label = "run";
|
|
@@ -134,25 +127,37 @@ export function InputBox({
|
|
|
134
127
|
onSubmit: (v: string) => void;
|
|
135
128
|
inputKey?: number;
|
|
136
129
|
}) {
|
|
130
|
+
const { stdout } = useStdout();
|
|
131
|
+
const [cols, setCols] = useState(stdout?.columns ?? 80);
|
|
132
|
+
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
const handler = () => setCols(stdout?.columns ?? 80);
|
|
135
|
+
stdout?.on("resize", handler);
|
|
136
|
+
return () => {
|
|
137
|
+
stdout?.off("resize", handler);
|
|
138
|
+
};
|
|
139
|
+
}, [stdout]);
|
|
140
|
+
|
|
141
|
+
const rule = "─".repeat(Math.max(1, cols));
|
|
142
|
+
|
|
137
143
|
return (
|
|
138
|
-
<Box
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
borderRight={false}
|
|
143
|
-
borderLeft={false}
|
|
144
|
-
borderColor={"gray"}
|
|
145
|
-
borderStyle="single"
|
|
146
|
-
>
|
|
144
|
+
<Box flexDirection="column" marginTop={1}>
|
|
145
|
+
<Text color="gray" dimColor>
|
|
146
|
+
{rule}
|
|
147
|
+
</Text>
|
|
147
148
|
<Box gap={1}>
|
|
148
149
|
<Text color={ACCENT}>{">"}</Text>
|
|
149
|
-
<
|
|
150
|
+
<TextArea
|
|
150
151
|
key={inputKey}
|
|
151
152
|
value={value}
|
|
152
153
|
onChange={onChange}
|
|
153
154
|
onSubmit={onSubmit}
|
|
155
|
+
placeholder="ask anything..."
|
|
154
156
|
/>
|
|
155
157
|
</Box>
|
|
158
|
+
<Text color="gray" dimColor>
|
|
159
|
+
{rule}
|
|
160
|
+
</Text>
|
|
156
161
|
</Box>
|
|
157
162
|
);
|
|
158
163
|
}
|
|
@@ -196,7 +201,7 @@ export function ShortcutBar({
|
|
|
196
201
|
return (
|
|
197
202
|
<Box gap={3} marginTop={0}>
|
|
198
203
|
<Text color="gray" dimColor>
|
|
199
|
-
enter send · ^
|
|
204
|
+
enter send · ctrl+enter newline · ctrl+del del word · ^f force · ^c exit
|
|
200
205
|
</Text>
|
|
201
206
|
{forceApprove ? (
|
|
202
207
|
<Text color={RED}>⚡⚡ force-all</Text>
|
|
@@ -244,7 +249,7 @@ export function CloningView({
|
|
|
244
249
|
<History committed={committed} />
|
|
245
250
|
<Box gap={1} marginTop={1}>
|
|
246
251
|
<Text color={ACCENT}>
|
|
247
|
-
<Spinner />
|
|
252
|
+
<Spinner type="line" />
|
|
248
253
|
</Text>
|
|
249
254
|
<Text color="gray">cloning </Text>
|
|
250
255
|
<Text color={ACCENT}>{stage.repoUrl}</Text>
|
|
@@ -4,11 +4,11 @@ import Spinner from "ink-spinner";
|
|
|
4
4
|
import { useState } from "react";
|
|
5
5
|
import path from "path";
|
|
6
6
|
import os from "os";
|
|
7
|
-
import
|
|
7
|
+
import { TextArea } from "./TextArea";
|
|
8
8
|
import { ACCENT } from "../../colors";
|
|
9
9
|
import { ProviderPicker } from "../provider/ProviderPicker";
|
|
10
10
|
import { startCloneRepo } from "../../utils/repo";
|
|
11
|
-
import { useThinkingPhrase } from "../../utils/thinking";
|
|
11
|
+
import { useThinkingPhrase, useThinkingTip, useThinkingTimer } from "../../utils/thinking";
|
|
12
12
|
import { walkDir, applyPatches, toCloneUrl } from "../../utils/chat";
|
|
13
13
|
import { appendMemory } from "../../utils/memory";
|
|
14
14
|
import { getChatNameSuggestions, saveChat } from "../../utils/chatHistory";
|
|
@@ -136,7 +136,7 @@ function ForceAllWarning({
|
|
|
136
136
|
esc
|
|
137
137
|
</Text>
|
|
138
138
|
<Text color="gray"> to cancel: </Text>
|
|
139
|
-
<
|
|
139
|
+
<TextArea
|
|
140
140
|
value={input}
|
|
141
141
|
onChange={setInput}
|
|
142
142
|
onSubmit={(v) => onConfirm(v.trim().toLowerCase() === "yes")}
|
|
@@ -147,9 +147,18 @@ function ForceAllWarning({
|
|
|
147
147
|
);
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
export const ChatRunner = ({
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
export const ChatRunner = ({
|
|
151
|
+
repoPath,
|
|
152
|
+
autoForce = false,
|
|
153
|
+
}: {
|
|
154
|
+
repoPath: string;
|
|
155
|
+
autoForce?: boolean;
|
|
156
|
+
}) => {
|
|
157
|
+
const chat = useChat(repoPath, autoForce);
|
|
158
|
+
const isThinking = chat.stage.type === "thinking";
|
|
159
|
+
const thinkingPhrase = useThinkingPhrase(isThinking);
|
|
160
|
+
const thinkingTip = useThinkingTip(isThinking);
|
|
161
|
+
const thinkingTimer = useThinkingTimer(isThinking);
|
|
153
162
|
|
|
154
163
|
const handleStageKey = (input: string, key: any) => {
|
|
155
164
|
const { stage } = chat;
|
|
@@ -321,10 +330,14 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
321
330
|
if (msg?.type === "plan") {
|
|
322
331
|
chat.applyPatchesAndContinue(msg.patches);
|
|
323
332
|
const applied: Message = { ...msg, applied: true };
|
|
324
|
-
chat.
|
|
325
|
-
|
|
333
|
+
const updatedAll = chat.allMessages.map((m, i) =>
|
|
334
|
+
i === chat.pendingMsgIndex ? applied : m,
|
|
326
335
|
);
|
|
336
|
+
chat.setAllMessages(updatedAll);
|
|
327
337
|
chat.setCommitted((prev) => [...prev, applied]);
|
|
338
|
+
chat.setPendingMsgIndex(null);
|
|
339
|
+
chat.continueAfterChanges(updatedAll, msg.content || "code changes");
|
|
340
|
+
return;
|
|
328
341
|
}
|
|
329
342
|
}
|
|
330
343
|
chat.setPendingMsgIndex(null);
|
|
@@ -352,6 +365,26 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
352
365
|
}
|
|
353
366
|
};
|
|
354
367
|
|
|
368
|
+
useInput(
|
|
369
|
+
(input, key) => {
|
|
370
|
+
if (!(key.ctrl && input === "f")) return;
|
|
371
|
+
if (chat.forceApprove) {
|
|
372
|
+
chat.setForceApprove(false);
|
|
373
|
+
chat.setAutoApprove(false);
|
|
374
|
+
const msg: Message = {
|
|
375
|
+
role: "assistant",
|
|
376
|
+
content: "Force-all mode OFF — tools will ask for permission again.",
|
|
377
|
+
type: "text",
|
|
378
|
+
};
|
|
379
|
+
chat.setCommitted((prev) => [...prev, msg]);
|
|
380
|
+
chat.setAllMessages((prev: Message[]) => [...prev, msg]);
|
|
381
|
+
} else {
|
|
382
|
+
chat.setShowForceWarning(true);
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
{ isActive: chat.stage.type === "idle" },
|
|
386
|
+
);
|
|
387
|
+
|
|
355
388
|
const chatInput = useChatInput(
|
|
356
389
|
chat.stage,
|
|
357
390
|
chat.showTimeline,
|
|
@@ -408,7 +441,7 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
408
441
|
<Box gap={1} marginTop={1}>
|
|
409
442
|
<Text color={ACCENT}>*</Text>
|
|
410
443
|
<Text color={ACCENT}>
|
|
411
|
-
<Spinner />
|
|
444
|
+
<Spinner type="arc" />
|
|
412
445
|
</Text>
|
|
413
446
|
<Text color="gray" dimColor>
|
|
414
447
|
indexing codebase…
|
|
@@ -476,12 +509,19 @@ export const ChatRunner = ({ repoPath }: { repoPath: string }) => {
|
|
|
476
509
|
)}
|
|
477
510
|
|
|
478
511
|
{!chat.showForceWarning && stage.type === "thinking" && (
|
|
479
|
-
<Box
|
|
480
|
-
<
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
512
|
+
<Box flexDirection="column">
|
|
513
|
+
<Box gap={1}>
|
|
514
|
+
<Text color={ACCENT}>●</Text>
|
|
515
|
+
<TypewriterText text={thinkingPhrase} />
|
|
516
|
+
<Text color="gray" dimColor>
|
|
517
|
+
{thinkingTimer ? `· ${thinkingTimer} ` : ""}· esc cancel
|
|
518
|
+
</Text>
|
|
519
|
+
</Box>
|
|
520
|
+
<Box marginLeft={2}>
|
|
521
|
+
<Text color="gray" dimColor>
|
|
522
|
+
tip: {thinkingTip}
|
|
523
|
+
</Text>
|
|
524
|
+
</Box>
|
|
485
525
|
</Box>
|
|
486
526
|
)}
|
|
487
527
|
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { Text, useInput } from "ink";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
|
|
5
|
+
function isWordChar(ch: string): boolean {
|
|
6
|
+
return /[\w]/.test(ch);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function wordBoundaryLeft(text: string, pos: number): number {
|
|
10
|
+
if (pos <= 0) return 0;
|
|
11
|
+
let i = pos - 1;
|
|
12
|
+
|
|
13
|
+
while (i > 0 && !isWordChar(text[i]!)) i--;
|
|
14
|
+
|
|
15
|
+
while (i > 0 && isWordChar(text[i - 1]!)) i--;
|
|
16
|
+
return i;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function wordBoundaryRight(text: string, pos: number): number {
|
|
20
|
+
const len = text.length;
|
|
21
|
+
if (pos >= len) return len;
|
|
22
|
+
let i = pos;
|
|
23
|
+
|
|
24
|
+
while (i < len && isWordChar(text[i]!)) i++;
|
|
25
|
+
|
|
26
|
+
while (i < len && !isWordChar(text[i]!)) i++;
|
|
27
|
+
return i;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface TextAreaProps {
|
|
31
|
+
value: string;
|
|
32
|
+
onChange: (value: string) => void;
|
|
33
|
+
onSubmit: (value: string) => void;
|
|
34
|
+
focus?: boolean;
|
|
35
|
+
placeholder?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function TextArea({
|
|
39
|
+
value,
|
|
40
|
+
onChange,
|
|
41
|
+
onSubmit,
|
|
42
|
+
focus = true,
|
|
43
|
+
placeholder = "",
|
|
44
|
+
}: TextAreaProps) {
|
|
45
|
+
const [cursor, setCursor] = useState(value.length);
|
|
46
|
+
const [prevValue, setPrevValue] = useState(value);
|
|
47
|
+
|
|
48
|
+
if (value !== prevValue) {
|
|
49
|
+
setPrevValue(value);
|
|
50
|
+
const lenDiff = Math.abs(value.length - prevValue.length);
|
|
51
|
+
if (cursor > value.length || lenDiff > 1) {
|
|
52
|
+
setCursor(value.length);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
useInput(
|
|
57
|
+
(input, key) => {
|
|
58
|
+
if (key.upArrow || key.downArrow) return;
|
|
59
|
+
if (key.tab || (key.shift && key.tab)) return;
|
|
60
|
+
if (key.ctrl && input === "c") return;
|
|
61
|
+
|
|
62
|
+
const isShiftEnter =
|
|
63
|
+
(key.return && key.shift) ||
|
|
64
|
+
input === "\x1b[27;2;13~" ||
|
|
65
|
+
input === "\x1b[13;2u";
|
|
66
|
+
|
|
67
|
+
if (key.return && !key.meta && !key.shift && !isShiftEnter) {
|
|
68
|
+
onSubmit(value);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (isShiftEnter) {
|
|
73
|
+
const next = value.slice(0, cursor) + "\n" + value.slice(cursor);
|
|
74
|
+
onChange(next);
|
|
75
|
+
setCursor((c) => c + 1);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (key.leftArrow && key.ctrl) {
|
|
80
|
+
setCursor(wordBoundaryLeft(value, cursor));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (key.rightArrow && key.ctrl) {
|
|
85
|
+
setCursor(wordBoundaryRight(value, cursor));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (key.leftArrow) {
|
|
90
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (key.rightArrow) {
|
|
95
|
+
setCursor((c) => Math.min(value.length, c + 1));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (key.ctrl && input === "a") {
|
|
100
|
+
const lineStart = value.lastIndexOf("\n", cursor - 1) + 1;
|
|
101
|
+
setCursor(lineStart);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (key.ctrl && input === "e") {
|
|
106
|
+
const lineEnd = value.indexOf("\n", cursor);
|
|
107
|
+
setCursor(lineEnd === -1 ? value.length : lineEnd);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (key.ctrl && input === "u") {
|
|
112
|
+
const lineStart = value.lastIndexOf("\n", cursor - 1) + 1;
|
|
113
|
+
onChange(value.slice(0, lineStart) + value.slice(cursor));
|
|
114
|
+
setCursor(lineStart);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (key.ctrl && input === "k") {
|
|
119
|
+
const lineEnd = value.indexOf("\n", cursor);
|
|
120
|
+
onChange(
|
|
121
|
+
value.slice(0, cursor) + (lineEnd === -1 ? "" : value.slice(lineEnd)),
|
|
122
|
+
);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (key.ctrl && input === "f") return;
|
|
127
|
+
|
|
128
|
+
if ((key.ctrl && key.delete) || input === "\x1b[3;5~") {
|
|
129
|
+
const to = wordBoundaryLeft(value, cursor);
|
|
130
|
+
onChange(value.slice(0, to) + value.slice(cursor));
|
|
131
|
+
setCursor(to);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (key.backspace || key.delete) {
|
|
136
|
+
if (cursor > 0) {
|
|
137
|
+
onChange(value.slice(0, cursor - 1) + value.slice(cursor));
|
|
138
|
+
setCursor((c) => c - 1);
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (key.escape) return;
|
|
144
|
+
|
|
145
|
+
if (input) {
|
|
146
|
+
const next = value.slice(0, cursor) + input + value.slice(cursor);
|
|
147
|
+
onChange(next);
|
|
148
|
+
setCursor((c) => c + input.length);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
{ isActive: focus },
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
if (value.length === 0 && placeholder) {
|
|
155
|
+
return (
|
|
156
|
+
<Text>
|
|
157
|
+
{chalk.inverse(placeholder[0] ?? " ")}
|
|
158
|
+
{placeholder.length > 1 ? chalk.gray(placeholder.slice(1)) : ""}
|
|
159
|
+
</Text>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let rendered = "";
|
|
164
|
+
for (let i = 0; i < value.length; i++) {
|
|
165
|
+
const ch = value[i]!;
|
|
166
|
+
if (i === cursor) {
|
|
167
|
+
rendered += ch === "\n" ? chalk.inverse(" ") + "\n" : chalk.inverse(ch);
|
|
168
|
+
} else {
|
|
169
|
+
rendered += ch;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (cursor === value.length) {
|
|
173
|
+
rendered += chalk.inverse(" ");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return <Text>{rendered}</Text>;
|
|
177
|
+
}
|