mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-21 15:55:50 +00:00
feat: 第一个可以用的 ink 组件抽象 (#158)
This commit is contained in:
28
bun.lock
28
bun.lock
@@ -17,6 +17,7 @@
|
|||||||
"@anthropic-ai/sandbox-runtime": "^0.0.44",
|
"@anthropic-ai/sandbox-runtime": "^0.0.44",
|
||||||
"@anthropic-ai/sdk": "^0.80.0",
|
"@anthropic-ai/sdk": "^0.80.0",
|
||||||
"@anthropic-ai/vertex-sdk": "^0.14.4",
|
"@anthropic-ai/vertex-sdk": "^0.14.4",
|
||||||
|
"@anthropic/ink": "workspace:*",
|
||||||
"@aws-sdk/client-bedrock": "^3.1020.0",
|
"@aws-sdk/client-bedrock": "^3.1020.0",
|
||||||
"@aws-sdk/client-bedrock-runtime": "^3.1020.0",
|
"@aws-sdk/client-bedrock-runtime": "^3.1020.0",
|
||||||
"@aws-sdk/client-sts": "^3.1020.0",
|
"@aws-sdk/client-sts": "^3.1020.0",
|
||||||
@@ -138,6 +139,29 @@
|
|||||||
"name": "@ant/computer-use-swift",
|
"name": "@ant/computer-use-swift",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
},
|
},
|
||||||
|
"packages/@ant/ink": {
|
||||||
|
"name": "@anthropic/ink",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"auto-bind": "^5.0.1",
|
||||||
|
"bidi-js": "^1.0.3",
|
||||||
|
"chalk": "^5.6.2",
|
||||||
|
"cli-boxes": "^4.0.1",
|
||||||
|
"emoji-regex": "^10.6.0",
|
||||||
|
"figures": "^6.1.0",
|
||||||
|
"get-east-asian-width": "^1.5.0",
|
||||||
|
"indent-string": "^5.0.0",
|
||||||
|
"lodash-es": "^4.17.23",
|
||||||
|
"react": "^19.2.4",
|
||||||
|
"react-reconciler": "^0.33.0",
|
||||||
|
"signal-exit": "^4.1.0",
|
||||||
|
"strip-ansi": "^7.2.0",
|
||||||
|
"supports-hyperlinks": "^4.4.0",
|
||||||
|
"type-fest": "^5.5.0",
|
||||||
|
"usehooks-ts": "^3.1.1",
|
||||||
|
"wrap-ansi": "^10.0.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
"packages/audio-capture-napi": {
|
"packages/audio-capture-napi": {
|
||||||
"name": "audio-capture-napi",
|
"name": "audio-capture-napi",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -190,6 +214,8 @@
|
|||||||
|
|
||||||
"@anthropic-ai/vertex-sdk": ["@anthropic-ai/vertex-sdk@0.14.4", "https://registry.npmmirror.com/@anthropic-ai/vertex-sdk/-/vertex-sdk-0.14.4.tgz", { "dependencies": { "@anthropic-ai/sdk": ">=0.50.3 <1", "google-auth-library": "^9.4.2" } }, "sha512-BZUPRWghZxfSFtAxU563wH+jfWBPoedAwsVxG35FhmNsjeV8tyfN+lFriWhCpcZApxA4NdT6Soov+PzfnxxD5g=="],
|
"@anthropic-ai/vertex-sdk": ["@anthropic-ai/vertex-sdk@0.14.4", "https://registry.npmmirror.com/@anthropic-ai/vertex-sdk/-/vertex-sdk-0.14.4.tgz", { "dependencies": { "@anthropic-ai/sdk": ">=0.50.3 <1", "google-auth-library": "^9.4.2" } }, "sha512-BZUPRWghZxfSFtAxU563wH+jfWBPoedAwsVxG35FhmNsjeV8tyfN+lFriWhCpcZApxA4NdT6Soov+PzfnxxD5g=="],
|
||||||
|
|
||||||
|
"@anthropic/ink": ["@anthropic/ink@workspace:packages/@ant/ink"],
|
||||||
|
|
||||||
"@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "https://registry.npmmirror.com/@aws-crypto/crc32/-/crc32-5.2.0.tgz", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="],
|
"@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "https://registry.npmmirror.com/@aws-crypto/crc32/-/crc32-5.2.0.tgz", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="],
|
||||||
|
|
||||||
"@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "https://registry.npmmirror.com/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="],
|
"@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "https://registry.npmmirror.com/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="],
|
||||||
@@ -1198,7 +1224,7 @@
|
|||||||
|
|
||||||
"open": ["open@10.2.0", "https://registry.npmmirror.com/open/-/open-10.2.0.tgz", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
|
"open": ["open@10.2.0", "https://registry.npmmirror.com/open/-/open-10.2.0.tgz", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
|
||||||
|
|
||||||
"openai": ["openai@6.33.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-xAYN1W3YsDXJWA5F277135YfkEk6H7D3D6vWwRhJ3OEkzRgcyK8z/P5P9Gyi/wB4N8kK9kM5ZjprfvyHagKmpw=="],
|
"openai": ["openai@6.33.0", "https://registry.npmmirror.com/openai/-/openai-6.33.0.tgz", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-xAYN1W3YsDXJWA5F277135YfkEk6H7D3D6vWwRhJ3OEkzRgcyK8z/P5P9Gyi/wB4N8kK9kM5ZjprfvyHagKmpw=="],
|
||||||
|
|
||||||
"os-tmpdir": ["os-tmpdir@1.0.2", "https://registry.npmmirror.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz", {}, "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g=="],
|
"os-tmpdir": ["os-tmpdir@1.0.2", "https://registry.npmmirror.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz", {}, "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g=="],
|
||||||
|
|
||||||
|
|||||||
@@ -131,6 +131,7 @@
|
|||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
"https-proxy-agent": "^8.0.0",
|
"https-proxy-agent": "^8.0.0",
|
||||||
"ignore": "^7.0.5",
|
"ignore": "^7.0.5",
|
||||||
|
"@anthropic/ink": "workspace:*",
|
||||||
"image-processor-napi": "workspace:*",
|
"image-processor-napi": "workspace:*",
|
||||||
"indent-string": "^5.0.0",
|
"indent-string": "^5.0.0",
|
||||||
"jsonc-parser": "^3.3.1",
|
"jsonc-parser": "^3.3.1",
|
||||||
|
|||||||
30
packages/@ant/ink/package.json
Normal file
30
packages/@ant/ink/package.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "@anthropic/ink",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "./src/index.ts",
|
||||||
|
"types": "./src/index.ts",
|
||||||
|
"exports": {
|
||||||
|
".": "./src/index.ts"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"auto-bind": "^5.0.1",
|
||||||
|
"bidi-js": "^1.0.3",
|
||||||
|
"chalk": "^5.6.2",
|
||||||
|
"cli-boxes": "^4.0.1",
|
||||||
|
"emoji-regex": "^10.6.0",
|
||||||
|
"figures": "^6.1.0",
|
||||||
|
"get-east-asian-width": "^1.5.0",
|
||||||
|
"indent-string": "^5.0.0",
|
||||||
|
"lodash-es": "^4.17.23",
|
||||||
|
"react": "^19.2.4",
|
||||||
|
"react-reconciler": "^0.33.0",
|
||||||
|
"signal-exit": "^4.1.0",
|
||||||
|
"strip-ansi": "^7.2.0",
|
||||||
|
"supports-hyperlinks": "^4.4.0",
|
||||||
|
"type-fest": "^5.5.0",
|
||||||
|
"usehooks-ts": "^3.1.1",
|
||||||
|
"wrap-ansi": "^10.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,14 +3,14 @@ import React, {
|
|||||||
useContext,
|
useContext,
|
||||||
useInsertionEffect,
|
useInsertionEffect,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import instances from '../instances.js'
|
import instances from '../core/instances.js'
|
||||||
import {
|
import {
|
||||||
DISABLE_MOUSE_TRACKING,
|
DISABLE_MOUSE_TRACKING,
|
||||||
ENABLE_MOUSE_TRACKING,
|
ENABLE_MOUSE_TRACKING,
|
||||||
ENTER_ALT_SCREEN,
|
ENTER_ALT_SCREEN,
|
||||||
EXIT_ALT_SCREEN,
|
EXIT_ALT_SCREEN,
|
||||||
} from '../termio/dec.js'
|
} from '../core/termio/dec.js'
|
||||||
import { TerminalWriteContext } from '../useTerminalNotification.js'
|
import { TerminalWriteContext } from '../hooks/useTerminalNotification.js'
|
||||||
import Box from './Box.js'
|
import Box from './Box.js'
|
||||||
import { TerminalSizeContext } from './TerminalSizeContext.js'
|
import { TerminalSizeContext } from './TerminalSizeContext.js'
|
||||||
|
|
||||||
@@ -1,37 +1,62 @@
|
|||||||
import React, { PureComponent, type ReactNode } from 'react'
|
import React, { PureComponent, type ReactNode } from 'react'
|
||||||
import { updateLastInteractionTime } from '../../bootstrap/state.js'
|
// Business-layer callbacks — replaced with inline defaults so this package
|
||||||
import { logForDebugging } from '../../utils/debug.js'
|
// has zero dependencies on business code. The business layer can inject
|
||||||
import { stopCapturingEarlyInput } from '../../utils/earlyInput.js'
|
// implementations via AppCallbacks when needed.
|
||||||
import { isEnvTruthy } from '../../utils/envUtils.js'
|
type AppCallbacks = {
|
||||||
import { isMouseClicksDisabled } from '../../utils/fullscreen.js'
|
updateLastInteractionTime?: () => void
|
||||||
import { logError } from '../../utils/log.js'
|
stopCapturingEarlyInput?: () => void
|
||||||
import { EventEmitter } from '../events/emitter.js'
|
isMouseClicksDisabled?: () => boolean
|
||||||
import { InputEvent } from '../events/input-event.js'
|
logError?: (error: unknown) => void
|
||||||
import { TerminalFocusEvent } from '../events/terminal-focus-event.js'
|
logForDebugging?: (message: string, opts?: { level?: string }) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Default no-op / safe-default implementations */
|
||||||
|
const defaultCallbacks: Required<AppCallbacks> = {
|
||||||
|
updateLastInteractionTime: () => {},
|
||||||
|
stopCapturingEarlyInput: () => {},
|
||||||
|
isMouseClicksDisabled: () => false,
|
||||||
|
logError: (error: unknown) => console.error(error),
|
||||||
|
logForDebugging: (_message: string, _opts?: { level?: string }) => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override the default no-op callbacks. Call this from the business layer
|
||||||
|
* (e.g. src/ink.tsx) before mounting <App>.
|
||||||
|
*/
|
||||||
|
export function setAppCallbacks(cb: AppCallbacks): void {
|
||||||
|
Object.assign(defaultCallbacks, cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEnvTruthy(value: string | undefined): boolean {
|
||||||
|
return value === '1' || value === 'true'
|
||||||
|
}
|
||||||
|
import { EventEmitter } from '../core/events/emitter.js'
|
||||||
|
import { InputEvent } from '../core/events/input-event.js'
|
||||||
|
import { TerminalFocusEvent } from '../core/events/terminal-focus-event.js'
|
||||||
import {
|
import {
|
||||||
INITIAL_STATE,
|
INITIAL_STATE,
|
||||||
type ParsedInput,
|
type ParsedInput,
|
||||||
type ParsedKey,
|
type ParsedKey,
|
||||||
type ParsedMouse,
|
type ParsedMouse,
|
||||||
parseMultipleKeypresses,
|
parseMultipleKeypresses,
|
||||||
} from '../parse-keypress.js'
|
} from '../core/parse-keypress.js'
|
||||||
import reconciler from '../reconciler.js'
|
import reconciler from '../core/reconciler.js'
|
||||||
import {
|
import {
|
||||||
finishSelection,
|
finishSelection,
|
||||||
hasSelection,
|
hasSelection,
|
||||||
type SelectionState,
|
type SelectionState,
|
||||||
startSelection,
|
startSelection,
|
||||||
} from '../selection.js'
|
} from '../core/selection.js'
|
||||||
import {
|
import {
|
||||||
isXtermJs,
|
isXtermJs,
|
||||||
setXtversionName,
|
setXtversionName,
|
||||||
supportsExtendedKeys,
|
supportsExtendedKeys,
|
||||||
} from '../terminal.js'
|
} from '../core/terminal.js'
|
||||||
import {
|
import {
|
||||||
getTerminalFocused,
|
getTerminalFocused,
|
||||||
setTerminalFocused,
|
setTerminalFocused,
|
||||||
} from '../terminal-focus-state.js'
|
} from '../core/terminal-focus-state.js'
|
||||||
import { TerminalQuerier, xtversion } from '../terminal-querier.js'
|
import { TerminalQuerier, xtversion } from '../core/terminal-querier.js'
|
||||||
import {
|
import {
|
||||||
DISABLE_KITTY_KEYBOARD,
|
DISABLE_KITTY_KEYBOARD,
|
||||||
DISABLE_MODIFY_OTHER_KEYS,
|
DISABLE_MODIFY_OTHER_KEYS,
|
||||||
@@ -39,7 +64,7 @@ import {
|
|||||||
ENABLE_MODIFY_OTHER_KEYS,
|
ENABLE_MODIFY_OTHER_KEYS,
|
||||||
FOCUS_IN,
|
FOCUS_IN,
|
||||||
FOCUS_OUT,
|
FOCUS_OUT,
|
||||||
} from '../termio/csi.js'
|
} from '../core/termio/csi.js'
|
||||||
import {
|
import {
|
||||||
DBP,
|
DBP,
|
||||||
DFE,
|
DFE,
|
||||||
@@ -48,7 +73,7 @@ import {
|
|||||||
EFE,
|
EFE,
|
||||||
HIDE_CURSOR,
|
HIDE_CURSOR,
|
||||||
SHOW_CURSOR,
|
SHOW_CURSOR,
|
||||||
} from '../termio/dec.js'
|
} from '../core/termio/dec.js'
|
||||||
import AppContext from './AppContext.js'
|
import AppContext from './AppContext.js'
|
||||||
import { ClockProvider } from './ClockContext.js'
|
import { ClockProvider } from './ClockContext.js'
|
||||||
import CursorDeclarationContext, {
|
import CursorDeclarationContext, {
|
||||||
@@ -292,7 +317,7 @@ export default class App extends PureComponent<Props, State> {
|
|||||||
// Both use the same stdin 'readable' + read() pattern, so they can't
|
// Both use the same stdin 'readable' + read() pattern, so they can't
|
||||||
// coexist -- our handler would drain stdin before Ink's can see it.
|
// coexist -- our handler would drain stdin before Ink's can see it.
|
||||||
// The buffered text is preserved for REPL.tsx via consumeEarlyInput().
|
// The buffered text is preserved for REPL.tsx via consumeEarlyInput().
|
||||||
stopCapturingEarlyInput()
|
defaultCallbacks.stopCapturingEarlyInput()
|
||||||
stdin.ref()
|
stdin.ref()
|
||||||
stdin.setRawMode(true)
|
stdin.setRawMode(true)
|
||||||
stdin.addListener('readable', this.handleReadable)
|
stdin.addListener('readable', this.handleReadable)
|
||||||
@@ -324,9 +349,9 @@ export default class App extends PureComponent<Props, State> {
|
|||||||
]).then(([r]) => {
|
]).then(([r]) => {
|
||||||
if (r) {
|
if (r) {
|
||||||
setXtversionName(r.name)
|
setXtversionName(r.name)
|
||||||
logForDebugging(`XTVERSION: terminal identified as "${r.name}"`)
|
defaultCallbacks.logForDebugging(`XTVERSION: terminal identified as "${r.name}"`)
|
||||||
} else {
|
} else {
|
||||||
logForDebugging('XTVERSION: no reply (terminal ignored query)')
|
defaultCallbacks.logForDebugging('XTVERSION: no reply (terminal ignored query)')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -436,7 +461,7 @@ export default class App extends PureComponent<Props, State> {
|
|||||||
// permanently wedge the stream: data stays buffered and 'readable'
|
// permanently wedge the stream: data stays buffered and 'readable'
|
||||||
// never re-emits. Catching here ensures the stream stays healthy so
|
// never re-emits. Catching here ensures the stream stays healthy so
|
||||||
// subsequent keystrokes are still delivered.
|
// subsequent keystrokes are still delivered.
|
||||||
logError(error)
|
defaultCallbacks.logError(error)
|
||||||
|
|
||||||
// Re-attach the listener in case the exception detached it.
|
// Re-attach the listener in case the exception detached it.
|
||||||
// Bun may remove the listener after an error; without this,
|
// Bun may remove the listener after an error; without this,
|
||||||
@@ -446,7 +471,7 @@ export default class App extends PureComponent<Props, State> {
|
|||||||
this.rawModeEnabledCount > 0 &&
|
this.rawModeEnabledCount > 0 &&
|
||||||
!stdin.listeners('readable').includes(this.handleReadable)
|
!stdin.listeners('readable').includes(this.handleReadable)
|
||||||
) {
|
) {
|
||||||
logForDebugging(
|
defaultCallbacks.logForDebugging(
|
||||||
'handleReadable: re-attaching stdin readable listener after error recovery',
|
'handleReadable: re-attaching stdin readable listener after error recovery',
|
||||||
{ level: 'warn' },
|
{ level: 'warn' },
|
||||||
)
|
)
|
||||||
@@ -556,7 +581,7 @@ function processKeysInBatch(
|
|||||||
!((i.button & 0x20) !== 0 && (i.button & 0x03) === 3)),
|
!((i.button & 0x20) !== 0 && (i.button & 0x03) === 3)),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
updateLastInteractionTime()
|
defaultCallbacks.updateLastInteractionTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
@@ -625,7 +650,7 @@ function processKeysInBatch(
|
|||||||
export function handleMouseEvent(app: App, m: ParsedMouse): void {
|
export function handleMouseEvent(app: App, m: ParsedMouse): void {
|
||||||
// Allow disabling click handling while keeping wheel scroll (which goes
|
// Allow disabling click handling while keeping wheel scroll (which goes
|
||||||
// through the keybinding system as 'wheelup'/'wheeldown', not here).
|
// through the keybinding system as 'wheelup'/'wheeldown', not here).
|
||||||
if (isMouseClicksDisabled()) return
|
if (defaultCallbacks.isMouseClicksDisabled()) return
|
||||||
|
|
||||||
const sel = app.props.selection
|
const sel = app.props.selection
|
||||||
// Terminal coords are 1-indexed; screen buffer is 0-indexed
|
// Terminal coords are 1-indexed; screen buffer is 0-indexed
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import React, { type PropsWithChildren, type Ref } from 'react'
|
import React, { type PropsWithChildren, type Ref } from 'react'
|
||||||
import type { Except } from 'type-fest'
|
import type { Except } from 'type-fest'
|
||||||
import type { DOMElement } from '../dom.js'
|
import type { DOMElement } from '../core/dom.js'
|
||||||
import type { ClickEvent } from '../events/click-event.js'
|
import type { ClickEvent } from '../core/events/click-event.js'
|
||||||
import type { FocusEvent } from '../events/focus-event.js'
|
import type { FocusEvent } from '../core/events/focus-event.js'
|
||||||
import type { KeyboardEvent } from '../events/keyboard-event.js'
|
import type { KeyboardEvent } from '../core/events/keyboard-event.js'
|
||||||
import type { Styles } from '../styles.js'
|
import type { Styles } from '../core/styles.js'
|
||||||
import * as warn from '../warn.js'
|
import * as warn from '../core/warn.js'
|
||||||
|
|
||||||
export type Props = Except<Styles, 'textWrap'> & {
|
export type Props = Except<Styles, 'textWrap'> & {
|
||||||
ref?: Ref<DOMElement>
|
ref?: Ref<DOMElement>
|
||||||
@@ -6,11 +6,11 @@ import React, {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import type { Except } from 'type-fest'
|
import type { Except } from 'type-fest'
|
||||||
import type { DOMElement } from '../dom.js'
|
import type { DOMElement } from '../core/dom.js'
|
||||||
import type { ClickEvent } from '../events/click-event.js'
|
import type { ClickEvent } from '../core/events/click-event.js'
|
||||||
import type { FocusEvent } from '../events/focus-event.js'
|
import type { FocusEvent } from '../core/events/focus-event.js'
|
||||||
import type { KeyboardEvent } from '../events/keyboard-event.js'
|
import type { KeyboardEvent } from '../core/events/keyboard-event.js'
|
||||||
import type { Styles } from '../styles.js'
|
import type { Styles } from '../core/styles.js'
|
||||||
import Box from './Box.js'
|
import Box from './Box.js'
|
||||||
|
|
||||||
type ButtonState = {
|
type ButtonState = {
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { createContext, useEffect, useState } from 'react'
|
import React, { createContext, useEffect, useState } from 'react'
|
||||||
import { FRAME_INTERVAL_MS } from '../constants.js'
|
import { FRAME_INTERVAL_MS } from '../core/constants.js'
|
||||||
import { useTerminalFocus } from '../hooks/use-terminal-focus.js'
|
import { useTerminalFocus } from '../hooks/use-terminal-focus.js'
|
||||||
|
|
||||||
export type Clock = {
|
export type Clock = {
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createContext } from 'react'
|
import { createContext } from 'react'
|
||||||
import type { DOMElement } from '../dom.js'
|
import type { DOMElement } from '../core/dom.js'
|
||||||
|
|
||||||
export type CursorDeclaration = {
|
export type CursorDeclaration = {
|
||||||
/** Display column (terminal cell width) within the declared node */
|
/** Display column (terminal cell width) within the declared node */
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { supportsHyperlinks } from '../supports-hyperlinks.js'
|
import { supportsHyperlinks } from '../core/supports-hyperlinks.js'
|
||||||
import Text from './Text.js'
|
import Text from './Text.js'
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
@@ -6,11 +6,10 @@ import React, {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import type { Except } from 'type-fest'
|
import type { Except } from 'type-fest'
|
||||||
import { markScrollActivity } from '../../bootstrap/state.js'
|
import type { DOMElement } from '../core/dom.js'
|
||||||
import type { DOMElement } from '../dom.js'
|
import { markDirty, scheduleRenderFrom } from '../core/dom.js'
|
||||||
import { markDirty, scheduleRenderFrom } from '../dom.js'
|
import { markCommitStart } from '../core/reconciler.js'
|
||||||
import { markCommitStart } from '../reconciler.js'
|
import type { Styles } from '../core/styles.js'
|
||||||
import type { Styles } from '../styles.js'
|
|
||||||
import Box from './Box.js'
|
import Box from './Box.js'
|
||||||
|
|
||||||
export type ScrollBoxHandle = {
|
export type ScrollBoxHandle = {
|
||||||
@@ -116,7 +115,7 @@ function ScrollBox({
|
|||||||
// Signal background intervals (IDE poll, LSP poll, GCS fetch, orphan
|
// Signal background intervals (IDE poll, LSP poll, GCS fetch, orphan
|
||||||
// check) to skip their next tick — they compete for the event loop and
|
// check) to skip their next tick — they compete for the event loop and
|
||||||
// contributed to 1402ms max frame gaps during scroll drain.
|
// contributed to 1402ms max frame gaps during scroll drain.
|
||||||
markScrollActivity()
|
// noop — injected by business layer via onScrollActivity callback
|
||||||
markDirty(el)
|
markDirty(el)
|
||||||
markCommitStart()
|
markCommitStart()
|
||||||
notify()
|
notify()
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createContext } from 'react'
|
import { createContext } from 'react'
|
||||||
import { EventEmitter } from '../events/emitter.js'
|
import { EventEmitter } from '../core/events/emitter.js'
|
||||||
import type { TerminalQuerier } from '../terminal-querier.js'
|
import type { TerminalQuerier } from '../core/terminal-querier.js'
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
/**
|
/**
|
||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
getTerminalFocusState,
|
getTerminalFocusState,
|
||||||
subscribeTerminalFocus,
|
subscribeTerminalFocus,
|
||||||
type TerminalFocusState,
|
type TerminalFocusState,
|
||||||
} from '../terminal-focus-state.js'
|
} from '../core/terminal-focus-state.js'
|
||||||
|
|
||||||
export type { TerminalFocusState }
|
export type { TerminalFocusState }
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import type { Color, Styles, TextStyles } from '../styles.js'
|
import type { Color, Styles, TextStyles } from '../core/styles.js'
|
||||||
|
|
||||||
type BaseProps = {
|
type BaseProps = {
|
||||||
/**
|
/**
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import Link from './components/Link.js'
|
import Link from '../components/Link.js'
|
||||||
import Text from './components/Text.js'
|
import Text from '../components/Text.js'
|
||||||
import type { Color } from './styles.js'
|
import type { Color } from './styles.js'
|
||||||
import {
|
import {
|
||||||
type NamedColor,
|
type NamedColor,
|
||||||
@@ -4,10 +4,14 @@ import {
|
|||||||
DiscreteEventPriority,
|
DiscreteEventPriority,
|
||||||
NoEventPriority,
|
NoEventPriority,
|
||||||
} from 'react-reconciler/constants.js'
|
} from 'react-reconciler/constants.js'
|
||||||
import { logError } from '../../utils/log.js'
|
|
||||||
import { HANDLER_FOR_EVENT } from './event-handlers.js'
|
import { HANDLER_FOR_EVENT } from './event-handlers.js'
|
||||||
import type { EventTarget, TerminalEvent } from './terminal-event.js'
|
import type { EventTarget, TerminalEvent } from './terminal-event.js'
|
||||||
|
|
||||||
|
// logError stub — replaced from business dependency; use injected logger in production
|
||||||
|
const logError = (error: unknown): void => {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
|
||||||
// --
|
// --
|
||||||
|
|
||||||
type DispatchListener = {
|
type DispatchListener = {
|
||||||
@@ -12,17 +12,14 @@ import React, { type ReactNode } from 'react'
|
|||||||
import type { FiberRoot } from 'react-reconciler'
|
import type { FiberRoot } from 'react-reconciler'
|
||||||
import { ConcurrentRoot } from 'react-reconciler/constants.js'
|
import { ConcurrentRoot } from 'react-reconciler/constants.js'
|
||||||
import { onExit } from 'signal-exit'
|
import { onExit } from 'signal-exit'
|
||||||
import { flushInteractionTime } from 'src/bootstrap/state.js'
|
import { getYogaCounters } from './yoga-layout/index.js'
|
||||||
import { getYogaCounters } from 'src/native-ts/yoga-layout/index.js'
|
|
||||||
import { logForDebugging } from 'src/utils/debug.js'
|
|
||||||
import { logError } from 'src/utils/log.js'
|
|
||||||
import { format } from 'util'
|
import { format } from 'util'
|
||||||
import { colorize } from './colorize.js'
|
import { colorize } from './colorize.js'
|
||||||
import App from './components/App.js'
|
import App from '../components/App.js'
|
||||||
import type {
|
import type {
|
||||||
CursorDeclaration,
|
CursorDeclaration,
|
||||||
CursorDeclarationSetter,
|
CursorDeclarationSetter,
|
||||||
} from './components/CursorDeclarationContext.js'
|
} from '../components/CursorDeclarationContext.js'
|
||||||
import { FRAME_INTERVAL_MS } from './constants.js'
|
import { FRAME_INTERVAL_MS } from './constants.js'
|
||||||
import * as dom from './dom.js'
|
import * as dom from './dom.js'
|
||||||
import { KeyboardEvent } from './events/keyboard-event.js'
|
import { KeyboardEvent } from './events/keyboard-event.js'
|
||||||
@@ -116,7 +113,7 @@ import {
|
|||||||
supportsTabStatus,
|
supportsTabStatus,
|
||||||
wrapForMultiplexer,
|
wrapForMultiplexer,
|
||||||
} from './termio/osc.js'
|
} from './termio/osc.js'
|
||||||
import { TerminalWriteProvider } from './useTerminalNotification.js'
|
import { TerminalWriteProvider } from '../hooks/useTerminalNotification.js'
|
||||||
|
|
||||||
// Alt-screen: renderer.ts sets cursor.visible = !isTTY || screen.height===0,
|
// Alt-screen: renderer.ts sets cursor.visible = !isTTY || screen.height===0,
|
||||||
// which is always false in alt-screen (TTY + content fills screen).
|
// which is always false in alt-screen (TTY + content fills screen).
|
||||||
@@ -140,6 +137,11 @@ function makeAltScreenParkPatch(terminalRows: number) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Logger = {
|
||||||
|
debug(message: string, options?: { level?: string }): void
|
||||||
|
error(error: Error | unknown): void
|
||||||
|
}
|
||||||
|
|
||||||
export type Options = {
|
export type Options = {
|
||||||
stdout: NodeJS.WriteStream
|
stdout: NodeJS.WriteStream
|
||||||
stdin: NodeJS.ReadStream
|
stdin: NodeJS.ReadStream
|
||||||
@@ -148,6 +150,16 @@ export type Options = {
|
|||||||
patchConsole: boolean
|
patchConsole: boolean
|
||||||
waitUntilExit?: () => Promise<void>
|
waitUntilExit?: () => Promise<void>
|
||||||
onFrame?: (event: FrameEvent) => void
|
onFrame?: (event: FrameEvent) => void
|
||||||
|
/** Called before each render cycle. Replaces flushInteractionTime(). */
|
||||||
|
onBeforeRender?: () => void
|
||||||
|
/** Injected logger. Replaces logForDebugging / logError imports. */
|
||||||
|
logger?: Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
/** No-op logger used when no logger is injected. */
|
||||||
|
const noopLogger: Logger = {
|
||||||
|
debug() {},
|
||||||
|
error() {},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Ink {
|
export default class Ink {
|
||||||
@@ -240,9 +252,11 @@ export default class Ink {
|
|||||||
// for log-update's relative-move invariants). Alt-screen doesn't need
|
// for log-update's relative-move invariants). Alt-screen doesn't need
|
||||||
// this — every frame begins with CSI H. null = no move emitted last frame.
|
// this — every frame begins with CSI H. null = no move emitted last frame.
|
||||||
private displayCursor: { x: number; y: number } | null = null
|
private displayCursor: { x: number; y: number } | null = null
|
||||||
|
private readonly logger: Logger
|
||||||
|
|
||||||
constructor(private readonly options: Options) {
|
constructor(private readonly options: Options) {
|
||||||
autoBind(this)
|
autoBind(this)
|
||||||
|
this.logger = options.logger ?? noopLogger
|
||||||
|
|
||||||
if (this.options.patchConsole) {
|
if (this.options.patchConsole) {
|
||||||
this.restoreConsole = this.patchConsole()
|
this.restoreConsole = this.patchConsole()
|
||||||
@@ -533,7 +547,7 @@ export default class Ink {
|
|||||||
// Date.now() at most once per frame instead of once per keypress.
|
// Date.now() at most once per frame instead of once per keypress.
|
||||||
// Done before the render to avoid dirtying state that would trigger
|
// Done before the render to avoid dirtying state that would trigger
|
||||||
// an extra React re-render cycle.
|
// an extra React re-render cycle.
|
||||||
flushInteractionTime()
|
this.options.onBeforeRender?.()
|
||||||
|
|
||||||
const renderStart = performance.now()
|
const renderStart = performance.now()
|
||||||
const terminalWidth = this.options.stdout.columns || 80
|
const terminalWidth = this.options.stdout.columns || 80
|
||||||
@@ -754,7 +768,7 @@ export default class Ink {
|
|||||||
this.rootNode,
|
this.rootNode,
|
||||||
patch.debug.triggerY,
|
patch.debug.triggerY,
|
||||||
)
|
)
|
||||||
logForDebugging(
|
this.logger.debug(
|
||||||
`[REPAINT] full reset · ${patch.reason} · row ${patch.debug.triggerY}\n` +
|
`[REPAINT] full reset · ${patch.reason} · row ${patch.debug.triggerY}\n` +
|
||||||
` prev: "${patch.debug.prevLine}"\n` +
|
` prev: "${patch.debug.prevLine}"\n` +
|
||||||
` next: "${patch.debug.nextLine}"\n` +
|
` next: "${patch.debug.nextLine}"\n` +
|
||||||
@@ -1274,7 +1288,7 @@ export default class Ink {
|
|||||||
// correctly. One extra paint of this message, but correct > fast.
|
// correctly. One extra paint of this message, but correct > fast.
|
||||||
dom.markDirty(el)
|
dom.markDirty(el)
|
||||||
const positions = scanPositions(rendered, this.searchHighlightQuery)
|
const positions = scanPositions(rendered, this.searchHighlightQuery)
|
||||||
logForDebugging(
|
this.logger.debug(
|
||||||
`scanElementSubtree: q='${this.searchHighlightQuery}' ` +
|
`scanElementSubtree: q='${this.searchHighlightQuery}' ` +
|
||||||
`el=${width}x${height}@(${elLeft},${elTop}) n=${positions.length} ` +
|
`el=${width}x${height}@(${elLeft},${elTop}) n=${positions.length} ` +
|
||||||
`[${positions
|
`[${positions
|
||||||
@@ -1580,7 +1594,7 @@ export default class Ink {
|
|||||||
// Store and remove all 'readable' event listeners temporarily
|
// Store and remove all 'readable' event listeners temporarily
|
||||||
// This prevents Ink from consuming stdin while the editor is active
|
// This prevents Ink from consuming stdin while the editor is active
|
||||||
const readableListeners = stdin.listeners('readable')
|
const readableListeners = stdin.listeners('readable')
|
||||||
logForDebugging(
|
this.logger.debug(
|
||||||
`[stdin] suspendStdin: removing ${readableListeners.length} readable listener(s), wasRawMode=${(stdin as NodeJS.ReadStream & { isRaw?: boolean }).isRaw ?? false}`,
|
`[stdin] suspendStdin: removing ${readableListeners.length} readable listener(s), wasRawMode=${(stdin as NodeJS.ReadStream & { isRaw?: boolean }).isRaw ?? false}`,
|
||||||
)
|
)
|
||||||
readableListeners.forEach(listener => {
|
readableListeners.forEach(listener => {
|
||||||
@@ -1610,12 +1624,12 @@ export default class Ink {
|
|||||||
|
|
||||||
// Re-attach all the stored listeners
|
// Re-attach all the stored listeners
|
||||||
if (this.stdinListeners.length === 0 && !this.wasRawMode) {
|
if (this.stdinListeners.length === 0 && !this.wasRawMode) {
|
||||||
logForDebugging(
|
this.logger.debug(
|
||||||
'[stdin] resumeStdin: called with no stored listeners and wasRawMode=false (possible desync)',
|
'[stdin] resumeStdin: called with no stored listeners and wasRawMode=false (possible desync)',
|
||||||
{ level: 'warn' },
|
{ level: 'warn' },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
logForDebugging(
|
this.logger.debug(
|
||||||
`[stdin] resumeStdin: re-attaching ${this.stdinListeners.length} listener(s), wasRawMode=${this.wasRawMode}`,
|
`[stdin] resumeStdin: re-attaching ${this.stdinListeners.length} listener(s), wasRawMode=${this.wasRawMode}`,
|
||||||
)
|
)
|
||||||
this.stdinListeners.forEach(({ event, listener }) => {
|
this.stdinListeners.forEach(({ event, listener }) => {
|
||||||
@@ -1833,9 +1847,9 @@ export default class Ink {
|
|||||||
const con = console
|
const con = console
|
||||||
const originals: Partial<Record<keyof Console, Console[keyof Console]>> = {}
|
const originals: Partial<Record<keyof Console, Console[keyof Console]>> = {}
|
||||||
const toDebug = (...args: unknown[]) =>
|
const toDebug = (...args: unknown[]) =>
|
||||||
logForDebugging(`console.log: ${format(...args)}`)
|
this.logger.debug(`console.log: ${format(...args)}`)
|
||||||
const toError = (...args: unknown[]) =>
|
const toError = (...args: unknown[]) =>
|
||||||
logError(new Error(`console.error: ${format(...args)}`))
|
this.logger.error(new Error(`console.error: ${format(...args)}`))
|
||||||
for (const m of CONSOLE_STDOUT_METHODS) {
|
for (const m of CONSOLE_STDOUT_METHODS) {
|
||||||
originals[m] = con[m]
|
originals[m] = con[m]
|
||||||
con[m] = toDebug
|
con[m] = toDebug
|
||||||
@@ -1873,7 +1887,7 @@ export default class Ink {
|
|||||||
cb?: (err?: Error) => void,
|
cb?: (err?: Error) => void,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
const callback = typeof encodingOrCb === 'function' ? encodingOrCb : cb
|
const callback = typeof encodingOrCb === 'function' ? encodingOrCb : cb
|
||||||
// Reentrancy guard: logForDebugging → writeToStderr → here. Pass
|
// Reentrancy guard: logger.debug → writeToStderr → here. Pass
|
||||||
// through to the original so --debug-to-stderr still works and we
|
// through to the original so --debug-to-stderr still works and we
|
||||||
// don't stack-overflow.
|
// don't stack-overflow.
|
||||||
if (reentered) {
|
if (reentered) {
|
||||||
@@ -1887,7 +1901,7 @@ export default class Ink {
|
|||||||
typeof chunk === 'string'
|
typeof chunk === 'string'
|
||||||
? chunk
|
? chunk
|
||||||
: Buffer.from(chunk).toString('utf8')
|
: Buffer.from(chunk).toString('utf8')
|
||||||
logForDebugging(`[stderr] ${text}`, { level: 'warn' })
|
this.logger.debug(`[stderr] ${text}`, { level: 'warn' })
|
||||||
if (this.altScreenActive && !this.isUnmounted && !this.isPaused) {
|
if (this.altScreenActive && !this.isUnmounted && !this.isPaused) {
|
||||||
this.prevFrameContaminated = true
|
this.prevFrameContaminated = true
|
||||||
this.scheduleRender()
|
this.scheduleRender()
|
||||||
@@ -11,7 +11,7 @@ import Yoga, {
|
|||||||
PositionType,
|
PositionType,
|
||||||
Wrap,
|
Wrap,
|
||||||
type Node as YogaNode,
|
type Node as YogaNode,
|
||||||
} from 'src/native-ts/yoga-layout/index.js'
|
} from '../yoga-layout/index.js'
|
||||||
import {
|
import {
|
||||||
type LayoutAlign,
|
type LayoutAlign,
|
||||||
LayoutDisplay,
|
LayoutDisplay,
|
||||||
@@ -3,7 +3,8 @@ import {
|
|||||||
ansiCodesToString,
|
ansiCodesToString,
|
||||||
diffAnsiCodes,
|
diffAnsiCodes,
|
||||||
} from '@alcalzone/ansi-tokenize'
|
} from '@alcalzone/ansi-tokenize'
|
||||||
import { logForDebugging } from '../utils/debug.js'
|
/** Debug logger — no-op placeholder until proper logger injection is added */
|
||||||
|
const logForDebugging = (_message: string) => {}
|
||||||
import type { Diff, FlickerReason, Frame } from './frame.js'
|
import type { Diff, FlickerReason, Frame } from './frame.js'
|
||||||
import type { Point } from './layout/geometry.js'
|
import type { Point } from './layout/geometry.js'
|
||||||
import {
|
import {
|
||||||
@@ -4,9 +4,11 @@ import {
|
|||||||
styledCharsFromTokens,
|
styledCharsFromTokens,
|
||||||
tokenize,
|
tokenize,
|
||||||
} from '@alcalzone/ansi-tokenize'
|
} from '@alcalzone/ansi-tokenize'
|
||||||
import { logForDebugging } from '../utils/debug.js'
|
import { getGraphemeSegmenter } from './utils/grapheme.js'
|
||||||
import { getGraphemeSegmenter } from '../utils/intl.js'
|
import sliceAnsi from './utils/sliceAnsi.js'
|
||||||
import sliceAnsi from '../utils/sliceAnsi.js'
|
|
||||||
|
/** Debug logger — no-op placeholder until proper logger injection is added */
|
||||||
|
const logForDebugging = (_message: string) => {}
|
||||||
import { reorderBidi } from './bidi.js'
|
import { reorderBidi } from './bidi.js'
|
||||||
import { type Rectangle, unionRect } from './layout/geometry.js'
|
import { type Rectangle, unionRect } from './layout/geometry.js'
|
||||||
import {
|
import {
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
/* eslint-disable custom-rules/no-top-level-side-effects */
|
/* eslint-disable custom-rules/no-top-level-side-effects */
|
||||||
|
|
||||||
import { appendFileSync } from 'fs'
|
|
||||||
import createReconciler from 'react-reconciler'
|
import createReconciler from 'react-reconciler'
|
||||||
import { getYogaCounters } from 'src/native-ts/yoga-layout/index.js'
|
import { getYogaCounters } from './yoga-layout/index.js'
|
||||||
import { isEnvTruthy } from '../utils/envUtils.js'
|
|
||||||
import {
|
import {
|
||||||
appendChildNode,
|
appendChildNode,
|
||||||
clearYogaNodeReferences,
|
clearYogaNodeReferences,
|
||||||
@@ -179,15 +177,16 @@ export function getOwnerChain(fiber: unknown): string[] {
|
|||||||
let debugRepaints: boolean | undefined
|
let debugRepaints: boolean | undefined
|
||||||
export function isDebugRepaintsEnabled(): boolean {
|
export function isDebugRepaintsEnabled(): boolean {
|
||||||
if (debugRepaints === undefined) {
|
if (debugRepaints === undefined) {
|
||||||
debugRepaints = isEnvTruthy(process.env.CLAUDE_CODE_DEBUG_REPAINTS)
|
debugRepaints = process.env.CLAUDE_CODE_DEBUG_REPAINTS === '1'
|
||||||
}
|
}
|
||||||
return debugRepaints
|
return debugRepaints
|
||||||
}
|
}
|
||||||
|
|
||||||
export const dispatcher = new Dispatcher()
|
export const dispatcher = new Dispatcher()
|
||||||
|
|
||||||
// --- COMMIT INSTRUMENTATION (temp debugging) ---
|
// --- COMMIT INSTRUMENTATION (debug logging) ---
|
||||||
// eslint-disable-next-line custom-rules/no-process-env-top-level -- debug instrumentation, read-once is fine
|
// Uses console.warn instead of fs.appendFileSync to avoid filesystem dependencies.
|
||||||
|
// Set CLAUDE_CODE_COMMIT_LOG=1 to enable debug logging to stderr.
|
||||||
const COMMIT_LOG = process.env.CLAUDE_CODE_COMMIT_LOG
|
const COMMIT_LOG = process.env.CLAUDE_CODE_COMMIT_LOG
|
||||||
let _commits = 0
|
let _commits = 0
|
||||||
let _lastLog = 0
|
let _lastLog = 0
|
||||||
@@ -195,6 +194,12 @@ let _lastCommitAt = 0
|
|||||||
let _maxGapMs = 0
|
let _maxGapMs = 0
|
||||||
let _createCount = 0
|
let _createCount = 0
|
||||||
let _prepareAt = 0
|
let _prepareAt = 0
|
||||||
|
|
||||||
|
/** Debug log helper — replaces fs.appendFileSync with console.warn. */
|
||||||
|
function debugLog(message: string): void {
|
||||||
|
// biome-ignore lint/suspicious/noConsole: debug instrumentation
|
||||||
|
console.warn(`[ink-commit] ${message}`)
|
||||||
|
}
|
||||||
// --- END ---
|
// --- END ---
|
||||||
|
|
||||||
// --- SCROLL PROFILING (bench/scroll-e2e.sh reads via getLastYogaMs) ---
|
// --- SCROLL PROFILING (bench/scroll-e2e.sh reads via getLastYogaMs) ---
|
||||||
@@ -255,18 +260,14 @@ const reconciler = createReconciler<
|
|||||||
_lastCommitAt = now
|
_lastCommitAt = now
|
||||||
const reconcileMs = _prepareAt > 0 ? now - _prepareAt : 0
|
const reconcileMs = _prepareAt > 0 ? now - _prepareAt : 0
|
||||||
if (gap > 30 || reconcileMs > 20 || _createCount > 50) {
|
if (gap > 30 || reconcileMs > 20 || _createCount > 50) {
|
||||||
// eslint-disable-next-line custom-rules/no-sync-fs -- debug instrumentation
|
debugLog(
|
||||||
appendFileSync(
|
`${now.toFixed(1)} gap=${gap.toFixed(1)}ms reconcile=${reconcileMs.toFixed(1)}ms creates=${_createCount}`,
|
||||||
COMMIT_LOG,
|
|
||||||
`${now.toFixed(1)} gap=${gap.toFixed(1)}ms reconcile=${reconcileMs.toFixed(1)}ms creates=${_createCount}\n`,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_createCount = 0
|
_createCount = 0
|
||||||
if (now - _lastLog > 1000) {
|
if (now - _lastLog > 1000) {
|
||||||
// eslint-disable-next-line custom-rules/no-sync-fs -- debug instrumentation
|
debugLog(
|
||||||
appendFileSync(
|
`${now.toFixed(1)} commits=${_commits}/s maxGap=${_maxGapMs.toFixed(1)}ms`,
|
||||||
COMMIT_LOG,
|
|
||||||
`${now.toFixed(1)} commits=${_commits}/s maxGap=${_maxGapMs.toFixed(1)}ms\n`,
|
|
||||||
)
|
)
|
||||||
_commits = 0
|
_commits = 0
|
||||||
_maxGapMs = 0
|
_maxGapMs = 0
|
||||||
@@ -281,10 +282,8 @@ const reconciler = createReconciler<
|
|||||||
const layoutMs = performance.now() - _t0
|
const layoutMs = performance.now() - _t0
|
||||||
if (layoutMs > 20) {
|
if (layoutMs > 20) {
|
||||||
const c = getYogaCounters()
|
const c = getYogaCounters()
|
||||||
// eslint-disable-next-line custom-rules/no-sync-fs -- debug instrumentation
|
debugLog(
|
||||||
appendFileSync(
|
`${_t0.toFixed(1)} SLOW_YOGA ${layoutMs.toFixed(1)}ms visited=${c.visited} measured=${c.measured} hits=${c.cacheHits} live=${c.live}`,
|
||||||
COMMIT_LOG,
|
|
||||||
`${_t0.toFixed(1)} SLOW_YOGA ${layoutMs.toFixed(1)}ms visited=${c.visited} measured=${c.measured} hits=${c.cacheHits} live=${c.live}\n`,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -305,10 +304,8 @@ const reconciler = createReconciler<
|
|||||||
if (COMMIT_LOG) {
|
if (COMMIT_LOG) {
|
||||||
const renderMs = performance.now() - _tr
|
const renderMs = performance.now() - _tr
|
||||||
if (renderMs > 10) {
|
if (renderMs > 10) {
|
||||||
// eslint-disable-next-line custom-rules/no-sync-fs -- debug instrumentation
|
debugLog(
|
||||||
appendFileSync(
|
`${_tr.toFixed(1)} SLOW_PAINT ${renderMs.toFixed(1)}ms`,
|
||||||
COMMIT_LOG,
|
|
||||||
`${_tr.toFixed(1)} SLOW_PAINT ${renderMs.toFixed(1)}ms\n`,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import noop from 'lodash-es/noop.js'
|
import noop from 'lodash-es/noop.js'
|
||||||
import type { ReactElement } from 'react'
|
import type { ReactElement } from 'react'
|
||||||
import { LegacyRoot } from 'react-reconciler/constants.js'
|
import { LegacyRoot } from 'react-reconciler/constants.js'
|
||||||
import { logForDebugging } from '../utils/debug.js'
|
/** Debug logger — no-op placeholder until proper logger injection is added */
|
||||||
|
const logForDebugging = (_message: string) => {}
|
||||||
import { createNode, type DOMElement } from './dom.js'
|
import { createNode, type DOMElement } from './dom.js'
|
||||||
import { FocusManager } from './focus.js'
|
import { FocusManager } from './focus.js'
|
||||||
import Output from './output.js'
|
import Output from './output.js'
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { logForDebugging } from 'src/utils/debug.js'
|
/** Debug logger — no-op placeholder until proper logger injection is added */
|
||||||
|
const logForDebugging = (_message: string, _opts?: { level?: string }) => {}
|
||||||
import { type DOMElement, markDirty } from './dom.js'
|
import { type DOMElement, markDirty } from './dom.js'
|
||||||
import type { Frame } from './frame.js'
|
import type { Frame } from './frame.js'
|
||||||
import { consumeAbsoluteRemovedFlag } from './node-cache.js'
|
import { consumeAbsoluteRemovedFlag } from './node-cache.js'
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
import { logForDebugging } from 'src/utils/debug.js'
|
|
||||||
import { Stream } from 'stream'
|
import { Stream } from 'stream'
|
||||||
import type { FrameEvent } from './frame.js'
|
import type { FrameEvent } from './frame.js'
|
||||||
import Ink, { type Options as InkOptions } from './ink.js'
|
import Ink, { type Options as InkOptions } from './ink.js'
|
||||||
@@ -114,9 +113,12 @@ const wrappedRender = async (
|
|||||||
// write overwrites scrollback instead of appending below the logo.
|
// write overwrites scrollback instead of appending below the logo.
|
||||||
await Promise.resolve()
|
await Promise.resolve()
|
||||||
const instance = renderSync(node, options)
|
const instance = renderSync(node, options)
|
||||||
logForDebugging(
|
if (process.env.CLAUDE_CODE_DEBUG_REPAINTS === '1') {
|
||||||
`[render] first ink render: ${Math.round(process.uptime() * 1000)}ms since process start`,
|
// biome-ignore lint/suspicious/noConsole: debug instrumentation
|
||||||
)
|
console.warn(
|
||||||
|
`[render] first ink render: ${Math.round(process.uptime() * 1000)}ms since process start`,
|
||||||
|
)
|
||||||
|
}
|
||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import emojiRegex from 'emoji-regex'
|
import emojiRegex from 'emoji-regex'
|
||||||
import { eastAsianWidth } from 'get-east-asian-width'
|
import { eastAsianWidth } from 'get-east-asian-width'
|
||||||
import stripAnsi from 'strip-ansi'
|
import stripAnsi from 'strip-ansi'
|
||||||
import { getGraphemeSegmenter } from '../utils/intl.js'
|
import { getGraphemeSegmenter } from './utils/grapheme.js'
|
||||||
|
|
||||||
const EMOJI_REGEX = emojiRegex()
|
const EMOJI_REGEX = emojiRegex()
|
||||||
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
import { coerce } from 'semver'
|
import { coerce, gte } from 'semver'
|
||||||
import type { Writable } from 'stream'
|
import type { Writable } from 'stream'
|
||||||
import { env } from '../utils/env.js'
|
|
||||||
import { gte } from '../utils/semver.js'
|
|
||||||
import { getClearTerminalSequence } from './clearTerminal.js'
|
import { getClearTerminalSequence } from './clearTerminal.js'
|
||||||
import type { Diff } from './frame.js'
|
import type { Diff } from './frame.js'
|
||||||
import { cursorMove, cursorTo, eraseLines } from './termio/csi.js'
|
import { cursorMove, cursorTo, eraseLines } from './termio/csi.js'
|
||||||
@@ -165,7 +163,7 @@ const EXTENDED_KEYS_TERMINALS = [
|
|||||||
/** True if this terminal correctly handles extended key reporting
|
/** True if this terminal correctly handles extended key reporting
|
||||||
* (Kitty keyboard protocol + xterm modifyOtherKeys). */
|
* (Kitty keyboard protocol + xterm modifyOtherKeys). */
|
||||||
export function supportsExtendedKeys(): boolean {
|
export function supportsExtendedKeys(): boolean {
|
||||||
return EXTENDED_KEYS_TERMINALS.includes(env.terminal ?? '')
|
return EXTENDED_KEYS_TERMINALS.includes(process.env.TERM_PROGRAM ?? '')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** True if the terminal scrolls the viewport when it receives cursor-up
|
/** True if the terminal scrolls the viewport when it receives cursor-up
|
||||||
@@ -3,9 +3,26 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Buffer } from 'buffer'
|
import { Buffer } from 'buffer'
|
||||||
import { env } from '../../utils/env.js'
|
import { execFile as nodeExecFile } from 'child_process'
|
||||||
import { execFileNoThrow } from '../../utils/execFileNoThrow.js'
|
|
||||||
import { BEL, ESC, ESC_TYPE, SEP } from './ansi.js'
|
import { BEL, ESC, ESC_TYPE, SEP } from './ansi.js'
|
||||||
|
|
||||||
|
/** Promise-based wrapper around child_process.execFile */
|
||||||
|
function execFileNoThrow(
|
||||||
|
command: string,
|
||||||
|
args: string[],
|
||||||
|
options: { input?: string; useCwd?: boolean; timeout?: number } = {},
|
||||||
|
): Promise<{ code: number; stdout: string; stderr: string }> {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const { input, timeout } = options
|
||||||
|
const proc = nodeExecFile(command, args, { timeout }, (error, stdout, stderr) => {
|
||||||
|
resolve({ code: error ? 1 : 0, stdout: stdout ?? '', stderr: stderr ?? '' })
|
||||||
|
})
|
||||||
|
if (input && proc.stdin) {
|
||||||
|
proc.stdin.write(input)
|
||||||
|
proc.stdin.end()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
import type { Action, Color, TabStatusAction } from './types.js'
|
import type { Action, Color, TabStatusAction } from './types.js'
|
||||||
|
|
||||||
export const OSC_PREFIX = ESC + String.fromCharCode(ESC_TYPE.OSC)
|
export const OSC_PREFIX = ESC + String.fromCharCode(ESC_TYPE.OSC)
|
||||||
@@ -16,7 +33,7 @@ export const ST = ESC + '\\'
|
|||||||
/** Generate an OSC sequence: ESC ] p1;p2;...;pN <terminator>
|
/** Generate an OSC sequence: ESC ] p1;p2;...;pN <terminator>
|
||||||
* Uses ST terminator for Kitty (avoids beeps), BEL for others */
|
* Uses ST terminator for Kitty (avoids beeps), BEL for others */
|
||||||
export function osc(...parts: (string | number)[]): string {
|
export function osc(...parts: (string | number)[]): string {
|
||||||
const terminator = env.terminal === 'kitty' ? ST : BEL
|
const terminator = process.env.TERM_PROGRAM === 'kitty' ? ST : BEL
|
||||||
return `${OSC_PREFIX}${parts.join(SEP)}${terminator}`
|
return `${OSC_PREFIX}${parts.join(SEP)}${terminator}`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
* - Style tracking: maintains current text style state
|
* - Style tracking: maintains current text style state
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getGraphemeSegmenter } from '../../utils/intl.js'
|
import { getGraphemeSegmenter } from '../utils/grapheme.js'
|
||||||
import { C0 } from './ansi.js'
|
import { C0 } from './ansi.js'
|
||||||
import { CSI, CURSOR_STYLES, ERASE_DISPLAY, ERASE_LINE_REGION } from './csi.js'
|
import { CSI, CURSOR_STYLES, ERASE_DISPLAY, ERASE_LINE_REGION } from './csi.js'
|
||||||
import { DEC } from './dec.js'
|
import { DEC } from './dec.js'
|
||||||
53
packages/@ant/ink/src/core/utils/grapheme.ts
Normal file
53
packages/@ant/ink/src/core/utils/grapheme.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* Shared Intl object instances with lazy initialization.
|
||||||
|
*
|
||||||
|
* Intl constructors are expensive (~0.05-0.1ms each), so we cache instances
|
||||||
|
* for reuse across the codebase instead of creating new ones each time.
|
||||||
|
* Lazy initialization ensures we only pay the cost when actually needed.
|
||||||
|
*
|
||||||
|
* Vendored from src/utils/intl.ts for package independence.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Segmenters for Unicode text processing (lazily initialized)
|
||||||
|
let graphemeSegmenter: Intl.Segmenter | null = null
|
||||||
|
let wordSegmenter: Intl.Segmenter | null = null
|
||||||
|
|
||||||
|
export function getGraphemeSegmenter(): Intl.Segmenter {
|
||||||
|
if (!graphemeSegmenter) {
|
||||||
|
graphemeSegmenter = new Intl.Segmenter(undefined, {
|
||||||
|
granularity: 'grapheme',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return graphemeSegmenter
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the first grapheme cluster from a string.
|
||||||
|
* Returns '' for empty strings.
|
||||||
|
*/
|
||||||
|
export function firstGrapheme(text: string): string {
|
||||||
|
if (!text) return ''
|
||||||
|
const segments = getGraphemeSegmenter().segment(text)
|
||||||
|
const first = segments[Symbol.iterator]().next().value
|
||||||
|
return first?.segment ?? ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the last grapheme cluster from a string.
|
||||||
|
* Returns '' for empty strings.
|
||||||
|
*/
|
||||||
|
export function lastGrapheme(text: string): string {
|
||||||
|
if (!text) return ''
|
||||||
|
let last = ''
|
||||||
|
for (const { segment } of getGraphemeSegmenter().segment(text)) {
|
||||||
|
last = segment
|
||||||
|
}
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getWordSegmenter(): Intl.Segmenter {
|
||||||
|
if (!wordSegmenter) {
|
||||||
|
wordSegmenter = new Intl.Segmenter(undefined, { granularity: 'word' })
|
||||||
|
}
|
||||||
|
return wordSegmenter
|
||||||
|
}
|
||||||
97
packages/@ant/ink/src/core/utils/sliceAnsi.ts
Normal file
97
packages/@ant/ink/src/core/utils/sliceAnsi.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
* Slice a string containing ANSI escape codes.
|
||||||
|
*
|
||||||
|
* Vendored from src/utils/sliceAnsi.ts for package independence.
|
||||||
|
* The only external dependency is stringWidth from the core package.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
type AnsiCode,
|
||||||
|
ansiCodesToString,
|
||||||
|
reduceAnsiCodes,
|
||||||
|
tokenize,
|
||||||
|
undoAnsiCodes,
|
||||||
|
} from '@alcalzone/ansi-tokenize'
|
||||||
|
import { stringWidth } from '../stringWidth.js'
|
||||||
|
|
||||||
|
// A code is an "end code" if its code equals its endCode (e.g., hyperlink close)
|
||||||
|
function isEndCode(code: AnsiCode): boolean {
|
||||||
|
return code.code === code.endCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter to only include "start codes" (not end codes)
|
||||||
|
function filterStartCodes(codes: AnsiCode[]): AnsiCode[] {
|
||||||
|
return codes.filter(c => !isEndCode(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slice a string containing ANSI escape codes.
|
||||||
|
*
|
||||||
|
* Unlike the slice-ansi package, this properly handles OSC 8 hyperlink
|
||||||
|
* sequences because @alcalzone/ansi-tokenize tokenizes them correctly.
|
||||||
|
*/
|
||||||
|
export default function sliceAnsi(
|
||||||
|
str: string,
|
||||||
|
start: number,
|
||||||
|
end?: number,
|
||||||
|
): string {
|
||||||
|
// Don't pass `end` to tokenize — it counts code units, not display cells,
|
||||||
|
// so it drops tokens early for text with zero-width combining marks.
|
||||||
|
const tokens = tokenize(str)
|
||||||
|
let activeCodes: AnsiCode[] = []
|
||||||
|
let position = 0
|
||||||
|
let result = ''
|
||||||
|
let include = false
|
||||||
|
|
||||||
|
for (const token of tokens) {
|
||||||
|
// Advance by display width, not code units. Combining marks (Devanagari
|
||||||
|
// matras, virama, diacritics) are width 0 — counting them via .length
|
||||||
|
// advanced position past `end` early and truncated the slice. Callers
|
||||||
|
// pass start/end in display cells (via stringWidth), so position must
|
||||||
|
// track the same units.
|
||||||
|
const width =
|
||||||
|
token.type === 'ansi' ? 0 : token.type === 'char' ? (token.fullWidth ? 2 : stringWidth(token.value)) : 0
|
||||||
|
|
||||||
|
// Break AFTER trailing zero-width marks — a combining mark attaches to
|
||||||
|
// the preceding base char, so "भा" (भ + ा, 1 display cell) sliced at
|
||||||
|
// end=1 must include the ा. Breaking on position >= end BEFORE the
|
||||||
|
// zero-width check would drop it and render भ bare. ANSI codes are
|
||||||
|
// width 0 but must NOT be included past end (they open new style runs
|
||||||
|
// that leak into the undo sequence), so gate on char type too. The
|
||||||
|
// !include guard ensures empty slices (start===end) stay empty even
|
||||||
|
// when the string starts with a zero-width char (BOM, ZWJ).
|
||||||
|
if (end !== undefined && position >= end) {
|
||||||
|
if (token.type === 'ansi' || width > 0 || !include) break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.type === 'ansi') {
|
||||||
|
activeCodes.push(token)
|
||||||
|
if (include) {
|
||||||
|
// Emit all ANSI codes during the slice
|
||||||
|
result += token.code
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!include && position >= start) {
|
||||||
|
// Skip leading zero-width marks at the start boundary — they belong
|
||||||
|
// to the preceding base char in the left half. Without this, the
|
||||||
|
// mark appears in BOTH halves: left+right ≠ original. Only applies
|
||||||
|
// when start > 0 (otherwise there's no preceding char to own it).
|
||||||
|
if (start > 0 && width === 0) continue
|
||||||
|
include = true
|
||||||
|
// Reduce and filter to only active start codes
|
||||||
|
activeCodes = filterStartCodes(reduceAnsiCodes(activeCodes))
|
||||||
|
result = ansiCodesToString(activeCodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (include) {
|
||||||
|
result += (token as any).value
|
||||||
|
}
|
||||||
|
|
||||||
|
position += width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only undo start codes that are still active
|
||||||
|
const activeStartCodes = filterStartCodes(reduceAnsiCodes(activeCodes))
|
||||||
|
result += ansiCodesToString(undoAnsiCodes(activeStartCodes))
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import sliceAnsi from '../utils/sliceAnsi.js'
|
import sliceAnsi from './utils/sliceAnsi.js'
|
||||||
import { stringWidth } from './stringWidth.js'
|
import { stringWidth } from './stringWidth.js'
|
||||||
import type { Styles } from './styles.js'
|
import type { Styles } from './styles.js'
|
||||||
import { wrapAnsi } from './wrapAnsi.js'
|
import { wrapAnsi } from './wrapAnsi.js'
|
||||||
134
packages/@ant/ink/src/core/yoga-layout/enums.ts
Normal file
134
packages/@ant/ink/src/core/yoga-layout/enums.ts
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
/**
|
||||||
|
* Yoga enums — ported from yoga-layout/src/generated/YGEnums.ts
|
||||||
|
* Kept as `const` objects (not TS enums) per repo convention.
|
||||||
|
* Values match upstream exactly so callers don't change.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const Align = {
|
||||||
|
Auto: 0,
|
||||||
|
FlexStart: 1,
|
||||||
|
Center: 2,
|
||||||
|
FlexEnd: 3,
|
||||||
|
Stretch: 4,
|
||||||
|
Baseline: 5,
|
||||||
|
SpaceBetween: 6,
|
||||||
|
SpaceAround: 7,
|
||||||
|
SpaceEvenly: 8,
|
||||||
|
} as const
|
||||||
|
export type Align = (typeof Align)[keyof typeof Align]
|
||||||
|
|
||||||
|
export const BoxSizing = {
|
||||||
|
BorderBox: 0,
|
||||||
|
ContentBox: 1,
|
||||||
|
} as const
|
||||||
|
export type BoxSizing = (typeof BoxSizing)[keyof typeof BoxSizing]
|
||||||
|
|
||||||
|
export const Dimension = {
|
||||||
|
Width: 0,
|
||||||
|
Height: 1,
|
||||||
|
} as const
|
||||||
|
export type Dimension = (typeof Dimension)[keyof typeof Dimension]
|
||||||
|
|
||||||
|
export const Direction = {
|
||||||
|
Inherit: 0,
|
||||||
|
LTR: 1,
|
||||||
|
RTL: 2,
|
||||||
|
} as const
|
||||||
|
export type Direction = (typeof Direction)[keyof typeof Direction]
|
||||||
|
|
||||||
|
export const Display = {
|
||||||
|
Flex: 0,
|
||||||
|
None: 1,
|
||||||
|
Contents: 2,
|
||||||
|
} as const
|
||||||
|
export type Display = (typeof Display)[keyof typeof Display]
|
||||||
|
|
||||||
|
export const Edge = {
|
||||||
|
Left: 0,
|
||||||
|
Top: 1,
|
||||||
|
Right: 2,
|
||||||
|
Bottom: 3,
|
||||||
|
Start: 4,
|
||||||
|
End: 5,
|
||||||
|
Horizontal: 6,
|
||||||
|
Vertical: 7,
|
||||||
|
All: 8,
|
||||||
|
} as const
|
||||||
|
export type Edge = (typeof Edge)[keyof typeof Edge]
|
||||||
|
|
||||||
|
export const Errata = {
|
||||||
|
None: 0,
|
||||||
|
StretchFlexBasis: 1,
|
||||||
|
AbsolutePositionWithoutInsetsExcludesPadding: 2,
|
||||||
|
AbsolutePercentAgainstInnerSize: 4,
|
||||||
|
All: 2147483647,
|
||||||
|
Classic: 2147483646,
|
||||||
|
} as const
|
||||||
|
export type Errata = (typeof Errata)[keyof typeof Errata]
|
||||||
|
|
||||||
|
export const ExperimentalFeature = {
|
||||||
|
WebFlexBasis: 0,
|
||||||
|
} as const
|
||||||
|
export type ExperimentalFeature =
|
||||||
|
(typeof ExperimentalFeature)[keyof typeof ExperimentalFeature]
|
||||||
|
|
||||||
|
export const FlexDirection = {
|
||||||
|
Column: 0,
|
||||||
|
ColumnReverse: 1,
|
||||||
|
Row: 2,
|
||||||
|
RowReverse: 3,
|
||||||
|
} as const
|
||||||
|
export type FlexDirection = (typeof FlexDirection)[keyof typeof FlexDirection]
|
||||||
|
|
||||||
|
export const Gutter = {
|
||||||
|
Column: 0,
|
||||||
|
Row: 1,
|
||||||
|
All: 2,
|
||||||
|
} as const
|
||||||
|
export type Gutter = (typeof Gutter)[keyof typeof Gutter]
|
||||||
|
|
||||||
|
export const Justify = {
|
||||||
|
FlexStart: 0,
|
||||||
|
Center: 1,
|
||||||
|
FlexEnd: 2,
|
||||||
|
SpaceBetween: 3,
|
||||||
|
SpaceAround: 4,
|
||||||
|
SpaceEvenly: 5,
|
||||||
|
} as const
|
||||||
|
export type Justify = (typeof Justify)[keyof typeof Justify]
|
||||||
|
|
||||||
|
export const MeasureMode = {
|
||||||
|
Undefined: 0,
|
||||||
|
Exactly: 1,
|
||||||
|
AtMost: 2,
|
||||||
|
} as const
|
||||||
|
export type MeasureMode = (typeof MeasureMode)[keyof typeof MeasureMode]
|
||||||
|
|
||||||
|
export const Overflow = {
|
||||||
|
Visible: 0,
|
||||||
|
Hidden: 1,
|
||||||
|
Scroll: 2,
|
||||||
|
} as const
|
||||||
|
export type Overflow = (typeof Overflow)[keyof typeof Overflow]
|
||||||
|
|
||||||
|
export const PositionType = {
|
||||||
|
Static: 0,
|
||||||
|
Relative: 1,
|
||||||
|
Absolute: 2,
|
||||||
|
} as const
|
||||||
|
export type PositionType = (typeof PositionType)[keyof typeof PositionType]
|
||||||
|
|
||||||
|
export const Unit = {
|
||||||
|
Undefined: 0,
|
||||||
|
Point: 1,
|
||||||
|
Percent: 2,
|
||||||
|
Auto: 3,
|
||||||
|
} as const
|
||||||
|
export type Unit = (typeof Unit)[keyof typeof Unit]
|
||||||
|
|
||||||
|
export const Wrap = {
|
||||||
|
NoWrap: 0,
|
||||||
|
Wrap: 1,
|
||||||
|
WrapReverse: 2,
|
||||||
|
} as const
|
||||||
|
export type Wrap = (typeof Wrap)[keyof typeof Wrap]
|
||||||
2578
packages/@ant/ink/src/core/yoga-layout/index.ts
Normal file
2578
packages/@ant/ink/src/core/yoga-layout/index.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
import { useContext, useEffect, useState } from 'react'
|
import { useContext, useEffect, useState } from 'react'
|
||||||
import { ClockContext } from '../components/ClockContext.js'
|
import { ClockContext } from '../components/ClockContext.js'
|
||||||
import type { DOMElement } from '../dom.js'
|
import type { DOMElement } from '../core/dom.js'
|
||||||
import { useTerminalViewport } from './use-terminal-viewport.js'
|
import { useTerminalViewport } from './use-terminal-viewport.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useCallback, useContext, useLayoutEffect, useRef } from 'react'
|
import { useCallback, useContext, useLayoutEffect, useRef } from 'react'
|
||||||
import CursorDeclarationContext from '../components/CursorDeclarationContext.js'
|
import CursorDeclarationContext from '../components/CursorDeclarationContext.js'
|
||||||
import type { DOMElement } from '../dom.js'
|
import type { DOMElement } from '../core/dom.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Declares where the terminal cursor should be parked after each frame.
|
* Declares where the terminal cursor should be parked after each frame.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useLayoutEffect } from 'react'
|
import { useEffect, useLayoutEffect } from 'react'
|
||||||
import { useEventCallback } from 'usehooks-ts'
|
import { useEventCallback } from 'usehooks-ts'
|
||||||
import type { InputEvent, Key } from '../events/input-event.js'
|
import type { InputEvent, Key } from '../core/events/input-event.js'
|
||||||
import useStdin from './use-stdin.js'
|
import useStdin from './use-stdin.js'
|
||||||
|
|
||||||
type Handler = (input: string, key: Key, event: InputEvent) => void
|
type Handler = (input: string, key: Key, event: InputEvent) => void
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useContext, useMemo } from 'react'
|
import { useContext, useMemo } from 'react'
|
||||||
import StdinContext from '../components/StdinContext.js'
|
import StdinContext from '../components/StdinContext.js'
|
||||||
import type { DOMElement } from '../dom.js'
|
import type { DOMElement } from '../core/dom.js'
|
||||||
import instances from '../instances.js'
|
import instances from '../core/instances.js'
|
||||||
import type { MatchPosition } from '../render-to-screen.js'
|
import type { MatchPosition } from '../core/render-to-screen.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the search highlight query on the Ink instance. Non-empty → all
|
* Set the search highlight query on the Ink instance. Non-empty → all
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user