d3ro-voice/packages/core/src/terminal-prompt.ts

49 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// packages/core — 터미널 화면에서 "지금 치고 있는 입력 줄" 을 뽑는다.
//
// 터미널은 화면 버퍼 전체가 입력창으로 읽힌다(상태줄·명령·출력 포함). 제안은 입력 줄만
// 접두로 써야 하므로 그 파싱을 한곳에 둔다. 순수 함수만 둔다 — 다른 core 모듈을
// import 하지 않는다 (input-intelligence 가 이 모듈을 다시 export 한다).
/** 상자·블록·도형·기타 기호·딩뱃 — 터미널 UI/상태줄의 지문이다. */
export const NON_PROSE_GLYPH_PATTERN = /[←-⇿─-➿⬀-⯿]/u
/**
* 입력 줄의 시작 표시: Claude Code/Codex `>`·`›`, starship `❯`, PowerShell `PS …>`, cmd `C:\…>`,
* POSIX `$ # %`(단독이거나 앞에 경로·호스트 표지가 있을 때만 — 출력의 `100% 완료` 를 입력으로 보지 않는다).
*/
const TERMINAL_PROMPT_PATTERN =
/^(?:PS [^>\n]{1,260}>|[A-Za-z]:\\[^>\n]{0,260}>|(?:[^\s]*[@:~/\\][^\s]*)?[$#%]|>|›|❯|»)[ \u00A0]?(.*)$/u
/** 테두리·상자 줄(─━═╭╮╰╯ 등)이나 비어 있는 줄에서 입력 영역이 끝난다. */
const TERMINAL_BORDER_PATTERN = /^[\s─━═┄┈╌╍╭╮╰╯┌┐└┘├┤┬┴┼│┃║▔▁▏▕-]*$/u
const TERMINAL_EDGE_PATTERN = /^[\s│┃║]+|[\s│┃║]+$/gu
const TERMINAL_SCAN_LINES = 40
/**
* 터미널 화면 텍스트에서 "지금 치고 있는 입력" 을 뽑는다.
*
* 문서형 터미널은 캐럿 위치를 주지 않고 화면 전체를 돌려준다 — 마지막 줄은 대개
* 상태줄이다. 아래에서 위로 가장 가까운 프롬프트 줄을 찾고, 그 아래로 테두리/빈 줄이
* 나올 때까지의 이어진 줄(줄바꿈된 긴 입력)을 붙인다. 프롬프트를 못 찾으면 null —
* 제안하지 않는다(출력·상태줄로 제안을 만들지 않는다).
*/
export function extractTerminalPromptPrefix(screen: string | null | undefined): string | null {
if (!screen) return null
const lines = screen.replace(/\r/g, '').split('\n')
const start = Math.max(0, lines.length - TERMINAL_SCAN_LINES)
for (let i = lines.length - 1; i >= start; i--) {
const line = lines[i].replace(TERMINAL_EDGE_PATTERN, '')
const match = TERMINAL_PROMPT_PATTERN.exec(line)
if (!match) continue
const parts = [match[1].trimEnd()]
for (let j = i + 1; j < lines.length; j++) {
const raw = lines[j]
if (TERMINAL_BORDER_PATTERN.test(raw)) break
const continuation = raw.replace(TERMINAL_EDGE_PATTERN, '')
// 상태줄 기호(◑ ▕░ 등)가 섞인 줄은 입력이 아니다.
if (!continuation || NON_PROSE_GLYPH_PATTERN.test(continuation)) break
parts.push(continuation)
}
return parts.join(' ').replace(/\s+/gu, ' ').trimStart()
}
return null
}