mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-28 06:51:16 +08:00
chore(tui): /clean — drop dead capture-pad path, dedupe gutter handlers
- TextInput: remove unused leftCaptureColumns prop and capture-pad math, drop unused mouseApi.startAt, fold mouse offset into a single offsetAt helper, share a MouseEventLite type across the four handlers. - appLayout: hoist a GutterMouseEvent type and an endInputDrag callback so the spacer/prompt/input rows share one shape. - _tui_need_npm_install: lift the runtime-only key set to a module constant, collapse nested isinstance checks, and document the mtime fallback. Made-with: Cursor
This commit is contained in:
@@ -829,6 +829,9 @@ def _print_tui_exit_summary(session_id: Optional[str], active_session_file: Opti
|
||||
)
|
||||
|
||||
|
||||
_NPM_LOCK_RUNTIME_KEYS = frozenset({"ideallyInert"})
|
||||
|
||||
|
||||
def _tui_need_npm_install(root: Path) -> bool:
|
||||
"""True when @hermes/ink is missing or node_modules is behind package-lock.json."""
|
||||
ink = root / "node_modules" / "@hermes" / "ink" / "package.json"
|
||||
@@ -841,29 +844,32 @@ def _tui_need_npm_install(root: Path) -> bool:
|
||||
if not marker.is_file():
|
||||
return True
|
||||
|
||||
# Compare lockfile contents, not mtimes: git checkouts and npm rewrites
|
||||
# can bump the root lockfile timestamp even when installed deps already
|
||||
# match. Fall back to mtime when either file is unparseable.
|
||||
try:
|
||||
wanted = json.loads(lock.read_text(encoding="utf-8")).get("packages") or {}
|
||||
installed = json.loads(marker.read_text(encoding="utf-8")).get("packages") or {}
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return lock.stat().st_mtime > marker.stat().st_mtime
|
||||
|
||||
ignored = {"ideallyInert"}
|
||||
|
||||
def comparable(pkg: dict) -> dict:
|
||||
return {k: v for k, v in pkg.items() if k not in ignored}
|
||||
return {k: v for k, v in pkg.items() if k not in _NPM_LOCK_RUNTIME_KEYS}
|
||||
|
||||
for name, pkg in wanted.items():
|
||||
if name == "":
|
||||
if not name:
|
||||
continue
|
||||
|
||||
if not isinstance(pkg, dict):
|
||||
continue
|
||||
|
||||
if name not in installed:
|
||||
if isinstance(pkg, dict) and (pkg.get("optional") or pkg.get("peer")):
|
||||
if pkg.get("optional") or pkg.get("peer"):
|
||||
continue
|
||||
return True
|
||||
|
||||
if isinstance(pkg, dict) and isinstance(installed[name], dict):
|
||||
if comparable(pkg) != comparable(installed[name]):
|
||||
return True
|
||||
if isinstance(installed[name], dict) and comparable(pkg) != comparable(installed[name]):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ const ComposerPane = memo(function ComposerPane({
|
||||
const inputHeight = inputVisualHeight(composer.input, inputColumns)
|
||||
const inputMouseRef = useRef<null | TextInputMouseApi>(null)
|
||||
|
||||
const captureInputDrag = (e: { button: number; localCol?: number; localRow?: number; stopImmediatePropagation?: () => void }) => {
|
||||
const captureInputDrag = (e: GutterMouseEvent) => {
|
||||
if (e.button !== 0) {
|
||||
return
|
||||
}
|
||||
@@ -138,7 +138,7 @@ const ComposerPane = memo(function ComposerPane({
|
||||
inputMouseRef.current?.startAtBeginning()
|
||||
}
|
||||
|
||||
const dragIntoInput = (e: { button: number; localCol?: number; localRow?: number; stopImmediatePropagation?: () => void }) => {
|
||||
const dragIntoInput = (e: GutterMouseEvent) => {
|
||||
if (e.button !== 0) {
|
||||
return
|
||||
}
|
||||
@@ -147,6 +147,8 @@ const ComposerPane = memo(function ComposerPane({
|
||||
inputMouseRef.current?.dragAt(e.localRow ?? 0, (e.localCol ?? 0) - pw)
|
||||
}
|
||||
|
||||
const endInputDrag = () => inputMouseRef.current?.end()
|
||||
|
||||
return (
|
||||
<NoSelect
|
||||
flexDirection="column"
|
||||
@@ -179,12 +181,7 @@ const ComposerPane = memo(function ComposerPane({
|
||||
{status.stickyPrompt}
|
||||
</Text>
|
||||
) : (
|
||||
<Box
|
||||
height={1}
|
||||
onMouseDown={captureInputDrag}
|
||||
onMouseDrag={dragIntoInput}
|
||||
onMouseUp={() => inputMouseRef.current?.end()}
|
||||
/>
|
||||
<Box height={1} onMouseDown={captureInputDrag} onMouseDrag={dragIntoInput} onMouseUp={endInputDrag} />
|
||||
)}
|
||||
|
||||
<StatusRulePane at="top" composer={composer} status={status} />
|
||||
@@ -211,12 +208,7 @@ const ComposerPane = memo(function ComposerPane({
|
||||
</Box>
|
||||
))}
|
||||
|
||||
<Box
|
||||
onMouseDown={captureInputDrag}
|
||||
onMouseDrag={dragIntoInput}
|
||||
onMouseUp={() => inputMouseRef.current?.end()}
|
||||
position="relative"
|
||||
>
|
||||
<Box onMouseDown={captureInputDrag} onMouseDrag={dragIntoInput} onMouseUp={endInputDrag} position="relative">
|
||||
<Box width={pw}>
|
||||
{sh ? (
|
||||
<Text color={ui.theme.color.shellDollar}>$ </Text>
|
||||
@@ -362,3 +354,10 @@ export const AppLayout = memo(function AppLayout({
|
||||
</Shell>
|
||||
)
|
||||
})
|
||||
|
||||
type GutterMouseEvent = {
|
||||
button: number
|
||||
localCol?: number
|
||||
localRow?: number
|
||||
stopImmediatePropagation?: () => void
|
||||
}
|
||||
|
||||
@@ -282,7 +282,6 @@ const isPasteResultPromise = (
|
||||
|
||||
export function TextInput({
|
||||
columns = 80,
|
||||
leftCaptureColumns = 0,
|
||||
value,
|
||||
onChange,
|
||||
onPaste,
|
||||
@@ -332,25 +331,18 @@ export function TextInput({
|
||||
)
|
||||
|
||||
const layout = useMemo(() => cursorLayout(display, cur, columns), [columns, cur, display])
|
||||
const capturePad = Math.max(0, leftCaptureColumns)
|
||||
|
||||
const boxRef = useDeclaredCursor({
|
||||
line: layout.line,
|
||||
column: layout.column + capturePad,
|
||||
column: layout.column,
|
||||
active: focus && termFocus && !selected
|
||||
})
|
||||
|
||||
// Hide the hardware cursor while an input selection is active so it can't
|
||||
// auto-wrap below the prompt when the rendered (inverted) text exactly
|
||||
// fills the row, and so it doesn't paint a ghost block on the first
|
||||
// selected cell when we re-park it. Restore on unmount or when selection
|
||||
// clears so the cursor reappears as soon as the user resumes typing.
|
||||
// Hide the hardware cursor during a selection: prevents auto-wrap into
|
||||
// the row below when inverted text exactly fills the column width, and
|
||||
// avoids parking a ghost block on the first selected cell.
|
||||
useEffect(() => {
|
||||
if (!focus || !stdout?.isTTY) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!selected) {
|
||||
if (!focus || !selected || !stdout?.isTTY) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -677,11 +669,6 @@ export function TextInput({
|
||||
commit(nextValue, nextCursor)
|
||||
}
|
||||
|
||||
const mouseOffset = (e: { localCol?: number; localRow?: number }) => ({
|
||||
col: (e.localCol ?? 0) - capturePad,
|
||||
row: e.localRow ?? 0
|
||||
})
|
||||
|
||||
const startMouseSelection = (next: number) => {
|
||||
const c = snapPos(vRef.current, next)
|
||||
|
||||
@@ -716,11 +703,13 @@ export function TextInput({
|
||||
}
|
||||
}
|
||||
|
||||
const offsetAt = (e: { localCol?: number; localRow?: number }) =>
|
||||
offsetFromPosition(display, e.localRow ?? 0, e.localCol ?? 0, columns)
|
||||
|
||||
if (mouseApiRef) {
|
||||
mouseApiRef.current = {
|
||||
dragAt: (row, col) => dragMouseSelection(offsetFromPosition(display, row, col, columns)),
|
||||
end: endMouseSelection,
|
||||
startAt: (row, col) => startMouseSelection(offsetFromPosition(display, row, col, columns)),
|
||||
startAtBeginning: () => startMouseSelection(0)
|
||||
}
|
||||
}
|
||||
@@ -983,30 +972,24 @@ export function TextInput({
|
||||
|
||||
return (
|
||||
<Box
|
||||
onClick={(e: { localCol?: number; localRow?: number; stopImmediatePropagation?: () => void }) => {
|
||||
onClick={(e: MouseEventLite) => {
|
||||
if (!focus) {
|
||||
return
|
||||
}
|
||||
|
||||
e.stopImmediatePropagation?.()
|
||||
clearSel()
|
||||
const pos = mouseOffset(e)
|
||||
const next = offsetFromPosition(display, pos.row, pos.col, columns)
|
||||
const next = offsetAt(e)
|
||||
setCur(next)
|
||||
curRef.current = next
|
||||
}}
|
||||
onMouseDown={(e: {
|
||||
button: number
|
||||
localCol?: number
|
||||
localRow?: number
|
||||
stopImmediatePropagation?: () => void
|
||||
}) => {
|
||||
onMouseDown={(e: MouseEventLite) => {
|
||||
if (!focus) {
|
||||
return
|
||||
}
|
||||
|
||||
// Right-click to paste: route through the same hotkey path as
|
||||
// Alt+V so the composer's clipboard RPC (text or image) handles it.
|
||||
// Right-click → route through the same path as Alt+V so the composer
|
||||
// clipboard RPC (text or image) handles it.
|
||||
if (e.button === 2) {
|
||||
e.stopImmediatePropagation?.()
|
||||
emitPaste({ cursor: curRef.current, hotkey: true, text: '', value: vRef.current })
|
||||
@@ -1019,39 +1002,34 @@ export function TextInput({
|
||||
}
|
||||
|
||||
e.stopImmediatePropagation?.()
|
||||
const pos = mouseOffset(e)
|
||||
const next = offsetFromPosition(display, pos.row, pos.col, columns)
|
||||
startMouseSelection(next)
|
||||
startMouseSelection(offsetAt(e))
|
||||
}}
|
||||
onMouseDrag={(e: {
|
||||
button: number
|
||||
localCol?: number
|
||||
localRow?: number
|
||||
stopImmediatePropagation?: () => void
|
||||
}) => {
|
||||
onMouseDrag={(e: MouseEventLite) => {
|
||||
if (!focus || e.button !== 0 || mouseAnchorRef.current === null) {
|
||||
return
|
||||
}
|
||||
|
||||
e.stopImmediatePropagation?.()
|
||||
const pos = mouseOffset(e)
|
||||
const next = offsetFromPosition(display, pos.row, pos.col, columns)
|
||||
dragMouseSelection(next)
|
||||
dragMouseSelection(offsetAt(e))
|
||||
}}
|
||||
onMouseUp={(e: { stopImmediatePropagation?: () => void }) => {
|
||||
onMouseUp={(e: MouseEventLite) => {
|
||||
e.stopImmediatePropagation?.()
|
||||
endMouseSelection()
|
||||
}}
|
||||
marginLeft={capturePad ? -capturePad : undefined}
|
||||
paddingLeft={capturePad || undefined}
|
||||
ref={boxRef}
|
||||
width={columns + capturePad}
|
||||
>
|
||||
<Text wrap="wrap-char">{rendered}</Text>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
type MouseEventLite = {
|
||||
button?: number
|
||||
localCol?: number
|
||||
localRow?: number
|
||||
stopImmediatePropagation?: () => void
|
||||
}
|
||||
|
||||
export interface PasteEvent {
|
||||
bracketed?: boolean
|
||||
cursor: number
|
||||
@@ -1063,7 +1041,6 @@ export interface PasteEvent {
|
||||
interface TextInputProps {
|
||||
columns?: number
|
||||
focus?: boolean
|
||||
leftCaptureColumns?: number
|
||||
mask?: string
|
||||
mouseApiRef?: MutableRefObject<null | TextInputMouseApi>
|
||||
onChange: (v: string) => void
|
||||
@@ -1078,6 +1055,5 @@ interface TextInputProps {
|
||||
export interface TextInputMouseApi {
|
||||
dragAt: (row: number, col: number) => void
|
||||
end: () => void
|
||||
startAt: (row: number, col: number) => void
|
||||
startAtBeginning: () => void
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user