mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-08 08:04:50 -10:00
feat(ui): polish pickers, narrow-term reflow, multi-line indent
- ShellConfirm border yellow→red (highest-risk gate distinct from review) - EditConfirm border cyan→yellow (decouple from PlanConfirm cyan) - PromptInput: extract [Esc]-to-stop into separate dim row; narrow mode (≤90 cols) condenses prefix `you ›`→`›` with proportional indent - EventLog: user/warning/error multi-line text now aligns under glyph via indentContinuationLines helper - Slash/At/SlashArg suggestion overlays: marginTop separates from scrollback
This commit is contained in:
1 parent
9e3c62ed1e
commit
d6bb4054dc
8 files changed
+104
-71
No files matched your search
+1
-1
@@ -1018,7 +1018,7 @@ export function App({
|
||||
if (!codeMode) return "not in code mode";
|
||||
const blocks = pendingEdits.current;
|
||||
if (blocks.length === 0) {
|
||||
return "nothing pending — the assistant hasn't proposed edits since the last /apply or /discard.";
|
||||
return "nothing pending — the model hasn't proposed edits since the last /apply or /discard.";
|
||||
}
|
||||
const snaps = snapshotBeforeEdits(blocks, codeMode.rootDir);
|
||||
const results = applyEditBlocks(blocks, codeMode.rootDir);
|
||||
|
||||
@@ -30,7 +30,7 @@ export function AtMentionSuggestions({
|
||||
if (matches === null) return null;
|
||||
if (matches.length === 0) {
|
||||
return (
|
||||
<Box paddingX={1}>
|
||||
<Box paddingX={1} marginTop={1}>
|
||||
<Text color="yellow">no files match "@{query}"</Text>
|
||||
<Text dimColor>
|
||||
{" "}
|
||||
@@ -47,7 +47,7 @@ export function AtMentionSuggestions({
|
||||
const hiddenAbove = windowStart;
|
||||
const hiddenBelow = total - windowStart - shown.length;
|
||||
return (
|
||||
<Box flexDirection="column" paddingX={1}>
|
||||
<Box flexDirection="column" paddingX={1} marginTop={1}>
|
||||
{hiddenAbove > 0 ? <Text dimColor> ↑ {hiddenAbove} more above</Text> : null}
|
||||
{shown.map((path, i) => (
|
||||
<FileRow key={path} path={path} isSelected={windowStart + i === selectedIndex} />
|
||||
|
||||
@@ -134,14 +134,14 @@ export function EditConfirm({ block, onChoose }: EditConfirmProps) {
|
||||
const showScrollHud = hiddenAbove + hiddenBelow > 0;
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" borderStyle="round" borderColor="cyan" paddingX={1} marginY={1}>
|
||||
<Box flexDirection="column" borderStyle="round" borderColor="yellow" paddingX={1} marginY={1}>
|
||||
<Box>
|
||||
<Text bold color="cyan">
|
||||
<Text bold color="yellow">
|
||||
{"▸ model wants to edit a file"}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text color="cyan" dimColor>
|
||||
<Text color="yellow" dimColor>
|
||||
{"──────────────────────────────────────────"}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
+14
-3
@@ -149,6 +149,17 @@ function RoleGlyph({
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pad continuation lines so wrapped multi-line text aligns under the
|
||||
* glyph indent instead of jumping back to column 0. Header is `glyph
|
||||
* (1 col) + " " (2 cols)`, so subsequent lines need 3 leading spaces
|
||||
* to land under the body's first character.
|
||||
*/
|
||||
function indentContinuationLines(text: string): string {
|
||||
if (!text.includes("\n")) return text;
|
||||
return text.split("\n").join("\n ");
|
||||
}
|
||||
|
||||
export const EventRow = React.memo(function EventRow({
|
||||
event,
|
||||
projectRoot,
|
||||
@@ -162,7 +173,7 @@ export const EventRow = React.memo(function EventRow({
|
||||
<RoleGlyph glyph={ROLE_GLYPH.user} color="cyan" />
|
||||
<Text>
|
||||
{" "}
|
||||
{event.text}
|
||||
{indentContinuationLines(event.text)}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
@@ -249,7 +260,7 @@ export const EventRow = React.memo(function EventRow({
|
||||
<RoleGlyph glyph={ROLE_GLYPH.error} color="red" />
|
||||
<Text color="red">
|
||||
{" "}
|
||||
{event.text}
|
||||
{indentContinuationLines(event.text)}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
@@ -394,7 +405,7 @@ export const EventRow = React.memo(function EventRow({
|
||||
<RoleGlyph glyph={ROLE_GLYPH.warning} color="yellow" />
|
||||
<Text color="yellow">
|
||||
{" "}
|
||||
{event.text}
|
||||
{indentContinuationLines(event.text)}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
|
||||
+76
-54
@@ -1,4 +1,4 @@
|
||||
import { Box, Text, useInput } from "ink";
|
||||
import { Box, Text, useInput, useStdout } from "ink";
|
||||
// biome-ignore lint/style/useImportType: tsconfig jsx=react needs React as a runtime value (classic transform compiles JSX to React.createElement)
|
||||
import React, { useRef, useState } from "react";
|
||||
import { type MultilineKey, lineAndColumn, processMultilineKey } from "./multiline-keys.js";
|
||||
@@ -274,9 +274,24 @@ export function PromptInput({
|
||||
{ isActive: !disabled },
|
||||
);
|
||||
|
||||
// Narrow-terminal mode: drop the `you ›` (6 col) prefix to a `›` (2
|
||||
// col) marker so the writable area on 80-col terminals reclaims 4
|
||||
// cols. Threshold of 90 keeps the friendly `you ›` on anything that
|
||||
// resembles a normal terminal; modern laptops tend to default to
|
||||
// 100+ cols, narrow ones (split panes, embedded shells) tend to be
|
||||
// <=90. Continuation indent shrinks proportionally so wrapped lines
|
||||
// still align under the prompt arrow.
|
||||
const { stdout } = useStdout();
|
||||
const cols = stdout?.columns ?? 80;
|
||||
const narrow = cols <= 90;
|
||||
const promptPrefix = narrow ? "› " : "you › ";
|
||||
const continuationIndent = narrow ? " " : " ";
|
||||
const placeholderActive = narrow
|
||||
? "type a message, or /command"
|
||||
: "type a message, or /command · [Shift+Enter] / [Ctrl+J] newline";
|
||||
const effectivePlaceholder = disabled
|
||||
? (placeholder ?? "…waiting for response… · [Esc] to stop")
|
||||
: (placeholder ?? "type a message, or /command · [Shift+Enter] / [Ctrl+J] newline");
|
||||
? (placeholder ?? "…waiting for response…")
|
||||
: (placeholder ?? placeholderActive);
|
||||
|
||||
const lines = value.length > 0 ? value.split("\n") : [""];
|
||||
const borderColor = disabled ? "gray" : "cyan";
|
||||
@@ -298,63 +313,70 @@ export function PromptInput({
|
||||
const showHugeBufferHints = lines.length > 20;
|
||||
|
||||
return (
|
||||
<Box borderStyle="round" borderColor={borderColor} paddingX={1} flexDirection="column">
|
||||
{renderItems.map((item, renderIdx) => {
|
||||
if (item.kind === "skip") {
|
||||
<>
|
||||
<Box borderStyle="round" borderColor={borderColor} paddingX={1} flexDirection="column">
|
||||
{renderItems.map((item, renderIdx) => {
|
||||
if (item.kind === "skip") {
|
||||
return (
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: stable — skip markers are derived from a fixed-size window over `lines`
|
||||
<Box key={`skip-${renderIdx}`}>
|
||||
<Text dimColor>{continuationIndent}</Text>
|
||||
<Text
|
||||
dimColor
|
||||
>{`[… ${item.linesHidden} line${item.linesHidden === 1 ? "" : "s"} hidden — full content kept, submitted on Enter …]`}</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
const line = item.line;
|
||||
const i = item.originalIndex;
|
||||
const isFirst = i === 0;
|
||||
const showPlaceholder = isFirst && value.length === 0;
|
||||
const isCursorLine = i === cursorLine;
|
||||
return (
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: stable — skip markers are derived from a fixed-size window over `lines`
|
||||
<Box key={`skip-${renderIdx}`}>
|
||||
<Text dimColor>{" "}</Text>
|
||||
<Text
|
||||
dimColor
|
||||
>{`[… ${item.linesHidden} line${item.linesHidden === 1 ? "" : "s"} hidden — full content kept, submitted on Enter …]`}</Text>
|
||||
<Box key={`ln-${i}`}>
|
||||
{isFirst ? (
|
||||
<Text bold color={borderColor}>
|
||||
{promptPrefix}
|
||||
</Text>
|
||||
) : (
|
||||
<Text dimColor>{continuationIndent}</Text>
|
||||
)}
|
||||
{showPlaceholder ? (
|
||||
<>
|
||||
{isCursorLine && !disabled ? (
|
||||
<Text color={borderColor}>{showCursor ? "▌" : " "}</Text>
|
||||
) : null}
|
||||
<Text dimColor>{effectivePlaceholder}</Text>
|
||||
</>
|
||||
) : isCursorLine && !disabled ? (
|
||||
<LineWithCursor
|
||||
line={line}
|
||||
col={cursorCol}
|
||||
showCursor={showCursor}
|
||||
borderColor={borderColor}
|
||||
pastes={pastesRef.current}
|
||||
/>
|
||||
) : (
|
||||
<RenderLine line={line} pastes={pastesRef.current} />
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
const line = item.line;
|
||||
const i = item.originalIndex;
|
||||
const isFirst = i === 0;
|
||||
const showPlaceholder = isFirst && value.length === 0;
|
||||
const isCursorLine = i === cursorLine;
|
||||
return (
|
||||
<Box key={`ln-${i}`}>
|
||||
{isFirst ? (
|
||||
<Text bold color={borderColor}>
|
||||
you ›{" "}
|
||||
</Text>
|
||||
) : (
|
||||
<Text dimColor>{" "}</Text>
|
||||
)}
|
||||
{showPlaceholder ? (
|
||||
<>
|
||||
{isCursorLine && !disabled ? (
|
||||
<Text color={borderColor}>{showCursor ? "▌" : " "}</Text>
|
||||
) : null}
|
||||
<Text dimColor>{effectivePlaceholder}</Text>
|
||||
</>
|
||||
) : isCursorLine && !disabled ? (
|
||||
<LineWithCursor
|
||||
line={line}
|
||||
col={cursorCol}
|
||||
showCursor={showCursor}
|
||||
borderColor={borderColor}
|
||||
pastes={pastesRef.current}
|
||||
/>
|
||||
) : (
|
||||
<RenderLine line={line} pastes={pastesRef.current} />
|
||||
)}
|
||||
})}
|
||||
{showHugeBufferHints && !disabled ? (
|
||||
<Box>
|
||||
<Text dimColor>{continuationIndent}</Text>
|
||||
<Text dimColor>
|
||||
{`[${lines.length} lines · PageUp/PageDown jump to top/bottom · Ctrl+U clear · Ctrl+W del word]`}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
{showHugeBufferHints && !disabled ? (
|
||||
<Box>
|
||||
<Text dimColor>{" "}</Text>
|
||||
<Text dimColor>
|
||||
{`[${lines.length} lines · PageUp/PageDown jump to top/bottom · Ctrl+U clear · Ctrl+W del word]`}
|
||||
</Text>
|
||||
) : null}
|
||||
</Box>
|
||||
{disabled ? (
|
||||
<Box paddingX={1}>
|
||||
<Text dimColor>[Esc] to stop</Text>
|
||||
</Box>
|
||||
) : null}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,9 +35,9 @@ export interface ShellConfirmProps {
|
||||
export function ShellConfirm({ command, allowPrefix, kind, onChoose }: ShellConfirmProps) {
|
||||
const isBackground = kind === "run_background";
|
||||
return (
|
||||
<Box flexDirection="column" borderStyle="round" borderColor="yellow" paddingX={1} marginY={1}>
|
||||
<Box flexDirection="column" borderStyle="round" borderColor="red" paddingX={1} marginY={1}>
|
||||
<Box>
|
||||
<Text bold color="yellow">
|
||||
<Text bold color="red">
|
||||
{isBackground
|
||||
? "▸ model wants to start a BACKGROUND process"
|
||||
: "▸ model wants to run a shell command"}
|
||||
@@ -51,7 +51,7 @@ export function ShellConfirm({ command, allowPrefix, kind, onChoose }: ShellConf
|
||||
</Box>
|
||||
) : null}
|
||||
<Box>
|
||||
<Text color="yellow" dimColor>
|
||||
<Text color="red" dimColor>
|
||||
{"──────────────────────────────────────────"}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
@@ -39,7 +39,7 @@ export function SlashArgPicker({
|
||||
// Hint mode: a single dim row explaining the argsHint + summary.
|
||||
if (kind === "hint") {
|
||||
return (
|
||||
<Box paddingX={1}>
|
||||
<Box paddingX={1} marginTop={1}>
|
||||
<Text dimColor>
|
||||
{" "}
|
||||
<Text bold>/{spec.cmd}</Text>
|
||||
@@ -52,7 +52,7 @@ export function SlashArgPicker({
|
||||
if (matches === null) return null;
|
||||
if (matches.length === 0) {
|
||||
return (
|
||||
<Box flexDirection="column" paddingX={1}>
|
||||
<Box flexDirection="column" paddingX={1} marginTop={1}>
|
||||
<Text dimColor>
|
||||
{" "}
|
||||
<Text bold>/{spec.cmd}</Text>
|
||||
@@ -71,7 +71,7 @@ export function SlashArgPicker({
|
||||
const hiddenAbove = windowStart;
|
||||
const hiddenBelow = total - windowStart - shown.length;
|
||||
return (
|
||||
<Box flexDirection="column" paddingX={1}>
|
||||
<Box flexDirection="column" paddingX={1} marginTop={1}>
|
||||
<Text dimColor>
|
||||
{" "}
|
||||
<Text bold>/{spec.cmd}</Text>
|
||||
|
||||
@@ -28,7 +28,7 @@ export function SlashSuggestions({
|
||||
if (matches === null) return null;
|
||||
if (matches.length === 0) {
|
||||
return (
|
||||
<Box paddingX={1}>
|
||||
<Box paddingX={1} marginTop={1}>
|
||||
<Text color="yellow">no slash command matches that prefix</Text>
|
||||
<Text dimColor> — Backspace to edit, or /help for the full list</Text>
|
||||
</Box>
|
||||
@@ -45,7 +45,7 @@ export function SlashSuggestions({
|
||||
const hiddenAbove = windowStart;
|
||||
const hiddenBelow = total - windowStart - shown.length;
|
||||
return (
|
||||
<Box flexDirection="column" paddingX={1}>
|
||||
<Box flexDirection="column" paddingX={1} marginTop={1}>
|
||||
{hiddenAbove > 0 ? <Text dimColor> ↑ {hiddenAbove} more above</Text> : null}
|
||||
{shown.map((spec, i) => (
|
||||
<SuggestionRow key={spec.cmd} spec={spec} isSelected={windowStart + i === selectedIndex} />
|
||||
|
||||
Reference in new issue
Block a user