49 lines
2.7 KiB
TypeScript
49 lines
2.7 KiB
TypeScript
// 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
|
||
}
|