mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
feat(input): move physical cursor to visual cursor for IME input (#4652)
* feat(input): add useCursor hook for IME physical cursor tracking * refactor(input): optimize cursor positioning effect * feat(input): move setCursorPosition to render phase for immediate cursor positioning * fix(input): calculate absolute cursor position by walking yoga tree * fix(input): use addLayoutListener instead of useCursor for zero-jitter cursor positioning * perf(input): stable addLayoutListener subscription and skip redundant cursor updates * fix(input): revert lastPos dedup that broke cursorDirty one-shot flag * feat(input): use patch-package to expose Ink internals for IME cursor positioning * fix(input): address review feedback — prefixWidth, remove useBoxMetrics, pin ink version Co-authored-by: Shaojin Wen <[email protected]> --------- Co-authored-by: Shaojin Wen <[email protected]>
This commit is contained in:
1 parent
7ee37277de
commit
77458ad21f
7 files changed
+335
-9
No files matched your search
Generated
+195
@@ -54,6 +54,7 @@
|
||||
"mock-fs": "^5.5.0",
|
||||
"msw": "^2.10.4",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"patch-package": "^8.0.1",
|
||||
"prettier": "^3.5.3",
|
||||
"react-devtools-core": "^6.1.5",
|
||||
"semver": "^7.7.2",
|
||||
@@ -4990,6 +4991,13 @@
|
||||
"addons/*"
|
||||
]
|
||||
},
|
||||
"node_modules/@yarnpkg/lockfile": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
|
||||
"integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/abort-controller": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
@@ -6230,6 +6238,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ci-info": {
|
||||
"version": "3.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
|
||||
"integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/sibiraj-s"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/cjs-module-lexer": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz",
|
||||
@@ -8729,6 +8753,16 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/find-yarn-workspace-root": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz",
|
||||
"integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"micromatch": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/flat-cache": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
|
||||
@@ -10966,6 +11000,26 @@
|
||||
"integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/json-stable-stringify": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz",
|
||||
"integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.8",
|
||||
"call-bound": "^1.0.4",
|
||||
"isarray": "^2.0.5",
|
||||
"jsonify": "^0.0.1",
|
||||
"object-keys": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/json-stable-stringify-without-jsonify": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
|
||||
@@ -11009,6 +11063,16 @@
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonify": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz",
|
||||
"integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==",
|
||||
"dev": true,
|
||||
"license": "Public Domain",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonrepair": {
|
||||
"version": "3.13.1",
|
||||
"resolved": "https://registry.npmjs.org/jsonrepair/-/jsonrepair-3.13.1.tgz",
|
||||
@@ -11065,6 +11129,16 @@
|
||||
"json-buffer": "3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/klaw-sync": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
|
||||
"integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.1.11"
|
||||
}
|
||||
},
|
||||
"node_modules/kleur": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
|
||||
@@ -12813,6 +12887,107 @@
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz",
|
||||
"integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@yarnpkg/lockfile": "^1.1.0",
|
||||
"chalk": "^4.1.2",
|
||||
"ci-info": "^3.7.0",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"find-yarn-workspace-root": "^2.0.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"json-stable-stringify": "^1.0.2",
|
||||
"klaw-sync": "^6.0.0",
|
||||
"minimist": "^1.2.6",
|
||||
"open": "^7.4.2",
|
||||
"semver": "^7.5.3",
|
||||
"slash": "^2.0.0",
|
||||
"tmp": "^0.2.4",
|
||||
"yaml": "^2.2.2"
|
||||
},
|
||||
"bin": {
|
||||
"patch-package": "index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">5"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package/node_modules/fs-extra": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
|
||||
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package/node_modules/is-docker": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
|
||||
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"is-docker": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package/node_modules/is-wsl": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
|
||||
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-docker": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package/node_modules/open": {
|
||||
"version": "7.4.2",
|
||||
"resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
|
||||
"integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-docker": "^2.0.0",
|
||||
"is-wsl": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package/node_modules/universalify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-browserify": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
|
||||
@@ -14615,6 +14790,16 @@
|
||||
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/slash": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
|
||||
"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/slice-ansi": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz",
|
||||
@@ -15599,6 +15784,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.2.7",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz",
|
||||
"integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.14"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
"build:sdk:python": "python3 -m build packages/sdk-python",
|
||||
"check-i18n": "npm run check-i18n --workspace=packages/cli",
|
||||
"preflight": "npm run clean && npm ci && npm run format && npm run lint:ci && npm run build && npm run typecheck && npm run test:ci",
|
||||
"postinstall": "patch-package",
|
||||
"prepare": "husky && npm run build && npm run bundle",
|
||||
"prepare:package": "node scripts/prepare-package.js",
|
||||
"package:hosted-installation": "node scripts/build-hosted-installation-assets.js",
|
||||
@@ -126,6 +127,7 @@
|
||||
"mock-fs": "^5.5.0",
|
||||
"msw": "^2.10.4",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"patch-package": "^8.0.1",
|
||||
"prettier": "^3.5.3",
|
||||
"react-devtools-core": "^6.1.5",
|
||||
"semver": "^7.7.2",
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
"fzf": "^0.5.2",
|
||||
"glob": "^10.5.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"ink": "^7.0.3",
|
||||
"ink": "7.0.3",
|
||||
"ink-gradient": "^3.0.0",
|
||||
"ink-link": "^4.1.0",
|
||||
"ink-spinner": "^5.0.0",
|
||||
|
||||
@@ -19,9 +19,11 @@
|
||||
* and AgentComposer (with minimal customization).
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useCallback, useContext, useEffect, useRef } from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { addLayoutListener, type DOMElement } from 'ink/dom';
|
||||
import CursorContext from 'ink/components/CursorContext';
|
||||
import chalk from 'chalk';
|
||||
import type { TextBuffer } from './shared/text-buffer.js';
|
||||
import type { Key } from '../hooks/useKeypress.js';
|
||||
@@ -67,7 +69,9 @@ export interface BaseTextInputProps {
|
||||
/** Placeholder text shown when the buffer is empty. */
|
||||
placeholder?: string;
|
||||
/** Custom prefix node (defaults to `> `). */
|
||||
prefix?: React.ReactNode;
|
||||
prefix?: ReactNode;
|
||||
/** Width of the prefix in terminal columns. Defaults to 2 (for "> "). */
|
||||
prefixWidth?: number;
|
||||
/** Border color for the input box. */
|
||||
borderColor?: string;
|
||||
/** Label rendered on the top border line (right-aligned). Plain string for width calculation. */
|
||||
@@ -78,7 +82,7 @@ export interface BaseTextInputProps {
|
||||
* Custom line renderer for advanced rendering (e.g. syntax highlighting).
|
||||
* When not provided, lines are rendered as plain text with cursor overlay.
|
||||
*/
|
||||
renderLine?: (opts: RenderLineOptions) => React.ReactNode;
|
||||
renderLine?: (opts: RenderLineOptions) => ReactNode;
|
||||
}
|
||||
|
||||
// ─── Default line renderer ──────────────────────────────────
|
||||
@@ -92,7 +96,7 @@ export function defaultRenderLine({
|
||||
isOnCursorLine,
|
||||
cursorCol,
|
||||
showCursor,
|
||||
}: RenderLineOptions): React.ReactNode {
|
||||
}: RenderLineOptions): ReactNode {
|
||||
if (!isOnCursorLine || !showCursor) {
|
||||
return <Text>{lineText || ' '}</Text>;
|
||||
}
|
||||
@@ -122,20 +126,34 @@ export function defaultRenderLine({
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Helpers ────────────────────────────────────────────────
|
||||
|
||||
// Walk up Ink's internal DOM tree to find the root node (ink-root).
|
||||
// addLayoutListener requires the root node specifically.
|
||||
function findRootNode(
|
||||
node: (Record<string, unknown> & { parentNode?: unknown }) | null,
|
||||
): DOMElement | undefined {
|
||||
if (!node) return undefined;
|
||||
if (!node.parentNode)
|
||||
return node['nodeName'] === 'ink-root' ? (node as DOMElement) : undefined;
|
||||
return findRootNode(node.parentNode as Record<string, unknown>);
|
||||
}
|
||||
|
||||
// ─── Component ──────────────────────────────────────────────
|
||||
|
||||
export const BaseTextInput: React.FC<BaseTextInputProps> = ({
|
||||
export const BaseTextInput = ({
|
||||
buffer,
|
||||
onSubmit,
|
||||
onKeypress,
|
||||
showCursor = true,
|
||||
placeholder,
|
||||
prefix,
|
||||
prefixWidth = 2,
|
||||
borderColor,
|
||||
topRightLabel,
|
||||
isActive = true,
|
||||
renderLine = defaultRenderLine,
|
||||
}) => {
|
||||
}: BaseTextInputProps): ReactNode => {
|
||||
// ── Keyboard handling ──
|
||||
|
||||
const handleKey = useCallback(
|
||||
@@ -249,6 +267,81 @@ export const BaseTextInput: React.FC<BaseTextInputProps> = ({
|
||||
const [cursorVisualRow, cursorVisualCol] = buffer.visualCursor;
|
||||
const scrollVisualRow = buffer.visualScrollRow;
|
||||
|
||||
// ── Physical cursor positioning for IME ──
|
||||
// addLayoutListener fires in resetAfterCommit AFTER calculateLayout()
|
||||
// but BEFORE onRender() — yoga layout is fresh, terminal not yet written.
|
||||
// addLayoutListener requires the root node (ink-root), not the component
|
||||
// node. We find it by walking up the Ink DOM parent chain.
|
||||
const rootRef = useRef(null);
|
||||
const cursorCtx = useContext(CursorContext);
|
||||
|
||||
// Use a ref to hold mutable state so the layout listener callback
|
||||
// always reads the latest values without needing to resubscribe.
|
||||
const stateRef = useRef({
|
||||
showCursor,
|
||||
cursorVisualRow,
|
||||
cursorVisualCol,
|
||||
scrollVisualRow,
|
||||
linesToRender,
|
||||
prefixWidth,
|
||||
});
|
||||
stateRef.current = {
|
||||
showCursor,
|
||||
cursorVisualRow,
|
||||
cursorVisualCol,
|
||||
scrollVisualRow,
|
||||
linesToRender,
|
||||
prefixWidth,
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const rootNode = findRootNode(rootRef.current);
|
||||
if (!rootNode) return;
|
||||
const unsub = addLayoutListener(rootNode, () => {
|
||||
const {
|
||||
showCursor: sc,
|
||||
cursorVisualRow: vr,
|
||||
cursorVisualCol: vc,
|
||||
scrollVisualRow: sr,
|
||||
linesToRender: lt,
|
||||
prefixWidth: pw,
|
||||
} = stateRef.current;
|
||||
if (!sc) {
|
||||
cursorCtx.setCursorPosition(undefined);
|
||||
return;
|
||||
}
|
||||
const node = rootRef.current;
|
||||
if (!node) return;
|
||||
let absTop = 0;
|
||||
let absLeft = 0;
|
||||
let n: unknown = node;
|
||||
while (n) {
|
||||
const nd = n as {
|
||||
yogaNode?: { getComputedLayout(): { top: number; left: number } };
|
||||
parentNode?: unknown;
|
||||
};
|
||||
const layout = nd.yogaNode?.getComputedLayout();
|
||||
if (layout) {
|
||||
absTop += layout.top;
|
||||
absLeft += layout.left;
|
||||
}
|
||||
n = nd.parentNode;
|
||||
}
|
||||
const relativeRow = vr - sr;
|
||||
const lineText = lt[relativeRow] || '';
|
||||
const textBeforeCursor = cpSlice(lineText, 0, vc);
|
||||
const physicalCol = stringWidth(textBeforeCursor);
|
||||
cursorCtx.setCursorPosition({
|
||||
x: absLeft + pw + physicalCol,
|
||||
y: absTop + relativeRow + 1,
|
||||
});
|
||||
});
|
||||
return () => {
|
||||
unsub();
|
||||
cursorCtx.setCursorPosition(undefined);
|
||||
};
|
||||
}, [cursorCtx]);
|
||||
|
||||
const resolvedBorderColor = borderColor ?? theme.border.focused;
|
||||
const resolvedPrefix = prefix ?? (
|
||||
<Text color={theme.text.accent}>{'> '}</Text>
|
||||
@@ -264,7 +357,7 @@ export const BaseTextInput: React.FC<BaseTextInputProps> = ({
|
||||
: '─'.repeat(columns);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Box ref={rootRef} flexDirection="column">
|
||||
<Text color={resolvedBorderColor} wrap="truncate-end">
|
||||
{topBorderLine}
|
||||
</Text>
|
||||
|
||||
@@ -1580,6 +1580,17 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
</Text>
|
||||
);
|
||||
|
||||
// Calculate prefix width for physical cursor positioning
|
||||
const prefixWidth = shellModeActive
|
||||
? reverseSearchActive
|
||||
? 6 // "(r:) " (inner) + " " (outer) = 6 cols
|
||||
: 2 // "! " = 2 chars
|
||||
: commandSearchActive
|
||||
? 6 // "(r:) " (inner) + " " (outer) = 6 cols
|
||||
: showYoloStyling
|
||||
? 2 // "* " = 2 chars
|
||||
: 2; // "> " = 2 chars
|
||||
|
||||
return (
|
||||
<>
|
||||
{attachments.length > 0 && (
|
||||
@@ -1610,6 +1621,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
: placeholder
|
||||
}
|
||||
prefix={prefixNode}
|
||||
prefixWidth={prefixWidth}
|
||||
borderColor={borderColor}
|
||||
topRightLabel={uiState.sessionName || undefined}
|
||||
isActive={!isEmbeddedShellFocused}
|
||||
|
||||
@@ -260,6 +260,7 @@ export const AgentComposer: React.FC<AgentComposerProps> = ({ agentId }) => {
|
||||
const prefixNode = (
|
||||
<Text color={statusColor ?? theme.text.accent}>{isYolo ? '*' : '>'} </Text>
|
||||
);
|
||||
const prefixWidth = 2; // "> " or "* " = 2 chars
|
||||
|
||||
return (
|
||||
<StreamingContext.Provider value={streamingState}>
|
||||
@@ -292,6 +293,7 @@ export const AgentComposer: React.FC<AgentComposerProps> = ({ agentId }) => {
|
||||
showCursor={isInputActive && !agentTabBarFocused}
|
||||
placeholder={' ' + t('Send a message to this agent')}
|
||||
prefix={prefixNode}
|
||||
prefixWidth={prefixWidth}
|
||||
borderColor={inputBorderColor}
|
||||
isActive={isInputActive && !agentShellFocused}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
diff --git a/node_modules/ink/package.json b/node_modules/ink/package.json
|
||||
--- a/node_modules/ink/package.json
|
||||
+++ b/node_modules/ink/package.json
|
||||
@@ -11,8 +11,16 @@
|
||||
},
|
||||
"type": "module",
|
||||
"exports": {
|
||||
- "types": "./build/index.d.ts",
|
||||
- "default": "./build/index.js"
|
||||
+ ".": {
|
||||
+ "types": "./build/index.d.ts",
|
||||
+ "default": "./build/index.js"
|
||||
+ },
|
||||
+ "./dom": {
|
||||
+ "default": "./build/dom.js"
|
||||
+ },
|
||||
+ "./components/CursorContext": {
|
||||
+ "default": "./build/components/CursorContext.js"
|
||||
+ }
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
Reference in new issue
Block a user