Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
28 changes: 27 additions & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
"highlight.js": "^11.11.1",
"https-proxy-agent": "^8.0.0",
"ignore": "^7.0.5",
"@anthropic/ink": "workspace:*",
"image-processor-napi": "workspace:*",
"indent-string": "^5.0.0",
"jsonc-parser": "^3.3.1",
Expand Down
30 changes: 30 additions & 0 deletions packages/@ant/ink/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import React, {
useContext,
useInsertionEffect,
} from 'react'
import instances from '../instances.js'
import instances from '../core/instances.js'
import {
DISABLE_MOUSE_TRACKING,
ENABLE_MOUSE_TRACKING,
ENTER_ALT_SCREEN,
EXIT_ALT_SCREEN,
} from '../termio/dec.js'
import { TerminalWriteContext } from '../useTerminalNotification.js'
} from '../core/termio/dec.js'
import { TerminalWriteContext } from '../hooks/useTerminalNotification.js'
import Box from './Box.js'
import { TerminalSizeContext } from './TerminalSizeContext.js'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,45 +1,70 @@
import React, { PureComponent, type ReactNode } from 'react'
import { updateLastInteractionTime } from '../../bootstrap/state.js'
import { logForDebugging } from '../../utils/debug.js'
import { stopCapturingEarlyInput } from '../../utils/earlyInput.js'
import { isEnvTruthy } from '../../utils/envUtils.js'
import { isMouseClicksDisabled } from '../../utils/fullscreen.js'
import { logError } from '../../utils/log.js'
import { EventEmitter } from '../events/emitter.js'
import { InputEvent } from '../events/input-event.js'
import { TerminalFocusEvent } from '../events/terminal-focus-event.js'
// Business-layer callbacks — replaced with inline defaults so this package
// has zero dependencies on business code. The business layer can inject
// implementations via AppCallbacks when needed.
type AppCallbacks = {
updateLastInteractionTime?: () => void
stopCapturingEarlyInput?: () => void
isMouseClicksDisabled?: () => boolean
logError?: (error: unknown) => void
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 {
INITIAL_STATE,
type ParsedInput,
type ParsedKey,
type ParsedMouse,
parseMultipleKeypresses,
} from '../parse-keypress.js'
import reconciler from '../reconciler.js'
} from '../core/parse-keypress.js'
import reconciler from '../core/reconciler.js'
import {
finishSelection,
hasSelection,
type SelectionState,
startSelection,
} from '../selection.js'
} from '../core/selection.js'
import {
isXtermJs,
setXtversionName,
supportsExtendedKeys,
} from '../terminal.js'
} from '../core/terminal.js'
import {
getTerminalFocused,
setTerminalFocused,
} from '../terminal-focus-state.js'
import { TerminalQuerier, xtversion } from '../terminal-querier.js'
} from '../core/terminal-focus-state.js'
import { TerminalQuerier, xtversion } from '../core/terminal-querier.js'
import {
DISABLE_KITTY_KEYBOARD,
DISABLE_MODIFY_OTHER_KEYS,
ENABLE_KITTY_KEYBOARD,
ENABLE_MODIFY_OTHER_KEYS,
FOCUS_IN,
FOCUS_OUT,
} from '../termio/csi.js'
} from '../core/termio/csi.js'
import {
DBP,
DFE,
Expand All @@ -48,7 +73,7 @@ import {
EFE,
HIDE_CURSOR,
SHOW_CURSOR,
} from '../termio/dec.js'
} from '../core/termio/dec.js'
import AppContext from './AppContext.js'
import { ClockProvider } from './ClockContext.js'
import CursorDeclarationContext, {
Expand Down Expand Up @@ -292,7 +317,7 @@ export default class App extends PureComponent<Props, State> {
// Both use the same stdin 'readable' + read() pattern, so they can't
// coexist -- our handler would drain stdin before Ink's can see it.
// The buffered text is preserved for REPL.tsx via consumeEarlyInput().
stopCapturingEarlyInput()
defaultCallbacks.stopCapturingEarlyInput()
stdin.ref()
stdin.setRawMode(true)
stdin.addListener('readable', this.handleReadable)
Expand Down Expand Up @@ -324,9 +349,9 @@ export default class App extends PureComponent<Props, State> {
]).then(([r]) => {
if (r) {
setXtversionName(r.name)
logForDebugging(`XTVERSION: terminal identified as "${r.name}"`)
defaultCallbacks.logForDebugging(`XTVERSION: terminal identified as "${r.name}"`)
} else {
logForDebugging('XTVERSION: no reply (terminal ignored query)')
defaultCallbacks.logForDebugging('XTVERSION: no reply (terminal ignored query)')
}
})
})
Expand Down Expand Up @@ -436,7 +461,7 @@ export default class App extends PureComponent<Props, State> {
// permanently wedge the stream: data stays buffered and 'readable'
// never re-emits. Catching here ensures the stream stays healthy so
// subsequent keystrokes are still delivered.
logError(error)
defaultCallbacks.logError(error)

// Re-attach the listener in case the exception detached it.
// Bun may remove the listener after an error; without this,
Expand All @@ -446,7 +471,7 @@ export default class App extends PureComponent<Props, State> {
this.rawModeEnabledCount > 0 &&
!stdin.listeners('readable').includes(this.handleReadable)
) {
logForDebugging(
defaultCallbacks.logForDebugging(
'handleReadable: re-attaching stdin readable listener after error recovery',
{ level: 'warn' },
)
Expand Down Expand Up @@ -556,7 +581,7 @@ function processKeysInBatch(
!((i.button & 0x20) !== 0 && (i.button & 0x03) === 3)),
)
) {
updateLastInteractionTime()
defaultCallbacks.updateLastInteractionTime()
}

for (const item of items) {
Expand Down Expand Up @@ -625,7 +650,7 @@ function processKeysInBatch(
export function handleMouseEvent(app: App, m: ParsedMouse): void {
// Allow disabling click handling while keeping wheel scroll (which goes
// through the keybinding system as 'wheelup'/'wheeldown', not here).
if (isMouseClicksDisabled()) return
if (defaultCallbacks.isMouseClicksDisabled()) return

const sel = app.props.selection
// Terminal coords are 1-indexed; screen buffer is 0-indexed
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, { type PropsWithChildren, type Ref } from 'react'
import type { Except } from 'type-fest'
import type { DOMElement } from '../dom.js'
import type { ClickEvent } from '../events/click-event.js'
import type { FocusEvent } from '../events/focus-event.js'
import type { KeyboardEvent } from '../events/keyboard-event.js'
import type { Styles } from '../styles.js'
import * as warn from '../warn.js'
import type { DOMElement } from '../core/dom.js'
import type { ClickEvent } from '../core/events/click-event.js'
import type { FocusEvent } from '../core/events/focus-event.js'
import type { KeyboardEvent } from '../core/events/keyboard-event.js'
import type { Styles } from '../core/styles.js'
import * as warn from '../core/warn.js'

export type Props = Except<Styles, 'textWrap'> & {
ref?: Ref<DOMElement>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import React, {
useState,
} from 'react'
import type { Except } from 'type-fest'
import type { DOMElement } from '../dom.js'
import type { ClickEvent } from '../events/click-event.js'
import type { FocusEvent } from '../events/focus-event.js'
import type { KeyboardEvent } from '../events/keyboard-event.js'
import type { Styles } from '../styles.js'
import type { DOMElement } from '../core/dom.js'
import type { ClickEvent } from '../core/events/click-event.js'
import type { FocusEvent } from '../core/events/focus-event.js'
import type { KeyboardEvent } from '../core/events/keyboard-event.js'
import type { Styles } from '../core/styles.js'
import Box from './Box.js'

type ButtonState = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
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'

export type Clock = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createContext } from 'react'
import type { DOMElement } from '../dom.js'
import type { DOMElement } from '../core/dom.js'

export type CursorDeclaration = {
/** Display column (terminal cell width) within the declared node */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ReactNode } from 'react'
import React from 'react'
import { supportsHyperlinks } from '../supports-hyperlinks.js'
import { supportsHyperlinks } from '../core/supports-hyperlinks.js'
import Text from './Text.js'

export type Props = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ import React, {
useState,
} from 'react'
import type { Except } from 'type-fest'
import { markScrollActivity } from '../../bootstrap/state.js'
import type { DOMElement } from '../dom.js'
import { markDirty, scheduleRenderFrom } from '../dom.js'
import { markCommitStart } from '../reconciler.js'
import type { Styles } from '../styles.js'
import type { DOMElement } from '../core/dom.js'
import { markDirty, scheduleRenderFrom } from '../core/dom.js'
import { markCommitStart } from '../core/reconciler.js'
import type { Styles } from '../core/styles.js'
import Box from './Box.js'

export type ScrollBoxHandle = {
Expand Down Expand Up @@ -116,7 +115,7 @@ function ScrollBox({
// Signal background intervals (IDE poll, LSP poll, GCS fetch, orphan
// check) to skip their next tick — they compete for the event loop and
// contributed to 1402ms max frame gaps during scroll drain.
markScrollActivity()
// noop — injected by business layer via onScrollActivity callback
markDirty(el)
markCommitStart()
notify()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createContext } from 'react'
import { EventEmitter } from '../events/emitter.js'
import type { TerminalQuerier } from '../terminal-querier.js'
import { EventEmitter } from '../core/events/emitter.js'
import type { TerminalQuerier } from '../core/terminal-querier.js'

export type Props = {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
getTerminalFocusState,
subscribeTerminalFocus,
type TerminalFocusState,
} from '../terminal-focus-state.js'
} from '../core/terminal-focus-state.js'

export type { TerminalFocusState }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ReactNode } 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 = {
/**
Expand Down
4 changes: 2 additions & 2 deletions src/ink/Ansi.tsx → packages/@ant/ink/src/core/Ansi.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react'
import Link from './components/Link.js'
import Text from './components/Text.js'
import Link from '../components/Link.js'
import Text from '../components/Text.js'
import type { Color } from './styles.js'
import {
type NamedColor,
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import {
DiscreteEventPriority,
NoEventPriority,
} from 'react-reconciler/constants.js'
import { logError } from '../../utils/log.js'
import { HANDLER_FOR_EVENT } from './event-handlers.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 = {
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading
Loading