diff --git a/Cargo.lock b/Cargo.lock index 9cab5524..f0e96446 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1291,6 +1291,7 @@ dependencies = [ "syntect", "tempfile", "toml", + "toml_edit", "tracing", "tracing-appender", "tracing-subscriber", @@ -2582,6 +2583,19 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + [[package]] name = "toml_parser" version = "1.1.3+spec-1.1.0" @@ -3154,6 +3168,9 @@ name = "winnow" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] [[package]] name = "winreg" diff --git a/Cargo.toml b/Cargo.toml index cfb3884c..82fcbc39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,9 @@ portable-pty = "0.9" clap = { version = "4", features = ["derive"] } serde = { version = "1", features = ["derive"] } serde_json = "1" +tempfile = "3" toml = "1" +toml_edit = "0.25" dirs = "6" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } @@ -82,6 +84,3 @@ ctrlc = "3" # `GetLocalTime` for the wall clock. Already in the lockfile transitively; # adding it directly here so the feature gate is explicit. windows-sys = { version = "0.61", features = ["Win32_System_Time", "Win32_Storage_FileSystem", "Win32_Foundation"] } - -[dev-dependencies] -tempfile = "3" diff --git a/config.example.toml b/config.example.toml index 3620edd9..c9c35b77 100644 --- a/config.example.toml +++ b/config.example.toml @@ -175,7 +175,8 @@ live_watch = true # watch expanded dirs and refresh the tree live; set f # enabled = false # off by default; set true to actually run it # watch_on_signal = false # off by default; see the paragraphs above # allowed_resume_flags = ["--resume", "resume", "--session"] -# # empty by default, which refuses relaunches. +# # empty by default, which refuses relaunch +# # arguments. Entries are flags/subcommands. # # These three are what the bundled recovery # # plugin needs for Claude/Codex/OpenCode. # diff --git a/docs/architecture.md b/docs/architecture.md index d98a9e29..da9fb799 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -66,8 +66,9 @@ OSC 0/2 탭 타이틀 캡처, 마우스 캡처(클릭 포커스/포워딩, 휠 ``` src/ ├── main.rs # entry point: dispatch to daemon / attach / serve / init -├── cli.rs, cli/ # Cli/Commands, run_daemon/run_init, `nightcrow plugin` +├── cli.rs, cli/ # Cli/Commands + attach/daemon/init/stop/plugin command handlers ├── test_util.rs # #[cfg(test)] git fixture helpers shared across modules +├── persistence.rs # typed JSON reads + same-directory atomic replacement ├── daemon/ # the session socket │ ├── socket.rs, lock.rs, detach.rs # 0600 socket + stale handling, flock single- │ │ # instance lock, backgrounding by re-exec (not fork) @@ -90,8 +91,8 @@ src/ │ # logging.rs (file logger, rotation + retention), paths.rs │ # (tilde expansion), signals.rs (SIGINT/SIGTERM shutdown), │ # threading.rs (try_timed_join) -├── app.rs, app/ # App struct + per-feature impls: auto_follow, commit-log -│ # fetch/pagination/apply, diff & file-view loaders, focus, +├── app.rs, app/ # App aggregate + InteractionState; per-feature impls: auto_follow, +│ # commit-log fetch/pagination/apply, diff & file-view loaders, focus, │ # navigation, log_nav, scroll, session_io, snapshot_io, │ # terminal_ctrl, tree, tree_nav ├── config.rs, config/ # config.toml root + layout/theme/input, log, panels, @@ -140,7 +141,8 @@ src/ │ ├── guard_budget.rs # per-slot rate ceilings, keyed by PaneToken │ ├── guard_watch.rs # the one rule that can widen what a plugin sees │ ├── guard_refusal.rs, guard_text.rs # refusal reasons; bounding plugin-supplied text -│ └── registry.rs # ~/.nightcrow/plugins: install / list / remove +│ └── registry.rs, registry/ # ~/.nightcrow/plugins: config snippets, executable +│ # resolution, atomic install/list/remove storage ├── git/ │ ├── diff.rs, diff/ # types, snapshot loader, diff/commit loaders, commit_log, refs │ ├── clone.rs, clone/ # delegate `git clone` to the binary; URL scheme whitelist @@ -149,22 +151,22 @@ src/ ├── input/ # Action enum (mod.rs), routing.rs (map_key, prefix_action, │ # prefix_action_fullscreen, vim j/k), encode.rs (encode_key, │ # encode_wheel/button/arrow, CSI/SS3 helpers) -└── web/ # browser surface - ├── common/ # server-agnostic primitives (no git or terminals): auth.rs - │ # (Argon2 verify, session tokens, login rate limit), http.rs, - │ # sse.rs (SseStream), conn.rs (ConnectionSlot accounting) +├── session/ # daemon-owned, transport-neutral shared session core +│ ├── state.rs, operations.rs, reload.rs # ownership, mutations, live config reload +│ ├── catalog/ # opaque repo ids, atomic swap, ordering, config tables +│ ├── runtime/ # SnapshotChannel drain + conflated status fan-out +│ ├── terminal/ # TerminalHub, PtyBackend ownership, shared terminal frames +│ ├── size_owner.rs # which client screen the session PTYs are fitted to +│ └── prefs/ # persisted accent, active repo, browser arrangements +└── web/ # browser transport + ├── common/ # HTTP primitives: auth, parsing, SSE, connection slots └── viewer/ # native web viewer ([web_viewer] / `serve`) - ├── limits.rs # ceilings: log page, tree entries, diff bytes/lines, PTYs - ├── dto/ # whitelisted wire types + PROTOCOL_VERSION envelope - ├── catalog/ # opaque repo ids, atomic swap, ordering, config tables - ├── runtime/ # per-repo thread: SnapshotChannel drain + conflated SSE fan-out - ├── terminal/ # per-repo TerminalHub owning its own PtyBackend - ├── session.rs, reload.rs, size_owner.rs # transport-independent session ops; - │ # live config.toml re-read; which screen the PTYs are fitted to - ├── prefs/ # ~/.nightcrow/viewer.json: accent, widths, active repo, maximized - ├── clone_jobs.rs, clone_jobs/ # in-flight clone tracking (one at a time) - ├── highlight.rs, assets.rs # syntect spans for payloads; rust-embed of dist + CSP - └── server/ # HTTP routes, dispatch, mutations, SSE, /ws/term + ├── limits.rs # HTTP/git serialization ceilings + ├── dto/ # whitelisted browser wire types + PROTOCOL_VERSION + ├── status_payload.rs # status encoder injected into the session runtime + ├── clone_jobs.rs, clone_jobs/ # in-flight clone tracking + ├── highlight.rs, assets.rs # syntect spans; embedded frontend bundle + └── server/ # HTTP-only state; handlers split by HTTP/repo/SSE/terminal ``` ## Stack @@ -178,14 +180,23 @@ src/ | 터미널 에뮬레이션 | alacritty_terminal 0.26 | | 파일시스템 감시 | notify 8.2 + notify-debouncer-mini 0.7 | | 파일 로깅 | tracing + tracing-subscriber + tracing-appender | -| 설정 파싱 | toml 0.9 + serde | -| 세션 저장 | serde_json | +| 설정 파싱 | toml 1 + serde | +| 형식 보존 설정 편집 | toml_edit 0.25 | +| 세션 JSON 저장 | serde_json | +| 임시 파일 기반 교체 | tempfile 3 | | CLI args | clap 4 (derive) | | 프로세스 제어 | libc (`flock`) + signal-hook 0.4 (SIGTERM/SIGINT) | | 웹 서버 | tungstenite 0.30 (sync WS) + argon2 0.5 + getrandom 0.4 | | 웹 뷰어 번들 임베드 | rust-embed 8.12 (`viewer-ui/dist`) | | 웹 뷰어 프론트엔드 | React 19 + TypeScript 7 + Vite 8 + Tailwind v4 + `@xterm/xterm` 6, 마크다운은 react-markdown 10(+remark-gfm, rehype-highlight), 테스트는 vitest 4 | +`toml_edit`는 생성한 웹 비밀번호만 바꾸면서 기존 TOML의 주석·공백·키 순서를 보존하려고 쓴다. +`toml`로 전체 문서를 다시 직렬화하거나 문자열을 직접 고치는 대안은 서식을 잃거나 동등한 table 표현을 +빠뜨릴 수 있어 채택하지 않았다. `tempfile`은 상태와 설정을 대상 디렉터리의 충돌 방지 임시 파일에 쓴 뒤 +`persist`로 교체하는 데 쓴다. 같은 디렉터리를 쓰면 부분 기록과 파일시스템 간 이동 위험을 줄이지만, +교체의 원자성은 파일시스템과 플랫폼에 달려 있다. `std::fs`만 쓰는 대안은 고유 이름의 배타적 생성, +실패 시 정리, Windows 교체 동작을 직접 구현해야 하므로 채택하지 않았다. + PTY 관리는 portable-pty 기반 `PtyBackend` 단일 구현으로 정리됐다. 초기에는 tmux control-mode 백엔드(`TmuxBackend`)도 병행 지원했으나, 중첩 TUI 키보드 라우팅 문제를 leader(prefix) 모델로 해결하면서 tmux 의존성 없이 `PtyBackend`만으로 충분해져 제거했다. @@ -218,7 +229,8 @@ PTY 관리는 portable-pty 기반 `PtyBackend` 단일 구현으로 정리됐다. ## Future Refactor Notes -- `App`은 도메인별 sub-struct(`StatusView`, `LogView`, `DiffPane`, `TerminalState`, `RepoInput`)와 +- `App`은 도메인별 sub-struct(`StatusView`, `LogView`, `DiffPane`, `TerminalState`, + `InteractionState`, `RepoInput`)와 `app/` 서브모듈로 impl 책임이 나뉘어 있지만, 여전히 한 구조체가 모든 sub-state를 들고 있다. 추가 분리가 필요해지면 sub-struct별 명시적 manager로 승격하는 게 다음 단계다. - 대형 diff에서 j/k 빠른 탐색 시 동기 diff 로드가 여전히 ms 단위 블로킹을 만들 수 있다. Repository diff --git a/docs/architecture/plugin-host.md b/docs/architecture/plugin-host.md index 1fb7d7d1..90a99b78 100644 --- a/docs/architecture/plugin-host.md +++ b/docs/architecture/plugin-host.md @@ -78,10 +78,11 @@ relaunch하는가, 되돌릴 명령이 있는가, 제어문자가 섞이지 않 - **프로세스 해제와 slot 폐기를 분리한다**: 한도 대기는 몇 시간일 수 있다. 죽은 자식의 fd와 스레드를 그 시간 내내 붙잡는 것은 낭비이므로 `release_process`는 PTY를 놓고 slot만 남긴다. 아무도 relaunch하지 않으면 `PENDING_RELAUNCH_TTL`에 slot을 폐기한다. -- **권한 인자는 사용자가 선언한다**: relaunch가 덧붙일 수 있는 플래그는 `[[plugin]]`의 - `allowed_resume_flags`뿐이고 기본은 빈 목록이다. 코어가 특정 CLI의 위험 플래그 이름을 하드코딩하는 - 대안은 곧 코어가 provider를 아는 것이라 택하지 않았다. 인자는 셸 메타문자를 거부한 뒤 개별로 - quote되며, 원래 명령 문자열은 수정되지 않는다(다음 relaunch가 인자를 누적하지 않도록). +- **권한 인자는 사용자가 선언한다**: relaunch의 첫 토큰(플래그 또는 subcommand)과 `-`/`/`로 + 시작하는 option 토큰은 `[[plugin]].allowed_resume_flags`에 있어야 하며 기본은 빈 목록이다. 코어가 + 특정 CLI의 위험 플래그 이름을 하드코딩하면 provider 경계를 깨므로 택하지 않았다. 허용 문자는 POSIX + shell과 `cmd.exe`에서 그대로 전달되는 안전한 공통 집합으로 제한하며 별도 quote 문자를 넣지 않는다. + 원래 명령 문자열은 수정하지 않아 다음 relaunch에 인자가 누적되지 않는다. - **와이어 계약이 두 벌 있다**: plugin은 독립 빌드라 `plugins/nightcrow-recovery`가 프로토콜 타입을 따로 갖는다. `PROTOCOL_VERSION`을 진짜 주장으로 만들려면 그래야 하고, 양쪽 모두 JSON 모양을 리터럴로 고정한 테스트가 있어 드리프트는 테스트 실패로 나타난다. diff --git a/docs/architecture/session.md b/docs/architecture/session.md index ce3490a8..ebde79ed 100644 --- a/docs/architecture/session.md +++ b/docs/architecture/session.md @@ -70,7 +70,7 @@ trait TerminalBackend { 편의보다 무겁기 때문이다 — TUI와 브라우저를 나란히 두면 같은 세션이 두 색으로 보였고, 어느 쪽이 이 세션의 색이냐는 물음에 답할 수 있는 값이 아예 없었다. 저장소별 색이 대신하던 "지금 어느 프로젝트인가"는 탭 이름과 활성 탭 강조가 이미 답한다. 값은 `viewer.json` 하나에 살고 -(`web/viewer/prefs`), 어느 표면에서 바꾸든 세션 전체가 따라온다 — 대신 프로젝트를 바꿔도 색은 +(`session/prefs`), 어느 표면에서 바꾸든 세션 전체가 따라온다 — 대신 프로젝트를 바꿔도 색은 그대로다. `[theme] name`은 아직 한 번도 색을 고르지 않은 세션의 시작색으로 남는다. ### 데몬이 세션을 감시한다 (`daemon/watch.rs`) @@ -100,7 +100,7 @@ alternate screen을 쓰는 풀스크린 TUI를 나중에 다시 흘릴 방법은 `claim_size`로 명시적 탈취(TUI ` z`, 뷰어의 "fit to this screen" 버튼), 소유자가 떠나면 남은 중 가장 최근에게, 아무도 없으면 마지막 크기 유지. -- **소유권은 hub별이 아니라 세션 하나가 갖는다**(`web/viewer/size_owner.rs`). 어느 repo가 앞에 +- **소유권은 hub별이 아니라 세션 하나가 갖는다**(`session/size_owner.rs`). 어느 repo가 앞에 있는지는 세션 공유라 "이 세션은 어느 화면에 맞춰져 있나"는 질문이 하나다. hub마다 따로 답하던 때는 탭을 옮길 때마다 붙어 있는 모든 페이지가 동시에 재접속해 소유권이 **핸드셰이크가 늦게 끝난 쪽**으로 갔다. @@ -179,7 +179,7 @@ awake를 한 번 더 보고 잠들었으면 결과를 넘기지 않는다. 첫 지점을 테스트로 고정해 두고 상한은 바꾸지 않았다 — 거기 닿는 출력은 대부분 텍스트가 아니라 repaint 시퀀스이고, 상한은 저장소×pane마다 지불된다. -**붙는 클라이언트에게는 기록이 아니라 상태를 준다**(`web/viewer/terminal/hub_modes.rs`, +**붙는 클라이언트에게는 기록이 아니라 상태를 준다**(`session/terminal/hub_modes.rs`, `hub_repaint.rs`, `runtime/emulator/modes.rs`). 바이트 링은 역사이지 스냅샷이 아니어서, 프로그램이 시작할 때 한 번 켜고 다시 말하지 않는 것들(alternate screen, 마우스 리포팅, bracketed paste, DECCKM)은 하루 지난 pane에서 이미 밀려나 있다. 그러면 클라이언트는 **프로그램이 설정한 적 없는 @@ -201,7 +201,7 @@ DECCKM)은 하루 지난 pane에서 이미 밀려나 있다. 그러면 클라이 값만 전달한다") 그 repaint가 사라졌다. 사용자가 깨진 화면에 누르는 복구 키가 `Ctrl+L`이고 fullscreen Claude Code는 그것을 2초 안에 두 번 받으면 `/clear`를 실행한다 — 대화가 연달아 지워졌다. -**입력의 출처를 기록한다**(`web/viewer/terminal/hub_diag.rs`, `session.rs`, +**입력의 출처를 기록한다**(`session/terminal/hub_diag.rs`, `session.rs`, `viewer-ui/src/lib/clearKeyProbe.ts`). 특정 사건 때문에 존재하는 계측이다 — 5초 사이에 대화가 14번 지워졌는데 `0x0c`가 30번쯤 기계적 간격으로 들어왔다는 뜻이고, **무엇이 보냈는지 알 수 없었다**. nightcrow가 합성하는 입력은 스크롤·마우스 리포트와 plugin의 `continue`뿐이고 후자는 그 @@ -218,7 +218,7 @@ DECCKM)은 하루 지난 pane에서 이미 밀려나 있다. 그러면 클라이 말이므로 분당 상한을 두고, `code`는 ASCII 영숫자 16자로 깎는다(줄바꿈이 들어오면 로그 한 줄을 위조할 수 있다). 원인이 특정되면 이 계측은 지운다. -## Config Reload (`web/viewer/reload.rs`) +## Config Reload (`session/reload.rs`) `config.toml`을 고칠 때마다 데몬을 내렸다 올리면 살아 있는 pane이 전부 죽는다 — agent CLI가 작업 중이던 것까지. 그래서 **두 테이블만 다시 읽는다.** 무엇이 즉시 닿고 무엇이 안 닿는지는 @@ -271,7 +271,7 @@ DECCKM)은 하루 지난 pane에서 이미 밀려나 있다. 그러면 클라이 때문이다 — `nightcrow-recovery`의 `panes: HashMap`은 메모리뿐이다. host가 대신 경고할 수 없다: **살아 있는** pane에 대한 대기는 plugin 안에만 있고 host의 `pending`에는 없다. 그래서 이 손실의 범위를 좁히는 것이 `spec_changed`의 진짜 값이다. -- **동시 reload는 직렬화한다**(`ViewerState::reload_lock`). 두 클라이언트가 동시에 누르면 세션의 +- **동시 reload는 직렬화한다**(`SessionState::reload_lock`). 두 클라이언트가 동시에 누르면 세션의 저장소들이 서로 다른 파일을 전달받은 상태로 남을 수 있다. - **reload와 프로젝트 열기의 경합은 Catalog의 mutation lock이 막는다.** 테이블 교체와 "알려줄 저장소 목록" 스냅샷을 **같은 락 안에서** 처리하고 그 목록을 호출자에게 돌려준다 diff --git a/docs/architecture/ui.md b/docs/architecture/ui.md index e7cdfd1c..e7e41f2f 100644 --- a/docs/architecture/ui.md +++ b/docs/architecture/ui.md @@ -11,8 +11,9 @@ 가로채이지 않고 PTY로 통과해야 하므로, 앱 전역 명령은 leader 뒤에 한 키를 눌러야만 실행된다. - **Leader (prefix)**: 기본값 `Ctrl+F`, `[input] leader`로 변경 가능(`config.rs::parse_leader`가 - `ctrl+`만 허용하고 예약키·인코딩 불가 chord는 거부). leader를 누르면 `App.prefix_armed`가 - 켜지고 다음 키 한 개가 앱 명령(`input::prefix_action`)으로 해석된다. **타임아웃은 없다** — 해제 + `ctrl+`만 허용하고 예약키·인코딩 불가 chord는 거부). leader를 누르면 + `App.interaction.prefix_armed`가 켜지고 다음 키 한 개가 앱 명령(`input::prefix_action`)으로 + 해석된다. **타임아웃은 없다** — 해제 경로는 셋뿐이다: 매핑된 키 → Action 실행 후 해제, 미매핑 키 → 소비 후 해제, `Esc`/`Ctrl+C` → 취소. ` `는 terminal focus에서 leader를 `encode_key`로 리터럴 PTY 전송한다. - **prefix 매핑**: `t`=NewPane, `w`=ClosePane(terminal focus 한정 — unfocus 시 active pane이 다른 diff --git a/docs/architecture/web.md b/docs/architecture/web.md index 9ea14ee4..0d710cd3 100644 --- a/docs/architecture/web.md +++ b/docs/architecture/web.md @@ -40,27 +40,20 @@ git 데이터도 터미널도 전혀 모르는 계층이며, 웹 표면이 하 `../../etc/passwd`를 받아들였다. `load_file_diff`가 경로를 파일이 아니라 git pathspec으로 넘겨 검증기에 닿지 않았고 공격자의 경로를 그대로 되돌려줬다. **라우트가 "어떤 로더를 호출하느냐"에 따라 우연히 안전해서는 안 된다.** -- **저장소는 opaque id로만 지정**한다(`catalog/`). 클라이언트가 디렉토리를 이름 붙일 수 없으므로 +- **저장소는 opaque id로만 지정**한다(`src/session/catalog/`). 클라이언트가 디렉토리를 이름 붙일 수 없으므로 "어느 저장소인가"는 검증할 입력이 아니라 성공하거나 404가 되는 조회다. id는 프로세스 수명 동안 안정적이라 무관한 탭을 열고 닫아도 다른 id가 재배치되지 않는다. -- **카탈로그에 들어오는 모든 경로는 `resolve_repo_path`의 것 하나로 정규화된다**(`Catalog::normalized`). - 카탈로그는 served 집합·`hidden`·`order` 전부를 **문자열 비교**로 동일성을 판단하므로, 한 worktree가 - 두 철자로 들어오면 두 탭이 된다. 실제로 그랬다 — 클라이언트가 여는 경로(`add_path`)만 정규화를 - 거치고 시작 시 base(`set_paths`: `--repo` 인자와 `workspace.json`)는 날것이라, 심링크를 지나거나 - 끝 슬래시가 다른 같은 저장소가 탭 두 개를 차지했다. 정규화는 호출부가 아니라 **경계 안쪽**에 둔다: - 입구가 넷이고(`set_paths`·`add_path`·`remove_path`·`reorder`) 호출부마다 기억하게 하면 다음 입구가 - 또 빠뜨린다. **기존 `workspace.json`은 저절로 낫는다** — 로드할 때 정규화되고 저장할 때 정규화된 - 형태로 다시 쓰인다. **경로를 키로 쓰는 나머지는 업그레이드 시 한 번 어긋난다** — `viewer.json`의 - `active_repo`와 프로젝트별 maximized(`prefs/maximized.rs`), 그리고 TUI가 기억한 뷰 - 상태(`session_for`)가 옛 철자로 저장돼 있으면 매칭되지 않는다. 결과는 그 프로젝트가 한 번 - 첫 화면으로 돌아오는 것뿐이고(active는 첫 repo로 폴백, 나머지는 기본값), 쓰면 다시 쌓인다. - 마이그레이션 코드는 영구히 남는 데 비해 되찾는 것이 그 정도라 두지 않았다. -- **저장소별 런타임**(`runtime/`): `SnapshotChannel`은 단일 consumer `mpsc`라 자기 것을 띄운다. +- **카탈로그 경로는 경계에서 정규화**한다(`Catalog::normalized`). `set_paths`·`add_path`·`remove_path`· + `reorder`가 모두 `resolve_repo_path`의 단일 철자를 사용한다. served 집합·`hidden`·`order`가 문자열로 + 동일성을 판단하므로, 중첩 디렉터리·심링크·끝 슬래시로 같은 worktree를 다시 열어도 탭이 중복되지 않는다. + 기존 저장 상태의 옛 철자는 다음 쓰기에서 정규화되며, 일시적으로 active/pane preference가 기본값으로 + 돌아올 수는 있어도 영구 마이그레이션 코드를 두지는 않는다. +- **저장소별 런타임**(`src/session/runtime/`): `SnapshotChannel`은 단일 consumer `mpsc`라 자기 것을 띄운다. 스냅샷을 wire 페이로드로 한 번만 줄여 팬아웃한다. **팬아웃은 conflate**된다 — 느린 구독자는 최신 상태를 받지 밀린 과거를 재생하지 않는다(슬롯 1개 + 1-depth 병합 wakeup). 소켓 I/O 중 락을 잡지 않는다. 페이로드가 직전과 동일하면 발행하지 않는다: producer는 변화가 아니라 타이머로 tick하므로, 그러지 않으면 유휴 저장소가 매초 스트리밍하며 seq를 태워 "뭔가 바뀌었나"의 지표로 쓸 수 없게 된다. -- **터미널**(`terminal/`)은 **세션의 터미널이고 attach한 TUI가 보는 것과 같은 pane**이다 +- **터미널**(`src/session/terminal/`)은 **세션의 터미널이고 attach한 TUI가 보는 것과 같은 pane**이다 ([session.md](session.md#세션-공유-데몬--클라이언트) 참고). raw PTY 바이트를 그대로 보낸다 — **화면은 서버가 그리지 않는다**(xterm.js가 이미 에뮬레이터다). 허브가 스트림을 파싱하는 것은 딱 한 가지, 다른 방법으로는 알 수 없는 **pane의 모드**를 위해서다. 4바이트 LE pane id를 앞에 붙인 @@ -114,7 +107,7 @@ git 데이터도 터미널도 전혀 모르는 계층이며, 웹 표면이 하 ### 순서는 서버가 authoritative하다 -- **터미널 pane 순서**(`terminal.rs::reorder_panes`, `lib/paneOrder.ts`): 클라이언트가 pane 헤더를 +- **터미널 pane 순서**(`src/session/terminal/hub_layout.rs::reorder_panes`, `lib/paneOrder.ts`): 클라이언트가 pane 헤더를 드래그하면 원하는 전체 순서를 `reorder`로 보내고, hub가 살아있는 pane에 맞춰 재조정한 뒤 (`canonical_order`: 요청 순서 중 실재하는 id 먼저, 요청이 빠뜨린 live pane은 현재 순서로 뒤에, 모르는 id·중복은 버림) canonical 순서를 `reordered`로 **전 클라이언트에 broadcast**한다. @@ -122,7 +115,7 @@ git 데이터도 터미널도 전혀 모르는 계층이며, 웹 표면이 하 순서로 수렴한다. 순서는 hub의 pane Vec에 살아 재접속 replay와 다른 기기가 자동으로 따라오고 디스크에는 쓰지 않는다. DnD는 HTML5 drag가 아니라 pointer 이벤트라(sidebar divider와 같은 선택) 폰 터치도 마우스와 동일하다. -- **어느 pane이 패널을 채우는지(zoom)**(`terminal/hub_zoom.rs`, `lib/zoom.ts`): 순서와 같은 자리·같은 +- **어느 pane이 패널을 채우는지(zoom)**(`src/session/terminal/hub_zoom.rs`, `lib/zoom.ts`): 순서와 같은 자리·같은 이유다. 클라이언트는 `zoom`을 보낸 뒤 `zoomed` echo로만 반영하고, `connect`가 현재 zoom을 재생해 **새로고침한 페이지가 zoom한 채로 돌아온다** — 전에는 한 페이지의 `useState`에 살아 리로드마다 사라졌다. **프레임 순서가 계약이다**: `Created`보다 zoom 해제가 먼저, replay에서는 pane보다 zoom이 @@ -130,10 +123,10 @@ git 데이터도 터미널도 전혀 모르는 계층이며, 웹 표면이 하 막는다). 클라이언트는 무엇을 그릴지를 raw 값이 아니라 살아있는 pane 목록에서 파생시켜 (`renderedZoom`) 두 프레임 사이의 렌더에서 빈 패널이 나오지 않게 한다. **디스크에는 쓰지 않으며 쓸 수도 없다** — zoom은 pane을 가리키고 pane은 데몬의 자식이라 재시작하면 가리킬 대상이 없다. 패널 - 단위 maximize(`prefs/maximized.rs`)가 파일에 남는 것과의 차이가 이것이다. **attach한 TUI는 + 단위 maximize(`src/session/prefs/maximized.rs`)가 파일에 남는 것과의 차이가 이것이다. **attach한 TUI는 통보받고 무시한다**(`backend/hub.rs`): TUI의 zoom은 자기 활성 pane을 따르고 diff 뷰어까지 덮는 다른 질문이다. -- **프로젝트 탭 순서**(`catalog/`, `POST /api/repos/order`): 같은 모양이되 **전송 채널이 다르다** — +- **프로젝트 탭 순서**(`src/session/catalog/`, `POST /api/repos/order`): 같은 모양이되 **전송 채널이 다르다** — repo 목록에는 전용 WebSocket이 없고 `/api/repos` 폴링뿐이라 broadcast 대신 REST로 갱신하고 다음 폴링이 그것을 받는다. **순서가 `rebuild`를 견디게** `Catalog`에 명시적 `order` overlay를 두어 `union_paths`가 base+added 자연 순서를 그 위에 정렬한다(순서에 없는 새 repo는 끝에). 폴링 스냅백은 @@ -211,9 +204,13 @@ Node 없는 설치가 전부 깨진다). CI가 재빌드해 커밋된 번들과 `viewer-ui/src`는 화면 조립과 재사용 단위를 분리한다. `pages/`는 화면 조립, `components/`는 재사용 UI, `hooks/`는 UI·터미널·저장소 상태, `lib/`는 API 이외의 순수 도메인/레이아웃 유틸리티, `api/`는 -서버 wire 계약과 HTTP 클라이언트, `styles/`는 전역 스타일이다. `pages/App.tsx`는 조립만 하고 상태 -배선은 도메인 훅이 쥔다 — 서로만 주고받는 ref들을 App에 늘어놓으면 그 handshake가 조립 코드에 섞여 -하나를 빠뜨렸을 때 원인이 보이지 않는다. +서버 wire 계약과 HTTP 클라이언트, `styles/`는 전역 스타일이다. `pages/App.tsx`는 조립만 하고 +`useAppViewModel`이 인증·프로젝트·clone을, `useRepoWorkspace`가 선택한 저장소의 status/log/pane을 +소유한다. 서로만 주고받는 ref들을 App에 늘어놓으면 그 handshake가 조립 코드에 섞여 하나를 빠뜨렸을 +때 원인이 보이지 않는다. `RepoShell`은 flat prop bag 대신 repository/sidebar/filePane/layout 계약을 +받는다. +터미널 WebSocket 메시지는 `api/terminal.ts`가 decode/encode하는 단일 경계를 두고, 각 terminal hook은 +검증된 discriminated union만 처리한다. wire 문자열을 hook마다 다시 해석하거나 조립하지 않는다. ### 서버 저장 preference @@ -222,7 +219,7 @@ UI, `hooks/`는 UI·터미널·저장소 상태, `lib/`는 API 이외의 순수 고정하는데, 눈대중이 아니라 기존 amber `#d9a441`(OKLCH L=0.751 C=0.130 h=79.8)의 **명도·채도를 유지한 채 hue만 돌려** 파생시킨다 — 어느 프리셋을 골라도 ink 스케일 위에서 가독성이 같다. 적용은 root의 `--color-accent` 오버라이드 하나로 끝난다. 저장은 `~/.nightcrow/viewer.json` - (`viewer/prefs/`), **저장소별이 아니라 세션 전역**이다: 뷰어는 여러 기기에서 열리고, repo id는 + (`src/session/prefs/`), **저장소별이 아니라 세션 전역**이다: 뷰어는 여러 기기에서 열리고, repo id는 프로세스 수명 동안만 안정적이라 저장소별 키는 재시작마다 사라진다. 전달은 3초 `/api/repos` 폴링에 얹고 쓰기는 `POST /api/prefs`(cross-site가 트리거할 수 없도록 GET이 아닌 POST, 인증 뒤). 순서 문제는 `useViewerPrefs`가 로컬 변경 횟수를 세어 자기보다 오래된 응답의 accent만 버리는 것으로 @@ -274,7 +271,7 @@ UI, `hooks/`는 UI·터미널·저장소 상태, `lib/`는 API 이외의 순수 auto-placement로 매 브레이크포인트에서 보이는 4개를 같은 track에 떨어뜨리므로, element를 하나 더 넣으면 나머지가 엉뚱한 track으로 밀린다. 그래서 터미널 패널 **안에서** 그 패널이 이미 그리는 `border-t` 위에 absolute로 얹는다. maximize 중과 `md` 미만에서는 렌더하지 않는다. -- **패널 최대화는 프로젝트별로 저장한다**(`prefs/maximized.rs`). "이 프로젝트의 화면을 어떻게 +- **패널 최대화는 프로젝트별로 저장한다**(`src/session/prefs/maximized.rs`). "이 프로젝트의 화면을 어떻게 배치했나"는 **view state**이고 TUI는 그것을 세션 파일에 프로젝트별로 이미 들고 있었다. **TUI의 파일에 쓰지 않고 공유하지도 않는다** — `workspace.json`은 TUI가 붙어 있는 동안 TUI 소유이고, 40행 터미널의 최대화와 1400px 창의 최대화는 애초에 같은 답이 아니다. **키는 절대 경로**로 @@ -304,7 +301,8 @@ UI, `hooks/`는 UI·터미널·저장소 상태, `lib/`는 API 이외의 순수 번호는 `select-none`. **파일 뷰의 번호는 프로토콜에 없다**: 인덱스가 곧 번호라 서버가 실어 보낼 이유가 없다. hunk 헤더는 TUI와 달리 gutter 자리를 비우지 않고 폭 전체를 쓰는 띠로 남긴다. - **사이드바 목록은 잘라내지 않고 가로로 스크롤한다**. `truncate`를 쓰지 않는 이유는 두 행을 구분하는 - 것이 대개 경로의 **꼬리**이기 때문이다 — `src/web/viewer/server.rs`와 `terminal.rs`는 말줄임이 + 것이 대개 경로의 **꼬리**이기 때문이다 — `src/session/terminal/hub_layout.rs`와 + `src/session/terminal/hub_zoom.rs`는 말줄임이 지우는 바로 그 부분에서만 갈린다. 단 TUI는 접두 컬럼을 고정한 채 가변 텍스트만 미는 반면 뷰어는 **행 전체가 함께 스크롤된다**(VS Code 탐색기와 같은 동작). `position: sticky` 접두 고정은 sticky 요소가 자기 배경과 hover 상태까지 따라가야 해서 기각했다. @@ -372,7 +370,7 @@ UI, `hooks/`는 UI·터미널·저장소 상태, `lib/`는 API 이외의 순수 저장소 안에서만 의미가 있어 전환 순간 날아오던 프레임이 새 프로젝트의 같은 id pane에 붙을 수 있으므로 `onmessage`가 자기 소켓이 아직 살아 있는지 먼저 확인한다. 터미널 패널에는 key를 줄 수 없어서(저장소별 마지막 포커스 pane 기억이 함께 사라진다) 정리는 여기서도 layout effect다. -- **끊긴 연결은 조용히 self-heal한다**(`api.ts`, `App.tsx`). 모바일 브라우저는 화면이 꺼지면 진행 중이던 +- **끊긴 연결은 조용히 self-heal한다**(`api.ts`, `useAppViewModel.ts`). 모바일 브라우저는 화면이 꺼지면 진행 중이던 fetch를 끊는데, 복귀 시 그 요청이 네트워크 실패로 reject된다(Chrome "Failed to fetch", Safari "Load failed"). 이걸 **fetch 경계에서 `NetworkError`로 감싸** HTTP 오류(`ApiError`)나 응답 처리 중의 `TypeError`(진짜 결함이라 반드시 노출)와 구분한다. `NetworkError`는 친절한 메시지를 담아 `err.message`를 @@ -459,7 +457,7 @@ UI, `hooks/`는 UI·터미널·저장소 상태, `lib/`는 API 이외의 순수 id로(없으면 `null`로) 답한다 — 동시 클론이 하나이므로 모호하지 않다. `null`은 에러가 아니라 "붙을 것이 없다"는 명시적 답이다. 숫자로 파싱되지 않는 `job`은 여전히 400 — 오타가 조용히 "무엇이 돌고 있나"로 바뀌면 그 클라이언트는 남의 job에 붙는다. -- **클론 job의 주인은 폴더 피커가 아니라 그 위다**(`useClone`을 `App.tsx`에서 호출). 훅을 피커 안에서 +- **클론 job의 주인은 폴더 피커가 아니라 그 위다**(`useClone`을 `useAppViewModel`에서 호출). 훅을 피커 안에서 부르면 다이얼로그를 닫는 순간 관측자가 unmount되어 완료 토스트도 실패 메시지도 없고 끝난 repo도 열리지 않는다. 피커는 `(부모 경로, URL)`을 올리기만 한다. **로그인 직후 진행 중인 job에 자동으로 붙는다** — 붙을 게 없거나 probe가 실패하면 조용히 넘어간다. diff --git a/docs/configuration.md b/docs/configuration.md index 4dade593..60cdbdbb 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -156,8 +156,8 @@ watch_on_signal = false # off by default; when true, a pane no # once something inside it quotes that pane's # token to this plugin. Such a pane is never # relaunched, only typed into while it lives. -allowed_resume_flags = [] # flags the plugin may append to re-open a - # session; empty refuses every relaunch +allowed_resume_flags = [] # flags/subcommands the plugin may append to + # re-open a session; empty refuses arguments [plugin.env] # plugin process only, never terminal panes NIGHTCROW_RECOVERY_LOG = "info" diff --git a/docs/plugins.md b/docs/plugins.md index 526a2348..6ba4aa39 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -39,10 +39,10 @@ into a terminal should be a change you read before it takes effect: name = "recovery" command = "nightcrow-recovery" enabled = true -# Flags the plugin may append to re-open a session. Empty by default, which -# refuses every relaunch. nightcrow cannot know what a CLI's flags mean, so it -# will not add one you did not list — that is what keeps a plugin from changing -# how a CLI asks for your approval. +# Resume flags or subcommands the plugin may append. Empty by default, which +# refuses every relaunch with arguments. nightcrow cannot know what a CLI's +# control tokens mean, so it will not add one you did not list — that is what +# keeps a plugin from changing how a CLI asks for your approval. allowed_resume_flags = ["--resume", "resume", "--session"] [[startup_command]] diff --git a/plugins/nightcrow-recovery/src/helper_delegate.rs b/plugins/nightcrow-recovery/src/helper_delegate.rs index ecf3e798..ff713da4 100644 --- a/plugins/nightcrow-recovery/src/helper_delegate.rs +++ b/plugins/nightcrow-recovery/src/helper_delegate.rs @@ -21,6 +21,11 @@ const SHELL: &str = "sh"; #[cfg(windows)] const SHELL: &str = "cmd.exe"; +#[cfg(not(windows))] +const SHELL_COMMAND_ARG: &str = "-c"; +#[cfg(windows)] +const SHELL_COMMAND_ARG: &str = "/C"; + /// Most stdout to take from a displaced command. A statusline is one short line; /// this only stops a runaway script from growing this process. const MAX_DELEGATED_STDOUT_BYTES: u64 = 64 * 1024; @@ -31,15 +36,16 @@ const EXIT_POLL: Duration = Duration::from_millis(2); /// Run `command` with `raw` on its stdin and bring back what it printed, or /// `None` if it could not be started, did not end well, or overran `budget`. /// -/// Through `sh -c`, not an argv we split ourselves: the provider documents that a -/// `statusLine` command "runs in a shell", and its own examples rely on it — a `~` -/// path, a `jq` pipeline, an inline `$(...)`. Re-splitting the string the user wrote -/// would quietly change what it means. This is the user's own configuration rather +/// Through the platform shell's command mode (`sh -c` or `cmd.exe /C`), not an +/// argv we split ourselves: the provider documents that a `statusLine` command +/// "runs in a shell", and its own examples rely on it — a `~` path, a `jq` +/// pipeline, an inline `$(...)`. Re-splitting the string the user wrote would +/// quietly change what it means. This is the user's own configuration rather /// than input from a stranger, but it is also not ours to reinterpret. pub(super) fn capture(command: &str, raw: &[u8], budget: Duration) -> Option { let deadline = Instant::now() + budget; let mut child = Command::new(SHELL) - .arg("-c") + .arg(SHELL_COMMAND_ARG) .arg(command) .stdin(Stdio::piped()) .stdout(Stdio::piped()) @@ -120,3 +126,16 @@ fn abandon(mut child: Child) -> Option { fn remaining(deadline: Instant) -> Duration { deadline.saturating_duration_since(Instant::now()) } + +#[cfg(all(test, windows))] +mod tests { + use super::*; + + #[test] + fn capture_executes_the_displaced_command_through_cmd() { + assert_eq!( + capture("echo delegated", b"{}", Duration::from_secs(5)).as_deref(), + Some("delegated") + ); + } +} diff --git a/plugins/nightcrow-recovery/src/provider/mod.rs b/plugins/nightcrow-recovery/src/provider/mod.rs index 30c643ad..076b56b5 100644 --- a/plugins/nightcrow-recovery/src/provider/mod.rs +++ b/plugins/nightcrow-recovery/src/provider/mod.rs @@ -8,6 +8,7 @@ use crate::protocol::{PaneGeneration, PaneToken}; use serde_json::Value; +use std::path::Path; pub mod claude; pub mod codex; @@ -107,7 +108,6 @@ pub enum ResumePlan { Hold(&'static str), } -#[allow(unused_variables)] pub trait Provider { /// Stable adapter name, reported in `status` and used in logs. fn name(&self) -> &'static str; @@ -115,29 +115,34 @@ pub trait Provider { /// Terminal text the pane produced. This is the least reliable source — /// wording changes between releases — so an adapter uses it only as a /// documented fallback. - fn on_output(&mut self, ctx: &PaneContext, text: &str, now_epoch: i64) -> Option { + fn on_output( + &mut self, + _ctx: &PaneContext, + _text: &str, + _now_epoch: i64, + ) -> Option { None } /// A signal that arrived over the IPC socket. fn on_signal( &mut self, - ctx: &PaneContext, - signal: &OutOfBand, - now_epoch: i64, + _ctx: &PaneContext, + _signal: &OutOfBand, + _now_epoch: i64, ) -> Option { None } /// Called on the plugin's timer, for an adapter that has to look somewhere /// itself (a rollout file, an HTTP endpoint). - fn poll(&mut self, ctx: &PaneContext, now_epoch: i64) -> Option { + fn poll(&mut self, _ctx: &PaneContext, _now_epoch: i64) -> Option { None } /// The pane's process has ended. Adapters that must not act while a /// provider is retrying internally use this as their gate. - fn on_exit(&mut self, ctx: &PaneContext) {} + fn on_exit(&mut self, _ctx: &PaneContext) {} /// How to resume, or `None` when this adapter cannot say safely. /// @@ -150,7 +155,15 @@ pub trait Provider { /// something this plugin knows nothing about — in which case the plugin stays /// out of the pane entirely. pub fn detect(command: Option<&str>) -> Option> { - let program = first_word(command?)?; + let word = first_word(command?)?; + let program = Path::new(word).file_name()?.to_str()?; + #[cfg(windows)] + let program = program.to_ascii_lowercase(); + #[cfg(windows)] + let program = [".exe", ".cmd", ".bat", ".com", ".ps1"] + .iter() + .find_map(|suffix| program.strip_suffix(suffix)) + .unwrap_or(&program); match program { "claude" => Some(Box::new(claude::Claude::default())), "codex" => Some(Box::new(codex::Codex::default())), @@ -178,12 +191,21 @@ pub fn detect_from_signal(kind: SignalKind) -> Option> { } } -/// The command's program name without its directory, so `/usr/local/bin/claude` -/// and `claude --foo` both resolve to `claude`. +/// The command's first shell word, including a quoted executable path. fn first_word(command: &str) -> Option<&str> { - let first = command.split_whitespace().next()?; - let base = first.rsplit('/').next().unwrap_or(first); - (!base.is_empty()).then_some(base) + let command = command.trim_start(); + let quote = command.chars().next()?; + if quote == '"' || (cfg!(not(windows)) && quote == '\'') { + let quoted = &command[quote.len_utf8()..]; + let end = quoted.find(quote)?; + let trailing = "ed[end + quote.len_utf8()..]; + if !trailing.is_empty() && !trailing.starts_with(char::is_whitespace) { + return None; + } + return (!quoted[..end].is_empty()).then_some("ed[..end]); + } + + command.split_whitespace().next() } /// Furthest ahead a reported reset time is believed. diff --git a/plugins/nightcrow-recovery/src/provider/mod_tests.rs b/plugins/nightcrow-recovery/src/provider/mod_tests.rs index 8ee1b9c0..29550197 100644 --- a/plugins/nightcrow-recovery/src/provider/mod_tests.rs +++ b/plugins/nightcrow-recovery/src/provider/mod_tests.rs @@ -76,6 +76,19 @@ fn each_known_provider_is_recognised_from_its_command_line() { } } +#[cfg(windows)] +#[test] +fn windows_executable_paths_and_wrapper_shims_are_recognised() { + for (command, name) in [ + (r"C:\Tools\claude.exe --model opus", "claude"), + (r#""C:\Program Files\OpenAI\codex.cmd" resume"#, "codex"), + (r"C:\Tools\opencode.PS1", "opencode"), + ] { + let provider = detect(Some(command)).unwrap_or_else(|| panic!("{command} is known")); + assert_eq!(provider.name(), name); + } +} + #[test] fn a_pane_running_something_else_is_not_watched_at_all() { for command in [ @@ -85,6 +98,8 @@ fn a_pane_running_something_else_is_not_watched_at_all() { Some("bash"), Some("zsh -l"), Some("claudette"), + Some(r#""C:\Tools\claude.exe"suffix"#), + Some(r#""C:\Tools\claude.exe"#), ] { assert!( detect(command).is_none(), diff --git a/src/app.rs b/src/app.rs index f49b6dcb..ccdfee76 100644 --- a/src/app.rs +++ b/src/app.rs @@ -7,6 +7,7 @@ mod commit_log_pagination; mod diff_load; mod file_view_load; mod focus; +mod interaction; mod log_nav; mod navigation; mod scroll; @@ -28,15 +29,14 @@ pub use crate::ui::file_view::{FileViewKey, FileViewState}; pub use crate::ui::log_view::LogView; pub use crate::ui::status_view::StatusView; pub use crate::ui::tree_view::TreeView; -use crossterm::event::{KeyEvent, KeyModifiers}; +pub(crate) use interaction::{InteractionState, leader_label_of}; use std::time::Instant; pub(crate) const LIST_PAGE_SIZE: usize = 10; pub(crate) const DIFF_PAGE_SIZE: usize = 20; // Keying expiry on the variant (not message text) forces a decision about -// when each new kind goes away — the old text-match scheme left several kinds -// never cleared. +// when each new kind goes away. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum NoticeKind { Git, @@ -54,17 +54,6 @@ pub struct Notice { pub text: String, } -// Free function so the empty screen (no project) can label its hints too. -pub fn leader_label_of(leader: KeyEvent) -> String { - match leader.code { - crossterm::event::KeyCode::Char(c) if leader.modifiers.contains(KeyModifiers::CONTROL) => { - format!("^{}", c.to_ascii_uppercase()) - } - crossterm::event::KeyCode::Char(c) => c.to_string(), - _ => "".to_string(), - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)] pub enum ViewMode { #[default] @@ -80,8 +69,7 @@ pub enum Focus { Terminal, } -// Auto-follow state: idle timer + last-steered path. Behaviour config stays -// on `cfg_agent_indicator` since the file-list renderer also reads it. +// Auto-follow state: idle timer + last-steered path. #[derive(Default)] pub struct AutoFollow { pub last_manual_nav_at: Option, @@ -97,9 +85,7 @@ pub struct App { pub repo_path: String, /// The daemon's opaque id for this repository, once attached. /// - /// `None` when running without a daemon, and until the first set arrives: - /// the id is the daemon's name for the repository, not a property of it, - /// so it cannot be derived here. + /// `None` when running without a daemon, and until the first set arrives. pub repo_id: Option, pub log_view: LogView, pub tree_view: TreeView, @@ -110,24 +96,19 @@ pub struct App { // (active only) — a background project's git work defers until its tab shows. pub(crate) pending_snapshot: Option, // Filesystem watcher for live tree refresh; active only in `ViewMode::Tree`. - // Inert when the OS watcher couldn't start (refresh-on-entry is the fallback). pub(crate) tree_watch: crate::runtime::tree_watch::TreeWatcher, // Watcher-touched directories not yet re-read. Filled by `drain_tree_watcher` - // (every project), consumed by `poll_tree_watcher` (active only) so a hidden - // project's tree refreshes when shown, not on the UI thread meanwhile. + // (every project), consumed by `poll_tree_watcher` (active only). pub(crate) tree_dirty: std::collections::BTreeSet, // Set when events were dropped/unattributed: next refresh re-reads everything. pub(crate) tree_dirty_all: bool, - // Saved selection waiting on the first snapshot. Cannot conflict with user - // input: an empty list offers nothing to select. + // Saved selection waiting on the first snapshot. pub(crate) pending_selection: Option<(String, usize)>, // Terminal focus, active pane, and fullscreen waiting on the panes. // // Panes belong to the session, so a fresh view has none until the daemon - // reports them — there is nothing to focus at the moment this would - // otherwise be applied. A fresh launch starts with the default here (focus - // the terminals when they show up) and a restored session replaces it with - // what was saved, so the two cannot fight over the first frame. + // reports them. A fresh launch starts with the default here and a restored + // session replaces it with what was saved. pub(crate) pending_terminal: Option, // Cached `git2::Repository` for sync loads. Opened lazily, invalidated in // `change_repo`. The snapshot worker keeps its own handle (`!Send`). @@ -142,28 +123,10 @@ pub struct App { // `None` for detached HEAD / unborn branch / bare repo. pub branch_name: Option, // Ref chips and ahead/behind sets for the Log view. Rebuilt only when - // `last_refs_fingerprint` disagrees with the newest snapshot's, so a fetch - // that moves `origin/dev` refreshes it but an idle poll does not. + // `last_refs_fingerprint` disagrees with the newest snapshot's. pub log_decorations: crate::git::diff::LogDecorations, pub(crate) last_refs_fingerprint: Option, - pub leader: KeyEvent, - // No timeout: stays armed until a follow-up key or `Esc`/`Ctrl+C` resolves it. - pub prefix_armed: bool, - // Mutually exclusive with `prefix_armed` (arming this clears the prefix). - // Same no-timeout model as the prefix. - pub awaiting_swap_target: bool, - // A release pairs with the press's pane, not the pane under the pointer: - // drag reports aren't forwarded, so the program that saw the press must see - // the release. Single slot — a second press overwrites (no multi-button). - pub pending_mouse_press: Option<( - crate::backend::PaneId, - crossterm::event::MouseButton, - u16, - u16, - )>, - // Mirror of `[mouse] enabled`. Gates only the hint bar's clickability — - // with capture off no mouse event arrives, but a label must not lie. - pub mouse_enabled: bool, + pub(crate) interaction: InteractionState, } #[cfg(test)] diff --git a/src/app/app_impl.rs b/src/app/app_impl.rs index 4bcff3de..4fe20ab6 100644 --- a/src/app/app_impl.rs +++ b/src/app/app_impl.rs @@ -1,4 +1,4 @@ -use crate::app::{App, AutoFollow, Focus, Notice, NoticeKind, ViewMode}; +use crate::app::{App, AutoFollow, Focus, InteractionState, Notice, NoticeKind, ViewMode}; use crate::backend::TerminalBackend; use crate::runtime::snapshot::SnapshotChannel; use crossterm::event::KeyEvent; @@ -56,7 +56,7 @@ impl App { // record would leave that program in a drag/selection state with no // release ever coming. pub fn release_pending_press_in_place(&mut self) { - if let Some((id, button, col, row)) = self.pending_mouse_press.take() { + if let Some((id, button, col, row)) = self.interaction.pending_mouse_press.take() { self.terminal.click_pane(id, button, false, col, row); } } @@ -120,29 +120,13 @@ impl App { branch_name: None, log_decorations: Default::default(), last_refs_fingerprint: None, - leader, - prefix_armed: false, - awaiting_swap_target: false, - pending_mouse_press: None, - mouse_enabled: true, + interaction: InteractionState::new(leader), }; tracing::info!(repo = %app.repo_path, "nightcrow started"); app } - pub fn prefix_armed(&self) -> bool { - self.prefix_armed - } - - pub fn arm_prefix(&mut self) { - self.prefix_armed = true; - } - - pub fn cancel_prefix(&mut self) { - self.prefix_armed = false; - } - // The repo dialog is process-level, so the full modal test lives on // `Workspace::overlay_active`; both feed the key and mouse handlers so a // click can never reach behind a modal that swallows keystrokes. @@ -153,30 +137,4 @@ impl App { || self.log_view.commit_search_active || self.log_view.file_search_active } - - pub fn awaiting_swap_target(&self) -> bool { - self.awaiting_swap_target - } - - // Clears the prefix so the two follow-up states never overlap. - pub fn begin_swap_target(&mut self) { - self.prefix_armed = false; - self.awaiting_swap_target = true; - } - - pub fn cancel_swap_target(&mut self) { - self.awaiting_swap_target = false; - } - - pub fn leader_label(&self) -> String { - crate::app::leader_label_of(self.leader) - } - - // Any modifier beyond the leader's own (Alt, Shift, Super, Hyper, Meta — - // enhanced keyboard protocols report the latter three) makes it a different - // chord that passes straight through to the PTY, so compare the full - // modifier set exactly. - pub fn is_leader_key(&self, key: KeyEvent) -> bool { - key.code == self.leader.code && key.modifiers == self.leader.modifiers - } } diff --git a/src/app/interaction.rs b/src/app/interaction.rs new file mode 100644 index 00000000..8d4e7fee --- /dev/null +++ b/src/app/interaction.rs @@ -0,0 +1,48 @@ +use crate::backend::PaneId; +use crossterm::event::{KeyEvent, KeyModifiers, MouseButton}; + +// Kept free-standing so the empty workspace can label its hints too. +pub(crate) fn leader_label_of(leader: KeyEvent) -> String { + match leader.code { + crossterm::event::KeyCode::Char(c) if leader.modifiers.contains(KeyModifiers::CONTROL) => { + format!("^{}", c.to_ascii_uppercase()) + } + crossterm::event::KeyCode::Char(c) => c.to_string(), + _ => "".to_string(), + } +} + +pub(crate) struct InteractionState { + pub(crate) leader: KeyEvent, + // No timeout: a follow-up key or explicit cancellation resolves it. + pub(crate) prefix_armed: bool, + // Arming this clears `prefix_armed`. + pub(crate) awaiting_swap_target: bool, + // Releases pair with the pane that received the press, not the pointer. + pub(crate) pending_mouse_press: Option<(PaneId, MouseButton, u16, u16)>, + // Mirrors `[mouse] enabled` for hint-bar click affordances. + pub(crate) mouse_enabled: bool, +} + +impl InteractionState { + pub(crate) fn new(leader: KeyEvent) -> Self { + Self { + leader, + prefix_armed: false, + awaiting_swap_target: false, + pending_mouse_press: None, + mouse_enabled: true, + } + } + + // Enhanced keyboard protocols can add modifier bits, so only the exact + // configured chord is the leader; augmented chords pass through. + pub(crate) fn is_leader_key(&self, key: KeyEvent) -> bool { + key.code == self.leader.code && key.modifiers == self.leader.modifiers + } + + pub(crate) fn begin_swap_target(&mut self) { + self.prefix_armed = false; + self.awaiting_swap_target = true; + } +} diff --git a/src/app/tests/helpers.rs b/src/app/tests/helpers.rs index 4edb56f5..2b79be19 100644 --- a/src/app/tests/helpers.rs +++ b/src/app/tests/helpers.rs @@ -76,11 +76,10 @@ pub(crate) fn app_with_files(files: Vec<&str>) -> App { branch_name: None, log_decorations: Default::default(), last_refs_fingerprint: None, - leader: KeyEvent::new(KeyCode::Char('f'), KeyModifiers::CONTROL), - prefix_armed: false, - awaiting_swap_target: false, - pending_mouse_press: None, - mouse_enabled: true, + interaction: InteractionState::new(KeyEvent::new( + KeyCode::Char('f'), + KeyModifiers::CONTROL, + )), } } diff --git a/src/app/tests/leader_notice.rs b/src/app/tests/leader_notice.rs index a4533bfb..c822647a 100644 --- a/src/app/tests/leader_notice.rs +++ b/src/app/tests/leader_notice.rs @@ -3,16 +3,16 @@ use super::*; #[test] fn leader_label_renders_ctrl_chord_as_caret_uppercase() { let mut app = app_with_files(vec!["a.rs"]); - assert_eq!(app.leader_label(), "^F"); - app.leader = KeyEvent::new(KeyCode::Char('b'), KeyModifiers::CONTROL); - assert_eq!(app.leader_label(), "^B"); + assert_eq!(leader_label_of(app.interaction.leader), "^F"); + app.interaction.leader = KeyEvent::new(KeyCode::Char('b'), KeyModifiers::CONTROL); + assert_eq!(leader_label_of(app.interaction.leader), "^B"); } #[test] fn leader_label_without_ctrl_prints_raw_char() { let mut app = app_with_files(vec!["a.rs"]); - app.leader = KeyEvent::new(KeyCode::Char('x'), KeyModifiers::NONE); - assert_eq!(app.leader_label(), "x"); + app.interaction.leader = KeyEvent::new(KeyCode::Char('x'), KeyModifiers::NONE); + assert_eq!(leader_label_of(app.interaction.leader), "x"); } /// Expiry is keyed on the notice's kind. The previous design matched the diff --git a/src/app/tests/mod.rs b/src/app/tests/mod.rs index f91d7705..14a28990 100644 --- a/src/app/tests/mod.rs +++ b/src/app/tests/mod.rs @@ -12,7 +12,7 @@ use crate::git::diff::{ChangedFile, CommitEntry, RepoSnapshot, StatusKind, load_ use crate::runtime::snapshot::SnapshotMsg; use crate::runtime::terminal::{PaneInfo, SCROLLBACK_LINES, TerminalFullscreen}; use crate::test_util::{make_repo, open_repo, run_git}; -use crossterm::event::{KeyCode, KeyModifiers}; +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use std::collections::HashMap; use std::path::Path; use std::sync::mpsc; diff --git a/src/application/attach.rs b/src/application/attach.rs index 8c4959f1..48a2509a 100644 --- a/src/application/attach.rs +++ b/src/application/attach.rs @@ -1,20 +1,16 @@ //! `nightcrow attach`: run the TUI against a session the daemon owns. //! -//! The difference from running standalone is where the tabs come from. Here -//! the daemon has them, so nothing is restored from the workspace file and +//! The daemon has the tabs, so nothing is restored from the workspace file and //! nothing is written back to it — the daemon is doing that. This client starts -//! with no projects and adopts the set the daemon volunteers on attach, which -//! is also how it learns about every change afterwards. +//! with no projects and adopts the set the daemon volunteers on attach. use crate::application::event_loop::{ProjectContext, main_loop}; use crate::application::session_link::SessionLink; use crate::application::splash::{SplashOutcome, splash_loop}; -use crate::application::terminal_guard::TerminalGuard; +use crate::application::terminal_guard::{TerminalGuard, open_terminal, restore_terminal}; use crate::daemon::client::DaemonClient; use crate::workspace::Workspace; use anyhow::Result; -use ratatui::{Terminal, backend::CrosstermBackend}; -use std::io; use syntect::highlighting::ThemeSet; /// Attach to the daemon and run the TUI until the user leaves or it goes away. @@ -24,15 +20,13 @@ pub(crate) fn run_attach() -> Result<()> { let cfg = crate::config::load_config()?; // Parsed before the alternate screen so its error is readable. The // configured startup terminals are not read here at all: the daemon runs - // them once for the whole session, and a client that resolved its own copy - // would only be able to disagree with it. + // them once for the whole session. let leader = crate::config::parse_leader(&cfg.input.leader)?; // Anchored where the daemon's is, not at the working directory. The // relative default (`.nightcrow/logs`) resolved against the cwd would // create that directory inside whatever repository the client was started - // from, and nightcrow writes nothing inside a repository it is only - // reading. + // from. let _log_guard = crate::platform::logging::init_logging( &cfg.log, &crate::platform::paths::state_dir_anchor(), @@ -42,26 +36,21 @@ pub(crate) fn run_attach() -> Result<()> { let _guard = TerminalGuard::enter(cfg.mouse.enabled)?; let original_hook = std::panic::take_hook(); std::panic::set_hook(Box::new(move |info| { - let _ = crossterm::terminal::disable_raw_mode(); - let _ = crossterm::execute!( - io::stdout(), - crossterm::event::DisableMouseCapture, - crossterm::terminal::LeaveAlternateScreen - ); + restore_terminal(); original_hook(info); })); - let mut terminal = Terminal::new(CrosstermBackend::new(io::stdout()))?; + let mut terminal = open_terminal()?; let ss = two_face::syntax::extra_newlines(); let ts = ThemeSet::load_defaults(); let ctx = ProjectContext { cfg: &cfg, leader }; // Starts empty on purpose: the daemon's first message is the set, and // seeding from the workspace file would put tabs on screen that the session - // does not have, only to close them a frame later. + // does not have. let mut ws = Workspace::new(leader); // View state is still this client's to remember — which file was selected - // in a repository is not part of the shared session (see the plan's - // shared/per-client boundary), so it is read from the same file as before. + // in a repository is not part of the shared session, so it is read from the + // same file as before. if let Some(stored) = crate::workspace::persistence::load_workspace() { ws.set_remembered(stored.sessions); } @@ -69,18 +58,14 @@ pub(crate) fn run_attach() -> Result<()> { // Read from the session's file, not asked of the daemon. The set that // carries the accent is sent by the watcher now, which does not race the // handshake to get there first — and this screen draws before `main_loop`, - // the only thing that drains the connection. The daemon writes the file on - // every change, so it is behind by nothing that matters, and the first set - // corrects it either way. `[theme]` names what a session with no stored - // colour starts in. - let session_accent = - crate::web::viewer::prefs::PrefsStore::load_seeded(cfg.theme.preset_index()) - .get() - .accent; + // the only thing that drains the connection. `[theme]` names what a session + // with no stored colour starts in. + let session_accent = crate::session::prefs::PrefsStore::load_seeded(cfg.theme.preset_index()) + .get() + .accent; // The splash is not the only screen that draws before the daemon's first // set arrives. Without this the first frames of the main view would come up - // in the default rather than the session's colour, and the splash that just - // painted correctly would appear to change its mind. + // in the default rather than the session's colour. ws.set_accent_index(session_accent); if matches!( @@ -92,7 +77,7 @@ pub(crate) fn run_attach() -> Result<()> { } // The view state is written whichever way the loop ends. Losing which file // was selected because the daemon stopped would be a second insult, and this - // half of the session file is the client's own — see `persist_view_state`. + // half of the session file is the client's own. let ended = main_loop( &mut terminal, &mut ws, diff --git a/src/application/bootstrap.rs b/src/application/bootstrap.rs index fcb7f6c9..b0b73bcd 100644 --- a/src/application/bootstrap.rs +++ b/src/application/bootstrap.rs @@ -11,7 +11,7 @@ pub(crate) fn init_app( let mut app = App::new(repo_path.to_string(), cfg.log.prompt_log, leader, backend); app.cfg_agent_indicator = cfg.agent_indicator.clone(); app.cfg_tree = cfg.tree.clone(); - app.mouse_enabled = cfg.mouse.enabled; + app.interaction.mouse_enabled = cfg.mouse.enabled; if cfg.tree.live_watch { app.tree_watch = crate::runtime::tree_watch::TreeWatcher::new(); } diff --git a/src/application/event_loop.rs b/src/application/event_loop.rs index 75de0fad..d83cc437 100644 --- a/src/application/event_loop.rs +++ b/src/application/event_loop.rs @@ -3,17 +3,16 @@ use crate::application::input::dispatch::{KeyOutcome, dispatch_key}; use crate::application::input::mouse::dispatch_mouse; use crate::application::input::paste::dispatch_paste; use crate::application::session_link::SessionLink; +use crate::application::terminal_guard::TuiTerminal; use crate::workspace::Workspace; use crossterm::event::{self, Event}; use ratatui::layout::Rect; -use ratatui::{Terminal, backend::CrosstermBackend}; -use std::io; use std::time::Duration; use syntect::highlighting::ThemeSet; use syntect::parsing::SyntaxSet; pub(crate) fn main_loop( - terminal: &mut Terminal>, + terminal: &mut TuiTerminal, ws: &mut Workspace, ss: &SyntaxSet, ts: &ThemeSet, @@ -23,18 +22,15 @@ pub(crate) fn main_loop( ) -> anyhow::Result<()> { loop { // Whoever owns the tab list gets the first word each tick: attached, - // the set may have changed under this client since the last frame, and - // rendering a stale one would show a tab the session no longer has. + // the set may have changed under this client since the last frame. link.sync(ws, ctx); if !link.is_connected() { tracing::info!("daemon connection lost"); // Reported rather than returned quietly. Leaving on a lost // connection looks identical to leaving on purpose — the terminal - // comes back with no explanation — and the two are not the same - // thing to whoever was working in it. Deliberately not "the session + // comes back with no explanation. Deliberately not "the session // is gone": the daemon may well be running and have dropped only - // this connection (a client that fell too far behind is cut off), so - // what is certain is stated and the rest is left to reattaching. + // this connection. anyhow::bail!( "the connection to the session ended. The session may still be running — \ reattach with `nightcrow attach`" @@ -42,23 +38,17 @@ pub(crate) fn main_loop( } // Every project drains its queues, not just the visible one: the // snapshot worker and PTY reader produce into unbounded channels - // regardless of which tab is on screen, so skipping the background - // ones would let them grow until the user switched back. + // regardless of which tab is on screen. // - // Only the active project *applies* its snapshot, though. That runs a - // full `refresh_diff`, and doing it for every open project would put - // several repositories' git diffs on the UI thread every tick. A - // background snapshot waits in `pending_snapshot` until its tab is - // shown (see `App::drain_snapshot`). + // Only the active project *applies* its snapshot, though. A background + // snapshot waits in `pending_snapshot` until its tab is shown. let active = ws.active_index(); for (i, project) in ws.projects_mut().iter_mut().enumerate() { if i == active { project.poll_snapshot(); // Applying a commit-log page can trigger a further prefetch and // load a commit diff synchronously, so it stays with the - // snapshot as active-only work. A hidden project's in-flight - // fetch is capped at one by `CommitLogPagination`, so its reply - // can wait in the channel without growing. + // snapshot as active-only work. project.poll_commit_log_page_fetch(); } else { project.drain_snapshot(); @@ -66,8 +56,7 @@ pub(crate) fn main_loop( // Both are cheap drains that must run everywhere: the tree watcher // to keep OS filesystem events from piling up, the terminal to // consume PTY output before the pipe fills and blocks the child. - // Acting on a watcher event rereads directories and previews a - // file, so like the snapshot that is active-only; a hidden project + // Acting on a watcher event is active-only; a hidden project // records the event and refreshes when its tab comes forward. if i == active { project.poll_tree_watcher(); @@ -167,7 +156,7 @@ pub(crate) fn main_loop( /// Carry out a handler's outcome. Returns `true` when the app should quit. pub(crate) fn apply_outcome( - terminal: &mut Terminal>, + terminal: &mut TuiTerminal, ws: &mut Workspace, link: &mut SessionLink, outcome: KeyOutcome, diff --git a/src/application/input/dispatch.rs b/src/application/input/dispatch.rs index eb89f76e..0305fe8c 100644 --- a/src/application/input/dispatch.rs +++ b/src/application/input/dispatch.rs @@ -25,26 +25,18 @@ pub(crate) enum KeyOutcome { /// A workspace-level action requested by a key or click. #[derive(Debug, PartialEq, Eq)] pub(crate) enum ProjectRequest { - /// Focus the tab at this index. Out-of-range indices are inert. Switch(usize), - /// Close the active tab. Refused when it is the only one. Close, - /// Open this resolved repo path as a tab, or focus the tab already on it. Open(String), - /// Raise the open-repo dialog. It lives on the workspace, so a handler - /// holding one project cannot open it directly. OpenDialog, - /// Move the session's accent one step along the cycle. CycleAccent, - /// Ask the session to re-read `config.toml`. ReloadConfig, } /// Everything a project needs beyond its repo path. /// /// Threaded to the input handlers rather than stored on `Workspace` so the -/// workspace stays a pure state container: opening a tab is the only thing -/// that needs the config, and it borrows it for the duration of one keypress. +/// workspace stays a pure state container. pub(crate) struct ProjectContext<'a> { pub(crate) cfg: &'a crate::config::Config, pub(crate) leader: KeyEvent, @@ -67,9 +59,9 @@ pub(crate) fn handle_key(app: &mut App, key: KeyEvent) -> KeyOutcome { // the user resumed typing. Runs before dispatch so an action that raises // a *new* notice still leaves it standing. if app.search_overlay_active() - || app.prefix_armed() - || app.awaiting_swap_target() - || app.is_leader_key(key) + || app.interaction.prefix_armed + || app.interaction.awaiting_swap_target + || app.interaction.is_leader_key(key) || app.focus != Focus::Terminal { app.dismiss_notice_on_app_input(); @@ -83,8 +75,8 @@ pub(crate) fn handle_key(app: &mut App, key: KeyEvent) -> KeyOutcome { // A prefix (or swap-target) could only be armed if an overlay opened // out from under it; disarm both so neither indicator lingers behind a // modal. - app.cancel_prefix(); - app.cancel_swap_target(); + app.interaction.prefix_armed = false; + app.interaction.awaiting_swap_target = false; // Search overlays are handled inside the focus-local upper handler. handle_upper_key(app, key, Action::None); return KeyOutcome::Continue; @@ -93,20 +85,20 @@ pub(crate) fn handle_key(app: &mut App, key: KeyEvent) -> KeyOutcome { // Swap-target mode is armed (` s`): this key is the digit naming // the pane to swap the active pane with. Checked before the prefix so its // dedicated follow-up handler owns the key. - if app.awaiting_swap_target() { + if app.interaction.awaiting_swap_target { return handle_swap_target_followup(app, key); } // Prefix is armed: this key is the single follow-up. Resolve it three // ways — Esc/Ctrl+C cancels, the leader again sends a literal leader to // the PTY, a mapped key runs its action; any other key is consumed. - if app.prefix_armed() { + if app.interaction.prefix_armed { return handle_prefix_followup(app, key); } // The leader chord arms the prefix; nothing else happens this tick. - if app.is_leader_key(key) { - app.arm_prefix(); + if app.interaction.is_leader_key(key) { + app.interaction.prefix_armed = true; return KeyOutcome::Continue; } @@ -188,7 +180,7 @@ pub(super) fn handle_global_action(app: &mut App, action: Action) -> Option app.terminal.scroll_active(true, SCROLL_LINE_STEP), Action::TermScrollLineDown => app.terminal.scroll_active(false, SCROLL_LINE_STEP), _ => { - if let Some(data) = encode_key(key) { + let app_cursor = app.terminal.active_pane_app_cursor(); + if let Some(data) = encode_key(key, app_cursor) { app.terminal.send_input(&data); } } diff --git a/src/application/input/mouse.rs b/src/application/input/mouse.rs index 1603bce5..97158b32 100644 --- a/src/application/input/mouse.rs +++ b/src/application/input/mouse.rs @@ -61,10 +61,8 @@ pub(crate) fn dispatch_mouse( /// Route a captured mouse event to the pane under the pointer. Releases pair /// with the press's pane (not the pointer pane); wheel scrolls the pane under /// the pointer; a left press outside pane content can focus an upper panel, -/// jump via a tab/`+N` marker, or run a hint-bar shortcut (dispatched as -/// synthesized keypresses so click and key share the path). In swap mode a -/// left click names the swap target. Drag/motion reports are not forwarded — -/// inner-program text selection stays with the outer terminal's Shift+drag. +/// jump via a tab/`+N` marker, or run a hint-bar shortcut. In swap mode a +/// left click names the swap target. Drag/motion reports are not forwarded. pub(crate) fn handle_mouse( app: &mut App, tabs: crate::ui::Chrome<'_>, @@ -73,26 +71,22 @@ pub(crate) fn handle_mouse( layout: &crate::config::LayoutConfig, ) -> KeyOutcome { // Releases route by the pending press, not the pointer, so they must be - // handled before the hit test — the pointer may have left the pane. They - // also bypass the modal guard: the press happened before the modal opened, - // and swallowing the release would leave the pending slot stale. + // handled before the hit test — the pointer may have left the pane. if let MouseEventKind::Up(_) = mouse.kind { release_pending_press(app, screen, layout, mouse.column, mouse.row); return KeyOutcome::Continue; } // Modal overlays own all other input while open — same rule as the key - // handler: a click behind a modal must not move focus or reach a pane. + // handler. if app.search_overlay_active() { return KeyOutcome::Continue; } // Pane-swap mode: a press names the swap target the way a digit does. - // Without this branch a click would change the active pane while leaving - // swap mode armed, so a later digit would swap the wrong pane. Wheel - // events fall through (like a paste): they don't name a pane. - if app.awaiting_swap_target() + // Wheel events fall through (like a paste): they don't name a pane. + if app.interaction.awaiting_swap_target && let MouseEventKind::Down(button) = mouse.kind { - app.cancel_swap_target(); + app.interaction.awaiting_swap_target = false; if button == crossterm::event::MouseButton::Left { let target = crate::ui::pane_at(app, screen, layout, mouse.column, mouse.row) .and_then(|(id, _)| app.terminal.panes.iter().position(|p| p.id == id)) @@ -110,13 +104,13 @@ pub(crate) fn handle_mouse( if button == crossterm::event::MouseButton::Left && let Some(idx) = crate::ui::project_tab_at(tabs, screen, mouse.column, mouse.row) { - app.cancel_prefix(); + app.interaction.prefix_armed = false; return KeyOutcome::Project(ProjectRequest::Switch(idx)); } if let Some(focus) = crate::ui::upper_panel_at(app, screen, layout, mouse.column, mouse.row) { - app.cancel_prefix(); + app.interaction.prefix_armed = false; app.focus = focus; } else if button == crossterm::event::MouseButton::Left { if let Some(idx) = @@ -124,7 +118,7 @@ pub(crate) fn handle_mouse( { // A tab click is a jump-key press with the pointer: same // prefix resolution and focus/fullscreen handling. - app.cancel_prefix(); + app.interaction.prefix_armed = false; app.switch_pane(idx); } else if let Some(click) = crate::ui::hint_click_at(app, tabs, screen, mouse.column, mouse.row) @@ -142,7 +136,7 @@ pub(crate) fn handle_mouse( MouseEventKind::Down(button) => { focus_clicked_pane(app, id); if app.terminal.click_pane(id, button, true, col, row) { - app.pending_mouse_press = Some((id, button, col, row)); + app.interaction.pending_mouse_press = Some((id, button, col, row)); } } MouseEventKind::ScrollUp => { @@ -173,11 +167,11 @@ fn dispatch_hint_click(app: &mut App, click: crate::ui::HintClick) -> KeyOutcome let plain = |c| KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE); match click { crate::ui::HintClick::Arm => { - let leader = app.leader; + let leader = app.interaction.leader; handle_key(app, leader) } crate::ui::HintClick::Leader(c) => { - let leader = app.leader; + let leader = app.interaction.leader; match handle_key(app, leader) { KeyOutcome::Continue => {} other => return other, @@ -201,10 +195,10 @@ fn release_pending_press( x: u16, y: u16, ) { - let Some((id, pressed, _, _)) = app.pending_mouse_press else { + let Some((id, pressed, _, _)) = app.interaction.pending_mouse_press else { return; }; - app.pending_mouse_press = None; + app.interaction.pending_mouse_press = None; let Some(rect) = crate::ui::terminal_content_areas(app, screen, layout) .into_iter() .find_map(|(pid, rect)| (pid == id).then_some(rect)) @@ -226,7 +220,7 @@ fn release_pending_press( /// a jump key does. A click is also a non-command event while the prefix is /// armed, so resolve the prefix first (same rule as `handle_paste`). fn focus_clicked_pane(app: &mut App, id: crate::backend::PaneId) { - app.cancel_prefix(); + app.interaction.prefix_armed = false; let Some(idx) = app.terminal.panes.iter().position(|p| p.id == id) else { return; }; diff --git a/src/application/input/paste.rs b/src/application/input/paste.rs index 909e4893..81ac6c9d 100644 --- a/src/application/input/paste.rs +++ b/src/application/input/paste.rs @@ -31,7 +31,7 @@ pub(crate) fn handle_paste(app: &mut App, text: &str) { // PREFIX indicator stuck and make the next key resolve as a follow-up. // Resolve the prefix first (tmux treats a non-command event as a cancel), // then route the paste normally. - app.cancel_prefix(); + app.interaction.prefix_armed = false; if app.focus == Focus::FileList && app.status_view.search_active { for ch in text.chars().filter(|c| !c.is_control()) { app.search_push(ch); diff --git a/src/application/input/prefix.rs b/src/application/input/prefix.rs index 96748902..86a5028c 100644 --- a/src/application/input/prefix.rs +++ b/src/application/input/prefix.rs @@ -12,15 +12,18 @@ use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; /// Resolve the single key pressed while the prefix is armed. The prefix is /// always disarmed before returning (tmux-style: one follow-up per leader). pub(super) fn handle_prefix_followup(app: &mut App, key: KeyEvent) -> KeyOutcome { - app.cancel_prefix(); + app.interaction.prefix_armed = false; // ` `: send the leader chord literally to the focused PTY so the // running program still sees the prefix key when the user means it. This // is resolved before the Esc/Ctrl+C cancel below so that a `ctrl+c` leader // can still deliver a literal Ctrl+C via `` (Esc remains a // universal cancel regardless of the configured leader). - if app.is_leader_key(key) { + if app.interaction.is_leader_key(key) { if app.focus == Focus::Terminal - && let Some(data) = encode_key(app.leader) + && let Some(data) = encode_key( + app.interaction.leader, + app.terminal.active_pane_app_cursor(), + ) { app.terminal.send_input(&data); } @@ -49,7 +52,7 @@ pub(super) fn handle_prefix_followup(app: &mut App, key: KeyEvent) -> KeyOutcome /// mapping is reused from `prefix_action` so it matches the focus-jump digits /// one-for-one (`3`..`9`,`0` → panes `0`..`7`). pub(super) fn handle_swap_target_followup(app: &mut App, key: KeyEvent) -> KeyOutcome { - app.cancel_swap_target(); + app.interaction.awaiting_swap_target = false; let is_ctrl_c = key.code == KeyCode::Char('c') && key.modifiers.contains(KeyModifiers::CONTROL); if key.code == KeyCode::Esc || is_ctrl_c { diff --git a/src/application/session_link.rs b/src/application/session_link.rs index 06abed22..224adc13 100644 --- a/src/application/session_link.rs +++ b/src/application/session_link.rs @@ -2,18 +2,15 @@ //! //! The daemon owns which repositories are open and in what order. This client //! asks for a change and adopts whatever comes back — including changes another -//! client made, which arrive with nothing having asked. -//! -//! Which tab is in front is the daemon's too, so switching is a request and -//! every client follows the answer. What stays local is everything *inside* a -//! project — the view mode, the cursor, the scroll — which is what makes two -//! clients on one session more than two copies of one screen. +//! client made. Which tab is in front is the daemon's too, so switching is a +//! request and every client follows the answer. What stays local is everything +//! *inside* a project — the view mode, the cursor, the scroll. use crate::application::bootstrap::init_app; use crate::application::input::dispatch::{ProjectContext, ProjectRequest}; use crate::daemon::client::DaemonClient; use crate::daemon::protocol::{RepoSummary, ServerMessage}; -use crate::web::viewer::terminal::frame::ServerMessage as HubServerMessage; +use crate::session::terminal::frame::ServerMessage as HubServerMessage; use crate::workspace::Workspace; pub(crate) struct SessionLink { @@ -46,26 +43,20 @@ impl SessionLink { ws.set_accent_index(accent); } // A refusal this client asked for — a path that is not a - // directory, or one repository too many. Shown where every - // other refusal is shown. + // directory, or one repository too many. ServerMessage::Error { message } => { ws.raise_notice(crate::app::NoticeKind::Project, message); } // Shown where the refusal above is shown, because the two are the - // same answer to the same request — a reload either applied or it - // did not, and both are news for the client that asked and for - // nobody else. + // same answer to the same request. ServerMessage::Reloaded { summary } => { ws.raise_notice(crate::app::NoticeKind::Session, summary); } // Answered during the handshake; a later one would mean the - // daemon restarted under this client, which the connection loss - // reports on its own. + // daemon restarted under this client. ServerMessage::Hello { .. } => {} // Only a refusal reaches here. A pane created, exited, or - // reordered goes to that repository's backend, which is what - // renders it; a refusal is about a request rather than a pane, - // so it belongs on the tab that shows notices. + // reordered goes to that repository's backend. ServerMessage::Terminal { repo, event } => { if let HubServerMessage::Error { message } = event { notify_repo(ws, &repo, message); diff --git a/src/application/splash.rs b/src/application/splash.rs index fe697a1b..7cdcfbed 100644 --- a/src/application/splash.rs +++ b/src/application/splash.rs @@ -1,6 +1,5 @@ +use crate::application::terminal_guard::TuiTerminal; use crossterm::event::{self, Event, KeyCode, KeyEventKind}; -use ratatui::{Terminal, backend::CrosstermBackend}; -use std::io; pub(crate) enum SplashOutcome { Enter, @@ -14,7 +13,7 @@ pub(crate) enum SplashOutcome { /// that carries the colour has not arrived yet. Reading it here is what keeps /// the splash and the view a moment later from being two different colours. pub(crate) fn splash_loop( - terminal: &mut Terminal>, + terminal: &mut TuiTerminal, accent_idx: usize, ) -> anyhow::Result { let splash = crate::ui::splash::SplashState::new(); diff --git a/src/application/terminal_guard.rs b/src/application/terminal_guard.rs index 58caf23a..5c22302a 100644 --- a/src/application/terminal_guard.rs +++ b/src/application/terminal_guard.rs @@ -6,10 +6,38 @@ use crossterm::event::{ DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture, }; use crossterm::{ - execute, - terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode}, + Command, execute, + terminal::{ + DisableLineWrap, EnableLineWrap, EnterAlternateScreen, LeaveAlternateScreen, + disable_raw_mode, enable_raw_mode, + }, }; -use std::io; +use ratatui::{Terminal, backend::CrosstermBackend}; +use std::io::{self, BufWriter, Write}; + +/// The render target for every screen the TUI draws. +/// +/// `io::stdout()` is line buffered, and a Ratatui frame carries no newlines — +/// so a frame leaves this process in buffer-sized pieces rather than as one +/// write. On Windows each piece is its own console write that the host has to +/// re-parse, which is both the expensive path and the one where a frame can be +/// split. Buffering a whole frame collapses that to a single write per +/// `flush`, which Ratatui already performs exactly once per `draw`. +pub(crate) type TuiTerminal = Terminal>>; + +/// Sized so a full-screen redraw with per-cell styling never has to be split: +/// worst case is roughly one SGR sequence per cell, and a large window is a few +/// hundred thousand bytes of those. +const FRAME_BUFFER_BYTES: usize = 1 << 20; + +/// Build the buffered terminal. Enter [`TerminalGuard`] first — this only opens +/// the writer, it does not touch the terminal's modes. +pub(crate) fn open_terminal() -> io::Result { + Terminal::new(CrosstermBackend::new(BufWriter::with_capacity( + FRAME_BUFFER_BYTES, + io::stdout(), + ))) +} pub(crate) struct TerminalGuard; @@ -20,19 +48,20 @@ impl TerminalGuard { // `Event::Paste(String)` instead of a flood of `Event::Key` chars — // the latter would each be filtered as control chars by the search // handler and silently drop newlines. - match execute!(io::stdout(), EnterAlternateScreen, EnableBracketedPaste) { - Ok(()) => {} - Err(err) if err.kind() == io::ErrorKind::Unsupported => { + // Ratatui positions every changed cell itself. Host-side autowrap is + // therefore both unnecessary and dangerous: writing the bottom-right + // cell can scroll the physical screen while Ratatui's back buffer still + // describes the pre-scroll frame, leaving duplicated rows and stale + // fragments on subsequent partial draws. + if let Err(err) = execute!(io::stdout(), EnterAlternateScreen, DisableLineWrap) { + restore_terminal(); + return Err(err.into()); + } + if let Err(err) = execute!(io::stdout(), EnableBracketedPaste) { + if err.kind() == io::ErrorKind::Unsupported { tracing::warn!("bracketed paste unavailable; multi-line paste will be degraded"); - // EnterAlternateScreen may have succeeded before EnableBracketedPaste failed. - // Retry it — alternate screen entry is idempotent. - execute!(io::stdout(), EnterAlternateScreen).map_err(|err| { - let _ = disable_raw_mode(); - err - })?; - } - Err(err) => { - let _ = disable_raw_mode(); + } else { + restore_terminal(); return Err(err.into()); } } @@ -45,13 +74,7 @@ impl TerminalGuard { // failed), and no TerminalGuard exists yet to undo it on drop — // send the disable explicitly; it is harmless when capture never // took effect. - let _ = execute!( - io::stdout(), - DisableMouseCapture, - DisableBracketedPaste, - LeaveAlternateScreen - ); - let _ = disable_raw_mode(); + restore_terminal(); return Err(err.into()); } @@ -61,14 +84,79 @@ impl TerminalGuard { impl Drop for TerminalGuard { fn drop(&mut self) { - // DisableMouseCapture is unconditional: it merely writes the reset - // sequences, which are harmless when capture was never enabled. - let _ = execute!( - io::stdout(), - DisableMouseCapture, - DisableBracketedPaste, - LeaveAlternateScreen - ); - let _ = disable_raw_mode(); + restore_terminal(); + } +} + +/// Restore every terminal mode nightcrow may have enabled. +/// +/// The reset sequences and `disable_raw_mode` are safe to repeat, which lets +/// the panic hook restore the terminal immediately while `TerminalGuard` still +/// performs the same cleanup during unwinding. +pub(crate) fn restore_terminal() { + let _ = write_terminal_restore(io::stdout()); + let _ = disable_raw_mode(); +} + +fn write_terminal_restore(mut writer: impl Write) -> io::Result<()> { + #[cfg(windows)] + { + // Mouse capture is a WinAPI console-input mode even when output VT + // sequences are supported. Restore it through WinAPI unconditionally; + // emitting its ANSI spelling does not restore that saved input mode. + let mouse = DisableMouseCapture.execute_winapi(); + if EnableLineWrap.is_ansi_code_supported() { + let ansi = write_terminal_restore_ansi(&mut writer); + return mouse.and(ansi); + } + + // Bracketed paste has no legacy WinAPI equivalent. Run the remaining + // restores independently so its Unsupported error cannot prevent the + // line-wrap or screen-buffer restoration. + let wrap = EnableLineWrap.execute_winapi(); + let screen = LeaveAlternateScreen.execute_winapi(); + mouse.and(wrap).and(screen) + } + + #[cfg(not(windows))] + { + write_terminal_restore_ansi(&mut writer) + } +} + +fn write_terminal_restore_ansi(mut writer: impl Write) -> io::Result<()> { + // Mouse capture is unconditional: the reset sequences are harmless when + // capture was never enabled. + let mut commands = String::new(); + DisableMouseCapture + .write_ansi(&mut commands) + .map_err(io::Error::other)?; + DisableBracketedPaste + .write_ansi(&mut commands) + .map_err(io::Error::other)?; + EnableLineWrap + .write_ansi(&mut commands) + .map_err(io::Error::other)?; + LeaveAlternateScreen + .write_ansi(&mut commands) + .map_err(io::Error::other)?; + writer.write_all(commands.as_bytes())?; + writer.flush() +} + +#[cfg(test)] +mod tests { + use super::write_terminal_restore_ansi; + + #[test] + fn terminal_restore_disables_paste_and_leaves_the_alternate_screen() { + let mut output = Vec::new(); + + write_terminal_restore_ansi(&mut output).unwrap(); + + let output = String::from_utf8(output).unwrap(); + assert!(output.contains("\u{1b}[?2004l")); + assert!(output.contains("\u{1b}[?7h")); + assert!(output.contains("\u{1b}[?1049l")); } } diff --git a/src/application/tests/mouse_clicks.rs b/src/application/tests/mouse_clicks.rs index 0238af51..10347c5a 100644 --- a/src/application/tests/mouse_clicks.rs +++ b/src/application/tests/mouse_clicks.rs @@ -62,7 +62,7 @@ fn handle_mouse_click_completes_an_armed_swap_with_the_clicked_pane() { let first_id = app.terminal.panes[0].id; let (clicked_id, rect) = areas[1]; assert_ne!(clicked_id, first_id); - app.begin_swap_target(); + app.interaction.begin_swap_target(); let down = MouseEventKind::Down(crossterm::event::MouseButton::Left); handle_mouse( @@ -76,7 +76,7 @@ fn handle_mouse_click_completes_an_armed_swap_with_the_clicked_pane() { // The clicked pane is the swap target, exactly like its digit: the // previously active pane moves into the clicked slot and stays active, once // the session answers with the new order. - assert!(!app.awaiting_swap_target()); + assert!(!app.interaction.awaiting_swap_target); app.poll_terminal(); assert_eq!(app.terminal.panes[1].id, first_id); assert_eq!(app.terminal.active, 1); @@ -91,7 +91,7 @@ fn handle_mouse_tab_click_completes_an_armed_swap() { let (mut app, _) = app_with_two_panes_and_areas(); app.terminal.active = 0; let first_id = app.terminal.panes[0].id; - app.begin_swap_target(); + app.interaction.begin_swap_target(); let (x, y) = tab_xy_for(&app, 1); let down = MouseEventKind::Down(crossterm::event::MouseButton::Left); @@ -103,7 +103,7 @@ fn handle_mouse_tab_click_completes_an_armed_swap() { &crate::config::LayoutConfig::default(), ); - assert!(!app.awaiting_swap_target()); + assert!(!app.interaction.awaiting_swap_target); app.poll_terminal(); assert_eq!(app.terminal.panes[1].id, first_id); assert_eq!(app.terminal.active, 1); @@ -113,7 +113,7 @@ fn handle_mouse_tab_click_completes_an_armed_swap() { fn handle_mouse_press_elsewhere_cancels_an_armed_swap() { let (mut app, _) = app_with_two_panes_and_areas(); app.terminal.active = 0; - app.begin_swap_target(); + app.interaction.begin_swap_target(); let order_before: Vec<_> = app.terminal.panes.iter().map(|p| p.id).collect(); // (0, 0) is the header row: it names no pane, so the press must @@ -128,7 +128,7 @@ fn handle_mouse_press_elsewhere_cancels_an_armed_swap() { &crate::config::LayoutConfig::default(), ); - assert!(!app.awaiting_swap_target()); + assert!(!app.interaction.awaiting_swap_target); let order_after: Vec<_> = app.terminal.panes.iter().map(|p| p.id).collect(); assert_eq!(order_before, order_after); assert_eq!(app.terminal.active, 0); @@ -158,7 +158,7 @@ fn handle_mouse_hint_click_runs_the_named_leader_command() { "clicking ` t: new pane` must run the same command as the keys" ); assert!( - !app.prefix_armed(), + !app.interaction.prefix_armed, "the synthesized prefix must not linger" ); } @@ -179,7 +179,7 @@ fn handle_mouse_hint_click_on_the_leader_label_arms_the_prefix() { assert!(matches!(outcome, KeyOutcome::Continue)); assert!( - app.prefix_armed(), + app.interaction.prefix_armed, "clicking `: leader` must arm the prefix exactly like the chord" ); assert!( @@ -218,13 +218,16 @@ fn handle_mouse_arm_click_then_followup_click_runs_the_command() { panes_before + 1, "arm click + `t` click must open a pane like the key sequence" ); - assert!(!app.prefix_armed(), "the follow-up must consume the prefix"); + assert!( + !app.interaction.prefix_armed, + "the follow-up must consume the prefix" + ); } #[test] fn handle_mouse_hint_click_propagates_redraw_from_the_armed_row() { let mut app = app_with_terminal_pane(); - app.arm_prefix(); + app.interaction.prefix_armed = true; let x = hint_x_for(&app, crate::ui::HintClick::Plain('r')); let down = MouseEventKind::Down(crossterm::event::MouseButton::Left); @@ -237,7 +240,10 @@ fn handle_mouse_hint_click_propagates_redraw_from_the_armed_row() { ); assert!(matches!(outcome, KeyOutcome::Redraw)); - assert!(!app.prefix_armed(), "the follow-up must consume the prefix"); + assert!( + !app.interaction.prefix_armed, + "the follow-up must consume the prefix" + ); } #[test] diff --git a/src/application/tests/mouse_empty.rs b/src/application/tests/mouse_empty.rs index f1f745b4..236de57a 100644 --- a/src/application/tests/mouse_empty.rs +++ b/src/application/tests/mouse_empty.rs @@ -59,7 +59,13 @@ fn the_dialog_still_lets_a_pending_release_through() { &crate::config::LayoutConfig::default(), true, ); - assert!(ws.active().unwrap().pending_mouse_press.is_some()); + assert!( + ws.active() + .unwrap() + .interaction + .pending_mouse_press + .is_some() + ); ws.start_repo_input(); dispatch_mouse( @@ -72,7 +78,11 @@ fn the_dialog_still_lets_a_pending_release_through() { ); assert!( - ws.active().unwrap().pending_mouse_press.is_none(), + ws.active() + .unwrap() + .interaction + .pending_mouse_press + .is_none(), "the dialog must not swallow the release" ); } @@ -103,12 +113,18 @@ fn switching_projects_releases_a_pending_press_to_its_own_pane() { &crate::config::LayoutConfig::default(), true, ); - assert!(ws.active().unwrap().pending_mouse_press.is_some()); + assert!( + ws.active() + .unwrap() + .interaction + .pending_mouse_press + .is_some() + ); ws.add(app_with_files(vec!["b.rs"])); let old = &ws.projects()[0]; - assert!(old.pending_mouse_press.is_none()); + assert!(old.interaction.pending_mouse_press.is_none()); assert_eq!( backend_payloads(old), vec![b"\x1b[<0;1;1M".to_vec(), b"\x1b[<0;1;1m".to_vec()], diff --git a/src/application/tests/mouse_release.rs b/src/application/tests/mouse_release.rs index 0e1f22e2..32027c6d 100644 --- a/src/application/tests/mouse_release.rs +++ b/src/application/tests/mouse_release.rs @@ -45,7 +45,7 @@ fn handle_mouse_release_follows_the_pressed_pane_when_the_pointer_moves_away() { backend_payloads(&app), vec![b"\x1b[<0;1;1M".to_vec(), release] ); - assert!(app.pending_mouse_press.is_none()); + assert!(app.interaction.pending_mouse_press.is_none()); } #[test] @@ -84,7 +84,7 @@ fn handle_mouse_completes_a_pending_release_even_while_the_repo_modal_is_open() backend_payloads(&app), vec![b"\x1b[<0;1;1M".to_vec(), b"\x1b[<0;1;1m".to_vec()] ); - assert!(app.pending_mouse_press.is_none()); + assert!(app.interaction.pending_mouse_press.is_none()); } #[test] @@ -122,5 +122,5 @@ fn handle_mouse_release_pairs_by_the_stored_press_button() { backend_payloads(&app), vec![b"\x1b[<2;1;1M".to_vec(), b"\x1b[<2;1;1m".to_vec()] ); - assert!(app.pending_mouse_press.is_none()); + assert!(app.interaction.pending_mouse_press.is_none()); } diff --git a/src/application/tests/paste.rs b/src/application/tests/paste.rs index 8b755b0d..168b9592 100644 --- a/src/application/tests/paste.rs +++ b/src/application/tests/paste.rs @@ -8,12 +8,12 @@ use crate::application::input::paste::{dispatch_paste, handle_paste}; fn paste_while_prefix_armed_cancels_prefix() { let mut app = app_with_terminal_pane(); let _ = handle_key(&mut app, leader()); - assert!(app.prefix_armed()); + assert!(app.interaction.prefix_armed); handle_paste(&mut app, "hello"); assert!( - !app.prefix_armed(), + !app.interaction.prefix_armed, "a paste must resolve (cancel) the armed prefix" ); } diff --git a/src/application/tests/prefix.rs b/src/application/tests/prefix.rs index 30d2f938..7b30edd9 100644 --- a/src/application/tests/prefix.rs +++ b/src/application/tests/prefix.rs @@ -28,11 +28,14 @@ fn handle_key_leader_then_q_quits() { let first = handle_key(&mut app, leader()); assert!(matches!(first, KeyOutcome::Continue)); - assert!(app.prefix_armed(), "leader must arm the prefix"); + assert!(app.interaction.prefix_armed, "leader must arm the prefix"); let second = handle_key(&mut app, press(KeyCode::Char('q'), KeyModifiers::NONE)); assert!(matches!(second, KeyOutcome::Quit)); - assert!(!app.prefix_armed(), "prefix must disarm after follow-up"); + assert!( + !app.interaction.prefix_armed, + "prefix must disarm after follow-up" + ); } #[test] @@ -44,7 +47,10 @@ fn handle_key_bare_ctrl_f_arms_prefix_and_does_not_quit() { let outcome = handle_key(&mut app, press(KeyCode::Char('f'), KeyModifiers::CONTROL)); assert!(matches!(outcome, KeyOutcome::Continue)); - assert!(app.prefix_armed(), "the leader press arms the prefix"); + assert!( + app.interaction.prefix_armed, + "the leader press arms the prefix" + ); } #[test] @@ -55,7 +61,10 @@ fn leader_x_asks_the_workspace_to_close_the_project() { let outcome = handle_key(&mut app, press(KeyCode::Char('x'), KeyModifiers::NONE)); assert_eq!(outcome, KeyOutcome::Project(ProjectRequest::Close)); - assert!(!app.prefix_armed(), "prefix must disarm after follow-up"); + assert!( + !app.interaction.prefix_armed, + "prefix must disarm after follow-up" + ); } #[test] @@ -88,22 +97,28 @@ fn a_doubled_leader_on_the_empty_screen_does_not_quit() { fn handle_key_leader_esc_cancels() { let mut app = app_with_files(vec!["a.rs"]); let _ = handle_key(&mut app, leader()); - assert!(app.prefix_armed()); + assert!(app.interaction.prefix_armed); let outcome = handle_key(&mut app, press(KeyCode::Esc, KeyModifiers::NONE)); assert!(matches!(outcome, KeyOutcome::Continue)); - assert!(!app.prefix_armed(), "Esc must cancel the armed prefix"); + assert!( + !app.interaction.prefix_armed, + "Esc must cancel the armed prefix" + ); } #[test] fn handle_key_leader_ctrl_c_cancels() { let mut app = app_with_terminal_pane(); let _ = handle_key(&mut app, leader()); - assert!(app.prefix_armed()); + assert!(app.interaction.prefix_armed); let outcome = handle_key(&mut app, press(KeyCode::Char('c'), KeyModifiers::CONTROL)); assert!(matches!(outcome, KeyOutcome::Continue)); - assert!(!app.prefix_armed(), "Ctrl+C must cancel the armed prefix"); + assert!( + !app.interaction.prefix_armed, + "Ctrl+C must cancel the armed prefix" + ); // The cancel is consumed, never leaked to the PTY. assert!( backend_payloads(&app).is_empty(), @@ -112,50 +127,18 @@ fn handle_key_leader_ctrl_c_cancels() { } #[test] -fn handle_key_ctrl_super_leader_passes_through() { - // A Super/Hyper/Meta bit on top of Ctrl+ (enhanced keyboard - // protocols report these) is a different chord, so it must reach the - // PTY rather than arm the prefix. - let mut app = app_with_terminal_pane(); - - let outcome = handle_key( - &mut app, - press( - KeyCode::Char('f'), - KeyModifiers::CONTROL | KeyModifiers::SUPER, - ), - ); - - assert!(matches!(outcome, KeyOutcome::Continue)); - assert!( - !app.prefix_armed(), - "Ctrl+Super+leader must not arm the prefix" - ); -} - -#[test] -fn handle_key_ctrl_alt_leader_passes_through() { - // Ctrl+Alt+ carries an extra modifier, so it is NOT the leader - // chord — it must reach the PTY rather than arm the prefix. - let mut app = app_with_terminal_pane(); - - let outcome = handle_key( - &mut app, - press( - KeyCode::Char('f'), - KeyModifiers::CONTROL | KeyModifiers::ALT, - ), - ); - - assert!(matches!(outcome, KeyOutcome::Continue)); - assert!( - !app.prefix_armed(), - "Ctrl+Alt+leader must not arm the prefix" - ); - assert!( - !backend_payloads(&app).is_empty(), - "Ctrl+Alt+leader must pass through to the PTY" - ); +fn leader_with_an_extra_modifier_passes_through() { + for extra in [KeyModifiers::ALT, KeyModifiers::SUPER] { + let mut app = app_with_terminal_pane(); + let outcome = handle_key( + &mut app, + press(KeyCode::Char('f'), KeyModifiers::CONTROL | extra), + ); + + assert!(matches!(outcome, KeyOutcome::Continue)); + assert!(!app.interaction.prefix_armed); + assert!(!backend_payloads(&app).is_empty()); + } } #[test] @@ -164,14 +147,14 @@ fn leader_leader_sends_literal_leader_even_when_leader_is_ctrl_c() { // as a literal Ctrl+C (0x03); the leader-again path takes precedence // over the Ctrl+C cancel path. let mut app = app_with_terminal_pane(); - app.leader = press(KeyCode::Char('c'), KeyModifiers::CONTROL); + app.interaction.leader = press(KeyCode::Char('c'), KeyModifiers::CONTROL); let _ = handle_key(&mut app, press(KeyCode::Char('c'), KeyModifiers::CONTROL)); - assert!(app.prefix_armed()); + assert!(app.interaction.prefix_armed); let outcome = handle_key(&mut app, press(KeyCode::Char('c'), KeyModifiers::CONTROL)); assert!(matches!(outcome, KeyOutcome::Continue)); - assert!(!app.prefix_armed()); + assert!(!app.interaction.prefix_armed); assert_eq!( backend_payloads(&app).concat(), vec![0x03], @@ -183,11 +166,11 @@ fn leader_leader_sends_literal_leader_even_when_leader_is_ctrl_c() { fn handle_key_leader_unmapped_followup_cancels() { let mut app = app_with_terminal_pane(); let _ = handle_key(&mut app, leader()); - assert!(app.prefix_armed()); + assert!(app.interaction.prefix_armed); let outcome = handle_key(&mut app, press(KeyCode::Char('z'), KeyModifiers::NONE)); assert!(matches!(outcome, KeyOutcome::Continue)); - assert!(!app.prefix_armed()); + assert!(!app.interaction.prefix_armed); // The unmapped follow-up is consumed, NOT forwarded to the PTY. assert!( backend_payloads(&app).is_empty(), @@ -199,11 +182,11 @@ fn handle_key_leader_unmapped_followup_cancels() { fn handle_key_double_leader_sends_literal_to_pty() { let mut app = app_with_terminal_pane(); let _ = handle_key(&mut app, leader()); - assert!(app.prefix_armed()); + assert!(app.interaction.prefix_armed); let outcome = handle_key(&mut app, leader()); assert!(matches!(outcome, KeyOutcome::Continue)); - assert!(!app.prefix_armed()); + assert!(!app.interaction.prefix_armed); // Ctrl+F encodes to 0x06 (ACK) — the literal leader byte. assert_eq!(backend_payloads(&app), vec![vec![0x06]]); } @@ -268,7 +251,7 @@ fn handle_key_leader_w_is_ignored_without_terminal_focus() { before, "leader+w must be a no-op outside terminal focus" ); - assert!(!app.prefix_armed()); + assert!(!app.interaction.prefix_armed); assert!( backend_payloads(&app).is_empty(), "the consumed follow-up must not reach the PTY" diff --git a/src/application/tests/prefix_digits.rs b/src/application/tests/prefix_digits.rs index 8de0a21c..e6740803 100644 --- a/src/application/tests/prefix_digits.rs +++ b/src/application/tests/prefix_digits.rs @@ -45,7 +45,7 @@ fn handle_key_leader_digits_mirror_focus_and_pane_fkeys() { assert_eq!(app.terminal.active, 7, "leader+0 must mirror F10 → pane 7"); assert!( - !app.prefix_armed(), + !app.interaction.prefix_armed, "a mapped follow-up must disarm the prefix" ); assert!( diff --git a/src/application/tests/search.rs b/src/application/tests/search.rs index bd6dfe72..4cbdb7ab 100644 --- a/src/application/tests/search.rs +++ b/src/application/tests/search.rs @@ -15,7 +15,10 @@ fn handle_key_overlay_blocks_leader_when_diff_search_active() { let before = app.mode; let _ = handle_key(&mut app, leader()); - assert!(!app.prefix_armed(), "leader must not arm behind an overlay"); + assert!( + !app.interaction.prefix_armed, + "leader must not arm behind an overlay" + ); let _ = handle_key(&mut app, press(KeyCode::Char('l'), KeyModifiers::NONE)); assert_eq!( diff --git a/src/application/tests/swap.rs b/src/application/tests/swap.rs index e3c7db29..5f923ab0 100644 --- a/src/application/tests/swap.rs +++ b/src/application/tests/swap.rs @@ -21,14 +21,20 @@ fn handle_key_leader_s_then_digit_swaps_active_pane() { // ` s` arms swap mode without acting. let _ = handle_key(&mut app, leader()); let _ = handle_key(&mut app, press(KeyCode::Char('s'), KeyModifiers::NONE)); - assert!(app.awaiting_swap_target(), "leader+s must arm swap mode"); - assert!(!app.prefix_armed(), "swap mode must clear the prefix"); + assert!( + app.interaction.awaiting_swap_target, + "leader+s must arm swap mode" + ); + assert!( + !app.interaction.prefix_armed, + "swap mode must clear the prefix" + ); // The digit resolves the swap — as a request to the session, which the tab // list follows once the order comes back. let _ = handle_key(&mut app, press(KeyCode::Char('5'), KeyModifiers::NONE)); assert!( - !app.awaiting_swap_target(), + !app.interaction.awaiting_swap_target, "the digit must disarm swap mode" ); app.poll_terminal(); @@ -54,7 +60,7 @@ fn handle_key_leader_s_esc_cancels_without_swapping() { let _ = handle_key(&mut app, press(KeyCode::Char('s'), KeyModifiers::NONE)); let _ = handle_key(&mut app, press(KeyCode::Esc, KeyModifiers::NONE)); - assert!(!app.awaiting_swap_target()); + assert!(!app.interaction.awaiting_swap_target); assert_eq!(app.terminal.active, 0); let after: Vec<_> = app.terminal.panes.iter().map(|p| p.id).collect(); assert_eq!(order, after, "esc must leave pane order unchanged"); @@ -75,7 +81,7 @@ fn handle_key_leader_s_non_digit_cancels() { let _ = handle_key(&mut app, press(KeyCode::Char('s'), KeyModifiers::NONE)); let _ = handle_key(&mut app, press(KeyCode::Char('z'), KeyModifiers::NONE)); - assert!(!app.awaiting_swap_target()); + assert!(!app.interaction.awaiting_swap_target); let after: Vec<_> = app.terminal.panes.iter().map(|p| p.id).collect(); assert_eq!(order, after); assert!(backend_payloads(&app).is_empty()); @@ -93,11 +99,11 @@ fn handle_key_leader_s_without_terminal_focus_does_not_arm() { let _ = handle_key(&mut app, press(KeyCode::Char('s'), KeyModifiers::NONE)); assert!( - !app.awaiting_swap_target(), + !app.interaction.awaiting_swap_target, "leader+s must not arm swap mode without terminal focus" ); assert!( - !app.prefix_armed(), + !app.interaction.prefix_armed, "the follow-up must still disarm the prefix" ); assert!( @@ -115,7 +121,7 @@ fn handle_key_leader_s_with_single_pane_does_not_arm() { let _ = handle_key(&mut app, press(KeyCode::Char('s'), KeyModifiers::NONE)); assert!( - !app.awaiting_swap_target(), + !app.interaction.awaiting_swap_target, "leader+s must not arm swap mode with a single pane" ); assert!(backend_payloads(&app).is_empty()); diff --git a/src/application/tests/terminal.rs b/src/application/tests/terminal.rs index 74763c8b..5ddcb3f3 100644 --- a/src/application/tests/terminal.rs +++ b/src/application/tests/terminal.rs @@ -79,3 +79,18 @@ fn handle_key_bare_c_in_a_terminal_pane_reaches_the_program() { assert_eq!(backend_payloads(&app), vec![b"c".to_vec()]); } + +#[test] +fn terminal_arrow_uses_the_active_panes_application_cursor_mode() { + let mut app = app_with_terminal_pane(); + let pane = app.terminal.panes[0].id; + app.terminal + .emulators + .get_mut(&pane) + .expect("pane emulator") + .process(b"\x1b[?1h"); + + let _ = handle_key(&mut app, press(KeyCode::Up, KeyModifiers::NONE)); + + assert_eq!(backend_payloads(&app), vec![b"\x1bOA".to_vec()]); +} diff --git a/src/application/tests/workspace.rs b/src/application/tests/workspace.rs index 038fd230..d1b785f9 100644 --- a/src/application/tests/workspace.rs +++ b/src/application/tests/workspace.rs @@ -114,7 +114,7 @@ fn dialog_swallows_the_leader_instead_of_arming_the_prefix() { // The dispatcher gives the dialog every key, so the leader is typed // (and rejected as a control char) rather than arming a prefix behind // the modal. - assert!(!ws.active().unwrap().prefix_armed()); + assert!(!ws.active().unwrap().interaction.prefix_armed); assert!(ws.repo_input.active); } diff --git a/src/backend/hub.rs b/src/backend/hub.rs index a9ea2e24..ac7b2cad 100644 --- a/src/backend/hub.rs +++ b/src/backend/hub.rs @@ -4,16 +4,14 @@ //! asks. One per repository, all sharing the connection the client attached //! with, so the panes it reports are the same ones the browser is looking at. //! -//! The consequences of not owning them show up in three places. A pane arrives -//! as an event instead of a return value, because its id comes from where the -//! PTY actually lives and because a pane another client opened has to arrive the -//! same way. Its size is not this client's to assume. And VT emulation still -//! happens here: the bytes are raw either way, so `PaneEmulator` reads them from -//! a socket exactly as it read them from a PTY. +//! A pane arrives as an event instead of a return value, because its id comes +//! from where the PTY actually lives. Its size is not this client's to assume. +//! VT emulation still happens here: the bytes are raw either way, so +//! `PaneEmulator` reads them from a socket exactly as it read them from a PTY. use super::{BackendEvent, PaneId, TerminalBackend}; use crate::daemon::terminal_link::{TerminalLink, TerminalMessage}; -use crate::web::viewer::terminal::frame::{ +use crate::session::terminal::frame::{ ClientMessage as HubClientMessage, ServerMessage as HubServerMessage, }; use anyhow::{Result, bail}; @@ -33,8 +31,7 @@ impl HubBackend { /// Answered with no sizes at all, because this client has measured nothing: /// the offer arrives on attach, before the first frame has laid out a single /// pane. The hub opens them at its own default and the first layout corrects - /// it — one repaint, in the same beat as the shell's first prompt, which is - /// where every locally-opened pane used to start too. + /// it. fn size_startup_panes(&self) { if let Err(err) = self .link diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 5947e38d..8de8b4d6 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -15,20 +15,16 @@ pub type PaneId = u32; pub enum BackendEvent { /// A pane now exists. Reported rather than returned from `create_pane` /// because a backend serving a shared session cannot answer on the spot: - /// the id comes from wherever the PTY actually lives, and a pane another - /// client created arrives the same way, with nothing here having asked. + /// the id comes from wherever the PTY actually lives. Created { pane: PaneId, rows: u16, cols: u16, /// Whether this side asked for it. A pane someone else opened must not - /// take the focus away from what this client is looking at — which tab - /// and pane a client sits on is its own business. + /// take the focus away from what this client is looking at. requested: bool, /// The name the session gives the pane, which only a configured startup - /// terminal has. `None` leaves the naming to this client (the title it - /// queued for a pane it asked for, else a positional default), and a - /// program emitting OSC 0/2 renames it either way. + /// terminal has. `None` leaves the naming to this client. title: Option, }, Output { @@ -42,8 +38,7 @@ pub enum BackendEvent { /// /// Only a backend serving a shared session reports this, and it is not /// necessarily what this side asked for: the size belongs to whichever - /// client owns the sizing, and one request can be clamped. An emulator has - /// to wrap where the child does, so this is what it follows. + /// client owns the sizing. An emulator has to wrap where the child does. Resized { pane: PaneId, rows: u16, @@ -52,26 +47,22 @@ pub enum BackendEvent { /// The canonical order of the panes. /// /// Only a backend serving a shared session reports this: the order is part - /// of what the session owns, so it arrives the same way a pane does — by - /// being told, whether or not this side asked. Ids this side does not know - /// are ignored and panes the order omits keep their place, so an order that - /// raced a create or an exit still applies. + /// of what the session owns. Ids this side does not know are ignored and + /// panes the order omits keep their place. Reordered { order: Vec, }, /// Whether this side is the one whose layout sets the pane sizes. /// /// A PTY has one size and a child cannot be re-flowed after the fact, so - /// one client decides it and the rest watch. Owning a local `PtyBackend` - /// means always owning the sizing, which is why nothing reports it there. + /// one client decides it and the rest watch. SizeOwnership { owned: bool, }, /// What a plugin driving `pane` reports about getting it running again. /// /// Only a backend serving a shared session reports this: the plugins run - /// beside the session's panes, not beside this client. Pane metadata, not - /// screen content — nothing here reaches an emulator. + /// beside the session's panes, not beside this client. Recovery { pane: PaneId, /// The plugin's own short label. Uninterpreted here; the one value with a @@ -87,12 +78,10 @@ pub enum BackendEvent { pub trait TerminalBackend { /// Ask for a pane sized `rows`x`cols`. When `command` is `Some`, the - /// pane's shell runs that command immediately (via `$SHELL -lc `); - /// `None` spawns a bare interactive shell. + /// pane's shell runs that command immediately; `None` spawns a bare + /// interactive shell. /// /// The pane arrives as [`BackendEvent::Created`], not as a return value. - /// `Ok` means the request was made, and an error means it could not be — - /// neither says the pane exists yet. fn create_pane(&mut self, rows: u16, cols: u16, command: Option<&str>) -> Result<()>; fn destroy_pane(&mut self, id: PaneId); fn send_input(&mut self, id: PaneId, data: &[u8]) -> Result<()>; @@ -102,8 +91,7 @@ pub trait TerminalBackend { /// Ask for the panes to be put in this order. /// /// A no-op by default: a backend that owns its panes has no one to negotiate - /// the order with, and whoever holds the list can put them in order itself. - /// A shared session answers with [`BackendEvent::Reordered`]. + /// the order with. A shared session answers with [`BackendEvent::Reordered`]. fn reorder(&mut self, order: &[PaneId]) { let _ = order; } @@ -111,8 +99,7 @@ pub trait TerminalBackend { /// Ask to become the client whose layout sets the pane sizes. /// /// A no-op by default: a backend that owns its PTYs is the only client they - /// have, so there is nobody to take them from. Only a backend serving a - /// shared session has anything to ask. + /// have. Only a backend serving a shared session has anything to ask. fn claim_size(&mut self) {} /// Ask the session to give up on a pane's pending recovery. diff --git a/src/backend/pty.rs b/src/backend/pty.rs index aa9b3503..2677fe80 100644 --- a/src/backend/pty.rs +++ b/src/backend/pty.rs @@ -16,14 +16,10 @@ mod spawn; /// Reap window for PTY reader / wait threads. Bigger than the commit-log /// REAP_TIMEOUT because `read()` on a PTY master can stay blocked if a -/// daemonized grandchild inherited the slave fd — portable_pty does not -/// guarantee CLOEXEC on every platform, so the join must be detachable. +/// daemonized grandchild inherited the slave fd. const PTY_REAP_TIMEOUT: Duration = Duration::from_millis(50); /// Max events drained from any one pane in a single `drain_events` call. -/// A pane that produces output faster than the UI loop consumes it would -/// otherwise monopolize the per-frame drain and starve sibling panes — -/// the round-robin cap bounds the work per pane to a small constant. const PER_PANE_DRAIN_BUDGET: usize = 64; /// How long a pane whose child has exited keeps draining before the exit is @@ -45,7 +41,7 @@ pub(super) enum PtyEvent { /// The child's death is not: on Windows `ClosePseudoConsole` only runs when /// the master is dropped, so `read()` never returns EOF and the child's exit /// is the *only* signal a pane gets. `Draining` holds the exit back until the -/// channel is dry, so the last output still reaches the emulator. +/// channel is dry. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(super) enum ExitPhase { /// No exit signal seen. @@ -96,9 +92,7 @@ impl Drop for PtyPane { pub struct PtyBackend { // BTreeMap (not HashMap) so per-frame event drain visits panes in // PaneId order — IDs are monotonic, so this matches creation order - // and stays deterministic across runs. HashMap iteration was random - // per process, which made inter-pane event ordering unobservable - // and could mask fairness regressions in tests. + // and stays deterministic across runs. pub(super) panes: BTreeMap, /// Slot bookkeeping — identity, launch, idle clock — kept beside `panes` /// rather than inside `PtyPane` because a relaunch replaces the `PtyPane` @@ -106,7 +100,7 @@ pub struct PtyBackend { pub(super) slots: PaneSlots, pub(super) next_id: PaneId, // Each new pane spawns the shell here so its cwd matches the repo - // nightcrow is tracking, even when the binary was launched elsewhere. + // nightcrow is tracking. pub(super) cwd: PathBuf, /// Panes created since the last drain, waiting to be reported. /// diff --git a/src/backend/pty_spawn.rs b/src/backend/pty_spawn.rs index 3fb4a4dd..3d660c3b 100644 --- a/src/backend/pty_spawn.rs +++ b/src/backend/pty_spawn.rs @@ -5,6 +5,8 @@ use crate::backend::slot::{PaneLaunch, resume_command_line}; use anyhow::Result; use portable_pty::{CommandBuilder, NativePtySystem, PtySize, PtySystem}; use std::io::Read; +#[cfg(windows)] +use std::path::{Component, Path, Prefix}; use std::sync::mpsc; use std::thread; use std::time::Instant; @@ -104,6 +106,8 @@ impl PtyBackend { // pass placeholder paths). The clean canonicalize strips the Windows // verbatim prefix (`\\?\`) that cmd.exe rejects as a UNC path. if let Ok(canonical) = crate::platform::paths::canonicalize_clean(&self.cwd) { + #[cfg(windows)] + ensure_windows_shell_supports_cwd(&shell, &canonical)?; cmd.cwd(canonical); } let mut child = pair.slave.spawn_command(cmd)?; @@ -160,3 +164,44 @@ impl PtyBackend { Ok(id) } } + +#[cfg(windows)] +fn ensure_windows_shell_supports_cwd(shell: &str, cwd: &Path) -> Result<()> { + let is_cmd = Path::new(shell) + .file_name() + .and_then(|name| name.to_str()) + .is_some_and(|name| { + name.eq_ignore_ascii_case("cmd") || name.eq_ignore_ascii_case("cmd.exe") + }); + let is_unc = matches!( + cwd.components().next(), + Some(Component::Prefix(prefix)) + if matches!(prefix.kind(), Prefix::UNC(_, _) | Prefix::VerbatimUNC(_, _)) + ); + if is_cmd && is_unc { + anyhow::bail!( + "cmd.exe cannot use UNC working directory {}; configure [shell].program to PowerShell or another UNC-capable shell", + cwd.display() + ); + } + Ok(()) +} + +#[cfg(all(test, windows))] +mod windows_cwd_tests { + use super::ensure_windows_shell_supports_cwd; + use std::path::Path; + + #[test] + fn cmd_rejects_unc_but_not_drive_working_directories() { + assert!( + ensure_windows_shell_supports_cwd("cmd.exe", Path::new(r"\\server\share\repo")) + .is_err() + ); + assert!(ensure_windows_shell_supports_cwd("cmd.exe", Path::new(r"C:\repo")).is_ok()); + assert!( + ensure_windows_shell_supports_cwd("pwsh.exe", Path::new(r"\\server\share\repo")) + .is_ok() + ); + } +} diff --git a/src/backend/pty_tests.rs b/src/backend/pty_tests.rs index 20d19387..85e672a6 100644 --- a/src/backend/pty_tests.rs +++ b/src/backend/pty_tests.rs @@ -2,379 +2,43 @@ use super::*; use crate::backend::identity::FIRST_GENERATION; use crate::config::ShellConfig; -/// Long enough that a `printf` and its exit are certainly drained. +#[path = "pty_tests/identity.rs"] +mod identity; +#[path = "pty_tests/lifecycle.rs"] +mod lifecycle; +#[path = "pty_tests/relaunch.rs"] +mod relaunch; + #[cfg(unix)] const RELAUNCH_MARKER: &str = "nightcrow-relaunched"; - -#[test] -fn pty_backend_create_and_destroy_pane() { - let mut backend = PtyBackend::new(".", ShellConfig::default()); - let id = backend.open_pane(24, 80, None).expect("open_pane failed"); - assert_eq!(id, 1); - backend.destroy_pane(id); - assert!(!backend.panes.contains_key(&id)); -} - -/// Deadline for the real-PTY tests below. They spawn the user's actual -/// `$SHELL` (an interactive zsh sources the full rc chain), and cargo -/// runs tests in parallel, so several shells can be initializing at -/// once — under load a 3 s budget was measurably flaky (~2/25 runs). -/// A generous bound only delays the failure verdict; passing runs -/// still finish as soon as the events arrive. const PTY_TEST_DEADLINE: Duration = Duration::from_secs(15); -/// Answer the pseudoconsole's cursor-position query. -/// -/// Windows ConPTY is created with `PSUEDOCONSOLE_INHERIT_CURSOR`, so the host -/// emits `ESC[6n` and holds the console session until a Device Status Report -/// comes back — the child does not run a single instruction before that. -/// In the app the emulator answers (`TerminalState::poll` writes -/// `events.pty_writes` back); a test driving the backend directly has to do -/// the same or nothing ever starts. On Unix the query never arrives and this -/// is inert. -fn answer_cursor_query(backend: &mut PtyBackend, id: PaneId, data: &[u8]) { - if data.windows(4).any(|w| w == b"\x1b[6n") { - let _ = backend.send_input(id, b"\x1b[1;1R"); - } +struct DrainedPane { + output: Vec, + exits: usize, } -/// Drain until the pane reports its exit, answering the cursor query on the -/// way. Returns how many `Exited` events were seen, and leaves the backend -/// drained up to that point. -fn drain_until_exit(backend: &mut PtyBackend, id: PaneId) -> usize { +/// Drain a real PTY through its exit and answer ConPTY's startup cursor query. +fn drain_until_exit(backend: &mut PtyBackend, id: PaneId) -> DrainedPane { let deadline = Instant::now() + PTY_TEST_DEADLINE; - let mut exits = 0; - while Instant::now() < deadline && exits == 0 { + let mut drained = DrainedPane { + output: Vec::new(), + exits: 0, + }; + while Instant::now() < deadline && drained.exits == 0 { for event in backend.drain_events() { match event { BackendEvent::Output { pane, data } if pane == id => { - answer_cursor_query(backend, id, &data); + if data.windows(4).any(|window| window == b"\x1b[6n") { + let _ = backend.send_input(id, b"\x1b[1;1R"); + } + drained.output.extend(data); } - BackendEvent::Exited { pane } if pane == id => exits += 1, - _ => {} - } - } - thread::sleep(Duration::from_millis(10)); - } - exits -} - -/// A pane whose shell exits must report `Exited`, on every platform. -/// -/// Windows has no second signal to fall back on: the master stays readable -/// while we hold it (the pseudoconsole releases the pipe only on -/// `ClosePseudoConsole`), so the reader thread never reaches EOF and the -/// child's own death is the sole cue. `exit` is spelled the same for -/// `cmd /C` and `sh -lc`, so one test covers both. -#[test] -fn a_pane_whose_shell_exits_reports_it() { - let mut backend = PtyBackend::new(".", ShellConfig::default()); - let id = backend.open_pane(24, 80, Some("exit")).expect("open_pane"); - - assert_eq!( - drain_until_exit(&mut backend, id), - 1, - "pane did not report its shell's exit" - ); -} - -/// The exit is reported once. `drain_events` keeps being called after it — -/// the caller destroys the pane in response, and a backend that re-reported -/// on every subsequent drain would make that cleanup racy. -#[test] -fn a_reported_exit_is_not_reported_again() { - let mut backend = PtyBackend::new(".", ShellConfig::default()); - let id = backend.open_pane(24, 80, Some("exit")).expect("open_pane"); - - assert_eq!( - drain_until_exit(&mut backend, id), - 1, - "exit was not reported exactly once" - ); - - // Keep draining past the first report — this is the window a re-report - // would land in. - for _ in 0..20 { - thread::sleep(Duration::from_millis(10)); - for event in backend.drain_events() { - assert!( - !matches!(event, BackendEvent::Exited { pane } if pane == id), - "exit was reported a second time" - ); - } - } - - backend.destroy_pane(id); -} - -#[test] -#[cfg(unix)] -fn pty_backend_drains_output_before_exit_event() { - let mut backend = PtyBackend::new(".", ShellConfig::default()); - let id = backend.open_pane(24, 80, None).expect("open_pane failed"); - - backend - .send_input(id, b"printf nightcrow-pty-output; exit\n") - .expect("send_input failed"); - - let deadline = Instant::now() + PTY_TEST_DEADLINE; - let mut output = Vec::new(); - let mut saw_exit = false; - while Instant::now() < deadline { - for event in backend.drain_events() { - match event { - BackendEvent::Output { data, .. } => output.extend(data), - BackendEvent::Exited { pane } if pane == id => saw_exit = true, - // A local backend answers `open_pane` directly and its panes - // are nobody else's to size, so none of the rest occur here. + BackendEvent::Exited { pane } if pane == id => drained.exits += 1, _ => {} } } - if saw_exit { - break; - } thread::sleep(Duration::from_millis(10)); } - - assert!(saw_exit, "PTY did not exit before timeout"); - assert!( - String::from_utf8_lossy(&output).contains("nightcrow-pty-output"), - "PTY output was not drained before exit" - ); -} - -#[test] -fn opening_a_pane_gives_it_a_token_at_the_first_generation() { - let mut backend = PtyBackend::new(".", ShellConfig::default()); - let id = backend.open_pane(24, 80, None).expect("open_pane failed"); - - let identity = backend.slot(id).expect("pane has a slot").identity.clone(); - assert_eq!(identity.generation, FIRST_GENERATION); - assert!(!identity.token.as_str().is_empty()); - - backend.destroy_pane(id); -} - -#[test] -fn a_token_resolves_to_the_pane_holding_it() { - let mut backend = PtyBackend::new(".", ShellConfig::default()); - let id = backend.open_pane(24, 80, None).expect("open_pane failed"); - let token = backend.slot(id).expect("slot").identity.token.clone(); - - assert_eq!(backend.pane_for_token(&token), Some(id)); - backend.destroy_pane(id); - assert_eq!(backend.pane_for_token(&token), None); -} - -#[test] -fn relaunching_a_pane_keeps_the_token_and_advances_the_generation() { - let mut backend = PtyBackend::new(".", ShellConfig::default()); - let first = backend - .open_pane(24, 80, Some("printf first; exit")) - .expect("open_pane failed"); - let token = backend.slot(first).expect("slot").identity.token.clone(); - - let second = backend - .relaunch_pane(first, 24, 80, &[], &[]) - .expect("relaunch failed"); - - // A new id is unavoidable — ids are monotonic and clients treat `Exited` as - // final — so the token is what carries the slot's identity across. - assert_ne!(second, first); - let slot = backend.slot(second).expect("relaunched slot"); - assert_eq!(slot.identity.token, token); - assert_eq!(slot.identity.generation, FIRST_GENERATION + 1); - // The old id stops resolving, so a decision made about it cannot land here. - assert!(backend.slot(first).is_none()); - assert_eq!(backend.pane_for_token(&token), Some(second)); -} - -#[test] -#[cfg(unix)] -fn a_relaunch_reproduces_the_original_command() { - let mut backend = PtyBackend::new(".", ShellConfig::default()); - let first = backend - .open_pane(24, 80, Some(&format!("printf {RELAUNCH_MARKER}; exit"))) - .expect("open_pane failed"); - let second = backend - .relaunch_pane(first, 24, 80, &[], &[]) - .expect("relaunch failed"); - - let deadline = Instant::now() + PTY_TEST_DEADLINE; - let mut output = Vec::new(); - let mut saw_exit = false; - while Instant::now() < deadline { - for event in backend.drain_events() { - match event { - BackendEvent::Output { pane, data } if pane == second => output.extend(data), - BackendEvent::Exited { pane } if pane == second => saw_exit = true, - _ => {} - } - } - if saw_exit { - break; - } - thread::sleep(Duration::from_millis(10)); - } - - assert!( - String::from_utf8_lossy(&output).contains(RELAUNCH_MARKER), - "relaunch did not re-run the original command" - ); -} - -#[test] -fn a_relaunch_keeps_the_original_command_rather_than_accumulating_resume_args() { - let mut backend = PtyBackend::new(".", ShellConfig::default()); - let allowed = vec!["--flag".to_string()]; - let args = vec!["--flag".to_string()]; - let first = backend - .open_pane(24, 80, Some("true")) - .expect("open_pane failed"); - - let second = backend - .relaunch_pane(first, 24, 80, &args, &allowed) - .expect("first relaunch"); - // The retained launch is the original invocation, so a second relaunch does - // not stack another copy of the resume arguments onto it. - assert_eq!( - backend - .slot(second) - .expect("slot") - .launch - .command - .as_deref(), - Some("true") - ); - - let third = backend - .relaunch_pane(second, 24, 80, &args, &allowed) - .expect("second relaunch"); - assert_eq!( - backend.slot(third).expect("slot").launch.command.as_deref(), - Some("true") - ); -} - -#[test] -fn relaunching_a_pane_that_is_gone_is_refused() { - let mut backend = PtyBackend::new(".", ShellConfig::default()); - let id = backend.open_pane(24, 80, Some("true")).expect("open_pane"); - backend.destroy_pane(id); - - let err = backend.relaunch_pane(id, 24, 80, &[], &[]).unwrap_err(); - assert!(err.to_string().contains("no slot"), "{err}"); -} - -#[test] -fn a_refused_relaunch_leaves_the_pane_running() { - let mut backend = PtyBackend::new(".", ShellConfig::default()); - let id = backend - .open_pane(24, 80, Some("sleep 30")) - .expect("open_pane"); - let token = backend.slot(id).expect("slot").identity.token.clone(); - - // Not in the allowlist, so the command line is refused before anything is - // torn down. - let args = vec!["--nope".to_string()]; - assert!(backend.relaunch_pane(id, 24, 80, &args, &[]).is_err()); - - assert_eq!(backend.pane_for_token(&token), Some(id)); - assert!(backend.panes.contains_key(&id)); - backend.destroy_pane(id); -} - -#[test] -fn two_panes_get_distinct_tokens() { - let mut backend = PtyBackend::new(".", ShellConfig::default()); - let a = backend.open_pane(24, 80, None).expect("open_pane failed"); - let b = backend.open_pane(24, 80, None).expect("open_pane failed"); - - // Two panes on one repository is a supported layout, so the token — not the - // working directory — is what tells them apart. - let ta = backend.slot(a).expect("a").identity.token.clone(); - let tb = backend.slot(b).expect("b").identity.token.clone(); - assert_ne!(ta, tb); - - backend.destroy_pane(a); - backend.destroy_pane(b); -} - -#[test] -fn destroying_a_pane_retires_its_token() { - let mut backend = PtyBackend::new(".", ShellConfig::default()); - let id = backend.open_pane(24, 80, None).expect("open_pane failed"); - backend.destroy_pane(id); - - // A held token must stop resolving, or it would address whatever id lands - // in this slot next. - assert!(backend.slot(id).is_none()); -} - -#[test] -#[cfg(unix)] -fn a_panes_child_process_sees_its_token_in_the_environment() { - let mut backend = PtyBackend::new(".", ShellConfig::default()); - let id = backend - .open_pane(24, 80, Some("printf %s \"$NIGHTCROW_PANE_TOKEN\"; exit")) - .expect("open_pane failed"); - let token = backend.slot(id).expect("slot").identity.token.clone(); - - let deadline = Instant::now() + PTY_TEST_DEADLINE; - let mut output = Vec::new(); - let mut saw_exit = false; - while Instant::now() < deadline { - for event in backend.drain_events() { - match event { - BackendEvent::Output { data, .. } => output.extend(data), - BackendEvent::Exited { pane } if pane == id => saw_exit = true, - _ => {} - } - } - if saw_exit { - break; - } - thread::sleep(Duration::from_millis(10)); - } - - // This is the correlation path a provider's own hook processes inherit. - assert!( - String::from_utf8_lossy(&output).contains(token.as_str()), - "pane token was not exported to the child environment" - ); -} - -#[test] -#[cfg(unix)] -fn pty_backend_runs_startup_command() { - let mut backend = PtyBackend::new(".", ShellConfig::default()); - // The command runs itself on launch — no input is sent. `exit` keeps - // the test bounded by ending the shell after the command prints. - let id = backend - .open_pane(24, 80, Some("printf nightcrow-startup-ran; exit")) - .expect("open_pane failed"); - - let deadline = Instant::now() + PTY_TEST_DEADLINE; - let mut output = Vec::new(); - let mut saw_exit = false; - while Instant::now() < deadline { - for event in backend.drain_events() { - match event { - BackendEvent::Output { data, .. } => output.extend(data), - BackendEvent::Exited { pane } if pane == id => saw_exit = true, - // A local backend answers `open_pane` directly and its panes - // are nobody else's to size, so none of the rest occur here. - _ => {} - } - } - if saw_exit { - break; - } - thread::sleep(Duration::from_millis(10)); - } - - assert!( - String::from_utf8_lossy(&output).contains("nightcrow-startup-ran"), - "startup command did not run automatically" - ); + drained } diff --git a/src/backend/pty_tests/identity.rs b/src/backend/pty_tests/identity.rs new file mode 100644 index 00000000..727448bb --- /dev/null +++ b/src/backend/pty_tests/identity.rs @@ -0,0 +1,62 @@ +use super::*; + +#[test] +fn opening_a_pane_gives_it_a_token_at_the_first_generation() { + let mut backend = PtyBackend::new(".", ShellConfig::default()); + let id = backend.open_pane(24, 80, None).expect("open_pane failed"); + + let identity = backend.slot(id).expect("pane has a slot").identity.clone(); + assert_eq!(identity.generation, FIRST_GENERATION); + assert!(!identity.token.as_str().is_empty()); + backend.destroy_pane(id); +} + +#[test] +fn a_token_resolves_to_the_pane_holding_it() { + let mut backend = PtyBackend::new(".", ShellConfig::default()); + let id = backend.open_pane(24, 80, None).expect("open_pane failed"); + let token = backend.slot(id).expect("slot").identity.token.clone(); + + assert_eq!(backend.pane_for_token(&token), Some(id)); + backend.destroy_pane(id); + assert_eq!(backend.pane_for_token(&token), None); +} + +#[test] +fn two_panes_get_distinct_tokens() { + let mut backend = PtyBackend::new(".", ShellConfig::default()); + let a = backend.open_pane(24, 80, None).expect("open_pane failed"); + let b = backend.open_pane(24, 80, None).expect("open_pane failed"); + + let ta = backend.slot(a).expect("a").identity.token.clone(); + let tb = backend.slot(b).expect("b").identity.token.clone(); + assert_ne!(ta, tb); + backend.destroy_pane(a); + backend.destroy_pane(b); +} + +#[test] +fn destroying_a_pane_retires_its_token() { + let mut backend = PtyBackend::new(".", ShellConfig::default()); + let id = backend.open_pane(24, 80, None).expect("open_pane failed"); + backend.destroy_pane(id); + + assert!(backend.slot(id).is_none()); +} + +#[test] +#[cfg(unix)] +fn a_panes_child_process_sees_its_token_in_the_environment() { + let mut backend = PtyBackend::new(".", ShellConfig::default()); + let id = backend + .open_pane(24, 80, Some("printf %s \"$NIGHTCROW_PANE_TOKEN\"; exit")) + .expect("open_pane failed"); + let token = backend.slot(id).expect("slot").identity.token.clone(); + + let drained = drain_until_exit(&mut backend, id); + + assert!( + String::from_utf8_lossy(&drained.output).contains(token.as_str()), + "pane token was not exported to the child environment" + ); +} diff --git a/src/backend/pty_tests/lifecycle.rs b/src/backend/pty_tests/lifecycle.rs new file mode 100644 index 00000000..2b484752 --- /dev/null +++ b/src/backend/pty_tests/lifecycle.rs @@ -0,0 +1,74 @@ +use super::*; + +#[test] +fn pty_backend_create_and_destroy_pane() { + let mut backend = PtyBackend::new(".", ShellConfig::default()); + let id = backend.open_pane(24, 80, None).expect("open_pane failed"); + assert_eq!(id, 1); + backend.destroy_pane(id); + assert!(!backend.panes.contains_key(&id)); +} + +#[test] +fn a_pane_whose_shell_exits_reports_it() { + let mut backend = PtyBackend::new(".", ShellConfig::default()); + let id = backend.open_pane(24, 80, Some("exit")).expect("open_pane"); + + assert_eq!( + drain_until_exit(&mut backend, id).exits, + 1, + "pane did not report its shell's exit" + ); +} + +#[test] +fn a_reported_exit_is_not_reported_again() { + let mut backend = PtyBackend::new(".", ShellConfig::default()); + let id = backend.open_pane(24, 80, Some("exit")).expect("open_pane"); + + assert_eq!(drain_until_exit(&mut backend, id).exits, 1); + for _ in 0..20 { + thread::sleep(Duration::from_millis(10)); + for event in backend.drain_events() { + assert!( + !matches!(event, BackendEvent::Exited { pane } if pane == id), + "exit was reported a second time" + ); + } + } + backend.destroy_pane(id); +} + +#[test] +#[cfg(unix)] +fn pty_backend_drains_output_before_exit_event() { + let mut backend = PtyBackend::new(".", ShellConfig::default()); + let id = backend.open_pane(24, 80, None).expect("open_pane failed"); + backend + .send_input(id, b"printf nightcrow-pty-output; exit\n") + .expect("send_input failed"); + + let drained = drain_until_exit(&mut backend, id); + + assert_eq!(drained.exits, 1, "PTY did not exit before timeout"); + assert!( + String::from_utf8_lossy(&drained.output).contains("nightcrow-pty-output"), + "PTY output was not drained before exit" + ); +} + +#[test] +#[cfg(unix)] +fn pty_backend_runs_startup_command() { + let mut backend = PtyBackend::new(".", ShellConfig::default()); + let id = backend + .open_pane(24, 80, Some("printf nightcrow-startup-ran; exit")) + .expect("open_pane failed"); + + let drained = drain_until_exit(&mut backend, id); + + assert!( + String::from_utf8_lossy(&drained.output).contains("nightcrow-startup-ran"), + "startup command did not run automatically" + ); +} diff --git a/src/backend/pty_tests/relaunch.rs b/src/backend/pty_tests/relaunch.rs new file mode 100644 index 00000000..c5f1d309 --- /dev/null +++ b/src/backend/pty_tests/relaunch.rs @@ -0,0 +1,96 @@ +use super::*; + +#[test] +fn relaunching_a_pane_keeps_the_token_and_advances_the_generation() { + let mut backend = PtyBackend::new(".", ShellConfig::default()); + let first = backend + .open_pane(24, 80, Some("printf first; exit")) + .expect("open_pane failed"); + let token = backend.slot(first).expect("slot").identity.token.clone(); + + let second = backend + .relaunch_pane(first, 24, 80, &[], &[]) + .expect("relaunch failed"); + + assert_ne!(second, first); + let slot = backend.slot(second).expect("relaunched slot"); + assert_eq!(slot.identity.token, token); + assert_eq!(slot.identity.generation, FIRST_GENERATION + 1); + assert!(backend.slot(first).is_none()); + assert_eq!(backend.pane_for_token(&token), Some(second)); +} + +#[test] +#[cfg(unix)] +fn a_relaunch_reproduces_the_original_command() { + let mut backend = PtyBackend::new(".", ShellConfig::default()); + let first = backend + .open_pane(24, 80, Some(&format!("printf {RELAUNCH_MARKER}; exit"))) + .expect("open_pane failed"); + let second = backend + .relaunch_pane(first, 24, 80, &[], &[]) + .expect("relaunch failed"); + + let drained = drain_until_exit(&mut backend, second); + + assert!( + String::from_utf8_lossy(&drained.output).contains(RELAUNCH_MARKER), + "relaunch did not re-run the original command" + ); +} + +#[test] +fn a_relaunch_keeps_the_original_command_rather_than_accumulating_resume_args() { + let mut backend = PtyBackend::new(".", ShellConfig::default()); + let allowed = vec!["--flag".to_string()]; + let args = vec!["--flag".to_string()]; + let first = backend + .open_pane(24, 80, Some("true")) + .expect("open_pane failed"); + + let second = backend + .relaunch_pane(first, 24, 80, &args, &allowed) + .expect("first relaunch"); + assert_eq!( + backend + .slot(second) + .expect("slot") + .launch + .command + .as_deref(), + Some("true") + ); + + let third = backend + .relaunch_pane(second, 24, 80, &args, &allowed) + .expect("second relaunch"); + assert_eq!( + backend.slot(third).expect("slot").launch.command.as_deref(), + Some("true") + ); +} + +#[test] +fn relaunching_a_pane_that_is_gone_is_refused() { + let mut backend = PtyBackend::new(".", ShellConfig::default()); + let id = backend.open_pane(24, 80, Some("true")).expect("open_pane"); + backend.destroy_pane(id); + + let err = backend.relaunch_pane(id, 24, 80, &[], &[]).unwrap_err(); + assert!(err.to_string().contains("no slot"), "{err}"); +} + +#[test] +fn a_refused_relaunch_leaves_the_pane_running() { + let mut backend = PtyBackend::new(".", ShellConfig::default()); + let id = backend + .open_pane(24, 80, Some("sleep 30")) + .expect("open_pane"); + let token = backend.slot(id).expect("slot").identity.token.clone(); + + let args = vec!["--nope".to_string()]; + assert!(backend.relaunch_pane(id, 24, 80, &args, &[]).is_err()); + assert_eq!(backend.pane_for_token(&token), Some(id)); + assert!(backend.panes.contains_key(&id)); + backend.destroy_pane(id); +} diff --git a/src/backend/slot.rs b/src/backend/slot.rs index 66837b56..2296beaa 100644 --- a/src/backend/slot.rs +++ b/src/backend/slot.rs @@ -6,10 +6,8 @@ use std::time::{Duration, Instant}; /// What it takes to put a pane's process back after it exits. /// -/// The hub discards the startup command once a pane is spawned, which is fine -/// until something wants to replace the process rather than the pane: a shell -/// cannot be asked what it was told to run. Keeping the text here is what makes -/// a relaunch reproduce the original launch instead of guessing at it. +/// The hub discards the startup command once a pane is spawned. Keeping the text +/// here is what makes a relaunch reproduce the original launch. #[derive(Debug, Clone, PartialEq, Eq)] pub struct PaneLaunch { /// Startup command exactly as configured, or `None` for a bare shell. @@ -28,8 +26,7 @@ impl PaneSlot { /// How long the pane has been quiet. /// /// Measured from the last byte the child produced, not from the last thing - /// written to it: a CLI that is mid-answer keeps emitting, and typing into - /// it then would interleave with what it is drawing. + /// written to it. pub fn idle_for(&self, now: Instant) -> Duration { now.saturating_duration_since(self.last_output) } @@ -38,8 +35,7 @@ impl PaneSlot { /// Per-pane slot bookkeeping, held beside the live PTYs. /// /// Separate from the PTY map because the two have different lifetimes: a -/// relaunch replaces the PTY while the slot — and so the token an outside -/// observer holds — has to survive it. +/// relaunch replaces the PTY while the slot has to survive it. #[derive(Debug, Default)] pub struct PaneSlots(BTreeMap); @@ -98,19 +94,18 @@ const MAX_RESUME_ARG_LEN: usize = 256; /// Deliberately narrower than "anything the shell can be made to swallow": the /// argument is appended to a command line that a login shell parses, so a value /// carrying a space, quote, backtick, `$`, or `;` is refused outright rather -/// than trusted to survive quoting. Quoting is applied as well — this is the -/// belt to that braces. +/// than escaped differently by every supported shell. fn is_safe_arg_char(c: char) -> bool { c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-' | ':' | '/' | '=' | '@' | '+') } /// Build the command line for a relaunch. /// -/// `allowed_flags` is the plugin's declared list from config. Anything that -/// looks like a flag must appear there, which is how the core refuses to weaken -/// a CLI's permission posture without knowing what any particular CLI's -/// permission flags are called — the user names what a plugin may pass, and a -/// flag they did not name cannot be smuggled in. +/// `allowed_flags` is the plugin's declared list from config. The first token +/// (flag or subcommand) and every option-like token must appear there. Values +/// following an approved control token remain provider data such as a session +/// id. This lets the core refuse an unapproved relaunch mode without knowing a +/// particular CLI's grammar. pub fn resume_command_line( base: Option<&str>, resume_args: &[String], @@ -132,7 +127,7 @@ pub fn resume_command_line( } let mut line = String::from(base); - for arg in resume_args { + for (index, arg) in resume_args.iter().enumerate() { if arg.is_empty() { bail!("relaunch argument must not be empty"); } @@ -145,36 +140,22 @@ pub fn resume_command_line( if !arg.chars().all(is_safe_arg_char) { bail!("relaunch argument {arg:?} holds characters that are not allowed"); } - if arg.starts_with('-') && !allowed_flags.iter().any(|f| f == arg) { + let requires_approval = index == 0 || arg.starts_with(['-', '/']); + if requires_approval && !allowed_flags.iter().any(|allowed| allowed == arg) { bail!( - "relaunch flag {arg:?} is not in the plugin's allowed_resume_flags; \ + "relaunch token {arg:?} is not in the plugin's allowed_resume_flags; \ add it there if the plugin is meant to pass it" ); } line.push(' '); - line.push_str(&shell_quote(arg)); + // Every permitted character is literal in both POSIX shells and + // `cmd.exe`. Adding POSIX single quotes here would make those quote + // bytes part of the argument on Windows. + line.push_str(arg); } Ok(line) } -/// Wrap a value so a login shell reads it as one literal word. -/// -/// Single quotes suspend every expansion the shell would otherwise perform, so -/// the only character needing care is the quote itself. -fn shell_quote(arg: &str) -> String { - let mut out = String::with_capacity(arg.len() + 2); - out.push('\''); - for c in arg.chars() { - if c == '\'' { - out.push_str("'\\''"); - } else { - out.push(c); - } - } - out.push('\''); - out -} - #[cfg(test)] #[path = "slot_tests.rs"] mod tests; diff --git a/src/backend/slot_tests.rs b/src/backend/slot_tests.rs index d4801a12..d3a17344 100644 --- a/src/backend/slot_tests.rs +++ b/src/backend/slot_tests.rs @@ -81,11 +81,30 @@ fn no_resume_arguments_leaves_the_original_command_untouched() { } #[test] -fn an_allowed_flag_and_its_value_are_appended_quoted() { +fn an_allowed_flag_and_its_value_are_appended_as_shell_neutral_words() { let args = vec!["--resume".to_string(), "abc-123".to_string()]; let allowed = vec!["--resume".to_string()]; let line = resume_command_line(Some("agent"), &args, &allowed).expect("allowed"); - assert_eq!(line, "agent '--resume' 'abc-123'"); + assert_eq!(line, "agent --resume abc-123"); +} + +#[cfg(windows)] +#[test] +fn resume_arguments_reach_cmd_without_posix_quote_bytes() { + let args = vec!["--resume".to_string(), "abc-123".to_string()]; + let allowed = vec!["--resume".to_string()]; + let line = resume_command_line(Some("echo"), &args, &allowed).expect("allowed"); + + let output = std::process::Command::new("cmd.exe") + .args(["/D", "/S", "/C", &line]) + .output() + .expect("cmd.exe executes the relaunch line"); + + assert!(output.status.success()); + assert_eq!( + String::from_utf8_lossy(&output.stdout).trim(), + "--resume abc-123" + ); } #[test] @@ -101,6 +120,23 @@ fn a_flag_the_plugin_was_not_allowed_to_pass_is_refused() { ); } +#[test] +fn an_unapproved_resume_subcommand_is_refused() { + let args = vec!["resume".to_string(), "abc-123".to_string()]; + let err = resume_command_line(Some("codex"), &args, &[]).unwrap_err(); + assert!(err.to_string().contains("allowed_resume_flags")); + + let line = resume_command_line(Some("codex"), &args, &["resume".to_string()]).unwrap(); + assert_eq!(line, "codex resume abc-123"); +} + +#[test] +fn an_unapproved_windows_style_option_is_refused_in_any_position() { + let args = vec!["resume".to_string(), "/dangerous".to_string()]; + let err = resume_command_line(Some("agent"), &args, &["resume".to_string()]).unwrap_err(); + assert!(err.to_string().contains("/dangerous")); +} + #[test] fn an_argument_holding_shell_metacharacters_is_refused() { let allowed: Vec = Vec::new(); @@ -148,10 +184,3 @@ fn a_pane_with_no_startup_command_cannot_be_relaunched() { let err = resume_command_line(None, &args, &allowed).unwrap_err(); assert!(err.to_string().contains("no startup command"), "{err}"); } - -#[test] -fn quoting_neutralises_an_embedded_single_quote() { - // Reached only if the charset check is ever loosened; the quoting has to be - // correct on its own rather than relying on that check. - assert_eq!(shell_quote("a'b"), "'a'\\''b'"); -} diff --git a/src/cli.rs b/src/cli.rs index 76b6906a..128b84f5 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,16 +1,21 @@ -use anyhow::{Context, Result}; use clap::{Parser, Subcommand}; use std::path::PathBuf; -use std::time::Duration; +mod attach; +mod daemon; +mod init; pub(crate) mod plugin_cmd; +mod stop; + +pub(crate) use attach::run_attach_detached; +pub(crate) use daemon::run_daemon; +pub(crate) use init::run_init; +pub(crate) use stop::run_stop; /// nightcrow — session daemon for agentic coding /// /// Run with no subcommand to start the session: a git diff viewer and -/// multi-terminal panes, served to a terminal (`nightcrow attach`) and to a -/// browser. Runs in the foreground until interrupted; the session outlives any -/// client that attaches to it. +/// multi-terminal panes, served to a terminal and to a browser. #[derive(Parser)] #[command(version, about, long_about = None)] pub(crate) struct Cli { @@ -23,19 +28,19 @@ pub(crate) struct Cli { #[arg(long)] pub(crate) port: Option, - /// Override the configured bind address. `0.0.0.0` exposes the server — - /// and the shells it serves — to the whole network over plain HTTP. + /// Override the configured bind address. `0.0.0.0` exposes the server + /// to the whole network over plain HTTP. #[arg(long)] pub(crate) bind: Option, /// Run the session in the background and return to the shell. /// - /// It gets its own session, so closing this terminal does not stop it, and - /// its output goes to ~/.nightcrow/daemon.out. A service manager should - /// start nightcrow *without* this — backgrounding is what it does itself. + /// It gets its own session, so closing this terminal does not stop it. + /// A service manager should start nightcrow *without* this — backgrounding + /// is what it does itself. /// /// With `attach`, this starts the session if none is running and then - /// attaches to it, so `nightcrow -d attach` is the one-command way in. + /// attaches to it. #[arg(short, long)] pub(crate) detach: bool, @@ -54,9 +59,8 @@ pub(crate) enum Commands { /// Attach the TUI to a running nightcrow daemon. /// /// The session — which repositories are open, and in what order — belongs - /// to the daemon, so this starts on whatever it is serving. Repositories - /// are opened from inside, with the leader chord's open dialog or the - /// browser's folder picker. Leaving does not end the session. + /// to the daemon, so this starts on whatever it is serving. Leaving does + /// not end the session. /// /// Requires a running daemon; pass `-d` to start one first if none is. Attach, @@ -71,370 +75,10 @@ pub(crate) enum Commands { /// Ask a running daemon to shut down. /// /// Sends a graceful shutdown request via the daemon socket. The daemon - /// runs the same shutdown sequence as SIGINT/SIGTERM — reaping every - /// child shell — and then exits. No force kill is attempted. + /// runs the same shutdown sequence as SIGINT/SIGTERM. Stop { /// Path to the daemon socket. Defaults to the standard location. #[arg(long)] socket: Option, }, } - -/// Run the session daemon in the foreground until it is stopped. -/// -/// The starting catalog is whatever was open last time, which may be nothing — -/// an empty catalog is a normal state, and the way in from there is a client: -/// the browser's folder picker or an attached TUI's open dialog. There is no -/// flag for it. Repositories are opened from inside the session, which is the -/// only place that can open one *and* have every other client see it. -pub(crate) fn run_daemon( - exec: Vec, - port: Option, - bind: Option, - detach: bool, -) -> Result<()> { - // Before the shutdown handlers and before anything is bound: the - // foreground copy hands the whole job over and exits, so it must not have - // taken the socket or the instance lock first. - if detach && !crate::daemon::detach::is_detached_child() { - let log = daemon_output_path()?; - let pid = crate::daemon::detach::respawn_in_background(&log)?; - eprintln!("nightcrow: session running in the background (pid {pid})"); - eprintln!("nightcrow: its output goes to {}", log.display()); - return Ok(()); - } - - // Before anything that can be interrupted. Opening repositories and running - // the startup shells takes long enough for a stop signal to land in the - // middle, and until the handlers exist such a signal kills the process - // outright — leaving the shells it had already spawned behind. - let shutdown = crate::platform::signals::ShutdownWatch::register()?; - - // A channel so both the signal path and the socket path converge on one - // shutdown trigger. The sender goes to the session so `nightcrow stop` can - // reach it; the receiver is what the main thread waits on. - let (shutdown_tx, shutdown_rx) = - std::sync::mpsc::sync_channel::(1); - - // Forward the signal watch onto the channel, so the main thread waits on - // one thing regardless of where the stop came from. - let signal_tx = shutdown_tx.clone(); - std::thread::Builder::new() - .name("nightcrow-signal-forward".into()) - .spawn(move || { - if let Ok(signal) = shutdown.wait() { - let _ = signal_tx.send(signal); - } - }) - .context("spawning the signal-forwarding thread")?; - - let mut cfg = crate::config::load_config()?; - if let Some(port) = port { - cfg.web_viewer.port = port; - } - if let Some(bind) = bind { - cfg.web_viewer.bind = bind; - } - // Logging comes up before anything is served, so a failure while opening - // repositories has somewhere to go. Anchored at the nightcrow directory - // rather than a repository: the daemon has no one repository, and the - // relative default (`.nightcrow/logs`) would otherwise land wherever the - // process happened to be started. - let _log_guard = crate::platform::logging::init_logging( - &cfg.log, - &crate::platform::paths::state_dir_anchor(), - ); - tracing::info!( - level = cfg.log.level.as_str(), - rotation = ?cfg.log.rotation, - prompt_log = cfg.log.prompt_log, - "logging initialized" - ); - - let path = crate::config::config_file_path()?; - if let Some(password) = crate::config::ensure_web_viewer_password(&mut cfg, &path)? { - eprintln!( - "nightcrow: generated a web viewer password and saved it to {}:", - path.display() - ); - eprintln!(" {password}"); - } - - // Repositories that have been moved or deleted since are dropped rather - // than served as broken tabs. - let paths: Vec = crate::workspace::persistence::load_workspace() - .map(|ws| ws.repos) - .unwrap_or_default() - .into_iter() - .filter(|repo| std::path::Path::new(repo).is_dir()) - .collect(); - // Resolved before anything is served so a too-many-panes error is a plain - // stderr line at startup rather than a failure the first client sees. - // Names and all: the session is what opens these panes now, so it is what - // has to know what they are called. Dropping the configured name here left - // every startup pane titled "shell 1" in every client. - let startup = crate::config::resolve_startup_commands(&cfg, &exec)?; - let server = crate::web::viewer::server::ViewerServer::start_from_config( - &cfg.web_viewer, - &cfg.agent_indicator, - &cfg.theme, - &cfg.shell, - &paths, - true, - startup, - // Remembered rather than folded away: a config reload re-reads the file's - // `[[startup_command]]` table, and these panes are not in the file. - exec, - cfg.plugins.clone(), - )?; - if paths.is_empty() { - // An empty catalog is a legitimate state — the same one the TUI starts - // in when launched with no repository. The viewer shows its - // no-repository state and can still be reached; the page's folder - // picker is the way in from there. - eprintln!( - "nightcrow: serving an empty catalog at http://{}/ — open a repository from a client", - server.addr() - ); - } else { - eprintln!( - "nightcrow: web viewer serving {} repositor{} at http://{}/", - paths.len(), - if paths.len() == 1 { "y" } else { "ies" }, - server.addr() - ); - } - // The attach socket comes up after the browser listener so a port conflict - // is reported before a socket file exists to clean up. A failure here does - // abort: unlike the viewer beside the TUI, this *is* the session, and - // running on with no way to attach would look like a silent success. - let socket_path = crate::daemon::socket::default_socket_path()?; - let socket = crate::daemon::socket::DaemonSocket::bind(&socket_path)?; - eprintln!( - "nightcrow: attach with `nightcrow attach` ({})", - socket.path().display() - ); - // The accept loop gets a clone; `socket` stays here so that returning from - // this function unlinks it and releases the instance lock. Parked in the - // accept thread it would be freed by process exit, which runs no destructor - // — and the socket file would outlive every clean shutdown. - let listener = socket - .listener() - .try_clone() - .context("cloning the daemon listener")?; - let attach_state = server.state(); - // Before the accept thread, so a session that cannot watch itself is - // reported here — where it can still fail — rather than by an accept loop - // nobody is reading the result of. - let session = crate::daemon::serve::start(attach_state, shutdown_tx)?; - std::thread::Builder::new() - .name("nightcrow-daemon-accept".into()) - .spawn(move || crate::daemon::serve::serve(listener, session)) - .context("spawning the daemon accept thread")?; - - if !server.addr().ip().is_loopback() { - // Worth saying out loud: this is not the default, it carries shells, - // and there is no TLS to fall back on. - eprintln!( - "nightcrow: WARNING bound to {} — repository contents and interactive", - server.addr().ip() - ); - eprintln!("nightcrow: shells are reachable from the network over plain HTTP."); - } - eprintln!("nightcrow: press Ctrl-C to stop"); - - // Wait for a stop signal — from the OS (forwarded by the signal thread) or - // from `nightcrow stop` (sent by the request handler). Both converge on the - // same channel, so the shutdown sequence is identical regardless of source. - let signal = shutdown_rx - .recv() - .context("the shutdown channel closed without a signal")?; - eprintln!("nightcrow: {} received, stopping", signal.as_str()); - tracing::info!(signal = signal.as_str(), "shutting down"); - server.shutdown(); - // Explicit so the order is visible: the socket file goes away and the - // instance lock is released only after the session has stopped, so nothing - // can attach to a daemon that is already tearing down its terminals. - drop(socket); - Ok(()) -} - -/// How long `-d attach` waits for the session it just started to accept. -/// Generous because startup opens every remembered repository and runs the -/// configured startup panes before the socket is bound. -const DAEMON_READY_TIMEOUT: Duration = Duration::from_secs(20); - -/// `nightcrow -d attach`: start a session if none is running, then attach. -/// -/// An already-running daemon is attached to as-is — spawning a second would -/// only lose the instance lock and leave a confusing line in daemon.out. -pub(crate) fn run_attach_detached() -> Result<()> { - let socket = crate::daemon::socket::default_socket_path()?; - if daemon_accepts(&socket) { - return crate::application::attach::run_attach(); - } - - let log = daemon_output_path()?; - let pid = crate::daemon::detach::respawn_in_background(&log)?; - eprintln!("nightcrow: started a session in the background (pid {pid})"); - eprintln!("nightcrow: its output goes to {}", log.display()); - wait_for_daemon(&socket, &log)?; - crate::application::attach::run_attach() -} - -/// Whether something is listening on the socket *now*. Connecting rather than -/// checking for the file: a crashed daemon leaves the path behind on Unix, and -/// a stale one would send `attach` into a connect error. -fn daemon_accepts(socket: &std::path::Path) -> bool { - crate::daemon::transport::UnixStream::connect(socket).is_ok() -} - -/// Poll until the session accepts, or give up and say where to look. The socket -/// is bound late in startup, so an early failure shows up here as a timeout -/// rather than as an error we can quote. -fn wait_for_daemon(socket: &std::path::Path, log: &std::path::Path) -> Result<()> { - let deadline = std::time::Instant::now() + DAEMON_READY_TIMEOUT; - while std::time::Instant::now() < deadline { - if daemon_accepts(socket) { - return Ok(()); - } - std::thread::sleep(Duration::from_millis(50)); - } - anyhow::bail!( - "the session did not start within {}s — see {}", - DAEMON_READY_TIMEOUT.as_secs(), - log.display() - ) -} - -/// Where a backgrounded session writes what it would have printed. -/// -/// Beside the socket and the workspace file rather than in the log directory: -/// this is the startup banner and any bind error, which a person goes looking -/// for by hand, not the rotated tracing log. -fn daemon_output_path() -> Result { - let home = dirs::home_dir().context("cannot determine the home directory")?; - Ok(home.join(".nightcrow").join("daemon.out")) -} - -pub(crate) fn run_init(force: bool) -> Result<()> { - match crate::config::init_config(force)? { - crate::config::InitOutcome::Created(path) => { - println!("Created starter config at {}", path.display()); - println!("Edit it to reserve startup commands, panel layout, theme, and more."); - } - crate::config::InitOutcome::AlreadyExists(path) => { - println!( - "Config already exists at {} — left untouched (pass --force to overwrite).", - path.display() - ); - } - } - Ok(()) -} - -/// Send a shutdown request to a running daemon via its socket. -/// -/// Connects to the daemon socket, sends `ClientMessage::Shutdown`, and waits -/// for the connection to close — which is the daemon's acknowledgment. Does -/// NOT attempt a force kill; the daemon runs the same graceful shutdown -/// sequence as SIGINT/SIGTERM. -pub(crate) fn run_stop(socket: Option) -> Result<()> { - use crate::daemon::frame::{read_frame, write_frame}; - use crate::daemon::protocol::ClientMessage; - use crate::daemon::transport::UnixStream; - use std::io::Write; - - let path = match socket { - Some(p) => p, - None => crate::daemon::socket::default_socket_path()?, - }; - - if !path.exists() { - anyhow::bail!( - "no daemon socket at {} — is a nightcrow daemon running?", - path.display() - ); - } - - let mut stream = UnixStream::connect(&path).with_context(|| { - format!( - "could not connect to the daemon socket at {} — the daemon may have stopped", - path.display() - ) - })?; - - let json = - serde_json::to_vec(&ClientMessage::Shutdown).context("encoding the shutdown request")?; - write_frame(&mut stream, &crate::daemon::frame::Frame::control(json)) - .context("sending the shutdown request")?; - stream.flush().context("flushing the shutdown request")?; - - // The daemon closes the connection as its acknowledgment. Wait for that - // rather than for a reply: the Shutdown message carries no answer by design. - // A read that returns Ok(None) means the daemon closed cleanly. - match read_frame(&mut stream) { - Ok(None) => { - println!("nightcrow: daemon is shutting down"); - Ok(()) - } - Ok(Some(_)) => { - // The daemon sent something before closing — unexpected, but the - // shutdown was still delivered. - println!("nightcrow: daemon is shutting down"); - Ok(()) - } - Err(err) => { - // A read error after sending Shutdown is expected: the daemon may - // close the connection before we finish reading. Treat it as success - // as long as the send succeeded. - let io_err = err.downcast_ref::(); - if io_err.is_some_and(|e| { - matches!( - e.kind(), - std::io::ErrorKind::ConnectionReset - | std::io::ErrorKind::ConnectionAborted - | std::io::ErrorKind::UnexpectedEof - ) - }) { - println!("nightcrow: daemon is shutting down"); - Ok(()) - } else { - Err(err).context("waiting for the daemon to acknowledge the shutdown") - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - /// Connecting, not `Path::exists`: a daemon that died leaves the socket - /// file behind on Unix, and `-d attach` would then skip the spawn and send - /// `attach` into a connect error. - #[test] - fn an_unbound_socket_path_does_not_read_as_a_running_daemon() { - let dir = tempfile::TempDir::new().expect("a temp dir"); - let path = dir.path().join("nightcrow.sock"); - - assert!(!daemon_accepts(&path), "nothing is listening there"); - - // A plain file standing where the socket would be is still not a - // daemon — this is the shape a stale Unix socket leaves behind. - std::fs::write(&path, b"").expect("write"); - assert!(!daemon_accepts(&path)); - } - - #[test] - fn a_bound_socket_reads_as_a_running_daemon() { - let dir = tempfile::TempDir::new().expect("a temp dir"); - let path = dir.path().join("live.sock"); - let _listener = - crate::daemon::transport::UnixListener::bind(&path).expect("bind the probe socket"); - - assert!(daemon_accepts(&path)); - // And the wait returns at once rather than burning its timeout. - wait_for_daemon(&path, dir.path()).expect("already accepting"); - } -} diff --git a/src/cli/attach.rs b/src/cli/attach.rs new file mode 100644 index 00000000..ce9c3da8 --- /dev/null +++ b/src/cli/attach.rs @@ -0,0 +1,65 @@ +use anyhow::Result; +use std::path::Path; +use std::time::{Duration, Instant}; + +const DAEMON_READY_TIMEOUT: Duration = Duration::from_secs(20); + +/// Start a session when needed, then attach to it. +pub(crate) fn run_attach_detached() -> Result<()> { + let socket = crate::daemon::socket::default_socket_path()?; + if daemon_accepts(&socket) { + return crate::application::attach::run_attach(); + } + + let log = super::daemon::daemon_output_path()?; + let pid = crate::daemon::detach::respawn_in_background(&log)?; + eprintln!("nightcrow: started a session in the background (pid {pid})"); + eprintln!("nightcrow: its output goes to {}", log.display()); + wait_for_daemon(&socket, &log)?; + crate::application::attach::run_attach() +} + +fn daemon_accepts(socket: &Path) -> bool { + crate::daemon::transport::UnixStream::connect(socket).is_ok() +} + +fn wait_for_daemon(socket: &Path, log: &Path) -> Result<()> { + let deadline = Instant::now() + DAEMON_READY_TIMEOUT; + while Instant::now() < deadline { + if daemon_accepts(socket) { + return Ok(()); + } + std::thread::sleep(Duration::from_millis(50)); + } + anyhow::bail!( + "the session did not start within {}s — see {}", + DAEMON_READY_TIMEOUT.as_secs(), + log.display() + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn an_unbound_socket_path_does_not_read_as_a_running_daemon() { + let dir = tempfile::TempDir::new().expect("a temp dir"); + let path = dir.path().join("nightcrow.sock"); + + assert!(!daemon_accepts(&path)); + std::fs::write(&path, b"").expect("write stale socket stand-in"); + assert!(!daemon_accepts(&path)); + } + + #[test] + fn a_bound_socket_reads_as_a_running_daemon() { + let dir = tempfile::TempDir::new().expect("a temp dir"); + let path = dir.path().join("live.sock"); + let _listener = + crate::daemon::transport::UnixListener::bind(&path).expect("bind probe socket"); + + assert!(daemon_accepts(&path)); + wait_for_daemon(&path, dir.path()).expect("already accepting"); + } +} diff --git a/src/cli/daemon.rs b/src/cli/daemon.rs new file mode 100644 index 00000000..083c35e9 --- /dev/null +++ b/src/cli/daemon.rs @@ -0,0 +1,137 @@ +use anyhow::{Context, Result}; +use std::path::{Path, PathBuf}; + +/// Run the session daemon, respawning it in the background when requested. +pub(crate) fn run_daemon( + exec: Vec, + port: Option, + bind: Option, + detach: bool, +) -> Result<()> { + if detach && !crate::daemon::detach::is_detached_child() { + let log = daemon_output_path()?; + let pid = crate::daemon::detach::respawn_in_background(&log)?; + eprintln!("nightcrow: session running in the background (pid {pid})"); + eprintln!("nightcrow: its output goes to {}", log.display()); + return Ok(()); + } + + // Register before opening repositories or panes so an early signal cannot + // leave already-spawned children behind. + let shutdown = crate::platform::signals::ShutdownWatch::register()?; + let (shutdown_tx, shutdown_rx) = + std::sync::mpsc::sync_channel::(1); + let signal_tx = shutdown_tx.clone(); + std::thread::Builder::new() + .name("nightcrow-signal-forward".into()) + .spawn(move || { + if let Ok(signal) = shutdown.wait() { + let _ = signal_tx.send(signal); + } + }) + .context("spawning the signal-forwarding thread")?; + + let mut cfg = crate::config::load_config()?; + if let Some(port) = port { + cfg.web_viewer.port = port; + } + if let Some(bind) = bind { + cfg.web_viewer.bind = bind; + } + let _log_guard = crate::platform::logging::init_logging( + &cfg.log, + &crate::platform::paths::state_dir_anchor(), + ); + tracing::info!( + level = cfg.log.level.as_str(), + rotation = ?cfg.log.rotation, + prompt_log = cfg.log.prompt_log, + "logging initialized" + ); + + let config_path = crate::config::config_file_path()?; + if let Some(password) = crate::config::ensure_web_viewer_password(&mut cfg, &config_path)? { + eprintln!( + "nightcrow: generated a web viewer password and saved it to {}:", + config_path.display() + ); + eprintln!(" {password}"); + } + + let paths: Vec = crate::workspace::persistence::load_workspace() + .map(|workspace| workspace.repos) + .unwrap_or_default() + .into_iter() + .filter(|repo| Path::new(repo).is_dir()) + .collect(); + let startup = crate::config::resolve_startup_commands(&cfg, &exec)?; + let server = crate::web::viewer::server::ViewerServer::start_from_config( + crate::web::viewer::server::ViewerLaunch { + viewer: &cfg.web_viewer, + agent_indicator: &cfg.agent_indicator, + theme: &cfg.theme, + shell: &cfg.shell, + paths: &paths, + persist: true, + startup_commands: startup, + // Retained separately because reload only re-reads configured panes. + cli_startup: exec, + plugins: cfg.plugins.clone(), + }, + )?; + if paths.is_empty() { + eprintln!( + "nightcrow: serving an empty catalog at http://{}/ — open a repository from a client", + server.addr() + ); + } else { + eprintln!( + "nightcrow: web viewer serving {} repositor{} at http://{}/", + paths.len(), + if paths.len() == 1 { "y" } else { "ies" }, + server.addr() + ); + } + + // Bind the attach socket after the browser listener so a port conflict + // cannot leave a daemon socket behind. + let socket_path = crate::daemon::socket::default_socket_path()?; + let socket = crate::daemon::socket::DaemonSocket::bind(&socket_path)?; + eprintln!( + "nightcrow: attach with `nightcrow attach` ({})", + socket.path().display() + ); + let listener = socket + .listener() + .try_clone() + .context("cloning the daemon listener")?; + let session = crate::daemon::serve::start(server.session_state(), shutdown_tx)?; + std::thread::Builder::new() + .name("nightcrow-daemon-accept".into()) + .spawn(move || crate::daemon::serve::serve(listener, session)) + .context("spawning the daemon accept thread")?; + + if !server.addr().ip().is_loopback() { + eprintln!( + "nightcrow: WARNING bound to {} — repository contents and interactive", + server.addr().ip() + ); + eprintln!("nightcrow: shells are reachable from the network over plain HTTP."); + } + eprintln!("nightcrow: press Ctrl-C to stop"); + + let signal = shutdown_rx + .recv() + .context("the shutdown channel closed without a signal")?; + eprintln!("nightcrow: {} received, stopping", signal.as_str()); + tracing::info!(signal = signal.as_str(), "shutting down"); + server.shutdown(); + // Keep the socket and instance lock until the session has stopped. + drop(socket); + Ok(()) +} + +pub(super) fn daemon_output_path() -> Result { + let home = dirs::home_dir().context("cannot determine the home directory")?; + Ok(home.join(".nightcrow").join("daemon.out")) +} diff --git a/src/cli/init.rs b/src/cli/init.rs new file mode 100644 index 00000000..330222eb --- /dev/null +++ b/src/cli/init.rs @@ -0,0 +1,17 @@ +use anyhow::Result; + +pub(crate) fn run_init(force: bool) -> Result<()> { + match crate::config::init_config(force)? { + crate::config::InitOutcome::Created(path) => { + println!("Created starter config at {}", path.display()); + println!("Edit it to reserve startup commands, panel layout, theme, and more."); + } + crate::config::InitOutcome::AlreadyExists(path) => { + println!( + "Config already exists at {} — left untouched (pass --force to overwrite).", + path.display() + ); + } + } + Ok(()) +} diff --git a/src/cli/stop.rs b/src/cli/stop.rs new file mode 100644 index 00000000..ab524a01 --- /dev/null +++ b/src/cli/stop.rs @@ -0,0 +1,51 @@ +use anyhow::{Context, Result}; +use std::io::Write; +use std::path::PathBuf; + +use crate::daemon::frame::{Frame, read_frame, write_frame}; +use crate::daemon::protocol::ClientMessage; +use crate::daemon::transport::UnixStream; + +/// Send a graceful shutdown request to a running daemon. +pub(crate) fn run_stop(socket: Option) -> Result<()> { + let path = match socket { + Some(path) => path, + None => crate::daemon::socket::default_socket_path()?, + }; + if !path.exists() { + anyhow::bail!( + "no daemon socket at {} — is a nightcrow daemon running?", + path.display() + ); + } + + let mut stream = UnixStream::connect(&path).with_context(|| { + format!( + "could not connect to the daemon socket at {} — the daemon may have stopped", + path.display() + ) + })?; + let json = + serde_json::to_vec(&ClientMessage::Shutdown).context("encoding the shutdown request")?; + write_frame(&mut stream, &Frame::control(json)).context("sending the shutdown request")?; + stream.flush().context("flushing the shutdown request")?; + + // Closing the connection is the daemon's acknowledgment. A reset is also + // expected when shutdown wins the race with this read. + if let Err(err) = read_frame(&mut stream) { + let expected_disconnect = err.downcast_ref::().is_some_and(|error| { + matches!( + error.kind(), + std::io::ErrorKind::ConnectionReset + | std::io::ErrorKind::ConnectionAborted + | std::io::ErrorKind::UnexpectedEof + ) + }); + if !expected_disconnect { + return Err(err).context("waiting for the daemon to acknowledge the shutdown"); + } + } + + println!("nightcrow: daemon is shutting down"); + Ok(()) +} diff --git a/src/config.rs b/src/config.rs index c3896060..c2742677 100644 --- a/src/config.rs +++ b/src/config.rs @@ -22,17 +22,11 @@ pub use web::{WebViewerConfig, ensure_web_viewer_password}; /// Upper bound on the number of `[[startup_command]]` + `--exec` panes opened /// at launch. The value matches the `F3`..`F10` / ` 3`..`9`,`0` jump-key -/// range, so every startup pane is reachable by a direct key: `F1`/`F2` reach -/// the upper panels (file list, diff) and `F3`..`F10` reach all eight terminal -/// panes. Runtime panes (opened one at a time by ` t`) are not bounded -/// by this — they may exceed eight, in which case the extras past the eighth -/// are reachable by focus cycling (`Shift+←/→`) rather than a jump key. +/// range, so every startup pane is reachable by a direct key. pub const MAX_STARTUP_COMMANDS: usize = 8; -/// Upper bound on `[[plugin]]` entries. Each one is a child process the host -/// keeps alive for the whole session, and a pane opts into exactly one of them, -/// so the bound tracks `MAX_STARTUP_COMMANDS` rather than being independently -/// generous. +/// Upper bound on `[[plugin]]` entries. Tracks `MAX_STARTUP_COMMANDS` rather +/// than being independently generous. pub const MAX_PLUGINS: usize = 8; #[derive(Debug, Clone, Default, Serialize, Deserialize)] @@ -47,17 +41,14 @@ pub struct Config { pub mouse: MouseConfig, pub web_viewer: WebViewerConfig, /// The shell every terminal pane is spawned with. When absent, the platform - /// default is used: `$SHELL` or `/bin/sh` on Unix, `%ComSpec%` or `cmd.exe` - /// on Windows. + /// default is used. pub shell: ShellConfig, /// Commands launched in their own terminal pane at startup, in order. - /// Maps from TOML `[[startup_command]]` array-of-tables. Empty by - /// default, which preserves the single empty-shell startup behaviour. + /// Maps from TOML `[[startup_command]]` array-of-tables. #[serde(rename = "startup_command")] pub startup_commands: Vec, - /// External plugin processes, from TOML `[[plugin]]`. Empty by default, and - /// every entry is additionally off until it sets `enabled = true`, so no - /// plugin runs unless the user asked for it twice over. + /// External plugin processes, from TOML `[[plugin]]`. Every entry is + /// additionally off until it sets `enabled = true`. #[serde(rename = "plugin")] pub plugins: Vec, } @@ -67,19 +58,13 @@ fn default_config_path() -> Option { } /// The path nightcrow reads/writes its config at (`~/.nightcrow/config.toml`), -/// resolved regardless of whether the file exists yet. Errors only when the -/// home directory cannot be determined. Used by the web-password bootstrap, -/// which may need to create the file to persist a generated credential. +/// resolved regardless of whether the file exists yet. pub fn config_file_path() -> Result { default_config_path() .ok_or_else(|| anyhow::anyhow!("cannot determine home directory for config path")) } -/// The shipped, commented configuration template, embedded at compile time so -/// a standalone binary (with no source checkout alongside it) can still hand -/// the user a starting file. `nightcrow init` writes this verbatim, and -/// `example_config_parses_and_validates` guards that it always parses and -/// validates against the current `Config`. +/// The shipped, commented configuration template, embedded at compile time. pub const EXAMPLE_CONFIG: &str = include_str!("../config.example.toml"); /// Result of `init_config`, so the caller can report precisely which path was @@ -91,7 +76,7 @@ pub enum InitOutcome { /// Write the embedded template to `~/.nightcrow/config.toml`, creating the /// parent directory if needed. An existing file is preserved unless `force` -/// is set, so re-running `init` never clobbers a user's edits by accident. +/// is set. pub fn init_config(force: bool) -> Result { let path = default_config_path() .ok_or_else(|| anyhow::anyhow!("cannot determine home directory for config path"))?; @@ -99,7 +84,7 @@ pub fn init_config(force: bool) -> Result { } /// Path-explicit core of `init_config` (no `$HOME` lookup) so the write/skip -/// behaviour is unit-testable against a temp directory. +/// behaviour is unit-testable. pub(super) fn write_config_template(path: &std::path::Path, force: bool) -> Result { if path.exists() && !force { return Ok(InitOutcome::AlreadyExists(path.to_path_buf())); @@ -117,8 +102,7 @@ pub(super) fn write_config_template(path: &std::path::Path, force: bool) -> Resu /// /// A missing file is a normal starting state, so this does not fail on one. A /// *reload* takes the stricter path ([`read_config_file`]): at that point the -/// file having gone missing is a mistake worth reporting, not an instruction to -/// run as if nothing were configured. +/// file having gone missing is a mistake worth reporting. pub fn load_config() -> Result { let path = match default_config_path() { Some(p) if p.exists() => p, @@ -128,9 +112,6 @@ pub fn load_config() -> Result { } /// Parse and validate the config at `path`, which must exist. -/// -/// Path-explicit so the reload path is testable against a temp file rather than -/// the caller's real `~/.nightcrow/config.toml`. pub fn read_config_file(path: &std::path::Path) -> Result { let text = std::fs::read_to_string(path) .with_context(|| format!("reading config file {}", path.display()))?; diff --git a/src/config/layout.rs b/src/config/layout.rs index 5e7cc97b..8bc495a7 100644 --- a/src/config/layout.rs +++ b/src/config/layout.rs @@ -2,13 +2,11 @@ use anyhow::Result; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use serde::{Deserialize, Serialize}; -/// Default leader chord. `Ctrl+F` is a one-handed left-hand chord that avoids -/// tmux's `Ctrl+B` (so nightcrow can run inside tmux) AND the Ctrl chords an -/// inner Claude Code pane reserves (`Ctrl+G` = external editor, plus -/// `Ctrl+O/R/S/T/L/…`). It also dodges terminal flow control (`Ctrl+Q`/`Ctrl+S` -/// = XON/XOFF) and the shell signals `Ctrl+C/D/Z`. Its only collision is -/// `Ctrl+F` as forward-char (readline) / page-forward (vim), which users -/// almost always reach via the arrow keys / PageDown instead; when needed it +/// Default leader chord. `Ctrl+F` avoids tmux's `Ctrl+B` (so nightcrow can +/// run inside tmux) and the Ctrl chords an inner Claude Code pane reserves. +/// It also dodges terminal flow control (`Ctrl+Q`/`Ctrl+S`) and shell signals +/// (`Ctrl+C/D/Z`). Its only collision is `Ctrl+F` as forward-char / page-forward, +/// which users almost always reach via arrow keys / PageDown; when needed it /// stays reachable via ``. pub(super) const DEFAULT_LEADER: &str = "ctrl+f"; @@ -42,15 +40,12 @@ pub enum Accent { } // Compile-time guard: a future refactor must not shrink `Accent::ALL` to -// empty, or `from_index` would rely on a runtime fallback we'd rather not -// exercise. `const` items don't accept `_` inside an `impl` block, so this -// lives at module scope. +// empty, or `from_index` would rely on a runtime fallback. const _: () = assert!(!Accent::ALL.is_empty(), "Accent::ALL must be non-empty"); impl Accent { // Variant declaration order MUST match this slice so accent indices already - // written down — the session's in `viewer.json`, and the per-repo ones older - // session files still carry — keep mapping to the same color. + // written down keep mapping to the same color. pub const ALL: &'static [Accent] = &[ Accent::Yellow, Accent::Cyan, @@ -72,13 +67,11 @@ impl Accent { pub fn index(self) -> usize { // Fall back to 0 when a variant is missing from `ALL` — should be - // unreachable, but a runtime panic on a UI helper is worse than a - // silently miscoloured tile. The roundtrip test pins the invariant. + // unreachable, but a runtime panic on a UI helper is worse. Self::ALL.iter().position(|&a| a == self).unwrap_or(0) } pub fn from_index(idx: usize) -> Accent { - // The compile-time guard above keeps `len > 0`, so `% len` is sound. Self::ALL .get(idx % Self::ALL.len()) .copied() @@ -103,9 +96,7 @@ impl ThemeConfig { #[serde(default)] pub struct InputConfig { /// The leader (prefix) chord. Every app command is reached by pressing - /// this key, then a follow-up (tmux-style). Accepts a single - /// `ctrl+` chord; the parser rejects anything that doubles as a - /// no-prefix reserved key (F1..F10, Shift+arrows, Shift+PgUp/PgDn). + /// this key, then a follow-up (tmux-style). pub leader: String, } diff --git a/src/config/tests/web.rs b/src/config/tests/web.rs index 0d070f33..e5a26006 100644 --- a/src/config/tests/web.rs +++ b/src/config/tests/web.rs @@ -1,6 +1,4 @@ -use crate::config::web::{ - GENERATED_PASSWORD_LEN, PASSWORD_ALPHABET, WEB_VIEWER_TABLE, insert_password, -}; +use crate::config::web::{GENERATED_PASSWORD_LEN, PASSWORD_ALPHABET, upsert_password}; use crate::config::{ Config, WebViewerConfig, ensure_web_viewer_password, generate_password, validate_config, }; @@ -103,22 +101,21 @@ fn generate_password_has_expected_length_and_alphabet() { } #[test] -fn insert_password_adds_line_under_existing_header() { +fn upsert_password_adds_missing_key_to_existing_table() { let source = "[web_viewer]\nbind = \"127.0.0.1\"\nport = 8091\n"; - let out = insert_password(source, WEB_VIEWER_TABLE, "secret"); - assert_eq!( - out, "[web_viewer]\npassword = \"secret\"\nbind = \"127.0.0.1\"\nport = 8091\n", - "the password line must land right after the [web_viewer] header" - ); - // The result round-trips and exposes the password. + let out = upsert_password(source, "secret").unwrap(); + + assert!(out.starts_with(source)); + assert_eq!(out.matches("password =").count(), 1); let cfg: Config = toml::from_str(&out).unwrap(); assert_eq!(cfg.web_viewer.password.as_deref(), Some("secret")); } #[test] -fn insert_password_appends_table_when_absent() { +fn upsert_password_appends_table_when_absent() { let source = "[layout]\nupper_pct = 55\n"; - let out = insert_password(source, WEB_VIEWER_TABLE, "secret"); + let out = upsert_password(source, "secret").unwrap(); + assert!(out.starts_with(source)); assert!(out.contains("\n[web_viewer]\npassword = \"secret\"\n")); let cfg: Config = toml::from_str(&out).unwrap(); @@ -126,51 +123,49 @@ fn insert_password_appends_table_when_absent() { } #[test] -fn insert_password_appends_table_into_empty_source() { - let out = insert_password("", WEB_VIEWER_TABLE, "secret"); +fn upsert_password_appends_table_into_empty_source() { + let out = upsert_password("", "secret").unwrap(); + assert_eq!(out, "[web_viewer]\npassword = \"secret\"\n"); let cfg: Config = toml::from_str(&out).unwrap(); assert_eq!(cfg.web_viewer.password.as_deref(), Some("secret")); } #[test] -fn insert_password_targets_the_named_table() { - // The credential must land under the table it was asked for. Writing it - // into whichever table comes first would put a password in a section that - // has no notion of one, and leave the viewer without one. - let source = "[layout]\nupper_pct = 55\n"; - - let out = insert_password(source, WEB_VIEWER_TABLE, "vsecret"); +fn upsert_password_ignores_commented_header() { + // A `# [web_viewer]` comment is not a real table header, so the password + // must be appended as a new table rather than inserted under the comment. + let source = "# [web_viewer] example\nfoo = 1\n"; + let out = upsert_password(source, "secret").unwrap(); - assert!( - out.contains("[web_viewer]\npassword = \"vsecret\""), - "got: {out}" - ); - let before = out.split("[web_viewer]").next().unwrap(); - assert!( - !before.contains("vsecret"), - "the viewer password leaked into an earlier table: {out}" - ); + assert!(out.contains("\n[web_viewer]\npassword = \"secret\"\n")); } #[test] -fn insert_password_finds_an_existing_viewer_table() { - let source = "[layout]\nupper_pct = 55\n\n[web_viewer]\nport = 8091\n"; +fn upsert_password_replaces_value_without_losing_decor() { + let source = "[web_viewer]\npassword = '' # generated on first use\nport = 8091\n"; - let out = insert_password(source, WEB_VIEWER_TABLE, "vsecret"); + let out = upsert_password(source, "secret").unwrap(); - let viewer = out.split("[web_viewer]").nth(1).unwrap(); - assert!(viewer.contains("password = \"vsecret\""), "got: {out}"); - assert_eq!(out.matches("[web_viewer]").count(), 1, "no duplicate table"); + assert_eq!(out.matches("password").count(), 1); + assert!(out.contains("password = \"secret\" # generated on first use")); + assert_eq!(out.matches("[web_viewer]").count(), 1); } #[test] -fn insert_password_ignores_commented_header() { - // A `# [web_viewer]` comment is not a real table header, so the password - // must be appended as a new table rather than inserted under the comment. - let source = "# [web_viewer] example\nfoo = 1\n"; - let out = insert_password(source, WEB_VIEWER_TABLE, "secret"); - assert!(out.contains("\n[web_viewer]\npassword = \"secret\"\n")); +fn upsert_password_preserves_crlf_when_appending_table() { + let source = "[layout]\r\nupper_pct = 55\r\n"; + + let out = upsert_password(source, "secret").unwrap(); + + assert!( + out.contains("\r\n[web_viewer]\r\npassword = \"secret\"\r\n"), + "unexpected CRLF output: {out:?}" + ); + assert!( + !out.replace("\r\n", "").contains('\n'), + "output contains a bare LF: {out:?}" + ); } #[test] @@ -208,6 +203,28 @@ fn ensure_web_password_generates_persists_and_sets() { assert_eq!(reparsed.web_viewer.password.as_deref(), Some(pw.as_str())); } +#[test] +fn ensure_web_password_replaces_an_empty_password_key() { + let dir = tempfile::TempDir::new().unwrap(); + let path = dir.path().join("config.toml"); + let source = "[web_viewer]\npassword = \"\" # generated on first use\nport = 8091\n"; + std::fs::write(&path, source).unwrap(); + let mut cfg: Config = toml::from_str(source).unwrap(); + + let password = ensure_web_viewer_password(&mut cfg, &path) + .unwrap() + .expect("an empty password must be replaced"); + + let saved = std::fs::read_to_string(&path).unwrap(); + assert_eq!(saved.matches("password =").count(), 1); + assert!(saved.contains("# generated on first use")); + let reparsed: Config = toml::from_str(&saved).unwrap(); + assert_eq!( + reparsed.web_viewer.password.as_deref(), + Some(password.as_str()) + ); +} + #[test] fn ensure_web_password_creates_file_when_absent() { let dir = tempfile::TempDir::new().unwrap(); diff --git a/src/config/web.rs b/src/config/web.rs index c60b594c..296ea13f 100644 --- a/src/config/web.rs +++ b/src/config/web.rs @@ -1,9 +1,11 @@ use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; +use std::io::Write; +use toml_edit::{DocumentMut, Item, Table, Value}; /// Default TCP port for the web viewer server. const DEFAULT_WEB_VIEWER_PORT: u16 = 8091; -pub(super) const WEB_VIEWER_TABLE: &str = "web_viewer"; +const WEB_VIEWER_TABLE: &str = "web_viewer"; /// Default bind address: loopback only. Exposing the server on a routable /// address is a deliberate opt-in because it grants live control of a shell. const DEFAULT_WEB_BIND: &str = "127.0.0.1"; @@ -76,7 +78,7 @@ pub fn ensure_web_viewer_password( return Ok(None); } let password = generate_password()?; - persist_password(path, WEB_VIEWER_TABLE, &password).with_context(|| { + persist_password(path, &password).with_context(|| { format!( "persisting generated web viewer password to {}", path.display() @@ -86,70 +88,88 @@ pub fn ensure_web_viewer_password( Ok(Some(password)) } -/// Write `password` into the `[{table}]` table of the TOML file at `path`. -fn persist_password(path: &std::path::Path, table: &str, password: &str) -> Result<()> { +/// Write `password` into the `[web_viewer]` table of the TOML file at `path`. +fn persist_password(path: &std::path::Path, password: &str) -> Result<()> { let existing = if path.exists() { std::fs::read_to_string(path) .with_context(|| format!("reading config file {}", path.display()))? } else { String::new() }; - let updated = insert_password(&existing, table, password); - if let Some(parent) = path.parent() { - std::fs::create_dir_all(parent) - .with_context(|| format!("creating config directory {}", parent.display()))?; - } - std::fs::write(path, &updated) + let updated = upsert_password(&existing, password)?; + let parent = path + .parent() + .filter(|parent| !parent.as_os_str().is_empty()) + .unwrap_or_else(|| std::path::Path::new(".")); + std::fs::create_dir_all(parent) + .with_context(|| format!("creating config directory {}", parent.display()))?; + + let mut pending = tempfile::NamedTempFile::new_in(parent) + .with_context(|| format!("creating a temporary file in {}", parent.display()))?; + pending + .write_all(updated.as_bytes()) .with_context(|| format!("writing config file {}", path.display()))?; - restrict_permissions(path); + pending + .as_file_mut() + .flush() + .with_context(|| format!("flushing config file {}", path.display()))?; + restrict_permissions(pending.path())?; + pending + .persist(path) + .map_err(|error| error.error) + .with_context(|| format!("replacing config file {}", path.display()))?; Ok(()) } -/// Pure text transform behind `persist_web_password`, isolated for testing. -pub(super) fn insert_password(source: &str, table: &str, password: &str) -> String { - let line = format!("password = \"{password}\""); - if let Some(insert_at) = table_header_line_end(source, table) { - let mut out = String::with_capacity(source.len() + line.len() + 1); - out.push_str(&source[..insert_at]); - out.push('\n'); - out.push_str(&line); - out.push_str(&source[insert_at..]); - out - } else { - let mut out = String::with_capacity(source.len() + line.len() + 16); - out.push_str(source); - if !out.is_empty() && !out.ends_with('\n') { - out.push('\n'); - } - if !out.is_empty() { - out.push('\n'); - } - out.push_str(&format!("[{table}]\n")); - out.push_str(&line); - out.push('\n'); - out +/// Format-preserving TOML transform behind [`persist_password`]. +pub(super) fn upsert_password(source: &str, password: &str) -> Result { + let uses_crlf = source.contains("\r\n"); + let mut document = source + .parse::() + .context("parsing config before persisting the web viewer password")?; + + if document.get(WEB_VIEWER_TABLE).is_none() { + document.insert(WEB_VIEWER_TABLE, Item::Table(Table::new())); } -} -fn table_header_line_end(source: &str, table: &str) -> Option { - let mut offset = 0; - for line in source.split_inclusive('\n') { - if line.trim() == format!("[{table}]") { - return Some(offset + line.trim_end_matches('\n').len()); - } - offset += line.len(); + let table_item = document + .get_mut(WEB_VIEWER_TABLE) + .expect("the web viewer table was inserted above"); + let table = table_item + .as_table_like_mut() + .context("web_viewer must be a TOML table")?; + + if let Some(existing) = table.get_mut("password") { + let decor = existing + .as_value() + .context("web_viewer.password must be a TOML value")? + .decor() + .clone(); + let mut replacement = Value::from(password); + *replacement.decor_mut() = decor; + *existing = Item::Value(replacement); + } else { + table.insert("password", Item::Value(Value::from(password))); } - None + + let output = document.to_string(); + Ok(if uses_crlf { + output.replace('\n', "\r\n") + } else { + output + }) } -fn restrict_permissions(path: &std::path::Path) { +fn restrict_permissions(path: &std::path::Path) -> Result<()> { #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; - let _ = std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600)); + std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600)) + .with_context(|| format!("restricting config permissions on {}", path.display()))?; } #[cfg(not(unix))] { let _ = path; } + Ok(()) } diff --git a/src/daemon/client.rs b/src/daemon/client.rs index c930dbe5..31f7a5ad 100644 --- a/src/daemon/client.rs +++ b/src/daemon/client.rs @@ -1,10 +1,8 @@ -//! The attaching side of the daemon socket. -//! -//! Shaped around the fact that the daemon speaks first. A client cannot sit in -//! a request/response loop — the session changes under it, and terminal output -//! will arrive with nothing having asked — so requests are sent and forgotten, -//! and everything the daemon says lands in a queue the caller drains on its own -//! schedule. That schedule is a TUI frame, which must never block on a socket. +//! The attaching side of the daemon socket. The daemon speaks first — the +//! session changes under the client, and terminal output arrives unprompted — +//! so requests are sent and forgotten, and everything the daemon says lands in +//! a queue the caller drains on its own schedule (a TUI frame, which must never +//! block on a socket). use super::protocol::{ClientMessage, ServerMessage, version}; use super::terminal_link::{TerminalLink, TerminalRouter}; @@ -17,42 +15,32 @@ use std::sync::mpsc::Receiver; use std::sync::{Arc, Mutex}; use std::time::Duration; -/// How long the opening handshake waits for the daemon to answer. -/// -/// Only the handshake is bounded. After it the connection is event-driven and a -/// quiet daemon is the normal state, so a timeout there would be a disconnect -/// invented out of an idle session. +/// How long the opening handshake waits for the daemon to answer. Only the +/// handshake is bounded — after it the connection is event-driven and a quiet +/// daemon is the normal state. const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(5); #[derive(Debug)] pub struct DaemonClient { - /// The write half. The reader thread owns the other. out: Writer, incoming: Receiver, /// Terminal traffic, split per repository for the backends that drain it. terminals: Arc, - /// This connection's id at the daemon, from the handshake. - /// - /// Handed to each repository's backend, which compares it against the - /// requester a new pane names — the one way to tell a pane this client - /// opened from one that appeared because another client did. + /// This connection's id at the daemon, from the handshake. Handed to each + /// repository's backend to tell a pane this client opened from one that + /// appeared because another client did. client: u64, - /// Cleared by the reader thread when the daemon goes away. - /// - /// A separate flag rather than the channel's disconnected state: reading - /// that means calling `try_recv`, which consumes a message when one is - /// waiting — so asking whether the daemon is there would throw away what it - /// last said. + /// Cleared by the reader thread when the daemon goes away. A separate flag + /// rather than the channel's disconnected state: reading that means calling + /// `try_recv`, which consumes a message when one is waiting. connected: Arc, } impl DaemonClient { - /// Attach to the daemon listening on `path`. - /// - /// Completes the version handshake before returning, so a caller that gets - /// a `DaemonClient` knows it is talking to a daemon of this build. The - /// repository set the daemon volunteers on attach is not waited for — it is - /// queued like any other message and drained with the rest. + /// Attach to the daemon listening on `path`. Completes the version handshake + /// before returning, so a caller that gets a `DaemonClient` knows it is + /// talking to a daemon of this build. The repository set the daemon + /// volunteers on attach is queued like any other message. pub fn connect(path: &Path) -> Result { let stream = UnixStream::connect(path).with_context(|| { format!( @@ -153,11 +141,9 @@ impl DaemonClient { ) } - /// Drop the terminal inboxes of repositories that are no longer open. - /// - /// Called with each set the daemon reports, which is also when the tabs are - /// reconciled: a repository that closed has no backend left to drain it, and - /// one this client never opened a tab for has an inbox nothing will. + /// Drop the terminal inboxes of repositories that are no longer open. Called + /// with each set the daemon reports, which is also when the tabs are + /// reconciled. pub fn retain_repos(&self, open: &[String]) { self.terminals.retain(open); } @@ -173,7 +159,7 @@ impl DaemonClient { } /// Ask the daemon to put a repository in front, by catalog id. Every client - /// follows, so the answer arrives as a broadcast like any other change. + /// follows, so the answer arrives as a broadcast. pub fn focus_repo(&mut self, id: &str) -> Result<()> { send( &self.out, @@ -184,17 +170,13 @@ impl DaemonClient { } /// Ask the daemon to paint the session in `accent`. Every client and the - /// browser follow, so the answer arrives as a broadcast like any other - /// change. + /// browser follow, so the answer arrives as a broadcast. pub fn set_accent(&mut self, accent: usize) -> Result<()> { send(&self.out, &ClientMessage::SetAccent { accent }) } - /// Ask the daemon to re-read `config.toml`. - /// - /// Answered to this client alone, as a [`ServerMessage::Reloaded`] or an - /// error: nothing a reload does is visible in what the other clients are - /// looking at. + /// Ask the daemon to re-read `config.toml`. Answered to this client alone, + /// as a [`ServerMessage::Reloaded`] or an error. pub fn reload_config(&mut self) -> Result<()> { send(&self.out, &ClientMessage::ReloadConfig) } diff --git a/src/daemon/client_tests.rs b/src/daemon/client_tests.rs index 3ba9e73c..6a5324f5 100644 --- a/src/daemon/client_tests.rs +++ b/src/daemon/client_tests.rs @@ -3,9 +3,6 @@ use crate::daemon::frame::{Frame, read_frame, write_frame}; use crate::daemon::protocol::{ServerMessage, version}; use crate::daemon::socket::DaemonSocket; use crate::daemon::transport::UnixListener; -use crate::web::common::auth::Auth; -use crate::web::viewer::prefs::PrefsStore; -use crate::web::viewer::server::{ViewerOptions, ViewerState}; use std::io::Write; use std::sync::Arc; use std::time::{Duration, Instant}; @@ -25,21 +22,17 @@ fn daemon(dir: &tempfile::TempDir, repos: &[String]) -> TestDaemon { let path = dir.path().join("d.sock"); let socket = DaemonSocket::bind(&path).expect("binds"); let listener = socket.listener().try_clone().expect("clones"); - let state = Arc::new(ViewerState::new(ViewerOptions { - bind: "127.0.0.1".parse().unwrap(), - port: 0, - auth: Auth::from_plaintext("swordfish").unwrap(), - repos: repos.to_vec(), - persist: false, - startup_commands: Vec::new(), - cli_startup: Vec::new(), - shell: crate::config::ShellConfig::default(), - hot: crate::config::AgentIndicatorConfig::default(), - // In the test's own directory, beside the socket: opening a repository - // records it as the active one, so this file is written, and a path - // shared with another test would make the two one session. - prefs: PrefsStore::at(dir.path().join("viewer.json")), - })); + let state = Arc::new(crate::session::SessionState::new( + crate::session::SessionOptions { + repos: repos.to_vec(), + persist: false, + startup_commands: Vec::new(), + cli_startup: Vec::new(), + shell: crate::config::ShellConfig::default(), + prefs: crate::session::prefs::PrefsStore::at(dir.path().join("viewer.json")), + status_encoder: crate::session::test_status_encoder, + }, + )); let (shutdown_tx, _shutdown_rx) = std::sync::mpsc::sync_channel(1); let session = crate::daemon::serve::start(state, shutdown_tx).expect("starts the watcher"); std::thread::spawn(move || crate::daemon::serve::serve(listener, session)); diff --git a/src/daemon/clients.rs b/src/daemon/clients.rs index de57a1fc..be88e63d 100644 --- a/src/daemon/clients.rs +++ b/src/daemon/clients.rs @@ -1,13 +1,12 @@ -//! The set of attached clients, and how a change reaches all of them. -//! -//! The session is shared, so a change one client makes is news for every other: -//! a repository opened in the browser has to appear in an attached TUI without -//! it having asked. That rules out plain request/response — the daemon speaks +//! The set of attached clients, and how a change reaches all of them. The +//! session is shared, so a change one client makes is news for every other: a +//! repository opened in the browser has to appear in an attached TUI without it +//! having asked. That rules out plain request/response — the daemon speaks //! first — so each client gets a queue and a thread that drains it, and state //! changes are broadcast rather than returned. //! //! Refusals are not broadcast. "No such directory" is an answer to the client -//! that asked and noise to everyone else, so it is addressed. +//! that asked and noise to everyone else. //! //! **Nothing here is ever skipped.** These queues carry pane output, and a //! client that misses one frame of it renders every frame after that from a @@ -20,41 +19,35 @@ use std::sync::Mutex; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::mpsc::{self, Receiver, SyncSender, TrySendError}; -/// Frames one client may have queued before it is considered wedged. -/// -/// Matches the hub's own client queue, because since the terminals were shared -/// this carries the same thing: a burst of pane output, where the depth is what -/// absorbs the gap between a hub broadcasting and a socket accepting. Reaching -/// the end of it means the client is not behind but stuck. +/// Frames one client may have queued before it is considered wedged. Matches +/// the hub's own client queue, since this carries the same thing: a burst of +/// pane output, where the depth absorbs the gap between a hub broadcasting and +/// a socket accepting. Reaching the end means the client is not behind but +/// stuck. const CLIENT_QUEUE_DEPTH: usize = 256; struct Attached { id: u64, tx: SyncSender, - /// A third handle on this client's socket, only ever used to end the - /// connection when its queue overflows. - /// - /// Dropping the sender alone would stop the writer thread and leave the - /// reader blocked, so the client would go quiet while still believing it was - /// attached. Closing the socket is what turns that into the disconnect it - /// actually is. + /// A third handle on this client's socket, used only to end the connection + /// when its queue overflows. Dropping the sender alone would stop the + /// writer thread and leave the reader blocked, so the client would go quiet + /// while still believing it was attached. Closing the socket turns that + /// into the disconnect it actually is. socket: UnixStream, - /// Whether this client is still waiting to be told the session's shape. - /// - /// Set when it attaches, and when it asks for the set outright. Cleared by - /// the watcher, which is the only thing that sends one — a client's frames - /// all coming from one thread is what makes their order the order the - /// session changed in. + /// Whether this client is still waiting to be told the session's shape. Set + /// when it attaches, and when it asks for the set outright. Cleared by the + /// watcher, which is the only thing that sends one — a client's frames all + /// coming from one thread is what makes their order the order the session + /// changed in. owed_set: bool, } impl Attached { - /// Queue `frame`, reporting whether this client is still worth keeping. - /// - /// A full queue means the client stopped draining: it is cut off, because - /// the alternative is serving it a stream with a hole in it. A disconnected - /// one has already gone — its writer thread ended when the socket broke — - /// so it is simply forgotten, not reported as stalled. + /// Queue `frame`, reporting whether this client is still worth keeping. A + /// full queue means the client stopped draining: it is cut off, because the + /// alternative is serving it a stream with a hole in it. A disconnected one + /// has already gone — its writer thread ended when the socket broke. fn queue(&self, frame: Frame) -> bool { match self.tx.try_send(frame) { Ok(()) => true, @@ -82,26 +75,36 @@ pub struct AttachedClients { } impl AttachedClients { - /// Register a client, returning its id and the queue its writer drains. + /// Register a client when the set is below `capacity`, returning its id and + /// the queue its writer drains. /// /// `socket` is a handle on the client's connection, used only to close it if /// the client stops draining. - pub fn connect(&self, socket: UnixStream) -> (u64, Receiver) { + /// + /// The capacity check and insertion share one lock hold. Checking `len` + /// before a connection thread starts would let a burst of threads all see + /// the same free slot and then register past the limit. + pub fn try_connect( + &self, + socket: UnixStream, + capacity: usize, + ) -> Option<(u64, Receiver)> { + let mut clients = self.inner.lock().expect("attached clients poisoned"); + if clients.len() >= capacity { + return None; + } let id = self.next_id.fetch_add(1, Ordering::AcqRel); let (tx, rx) = mpsc::sync_channel(CLIENT_QUEUE_DEPTH); - self.inner - .lock() - .expect("attached clients poisoned") - .push(Attached { - id, - tx, - socket, - // It has nothing on screen yet, and the watcher is what hands - // the session over. The caller wakes it (`Nudge::poke`) rather - // than sending anything itself. - owed_set: true, - }); - (id, rx) + clients.push(Attached { + id, + tx, + socket, + // It has nothing on screen yet, and the watcher is what hands + // the session over. The caller wakes it (`Nudge::poke`) rather + // than sending anything itself. + owed_set: true, + }); + Some((id, rx)) } /// Forget a client. Dropping its sender ends the writer draining it. @@ -112,6 +115,7 @@ impl AttachedClients { .retain(|client| client.id != id); } + #[cfg(test)] pub fn len(&self) -> usize { self.inner.lock().expect("attached clients poisoned").len() } diff --git a/src/daemon/clients_tests.rs b/src/daemon/clients_tests.rs index 39271345..0f00ada6 100644 --- a/src/daemon/clients_tests.rs +++ b/src/daemon/clients_tests.rs @@ -12,7 +12,9 @@ fn attach(clients: &AttachedClients) -> (u64, std::sync::mpsc::Receiver, // these tests go on to check for. far.set_read_timeout(Some(std::time::Duration::from_secs(1))) .expect("sets a timeout"); - let (id, rx) = clients.connect(near); + let (id, rx) = clients + .try_connect(near, usize::MAX) + .expect("test client fits"); (id, rx, far) } @@ -133,6 +135,51 @@ fn client_ids_are_not_reused_after_a_disconnect() { assert_ne!(first, second); } +#[test] +fn the_capacity_boundary_rejects_the_next_client() { + let clients = AttachedClients::default(); + let mut peers = Vec::new(); + for _ in 0..2 { + let (near, far) = UnixStream::pair().expect("a socket pair"); + peers.push(far); + assert!(clients.try_connect(near, 2).is_some()); + } + let (near, _far) = UnixStream::pair().expect("a socket pair"); + + assert!(clients.try_connect(near, 2).is_none()); + assert_eq!(clients.len(), 2); +} + +#[test] +fn concurrent_connections_cannot_overrun_the_capacity() { + const CAPACITY: usize = 4; + const CONTENDERS: usize = 24; + + let clients = std::sync::Arc::new(AttachedClients::default()); + let barrier = std::sync::Arc::new(std::sync::Barrier::new(CONTENDERS)); + let workers: Vec<_> = (0..CONTENDERS) + .map(|_| { + let clients = std::sync::Arc::clone(&clients); + let barrier = std::sync::Arc::clone(&barrier); + std::thread::spawn(move || { + let (near, far) = UnixStream::pair().expect("a socket pair"); + barrier.wait(); + let connected = clients.try_connect(near, CAPACITY).is_some(); + (connected, far) + }) + }) + .collect(); + + let connected = workers + .into_iter() + .map(|worker| worker.join().expect("connection worker").0) + .filter(|connected| *connected) + .count(); + + assert_eq!(connected, CAPACITY); + assert_eq!(clients.len(), CAPACITY); +} + #[test] fn a_client_is_owed_the_set_the_moment_it_attaches() { // It has nothing on screen yet, and the watcher is what hands the session diff --git a/src/daemon/detach.rs b/src/daemon/detach.rs index 673187e2..9ef99f69 100644 --- a/src/daemon/detach.rs +++ b/src/daemon/detach.rs @@ -1,11 +1,8 @@ -//! Putting the daemon into the background. -//! -//! Re-exec rather than fork. By the time this is decided the process has not -//! started its worker threads yet, but the pattern is the trap either way: -//! `fork` in a threaded process gives the child one thread and every lock in -//! whatever state it was in, and the daemonize crates exist mostly to be -//! careful about that. Spawning a fresh copy of this binary has no such state -//! to inherit. +//! Putting the daemon into the background. Re-exec rather than fork: by the +//! time this is decided the process has not started its worker threads yet, but +//! the pattern is the trap either way — `fork` in a threaded process gives the +//! child one thread and every lock in whatever state it was in. Spawning a fresh +//! copy of this binary has no such state to inherit. //! //! The child gets its own session (`setsid`), so closing the terminal that //! started it does not send it SIGHUP along with the shell's other children. diff --git a/src/daemon/frame.rs b/src/daemon/frame.rs index 60cf05ac..52fca828 100644 --- a/src/daemon/frame.rs +++ b/src/daemon/frame.rs @@ -1,21 +1,20 @@ -//! Framing for the daemon socket. -//! -//! A Unix socket is a byte stream with no message boundaries, so every message -//! carries its own length. The kind byte splits control messages from terminal -//! output for the same reason the viewer's WebSocket splits text frames from -//! binary ones: PTY bytes are not text, and routing them through JSON would pay -//! escaping and base64 expansion on the hottest path there is. +//! Framing for the daemon socket. A Unix socket is a byte stream with no +//! message boundaries, so every message carries its own length. The kind byte +//! splits control messages from terminal output for the same reason the +//! viewer's WebSocket splits text frames from binary ones: PTY bytes are not +//! text, and routing them through JSON would pay escaping and base64 expansion +//! on the hottest path there is. +use super::protocol::ServerMessage; use anyhow::{Context, Result, bail}; use std::io::{Read, Write}; -/// Largest payload one frame may carry. -/// -/// The reader allocates whatever length the frame announces, so this is the -/// ceiling on what a single message can make the process allocate. It is set -/// above the largest payload the protocol actually produces — a terminal -/// scrollback replay, capped at `MAX_TERMINAL_SCROLLBACK_BYTES` (256 KiB) per -/// pane — with room for a control message describing every pane at once. +/// Largest payload one frame may carry. The reader allocates whatever length +/// the frame announces, so this is the ceiling on what a single message can make +/// the process allocate. Set above the largest payload the protocol actually +/// produces — a terminal scrollback replay, capped at +/// `MAX_TERMINAL_SCROLLBACK_BYTES` (256 KiB) per pane — with room for a control +/// message describing every pane at once. pub const MAX_FRAME_BYTES: usize = 4 * 1024 * 1024; /// What a frame carries. Encoded as the first byte on the wire, so an unknown @@ -63,6 +62,37 @@ impl Frame { } } +/// Encode a daemon response as one control frame. +/// +/// Serialization is shared here so every producer has the same fallback, while +/// `context` keeps the failing operation visible in the log instead of reducing +/// a session update, reply, and terminal relay to the same diagnostic. +pub(super) fn encode_server( + message: &ServerMessage, + context: &'static str, + fallback_message: &'static str, +) -> Frame { + match serde_json::to_vec(message) { + Ok(json) => Frame::control(json), + Err(err) => { + tracing::error!(%err, context, "daemon: could not encode a server message"); + let fallback = ServerMessage::Error { + message: fallback_message.to_string(), + }; + Frame::control( + serde_json::to_vec(&fallback).unwrap_or_else(|fallback_err| { + tracing::error!( + %fallback_err, + context, + "daemon: could not encode the fallback server error" + ); + br#"{"type":"error","message":"server message could not be encoded"}"#.to_vec() + }), + ) + } + } +} + /// Write one frame: kind byte, big-endian length, payload. /// /// Does not flush — a caller sending several frames at once should flush after diff --git a/src/daemon/lock.rs b/src/daemon/lock.rs index 3812a9df..91d5e60a 100644 --- a/src/daemon/lock.rs +++ b/src/daemon/lock.rs @@ -1,11 +1,9 @@ -//! The single-instance lock. -//! -//! Two daemons on one socket would each serve half the attaching clients, and -//! the second to bind would displace the first. Deciding which is running has -//! to be exact, which rules out asking the socket: a `connect` that succeeds -//! does not prove a listener is alive — on macOS it can succeed against a -//! socket whose listener has closed, and the reset only shows up on the next -//! read. Probing that way was flaky in exactly the case it exists for. +//! The single-instance lock. Two daemons on one socket would each serve half +//! the attaching clients, and the second to bind would displace the first. +//! Deciding which is running has to be exact, which rules out asking the socket: +//! a `connect` that succeeds does not prove a listener is alive — on macOS it +//! can succeed against a socket whose listener has closed, and the reset only +//! shows up on the next read. //! //! An advisory lock answers instead. The kernel holds it for as long as the //! descriptor is open and releases it when the process ends — including a @@ -26,12 +24,11 @@ pub struct InstanceLock { } impl InstanceLock { - /// Take the lock, or report that another daemon holds it. - /// - /// The lock file is never removed. Unlinking it on release would let a - /// second daemon lock a file the first had already deleted, and both would - /// then believe they hold it — the classic lockfile race. An empty file - /// left behind costs nothing; the lock is the advisory lock, not the file. + /// Take the lock, or report that another daemon holds it. The lock file is + /// never removed — unlinking it on release would let a second daemon lock a + /// file the first had already deleted, and both would then believe they hold + /// it. An empty file left behind costs nothing; the lock is the advisory + /// lock, not the file. pub fn acquire(path: &Path) -> Result> { if let Some(parent) = path.parent() { std::fs::create_dir_all(parent) @@ -63,24 +60,20 @@ impl InstanceLock { /// What a failed lock means for the caller. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum Attempt { - /// Another daemon holds it. The normal negative answer. + /// Another daemon holds it. Held, - /// A signal arrived mid-call and the lock was never attempted. Says - /// nothing about who holds what, so the only correct response is to ask - /// again. + /// A signal arrived mid-call and the lock was never attempted. Says nothing + /// about who holds what, so the only correct response is to ask again. Interrupted, - /// Something else went wrong, and it must not be reported as either of the - /// above. + /// Something else went wrong. Failed, } -/// Read a lock failure. -/// -/// `Interrupted` earns its own arm because this process raises signals at -/// itself — a stop signal is how the daemon is asked to shut down. A signal -/// landing on the thread inside the lock call returns EINTR, which says nothing -/// about who holds the lock; reported as a failure it would refuse to start a -/// daemon for no reason. +/// Read a lock failure. `Interrupted` earns its own arm because this process +/// raises signals at itself — a stop signal is how the daemon is asked to shut +/// down. A signal landing on the thread inside the lock call returns EINTR, +/// which says nothing about who holds the lock; reported as a failure it would +/// refuse to start a daemon for no reason. /// /// std 가 EINTR 를 내부에서 재시도하는지는 문서화되어 있지 않다. /// 재시도한다면 이 arm 은 도달하지 않을 뿐 해가 없고, 재시도하지 diff --git a/src/daemon/protocol.rs b/src/daemon/protocol.rs index acb8cb24..ae53fa17 100644 --- a/src/daemon/protocol.rs +++ b/src/daemon/protocol.rs @@ -1,15 +1,12 @@ -//! What an attaching client and the daemon say to each other. -//! -//! Carried as JSON in [`FrameKind::Control`](super::frame::FrameKind::Control) -//! frames. Both sides ship in one binary, so this is not a compatibility -//! surface to negotiate — a client and daemon of different versions cannot meet -//! except by running two builds at once, which the version in [`Hello`] reports -//! rather than tries to bridge. +//! JSON messages between an attaching client and the daemon, carried in +//! [`FrameKind::Control`](super::frame::FrameKind::Control) frames. Both sides +//! ship in one binary, so version mismatch is reported rather than bridged. use crate::backend::PaneId; -use crate::web::viewer::terminal::frame::{ +use crate::session::terminal::frame::{ ClientMessage as HubClientMessage, ServerMessage as HubServerMessage, }; +use anyhow::{Result, bail}; use serde::{Deserialize, Serialize}; /// A request from an attached client. @@ -21,50 +18,48 @@ pub enum ClientMessage { /// The client's build, so a mismatch is reported rather than acted on. version: String, }, - /// Ask for the current repository set. ListRepos, /// Open a repository, or focus it if it is already open. - OpenRepo { path: String }, - /// Close the repository with this id. - CloseRepo { repo: String }, - /// Put this repository in front, for the whole session. - /// - /// Which project is active is shared, so switching tabs is a request rather - /// than a local move — every client follows the answer. What stays local is - /// everything inside a project: the view mode, the cursor, the scroll. - FocusRepo { repo: String }, - /// Put the repositories in this order. - ReorderRepos { order: Vec }, + OpenRepo { + path: String, + }, + CloseRepo { + repo: String, + }, + /// Put this repository in front, for the whole session. Active project is + /// shared — every client follows the answer. What stays local is everything + /// inside a project: view mode, cursor, scroll. + FocusRepo { + repo: String, + }, + ReorderRepos { + order: Vec, + }, /// Paint the session in this accent, for every client and the browser. /// /// An index into the accent cycle rather than a "next" step: two clients - /// cycling at once would otherwise each advance from what they last saw and - /// land somewhere neither asked for. An index past the end wraps, so a - /// client never has to know the cycle's length to stay in it. - SetAccent { accent: usize }, + /// cycling at once would each advance from what they last saw and land + /// somewhere neither asked for. An index past the end wraps. + SetAccent { + accent: usize, + }, /// Re-read `config.toml` and apply the tables the session owns. /// - /// Carries nothing: the file is the request. Sending its contents instead - /// would let a client reconfigure the session from something it made up, - /// where this way the daemon only ever acts on a file on its own disk that - /// the user wrote. + /// Carries nothing: the file is the request. Sending its contents would let + /// a client reconfigure the session from something it made up; this way the + /// daemon only acts on a file on its own disk that the user wrote. ReloadConfig, - /// Act on one repository's terminals. - /// - /// Carries the hub's own message rather than a parallel set: the browser - /// and an attached terminal ask for exactly the same things, and two - /// definitions of "create a pane" would drift. The repository has to ride - /// along because one socket multiplexes every open repository, where the - /// browser opens a connection per repository and needs no tag. + /// Act on one repository's terminals. Carries the hub's own message rather + /// than a parallel set so the two definitions of "create a pane" cannot + /// drift. The repository rides along because one socket multiplexes every + /// open repository, where the browser opens a connection per repository. Terminal { repo: String, message: HubClientMessage, }, - /// Ask the daemon to stop. Sent by `nightcrow stop`. - /// - /// The daemon runs the same shutdown sequence as SIGINT/SIGTERM — reaping - /// every child shell — and then closes the connection. No reply is sent; - /// the connection closing is the acknowledgment. + /// Ask the daemon to stop. Runs the same shutdown sequence as SIGINT/SIGTERM + /// — reaping every child shell — and then closes the connection. No reply + /// is sent; the connection closing is the acknowledgment. Shutdown, } @@ -75,11 +70,9 @@ pub enum ServerMessage { /// Answer to [`ClientMessage::Hello`], naming the daemon's build. Hello { version: String, - /// The id this connection is known by, so the client can tell a pane it + /// This connection's id at the daemon, so the client can tell a pane it /// asked for from one that arrived because someone else did. Panes are - /// created by request and reported to everybody, so without an identity - /// a client cannot tell the two apart — and would move its focus onto - /// whatever another client just opened. + /// created by request and reported to everybody. client: u64, }, /// The repository set, sent in answer to a list, open, close, or reorder. @@ -89,62 +82,40 @@ pub enum ServerMessage { /// it in between — a delta applied to a stale list silently diverges. Repos { repos: Vec, - /// The repository the session is focused on, which every client puts in - /// front. `None` when nothing has been focused yet, in which case a - /// client keeps whichever tab it is on. - /// - /// Carried with the set rather than announced separately because the two - /// change together — opening a repository focuses it — and a client that - /// learned them one at a time would render a tab list without knowing - /// which of them to show. + /// The repository the session is focused on. `None` when nothing has + /// been focused yet. Carried with the set because the two change + /// together — opening a repository focuses it. #[serde(default)] active: Option, - /// The accent the whole session paints in. - /// - /// Rides with the set because the watcher already broadcasts whenever - /// what it observes differs from what clients were told; a colour picked - /// in the browser reaches every attached terminal through that same - /// comparison, with nothing needing to remember to announce it. - /// - /// Required, unlike `active`: a default here would be a colour, and a - /// daemon too old to send one would have this client painting the - /// session yellow and claiming that was its choice. `None` for `active` - /// is a state the session really has; there is no such reading of a - /// missing accent. Two builds of the same version can meet — the - /// handshake compares version strings — so this is the only thing that - /// catches it, and a frame it cannot read ends the connection. + /// The accent the whole session paints in. Required, unlike `active`: + /// a default here would be a colour, and a daemon too old to send one + /// would have this client painting the session yellow and claiming that + /// was its choice. accent: usize, }, /// A request could not be carried out. The connection stays open: a refused /// request is an answer, not a protocol violation. Error { message: String }, - /// A reload was carried out, described for the person who asked. - /// - /// Answered to the asker alone, unlike a change to the served set. Nothing a - /// reload does is visible in what the other clients are looking at — the - /// startup list only reaches repositories opened later, and a plugin being - /// replaced is a child process nobody is watching — so telling them would be - /// a notice about something they did not do and cannot see. A refusal is - /// reported the same way as any other, through [`ServerMessage::Error`]. + /// A reload was carried out, described for the person who asked. Answered + /// to the asker alone — nothing a reload does is visible in what the other + /// clients are looking at. Reloaded { /// One line for the client to show. Built by the session so a browser /// toast and a terminal notice say the same thing. summary: String, }, /// Something happened to one repository's terminals — a pane was created, - /// exited, or reordered. Output does not come this way; it travels as - /// binary frames. + /// exited, or reordered. Output travels as binary frames, not here. Terminal { repo: String, event: HubServerMessage, }, } -/// One repository in the served set. -/// -/// A narrower view than the browser's `RepoDto`: an attaching client renders -/// with the TUI's own widgets and reads git locally, so it needs the identity -/// and the path, not the display fields the web UI derives. +/// One repository in the served set. Narrower than the browser's `RepoDto`: +/// an attaching client renders with the TUI's own widgets and reads git locally, +/// so it needs the identity and the path, not the display fields the web UI +/// derives. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RepoSummary { /// Opaque catalog id, stable for the daemon's lifetime. @@ -169,20 +140,28 @@ pub struct TerminalOutput { impl TerminalOutput { /// `[repo len][repo][pane id][bytes]`, with the id little-endian to match /// the hub's own binary framing. - pub fn encode(&self) -> Vec { + /// + /// Refuses repository ids longer than 255 bytes instead of truncating the + /// payload into a frame the receiver would misinterpret. + pub fn encode(&self) -> Result> { let repo = self.repo.as_bytes(); - // The id space is the catalog's, which hands out short opaque names; - // anything that does not fit a byte is a bug rather than input. - let len = u8::try_from(repo.len()).unwrap_or(0); + let Ok(len) = u8::try_from(repo.len()) else { + bail!( + "terminal output repository id is {} bytes; the protocol limit is {}", + repo.len(), + u8::MAX + ); + }; let mut out = Vec::with_capacity(1 + repo.len() + 4 + self.data.len()); out.push(len); - out.extend_from_slice(&repo[..usize::from(len)]); + out.extend_from_slice(repo); out.extend_from_slice(&self.pane.to_le_bytes()); out.extend_from_slice(&self.data); - out + Ok(out) } - /// Read one back, or `None` when the frame is too short to hold a header. + /// Read one back, or `None` when the header is truncated or its repository + /// id is not valid UTF-8. /// /// The daemon only encodes and the attaching client only decodes: output /// travels one way. diff --git a/src/daemon/protocol_tests.rs b/src/daemon/protocol_tests.rs index 5bfdc254..8da5949d 100644 --- a/src/daemon/protocol_tests.rs +++ b/src/daemon/protocol_tests.rs @@ -1,4 +1,4 @@ -use super::{ClientMessage, RepoSummary, ServerMessage, version}; +use super::{ClientMessage, RepoSummary, ServerMessage, TerminalOutput, version}; fn round_trip_client(message: &ClientMessage) -> ClientMessage { let json = serde_json::to_string(message).expect("encodes"); @@ -104,3 +104,37 @@ fn the_reported_version_is_this_build() { assert_eq!(version(), env!("CARGO_PKG_VERSION")); assert!(!version().is_empty()); } + +#[test] +fn terminal_output_accepts_empty_and_maximum_length_repository_ids() { + for repo in [String::new(), "r".repeat(u8::MAX as usize)] { + let output = TerminalOutput { + repo, + pane: 0x0102_0304, + data: vec![0xff, 0x00], + }; + + let encoded = output.encode().expect("repository id fits"); + assert_eq!(TerminalOutput::decode(&encoded), Some(output)); + } +} + +#[test] +fn terminal_output_rejects_a_repository_id_over_the_wire_limit() { + let output = TerminalOutput { + repo: "r".repeat(u8::MAX as usize + 1), + pane: 1, + data: Vec::new(), + }; + + let error = output.encode().expect_err("repository id is too long"); + assert!(error.to_string().contains("256 bytes"), "{error:#}"); +} + +#[test] +fn terminal_output_rejects_truncated_or_non_utf8_headers() { + assert_eq!(TerminalOutput::decode(&[]), None); + assert_eq!(TerminalOutput::decode(&[0, 1, 2, 3]), None); + assert_eq!(TerminalOutput::decode(&[2, b'r']), None); + assert_eq!(TerminalOutput::decode(&[1, 0xff, 0, 0, 0, 0]), None); +} diff --git a/src/daemon/requests.rs b/src/daemon/requests.rs index 458b3ce1..d24b7be5 100644 --- a/src/daemon/requests.rs +++ b/src/daemon/requests.rs @@ -1,16 +1,14 @@ -//! Carrying out one attached client's requests. -//! -//! A request is either a question — answered to the asker — or a change to the -//! session, which is not answered here at all: every client is looking at the -//! same session, so the watcher tells them all from one record of what they have -//! been told. Refusals go to the asker alone; a client must not flash an error -//! for somebody else's typo. +//! Carrying out one attached client's requests. A request is either a question +//! — answered to the asker — or a change to the session, which is not answered +//! here at all: every client is looking at the same session, so the watcher +//! tells them all from one record of what they have been told. Refusals go to +//! the asker alone; a client must not flash an error for somebody else's typo. -use super::frame::{FrameKind, read_frame}; +use super::frame::{FrameKind, encode_server, read_frame}; use super::protocol::{ClientMessage, ServerMessage, version}; -use super::serve::{Session, encode}; +use super::serve::Session; use super::transport::UnixStream; -use crate::web::viewer::session::{self, CloseError, OpenError}; +use crate::session::{self, CloseError, OpenError}; use anyhow::Result; use std::sync::Arc; @@ -30,7 +28,7 @@ pub(super) fn read_requests(mut stream: UnixStream, id: u64, session: &Session) // client stays attached and its next request is still served. Err(err) => session.clients.send_to( id, - encode(&ServerMessage::Error { + encode_reply(&ServerMessage::Error { message: format!("unreadable request: {err}"), }), ), @@ -64,7 +62,7 @@ fn handle(message: ClientMessage, id: u64, session: &Session) { message: format!("client is {client}, daemon is {daemon}"), } }; - session.clients.send_to(id, encode(&reply)); + session.clients.send_to(id, encode_reply(&reply)); } // Answered to the asker alone — nothing changed, so there is nothing to // tell the others — but not from here. The set is sent from one place so @@ -119,7 +117,7 @@ fn handle(message: ClientMessage, id: u64, session: &Session) { // telling them would be a notice about something they did not do and // cannot see. ClientMessage::ReloadConfig => { - let reply = match crate::web::viewer::reload::reload_config(state) { + let reply = match crate::session::reload::reload_config(state) { Ok(report) => ServerMessage::Reloaded { summary: report.summary(), }, @@ -129,7 +127,7 @@ fn handle(message: ClientMessage, id: u64, session: &Session) { message: err.to_string(), }, }; - session.clients.send_to(id, encode(&reply)); + session.clients.send_to(id, encode_reply(&reply)); } // Handed straight to the hub, which answers on the subscription rather // than here: a pane it creates is news for every client watching that @@ -170,8 +168,12 @@ fn changed(session: &Session) { fn refuse(id: u64, session: &Session, message: &str) { session.clients.send_to( id, - encode(&ServerMessage::Error { + encode_reply(&ServerMessage::Error { message: message.to_string(), }), ); } + +fn encode_reply(message: &ServerMessage) -> super::frame::Frame { + encode_server(message, "reply", "reply could not be encoded") +} diff --git a/src/daemon/serve.rs b/src/daemon/serve.rs index af59fb2c..3a92c96f 100644 --- a/src/daemon/serve.rs +++ b/src/daemon/serve.rs @@ -1,9 +1,8 @@ -//! The daemon's accept loop: one attached client, two threads. -//! -//! A client gets a reader and a writer because the daemon speaks unprompted — -//! the session is shared, so a repository opened in the browser has to reach an -//! attached TUI that never asked. The reader blocks on the socket; the writer -//! drains that client's queue. +//! The daemon's accept loop: one attached client, two threads. A client gets a +//! reader and a writer because the daemon speaks unprompted — the session is +//! shared, so a repository opened in the browser has to reach an attached TUI +//! that never asked. The reader blocks on the socket; the writer drains that +//! client's queue. //! //! Sized like the viewer's accept loop and for the same reason — a connection //! costs threads — but with a much lower ceiling. Clients here are terminals a @@ -12,36 +11,32 @@ use anyhow::Context; use super::clients::AttachedClients; -use super::frame::{Frame, write_frame}; -use super::protocol::ServerMessage; +use super::frame::write_frame; use super::terminals::TerminalBridges; use super::transport::{UnixListener, UnixStream}; use crate::platform::signals::Shutdown; -use crate::web::viewer::server::ViewerState; -use crate::web::viewer::session; +use crate::session; +use crate::session::SessionState; use std::collections::HashMap; use std::io::Write; use std::sync::mpsc::SyncSender; use std::sync::{Arc, Mutex}; -/// Clients that may be attached at once. -/// -/// Each is a person at a terminal, so this is generous for the real case while -/// still bounding a client stuck in a reconnect loop. +/// Clients that may be attached at once. Each is a person at a terminal, so +/// this is generous for the real case while still bounding a client stuck in a +/// reconnect loop. pub const MAX_ATTACHED_CLIENTS: usize = 16; /// Everything the connection threads share. pub struct Session { - pub(super) state: Arc, + pub(super) state: Arc, pub(super) clients: Arc, - /// Each attached client's terminal subscriptions. - /// - /// Kept here rather than on the thread that reads that client's socket, - /// because a repository can appear for reasons that have nothing to do with - /// any client's connection — the browser opened it — and it has to start - /// streaming for everyone. That thread is blocked in `read` and cannot act - /// on it; the watcher can. One lock per client, so following a change for - /// one never delays another's keystrokes. + /// Each attached client's terminal subscriptions. Kept here rather than on + /// the thread that reads that client's socket, because a repository can + /// appear for reasons that have nothing to do with any client's connection + /// — the browser opened it — and it has to start streaming for everyone. + /// One lock per client, so following a change for one never delays + /// another's keystrokes. pub(super) bridges: Mutex>>>, /// Wakes the watcher when a client has just asked for a change, so the /// answer does not wait out a poll interval. @@ -57,8 +52,7 @@ impl Session { /// Oldest client first, because subscribing is what takes a repository's /// pane sizing (the hub gives it to the newest connection): in ascending id /// order the newest client subscribes last, so a repository that has just - /// appeared is sized by the same client that sizes all the others rather - /// than by whichever one a hash map happened to yield first. + /// appeared is sized by the same client that sizes all the others. fn follow_all(&self, repos: &[session::SessionRepo]) { let mut bridges: Vec<(u64, Arc>)> = self .bridges @@ -86,7 +80,7 @@ impl Session { /// /// [`DaemonSocket`]: super::socket::DaemonSocket pub fn start( - state: Arc, + state: Arc, shutdown_tx: SyncSender, ) -> anyhow::Result> { let session = Arc::new(Session { @@ -98,12 +92,9 @@ pub fn start( }); // The only thing that sends the served set, so there is one record of what // clients have been told, one order they are told it in, and a change made - // through the browser reaches them at all. - // - // Started here, where it can be refused, rather than inside the accept loop: - // a session without a watcher serves clients that never learn what is open — - // not even on attach, which is asked of the watcher too — so a daemon that - // could not start one must not go on to say it is listening. + // through the browser reaches them at all. Started here, where it can be + // refused, rather than inside the accept loop: a session without a watcher + // serves clients that never learn what is open. let watched = Arc::clone(&session); std::thread::Builder::new() .name("nightcrow-session-watch".into()) @@ -124,13 +115,6 @@ pub fn start( pub fn serve(listener: UnixListener, session: Arc) { for stream in listener.incoming() { let Ok(stream) = stream else { continue }; - if session.clients.len() >= MAX_ATTACHED_CLIENTS { - // Dropped rather than answered: writing a refusal here would let one - // stalled client hold up every attach behind it, the same reason the - // viewer's accept loop closes instead of writing a 503. - tracing::debug!("daemon: refusing an attach over the client cap"); - continue; - } let session = Arc::clone(&session); let _ = std::thread::Builder::new() .name("nightcrow-attach".into()) @@ -150,7 +134,12 @@ fn attach(stream: UnixStream, session: &Session) { tracing::debug!("daemon: could not split an attaching client's socket"); return; }; - let (id, queue) = session.clients.connect(hangup); + let Some((id, queue)) = session.clients.try_connect(hangup, MAX_ATTACHED_CLIENTS) else { + // Dropped rather than answered: writing a refusal here would let one + // stalled client hold up every attach behind it. + tracing::debug!("daemon: refusing an attach over the client cap"); + return; + }; let bridges = Arc::new(Mutex::new(TerminalBridges::new( id, Arc::clone(&session.clients), @@ -209,22 +198,6 @@ fn attach(stream: UnixStream, session: &Session) { } } -/// Encode a message into a control frame. -/// -/// Encoding cannot fail for these types — they are plain data with no maps -/// keyed by anything but strings — so a failure would mean a bug in the -/// protocol definitions rather than anything a client did. It becomes an error -/// frame so the client sees *something* instead of a silently dropped reply. -pub(super) fn encode(message: &ServerMessage) -> Frame { - match serde_json::to_vec(message) { - Ok(json) => Frame::control(json), - Err(err) => { - tracing::error!(%err, "daemon: could not encode a reply"); - Frame::control(br#"{"type":"error","message":"reply could not be encoded"}"#.to_vec()) - } - } -} - #[cfg(test)] #[path = "serve_tests/mod.rs"] mod tests; diff --git a/src/daemon/serve_tests/harness.rs b/src/daemon/serve_tests/harness.rs index 0ebda36e..cec660e7 100644 --- a/src/daemon/serve_tests/harness.rs +++ b/src/daemon/serve_tests/harness.rs @@ -3,7 +3,7 @@ use crate::daemon::frame::{Frame, FrameKind, read_frame, write_frame}; use crate::daemon::protocol::{ClientMessage, ServerMessage, TerminalOutput, version}; use crate::daemon::socket::DaemonSocket; use crate::daemon::transport::UnixStream; -use crate::web::viewer::terminal::frame::ServerMessage as HubServerMessage; +use crate::session::terminal::frame::ServerMessage as HubServerMessage; use std::io::Write; /// A running daemon. Held by the test so its socket stays bound and its @@ -12,7 +12,7 @@ pub(super) struct TestDaemon { socket: DaemonSocket, /// The session itself, so a test can change it the way the browser does — /// through the session functions, with no attach connection involved. - state: std::sync::Arc, + state: std::sync::Arc, } impl TestDaemon { @@ -20,7 +20,7 @@ impl TestDaemon { self.socket.path() } - pub(super) fn state(&self) -> &crate::web::viewer::server::ViewerState { + pub(super) fn state(&self) -> &crate::session::SessionState { &self.state } } diff --git a/src/daemon/serve_tests/reload.rs b/src/daemon/serve_tests/reload.rs index 40d4d160..4adfd34d 100644 --- a/src/daemon/serve_tests/reload.rs +++ b/src/daemon/serve_tests/reload.rs @@ -1,6 +1,6 @@ //! Asking the daemon to re-read its config file. //! -//! The applying itself is pinned in `web::viewer::reload`, against a temp file. +//! The applying itself is pinned in `session::reload`, against a temp file. //! What is left here is the request path: who is answered. use super::harness::*; diff --git a/src/daemon/serve_tests/session.rs b/src/daemon/serve_tests/session.rs index 1f7451fb..b01ae5d6 100644 --- a/src/daemon/serve_tests/session.rs +++ b/src/daemon/serve_tests/session.rs @@ -229,7 +229,7 @@ fn a_repository_opened_through_the_browser_reaches_a_client_that_asked_nothing() // Straight against the session, the way the viewer's HTTP handler does it — // this client's connection is not involved at all. - crate::web::viewer::session::open_repo(daemon.state(), &path).expect("opens"); + crate::session::open_repo(daemon.state(), &path).expect("opens"); assert_eq!(repo_paths(&client.next_repos()), vec![resolved(&path)]); drop(repo); @@ -243,7 +243,7 @@ fn a_repository_closed_through_the_browser_reaches_it_too() { let mut client = Client::attach(daemon.path()); let id = client.repo_ids().pop().expect("one repository is open"); - crate::web::viewer::session::close_repo(daemon.state(), &id).expect("closes"); + crate::session::close_repo(daemon.state(), &id).expect("closes"); assert!(repo_paths(&client.next_repos()).is_empty()); drop(repo); diff --git a/src/daemon/serve_tests/terminals.rs b/src/daemon/serve_tests/terminals.rs index c9c2ea52..7a3ad391 100644 --- a/src/daemon/serve_tests/terminals.rs +++ b/src/daemon/serve_tests/terminals.rs @@ -1,7 +1,7 @@ use super::harness::*; use crate::daemon::frame::{Frame, write_frame}; use crate::daemon::protocol::ClientMessage; -use crate::web::viewer::terminal::frame::{ +use crate::session::terminal::frame::{ ClientMessage as HubClientMessage, ServerMessage as HubServerMessage, }; use std::io::Write; diff --git a/src/daemon/socket.rs b/src/daemon/socket.rs index abb02099..b55e383a 100644 --- a/src/daemon/socket.rs +++ b/src/daemon/socket.rs @@ -15,18 +15,16 @@ use std::path::{Path, PathBuf}; /// Socket file name under the nightcrow directory. const SOCKET_FILE: &str = "daemon.sock"; -/// Default path: `~/.nightcrow/daemon.sock`. -/// -/// Beside the config and workspace files rather than in `/tmp` or a runtime -/// directory: those are cleaned by the system on schedules that differ per OS, -/// and a socket that disappears under a running daemon strands every client. +/// Default path: `~/.nightcrow/daemon.sock`. Beside the config and workspace +/// files rather than in `/tmp` or a runtime directory: those are cleaned by the +/// system on schedules that differ per OS, and a socket that disappears under a +/// running daemon strands every client. pub fn default_socket_path() -> Result { let home = dirs::home_dir().context("cannot determine the home directory")?; Ok(home.join(".nightcrow").join(SOCKET_FILE)) } /// A bound daemon socket, held together with the claim that makes it ours. -/// /// Unlinks the socket when dropped; the lock is released with it. #[derive(Debug)] pub struct DaemonSocket { diff --git a/src/daemon/terminal_link.rs b/src/daemon/terminal_link.rs index a347192e..ac090698 100644 --- a/src/daemon/terminal_link.rs +++ b/src/daemon/terminal_link.rs @@ -9,7 +9,7 @@ use super::protocol::ClientMessage; use super::wire::{Writer, send}; use crate::backend::PaneId; -use crate::web::viewer::terminal::frame::{ +use crate::session::terminal::frame::{ ClientMessage as HubClientMessage, ServerMessage as HubServerMessage, }; use anyhow::Result; diff --git a/src/daemon/terminal_link_tests.rs b/src/daemon/terminal_link_tests.rs index 1c2aba8f..1bec9784 100644 --- a/src/daemon/terminal_link_tests.rs +++ b/src/daemon/terminal_link_tests.rs @@ -1,5 +1,5 @@ use super::*; -use crate::web::viewer::terminal::frame::ServerMessage as HubServerMessage; +use crate::session::terminal::frame::ServerMessage as HubServerMessage; fn created(pane: PaneId) -> TerminalMessage { TerminalMessage::Event(HubServerMessage::Created { diff --git a/src/daemon/terminals.rs b/src/daemon/terminals.rs index 7da50b32..2d967c67 100644 --- a/src/daemon/terminals.rs +++ b/src/daemon/terminals.rs @@ -1,22 +1,19 @@ -//! Wiring one attached client to every open repository's terminals. -//! -//! The hubs are the browser's too — one per repository, already fanning output -//! out to whoever has connected. An attaching client subscribes to all of them -//! at once, because it renders a tab per repository and a pane whose output it -//! stopped reading would fall behind its own scrollback. +//! Wiring one attached client to every open repository's terminals. The hubs +//! are the browser's too — one per repository, already fanning output out to +//! whoever has connected. An attaching client subscribes to all of them at once, +//! because it renders a tab per repository and a pane whose output it stopped +//! reading would fall behind its own scrollback. //! //! That costs a thread per client per repository. Bounded by -//! `MAX_ATTACHED_CLIENTS` × `MAX_PROJECTS`, and in practice one or two people -//! at terminals with a handful of repositories open — but it is a product, and -//! the ceiling is worth knowing before either factor is raised. +//! `MAX_ATTACHED_CLIENTS` × `MAX_PROJECTS`. use super::clients::AttachedClients; -use super::frame::Frame; +use super::frame::{Frame, encode_server}; use super::protocol::{ServerMessage, TerminalOutput}; -use crate::web::viewer::session::SessionRepo; -use crate::web::viewer::size_owner::ViewerId; -use crate::web::viewer::terminal::TerminalSession; -use crate::web::viewer::terminal::frame::{ +use crate::session::SessionRepo; +use crate::session::size_owner::ViewerId; +use crate::session::terminal::TerminalSession; +use crate::session::terminal::frame::{ ClientMessage as HubClientMessage, ServerMessage as HubServerMessage, TerminalFrame, }; use std::collections::HashMap; @@ -25,9 +22,8 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::time::Duration; /// How long a bridge waits on its hub before checking whether it should stop. -/// /// Only bounds how quickly a closed repository's thread notices; output is -/// delivered the moment it arrives, not on this tick. +/// delivered the moment it arrives. const BRIDGE_POLL: Duration = Duration::from_millis(100); /// One client's subscriptions, one per open repository. @@ -35,13 +31,10 @@ pub struct TerminalBridges { client: u64, clients: Arc, open: HashMap, - /// Whether this client's first subscription has been made. - /// - /// Attaching is a person sitting down, and that is the one moment this - /// client takes the session's sizing. Every subscription after it follows a - /// set that changed — a repository opened in a browser is not an arrival - /// here, and reading it as one would pull the sizing off whoever is actually - /// looking. + /// Whether this client's first subscription has been made. Attaching is a + /// person sitting down, and that is the one moment this client takes the + /// session's sizing. Every subscription after it follows a set that changed + /// — a repository opened in a browser is not an arrival here. arrived: bool, } @@ -62,14 +55,9 @@ impl TerminalBridges { } /// Subscribe to repositories that appeared and drop the ones that went. - /// /// Called with every set the client is told about, so a repository opened /// on another client starts streaming here without this one asking. - pub fn follow( - &mut self, - repos: &[SessionRepo], - catalog: &crate::web::viewer::catalog::Catalog, - ) { + pub fn follow(&mut self, repos: &[SessionRepo], catalog: &crate::session::catalog::Catalog) { self.open .retain(|id, _| repos.iter().any(|repo| &repo.id == id)); for repo in repos { @@ -82,17 +70,10 @@ impl TerminalBridges { let arriving = !self.arrived; let Some(bridge) = self.subscribe(&repo.id, arriving, &entry.terminals) else { // Left out of `open`, and the arrival left unspent, so the next - // set this client is told about tries again as if this had not - // been attempted — which, deliberately, it has not. - // - // "Next set" is the limit: this is called on attach and when the - // repository set changes, so a repository that fails here shows - // in the client's tabs with no terminals until something else - // moves. Left as is rather than given a retry of its own — - // reaching here means a thread could not be spawned, which on - // this daemon means the next connection cannot be served either, - // and a repository that streams nothing is the smaller half of - // that problem. + // set this client is told about tries again. "Next set" is the + // limit: this is called on attach and when the repository set + // changes, so a repository that fails here shows in the client's + // tabs with no terminals until something else moves. continue; }; self.arrived = true; @@ -108,23 +89,20 @@ impl TerminalBridges { } } - /// `None` when the relay thread could not be started, in which case nothing - /// has happened at all — see the ordering below. + /// `None` when the relay thread could not be started. fn subscribe( &self, repo: &str, arriving: bool, - hub: &Arc, + hub: &Arc, ) -> Option { let stop = Arc::new(AtomicBool::new(false)); // The thread first, and the subscription only once it exists. - // Subscribing is not free of consequence: it registers this client with - // the session's size ownership and, on an arrival, takes the sizing off - // whoever had it. Done in the other order, a thread that failed to start - // left a subscription nobody reads — the sizing displaced for the - // release grace, this client's one arrival spent, and the hub evicting a - // bridge it can never reach. Handing the session over afterwards is what - // lets the order be this way round. + // Subscribing registers this client with the session's size ownership + // and, on an arrival, takes the sizing off whoever had it. Done in the + // other order, a thread that failed to start left a subscription nobody + // reads — the sizing displaced, this client's one arrival spent, and + // the hub evicting a bridge it can never reach. let (hand_over, take) = std::sync::mpsc::channel::>(); let worker = { let stop = Arc::clone(&stop); @@ -160,15 +138,10 @@ impl TerminalBridges { // client's emulators start from the same place the browser's do. // // One viewer across every repository it subscribes to: this client is a - // single terminal showing one project at a time, not one screen per - // repository. Only the first of these subscriptions is an arrival — the - // rest follow a set that changed, and a repository opening elsewhere is - // not a person sitting down here. - // - // No socket to hand over: the loop above drains this session and passes - // frames on without blocking, so it cannot fall behind here. The - // backpressure that matters to an attached client is applied where it - // does own a socket (`AttachedClients::queue`). + // single terminal showing one project at a time. Only the first of + // these subscriptions is an arrival — the rest follow a set that + // changed, and a repository opening elsewhere is not a person sitting + // down here. let session = Arc::new(hub.connect(ViewerId::Attached(self.client), arriving, None)); let _ = hand_over.send(Arc::clone(&session)); Some(Bridge { @@ -188,28 +161,48 @@ impl TerminalBridges { /// way to know its per-repository hub ids. fn tag(repo: &str, frame: TerminalFrame, hub_client: u64, attached: u64) -> Frame { match frame { - TerminalFrame::Output { pane, data } => Frame::terminal( - TerminalOutput { + TerminalFrame::Output { pane, data } => { + let output = TerminalOutput { repo: repo.to_string(), pane, data, + }; + match output.encode() { + Ok(payload) => Frame::terminal(payload), + Err(err) => { + tracing::error!(%err, repo, "daemon: could not tag terminal output"); + encode_server( + &ServerMessage::Error { + message: "terminal output could not be relayed".into(), + }, + "terminal output relay error", + "terminal output could not be relayed", + ) + } } - .encode(), - ), + } // Parsed and re-encoded rather than passed through as text: the client // reads one message type, and a control frame smuggled through as an // opaque string would make the repository tag unreadable without // parsing it there instead. TerminalFrame::Control(json) => match serde_json::from_str(&json) { - Ok(event) => encode(&ServerMessage::Terminal { - repo: repo.to_string(), - event: rewrite_requester(event, hub_client, attached), - }), + Ok(event) => encode_server( + &ServerMessage::Terminal { + repo: repo.to_string(), + event: rewrite_requester(event, hub_client, attached), + }, + "terminal event", + "event could not be encoded", + ), Err(err) => { tracing::debug!(%err, "daemon: unreadable terminal control frame"); - encode(&ServerMessage::Error { - message: "terminal event could not be relayed".into(), - }) + encode_server( + &ServerMessage::Error { + message: "terminal event could not be relayed".into(), + }, + "terminal relay error", + "event could not be encoded", + ) } }, } @@ -236,16 +229,6 @@ fn rewrite_requester(event: HubServerMessage, hub_client: u64, attached: u64) -> } } -fn encode(message: &ServerMessage) -> Frame { - match serde_json::to_vec(message) { - Ok(json) => Frame::control(json), - Err(err) => { - tracing::error!(%err, "daemon: could not encode a terminal event"); - Frame::control(br#"{"type":"error","message":"event could not be encoded"}"#.to_vec()) - } - } -} - impl Drop for Bridge { fn drop(&mut self) { self.stop.store(true, Ordering::Release); diff --git a/src/daemon/terminals_tests.rs b/src/daemon/terminals_tests.rs index 4af3e46e..f058051c 100644 --- a/src/daemon/terminals_tests.rs +++ b/src/daemon/terminals_tests.rs @@ -7,7 +7,7 @@ use super::rewrite_requester; use crate::daemon::protocol::ServerMessage; -use crate::web::viewer::terminal::frame::ServerMessage as HubServerMessage; +use crate::session::terminal::frame::ServerMessage as HubServerMessage; const HUB_CLIENT: u64 = 4; const ATTACHED_CLIENT: u64 = 91; diff --git a/src/daemon/watch.rs b/src/daemon/watch.rs index 681d5ca9..7b9f331c 100644 --- a/src/daemon/watch.rs +++ b/src/daemon/watch.rs @@ -1,9 +1,8 @@ //! Telling attached clients about changes nobody on their connection asked for. -//! //! The session has two front doors. A repository opened in the browser goes //! through the HTTP handlers, and nothing on an attach socket is woken by it — //! so a client that asks for nothing would sit on a tab list that quietly went -//! stale, which is the one thing a shared session must not do. +//! stale. //! //! This is a thread that re-reads the session on a tick and tells everyone when //! it differs from what they were last told. Observing rather than being @@ -12,26 +11,24 @@ //! is a comparison of a handful of small structs at a rate nobody can see. use super::clients::AttachedClients; +use super::frame::encode_server; use super::protocol::{RepoSummary, ServerMessage}; -use crate::web::viewer::server::ViewerState; -use crate::web::viewer::session; +use crate::session; +use crate::session::SessionState; use std::sync::{Arc, Condvar, Mutex}; use std::time::Duration; -/// How long the watcher waits before re-reading the session unprompted. -/// -/// This bounds only changes it cannot be told about — the ones made through the +/// How long the watcher waits before re-reading the session unprompted. This +/// bounds only changes it cannot be told about — the ones made through the /// browser's HTTP handlers — where the alternative it replaces was "never". A -/// change asked for on an attach socket wakes it immediately (see [`Nudge`]), so -/// a keystroke never waits on this. +/// change asked for on an attach socket wakes it immediately (see [`Nudge`]). const TICK: Duration = Duration::from_millis(150); -/// A way to tell the watcher not to wait out its tick. -/// -/// A client that just asked for something is watching for it to happen, so the -/// answer cannot sit behind a poll interval. The change is still *read* from the -/// session rather than passed through here: this only says "look now", so a -/// handler that forgets to poke costs latency, never correctness. +/// A way to tell the watcher not to wait out its tick. A client that just asked +/// for something is watching for it to happen, so the answer cannot sit behind a +/// poll interval. The change is still *read* from the session rather than passed +/// through here: this only says "look now", so a handler that forgets to poke +/// costs latency, never correctness. #[derive(Default)] pub(super) struct Nudge { poked: Mutex, @@ -79,7 +76,7 @@ impl Nudge { /// the price of a branch that can be wrong. The owed-only path does not need it: /// those clients followed the set when they attached, and it has not changed. pub(super) fn watch( - state: Arc, + state: Arc, clients: Arc, nudge: Arc, follow: impl Fn(&[session::SessionRepo]), @@ -102,11 +99,15 @@ pub(super) fn watch( session::accent(&state), ); let frame = || { - encode(&ServerMessage::Repos { - repos: current.0.clone(), - active: current.1.clone(), - accent: current.2, - }) + encode_server( + &ServerMessage::Repos { + repos: current.0.clone(), + active: current.1.clone(), + accent: current.2, + }, + "session change", + "session change could not be encoded", + ) }; if told != current { follow(&repos); @@ -134,15 +135,3 @@ fn summarize(repos: &[session::SessionRepo]) -> Vec { }) .collect() } - -fn encode(message: &ServerMessage) -> super::frame::Frame { - match serde_json::to_vec(message) { - Ok(json) => super::frame::Frame::control(json), - Err(err) => { - tracing::error!(%err, "daemon: could not encode a session change"); - super::frame::Frame::control( - br#"{"type":"error","message":"session change could not be encoded"}"#.to_vec(), - ) - } - } -} diff --git a/src/daemon/wire.rs b/src/daemon/wire.rs index 51ebc920..381c2277 100644 --- a/src/daemon/wire.rs +++ b/src/daemon/wire.rs @@ -9,7 +9,7 @@ use super::frame::{Frame, FrameKind, read_frame, write_frame}; use super::protocol::{ClientMessage, ServerMessage, TerminalOutput}; use super::terminal_link::{TerminalMessage, TerminalRouter}; use super::transport::UnixStream; -use crate::web::viewer::terminal::frame::ServerMessage as HubServerMessage; +use crate::session::terminal::frame::ServerMessage as HubServerMessage; use anyhow::{Context, Result}; use std::io::Write; use std::sync::mpsc::Sender; diff --git a/src/git/clone.rs b/src/git/clone.rs index fdb80d19..0db3fe63 100644 --- a/src/git/clone.rs +++ b/src/git/clone.rs @@ -1,32 +1,20 @@ -//! Clone a remote repository by handing the URL to the `git` binary. +//! Clone a remote repository by delegating to the `git` binary. //! -//! libgit2 is not used here. The vendored build carries no SSH transport -//! (`libgit2-sys` pulls no `libssh2-sys`), so `git@host:path` — the form most -//! remotes are written in — would not resolve at all, and libgit2 also knows -//! nothing of credential helpers, `insteadOf` rewrites, or an agent-held key. -//! Delegating to `git` inherits that whole stack. This is not the "parse git's -//! output" pattern the project avoids: nothing here reads stdout, only the exit -//! status and stderr-on-failure. +//! libgit2 is not used: the vendored build carries no SSH transport, knows +//! nothing of credential helpers or `insteadOf` rewrites, and cannot resolve +//! `git@host:path` remotes. Delegating to `git` inherits that whole stack. +//! Nothing here reads stdout — only exit status and stderr-on-failure. use std::path::Path; use std::process::Command; -/// URL schemes the clone form accepts. -/// -/// This list is a security boundary, not a convenience. git resolves -/// `ext::` by **executing that command**, so an unfiltered URL is -/// remote code execution — and passing the URL as an argv item behind `--` -/// does not help, because the scheme is interpreted after argument parsing. -/// `file://` and bare local paths are excluded too: the caller reaches local -/// directories through the folder picker, so accepting them here would only -/// widen what a URL can name. -/// -/// `git://` is excluded as well. It carries neither authentication nor -/// encryption — anything in the path can serve arbitrary code as the clone — -/// and it is the one transport git gives no stall control for, so a dead -/// remote would hold the single clone slot until the server restarts. Hosts -/// have been retiring it for years; `https://` covers the same anonymous -/// fetch with both problems solved. +/// URL schemes the clone form accepts. This is a security boundary: git +/// resolves `ext::` by executing that command, so an unfiltered URL +/// is remote code execution. `file://` and bare local paths are excluded +/// because the caller reaches local directories through the folder picker. +/// `git://` is excluded too — it carries no authentication or encryption, and +/// git gives no stall control for it, so a dead remote would hold the clone +/// slot indefinitely. const ALLOWED_SCHEMES: [&str; 4] = ["https://", "http://", "ssh://", "git+ssh://"]; /// Longest URL accepted, to keep a hostile client from parking megabytes in a diff --git a/src/git/diff/commit_log.rs b/src/git/diff/commit_log.rs index 314b947f..a2b0550e 100644 --- a/src/git/diff/commit_log.rs +++ b/src/git/diff/commit_log.rs @@ -9,8 +9,6 @@ pub fn load_commit_log(repo: &Repository, max_count: usize) -> Result, @@ -81,13 +73,10 @@ pub fn load_commit_log_from( /// Render a commit oid as the conventional 7-character abbreviated form. /// -/// Previously this used `repo.find_object(...).short_id()`, which asks -/// libgit2 to compute the *minimum unique prefix length* — at the cost of -/// roughly O(log n) ODB lookups per commit. For a repo with thousands of -/// commits that cost was paid on every initial commit log load. git's own -/// default `core.abbrev` is 7, so a fixed 7-char prefix matches the -/// familiar form while making this an O(1) operation. Oid hex strings are -/// always 40 ASCII bytes, so the slice is sound. +/// Previously used `repo.find_object(...).short_id()`, which computes the +/// minimum unique prefix at O(log n) ODB lookups per commit. git's own default +/// `core.abbrev` is 7, so a fixed 7-char prefix matches the familiar form +/// while making this O(1). pub(crate) fn short_oid(oid: Oid) -> String { let s = oid.to_string(); s.get(..7).unwrap_or(&s).to_string() diff --git a/src/git/diff/types.rs b/src/git/diff/types.rs index 8d2e743d..a20361c4 100644 --- a/src/git/diff/types.rs +++ b/src/git/diff/types.rs @@ -3,9 +3,7 @@ use std::borrow::Cow; /// State of a single git status column. `index` (X) compares HEAD with the /// staged tree, `worktree` (Y) compares the staged tree with the working -/// directory. Either column can be `Unmodified` — that is what the old -/// single-status `ChangeStatus` could not express. Mirrors the codes used by -/// `git status --short`. +/// directory. Either column can be `Unmodified`. Mirrors `git status --short`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum StatusKind { Unmodified, @@ -56,16 +54,14 @@ pub struct ChangedFile { /// New/effective path. Used for diff loading, file preview, hot-file /// tracking, and selection restoration. pub path: String, - /// Old path for renames (display/search metadata only). `None` otherwise. + /// Old path for renames (display/search metadata only). pub old_path: Option, /// Index column (X): HEAD vs staged. pub index: StatusKind, /// Working-tree column (Y): staged vs working directory. pub worktree: StatusKind, - /// Pre-computed lowercase search text. For renames it contains both old and - /// new paths so either side matches the `contains` filter; otherwise it is - /// the lowercased `path`. Set on construction so the file-list filter - /// doesn't lowercase on every keystroke. + /// Pre-computed lowercase search text. For renames contains both old and + /// new paths so either side matches a `contains` filter. pub search_lower: String, } @@ -98,8 +94,7 @@ impl ChangedFile { } /// Two-character Git short status code (`XY`). Untracked is special-cased - /// to `??` and conflicts to `UU` to match git rather than emitting ` ?` - /// from a blank index plus untracked worktree. + /// to `??` and conflicts to `UU` to match git. pub fn short_code(&self) -> String { if self.index == StatusKind::Untracked || self.worktree == StatusKind::Untracked { return "??".to_string(); @@ -152,14 +147,11 @@ pub enum LineKind { pub struct DiffLine { pub kind: LineKind, pub content: String, - /// Line number on the pre-image side, as reported by libgit2. `None` for an - /// added line, which exists only on the new side — so the gutter can leave - /// that column blank instead of inventing a number. Also `None` on - /// hand-built fixtures and the synthetic binary-file hunk, where no real - /// line numbering exists. + /// Line number on the pre-image side. `None` for added lines (which exist + /// only on the new side), hand-built fixtures, and synthetic binary hunks. pub old_lineno: Option, - /// Line number on the post-image side. `None` for a removed line, which is - /// absent from the new side. Same `None` cases as `old_lineno` otherwise. + /// Line number on the post-image side. `None` for removed lines and the + /// same cases as `old_lineno`. pub new_lineno: Option, } @@ -167,9 +159,7 @@ pub struct DiffLine { pub struct DiffHunk { pub header: String, pub lines: Vec, - /// File this hunk belongs to. `Some` for hunks emitted by the diff - /// collectors below; `None` for hand-built fixtures in tests where the - /// path is irrelevant. Used by the renderer to pick a per-hunk syntax + /// File this hunk belongs to. Used by the renderer to pick per-hunk syntax /// in commit diffs (one commit can touch multiple file types). pub file_path: Option, } @@ -184,18 +174,15 @@ pub struct TrackingStatus { pub struct RepoSnapshot { pub files: Vec, pub tracking: Option, - /// HEAD commit oid at the moment the snapshot was taken. `None` for - /// empty or detached repositories with no resolvable HEAD. The main - /// thread compares this against `App::last_head_oid` to detect new - /// commits and refresh the Log view's cached commit list. + /// HEAD commit oid at snapshot time. `None` for empty or detached repos. + /// Compared against `App::last_head_oid` to detect new commits. pub head_oid: Option, - /// Current branch shorthand (e.g. `main`) when HEAD points at a branch. - /// `None` for detached HEAD, unborn branch, or bare repo so the header - /// can decide whether to render the branch chip. + /// Current branch shorthand (e.g. `main`). `None` for detached HEAD, + /// unborn branch, or bare repo. pub branch_name: Option, /// Digest over every ref name and target. Refs move without HEAD moving /// (a fetch advances `origin/dev`), so the Log view rebuilds its ref - /// decoration map when this changes rather than on HEAD changes alone. + /// decoration map when this changes. pub refs_fingerprint: u64, } @@ -205,8 +192,6 @@ pub struct CommitEntry { pub short_id: String, pub summary: String, /// Pre-computed lowercase form of `summary` for case-insensitive search. - /// Set on construction so the commit-log filter doesn't lowercase on every - /// keystroke. Mirrors `ChangedFile::search_lower`. pub summary_lower: String, pub author: String, /// Author email, shown only in the wide layout. Empty when the commit diff --git a/src/git/path/mod.rs b/src/git/path/mod.rs index 7f4d1833..25a45cca 100644 --- a/src/git/path/mod.rs +++ b/src/git/path/mod.rs @@ -1,10 +1,9 @@ //! Validation for repository-relative paths that reach the filesystem. //! -//! Every path that names a file *inside* a worktree goes through -//! [`resolve_in_workdir`] before being opened. Today's callers pass paths that -//! git itself produced, but the web surfaces route caller-supplied strings to -//! the same loaders, so the check lives at the filesystem boundary rather than -//! at each call site. +//! Every path that names a file inside a worktree goes through +//! [`resolve_in_workdir`] before being opened. The web surfaces route +//! caller-supplied strings to the same loaders, so the check lives at the +//! filesystem boundary rather than at each call site. use anyhow::{Result, anyhow}; use std::path::{Component, Path, PathBuf}; diff --git a/src/git/tree/mod.rs b/src/git/tree/mod.rs index b3cbe750..812ac344 100644 --- a/src/git/tree/mod.rs +++ b/src/git/tree/mod.rs @@ -1,11 +1,8 @@ //! Lazy, read-only directory listing for the file-tree navigator. //! -//! Each call reads exactly one directory level (`std::fs::read_dir`); the -//! caller decides when to descend, so an unexpanded subtree is never walked. -//! Listing is filtered against `.gitignore` (via libgit2) and repository -//! metadata, and symlinks are reported as non-directories so the navigator -//! never follows one — this is what keeps the tree free of cycles without a -//! visited-set. +//! Each call reads one directory level; the caller decides when to descend. +//! Symlinks are reported as non-directories so the navigator never follows +//! one — keeping the tree free of cycles without a visited-set. use anyhow::{Context, Result}; use git2::Repository; @@ -24,14 +21,9 @@ pub struct TreeEntry { /// workdir root). Entries are filtered and returned sorted with directories /// first, then case-sensitive alphabetical by name. /// -/// Filtering rules: -/// - `.git` is skipped at every level (repository metadata / object storage). -/// - When `respect_gitignore` is set, ignored paths are dropped via -/// `Repository::is_path_ignored`. -/// - Non-UTF-8 names are skipped: the file-view loader keys on `&str` paths and -/// cannot losslessly address them. -/// - Individual entries whose metadata cannot be read are skipped rather than -/// failing the whole listing. +/// `.git` is skipped at every level. Non-UTF-8 names are skipped because the +/// file-view loader keys on `&str` paths. Individual entries whose metadata +/// cannot be read are skipped rather than failing the whole listing. pub fn read_children( repo: &Repository, workdir: &Path, @@ -108,19 +100,14 @@ pub struct TreeMatch { } /// Recursively search the worktree for entries whose basename contains `query` -/// (case-insensitive substring), mirroring the TUI's tree search -/// (`App::build_tree_index` + `recompute_filter`): a full walk from the root, -/// one [`read_children`] call per directory, depth-capped at `max_depth` and -/// gitignore-filtered. Symlinked directories report `is_dir == false` and so are -/// never descended (the same cycle guard that browsing relies on). +/// (case-insensitive substring). Depth-capped at `max_depth`, gitignore-filtered, +/// and symlink-safe (symlinked directories are never descended). /// -/// Results are sorted by path for a stable listing and capped at `limit`. /// `max_visits` bounds how many entries the walk may inspect: unlike the TUI's -/// single-user in-process index, this runs per web request, so the traversal — -/// not just the retained results — must be bounded against a pathological or -/// hostile tree. `truncated` is true when either budget cut the walk short or -/// more matches existed than were returned. An empty `query` matches every -/// entry, so callers gate on non-empty input. +/// in-process index, this runs per web request, so the traversal must be bounded +/// against a pathological tree. `truncated` is true when either budget cut the +/// walk short or more matches existed than were returned. An empty `query` +/// matches every entry, so callers gate on non-empty input. pub fn search_tree( repo: &Repository, workdir: &Path, diff --git a/src/git/tree/tests.rs b/src/git/tree/tests.rs index 761b01c9..d24b154f 100644 --- a/src/git/tree/tests.rs +++ b/src/git/tree/tests.rs @@ -2,312 +2,17 @@ use super::*; use crate::test_util::{make_repo, open_repo, run_git}; use std::path::Path as StdPath; -fn names(entries: &[TreeEntry]) -> Vec<&str> { - entries.iter().map(|e| e.name.as_str()).collect() -} - -#[test] -fn read_children_sorts_dirs_first_then_alpha() { - let (dir, path) = make_repo(); - let root = StdPath::new(&path); - std::fs::create_dir(root.join("zeta_dir")).unwrap(); - std::fs::create_dir(root.join("alpha_dir")).unwrap(); - std::fs::write(root.join("b_file.txt"), "x").unwrap(); - std::fs::write(root.join("a_file.txt"), "x").unwrap(); - - let workdir = open_repo(&path); - let entries = read_children(&workdir, root, "", true).unwrap(); - - assert_eq!( - names(&entries), - vec!["alpha_dir", "zeta_dir", "a_file.txt", "b_file.txt"] - ); - assert!(entries[0].is_dir); - assert!(entries[1].is_dir); - assert!(!entries[2].is_dir); - drop(dir); -} - -#[test] -fn read_children_reads_nested_dir_lazily() { - let (dir, path) = make_repo(); - let root = StdPath::new(&path); - std::fs::create_dir_all(root.join("src").join("ui")).unwrap(); - std::fs::write(root.join("src").join("main.rs"), "fn main() {}").unwrap(); - - let repo = open_repo(&path); - let entries = read_children(&repo, root, "src", true).unwrap(); - - assert_eq!(names(&entries), vec!["ui", "main.rs"]); - drop(dir); -} - -#[test] -fn read_children_skips_git_metadata() { - let (dir, path) = make_repo(); - let root = StdPath::new(&path); - std::fs::write(root.join("a.txt"), "x").unwrap(); - - let repo = open_repo(&path); - let entries = read_children(&repo, root, "", true).unwrap(); - - // `.git` exists on disk (make_repo runs `git init`) but must never be - // listed. - assert!(!names(&entries).contains(&".git")); - assert!(names(&entries).contains(&"a.txt")); - drop(dir); -} - -#[test] -fn read_children_respects_gitignore_when_enabled() { - let (dir, path) = make_repo(); - let root = StdPath::new(&path); - std::fs::write(root.join(".gitignore"), "ignored.log\nbuild/\n").unwrap(); - std::fs::write(root.join("ignored.log"), "x").unwrap(); - std::fs::write(root.join("kept.rs"), "x").unwrap(); - std::fs::create_dir(root.join("build")).unwrap(); - // Commit the gitignore so libgit2 picks it up reliably. - run_git(&path, &["add", ".gitignore"]); - run_git(&path, &["commit", "-m", "add gitignore"]); - - let repo = open_repo(&path); - let filtered = read_children(&repo, root, "", true).unwrap(); - assert!(!names(&filtered).contains(&"ignored.log")); - assert!(!names(&filtered).contains(&"build")); - assert!(names(&filtered).contains(&"kept.rs")); - - // With the toggle off, ignored paths reappear. - let unfiltered = read_children(&repo, root, "", false).unwrap(); - assert!(names(&unfiltered).contains(&"ignored.log")); - assert!(names(&unfiltered).contains(&"build")); - drop(dir); -} - -#[cfg(unix)] -#[test] -fn read_children_refuses_to_list_the_git_directory() { - // Skipping `.git` from child listings is not enough: asking for it as - // the directory itself enumerated refs, hooks, and object layout. - let (dir, path) = make_repo(); - let root = StdPath::new(&path); - let repo = open_repo(&path); - - for asked in [".git", ".git/refs", ".GIT", "src/../.git"] { - let err = read_children(&repo, root, asked, false).unwrap_err(); - assert!( - !err.to_string().contains("failed to read directory"), - "{asked:?} must be refused by validation, not by chance: {err}" - ); - } - drop(dir); -} - -#[test] -fn read_children_refuses_traversal_that_stays_inside_the_worktree() { - // Containment alone accepts this — it resolves back inside the repo. - let (dir, path) = make_repo(); - let root = StdPath::new(&path); - std::fs::create_dir(root.join("src")).unwrap(); - let repo = open_repo(&path); - - let err = read_children(&repo, root, "src/..", false).unwrap_err(); - - assert!( - err.to_string().contains("plain relative path"), - "unexpected error: {err}" - ); - drop(dir); -} - -#[cfg(unix)] -#[test] -fn read_children_refuses_to_descend_a_symlinked_directory() { - let (dir, path) = make_repo(); - let root = StdPath::new(&path); - std::fs::create_dir(root.join("real_dir")).unwrap(); - std::fs::write(root.join("real_dir").join("secret.txt"), "x").unwrap(); - std::os::unix::fs::symlink(root.join("real_dir"), root.join("link_dir")).unwrap(); - - let repo = open_repo(&path); - // Expanding the symlink path directly (as a stale session or swapped - // cache entry could ask to) must be rejected, not followed. - let err = read_children(&repo, root, "link_dir", false).unwrap_err(); - assert!( - err.to_string().contains("symlinks are not followed"), - "unexpected error: {err}" - ); - // The real directory still reads normally. - assert!(read_children(&repo, root, "real_dir", false).is_ok()); - drop(dir); -} - -#[cfg(windows)] -fn junction(link: &Path, target: &Path) -> bool { - std::process::Command::new("cmd") - .args(["/C", "mklink", "/J"]) - .arg(link) - .arg(target) - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()) - .status() - .is_ok_and(|s| s.success()) -} - -#[cfg(windows)] -#[test] -fn read_children_refuses_to_descend_a_junction() { - let (dir, path) = make_repo(); - let root = StdPath::new(&path); - std::fs::create_dir(root.join("real_dir")).unwrap(); - std::fs::write(root.join("real_dir").join("secret.txt"), "x").unwrap(); - let link = root.join("link_dir"); - if !junction(&link, &root.join("real_dir")) { - eprintln!("skipping junction test: mklink /J failed (FAT32 or missing privilege?)"); - drop(dir); - return; - } - - let repo = open_repo(&path); - let err = read_children(&repo, root, "link_dir", false).unwrap_err(); - assert!( - err.to_string().contains("symlinks are not followed"), - "unexpected error: {err}" - ); - // The real directory still reads normally. - assert!(read_children(&repo, root, "real_dir", false).is_ok()); - drop(dir); -} - -#[cfg(unix)] -#[test] -fn read_children_rejects_escape_through_symlinked_parent() { - let (dir, path) = make_repo(); - let root = StdPath::new(&path); - // An external tree outside the repo, reachable via a symlinked parent. - let outside = tempfile::TempDir::new().unwrap(); - std::fs::create_dir(outside.path().join("sub")).unwrap(); - std::fs::write(outside.path().join("sub").join("secret.txt"), "x").unwrap(); - std::os::unix::fs::symlink(outside.path(), root.join("link")).unwrap(); +mod listing; +mod path_security; +mod search; - let repo = open_repo(&path); - // `link` is a symlink (intermediate component); `link/sub` must be - // rejected at the link, before the path is ever resolved outside. - let err = read_children(&repo, root, "link/sub", false).unwrap_err(); - assert!( - err.to_string().contains("symlinks are not followed"), - "unexpected error: {err}" - ); - drop(dir); +fn names(entries: &[TreeEntry]) -> Vec<&str> { + entries.iter().map(|entry| entry.name.as_str()).collect() } fn paths(matches: &[TreeMatch]) -> Vec<&str> { - matches.iter().map(|m| m.path.as_str()).collect() -} - -#[test] -fn search_tree_finds_nested_matches_by_basename() { - let (dir, path) = make_repo(); - let root = StdPath::new(&path); - std::fs::create_dir_all(root.join("src").join("ui")).unwrap(); - std::fs::write(root.join("src").join("main.rs"), "x").unwrap(); - std::fs::write(root.join("src").join("ui").join("tree_view.rs"), "x").unwrap(); - std::fs::write(root.join("README.md"), "x").unwrap(); - - let repo = open_repo(&path); - // Case-insensitive substring on the basename, matched recursively; sorted - // by full path. - let (matches, truncated) = search_tree(&repo, root, "RS", 64, 10_000, 100).unwrap(); - assert_eq!(paths(&matches), vec!["src/main.rs", "src/ui/tree_view.rs"]); - assert!(!truncated); - drop(dir); -} - -#[test] -fn search_tree_excludes_gitignored_and_git_metadata() { - let (dir, path) = make_repo(); - let root = StdPath::new(&path); - std::fs::write(root.join(".gitignore"), "target/\n").unwrap(); - std::fs::create_dir(root.join("target")).unwrap(); - std::fs::write(root.join("target").join("build.rs"), "x").unwrap(); - std::fs::write(root.join("keep.rs"), "x").unwrap(); - run_git(&path, &["add", ".gitignore"]); - run_git(&path, &["commit", "-m", "add gitignore"]); - - let repo = open_repo(&path); - let (matches, _) = search_tree(&repo, root, ".rs", 64, 10_000, 100).unwrap(); - assert_eq!(paths(&matches), vec!["keep.rs"]); - drop(dir); -} - -#[test] -fn search_tree_stops_descending_beyond_max_depth() { - let (dir, path) = make_repo(); - let root = StdPath::new(&path); - std::fs::create_dir_all(root.join("a").join("b")).unwrap(); - std::fs::write(root.join("a").join("shallow.txt"), "x").unwrap(); - std::fs::write(root.join("a").join("b").join("deep.txt"), "x").unwrap(); - - let repo = open_repo(&path); - // depth 0 = root's children ("a"); depth 1 = "a"'s children ("b", - // "shallow.txt"). With max_depth 1, "b"'s children are never read. - let (matches, _) = search_tree(&repo, root, ".txt", 1, 10_000, 100).unwrap(); - assert_eq!(paths(&matches), vec!["a/shallow.txt"]); - drop(dir); -} - -#[test] -fn search_tree_caps_results_and_flags_truncation() { - let (dir, path) = make_repo(); - let root = StdPath::new(&path); - for i in 0..5 { - std::fs::write(root.join(format!("hit_{i}.txt")), "x").unwrap(); - } - - let repo = open_repo(&path); - let (matches, truncated) = search_tree(&repo, root, "hit_", 64, 10_000, 3).unwrap(); - assert_eq!(matches.len(), 3); - assert!(truncated); - // Deterministic prefix: sorted then truncated. - assert_eq!(paths(&matches), vec!["hit_0.txt", "hit_1.txt", "hit_2.txt"]); - drop(dir); -} - -#[test] -fn search_tree_stops_when_the_visit_budget_is_exhausted() { - let (dir, path) = make_repo(); - let root = StdPath::new(&path); - for i in 0..20 { - std::fs::write(root.join(format!("f_{i:02}.txt")), "x").unwrap(); - } - - let repo = open_repo(&path); - // A tight visit budget cuts the walk short before every entry is seen, so - // the result is flagged incomplete even though it is under the result cap. - let (matches, truncated) = search_tree(&repo, root, ".txt", 64, 5, 100).unwrap(); - assert!(matches.len() <= 5, "got {} matches", matches.len()); - assert!(truncated); - drop(dir); -} - -#[cfg(unix)] -#[test] -fn read_children_reports_symlinked_dir_as_non_dir() { - let (dir, path) = make_repo(); - let root = StdPath::new(&path); - std::fs::create_dir(root.join("real_dir")).unwrap(); - std::os::unix::fs::symlink(root.join("real_dir"), root.join("link_dir")).unwrap(); - - let repo = open_repo(&path); - let entries = read_children(&repo, root, "", false).unwrap(); - - let link = entries + matches .iter() - .find(|e| e.name == "link_dir") - .expect("symlink should be listed"); - // A symlinked directory must report `is_dir == false` so the navigator - // treats it as a leaf and never follows it (cycle guard). - assert!(!link.is_dir); - let real = entries.iter().find(|e| e.name == "real_dir").unwrap(); - assert!(real.is_dir); - drop(dir); + .map(|matched| matched.path.as_str()) + .collect() } diff --git a/src/git/tree/tests/listing.rs b/src/git/tree/tests/listing.rs new file mode 100644 index 00000000..4fdc1ae6 --- /dev/null +++ b/src/git/tree/tests/listing.rs @@ -0,0 +1,75 @@ +use super::*; + +#[test] +fn read_children_sorts_dirs_first_then_alpha() { + let (dir, path) = make_repo(); + let root = StdPath::new(&path); + std::fs::create_dir(root.join("zeta_dir")).unwrap(); + std::fs::create_dir(root.join("alpha_dir")).unwrap(); + std::fs::write(root.join("b_file.txt"), "x").unwrap(); + std::fs::write(root.join("a_file.txt"), "x").unwrap(); + + let repo = open_repo(&path); + let entries = read_children(&repo, root, "", true).unwrap(); + + assert_eq!( + names(&entries), + vec!["alpha_dir", "zeta_dir", "a_file.txt", "b_file.txt"] + ); + assert!(entries[..2].iter().all(|entry| entry.is_dir)); + assert!(entries[2..].iter().all(|entry| !entry.is_dir)); + drop(dir); +} + +#[test] +fn read_children_reads_nested_dir_lazily() { + let (dir, path) = make_repo(); + let root = StdPath::new(&path); + std::fs::create_dir_all(root.join("src/ui")).unwrap(); + std::fs::write(root.join("src/main.rs"), "fn main() {}").unwrap(); + + let repo = open_repo(&path); + let entries = read_children(&repo, root, "src", true).unwrap(); + + assert_eq!(names(&entries), vec!["ui", "main.rs"]); + drop(dir); +} + +#[test] +fn read_children_skips_git_metadata() { + let (dir, path) = make_repo(); + let root = StdPath::new(&path); + std::fs::write(root.join("a.txt"), "x").unwrap(); + + let repo = open_repo(&path); + let entries = read_children(&repo, root, "", true).unwrap(); + + assert!(!names(&entries).contains(&".git")); + assert!(names(&entries).contains(&"a.txt")); + drop(dir); +} + +#[test] +fn read_children_respects_gitignore_when_enabled() { + let (dir, path) = make_repo(); + let root = StdPath::new(&path); + std::fs::write(root.join(".gitignore"), "ignored.log\nbuild/\n").unwrap(); + std::fs::write(root.join("ignored.log"), "x").unwrap(); + std::fs::write(root.join("kept.rs"), "x").unwrap(); + std::fs::create_dir(root.join("build")).unwrap(); + run_git(&path, &["add", ".gitignore"]); + run_git(&path, &["commit", "-m", "add gitignore"]); + + let repo = open_repo(&path); + let filtered_entries = read_children(&repo, root, "", true).unwrap(); + let filtered = names(&filtered_entries); + assert!(!filtered.contains(&"ignored.log")); + assert!(!filtered.contains(&"build")); + assert!(filtered.contains(&"kept.rs")); + + let unfiltered_entries = read_children(&repo, root, "", false).unwrap(); + let unfiltered = names(&unfiltered_entries); + assert!(unfiltered.contains(&"ignored.log")); + assert!(unfiltered.contains(&"build")); + drop(dir); +} diff --git a/src/git/tree/tests/path_security.rs b/src/git/tree/tests/path_security.rs new file mode 100644 index 00000000..bb2ba5ef --- /dev/null +++ b/src/git/tree/tests/path_security.rs @@ -0,0 +1,128 @@ +use super::*; + +#[cfg(unix)] +#[test] +fn read_children_refuses_to_list_the_git_directory() { + let (dir, path) = make_repo(); + let root = StdPath::new(&path); + let repo = open_repo(&path); + + for asked in [".git", ".git/refs", ".GIT", "src/../.git"] { + let err = read_children(&repo, root, asked, false).unwrap_err(); + assert!( + !err.to_string().contains("failed to read directory"), + "{asked:?} must be rejected by validation: {err}" + ); + } + drop(dir); +} + +#[test] +fn read_children_refuses_traversal_that_stays_inside_the_worktree() { + let (dir, path) = make_repo(); + let root = StdPath::new(&path); + std::fs::create_dir(root.join("src")).unwrap(); + let repo = open_repo(&path); + + let err = read_children(&repo, root, "src/..", false).unwrap_err(); + + assert!(err.to_string().contains("plain relative path"), "{err}"); + drop(dir); +} + +#[cfg(unix)] +#[test] +fn read_children_refuses_to_descend_a_symlinked_directory() { + let (dir, path) = make_repo(); + let root = StdPath::new(&path); + std::fs::create_dir(root.join("real_dir")).unwrap(); + std::fs::write(root.join("real_dir/secret.txt"), "x").unwrap(); + std::os::unix::fs::symlink(root.join("real_dir"), root.join("link_dir")).unwrap(); + + let repo = open_repo(&path); + let err = read_children(&repo, root, "link_dir", false).unwrap_err(); + assert!( + err.to_string().contains("symlinks are not followed"), + "{err}" + ); + assert!(read_children(&repo, root, "real_dir", false).is_ok()); + drop(dir); +} + +#[cfg(windows)] +fn junction(link: &StdPath, target: &StdPath) -> bool { + std::process::Command::new("cmd") + .args(["/C", "mklink", "/J"]) + .arg(link) + .arg(target) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .status() + .is_ok_and(|status| status.success()) +} + +#[cfg(windows)] +#[test] +fn read_children_refuses_to_descend_a_junction() { + let (dir, path) = make_repo(); + let root = StdPath::new(&path); + std::fs::create_dir(root.join("real_dir")).unwrap(); + std::fs::write(root.join("real_dir/secret.txt"), "x").unwrap(); + let link = root.join("link_dir"); + if !junction(&link, &root.join("real_dir")) { + eprintln!("skipping junction test: mklink /J failed"); + return; + } + + let repo = open_repo(&path); + let err = read_children(&repo, root, "link_dir", false).unwrap_err(); + assert!( + err.to_string().contains("symlinks are not followed"), + "{err}" + ); + assert!(read_children(&repo, root, "real_dir", false).is_ok()); + drop(dir); +} + +#[cfg(unix)] +#[test] +fn read_children_rejects_escape_through_symlinked_parent() { + let (dir, path) = make_repo(); + let root = StdPath::new(&path); + let outside = tempfile::TempDir::new().unwrap(); + std::fs::create_dir(outside.path().join("sub")).unwrap(); + std::fs::write(outside.path().join("sub/secret.txt"), "x").unwrap(); + std::os::unix::fs::symlink(outside.path(), root.join("link")).unwrap(); + + let repo = open_repo(&path); + let err = read_children(&repo, root, "link/sub", false).unwrap_err(); + assert!( + err.to_string().contains("symlinks are not followed"), + "{err}" + ); + drop(dir); +} + +#[cfg(unix)] +#[test] +fn read_children_reports_symlinked_dir_as_non_dir() { + let (dir, path) = make_repo(); + let root = StdPath::new(&path); + std::fs::create_dir(root.join("real_dir")).unwrap(); + std::os::unix::fs::symlink(root.join("real_dir"), root.join("link_dir")).unwrap(); + + let repo = open_repo(&path); + let entries = read_children(&repo, root, "", false).unwrap(); + + let link = entries + .iter() + .find(|entry| entry.name == "link_dir") + .unwrap(); + let real = entries + .iter() + .find(|entry| entry.name == "real_dir") + .unwrap(); + assert!(!link.is_dir); + assert!(real.is_dir); + drop(dir); +} diff --git a/src/git/tree/tests/search.rs b/src/git/tree/tests/search.rs new file mode 100644 index 00000000..5989a359 --- /dev/null +++ b/src/git/tree/tests/search.rs @@ -0,0 +1,78 @@ +use super::*; + +#[test] +fn search_tree_finds_nested_matches_by_basename() { + let (dir, path) = make_repo(); + let root = StdPath::new(&path); + std::fs::create_dir_all(root.join("src/ui")).unwrap(); + std::fs::write(root.join("src/main.rs"), "x").unwrap(); + std::fs::write(root.join("src/ui/tree_view.rs"), "x").unwrap(); + std::fs::write(root.join("README.md"), "x").unwrap(); + + let repo = open_repo(&path); + let (matches, truncated) = search_tree(&repo, root, "RS", 64, 10_000, 100).unwrap(); + assert_eq!(paths(&matches), vec!["src/main.rs", "src/ui/tree_view.rs"]); + assert!(!truncated); + drop(dir); +} + +#[test] +fn search_tree_excludes_gitignored_and_git_metadata() { + let (dir, path) = make_repo(); + let root = StdPath::new(&path); + std::fs::write(root.join(".gitignore"), "target/\n").unwrap(); + std::fs::create_dir(root.join("target")).unwrap(); + std::fs::write(root.join("target/build.rs"), "x").unwrap(); + std::fs::write(root.join("keep.rs"), "x").unwrap(); + run_git(&path, &["add", ".gitignore"]); + run_git(&path, &["commit", "-m", "add gitignore"]); + + let repo = open_repo(&path); + let (matches, _) = search_tree(&repo, root, ".rs", 64, 10_000, 100).unwrap(); + assert_eq!(paths(&matches), vec!["keep.rs"]); + drop(dir); +} + +#[test] +fn search_tree_stops_descending_beyond_max_depth() { + let (dir, path) = make_repo(); + let root = StdPath::new(&path); + std::fs::create_dir_all(root.join("a/b")).unwrap(); + std::fs::write(root.join("a/shallow.txt"), "x").unwrap(); + std::fs::write(root.join("a/b/deep.txt"), "x").unwrap(); + + let repo = open_repo(&path); + let (matches, _) = search_tree(&repo, root, ".txt", 1, 10_000, 100).unwrap(); + assert_eq!(paths(&matches), vec!["a/shallow.txt"]); + drop(dir); +} + +#[test] +fn search_tree_caps_results_and_flags_truncation() { + let (dir, path) = make_repo(); + let root = StdPath::new(&path); + for i in 0..5 { + std::fs::write(root.join(format!("hit_{i}.txt")), "x").unwrap(); + } + + let repo = open_repo(&path); + let (matches, truncated) = search_tree(&repo, root, "hit_", 64, 10_000, 3).unwrap(); + assert_eq!(paths(&matches), vec!["hit_0.txt", "hit_1.txt", "hit_2.txt"]); + assert!(truncated); + drop(dir); +} + +#[test] +fn search_tree_stops_when_the_visit_budget_is_exhausted() { + let (dir, path) = make_repo(); + let root = StdPath::new(&path); + for i in 0..20 { + std::fs::write(root.join(format!("f_{i:02}.txt")), "x").unwrap(); + } + + let repo = open_repo(&path); + let (matches, truncated) = search_tree(&repo, root, ".txt", 64, 5, 100).unwrap(); + assert!(matches.len() <= 5, "got {} matches", matches.len()); + assert!(truncated); + drop(dir); +} diff --git a/src/input/encode.rs b/src/input/encode.rs index f2a20067..6a281864 100644 --- a/src/input/encode.rs +++ b/src/input/encode.rs @@ -1,7 +1,10 @@ use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseButton}; /// Encode a crossterm KeyEvent as VT100/ANSI bytes for terminal pass-through. -pub fn encode_key(key: KeyEvent) -> Option> { +/// `app_cursor` is the active pane's DECCKM state; it changes unmodified arrow +/// keys from CSI (`ESC [ A`) to SS3 (`ESC O A`). Modified arrows keep xterm's +/// CSI modifier form. +pub fn encode_key(key: KeyEvent, app_cursor: bool) -> Option> { let ctrl = key.modifiers.contains(KeyModifiers::CONTROL); let alt = key.modifiers.contains(KeyModifiers::ALT); @@ -46,10 +49,10 @@ pub fn encode_key(key: KeyEvent) -> Option> { KeyCode::Esc => Some(vec![0x1b]), KeyCode::Tab => Some(vec![b'\t']), KeyCode::BackTab => Some(b"\x1b[Z".to_vec()), - KeyCode::Up => Some(csi_cursor(b'A', key.modifiers)), - KeyCode::Down => Some(csi_cursor(b'B', key.modifiers)), - KeyCode::Right => Some(csi_cursor(b'C', key.modifiers)), - KeyCode::Left => Some(csi_cursor(b'D', key.modifiers)), + KeyCode::Up => Some(cursor_arrow(b'A', key.modifiers, app_cursor)), + KeyCode::Down => Some(cursor_arrow(b'B', key.modifiers, app_cursor)), + KeyCode::Right => Some(cursor_arrow(b'C', key.modifiers, app_cursor)), + KeyCode::Left => Some(cursor_arrow(b'D', key.modifiers, app_cursor)), KeyCode::Home => Some(csi_cursor(b'H', key.modifiers)), KeyCode::End => Some(csi_cursor(b'F', key.modifiers)), KeyCode::PageUp => Some(csi_tilde(5, key.modifiers)), @@ -102,8 +105,7 @@ pub fn encode_button(button: MouseButton, press: bool, col: u16, row: u16) -> Ve /// that DECCKM-enabled programs expect over the default CSI form (`ESC [ A`). pub fn encode_arrow(up: bool, app_cursor: bool) -> Vec { let final_byte = if up { b'A' } else { b'B' }; - let introducer = if app_cursor { b'O' } else { b'[' }; - vec![0x1b, introducer, final_byte] + cursor_arrow(final_byte, KeyModifiers::NONE, app_cursor) } /// xterm modifier parameter for CSI sequences: `1 + (shift=1 | alt=2 | ctrl=4 | @@ -140,6 +142,14 @@ fn csi_cursor(final_byte: u8, mods: KeyModifiers) -> Vec { } } +fn cursor_arrow(final_byte: u8, mods: KeyModifiers, app_cursor: bool) -> Vec { + if mods.is_empty() && app_cursor { + vec![0x1b, b'O', final_byte] + } else { + csi_cursor(final_byte, mods) + } +} + /// Encode a `ESC [ ~` edit key (PageUp/PageDown/Delete), adding the /// `;` parameter when a modifier is held. fn csi_tilde(n: u8, mods: KeyModifiers) -> Vec { diff --git a/src/input/routing.rs b/src/input/routing.rs index ce8d973f..6b209266 100644 --- a/src/input/routing.rs +++ b/src/input/routing.rs @@ -8,10 +8,9 @@ use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; /// across terminals, and Shift+arrow / Shift+PgUp/PgDn carry a modifier. pub fn map_key(event: KeyEvent) -> Action { // Match reserved chords on their EXACT modifier set so any extra modifier - // falls through to the PTY: Shift+arrow must be shift-only (not - // Ctrl+Shift+arrow), and bare F-keys / arrows must carry no modifier at - // all — including Super/Hyper/Meta, so e.g. Super+F3 passes straight - // through instead of triggering a focus jump. + // falls through to the PTY: Shift+arrow must be shift-only, and bare + // F-keys / arrows must carry no modifier at all — including + // Super/Hyper/Meta, so e.g. Super+F3 passes straight through. let shift_only = event.modifiers == KeyModifiers::SHIFT; let no_mods = event.modifiers.is_empty(); @@ -38,14 +37,12 @@ pub fn map_key(event: KeyEvent) -> Action { } } -/// Classify the single follow-up key pressed after the leader. Returns the -/// app `Action`, or `Action::None` for an unmapped follow-up (which the -/// dispatcher consumes and drops). The follow-up is matched on the bare -/// character regardless of modifiers so ` t` works whether or not the -/// user is still holding a modifier from the leader chord. The digit row -/// addresses whatever the body is showing: `1` = file list, `2` = diff -/// viewer, `3`..`9`,`0` = terminal panes `0`..`7`. The bare F-keys are a -/// separate axis (project tabs), so the two never collide. +/// Classify the single follow-up key pressed after the leader. The follow-up +/// is matched on the bare character regardless of modifiers so ` t` works +/// whether or not the user is still holding a modifier from the leader chord. +/// The digit row addresses whatever the body is showing: `1` = file list, +/// `2` = diff viewer, `3`..`9`,`0` = terminal panes `0`..`7`. The bare F-keys +/// are a separate axis (project tabs), so the two never collide. pub fn prefix_action(event: KeyEvent) -> Action { match event.code { KeyCode::Char(c) => match c.to_ascii_lowercase() { @@ -57,15 +54,12 @@ pub fn prefix_action(event: KeyEvent) -> Action { 's' => Action::SwapPanePrompt, 'z' => Action::ClaimPaneSizing, // `c` for cancel. Bare, not `ctrl+c`: the follow-up handler treats - // `ctrl+c` as the universal prefix cancel, and it has to keep doing - // so whatever leader is configured. + // `ctrl+c` as the universal prefix cancel. 'c' => Action::CancelRecovery, 'o' => Action::OpenProject, 'x' => Action::CloseProject, 'p' => Action::CycleTheme, - // `u` for update-from-file. Not `r`, which is already Redraw, and not - // `R` — the follow-up is matched on the bare character (below), so - // upper and lower case cannot be told apart here. + // `u` for update-from-file. Not `r`, which is already Redraw. 'u' => Action::ReloadConfig, 'r' => Action::Redraw, 'q' => Action::Quit, diff --git a/src/input/tests/encode_tests.rs b/src/input/tests/encode_tests.rs index 3b344b0e..a692722e 100644 --- a/src/input/tests/encode_tests.rs +++ b/src/input/tests/encode_tests.rs @@ -63,10 +63,22 @@ fn encode_arrow_follows_application_cursor_mode() { assert_eq!(encode_arrow(false, true), b"\x1bOB".to_vec()); } +#[test] +fn encode_key_uses_application_cursor_mode_for_unmodified_arrows() { + let up = KeyEvent::new(KeyCode::Up, KeyModifiers::NONE); + assert_eq!(super::encode_key(up, true), Some(b"\x1bOA".to_vec())); + + let modified = KeyEvent::new(KeyCode::Up, KeyModifiers::CONTROL); + assert_eq!( + super::encode_key(modified, true), + Some(b"\x1b[1;5A".to_vec()) + ); +} + #[test] fn encode_key_emits_xterm_modifier_sequences() { use KeyModifiers as M; - let enc = |code, mods| encode_key(KeyEvent::new(code, mods)).unwrap(); + let enc = |code, mods| encode_key(KeyEvent::new(code, mods), false).unwrap(); // Unmodified cursor/F-keys keep their legacy sequences. assert_eq!(enc(KeyCode::Up, M::NONE), b"\x1b[A"); @@ -87,31 +99,31 @@ fn encode_key_emits_xterm_modifier_sequences() { #[test] fn encode_printable_char() { - assert_eq!(encode_key(key(KeyCode::Char('a'))), Some(b"a".to_vec())); + assert_eq!( + encode_key(key(KeyCode::Char('a')), false), + Some(b"a".to_vec()) + ); } #[test] fn encode_ctrl_c_as_etx() { - assert_eq!(encode_key(ctrl(KeyCode::Char('c'))), Some(vec![0x03])); + assert_eq!( + encode_key(ctrl(KeyCode::Char('c')), false), + Some(vec![0x03]) + ); } #[test] fn encode_ctrl_non_ascii_does_not_truncate_to_control_byte() { assert_eq!( - encode_key(ctrl(KeyCode::Char('ŀ'))), + encode_key(ctrl(KeyCode::Char('ŀ')), false), Some("ŀ".as_bytes().to_vec()) ); } -#[test] -fn encode_arrow_keys() { - assert_eq!(encode_key(key(KeyCode::Up)), Some(b"\x1b[A".to_vec())); - assert_eq!(encode_key(key(KeyCode::Down)), Some(b"\x1b[B".to_vec())); -} - #[test] fn encode_enter_as_cr() { - assert_eq!(encode_key(key(KeyCode::Enter)), Some(vec![b'\r'])); + assert_eq!(encode_key(key(KeyCode::Enter), false), Some(vec![b'\r'])); } #[test] @@ -119,7 +131,7 @@ fn encode_alt_enter_as_esc_cr() { // A pane program cannot tell "newline" from "submit" if the modifier is // dropped; ESC+CR is the Meta-prefixed form TUIs read as newline. assert_eq!( - encode_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::ALT)), + encode_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::ALT), false), Some(vec![0x1b, b'\r']) ); } @@ -128,7 +140,10 @@ fn encode_alt_enter_as_esc_cr() { fn encode_ctrl_space_as_nul() { // xterm convention: Ctrl+Space → NUL. The generic `c - '@'` formula // wraps for space (0x20 < 0x40), so this case needs special handling. - assert_eq!(encode_key(ctrl(KeyCode::Char(' '))), Some(vec![0x00])); + assert_eq!( + encode_key(ctrl(KeyCode::Char(' ')), false), + Some(vec![0x00]) + ); } #[test] @@ -136,13 +151,19 @@ fn encode_ctrl_slash_as_us() { // Ctrl+/ is conventionally 0x1F (US) on xterm; vim/less/emacs // bindings depend on it. Without the explicit mapping the slash // fell through as a literal '/' character. - assert_eq!(encode_key(ctrl(KeyCode::Char('/'))), Some(vec![0x1F])); + assert_eq!( + encode_key(ctrl(KeyCode::Char('/')), false), + Some(vec![0x1F]) + ); } #[test] fn encode_ctrl_question_as_del() { // Ctrl+? is conventionally DEL (0x7F). - assert_eq!(encode_key(ctrl(KeyCode::Char('?'))), Some(vec![0x7F])); + assert_eq!( + encode_key(ctrl(KeyCode::Char('?')), false), + Some(vec![0x7F]) + ); } #[test] @@ -150,7 +171,10 @@ fn encode_ctrl_right_bracket_via_formula() { // Sanity check: the `c.to_ascii_uppercase() - '@'` formula already // covered Ctrl+]. Pin it so a future refactor of the special-case // table doesn't accidentally regress it. - assert_eq!(encode_key(ctrl(KeyCode::Char(']'))), Some(vec![0x1D])); + assert_eq!( + encode_key(ctrl(KeyCode::Char(']')), false), + Some(vec![0x1D]) + ); } #[test] @@ -160,5 +184,5 @@ fn encode_ctrl_alt_char_prefixes_esc_to_control_byte() { KeyCode::Char('c'), KeyModifiers::CONTROL | KeyModifiers::ALT, ); - assert_eq!(encode_key(key), Some(vec![0x1b, 0x03])); + assert_eq!(encode_key(key, false), Some(vec![0x1b, 0x03])); } diff --git a/src/main.rs b/src/main.rs index 9ed80fea..9d0d0a08 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,9 +9,11 @@ mod config; mod daemon; mod git; mod input; +mod persistence; mod platform; pub mod plugin; mod runtime; +mod session; #[cfg(test)] mod test_util; mod ui; diff --git a/src/persistence.rs b/src/persistence.rs new file mode 100644 index 00000000..a715a7ec --- /dev/null +++ b/src/persistence.rs @@ -0,0 +1,57 @@ +use anyhow::{Context, Result}; +use serde::Serialize; +use serde::de::DeserializeOwned; +use std::io::{BufReader, Write}; +use std::path::Path; + +pub(crate) fn read_json(path: &Path) -> Result> { + let file = match std::fs::File::open(path) { + Ok(file) => file, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None), + Err(err) => return Err(err).with_context(|| format!("opening {}", path.display())), + }; + serde_json::from_reader(BufReader::new(file)) + .map(Some) + .with_context(|| format!("parsing {}", path.display())) +} + +pub(crate) fn write_json(path: &Path, value: &T) -> Result<()> { + let parent = path + .parent() + .filter(|parent| !parent.as_os_str().is_empty()) + .unwrap_or_else(|| Path::new(".")); + std::fs::create_dir_all(parent).with_context(|| format!("creating {}", parent.display()))?; + + let mut pending = tempfile::NamedTempFile::new_in(parent) + .with_context(|| format!("creating a temporary file in {}", parent.display()))?; + serde_json::to_writer(pending.as_file_mut(), value) + .with_context(|| format!("serializing {}", path.display()))?; + pending + .as_file_mut() + .flush() + .with_context(|| format!("flushing {}", path.display()))?; + pending + .persist(path) + .map_err(|err| err.error) + .with_context(|| format!("replacing {}", path.display()))?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn writing_json_replaces_an_existing_file() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("state.json"); + + write_json(&path, &vec!["old"]).unwrap(); + write_json(&path, &vec!["new"]).unwrap(); + + assert_eq!( + read_json::>(&path).unwrap(), + Some(vec!["new".into()]) + ); + } +} diff --git a/src/platform/logging.rs b/src/platform/logging.rs index 1e74c90e..0588af2c 100644 --- a/src/platform/logging.rs +++ b/src/platform/logging.rs @@ -7,8 +7,7 @@ use std::time::{Duration, SystemTime}; use tracing_appender::non_blocking::WorkerGuard; use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt}; -/// The directory nightcrow keeps its own files in, inside a repository. Only -/// a log directory under this one gets an ignore file written into it. +/// The directory nightcrow keeps its own files in, inside a repository. const NIGHTCROW_DIR: &str = ".nightcrow"; const LOG_FILE_PREFIX: &str = "nightcrow.log"; @@ -41,8 +40,7 @@ pub fn init_logging(config: &LogConfig, repo_path: &str) -> Option { let level = config.level.as_str(); // `prompt` is a dedicated tracing target for terminal prompt capture. We // pin it at info regardless of the global level so that enabling - // `prompt_log` always produces output, even when the rest of the app is - // restricted to e.g. "warn". + // `prompt_log` always produces output. let filter_str = if config.prompt_log { format!("{level},prompt=info") } else { @@ -95,15 +93,13 @@ pub fn init_logging(config: &LogConfig, repo_path: &str) -> Option { /// Drop a self-ignoring `.gitignore` in the log directory so logs never /// pollute the user's `git status` — the default `.nightcrow/logs` sits inside -/// the repo. A directory whose whole contents are ignored is not reported as -/// untracked, so this covers the `.nightcrow/` wrapper too. +/// the repo. /// /// Only into a directory nightcrow owns, meaning one under `.nightcrow`. The /// pattern is `*`, which has to ignore the ignore file itself to hide the /// directory — harmless in our own folder, but writing that into a directory -/// the user pointed `[log] dir` at (their repo root, a shared logs folder) -/// would make Git ignore everything untracked there, their own `.gitignore` -/// included. A custom location is the user's to manage. +/// the user pointed `[log] dir` at would make Git ignore everything untracked +/// there. A custom location is the user's to manage. /// /// Only written when missing: a user-edited file should not be clobbered. fn write_log_gitignore(log_dir: &Path) { @@ -142,8 +138,7 @@ fn cleanup_old_logs(log_dir: &Path, max_days: u32) { // First pass: collect candidate files with mtimes so we can identify the // newest one and preserve it. SizeRollingAppender resumes its highest // existing index on startup, so the latest log file may itself be older - // than the cutoff if write rate is low — deleting it would lose the - // active session's tail. + // than the cutoff — deleting it would lose the active session's tail. let mut candidates: Vec<(PathBuf, SystemTime)> = Vec::new(); for entry in entries.flatten() { let path = entry.path(); diff --git a/src/platform/paths.rs b/src/platform/paths.rs index bf666370..12defb0b 100644 --- a/src/platform/paths.rs +++ b/src/platform/paths.rs @@ -3,6 +3,11 @@ use std::borrow::Cow; use std::path::{Path, PathBuf}; +#[cfg(windows)] +use std::ffi::OsString; +#[cfg(windows)] +use std::path::{Component, Prefix}; + /// Expand a leading `~` to the user's home directory. /// /// Paths typed inside the TUI never pass through a shell, so `~/work` would @@ -24,24 +29,21 @@ pub(crate) fn expand_tilde(path: impl AsRef) -> PathBuf { /// Strip the verbatim prefix and normalise separators for display. /// -/// Storage and comparison use the canonical form as-is. Mixing them would -/// silently break `starts_with`-based boundary checks — git/path's worktree -/// gate depends on exactly that comparison. +/// Filesystem use stays in `Path` space. Mixing display normalization into it +/// would silently break `starts_with`-based boundary checks — git/path's +/// worktree gate depends on exactly that comparison. pub(crate) fn for_display(path: &Path) -> Cow<'_, str> { - let s = path.to_string_lossy(); - // `\\\\?\\` is the verbatim prefix Windows prepends to canonicalized paths. - // Strip it so the user sees `C:\Users\...` instead of `\\?\C:\Users\...`. - // Backslashes are also normalised to forward slashes so display paths are - // consistent across platforms — the browser client and TUI both show `/`. #[cfg(windows)] { - let stripped = s.strip_prefix(r"\\?\").unwrap_or(&s); - let normalized = stripped.replace('\\', "/"); - Cow::Owned(normalized) + // Convert the prefix while this is still a `Path`: going through a + // string here would corrupt non-Unicode components, and verbatim UNC + // needs to become `\\server\share`, not `UNC\server\share`. + let clean = without_verbatim_prefix(path); + Cow::Owned(clean.to_string_lossy().replace('\\', "/")) } #[cfg(not(windows))] { - s + path.to_string_lossy() } } @@ -56,8 +58,7 @@ pub(crate) fn canonicalize_clean(path: impl AsRef) -> std::io::Result) -> std::io::Result std::borrow::Cow<'_, str> { - if let Some(share) = path.strip_prefix(r"\\?\UNC\") { - return std::borrow::Cow::Owned(format!(r"\\{share}")); - } - match path.strip_prefix(r"\\?\") { - Some(rest) => std::borrow::Cow::Borrowed(rest), - None => std::borrow::Cow::Borrowed(path), +/// The conversion stays in `OsStr`/`Path` space so every UTF-16 code unit is +/// preserved. Other device/verbatim namespaces are left untouched: inventing +/// a non-verbatim spelling for them would change which object they name. +#[cfg(windows)] +fn without_verbatim_prefix(path: &Path) -> Cow<'_, Path> { + let mut components = path.components(); + let Some(Component::Prefix(prefix)) = components.next() else { + return Cow::Borrowed(path); + }; + + let mut clean = match prefix.kind() { + Prefix::VerbatimDisk(drive) => PathBuf::from(format!("{}:\\", char::from(drive))), + Prefix::VerbatimUNC(server, share) => { + let mut root = OsString::from(r"\\"); + root.push(server); + root.push(r"\"); + root.push(share); + PathBuf::from(root) + } + _ => return Cow::Borrowed(path), + }; + + for component in components { + if component != Component::RootDir { + clean.push(component.as_os_str()); + } } + Cow::Owned(clean) } /// The directory a relative state path — the log directory, chiefly — is @@ -133,25 +145,36 @@ mod tests { fn expand_tilde_leaves_a_user_qualified_tilde_alone() { assert_eq!(expand_tilde("~other/x"), PathBuf::from("~other/x")); } - #[test] - fn a_verbatim_drive_path_loses_only_its_prefix() { - assert_eq!(strip_verbatim_prefix(r"\\?\C:\code\app"), r"C:\code\app"); - } + #[cfg(windows)] #[test] - fn a_verbatim_share_stays_a_share() { - // Taking `\\?\` off alone leaves `UNC\server\share`, which is relative: - // it names no share, cannot be served, and cannot be a working - // directory. The share form has to be put back. + fn verbatim_drive_and_unc_paths_keep_their_native_roots() { + assert_eq!( + without_verbatim_prefix(Path::new(r"\\?\C:\Users\dev\repo")), + Path::new(r"C:\Users\dev\repo") + ); + assert_eq!( + without_verbatim_prefix(Path::new(r"\\?\UNC\server\share\repo")), + Path::new(r"\\server\share\repo") + ); assert_eq!( - strip_verbatim_prefix(r"\\?\UNC\server\share\project"), - r"\\server\share\project" + for_display(Path::new(r"\\?\UNC\server\share\repo")), + "//server/share/repo" ); } + #[cfg(windows)] #[test] - fn a_path_without_the_prefix_is_left_alone() { - assert_eq!(strip_verbatim_prefix(r"C:\code\app"), r"C:\code\app"); - assert_eq!(strip_verbatim_prefix("/home/x/code"), "/home/x/code"); + fn removing_a_verbatim_prefix_does_not_lossily_decode_components() { + use std::os::windows::ffi::OsStringExt; + + let undecodable = OsString::from_wide(&[b'r' as u16, 0xD800, b'p' as u16]); + let mut verbatim = PathBuf::from(r"\\?\C:\"); + verbatim.push(&undecodable); + let mut expected = PathBuf::from(r"C:\"); + expected.push(&undecodable); + + let clean = without_verbatim_prefix(&verbatim).into_owned(); + assert_eq!(clean, expected); } } diff --git a/src/platform/signals.rs b/src/platform/signals.rs index 8d8890d6..951104cb 100644 --- a/src/platform/signals.rs +++ b/src/platform/signals.rs @@ -17,7 +17,6 @@ pub enum Shutdown { /// Windows has no SIGTERM equivalent — the console control handler only /// produces `Interrupt`. This variant is still constructed on Unix and may /// be used by the `nightcrow stop` protocol path on both platforms. - #[cfg_attr(windows, allow(dead_code))] Terminate, } @@ -63,7 +62,37 @@ mod imp { mod imp { use super::Shutdown; use anyhow::{Context, Result}; - use std::sync::mpsc::{Receiver, sync_channel}; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::mpsc::{Receiver, SyncSender, sync_channel}; + + const INTERRUPT_EXIT_CODE: i32 = 130; + + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + enum InterruptAction { + Notify, + Exit, + } + + fn next_interrupt(seen: &AtomicBool) -> InterruptAction { + if seen.swap(true, Ordering::AcqRel) { + InterruptAction::Exit + } else { + InterruptAction::Notify + } + } + + fn handle_interrupt(seen: &AtomicBool, tx: &SyncSender) { + match next_interrupt(seen) { + InterruptAction::Notify => { + let _ = tx.try_send(Shutdown::Interrupt); + } + // `ctrlc` keeps its Windows console handler installed after our + // receiver is consumed. Returning here would swallow every later + // Ctrl-C, so a second event is the explicit escape from a wedged + // graceful shutdown. + InterruptAction::Exit => std::process::exit(INTERRUPT_EXIT_CODE), + } + } /// Windows 는 시그널 대신 콘솔 제어 이벤트를 쓴다. 콜백을 채널로 옮겨 /// register/wait 분리를 Unix 와 동일하게 유지한다 — 등록 시점부터 도착한 @@ -76,12 +105,10 @@ mod imp { impl Watch { pub(super) fn register() -> Result { - // 깊이 1: 두 번째 Ctrl-C 는 기본 처분(즉시 종료)에 맡긴다. - // 셧다운이 스스로 멈춰버린 경우의 탈출구가 되어야 한다 — - // Unix 쪽 `wait` 가 self 를 consume 하는 것과 같은 의도. let (tx, rx) = sync_channel(1); + let seen = AtomicBool::new(false); ctrlc::set_handler(move || { - let _ = tx.try_send(Shutdown::Interrupt); + handle_interrupt(&seen, &tx); }) .context("installing the console control handler")?; Ok(Self(rx)) @@ -93,6 +120,16 @@ mod imp { .context("the console control handler went away") } } + + #[cfg(test)] + pub(super) fn hard_exit_after_two_interrupts_for_test() -> ! { + let seen = AtomicBool::new(false); + let (tx, rx) = sync_channel(1); + handle_interrupt(&seen, &tx); + assert_eq!(rx.try_recv(), Ok(Shutdown::Interrupt)); + handle_interrupt(&seen, &tx); + unreachable!("the second interrupt exits the process") + } } /// Handlers for the stop signals, installed and listening. @@ -114,15 +151,20 @@ impl ShutdownWatch { /// Block until a stop signal has arrived — including one that arrived /// before this call, which is held from registration onward. /// - /// Consumes the watch: the handlers come down with it, so a second stop - /// signal after this returns takes its default disposition. That is - /// deliberate — Ctrl-C during a shutdown that has itself wedged should - /// still end the process. + /// Consumes the watch after the first stop request. On Windows the `ctrlc` + /// crate keeps its process-global handler installed, so that handler tracks + /// the first event and hard-exits on the second instead of swallowing it. + /// This keeps Ctrl-C as an escape from a shutdown that has itself wedged. pub fn wait(self) -> Result { self.0.wait() } } +#[cfg(all(test, windows))] +pub(super) fn windows_hard_exit_after_two_interrupts_for_test() -> ! { + imp::hard_exit_after_two_interrupts_for_test() +} + #[cfg(test)] #[path = "signals_tests.rs"] mod tests; diff --git a/src/platform/signals_tests.rs b/src/platform/signals_tests.rs index 7c970b4b..54553021 100644 --- a/src/platform/signals_tests.rs +++ b/src/platform/signals_tests.rs @@ -1,5 +1,8 @@ use super::Shutdown; +#[cfg(windows)] +const SECOND_INTERRUPT_CHILD: &str = "NIGHTCROW_TEST_SECOND_INTERRUPT_CHILD"; + // `as_str` / Shutdown 의 Eq 같은 순수 부분은 양쪽에서 돈다. // 이벤트 전달 자체는 Unix 에서만 검증한다: Windows 의 콘솔 제어 이벤트는 // 프로세스 그룹 전체로 가므로 테스트 러너를 함께 죽인다. @@ -68,3 +71,24 @@ fn each_stop_signal_names_itself() { assert_eq!(Shutdown::Interrupt.as_str(), "SIGINT"); assert_eq!(Shutdown::Terminate.as_str(), "SIGTERM"); } + +#[cfg(windows)] +#[test] +fn windows_interrupts_notify_once_then_hard_exit() { + if std::env::var_os(SECOND_INTERRUPT_CHILD).is_some() { + super::windows_hard_exit_after_two_interrupts_for_test(); + } + + let status = std::process::Command::new(std::env::current_exe().expect("current test binary")) + .args([ + "--exact", + "platform::signals::tests::windows_interrupts_notify_once_then_hard_exit", + ]) + .env(SECOND_INTERRUPT_CHILD, "1") + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .status() + .expect("run the isolated hard-exit contract"); + + assert_eq!(status.code(), Some(130)); +} diff --git a/src/plugin/guard.rs b/src/plugin/guard.rs index 9fde4e3e..2e9359ef 100644 --- a/src/plugin/guard.rs +++ b/src/plugin/guard.rs @@ -3,8 +3,7 @@ //! [`decode_command`](super::protocol::decode_command) checked shape and bounds; //! it never checked authority. Everything a plugin asks for passes through //! [`Guard::judge`], which returns either an [`Approved`] action naming a real -//! [`PaneId`] or a [`Refused`] saying why not. There is no other way through: a -//! [`PluginCommand`] carries a token, and only the guard turns one into a pane. +//! [`PaneId`] or a [`Refused`] saying why not. There is no other way through. //! //! That includes gaining a pane in the first place: `guard_watch` beside this //! file holds the only rule that can widen what a plugin is allowed to see. @@ -34,8 +33,7 @@ pub struct PaneFacts { /// [`PluginCommand::WatchPane`] differently. pub watched_by_another: bool, /// The requesting plugin's `watch_on_signal`: whether the operator allowed - /// it to be given a pane it was never named by. A property of the plugin - /// rather than of the pane, carried here so the rules stay one struct wide. + /// it to be given a pane it was never named by. pub may_watch_on_signal: bool, /// The pane's process is still running. pub alive: bool, diff --git a/src/plugin/guard_tests/relaunch.rs b/src/plugin/guard_tests/relaunch.rs index 674850a2..b8ef0e53 100644 --- a/src/plugin/guard_tests/relaunch.rs +++ b/src/plugin/guard_tests/relaunch.rs @@ -78,7 +78,7 @@ fn a_relaunch_with_an_allowed_flag_carries_the_built_command_line() { Approved::Relaunch { pane: PANE, resume_args: vec!["--resume".to_string(), "abc123".to_string()], - command_line: format!("{LAUNCH} '--resume' 'abc123'"), + command_line: format!("{LAUNCH} --resume abc123"), } ); } diff --git a/src/plugin/host.rs b/src/plugin/host.rs index f220c6c1..3c01a5bb 100644 --- a/src/plugin/host.rs +++ b/src/plugin/host.rs @@ -1,13 +1,9 @@ //! One long-lived child process per enabled plugin. //! -//! The host owns the process and three pump threads (see -//! [`host_pump`](super::host_pump)) and exposes only two non-blocking +//! The host owns the process and three pump threads and exposes two non-blocking //! operations: queue an event, take a decoded command. Nothing a plugin does can -//! make either of those block, because the terminal hub calls them on the thread -//! that also serves every pane. -//! -//! A plugin child is not one of `PtyBackend`'s panes, so [`PluginHost`] is the -//! only place it is ever reaped — hence `shutdown` running from `Drop` too. +//! make either block — the terminal hub calls them on the thread that also +//! serves every pane. use super::host_pump; use super::protocol::{PROTOCOL_VERSION, PluginCommand, PluginEvent, encode_event}; diff --git a/src/plugin/protocol.rs b/src/plugin/protocol.rs index 3b59f1c9..293e1aa7 100644 --- a/src/plugin/protocol.rs +++ b/src/plugin/protocol.rs @@ -3,42 +3,31 @@ //! Newline-delimited JSON over the plugin child's stdin and stdout: the host //! writes one [`PluginEvent`] per line, the plugin writes one //! [`PluginCommand`] per line. A line is the framing, so nothing here may emit -//! an embedded newline — [`encode_event`] refuses rather than corrupt the -//! stream. +//! an embedded newline. //! -//! Unlike the daemon's control protocol, the two sides here are separate -//! builds: a plugin is written against a version of this contract and shipped -//! independently. That makes [`PROTOCOL_VERSION`] a real negotiation, and a -//! mismatch is refused rather than half-understood. +//! Unlike the daemon's control protocol, the two sides are separate builds: a +//! plugin is shipped independently against a version of this contract. That +//! makes [`PROTOCOL_VERSION`] a real negotiation — a mismatch is refused. use crate::backend::{PaneGeneration, PaneToken}; use anyhow::{Result, anyhow, bail}; use serde::{Deserialize, Serialize}; /// Bumped when a field changes meaning. A plugin built against a version the -/// host does not speak is refused rather than half-understood. +/// host does not speak is refused. /// -/// 2 added [`PluginCommand::WatchPane`]. A version-1 plugin cannot ask for a -/// pane it was never named by, so an old build talking to this host would be -/// silently less capable rather than wrong — the bump is what makes that -/// difference loud instead. +/// 2 added [`PluginCommand::WatchPane`]. pub const PROTOCOL_VERSION: u32 = 2; -/// Longest line the host will read from a plugin. -/// -/// The plugin is untrusted input on a stream with no length prefix, so without -/// a cap a plugin that never writes a newline makes the host's reader allocate -/// without bound. 64 KiB is far above any legitimate command — the largest is a -/// [`PluginCommand::SendInput`] bounded by [`MAX_INPUT_BYTES`] plus JSON -/// escaping and a token — while still being a single small allocation. +/// Longest line the host will read from a plugin. Without a cap a plugin that +/// never writes a newline makes the host's reader allocate without bound. pub const MAX_LINE_BYTES: usize = 64 * 1024; /// Longest `data` a single [`PluginCommand::SendInput`] may carry. /// -/// Typed input stands in for a human at a keyboard: a prompt, a confirmation, -/// a pasted snippet. 8 KiB covers that with room to spare and keeps one command -/// from filling a PTY's input buffer, which would block the writer and stall -/// every other pane behind it. +/// Typed input stands in for a human at a keyboard. 8 KiB covers that and keeps +/// one command from filling a PTY's input buffer, which would stall every other +/// pane behind it. pub const MAX_INPUT_BYTES: usize = 8 * 1024; /// Sentinel [`decode_command`] answers a blank line with. Prefer diff --git a/src/plugin/registry.rs b/src/plugin/registry.rs index 9ff5ca1e..d06f43d1 100644 --- a/src/plugin/registry.rs +++ b/src/plugin/registry.rs @@ -1,52 +1,40 @@ -//! The on-disk set of installed plugin executables (`~/.nightcrow/plugins`). +//! Installed plugin executables and their explicit config state. //! -//! Installing a binary here does not switch it on: the host only ever launches -//! a plugin that `config.toml` declares in a `[[plugin]]` table *and* that some -//! pane can reach — either a `[[startup_command]]` opted one in by name, or -//! `watch_on_signal` lets a provider signal name one. That edit is left to the -//! user on purpose — a plugin can drive a pane's terminal, so the file that -//! grants it that has to be one a person read. -//! -//! Every function takes the plugins directory as a parameter so the filesystem -//! behaviour is testable against a temp directory. +//! Installation only places a binary in `~/.nightcrow/plugins`. A plugin stays +//! inert until the user declares it in config and opts a pane into it. -use anyhow::{Context, Result}; -use std::path::{Path, PathBuf}; +use anyhow::Result; +use std::path::PathBuf; -/// Subdirectory of `~/.nightcrow` holding installed plugin executables. -const PLUGINS_SUBDIR: &str = "plugins"; -/// Owner-only read/write/execute for an installed plugin. Least privilege, and -/// the same posture the config file's 0600 takes: nothing else on the machine -/// has business running a binary nightcrow will attach to a terminal. -#[cfg(unix)] -const PLUGIN_MODE: u32 = 0o700; -/// Longest accepted plugin name. -const MAX_NAME_LEN: usize = 64; +#[cfg(test)] +use std::path::Path; -/// `~/.nightcrow/plugins`, resolved whether or not it exists yet. Errors only -/// when the home directory cannot be determined. -pub fn default_plugins_dir() -> Result { - let home = dirs::home_dir().context("cannot determine the home directory")?; - Ok(home.join(".nightcrow").join(PLUGINS_SUBDIR)) -} +mod config; +mod executable; +mod storage; + +pub use config::{config_snippet, status}; +pub use storage::{default_plugins_dir, install, list, remove}; -/// Result of [`install`], so the caller can report exactly what was touched. +const MAX_NAME_LEN: usize = 64; + +/// Result of placing a plugin executable in the registry. #[derive(Debug)] pub enum InstallOutcome { Created(PathBuf), Replaced(PathBuf), - /// A plugin of that name was already installed and `force` was not set. + /// The destination existed and replacement was not requested. AlreadyExists(PathBuf), } -/// Result of [`remove`]. Removing something absent is a report, not a failure. +/// Result of removing a plugin; absence is a report, not an error. #[derive(Debug)] pub enum RemoveOutcome { Removed(PathBuf), NotInstalled(String), } -/// How `config.toml` currently refers to an installed plugin. +/// How the loaded config refers to an installed plugin. #[derive(Debug, PartialEq, Eq)] pub struct PluginStatus { pub declared: bool, @@ -55,13 +43,7 @@ pub struct PluginStatus { pub opt_ins: usize, } -/// Accept only a safe single filename. -/// -/// This is the path-traversal boundary: the name is joined onto the plugins -/// directory and then written to and deleted, so anything that could escape -/// that directory — a separator, `.`/`..` — is refused here rather than -/// sanitised. A leading `-` is refused too, because such a file name is read as -/// a flag by every command the user might later point at it. +/// Enforce the single-filename boundary used by install and remove. pub fn validate_name(name: &str) -> Result<()> { anyhow::ensure!(!name.is_empty(), "a plugin name must not be empty"); anyhow::ensure!( @@ -82,236 +64,12 @@ pub fn validate_name(name: &str) -> Result<()> { ); anyhow::ensure!( name.bytes() - .all(|b| b.is_ascii_alphanumeric() || b == b'.' || b == b'_' || b == b'-'), + .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-')), "plugin name \"{name}\" may only contain letters, digits, '.', '_' and '-'" ); Ok(()) } -/// Copy the executable at `source` into `base` under `name` (defaulting to the -/// source file stem) and restrict it to owner-only. -pub fn install( - base: &Path, - source: &Path, - name: Option<&str>, - force: bool, -) -> Result { - let name = match name { - Some(name) => name.to_string(), - None => derive_name(source)?, - }; - validate_name(&name)?; - check_source(source)?; - - let dest = base.join(&name); - let installed = dest.symlink_metadata().is_ok(); - if installed && !force { - return Ok(InstallOutcome::AlreadyExists(dest)); - } - std::fs::create_dir_all(base) - .with_context(|| format!("creating plugin directory {}", base.display()))?; - // Unlink rather than copy over: truncating a binary that is currently - // running fails with ETXTBSY, and a fresh inode leaves any live process - // on the old file. - if installed { - std::fs::remove_file(&dest) - .with_context(|| format!("replacing installed plugin {}", dest.display()))?; - } - std::fs::copy(source, &dest) - .with_context(|| format!("copying plugin {} to {}", source.display(), dest.display()))?; - restrict_permissions(&dest)?; - Ok(if installed { - InstallOutcome::Replaced(dest) - } else { - InstallOutcome::Created(dest) - }) -} - -/// Installed plugin names, sorted. A missing directory lists as empty — that is -/// the state before the first install, not an error. -pub fn list(base: &Path) -> Result> { - let entries = match std::fs::read_dir(base) { - Ok(entries) => entries, - Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()), - Err(e) => { - return Err(e).with_context(|| format!("reading plugin directory {}", base.display())); - } - }; - let mut names = Vec::new(); - for entry in entries { - let entry = - entry.with_context(|| format!("reading plugin directory {}", base.display()))?; - // Follows symlinks, so a hand-linked plugin still lists. - if !std::fs::metadata(entry.path()).is_ok_and(|m| m.is_file()) { - continue; - } - let raw = entry.file_name().to_string_lossy().into_owned(); - // On Windows, strip the `.exe` extension so the config references the - // bare name. The extension is re-attached after validation in `remove` - // and `resolve_program`. - #[cfg(windows)] - let name = raw - .strip_suffix(".exe") - .map(|s| s.to_string()) - .unwrap_or(raw); - #[cfg(not(windows))] - let name = raw; - names.push(name); - } - names.sort(); - Ok(names) -} - -/// Delete an installed plugin. -pub fn remove(base: &Path, name: &str) -> Result { - validate_name(name)?; - let path = resolve_installed_path(base, name); - if path.symlink_metadata().is_err() { - return Ok(RemoveOutcome::NotInstalled(name.to_string())); - } - std::fs::remove_file(&path) - .with_context(|| format!("removing installed plugin {}", path.display()))?; - Ok(RemoveOutcome::Removed(path)) -} - -/// Resolve a validated name to the on-disk path, attaching `.exe` on Windows -/// when the bare name does not exist. Called after `validate_name` so the -/// extension cannot be used to escape the plugins directory. -fn resolve_installed_path(base: &Path, name: &str) -> PathBuf { - let bare = base.join(name); - if bare.symlink_metadata().is_ok() { - return bare; - } - #[cfg(windows)] - { - let exe = base.join(format!("{name}.exe")); - if exe.symlink_metadata().is_ok() { - return exe; - } - } - bare -} - -/// What the loaded config says about `name`. -pub fn status(cfg: &crate::config::Config, name: &str) -> PluginStatus { - let declared = cfg.plugins.iter().find(|p| p.name == name); - PluginStatus { - declared: declared.is_some(), - enabled: declared.is_some_and(|p| p.enabled), - opt_ins: cfg - .startup_commands - .iter() - .filter(|sc| sc.plugin.as_deref() == Some(name)) - .count(), - } -} - -/// The `[[plugin]]` block to paste into `config.toml` after an install. -/// -/// Printed, never written: enabling a plugin hands it a pane's terminal, so the -/// opt-in stays an explicit edit the user reviews. `enabled = false` and an -/// empty `allowed_resume_flags` are the off positions of both switches. -pub fn config_snippet(name: &str, command: &Path) -> String { - format!( - "[[plugin]]\n\ - name = \"{name}\"\n\ - command = \"{}\"\n\ - args = []\n\ - allowed_resume_flags = []\n\ - enabled = false\n", - toml_escape(&command.display().to_string()) - ) -} - -fn toml_escape(value: &str) -> String { - value.replace('\\', "\\\\").replace('"', "\\\"") -} - -fn derive_name(source: &Path) -> Result { - source - .file_stem() - .map(|stem| stem.to_string_lossy().into_owned()) - .ok_or_else(|| { - anyhow::anyhow!( - "cannot derive a plugin name from {}; pass --name", - source.display() - ) - }) -} - -fn check_source(source: &Path) -> Result<()> { - let meta = std::fs::symlink_metadata(source).with_context(|| { - format!( - "plugin source {} cannot be read; it must be an existing executable file", - source.display() - ) - })?; - let meta = if meta.file_type().is_symlink() { - std::fs::metadata(source) - .with_context(|| format!("plugin source {} is a broken symlink", source.display()))? - } else { - meta - }; - anyhow::ensure!( - meta.is_file(), - "plugin source {} is not a regular file", - source.display() - ); - anyhow::ensure!( - is_executable(source), - "plugin source {} is not executable by the current user; chmod +x it first", - source.display() - ); - Ok(()) -} - -#[cfg(unix)] -fn is_executable(path: &Path) -> bool { - use std::os::unix::ffi::OsStrExt; - let Ok(c_path) = std::ffi::CString::new(path.as_os_str().as_bytes()) else { - return false; - }; - // access(2) rather than a permission-bit test: it answers the question that - // matters — whether *this* user may execute it — for owner, group and other - // in one call. - unsafe { libc::access(c_path.as_ptr(), libc::X_OK) == 0 } -} - -#[cfg(not(unix))] -fn is_executable(path: &Path) -> bool { - // Windows: check the extension against the PATHEXT list. A file without - // one of these extensions will not be launched by `Command::new`, so - // accepting it would let the user install a non-executable and wonder why - // it never starts. - let Some(ext) = path.extension().and_then(|e| e.to_str()) else { - return false; - }; - let pathext = std::env::var_os("PATHEXT").unwrap_or_else(|| { - std::ffi::OsString::from(".EXE;.CMD;.BAT;.COM;.PS1;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC") - }); - // `path.extension()` returns the extension without the leading dot, but - // PATHEXT entries include it. Prepend a dot so the comparison matches. - let ext_dotted = format!(".{}", ext.to_ascii_uppercase()); - pathext - .to_str() - .map(|s| s.split(';').any(|e| e.eq_ignore_ascii_case(&ext_dotted))) - .unwrap_or(false) -} - -fn restrict_permissions(path: &Path) -> Result<()> { - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - std::fs::set_permissions(path, std::fs::Permissions::from_mode(PLUGIN_MODE)) - .with_context(|| format!("restricting permissions on {}", path.display()))?; - } - #[cfg(not(unix))] - { - let _ = path; - } - Ok(()) -} - #[cfg(test)] #[path = "registry_tests.rs"] mod tests; diff --git a/src/plugin/registry/config.rs b/src/plugin/registry/config.rs new file mode 100644 index 00000000..c77eab4e --- /dev/null +++ b/src/plugin/registry/config.rs @@ -0,0 +1,50 @@ +use std::path::Path; + +use super::PluginStatus; + +/// What the loaded config says about an installed plugin. +pub fn status(cfg: &crate::config::Config, name: &str) -> PluginStatus { + let declared = cfg.plugins.iter().find(|plugin| plugin.name == name); + PluginStatus { + declared: declared.is_some(), + enabled: declared.is_some_and(|plugin| plugin.enabled), + opt_ins: cfg + .startup_commands + .iter() + .filter(|command| command.plugin.as_deref() == Some(name)) + .count(), + } +} + +/// A disabled `[[plugin]]` block for the user to review and paste. +pub fn config_snippet(name: &str, command: &Path) -> String { + let name = toml::Value::String(name.to_string()); + let command = toml::Value::String(command.display().to_string()); + format!( + "[[plugin]]\n\ + name = {name}\n\ + command = {command}\n\ + args = []\n\ + allowed_resume_flags = []\n\ + enabled = false\n" + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn config_snippet_uses_toml_string_encoding() { + let name = "watcher\nquoted\""; + let command = Path::new("plugins/line\nbreak\\\"watcher"); + let snippet = config_snippet(name, command); + let parsed: toml::Value = toml::from_str(&snippet).expect("valid TOML"); + + assert_eq!(parsed["plugin"][0]["name"].as_str(), Some(name)); + assert_eq!( + parsed["plugin"][0]["command"].as_str(), + Some(command.to_string_lossy().as_ref()) + ); + } +} diff --git a/src/plugin/registry/executable.rs b/src/plugin/registry/executable.rs new file mode 100644 index 00000000..9da15b8d --- /dev/null +++ b/src/plugin/registry/executable.rs @@ -0,0 +1,71 @@ +use anyhow::{Context, Result}; +use std::path::Path; + +#[cfg(unix)] +const PLUGIN_MODE: u32 = 0o700; + +pub(super) fn validate_source(source: &Path) -> Result<()> { + let metadata = std::fs::symlink_metadata(source).with_context(|| { + format!( + "plugin source {} cannot be read; it must be an existing executable file", + source.display() + ) + })?; + let metadata = if metadata.file_type().is_symlink() { + std::fs::metadata(source) + .with_context(|| format!("plugin source {} is a broken symlink", source.display()))? + } else { + metadata + }; + anyhow::ensure!( + metadata.is_file(), + "plugin source {} is not a regular file", + source.display() + ); + anyhow::ensure!( + is_executable(source), + "plugin source {} is not executable by the current user; chmod +x it first", + source.display() + ); + Ok(()) +} + +#[cfg(unix)] +fn is_executable(path: &Path) -> bool { + use std::os::unix::ffi::OsStrExt; + + let Ok(path) = std::ffi::CString::new(path.as_os_str().as_bytes()) else { + return false; + }; + // access(2) accounts for the current user's owner, group, and other bits. + unsafe { libc::access(path.as_ptr(), libc::X_OK) == 0 } +} + +#[cfg(not(unix))] +fn is_executable(path: &Path) -> bool { + let Some(extension) = path.extension().and_then(|extension| extension.to_str()) else { + return false; + }; + let pathext = std::env::var_os("PATHEXT").unwrap_or_else(|| { + std::ffi::OsString::from(".EXE;.CMD;.BAT;.COM;.PS1;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC") + }); + let extension = format!(".{}", extension.to_ascii_uppercase()); + pathext.to_str().is_some_and(|entries| { + entries + .split(';') + .any(|entry| entry.eq_ignore_ascii_case(&extension)) + }) +} + +pub(super) fn restrict_permissions(path: &Path) -> Result<()> { + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + + std::fs::set_permissions(path, std::fs::Permissions::from_mode(PLUGIN_MODE)) + .with_context(|| format!("restricting permissions on {}", path.display()))?; + } + #[cfg(not(unix))] + let _ = path; + Ok(()) +} diff --git a/src/plugin/registry/storage.rs b/src/plugin/registry/storage.rs new file mode 100644 index 00000000..552fadd5 --- /dev/null +++ b/src/plugin/registry/storage.rs @@ -0,0 +1,122 @@ +use anyhow::{Context, Result}; +use std::path::{Path, PathBuf}; + +use super::executable::{restrict_permissions, validate_source}; +use super::{InstallOutcome, RemoveOutcome, validate_name}; + +const PLUGINS_SUBDIR: &str = "plugins"; + +/// Resolve `~/.nightcrow/plugins` without creating it. +pub fn default_plugins_dir() -> Result { + let home = dirs::home_dir().context("cannot determine the home directory")?; + Ok(home.join(".nightcrow").join(PLUGINS_SUBDIR)) +} + +/// Copy an executable into the registry and restrict it to the owner. +pub fn install( + base: &Path, + source: &Path, + name: Option<&str>, + force: bool, +) -> Result { + let name = match name { + Some(name) => name.to_string(), + None => derive_name(source)?, + }; + validate_name(&name)?; + validate_source(source)?; + + let destination = base.join(&name); + let installed = destination.symlink_metadata().is_ok(); + if installed && !force { + return Ok(InstallOutcome::AlreadyExists(destination)); + } + std::fs::create_dir_all(base) + .with_context(|| format!("creating plugin directory {}", base.display()))?; + // A new inode avoids ETXTBSY when the installed binary is still running. + if installed { + std::fs::remove_file(&destination) + .with_context(|| format!("replacing installed plugin {}", destination.display()))?; + } + std::fs::copy(source, &destination).with_context(|| { + format!( + "copying plugin {} to {}", + source.display(), + destination.display() + ) + })?; + restrict_permissions(&destination)?; + + Ok(if installed { + InstallOutcome::Replaced(destination) + } else { + InstallOutcome::Created(destination) + }) +} + +/// Installed plugin names in sorted order; a missing registry is empty. +pub fn list(base: &Path) -> Result> { + let entries = match std::fs::read_dir(base) { + Ok(entries) => entries, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()), + Err(error) => { + return Err(error) + .with_context(|| format!("reading plugin directory {}", base.display())); + } + }; + let mut names = Vec::new(); + for entry in entries { + let entry = + entry.with_context(|| format!("reading plugin directory {}", base.display()))?; + if !std::fs::metadata(entry.path()).is_ok_and(|metadata| metadata.is_file()) { + continue; + } + let raw = entry.file_name().to_string_lossy().into_owned(); + #[cfg(windows)] + let name = raw.strip_suffix(".exe").map(str::to_string).unwrap_or(raw); + #[cfg(not(windows))] + let name = raw; + names.push(name); + } + names.sort(); + Ok(names) +} + +/// Remove a plugin or report that it was not installed. +pub fn remove(base: &Path, name: &str) -> Result { + validate_name(name)?; + let path = resolve_installed_path(base, name); + if path.symlink_metadata().is_err() { + return Ok(RemoveOutcome::NotInstalled(name.to_string())); + } + std::fs::remove_file(&path) + .with_context(|| format!("removing installed plugin {}", path.display()))?; + Ok(RemoveOutcome::Removed(path)) +} + +fn resolve_installed_path(base: &Path, name: &str) -> PathBuf { + let bare = base.join(name); + if bare.symlink_metadata().is_ok() { + return bare; + } + #[cfg(windows)] + { + let executable = base.join(format!("{name}.exe")); + if executable.symlink_metadata().is_ok() { + return executable; + } + } + bare +} + +fn derive_name(source: &Path) -> Result { + source + .file_stem() + .map(|stem| stem.to_string_lossy().into_owned()) + .ok_or_else(|| { + anyhow::anyhow!( + "cannot derive a plugin name from {}; pass --name", + source.display() + ) + }) +} diff --git a/src/runtime/emulator/mod.rs b/src/runtime/emulator/mod.rs index 22d6980e..73f62357 100644 --- a/src/runtime/emulator/mod.rs +++ b/src/runtime/emulator/mod.rs @@ -2,11 +2,8 @@ //! //! Wraps `Term` + the ANSI `Processor` + an event proxy behind the narrow //! contract the rest of nightcrow needs — feed PTY bytes, resize, scroll, -//! query cells/cursor/modes — so no module outside this file touches -//! alacritty internals except through `ScreenView`/`CellView`. This replaced -//! the vt100 crate, whose resize path panicked when a wide character was -//! truncated at the last column (vt100-rust issue #28) and whose upstream -//! had stalled on that class of boundary bugs. +//! query cells/cursor/modes. This replaced the vt100 crate, whose resize path +//! panicked when a wide character was truncated at the last column. use std::cell::RefCell; use std::rc::Rc; @@ -21,20 +18,17 @@ use alacritty_terminal::vte::ansi::Processor; #[derive(Default)] pub struct EmulatorEvents { /// Most recent OSC 0/2 window title in the processed chunk, already - /// stripped of control characters and surrounding whitespace. `None` - /// when the chunk set no (non-empty) title. + /// stripped of control characters and surrounding whitespace. pub title: Option, /// Terminal query responses (DA, DSR, ...) the emulator produced while - /// processing. Must be written back to the pane's PTY so programs that - /// interrogate their terminal (vim, tmux, ...) receive an answer. + /// processing. Must be written back to the pane's PTY. pub pty_writes: Vec, } /// Where a scroll request for a pane must be delivered. A program that owns /// its viewport keeps its transcript in its own memory, not in the emulator's /// scrollback, so scrolling the grid would move nothing; the scroll has to -/// reach the program as input instead. Which input it expects is announced by -/// the modes the program itself enabled. +/// reach the program as input instead. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ScrollSink { /// The program tracks the mouse and reports in SGR (1006) form: send it @@ -45,8 +39,7 @@ pub enum ScrollSink { ArrowKeys, /// Nothing claimed the scroll, so the emulator's own scrollback owns it. /// Interactive shells land here — the default, and the only branch that - /// writes nothing to the PTY. A shell would echo an unbound escape - /// sequence straight into its prompt, so this branch must stay silent. + /// writes nothing to the PTY. Scrollback, } @@ -109,8 +102,7 @@ impl PaneEmulator { } /// Feed raw PTY output through the emulator, updating the screen state. - /// Returns the side effects (title change, terminal query responses) - /// the caller must apply. + /// Returns the side effects (title change, terminal query responses). pub fn process(&mut self, bytes: &[u8]) -> EmulatorEvents { self.processor.advance(&mut self.term, bytes); let mut state = self.proxy.0.borrow_mut(); @@ -154,14 +146,12 @@ impl PaneEmulator { } /// Which input, if any, a scroll request for this pane must be turned - /// into. See `ScrollSink`. Mouse reporting wins over `alternateScroll` - /// because a program that asked for wheel events wants them even on the - /// alternate screen — that is also the order xterm resolves them in. + /// into. Mouse reporting wins over `alternateScroll` because a program + /// that asked for wheel events wants them even on the alternate screen. /// /// `MOUSE_MODE` alone is not enough: without `SGR_MOUSE` the program /// expects the legacy X10 encoding, which cannot address columns past - /// 223. Rather than emit a second encoding for a case no modern TUI - /// uses, such a pane falls back to `Scrollback`. + /// 223. Such a pane falls back to `Scrollback`. pub fn scroll_sink(&self) -> ScrollSink { let mode = self.term.mode(); if mode.intersects(TermMode::MOUSE_MODE) && mode.contains(TermMode::SGR_MOUSE) { @@ -174,10 +164,8 @@ impl PaneEmulator { } /// Whether the program asked for mouse button reports in SGR form — - /// the gate for forwarding clicks. The mode set is the same one that - /// routes wheel scrolls to `ScrollSink::MouseWheel`, but it is a - /// separate predicate because the meaning differs: a click has no - /// scrollback fallback, it is either claimed by the program or dropped. + /// the gate for forwarding clicks. A click has no scrollback fallback, + /// it is either claimed by the program or dropped. pub fn wants_mouse_buttons(&self) -> bool { let mode = self.term.mode(); mode.intersects(TermMode::MOUSE_MODE) && mode.contains(TermMode::SGR_MOUSE) @@ -211,9 +199,7 @@ impl PaneEmulator { } /// Clamp a requested pane size to alacritty's supported minimum grid. -/// `Term` expects its embedder to enforce `MIN_COLUMNS`/`MIN_SCREEN_LINES` -/// (the alacritty app clamps its window the same way); in particular a -/// 1-column grid makes wide-character reflow loop forever on resize. +/// A 1-column grid makes wide-character reflow loop forever on resize. /// /// `TerminalState` applies the same clamp to the backend PTY size and its /// `last_content_size` bookkeeping, so the PTY, the emulator grid, and the diff --git a/src/runtime/emulator/modes.rs b/src/runtime/emulator/modes.rs index b008f80d..ee288a28 100644 --- a/src/runtime/emulator/modes.rs +++ b/src/runtime/emulator/modes.rs @@ -5,17 +5,16 @@ /// The terminal modes a program sets once, at startup, and never repeats. /// /// A client that attaches later cannot learn these from the output it is -/// replayed: the bytes that set them are long gone from the pane's history, and -/// no program re-announces them. Carried as plain flags so a caller outside this -/// module can hold and compare them, and turned back into the sequences that -/// reproduce them by [`PaneModes::prelude`]. +/// replayed: the bytes that set them are long gone from the pane's history. +/// Carried as plain flags so a caller outside this module can hold and compare +/// them, and turned back into the sequences that reproduce them by +/// [`PaneModes::prelude`]. /// /// [`Default`] is a *freshly opened* terminal rather than all-false: `25` /// (visible cursor), `7` (autowrap) and `1007` (alternate scroll) are on until a -/// program turns them off. `a_freshly_opened_pane_matches_the_default` pins it to -/// what the emulator actually starts with, so an emulator upgrade that changes -/// its initial mode set fails there rather than silently mis-describing a pane -/// that has printed nothing yet. +/// program turns them off. Pinned to what the emulator actually starts with, so +/// an emulator upgrade that changes its initial mode set fails there rather than +/// silently mis-describing a pane that has printed nothing yet. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct PaneModes { /// DECSET 1049: the program draws on the alternate screen (vim, htop, @@ -38,8 +37,7 @@ pub struct PaneModes { pub mouse_drag: bool, /// DECSET 1003: report every motion. pub mouse_motion: bool, - /// DECSET 1006: report in SGR form. Without it a client's reports use the - /// legacy encoding the program is not expecting. + /// DECSET 1006: report in SGR form. pub sgr_mouse: bool, /// DECSET 1005. pub utf8_mouse: bool, diff --git a/src/runtime/snapshot.rs b/src/runtime/snapshot.rs index cb33c55b..b85fb17e 100644 --- a/src/runtime/snapshot.rs +++ b/src/runtime/snapshot.rs @@ -13,18 +13,16 @@ mod worker; use worker::Worker; /// Owns the receiver and wake channel for the background snapshot thread. -/// Dropping the struct signals the worker to exit and joins it before -/// returning, so a repo switch cannot leave the old-repo worker holding a -/// `git2::Repository` after the new channel is in place. +/// Dropping the struct signals the worker to exit and joins it, so a repo switch +/// cannot leave the old-repo worker holding a `git2::Repository` after the new +/// channel is in place. pub struct SnapshotChannel { rx: Receiver, /// Cleared to stop reading the tree without stopping the worker. /// /// A `git status` is not free and one runs per channel. A caller that knows - /// nobody is reading — the viewer with no client subscribed — turns it off - /// rather than paying for snapshots that go straight in the bin. The - /// filesystem watch goes with it, so a repository nobody reads holds no - /// watch descriptors either. + /// nobody is reading turns it off rather than paying for snapshots that go + /// straight in the bin. The filesystem watch goes with it. awake: Arc, /// Whether the worker is being told about *every* place a change can come /// from, rather than looking on a timer. False while asleep, on a tree the @@ -48,11 +46,9 @@ pub struct SnapshotChannel { handle: Option>, } -/// Shortest gap between two reads. -/// -/// The rate limit is what makes watching safe: a tree that churns without pause -/// costs exactly what the old fixed-interval poll cost and never more. Equal to -/// that old interval for that reason. +/// Shortest gap between two reads. The rate limit is what makes watching safe: +/// a tree that churns without pause costs exactly what the old fixed-interval +/// poll cost and never more. const MIN_READ_INTERVAL: Duration = Duration::from_millis(1000); /// Longest gap between two reads while awake and watching. @@ -60,7 +56,7 @@ const MIN_READ_INTERVAL: Duration = Duration::from_millis(1000); /// A watcher can miss an event, or install on part of a tree and fail on the /// rest, and "stale until the user happens to change something else" is not a /// state to leave a file list in. With no watcher at all this is not used: the -/// reader falls back to [`MIN_READ_INTERVAL`], which is what it did before. +/// reader falls back to [`MIN_READ_INTERVAL`]. const IDLE_READ_INTERVAL: Duration = Duration::from_secs(10); /// Reopen the cached `git2::Repository` handle every N reads so we observe @@ -82,7 +78,7 @@ impl SnapshotChannel { /// worker can win: it reads before that clears, which walks a tree nobody /// asked about and leaves the reading queued to be published after a later, /// newer one. The daemon opens every repository in a session and the browser - /// subscribes to one of them, so this is the ordinary case, not the odd one. + /// subscribes to one of them, so this is the ordinary case. pub fn spawn_asleep(repo_path: &str) -> Self { Self::start(repo_path, false) } @@ -167,14 +163,6 @@ pub struct SnapshotWatch { } impl SnapshotWatch { - /// Whether the reading is on. For a test whose claim is that some sequence - /// of callers left it in the right state — the count of who wants it read is - /// not the same fact as whether it is being read. - #[cfg(test)] - pub(crate) fn is_awake(&self) -> bool { - self.awake.load(Ordering::Acquire) - } - pub fn set_awake(&self, awake: bool) { self.awake.store(awake, Ordering::Release); // Woken rather than left to the interval: resuming means a client is diff --git a/src/runtime/snapshot/worker.rs b/src/runtime/snapshot/worker.rs index 918f406b..299b1525 100644 --- a/src/runtime/snapshot/worker.rs +++ b/src/runtime/snapshot/worker.rs @@ -1,11 +1,6 @@ //! The reader thread: watch the tree, read it when something changed, and stop -//! when nobody is listening. -//! -//! Everything here answers one question — is it time to read? — from three -//! inputs: what the filesystem reported, how long ago the last read was, and -//! whether anyone is reading at all. The bounds it works within are documented on -//! [`MIN_READ_INTERVAL`](super::MIN_READ_INTERVAL) and -//! [`IDLE_READ_INTERVAL`](super::IDLE_READ_INTERVAL). +//! when nobody is listening. Bounded by [`MIN_READ_INTERVAL`](super::MIN_READ_INTERVAL) +//! and [`IDLE_READ_INTERVAL`](super::IDLE_READ_INTERVAL). use super::{IDLE_READ_INTERVAL, MIN_READ_INTERVAL, REOPEN_REPO_EVERY_READS, SnapshotMsg, read}; use crate::runtime::snapshot_watch::{self, Roots, Wake}; @@ -21,7 +16,6 @@ pub(super) struct Worker { pub(super) root: PathBuf, pub(super) tx: Sender, pub(super) wake_rx: Receiver, - /// Handed to each watcher it installs. pub(super) wake_tx: Sender, pub(super) awake: Arc, pub(super) watching: Arc, @@ -31,18 +25,16 @@ pub(super) struct Worker { /// /// What was tried is recorded rather than inferred from the handles: a refusal /// leaves no handle, and re-deriving "not installed yet" from that would re-walk -/// the tree and log the same warning once a second for as long as the session -/// lives. A failure is answered by falling back to the interval — see -/// [`install`](snapshot_watch::install) — and retried only when what is wanted -/// changes, or when a repository nobody was reading is picked up again. +/// the tree and log the same warning once a second. A failure is answered by +/// falling back to the interval and retried only when what is wanted changes. #[derive(Default)] struct Watches { tree: Option, tree_attempted: bool, git_dir: Option, /// Whether the git directory has been looked for at all, and where it was - /// when it last was. Both, because "nowhere separate to watch" is an answer - /// and must not read as "not asked yet". + /// when it last was. "nowhere separate to watch" is an answer and must not + /// read as "not asked yet". git_dir_settled: bool, git_dir_at: Option, } @@ -53,7 +45,6 @@ struct ReadState { /// dropped whenever a read fails or the tree stops being watched. repo: Option, reads_since_open: u32, - /// Something happened that has not been read yet. changed: bool, last_read: Option, } @@ -142,10 +133,9 @@ impl Worker { } } - /// Keep a second watch on the git directory, for a repository that keeps it - /// outside the work tree — `git worktree add` and `--separate-git-dir` both - /// do. The index lives there, so without this a `git add` in such a checkout - /// goes unseen until the idle read comes round. + /// Keep a second watch on the git directory when it lives outside the work + /// tree — `git worktree add` and `--separate-git-dir` both do. The index + /// lives there, so without this a `git add` goes unseen until the idle read. /// /// Also settles `watching`, which says whether *every* place a change can /// come from is watched: it is what @@ -193,21 +183,11 @@ impl Worker { state.reads_since_open = 0; } Err(err) => { - // Counted as a read taken, `changed` and all. A directory that - // is not a repository yet may become one, but leaving - // `changed` standing to retry for that held the reader at - // `MIN_READ_INTERVAL` and sent this same error every second for - // as long as the session lived — the cost watching exists to - // remove, and enough on its own to make a test counting reads - // wait for a silence that never came. - // - // The watch is what notices instead: `git init` and a checkout - // appearing both write inside the tree being watched. And that - // watch is the whole of what can be watched here — the second - // one follows the git directory of a repository, and there is - // no repository — which is what earns the idle interval rather - // than the fixed fallback for a tree nothing reports on. See - // [`wait`](Self::wait). + // A directory that is not a repository yet may become one, + // but leaving `changed` standing to retry held the reader at + // `MIN_READ_INTERVAL` and sent this same error every second. + // The watch notices `git init` and checkouts appearing, so + // the idle interval is enough — see [`wait`](Self::wait). state.changed = false; self.watching.store(watch.tree.is_some(), Ordering::Release); state.last_read = Some(Instant::now()); diff --git a/src/runtime/snapshot_tests.rs b/src/runtime/snapshot_tests.rs index 6e5421f3..1dbd4195 100644 --- a/src/runtime/snapshot_tests.rs +++ b/src/runtime/snapshot_tests.rs @@ -1,72 +1,30 @@ -//! What the reader does with its time. -//! -//! The point of watching rather than polling is a repository that costs nothing -//! while nothing happens, so most of these are about *absence*: how many reads -//! arrive, not what they say. They use short windows against a ten-second idle -//! interval, so a slow machine delays them rather than failing them. -//! -//! The helpers that count reads are `pub(super)`: the same claim is made about a -//! path with no repository in it, and those tests sit beside the worker. That -//! "delays rather than fails" only holds for the absence windows; waiting for -//! something to arrive is bounded separately, by [`ARRIVAL`]. - use super::{IDLE_READ_INTERVAL, MIN_READ_INTERVAL, SnapshotChannel, SnapshotMsg}; use crate::test_util::{make_linked_worktree, make_repo, run_git}; use std::path::Path; use std::time::{Duration, Instant}; -/// How long an *absence* is observed for: long enough for a filesystem event to -/// travel and a read to happen, without reaching the idle interval that would -/// make the assertion meaningless. -/// -/// Only for windows a read must not appear in. A wait for one to *arrive* uses -/// [`ARRIVAL`] — bounding that by this same value is what makes a loaded machine -/// fail the test rather than delay it. -pub(super) const SETTLE: Duration = Duration::from_millis(2_500); +#[path = "snapshot_tests/changes.rs"] +mod changes; +#[path = "snapshot_tests/lifecycle.rs"] +mod lifecycle; -/// How long something expected is waited for before the reader is declared -/// broken. -/// -/// A backstop, not a measurement: nothing here asserts that a read arrived -/// *quickly*, only that it arrived, so the value has to sit far above the rate -/// limit that paces one — the same reasoning as [`quiesce`]'s own ceiling. At -/// `SETTLE` it sat at two and a half rate limits, and the suite's own parallelism -/// was enough to spend that, which showed up as reads that "never arrived" in a -/// reader that was merely waiting its turn on the CPU. +pub(super) const SETTLE: Duration = Duration::from_millis(2_500); const ARRIVAL: Duration = Duration::from_secs(15); -/// Wait for one snapshot, or fail. Errors count: what is being timed is the read, -/// not what it found. pub(super) fn next_read(channel: &SnapshotChannel) -> SnapshotMsg { let deadline = Instant::now() + ARRIVAL; while Instant::now() < deadline { - match channel.try_recv() { - Ok(msg) => return msg, - Err(_) => std::thread::sleep(Duration::from_millis(10)), + if let Ok(message) = channel.try_recv() { + return message; } + std::thread::sleep(Duration::from_millis(10)); } panic!("no snapshot arrived within {ARRIVAL:?}"); } -/// Drain until the reader has been quiet for several times its rate limit. -/// -/// For a test whose claim is about which reads happen: a read already owed when -/// the test acts — starting a watch owes one — would arrive on its own, and is -/// indistinguishable from the read being asserted, or from one the test says -/// must not happen at all. -/// -/// The window is what makes that hold, so it is wide. An owed read is taken -/// within `MIN_READ_INTERVAL` of the one before it *plus* however long the -/// worker waits to be scheduled, which under a full test run has been measured -/// at hundreds of milliseconds; returning before it arrives would put the read -/// this is meant to remove right back in the way. Still far below -/// `IDLE_READ_INTERVAL`, so the safety net cannot be what breaks the silence, -/// and far below what the caller then waits for a read. pub(super) fn quiesce(channel: &SnapshotChannel) { let quiet_for = MIN_READ_INTERVAL * 3; - // Only reached if reads never stop, which no test here can cause; a failure - // of the reader itself, not of the timing. - let deadline = Instant::now() + Duration::from_secs(15); + let deadline = Instant::now() + ARRIVAL; let mut last_read = Instant::now(); while Instant::now() < deadline { if channel.try_recv().is_ok() { @@ -79,7 +37,6 @@ pub(super) fn quiesce(channel: &SnapshotChannel) { panic!("the reader never went quiet for {quiet_for:?}"); } -/// How many snapshots arrive over `window`. pub(super) fn reads_during(channel: &SnapshotChannel, window: Duration) -> usize { let deadline = Instant::now() + window; let mut reads = 0; @@ -92,11 +49,6 @@ pub(super) fn reads_during(channel: &SnapshotChannel, window: Duration) -> usize reads } -/// A channel on a real repository, past its first read, with the watch confirmed. -/// -/// The watch is asserted rather than assumed: without it the reader falls back to -/// the old fixed interval, and every absence test below would fail for a reason -/// that has nothing to do with what it is checking. fn watched(path: &str) -> SnapshotChannel { let channel = SnapshotChannel::spawn(path); next_read(&channel); @@ -104,199 +56,6 @@ fn watched(path: &str) -> SnapshotChannel { while !channel.is_watching() && Instant::now() < deadline { std::thread::sleep(Duration::from_millis(10)); } - assert!( - channel.is_watching(), - "the work tree could not be watched, so this machine cannot run these tests" - ); + assert!(channel.is_watching(), "filesystem watch was not installed"); channel } - -#[test] -fn a_repository_is_read_once_on_arrival() { - // A client that has just opened one has nothing to render until this lands, - // so it does not wait for a change. - let (dir, path) = make_repo(); - let channel = SnapshotChannel::spawn(&path); - - assert!(matches!(next_read(&channel), SnapshotMsg::Ok(..))); - drop(dir); -} - -#[test] -fn a_repository_nothing_happens_in_is_not_read_again() { - // The whole point: a session left open costs nothing. The old reader walked - // the tree once a second forever, which on a large checkout was 129 ms of - // every second spent finding nothing. - let (dir, path) = make_repo(); - let channel = watched(&path); - // Starting the watch owes a read for the gap it was not up for, and that - // read lands a second or more later — inside the window below, where it - // would read as the walk this test says must not happen. Wait for silence - // rather than for a duration, so the count is only of idleness. - quiesce(&channel); - - let reads = reads_during(&channel, SETTLE); - - assert_eq!( - reads, 0, - "an idle repository must not be walked; the idle interval is {IDLE_READ_INTERVAL:?}" - ); - drop(dir); -} - -#[test] -fn a_file_written_in_the_work_tree_is_read_at_once() { - // And promptly: the old reader took up to its interval to notice, this one is - // told. Bounded well under the idle interval so what is being measured is the - // watch, not the safety net. - let (dir, path) = make_repo(); - let channel = watched(&path); - - std::fs::write(Path::new(&path).join("edited.rs"), "fn main() {}").unwrap(); - - let SnapshotMsg::Ok(snapshot, _) = next_read(&channel) else { - panic!("the read failed"); - }; - assert!( - snapshot.files.iter().any(|f| f.path == "edited.rs"), - "the read that followed the change must see it: {:?}", - snapshot.files - ); - drop(dir); -} - -#[test] -fn staging_a_file_is_read_even_though_the_work_tree_did_not_change() { - // `git add` moves the change from one status column to the other and touches - // nothing but `.git/index`. A watch on the work tree alone would miss it. - let (dir, path) = make_repo(); - std::fs::write(Path::new(&path).join("staged.rs"), "fn main() {}").unwrap(); - let channel = watched(&path); - // The write above may already have been read; take whatever is pending so the - // next read is the one the staging causes. - reads_during(&channel, Duration::from_millis(300)); - - run_git(&path, &["add", "staged.rs"]); - - let SnapshotMsg::Ok(snapshot, _) = next_read(&channel) else { - panic!("the read failed"); - }; - let staged = snapshot - .files - .iter() - .find(|f| f.path == "staged.rs") - .expect("the file is still listed"); - assert_eq!(staged.short_code(), "A ", "staged, not untracked"); - drop(dir); -} - -#[test] -fn staging_in_a_linked_worktree_is_read_too() { - // Its index is not in the tree at all — `git worktree add` leaves a `.git` - // file pointing at the main repository — so this passes only because the git - // directory is watched as well. Bounded well under the ten-second safety net, - // which would otherwise hide the gap. - let (main, elsewhere, tree) = make_linked_worktree(); - std::fs::write(Path::new(&tree).join("staged.rs"), "fn main() {}").unwrap(); - let channel = watched(&tree); - // Not a fixed pause: starting the second watch owes a read for the gap it - // was not up for, and that read would answer this test on its own. - quiesce(&channel); - - run_git(&tree, &["add", "staged.rs"]); - - let SnapshotMsg::Ok(snapshot, _) = next_read(&channel) else { - panic!("the read failed"); - }; - let staged = snapshot - .files - .iter() - .find(|f| f.path == "staged.rs") - .expect("the file is still listed"); - assert_eq!(staged.short_code(), "A ", "staged, not untracked"); - drop((main, elsewhere)); -} - -#[test] -fn a_burst_of_changes_is_read_at_the_old_pace_rather_than_per_event() { - // The rate limit is what makes watching safe: a tree that churns without - // pause — a build writing files git has not been told to ignore — costs - // exactly what the fixed-interval poll cost, never more. - let (dir, path) = make_repo(); - let channel = watched(&path); - - for i in 0..400 { - std::fs::write(Path::new(&path).join(format!("f{i}.rs")), "fn main() {}").unwrap(); - } - let window = Duration::from_millis(2_500); - let reads = reads_during(&channel, window); - - let ceiling = (window.as_millis() / MIN_READ_INTERVAL.as_millis()) as usize + 1; - assert!( - reads <= ceiling, - "400 writes produced {reads} reads in {window:?}; the rate limit allows {ceiling}" - ); - assert!(reads >= 1, "and the changes must be read at all"); - drop(dir); -} - -#[test] -fn changes_git_ignores_do_not_cause_a_read() { - // Build output is the loudest thing in a working tree and cannot appear in a - // status, so it is not worth a walk — which matters because a pane running a - // build is this app's ordinary state. - let (dir, path) = make_repo(); - std::fs::write(Path::new(&path).join(".gitignore"), "out/\n").unwrap(); - std::fs::create_dir_all(Path::new(&path).join("out")).unwrap(); - let channel = watched(&path); - // The read the watch owes arrives well after a fixed 300 ms pause would - // have returned, and it cannot be told apart from a walk the ignored writes - // caused. Waiting for the reader to go quiet first leaves the loop below as - // the only thing that could break the silence. - quiesce(&channel); - - for i in 0..200 { - std::fs::write( - Path::new(&path).join("out").join(format!("artifact{i}.o")), - "x", - ) - .unwrap(); - } - - assert_eq!( - reads_during(&channel, SETTLE), - 0, - "ignored paths must not wake the reader" - ); - drop(dir); -} - -#[test] -fn a_repository_nobody_is_reading_is_neither_read_nor_watched() { - // The viewer turns this off for a repository with no subscriber. Both halves - // stop: no walks, and no watch descriptors held for a tree nobody looks at. - let (dir, path) = make_repo(); - let channel = watched(&path); - - channel.watch().set_awake(false); - let deadline = Instant::now() + ARRIVAL; - while channel.is_watching() && Instant::now() < deadline { - std::thread::sleep(Duration::from_millis(10)); - } - assert!( - !channel.is_watching(), - "the watch is released with the reads" - ); - reads_during(&channel, Duration::from_millis(300)); - std::fs::write(Path::new(&path).join("unseen.rs"), "fn main() {}").unwrap(); - assert_eq!( - reads_during(&channel, Duration::from_millis(500)), - 0, - "a change in a repository nobody reads costs nothing" - ); - - // Resuming answers now rather than on the interval: a client is waiting. - channel.watch().set_awake(true); - assert!(matches!(next_read(&channel), SnapshotMsg::Ok(..))); - drop(dir); -} diff --git a/src/runtime/snapshot_tests/changes.rs b/src/runtime/snapshot_tests/changes.rs new file mode 100644 index 00000000..90d6b28f --- /dev/null +++ b/src/runtime/snapshot_tests/changes.rs @@ -0,0 +1,90 @@ +use super::*; + +#[test] +fn a_file_written_in_the_work_tree_is_read_at_once() { + let (dir, path) = make_repo(); + let channel = watched(&path); + + std::fs::write(Path::new(&path).join("edited.rs"), "fn main() {}").unwrap(); + + let SnapshotMsg::Ok(snapshot, _) = next_read(&channel) else { + panic!("the read failed"); + }; + assert!(snapshot.files.iter().any(|file| file.path == "edited.rs")); + drop(dir); +} + +#[test] +fn staging_a_file_is_read_even_though_the_work_tree_did_not_change() { + let (dir, path) = make_repo(); + std::fs::write(Path::new(&path).join("staged.rs"), "fn main() {}").unwrap(); + let channel = watched(&path); + reads_during(&channel, Duration::from_millis(300)); + + run_git(&path, &["add", "staged.rs"]); + + let SnapshotMsg::Ok(snapshot, _) = next_read(&channel) else { + panic!("the read failed"); + }; + let staged = snapshot + .files + .iter() + .find(|file| file.path == "staged.rs") + .expect("the file is listed"); + assert_eq!(staged.short_code(), "A "); + drop(dir); +} + +#[test] +fn staging_in_a_linked_worktree_is_read_too() { + let (main, elsewhere, tree) = make_linked_worktree(); + std::fs::write(Path::new(&tree).join("staged.rs"), "fn main() {}").unwrap(); + let channel = watched(&tree); + quiesce(&channel); + + run_git(&tree, &["add", "staged.rs"]); + + let SnapshotMsg::Ok(snapshot, _) = next_read(&channel) else { + panic!("the read failed"); + }; + let staged = snapshot + .files + .iter() + .find(|file| file.path == "staged.rs") + .expect("the file is listed"); + assert_eq!(staged.short_code(), "A "); + drop((main, elsewhere)); +} + +#[test] +fn a_burst_of_changes_is_read_at_the_old_pace_rather_than_per_event() { + let (dir, path) = make_repo(); + let channel = watched(&path); + + for i in 0..400 { + std::fs::write(Path::new(&path).join(format!("f{i}.rs")), "fn main() {}").unwrap(); + } + let window = Duration::from_millis(2_500); + let reads = reads_during(&channel, window); + + let ceiling = (window.as_millis() / MIN_READ_INTERVAL.as_millis()) as usize + 1; + assert!(reads <= ceiling, "{reads} reads; ceiling is {ceiling}"); + assert!(reads >= 1, "the changes must be read"); + drop(dir); +} + +#[test] +fn changes_git_ignores_do_not_cause_a_read() { + let (dir, path) = make_repo(); + std::fs::write(Path::new(&path).join(".gitignore"), "out/\n").unwrap(); + std::fs::create_dir_all(Path::new(&path).join("out")).unwrap(); + let channel = watched(&path); + quiesce(&channel); + + for i in 0..200 { + std::fs::write(Path::new(&path).join(format!("out/artifact{i}.o")), "x").unwrap(); + } + + assert_eq!(reads_during(&channel, SETTLE), 0); + drop(dir); +} diff --git a/src/runtime/snapshot_tests/lifecycle.rs b/src/runtime/snapshot_tests/lifecycle.rs new file mode 100644 index 00000000..f716288a --- /dev/null +++ b/src/runtime/snapshot_tests/lifecycle.rs @@ -0,0 +1,45 @@ +use super::*; + +#[test] +fn a_repository_is_read_once_on_arrival() { + let (dir, path) = make_repo(); + let channel = SnapshotChannel::spawn(&path); + + assert!(matches!(next_read(&channel), SnapshotMsg::Ok(..))); + drop(dir); +} + +#[test] +fn a_repository_nothing_happens_in_is_not_read_again() { + let (dir, path) = make_repo(); + let channel = watched(&path); + quiesce(&channel); + + assert_eq!( + reads_during(&channel, SETTLE), + 0, + "an idle repository must not be walked; fallback is {IDLE_READ_INTERVAL:?}" + ); + drop(dir); +} + +#[test] +fn a_repository_nobody_is_reading_is_neither_read_nor_watched() { + let (dir, path) = make_repo(); + let channel = watched(&path); + + channel.watch().set_awake(false); + let deadline = Instant::now() + ARRIVAL; + while channel.is_watching() && Instant::now() < deadline { + std::thread::sleep(Duration::from_millis(10)); + } + assert!(!channel.is_watching(), "the watch must be released"); + + reads_during(&channel, Duration::from_millis(300)); + std::fs::write(Path::new(&path).join("unseen.rs"), "fn main() {}").unwrap(); + assert_eq!(reads_during(&channel, Duration::from_millis(500)), 0); + + channel.watch().set_awake(true); + assert!(matches!(next_read(&channel), SnapshotMsg::Ok(..))); + drop(dir); +} diff --git a/src/runtime/snapshot_watch.rs b/src/runtime/snapshot_watch.rs index 2183fbc3..026f196e 100644 --- a/src/runtime/snapshot_watch.rs +++ b/src/runtime/snapshot_watch.rs @@ -1,36 +1,23 @@ -//! Noticing that a working tree changed, so the status reader can stop guessing. -//! -//! A `git status` walk lstats every tracked file — 3 ms on a small repository, -//! 129 ms on one with fifty thousand files (measured) — and running it on a timer -//! spends that whether or not anything happened. This watches instead, so an idle -//! repository costs nothing and a changed one is read at once. -//! -//! **Recursive, unlike the file-tree watcher next door.** That one watches only -//! the directories a user expanded, because a listing is per-directory; a status -//! covers the whole tree, so there is no smaller set that would answer the -//! question. The cost of that is real on Linux — one inotify descriptor per -//! directory, and a large checkout can be refused outright — so a failure to -//! install is not fatal: the reader falls back to the fixed interval it used -//! before this existed. +//! Recursive filesystem watcher for the whole working tree, so the status reader +//! can react to changes instead of polling. Failure to install is not fatal: the +//! reader falls back to a fixed interval. One inotify descriptor per directory on +//! Linux means a large checkout can be refused outright. use notify::event::{AccessKind, AccessMode}; use notify::{Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher}; use std::path::{Path, PathBuf}; use std::sync::mpsc::Sender; -/// What the snapshot worker waits for. pub(super) enum Wake { - /// The filesystem reported these paths. An empty list means something - /// changed that the watcher could not name — including its own error — which - /// is a reason to look rather than a reason to stop. + /// Filesystem paths that changed. An empty list means something changed that + /// the watcher could not name — including its own error — which is a reason + /// to look rather than a reason to stop. Changed(Vec), - /// The channel is going away. Stop, } -/// Start watching `root` and everything under it, or `None` when the platform -/// refuses. The watcher must be kept alive by the caller; dropping it stops the -/// watch. +/// Start watching `root` recursively, or `None` when the platform refuses. +/// The watcher must be kept alive by the caller; dropping it stops the watch. /// /// A caller that gets `None` must not retry on a timer: the refusals this hits /// (inotify's watch limit, a directory it may not read) are properties of the @@ -67,15 +54,12 @@ pub(super) fn install(root: &Path, wake: Sender) -> Option) -> Option> { let event = match event { Ok(event) => event, @@ -102,10 +86,9 @@ fn changed_paths(event: notify::Result) -> Option> { /// /// Both, because macOS resolves symlinks in the paths it hands back: a repository /// under `/var/folders/...` is reported under `/private/var/folders/...`. Windows -/// does the same to 8.3 short names, reporting `runneradmin` where the path said -/// `RUNNER~1`. A path that cannot be made relative to the tree is treated as -/// "cannot tell, read it", so getting this wrong does not break correctness — it -/// silently turns the whole filter off, which is the same as not having written it. +/// does the same to 8.3 short names. A path that cannot be made relative to the +/// tree is treated as "cannot tell, read it", so getting this wrong silently turns +/// the whole filter off — the same as not having written it. struct Prefix { given: PathBuf, canonical: PathBuf, @@ -150,17 +133,14 @@ impl Roots { } /// `None` clears it, for a repository that turned out to keep its git - /// directory inside the tree after all — a reopen can land on a different - /// repository than the last one did. + /// directory inside the tree after all. pub(super) fn set_external_git_dir(&mut self, git_dir: Option<&Path>) { self.git_dir = git_dir.map(Prefix::of); } } /// The repository's common directory when it does not live inside the watched -/// tree, which is exactly when it needs a watch of its own. -/// -/// The *common* directory rather than `path()`: a linked worktree's `path()` +/// tree. The *common* directory rather than `path()`: a linked worktree's `path()` /// holds its index but its refs are in the main repository's, and a commit made /// elsewhere on the same branch changes what a status says. The common directory /// contains both. @@ -207,8 +187,7 @@ fn matters(repo: Option<&git2::Repository>, roots: &Roots, path: &Path) -> bool }; // Build output is the loudest thing in a working tree and the one thing git // has been told to disregard: a `cargo build` writes thousands of files that - // cannot appear in a status. Skipping them is what makes this worth having, - // since a pane running a build is nightcrow's ordinary state. + // cannot appear in a status. Skipping them is what makes this worth having. // // A tracked file inside an ignored directory (added with `-f`) is the case // this skips wrongly. The idle read is what still catches it. diff --git a/src/runtime/snapshot_watch_tests.rs b/src/runtime/snapshot_watch_tests.rs index 558dd73b..ac11e406 100644 --- a/src/runtime/snapshot_watch_tests.rs +++ b/src/runtime/snapshot_watch_tests.rs @@ -1,14 +1,19 @@ use super::{Roots, any_matters, changed_paths, external_git_dir}; -use crate::test_util::{make_linked_worktree, make_repo, run_git}; +use crate::test_util::{make_linked_worktree, make_repo}; use notify::event::{AccessKind, AccessMode, CreateKind, Event, EventKind, Flag, ModifyKind}; use std::path::{Path, PathBuf}; +#[path = "snapshot_watch_tests/events.rs"] +mod events; +#[path = "snapshot_watch_tests/filter.rs"] +mod filter; +#[path = "snapshot_watch_tests/roots.rs"] +mod roots; + fn under(root: &str, relative: &str) -> Vec { vec![Path::new(root).join(relative)] } -/// The whole gate the watcher applies: the kind decides whether the event is -/// forwarded at all, and the paths then decide whether it is worth a read. fn wakes_the_reader(root: &str, event: notify::Result) -> bool { let repo = crate::test_util::open_repo(root); changed_paths(event) @@ -18,292 +23,3 @@ fn wakes_the_reader(root: &str, event: notify::Result) -> bool { fn at(kind: EventKind, root: &str, relative: &str) -> notify::Result { Ok(Event::new(kind).add_path(Path::new(root).join(relative))) } - -#[test] -fn a_source_file_matters() { - let (dir, path) = make_repo(); - let repo = crate::test_util::open_repo(&path); - - assert!(any_matters( - Some(&repo), - &Roots::of(Path::new(&path)), - &under(&path, "src/main.rs") - )); - drop(dir); -} - -#[test] -fn an_ignored_path_does_not() { - // The reason this filter exists: a build writes thousands of files git has - // been told to disregard, and none of them can appear in a status. - let (dir, path) = make_repo(); - std::fs::write(Path::new(&path).join(".gitignore"), "target/\n*.log\n").unwrap(); - let repo = crate::test_util::open_repo(&path); - - assert!(!any_matters( - Some(&repo), - &Roots::of(Path::new(&path)), - &under(&path, "target/debug/build/x.o") - )); - assert!(!any_matters( - Some(&repo), - &Roots::of(Path::new(&path)), - &under(&path, "build.log") - )); - drop(dir); -} - -#[test] -fn the_index_and_refs_matter_but_objects_and_reflogs_do_not() { - // A commit or a fetch writes objects and reflogs as well as the ref that - // names them; the ref and the index are what change a status, and reading on - // the object churn instead would walk the tree once per loose object. - let (dir, path) = make_repo(); - let repo = crate::test_util::open_repo(&path); - let roots = Roots::of(Path::new(&path)); - - for interesting in [ - ".git/index", - ".git/HEAD", - ".git/refs/heads/main", - ".git/packed-refs", - ] { - assert!( - any_matters(Some(&repo), &roots, &under(&path, interesting)), - "{interesting} changes what a status says" - ); - } - for noise in [ - ".git/objects/ab/cdef0123456789", - ".git/logs/HEAD", - ".git/index.lock", - ] { - assert!( - !any_matters(Some(&repo), &roots, &under(&path, noise)), - "{noise} does not" - ); - } - drop(dir); -} - -#[test] -fn a_change_the_watcher_could_not_name_matters() { - // An empty list is what a watcher error becomes: it may have missed events, - // so the answer is to look rather than to decide there was nothing. - let (dir, path) = make_repo(); - let repo = crate::test_util::open_repo(&path); - - assert!(any_matters(Some(&repo), &Roots::of(Path::new(&path)), &[])); - drop(dir); -} - -#[test] -fn without_a_repository_handle_everything_matters() { - // Nothing to ask about ignore rules yet, and guessing the other way would - // leave a real change unread. - let (dir, path) = make_repo(); - - assert!(any_matters( - None, - &Roots::of(Path::new(&path)), - &under(&path, "any.rs") - )); - drop(dir); -} - -#[test] -fn an_ordinary_repository_needs_no_second_watch() { - // `.git` is inside the tree, so the recursive watch already covers it and a - // second one would only double every event. - let (dir, path) = make_repo(); - let repo = crate::test_util::open_repo(&path); - - assert!(external_git_dir(&repo, &Roots::of(Path::new(&path))).is_none()); - drop(dir); -} - -/// `Prefix` keeps a canonical form precisely so a root spelled one way still -/// places paths that arrive spelled another — which is the normal case, since -/// libgit2 and the watcher both hand back the resolved path rather than the -/// spelling the root was opened with. On Windows `canonicalize` returns the -/// verbatim (`\\?\`) form, and nothing else in the process ever produces one, -/// so that fallback matched nothing at all and only the literal spelling was -/// left to carry the comparison. -#[test] -fn a_root_spelled_canonically_still_places_a_plainly_spelled_path() { - let (dir, path) = make_repo(); - let spelled_one_way = std::fs::canonicalize(&path).expect("the repo exists"); - let spelled_another = - crate::platform::paths::canonicalize_clean(&path).expect("the repo exists"); - - let roots = Roots::of(&spelled_one_way); - - assert!( - roots - .tree - .relative(&spelled_another.join("src/main.rs")) - .is_some(), - "one spelling of the root must place the other's paths" - ); - drop(dir); -} - -#[test] -fn a_linked_worktree_is_watched_where_its_state_lives() { - // `git worktree add` leaves a `.git` *file* pointing at the main - // repository's directory, and that is where the index and the refs are. A - // watch on the tree alone would never see a `git add`. - let (main, elsewhere, tree) = make_linked_worktree(); - let repo = crate::test_util::open_repo(&tree); - - let watched = - external_git_dir(&repo, &Roots::of(Path::new(&tree))).expect("the git dir is elsewhere"); - - assert!( - watched.join("worktrees").is_dir(), - "the main repository's git directory, which holds both trees' state: {}", - watched.display() - ); - drop((main, elsewhere)); -} - -#[test] -fn inside_a_git_directory_of_its_own_the_same_paths_matter() { - // Once that second watch is installed its events arrive named after a - // directory the tree filter cannot place, and "cannot place" means "read - // it" — which would turn every loose object into a walk. The git-metadata - // rules apply there instead. - let (dir, path) = make_repo(); - let elsewhere = tempfile::TempDir::new().unwrap(); - let git_dir = elsewhere.path().to_string_lossy().to_string(); - let repo = crate::test_util::open_repo(&path); - let mut roots = Roots::of(Path::new(&path)); - roots.set_external_git_dir(Some(Path::new(&git_dir))); - - for interesting in ["index", "HEAD", "refs/heads/main", "worktrees/wt/index"] { - assert!( - any_matters(Some(&repo), &roots, &under(&git_dir, interesting)), - "{interesting} changes what a status says" - ); - } - for noise in [ - "objects/ab/cdef0123456789", - "logs/HEAD", - "worktrees/wt/index.lock", - ] { - assert!( - !any_matters(Some(&repo), &roots, &under(&git_dir, noise)), - "{noise} does not" - ); - } - drop(dir); -} - -#[test] -fn a_submodules_git_directory_is_read_rather_than_judged() { - // A submodule keeps one under `modules//`, where the same objects and - // reflogs churn — but a submodule's name is its path in the tree, slashes - // included, so `modules/foo/objects/HEAD` is the `HEAD` of a submodule at - // `foo/objects` just as readily as the object store of one at `foo`. The - // rule stays at the top level rather than guess: over-reading costs a walk - // per second during a fetch, dropping the wrong one costs a change nobody - // sees. - let (dir, path) = make_repo(); - let elsewhere = tempfile::TempDir::new().unwrap(); - let git_dir = elsewhere.path().to_string_lossy().to_string(); - let repo = crate::test_util::open_repo(&path); - let mut roots = Roots::of(Path::new(&path)); - roots.set_external_git_dir(Some(Path::new(&git_dir))); - - for under_a_submodule in [ - "modules/sub/objects/ab/cdef", - "modules/sub/HEAD", - "modules/foo/objects/HEAD", - ] { - assert!( - any_matters(Some(&repo), &roots, &under(&git_dir, under_a_submodule)), - "{under_a_submodule} is read rather than judged" - ); - } - drop(dir); -} - -#[test] -fn a_path_outside_the_tree_matters() { - // The watcher should not report these. One that cannot be placed is read - // rather than dropped. - let (dir, path) = make_repo(); - let repo = crate::test_util::open_repo(&path); - run_git(&path, &["status"]); - - assert!(any_matters( - Some(&repo), - &Roots::of(Path::new(&path)), - &[PathBuf::from("/elsewhere/file.rs")] - )); - drop(dir); -} - -#[test] -fn an_open_caused_by_the_read_itself_does_not_wake_the_reader() { - // The loop this closes. A status read opens `HEAD`, the branch ref and the - // work-tree root, inotify reports each of those as an open, and every one of - // them clears the filter above — so the reader kept re-reading at the rate - // limit for as long as a repository was on screen. - let (dir, path) = make_repo(); - - for opened in [".git/HEAD", ".git/refs/heads/main", "src/main.rs", ""] { - let event = at( - EventKind::Access(AccessKind::Open(AccessMode::Any)), - &path, - opened, - ); - assert!( - !wakes_the_reader(&path, event), - "opening {opened} is a read, not a change" - ); - } - drop(dir); -} - -#[test] -fn a_finished_write_wakes_the_reader() { - // `IN_CLOSE_WRITE` is the one access that is not somebody looking, so it - // cannot go with the rest: dropping it would lose real changes on Linux. - let (dir, path) = make_repo(); - let closed = EventKind::Access(AccessKind::Close(AccessMode::Write)); - - assert!(wakes_the_reader(&path, at(closed, &path, ".git/HEAD"))); - assert!(wakes_the_reader(&path, at(closed, &path, "src/main.rs"))); - drop(dir); -} - -#[test] -fn an_ordinary_write_or_creation_wakes_the_reader() { - // Nothing outside `Access` was touched; this is what says so. - let (dir, path) = make_repo(); - - for kind in [ - EventKind::Modify(ModifyKind::Any), - EventKind::Create(CreateKind::File), - ] { - assert!( - wakes_the_reader(&path, at(kind, &path, "src/main.rs")), - "{kind:?} changes what a status says" - ); - } - drop(dir); -} - -#[test] -fn a_dropped_events_signal_wakes_the_reader() { - // An inotify queue overflow arrives as `Other` with the rescan flag and no - // paths at all, and a watcher error names nothing either. Both mean events - // were missed, so both must still reach the worker. - let (dir, path) = make_repo(); - - let overflowed = Ok(Event::new(EventKind::Other).set_flag(Flag::Rescan)); - assert!(wakes_the_reader(&path, overflowed)); - assert!(wakes_the_reader(&path, Err(notify::Error::generic("boom")))); - drop(dir); -} diff --git a/src/runtime/snapshot_watch_tests/events.rs b/src/runtime/snapshot_watch_tests/events.rs new file mode 100644 index 00000000..86341313 --- /dev/null +++ b/src/runtime/snapshot_watch_tests/events.rs @@ -0,0 +1,52 @@ +use super::*; + +#[test] +fn an_open_caused_by_the_read_itself_does_not_wake_the_reader() { + let (dir, path) = make_repo(); + + for opened in [".git/HEAD", ".git/refs/heads/main", "src/main.rs", ""] { + let event = at( + EventKind::Access(AccessKind::Open(AccessMode::Any)), + &path, + opened, + ); + assert!(!wakes_the_reader(&path, event), "opened {opened}"); + } + drop(dir); +} + +#[test] +fn a_finished_write_wakes_the_reader() { + let (dir, path) = make_repo(); + let closed = EventKind::Access(AccessKind::Close(AccessMode::Write)); + + assert!(wakes_the_reader(&path, at(closed, &path, ".git/HEAD"))); + assert!(wakes_the_reader(&path, at(closed, &path, "src/main.rs"))); + drop(dir); +} + +#[test] +fn an_ordinary_write_or_creation_wakes_the_reader() { + let (dir, path) = make_repo(); + + for kind in [ + EventKind::Modify(ModifyKind::Any), + EventKind::Create(CreateKind::File), + ] { + assert!( + wakes_the_reader(&path, at(kind, &path, "src/main.rs")), + "{kind:?}" + ); + } + drop(dir); +} + +#[test] +fn a_dropped_events_signal_wakes_the_reader() { + let (dir, path) = make_repo(); + + let overflowed = Ok(Event::new(EventKind::Other).set_flag(Flag::Rescan)); + assert!(wakes_the_reader(&path, overflowed)); + assert!(wakes_the_reader(&path, Err(notify::Error::generic("boom")))); + drop(dir); +} diff --git a/src/runtime/snapshot_watch_tests/filter.rs b/src/runtime/snapshot_watch_tests/filter.rs new file mode 100644 index 00000000..c4436c58 --- /dev/null +++ b/src/runtime/snapshot_watch_tests/filter.rs @@ -0,0 +1,85 @@ +use super::*; + +#[test] +fn a_source_file_matters() { + let (dir, path) = make_repo(); + let repo = crate::test_util::open_repo(&path); + + assert!(any_matters( + Some(&repo), + &Roots::of(Path::new(&path)), + &under(&path, "src/main.rs") + )); + drop(dir); +} + +#[test] +fn an_ignored_path_does_not() { + let (dir, path) = make_repo(); + std::fs::write(Path::new(&path).join(".gitignore"), "target/\n*.log\n").unwrap(); + let repo = crate::test_util::open_repo(&path); + let roots = Roots::of(Path::new(&path)); + + for ignored in ["target/debug/build/x.o", "build.log"] { + assert!(!any_matters(Some(&repo), &roots, &under(&path, ignored))); + } + drop(dir); +} + +#[test] +fn the_index_and_refs_matter_but_objects_and_reflogs_do_not() { + let (dir, path) = make_repo(); + let repo = crate::test_util::open_repo(&path); + let roots = Roots::of(Path::new(&path)); + + for interesting in [ + ".git/index", + ".git/HEAD", + ".git/refs/heads/main", + ".git/packed-refs", + ] { + assert!(any_matters(Some(&repo), &roots, &under(&path, interesting))); + } + for noise in [ + ".git/objects/ab/cdef0123456789", + ".git/logs/HEAD", + ".git/index.lock", + ] { + assert!(!any_matters(Some(&repo), &roots, &under(&path, noise))); + } + drop(dir); +} + +#[test] +fn a_change_the_watcher_could_not_name_matters() { + let (dir, path) = make_repo(); + let repo = crate::test_util::open_repo(&path); + + assert!(any_matters(Some(&repo), &Roots::of(Path::new(&path)), &[])); + drop(dir); +} + +#[test] +fn without_a_repository_handle_everything_matters() { + let (dir, path) = make_repo(); + + assert!(any_matters( + None, + &Roots::of(Path::new(&path)), + &under(&path, "any.rs") + )); + drop(dir); +} + +#[test] +fn a_path_outside_the_tree_matters() { + let (dir, path) = make_repo(); + let repo = crate::test_util::open_repo(&path); + + assert!(any_matters( + Some(&repo), + &Roots::of(Path::new(&path)), + &[PathBuf::from("/elsewhere/file.rs")] + )); + drop(dir); +} diff --git a/src/runtime/snapshot_watch_tests/roots.rs b/src/runtime/snapshot_watch_tests/roots.rs new file mode 100644 index 00000000..2c625f16 --- /dev/null +++ b/src/runtime/snapshot_watch_tests/roots.rs @@ -0,0 +1,81 @@ +use super::*; + +#[test] +fn an_ordinary_repository_needs_no_second_watch() { + let (dir, path) = make_repo(); + let repo = crate::test_util::open_repo(&path); + + assert!(external_git_dir(&repo, &Roots::of(Path::new(&path))).is_none()); + drop(dir); +} + +#[test] +fn a_root_spelled_canonically_still_places_a_plainly_spelled_path() { + let (dir, path) = make_repo(); + let canonical = std::fs::canonicalize(&path).expect("the repo exists"); + let clean = crate::platform::paths::canonicalize_clean(&path).expect("the repo exists"); + + assert!( + Roots::of(&canonical) + .tree + .relative(&clean.join("src/main.rs")) + .is_some() + ); + drop(dir); +} + +#[test] +fn a_linked_worktree_is_watched_where_its_state_lives() { + let (main, elsewhere, tree) = make_linked_worktree(); + let repo = crate::test_util::open_repo(&tree); + + let watched = external_git_dir(&repo, &Roots::of(Path::new(&tree))).expect("external git dir"); + + assert!(watched.join("worktrees").is_dir(), "{}", watched.display()); + drop((main, elsewhere)); +} + +#[test] +fn inside_an_external_git_directory_the_same_paths_matter() { + let (dir, path) = make_repo(); + let elsewhere = tempfile::TempDir::new().unwrap(); + let git_dir = elsewhere.path().to_string_lossy().to_string(); + let repo = crate::test_util::open_repo(&path); + let mut roots = Roots::of(Path::new(&path)); + roots.set_external_git_dir(Some(Path::new(&git_dir))); + + for interesting in ["index", "HEAD", "refs/heads/main", "worktrees/wt/index"] { + assert!(any_matters( + Some(&repo), + &roots, + &under(&git_dir, interesting) + )); + } + for noise in [ + "objects/ab/cdef0123456789", + "logs/HEAD", + "worktrees/wt/index.lock", + ] { + assert!(!any_matters(Some(&repo), &roots, &under(&git_dir, noise))); + } + drop(dir); +} + +#[test] +fn a_submodules_git_directory_is_read_rather_than_judged() { + let (dir, path) = make_repo(); + let elsewhere = tempfile::TempDir::new().unwrap(); + let git_dir = elsewhere.path().to_string_lossy().to_string(); + let repo = crate::test_util::open_repo(&path); + let mut roots = Roots::of(Path::new(&path)); + roots.set_external_git_dir(Some(Path::new(&git_dir))); + + for path in [ + "modules/sub/objects/ab/cdef", + "modules/sub/HEAD", + "modules/foo/objects/HEAD", + ] { + assert!(any_matters(Some(&repo), &roots, &under(&git_dir, path))); + } + drop(dir); +} diff --git a/src/runtime/terminal/lifecycle.rs b/src/runtime/terminal/lifecycle.rs index 609f5f9c..2842c424 100644 --- a/src/runtime/terminal/lifecycle.rs +++ b/src/runtime/terminal/lifecycle.rs @@ -5,8 +5,7 @@ use crate::runtime::terminal::{PaneInfo, SCROLLBACK_LINES, TerminalState}; impl TerminalState { /// Drain pending backend events into pane emulators and pane metadata. /// Returns the pane ids the backend signalled as exited so the caller - /// can run cross-cutting cleanup (focus redirect, fullscreen reset) - /// that depends on state outside this struct. + /// can run cross-cutting cleanup (focus redirect, fullscreen reset). pub fn poll(&mut self) -> Vec { let mut exited = Vec::new(); let events: Vec = self @@ -103,8 +102,7 @@ impl TerminalState { exited } - /// Allocate a new bare interactive-shell pane. Thin wrapper over - /// `create_pane_with` for the common "open an empty terminal" path. + /// Allocate a new bare interactive-shell pane. pub fn create_pane(&mut self) -> anyhow::Result<()> { self.create_pane_with(None, None) } @@ -112,8 +110,7 @@ impl TerminalState { /// Allocate a new backend pane and matching emulator. `command`, when /// present, is run in the pane's shell immediately; `label` sets the /// initial tab title (a program that emits OSC 0/2 can still override it - /// later). Both default sensibly when `None`. The caller is expected to - /// surface any error to the user. + /// later). Both default sensibly when `None`. pub fn create_pane_with( &mut self, command: Option<&str>, @@ -173,10 +170,9 @@ impl TerminalState { /// Take in a pane the backend reports. /// - /// Everything `create_pane_with` used to do on the spot, moved to where the - /// pane actually turns up. `requested` says whether this client asked: one - /// it did takes the focus, and one another client opened lands in the list - /// without moving anybody's cursor. + /// `requested` says whether this client asked: one it did takes the focus, + /// and one another client opened lands in the list without moving anybody's + /// cursor. fn adopt_pane( &mut self, id: PaneId, diff --git a/src/runtime/terminal/mod.rs b/src/runtime/terminal/mod.rs index 2a9a2ee8..168ae1b3 100644 --- a/src/runtime/terminal/mod.rs +++ b/src/runtime/terminal/mod.rs @@ -16,12 +16,10 @@ pub use recovery::PaneRecovery; /// Upper bound on a pane's in-flight prompt buffer before further chars are /// dropped. Prevents unbounded growth when a program writes a stream of bytes /// without ever sending `\r` / `\n` (progress bars, large pastes, `yes` piped -/// to cat). 4 KiB easily exceeds any realistic shell prompt line. +/// to cat). const PROMPT_BUFFER_MAX_BYTES: usize = 4096; -/// Scrollback line cap for every pane emulator. Lifted here so the terminal -/// state machine — which owns emulator creation now — defines its own budget -/// rather than reading it from `app`. +/// Scrollback line cap for every pane emulator. pub const SCROLLBACK_LINES: usize = 1000; /// Lines moved by a single line-scroll keypress (`Shift+Up`/`Shift+Down`). @@ -46,17 +44,13 @@ pub const MAX_VISIBLE_NORMAL: usize = 4; pub const MAX_VISIBLE_FULLSCREEN: usize = 8; /// Fullscreen state of the lower terminal panel. ` f` cycles through -/// these while the terminal is focused: `Off → Grid → Zoom → Off`. +/// `Off → Grid → Zoom → Off`. /// - `Off`: normal split — top viewer above, terminal split-view below. /// - `Grid`: terminal fills the body; up to `MAX_VISIBLE_FULLSCREEN` panes. -/// - `Zoom`: terminal fills the body showing only the active pane. Rendered -/// by the same grid path with a visible cap of 1, so no dedicated render -/// branch is needed. +/// - `Zoom`: terminal fills the body showing only the active pane. /// /// `Grid` and `Zoom` are visually identical whenever `Grid` would show a -/// single pane, so the cycle skips `Zoom` in that case (see -/// `TerminalState::zoom_distinct_from_grid` and -/// `App::toggle_terminal_fullscreen`). +/// single pane, so the cycle skips `Zoom` in that case. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum TerminalFullscreen { #[default] @@ -76,10 +70,9 @@ impl TerminalFullscreen { /// Compute the visible pane-index window `[start, start+len)` for a split /// grid capped at `max_visible` panes. `prev_start` is the previous window's /// start (0 for a fresh terminal); the window is nudged the minimum amount -/// needed to keep `active` inside it, rather than re-centering every call — -/// so paging through panes one at a time doesn't reshuffle the whole grid. -/// Shared by `TerminalState::sync_visible_window` (state update) and -/// `ui::terminal_tab` (rendering) so both always agree on what's visible. +/// needed to keep `active` inside it, rather than re-centering every call. +/// Shared by `TerminalState::sync_visible_window` and `ui::terminal_tab` so +/// both always agree on what's visible. pub(crate) fn visible_range( prev_start: usize, active: usize, @@ -106,21 +99,18 @@ pub struct TerminalState { pub panes: Vec, pub active: usize, /// Default size used to create a pane before any layout resize has run - /// (e.g. the very first pane on startup). Once a pane has a real content - /// Rect, its size lives in `last_content_size` instead. + /// (e.g. the very first pane on startup). pub size: (u16, u16), pub scroll: HashMap, pub fullscreen: TerminalFullscreen, /// Last (rows, cols) applied to each pane's backend + emulator via - /// `resize_visible_panes`. Panes currently scrolled out of the visible - /// window keep whatever size they had when they were last visible. + /// `resize_visible_panes`. Panes scrolled out of the visible window keep + /// whatever size they had when they were last visible. pub last_content_size: HashMap, /// Whether this client's layout is what sets the pane sizes. /// /// True unless a shared session says otherwise: a PTY has one size, so one - /// client decides it and the others render the grid they are given. Panes - /// this client owns follow its layout; panes it does not follow - /// [`BackendEvent::Resized`](crate::backend::BackendEvent::Resized). + /// client decides it and the others render the grid they are given. pub owns_size: bool, /// What each pane's plugin last reported about recovering it, for the panes /// any has spoken about. Deliberately outlives a pane's process: the report diff --git a/src/runtime/terminal/state.rs b/src/runtime/terminal/state.rs index 2c638508..63245045 100644 --- a/src/runtime/terminal/state.rs +++ b/src/runtime/terminal/state.rs @@ -46,6 +46,14 @@ impl TerminalState { .unwrap_or(self.size.0 as usize) } + /// Whether the active pane requested application cursor keys (DECCKM). + /// With no pane or emulator, terminal input uses the normal CSI form. + pub fn active_pane_app_cursor(&self) -> bool { + self.active_pane_id() + .and_then(|id| self.emulators.get(&id)) + .is_some_and(PaneEmulator::app_cursor) + } + /// Re-clamp `visible_start` against the current active pane and pane /// count. Must be called after anything that changes `active` or /// `panes.len()` (focus jumps, pane create/close, session restore) so diff --git a/src/runtime/terminal/tests/state_tests.rs b/src/runtime/terminal/tests/state_tests.rs index 2da62dae..80bea330 100644 --- a/src/runtime/terminal/tests/state_tests.rs +++ b/src/runtime/terminal/tests/state_tests.rs @@ -102,3 +102,19 @@ fn active_pane_rows_falls_back_to_default_with_no_panes() { let state = state_with_fake(); assert_eq!(state.active_pane_rows(), state.size.0 as usize); } + +#[test] +fn active_pane_application_cursor_mode_tracks_its_emulator() { + let mut state = state_with_fake(); + assert!(!state.active_pane_app_cursor()); + state.create_pane_now().unwrap(); + let pane = state.panes[0].id; + + state + .emulators + .get_mut(&pane) + .expect("pane emulator") + .process(b"\x1b[?1h"); + + assert!(state.active_pane_app_cursor()); +} diff --git a/src/runtime/tree_watch.rs b/src/runtime/tree_watch.rs index 47d517c2..6d3879b6 100644 --- a/src/runtime/tree_watch.rs +++ b/src/runtime/tree_watch.rs @@ -1,20 +1,11 @@ //! Filesystem watcher for the file-tree navigator (`ViewMode::Tree`). //! -//! The tree caches one directory level at a time and only re-reads on Tree-mode -//! entry; this watcher closes the gap so a folder created/moved/renamed/deleted -//! while Tree mode is open shows up without leaving and re-entering. It watches -//! only the directories the user has actually expanded (plus the root) — +//! Watches only the directories the user has actually expanded (plus the root) — //! NON-recursively — mirroring yazi/broot/nvim-tree. A recursive watch over the //! whole work tree would consume one inotify watch per directory and fall over -//! on large repositories (the reason broot keeps recursive watching off by -//! default), so the watch set is bounded to what is visible. -//! -//! Events are coalesced by `notify-debouncer-mini` over a short window and -//! reported as the set of repo-relative directories whose contents changed, so -//! the navigator can re-read just those instead of the whole cache. -//! Refresh-on-entry remains the fallback when the watcher cannot start (e.g. a -//! platform/filesystem where native events never arrive), so this layer is -//! strictly additive — its absence degrades to the previous behaviour. +//! on large repositories. Events are coalesced by `notify-debouncer-mini` and +//! reported as the set of repo-relative directories whose contents changed. +//! Refresh-on-entry remains the fallback when the watcher cannot start. use notify::RecursiveMode; use notify_debouncer_mini::{DebounceEventResult, Debouncer, new_debouncer}; @@ -33,8 +24,8 @@ const DEBOUNCE: Duration = Duration::from_millis(300); /// repo-relative directories. Dropping it stops the watcher thread. /// /// The working-tree root is supplied to `sync` per call rather than stored, so -/// a repo switch needs only a fresh `TreeWatcher` (no stale root to carry) and -/// construction does not depend on a repository handle being open yet. +/// a repo switch needs only a fresh `TreeWatcher` and construction does not +/// depend on a repository handle being open yet. /// /// In tests (and when the watcher fails to start) `debouncer` is `None`: the /// receiver still exists so `App` polling is uniform, and watch/unwatch calls @@ -98,8 +89,7 @@ impl TreeWatcher { /// An inert watcher that never observes anything: no OS watcher is created /// and `sync` performs no filesystem calls. Used when live watching is - /// disabled by config so the feature costs nothing (no watcher thread, no - /// inotify descriptors), with refresh-on-entry carrying the navigator. + /// disabled by config, with refresh-on-entry carrying the navigator. pub fn disabled() -> Self { // A dropped sender makes `drain_changed` see `Disconnected` and report // no changes; the field shape stays uniform with the active watcher. diff --git a/src/web/viewer/catalog/catalog_ids.rs b/src/session/catalog/catalog_ids.rs similarity index 73% rename from src/web/viewer/catalog/catalog_ids.rs rename to src/session/catalog/catalog_ids.rs index 1adf6da6..4c17e2b8 100644 --- a/src/web/viewer/catalog/catalog_ids.rs +++ b/src/session/catalog/catalog_ids.rs @@ -1,6 +1,5 @@ -use crate::web::viewer::dto::RepoDto; -use crate::web::viewer::runtime::RepoRuntime; -use crate::web::viewer::terminal::TerminalHub; +use crate::session::runtime::RepoRuntime; +use crate::session::terminal::TerminalHub; use std::collections::HashMap; use std::sync::Arc; @@ -15,13 +14,13 @@ pub struct RepoEntry { pub display_path: String, pub runtime: Arc, /// This repository's terminals. Independent of the TUI's panes — see - /// [`crate::web::viewer::terminal`]. + /// [`crate::session::terminal`]. pub terminals: Arc, } impl RepoEntry { - pub fn to_dto(&self) -> RepoDto { - RepoDto { + pub fn info(&self) -> RepoInfo { + RepoInfo { id: self.id.clone(), name: self.name.clone(), display_path: self.display_path.clone(), @@ -29,6 +28,15 @@ impl RepoEntry { } } +/// Repository identity safe to expose to a client. Absolute paths stay on +/// [`RepoEntry`] and are projected separately for attached local clients. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RepoInfo { + pub id: String, + pub name: String, + pub display_path: String, +} + /// Hands out ids and never reuses one, so a path that leaves and comes back /// keeps the identity a client already knows. #[derive(Default)] @@ -49,10 +57,10 @@ impl IdAssigner { } } -/// Result of [`crate::web::viewer::catalog::Catalog::add_path`]. +/// Result of [`crate::session::catalog::Catalog::add_path`]. pub enum AddOutcome { /// The repository is now served — newly added, or already present. - Added(RepoDto), + Added(RepoInfo), /// The served set is already at its ceiling; nothing was added. TooMany, } diff --git a/src/web/viewer/catalog/catalog_tests/config_tables.rs b/src/session/catalog/catalog_tests/config_tables.rs similarity index 100% rename from src/web/viewer/catalog/catalog_tests/config_tables.rs rename to src/session/catalog/catalog_tests/config_tables.rs diff --git a/src/web/viewer/catalog/catalog_tests/mod.rs b/src/session/catalog/catalog_tests/mod.rs similarity index 96% rename from src/web/viewer/catalog/catalog_tests/mod.rs rename to src/session/catalog/catalog_tests/mod.rs index 43b45255..6db37d22 100644 --- a/src/web/viewer/catalog/catalog_tests/mod.rs +++ b/src/session/catalog/catalog_tests/mod.rs @@ -238,16 +238,20 @@ fn an_unknown_id_does_not_resolve() { } #[test] -fn the_dto_exposes_only_the_whitelisted_identity_fields() { +fn public_info_omits_the_absolute_repository_path() { let (dir_a, a) = make_repo(); let catalog = Catalog::new(); catalog.set_paths(std::slice::from_ref(&a)); - let value = serde_json::to_value(&catalog.list()[0]).unwrap(); - - let mut keys: Vec<_> = value.as_object().unwrap().keys().cloned().collect(); - keys.sort(); - assert_eq!(keys, vec!["display_path", "id", "name"]); + let info = catalog.list().remove(0); + let RepoInfo { + id, + name, + display_path, + } = info; + assert_eq!(id, "r1"); + assert!(!name.is_empty()); + assert!(!display_path.is_empty()); catalog.shutdown(); drop(dir_a); } diff --git a/src/web/viewer/catalog/catalog_tests/ordering.rs b/src/session/catalog/catalog_tests/ordering.rs similarity index 100% rename from src/web/viewer/catalog/catalog_tests/ordering.rs rename to src/session/catalog/catalog_tests/ordering.rs diff --git a/src/web/viewer/catalog/config_tables.rs b/src/session/catalog/config_tables.rs similarity index 61% rename from src/web/viewer/catalog/config_tables.rs rename to src/session/catalog/config_tables.rs index ec432b58..e04d86e5 100644 --- a/src/web/viewer/catalog/config_tables.rs +++ b/src/session/catalog/config_tables.rs @@ -2,29 +2,15 @@ //! //! Separate from the served set because they answer a different question. The //! catalog proper is "which repositories exist"; this is "what does a repository -//! start as", which changes for an entirely different reason — the user edited -//! `config.toml` — and reaches only the hubs spawned afterwards. +//! start as", which changes when the user edits `config.toml` and reaches only +//! the hubs spawned afterwards. use super::Catalog; use std::sync::{Arc, Mutex}; impl Catalog { - /// Like [`Catalog::new`], but every terminal hub it spawns runs `startup` - /// as its startup terminals. - pub fn with_startup(startup_commands: Vec) -> Self { - Self { - startup_commands: Mutex::new(startup_commands), - ..Self::default() - } - } - - /// Like [`Catalog::with_startup`], and every hub is also given the - /// `[[plugin]]` table its startup commands may name. - /// - /// Paired with the startup commands rather than set separately, because the - /// two are one decision: a plugin is only ever reachable through a startup - /// command's `plugin =`, so a catalog with one and not the other is a - /// half-configured session. + /// Like [`Catalog::new`], with startup terminals and their plugin table. + #[cfg(test)] pub fn with_startup_and_plugins( startup_commands: Vec, plugins: Vec, @@ -32,13 +18,12 @@ impl Catalog { Self::with_startup_plugins_and_exec(startup_commands, plugins, Vec::new()) } - /// Like [`Catalog::with_startup_and_plugins`], remembering the `--exec` - /// commands that were merged into `startup_commands`. + /// Test constructor that also remembers the `--exec` commands merged into + /// `startup_commands`. /// /// Kept apart from the merged list rather than derived from it: a reload - /// re-reads the file and has to arrive at the same combined list, which means - /// knowing which of the panes came from the command line — nothing in the - /// merged list says. + /// re-reads the file and has to arrive at the same combined list. + #[cfg(test)] pub fn with_startup_plugins_and_exec( startup_commands: Vec, plugins: Vec, @@ -52,19 +37,20 @@ impl Catalog { } } - /// Like [`Catalog::with_startup_plugins_and_exec`], also setting the shell - /// every terminal pane is spawned with. + /// Construct the catalog tables and shell used by newly spawned panes. pub fn with_startup_plugins_exec_and_shell( startup_commands: Vec, plugins: Vec, cli_startup: Vec, shell: crate::config::ShellConfig, + status_encoder: crate::session::StatusEncoder, ) -> Self { Self { startup_commands: Mutex::new(startup_commands), plugins: Mutex::new(plugins), cli_startup, shell, + status_encoder: Some(status_encoder), ..Self::default() } } @@ -72,26 +58,17 @@ impl Catalog { /// Replace both configured tables, as a config reload does. /// /// `file_startup` is the file's `[[startup_command]]` table alone; the - /// remembered `--exec` panes are merged back on here, so the list a newly - /// opened repository gets is the one a restart would have produced. A merge - /// that would exceed the pane cap is refused and *neither* table is - /// replaced — a reload does not half-apply. + /// remembered `--exec` panes are merged back on here. A merge that would + /// exceed the pane cap is refused and *neither* table is replaced. /// /// Only the hubs spawned after this see the startup list. Telling the ones - /// already running is the caller's job (see - /// [`crate::web::viewer::reload`]) because it means restarting plugin - /// children, which is not a catalog concern. The entries to tell are returned - /// rather than fetched afterwards, which is what makes the split safe — see - /// below. + /// already running is the caller's job (see [`crate::session::reload`]). + /// The entries to tell are returned rather than fetched afterwards. /// /// Taken under the mutation lock, the same one every rebuild holds. Without /// it a repository opened in the same beat could fall between the two halves: /// its hub reads the old tables while the swap is still to come, and the - /// swap's snapshot is taken while its entry is still to be installed. Nobody - /// would then tell that hub, and it would run the previous `[[plugin]]` table - /// for as long as it stayed open. Holding the lock leaves only the two - /// orderings that are both correct: the open lands first and is in the - /// snapshot, or it lands second and reads the new tables. + /// swap's snapshot is taken while its entry is still to be installed. pub fn set_config_tables( &self, file_startup: &[crate::config::StartupCommand], @@ -111,6 +88,7 @@ impl Catalog { /// The `[[plugin]]` table as it stands, for the caller that has to tell the /// running hubs about it. + #[cfg(test)] pub fn plugins(&self) -> Vec { self.plugins .lock() @@ -120,6 +98,7 @@ impl Catalog { /// The merged startup list as it stands — configured panes then `--exec` /// ones. What the next hub will be given. + #[cfg(test)] pub fn startup_commands(&self) -> Vec { self.startup_commands .lock() diff --git a/src/web/viewer/catalog/mod.rs b/src/session/catalog/mod.rs similarity index 81% rename from src/web/viewer/catalog/mod.rs rename to src/session/catalog/mod.rs index acfc75c1..3c046f28 100644 --- a/src/web/viewer/catalog/mod.rs +++ b/src/session/catalog/mod.rs @@ -1,13 +1,9 @@ //! The set of repositories the viewer serves, and their runtimes. //! -//! Clients address a repository by an opaque `id`, never by path. That is the -//! whole point: a request cannot name a directory, only pick one the server -//! already decided to expose, so "which repository" is not an input to -//! validate — it is a lookup that either hits or 404s. -//! -//! Ids are stable for the process lifetime. A path keeps its id across catalog -//! updates, so opening or closing an unrelated tab does not renumber the others -//! and invalidate a client's bookmarks mid-session. +//! Clients address a repository by an opaque `id`, never by path — a request +//! cannot name a directory, only pick one the server already decided to expose. +//! Ids are stable for the process lifetime, so opening or closing an unrelated +//! tab does not renumber the others. //! //! Replacement is atomic and does no blocking work under the lock: the new list //! is built, swapped in, and only then are the dropped runtimes stopped — a @@ -22,8 +18,9 @@ //! Holding the invariant at the boundary is what keeps the next entry point from //! having to remember. -use crate::web::viewer::runtime::RepoRuntime; -use crate::web::viewer::terminal::TerminalHub; +use crate::session::StatusEncoder; +use crate::session::runtime::RepoRuntime; +use crate::session::terminal::TerminalHub; use std::path::Path; use std::sync::{Arc, Mutex}; @@ -31,7 +28,7 @@ mod catalog_ids; mod config_tables; mod ordering; use catalog_ids::IdAssigner; -pub use catalog_ids::{AddOutcome, RepoEntry}; +pub use catalog_ids::{AddOutcome, RepoEntry, RepoInfo}; #[derive(Default)] pub struct Catalog { @@ -41,51 +38,36 @@ pub struct Catalog { /// Repositories supplied by the CLI (`serve --repo`) or pushed from the TUI /// workspace. Replaced wholesale by [`Catalog::set_paths`]. base: Mutex>, - /// Repositories opened from the browser. Kept across `base` updates so a - /// workspace tab change in the TUI does not drop them. + /// Repositories opened from the browser. Kept across `base` updates. added: Mutex>, /// Repositories closed from the browser. Subtracted from the served set so - /// a `base` re-sync (a TUI tab change) does not resurrect a closed repo; - /// re-opening a path clears it from here. + /// a `base` re-sync does not resurrect a closed repo. hidden: Mutex>, order: Mutex>, /// Commands each repository's terminal hub runs as startup terminals on the - /// first client connect (empty = one bare shell). Applied to every hub the - /// catalog spawns. - /// - /// Behind a lock because a config reload replaces it. What that reaches is - /// only the hubs spawned *after* it: a hub creates its startup panes once - /// for its life, so a repository already open has spent this list, and the - /// panes it spent it on are running children nobody may replace on the - /// strength of a file edit. + /// first client connect. Behind a lock because a config reload replaces it; + /// only hubs spawned *after* the reload see the new list. startup_commands: Mutex>, /// The `--exec` panes the daemon was started with, appended after the - /// configured ones. Not behind a lock: these came from the command line, and - /// a reload of the config file cannot change what that said. + /// configured ones. Not behind a lock: these came from the command line. cli_startup: Vec, - /// The `[[plugin]]` table, handed to every hub the catalog spawns. A hub only - /// launches the ones its own startup commands opted into, so an entry here is - /// an offer rather than a process. - /// - /// Replaced by a reload like the list above, but with a further reach: the - /// hubs already running are told as well, because a plugin is a child process - /// rather than a pane and restarting one costs the session nothing. + /// The `[[plugin]]` table, handed to every hub the catalog spawns. Replaced + /// by a reload; the hubs already running are told as well, because a plugin + /// is a child process and restarting one costs the session nothing. plugins: Mutex>, /// The shell every terminal pane is spawned with. Fixed for the session's /// life: a config reload does not replace the shell of a running hub. shell: crate::config::ShellConfig, - /// Which screen this session's panes are fitted to, shared by every hub the - /// catalog spawns. - /// - /// One value for the session rather than one per repository: every client - /// shows the same repository (the daemon owns which is in front), so "which - /// screen is this fitted to" has a single answer. Asked per hub, it was - /// re-answered on every switch — see - /// [`crate::web::viewer::size_owner`]. - ownership: Arc, + /// Which screen this session's panes are fitted to, shared by every hub. + /// One value for the session rather than one per repository — see + /// [`crate::session::size_owner`]. + ownership: Arc, + /// Surface-owned status representation cached by each repository runtime. + status_encoder: Option, } impl Catalog { + #[cfg(test)] pub fn new() -> Self { Self::default() } @@ -146,8 +128,8 @@ impl Catalog { } } self.rebuild(); - match self.dto_for_path(&path) { - Some(dto) => AddOutcome::Added(dto), + match self.info_for_path(&path) { + Some(info) => AddOutcome::Added(info), // rebuild always creates the entry; this only trips if a concurrent // set_paths raced it back out, which the caller can treat as full. None => AddOutcome::TooMany, @@ -173,13 +155,13 @@ impl Catalog { self.rebuild(); } - fn dto_for_path(&self, path: &str) -> Option { + fn info_for_path(&self, path: &str) -> Option { self.entries .lock() .expect("catalog poisoned") .iter() .find(|e| e.path == path) - .map(|e| e.to_dto()) + .map(|e| e.info()) } /// Reconcile the live entries to `union_paths()`. A path already present @@ -221,7 +203,10 @@ impl Catalog { None => next.push(Arc::new(RepoEntry { name: repo_name(&path), display_path: display_path(&path), - runtime: RepoRuntime::spawn(&path), + runtime: RepoRuntime::spawn( + &path, + self.status_encoder.unwrap_or(empty_status_payload), + ), terminals: TerminalHub::spawn( &path, startup.clone(), @@ -260,6 +245,13 @@ impl Catalog { } } +fn empty_status_payload( + _: &crate::git::diff::RepoSnapshot, + _: &std::collections::HashMap, +) -> Option { + Some("{}".to_string()) +} + pub(super) fn repo_name(path: &str) -> String { Path::new(path.trim_end_matches('/')) .file_name() diff --git a/src/web/viewer/catalog/ordering.rs b/src/session/catalog/ordering.rs similarity index 100% rename from src/web/viewer/catalog/ordering.rs rename to src/session/catalog/ordering.rs diff --git a/src/web/viewer/catalog/views.rs b/src/session/catalog/views.rs similarity index 76% rename from src/web/viewer/catalog/views.rs rename to src/session/catalog/views.rs index ab1181cc..f86380d5 100644 --- a/src/web/viewer/catalog/views.rs +++ b/src/session/catalog/views.rs @@ -1,21 +1,19 @@ //! Reading the served set: the projections each surface needs of it. //! //! Each snapshots the same map under one lock. Reading it in two calls lets a -//! repository opened in between appear in one and not the other — and a client -//! handed a selection that is not in the list it was sent falls back and records -//! the fallback, overwriting what was remembered. +//! repository opened in between appear in one and not the other. -use super::{Catalog, RepoEntry}; +use super::{Catalog, RepoEntry, RepoInfo}; use std::collections::HashMap; use std::sync::Arc; /// One snapshot of the served set, in the three shapes a response needs it in. pub struct ServedView { - pub list: Vec, + pub list: Vec, /// Id standing for the remembered project, when it is served. pub active: Option, /// Which panel each served project was left maximized in, by id. - pub maximized: HashMap, + pub maximized: HashMap, } impl Catalog { @@ -29,13 +27,9 @@ impl Catalog { } /// Every served entry, for a caller that needs the runtimes themselves - /// rather than a client-facing projection — a config reload has to reach each - /// repository's terminal hub. + /// rather than a client-facing projection. /// - /// A snapshot: the `Arc`s are cloned out and the lock released, so a - /// repository retired while the caller is working through the list is one - /// whose hub has already stopped, and telling a stopped hub anything is a - /// no-op rather than a race. + /// A snapshot: the `Arc`s are cloned out and the lock released. pub fn entries(&self) -> Vec> { self.entries .lock() @@ -49,17 +43,15 @@ impl Catalog { /// `remembered`. /// /// One lock for both, because a client renders them together: a repository - /// opened between two separate reads would yield an active id that is - /// missing from the list beside it, and the client — seeing a selection it - /// cannot show — would fall back to its first tab and record *that*, - /// dropping the remembered project for good. + /// opened between two separate reads would yield an active id missing from + /// the list beside it. pub fn list_with_active( &self, remembered: Option<&str>, - maximized: &[crate::web::viewer::prefs::RepoMaximized], + maximized: &[crate::session::prefs::RepoMaximized], ) -> ServedView { let entries = self.entries.lock().expect("catalog poisoned"); - let list = entries.iter().map(|e| e.to_dto()).collect(); + let list = entries.iter().map(|e| e.info()).collect(); let active = remembered.and_then(|path| { entries .iter() @@ -72,8 +64,8 @@ impl Catalog { let arrangements = entries .iter() .filter_map(|e| { - crate::web::viewer::prefs::maximized::panel_of(maximized, &e.path) - .map(|panel| (e.id.clone(), panel.as_str())) + crate::session::prefs::maximized::panel_of(maximized, &e.path) + .map(|panel| (e.id.clone(), panel)) }) .collect(); ServedView { @@ -95,12 +87,12 @@ impl Catalog { .map(|e| e.id.clone()) } - pub fn list(&self) -> Vec { + pub fn list(&self) -> Vec { self.entries .lock() .expect("catalog poisoned") .iter() - .map(|e| e.to_dto()) + .map(|e| e.info()) .collect() } @@ -131,10 +123,12 @@ impl Catalog { .collect() } + #[cfg(test)] pub fn len(&self) -> usize { self.entries.lock().expect("catalog poisoned").len() } + #[cfg(test)] pub fn is_empty(&self) -> bool { self.len() == 0 } diff --git a/src/session/limits.rs b/src/session/limits.rs new file mode 100644 index 00000000..737aa5af --- /dev/null +++ b/src/session/limits.rs @@ -0,0 +1,13 @@ +//! Resource ceilings owned by the shared terminal session. + +/// Terminals one repository may hold open at once. +pub const MAX_PTYS_PER_REPO: usize = 8; + +/// Bounds on a PTY's size. Client-supplied dimensions are clamped rather than +/// trusted because the child's allocation grows with the requested area. +pub const MIN_PANE_DIMENSION: u16 = 1; +pub const MAX_PANE_ROWS: u16 = 500; +pub const MAX_PANE_COLS: u16 = 1_100; + +/// Raw PTY bytes retained per terminal for a reconnecting client. +pub const MAX_TERMINAL_SCROLLBACK_BYTES: usize = 256 * 1024; diff --git a/src/session/mod.rs b/src/session/mod.rs new file mode 100644 index 00000000..18ef0d50 --- /dev/null +++ b/src/session/mod.rs @@ -0,0 +1,24 @@ +//! Transport-neutral state shared by every nightcrow session surface. +//! +//! The daemon owns this state. Browser HTTP and attached-terminal transports +//! translate their own requests into the operations exposed here; neither owns +//! the repositories, terminal hubs, preferences, or PTY size arbitration. + +pub mod catalog; +pub mod limits; +pub mod prefs; +pub mod reload; +pub mod runtime; +pub mod size_owner; +pub mod terminal; + +mod operations; +mod state; + +pub use operations::{ + CloseError, OpenError, SessionRepo, accent, active_repo, close_repo, focus_repo, list_repos, + list_session_repos, open_repo, reorder_repos, set_accent, +}; +#[cfg(test)] +pub use state::test_status_encoder; +pub use state::{SessionOptions, SessionState, StatusEncoder}; diff --git a/src/web/viewer/session.rs b/src/session/operations.rs similarity index 67% rename from src/web/viewer/session.rs rename to src/session/operations.rs index 89b8d2b9..0ed6f20a 100644 --- a/src/web/viewer/session.rs +++ b/src/session/operations.rs @@ -4,18 +4,12 @@ //! Opening, closing, and reordering are session operations, not HTTP ones. The //! browser reaches them over HTTP and an attaching client reaches them over the //! daemon socket, and both must land on exactly the same state change — so the -//! change lives here and each transport keeps only its own translation: status -//! codes and JSON envelopes on one side, frames on the other. +//! change lives here and each transport keeps only its own translation. //! -//! Nothing here authenticates. Deciding who may ask is the transport's job, -//! and the two transports answer it differently: the browser presents a session -//! cookie, while reaching the socket already required being the user who owns -//! it. Folding that decision in here would put a single "trusted" flag in the -//! one place both paths share. +//! Nothing here authenticates. Deciding who may ask is the transport's job. -use super::server::ViewerState; -use crate::web::viewer::catalog::AddOutcome; -use crate::web::viewer::dto::RepoDto; +use super::SessionState; +use crate::session::catalog::{AddOutcome, RepoInfo}; /// Why a repository could not be opened. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -38,8 +32,7 @@ pub enum CloseError { /// One repository as an attaching client sees it. /// /// Carries the absolute path, which the browser's `RepoDto` deliberately does -/// not: an attached client reads git from that path itself, on the same -/// filesystem the daemon is on. +/// not: an attached client reads git from that path itself. #[derive(Debug, Clone, PartialEq, Eq)] pub struct SessionRepo { pub id: String, @@ -48,12 +41,12 @@ pub struct SessionRepo { /// The repositories currently served, in catalog order, as the browser sees /// them. -pub fn list_repos(state: &ViewerState) -> Vec { +pub fn list_repos(state: &SessionState) -> Vec { state.catalog.list() } /// The same set as an attaching client sees it. -pub fn list_session_repos(state: &ViewerState) -> Vec { +pub fn list_session_repos(state: &SessionState) -> Vec { state .catalog .id_paths() @@ -66,8 +59,8 @@ pub fn list_session_repos(state: &ViewerState) -> Vec { /// /// The path arrives from outside, so it is expanded, checked, and resolved to /// the worktree root before the catalog ever sees it — two spellings of one -/// repository must collapse to a single entry rather than open twice. -pub fn open_repo(state: &ViewerState, raw_path: &str) -> Result { +/// repository must collapse to a single entry. +pub fn open_repo(state: &SessionState, raw_path: &str) -> Result { let raw = raw_path.trim(); if raw.is_empty() { return Err(OpenError::EmptyPath); @@ -104,13 +97,10 @@ pub fn open_repo(state: &ViewerState, raw_path: &str) -> Result Option { +/// or when what is on file is no longer served. `None` only when nothing is open +/// at all. The stored value is a path, because ids only live as long as the +/// process (see [`super::prefs`]), so this is where it is translated back. +pub fn active_repo(state: &SessionState) -> Option { let stored = state .prefs .get() @@ -127,43 +117,27 @@ pub fn active_repo(state: &ViewerState) -> Option { }) } -/// Record how the project named by `id` is arranged. `None` un-maximizes. -pub fn set_maximized( - state: &ViewerState, - id: &str, - panel: Option, -) -> Result<(), CloseError> { - let entry = state.catalog.get(id).ok_or(CloseError::UnknownRepo)?; - state.prefs.set_maximized(entry.path.clone(), panel); - Ok(()) -} - /// Focus the repository named by `id` for the whole session. /// /// Which project is in front is shared, not per-client: the daemon owns it, and -/// every client renders the one the session names. What each client keeps to -/// itself is everything *within* that project — the view mode, the cursor, the -/// scroll (see the plan's shared/per-client boundary). -pub fn focus_repo(state: &ViewerState, id: &str) -> Result<(), CloseError> { +/// every client renders the one the session names. +pub fn focus_repo(state: &SessionState, id: &str) -> Result<(), CloseError> { let entry = state.catalog.get(id).ok_or(CloseError::UnknownRepo)?; state.prefs.set_active_repo(entry.path.clone()); Ok(()) } /// The accent every surface of this session paints in. -pub fn accent(state: &ViewerState) -> usize { +pub fn accent(state: &SessionState) -> usize { state.prefs.get().accent } /// Set the session's accent, returning what was stored. /// -/// Shared like the active project rather than kept per surface: a session seen -/// from a TUI and a browser at once was showing two colours with nothing able to -/// say which was its own (see the boundary in `docs/architecture.md`). An index -/// past the end of the cycle wraps rather than being refused, matching -/// `Accent::from_index`, so a client that drifts out of range self-corrects from -/// what it reads back. -pub fn set_accent(state: &ViewerState, accent: usize) -> usize { +/// Shared like the active project rather than kept per surface. An index past +/// the end of the cycle wraps rather than being refused, matching +/// `Accent::from_index`. +pub fn set_accent(state: &SessionState, accent: usize) -> usize { state.prefs.set_accent(accent).accent } @@ -173,7 +147,7 @@ pub fn set_accent(state: &ViewerState, accent: usize) -> usize { /// The updated set is not returned: each transport reads it back in its own /// projection, and both must do so afterwards anyway since another client can /// change the set in between. -pub fn close_repo(state: &ViewerState, id: &str) -> Result<(), CloseError> { +pub fn close_repo(state: &SessionState, id: &str) -> Result<(), CloseError> { let entry = state.catalog.get(id).ok_or(CloseError::UnknownRepo)?; state.catalog.remove_path(&entry.path); persist_workspace(state); @@ -185,7 +159,7 @@ pub fn close_repo(state: &ViewerState, id: &str) -> Result<(), CloseError> { /// Ids that no longer name a repository are dropped rather than refused: the /// only way to send one is to have raced a close on another client, and the /// catalog canonicalizes the requested order against what is actually live. -pub fn reorder_repos(state: &ViewerState, ids: &[String]) { +pub fn reorder_repos(state: &SessionState, ids: &[String]) { let paths: Vec = ids .iter() .filter_map(|id| state.catalog.get(id).map(|entry| entry.path.clone())) @@ -199,7 +173,7 @@ pub fn reorder_repos(state: &ViewerState, ids: &[String]) { /// `persist` (headless `serve`); alongside the TUI, the TUI owns that file. The /// existing per-repo view state and active tab are preserved; only the /// open-repo list is rewritten. -fn persist_workspace(state: &ViewerState) { +fn persist_workspace(state: &SessionState) { if !state.persist { return; } diff --git a/src/web/viewer/prefs/maximized.rs b/src/session/prefs/maximized.rs similarity index 58% rename from src/web/viewer/prefs/maximized.rs rename to src/session/prefs/maximized.rs index 57a5e84f..4f880478 100644 --- a/src/web/viewer/prefs/maximized.rs +++ b/src/session/prefs/maximized.rs @@ -1,42 +1,24 @@ //! Which panel each project was left maximized in. //! -//! The one preference here that belongs to a *project* rather than to the -//! viewer as a whole. "How is this repository's screen arranged" is view state, -//! and the TUI has kept the same thing per repository for as long as it has had -//! a session file (`workspace/persistence.rs`: `terminal_fullscreen`, -//! `diff_fullscreen`, `list_fullscreen`). This is the browser's half of that. -//! -//! **Not written into the TUI's file, and not shared with it.** `workspace.json` -//! belongs to the TUI whenever one is attached (`ViewerState::persist`), and the -//! arrangement would not carry anyway: maximizing on a 40-row terminal and in a -//! 1400 px window are not the same answer, which is the same reason `upper_pct` -//! is kept apart from `layout.upper_pct`. -//! -//! **Keyed by absolute path, like `active_repo` and for the same reason.** Repo -//! ids only live as long as the process (`catalog.rs`), so an id on disk would -//! name nothing after a restart — which is exactly the case this exists for. -//! The server translates; the client only ever sees ids. +//! The one preference that belongs to a *project* rather than to the viewer as a +//! whole. Not shared with the TUI: maximizing on a 40-row terminal and in a +//! 1400 px window are not the same answer. Keyed by absolute path, like +//! `active_repo` and for the same reason — repo ids only live as long as the +//! process. use serde::{Deserialize, Serialize}; -/// How many projects' arrangement to remember. Past this the entries whose -/// arrangement was set longest ago go, so the file cannot grow for every -/// repository ever opened. -/// +/// How many projects' arrangement to remember. Past this the oldest entries go. /// Ordered by when the arrangement was *set*, not when the project was last -/// looked at. Use-ordering would mean a preference write on every project -/// switch, to save a project that has had fifty others maximized after it — -/// which is the only way to reach the cap at all. The same bound the TUI puts on its own per-repo state -/// (`workspace::persistence::MAX_REMEMBERED`), for the same reason — stated -/// again rather than imported, because nothing would make the two wrong -/// together if one changed. +/// looked at — use-ordering would mean a preference write on every project +/// switch. Matches the TUI's `MAX_REMEMBERED` for the same reason. pub const MAX_REMEMBERED_MAXIMIZED: usize = 50; /// The panel filling the window, when one is. /// -/// "Nothing is maximized" is the absence of an entry rather than a variant: -/// that is the overwhelmingly common state, and storing it would mean a row on -/// file for every project a person ever glanced at. +/// "Nothing is maximized" is the absence of an entry rather than a variant — +/// that is the common state, and storing it would mean a row on file for every +/// project ever glanced at. #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] pub enum MaximizedPanel { @@ -46,7 +28,7 @@ pub enum MaximizedPanel { impl MaximizedPanel { /// Parse what a client sent. Unknown strings are `None` — the wire form is - /// a boundary input, and a panel nobody can render is not a 500. + /// a boundary input. pub fn parse(value: &str) -> Option { match value { "files" => Some(Self::Files), @@ -66,7 +48,7 @@ impl MaximizedPanel { /// One project's arrangement. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct RepoMaximized { - /// Absolute worktree path. See the module header for why not the id. + /// Absolute worktree path. pub repo: String, pub panel: MaximizedPanel, } diff --git a/src/web/viewer/prefs/maximized_tests.rs b/src/session/prefs/maximized_tests.rs similarity index 100% rename from src/web/viewer/prefs/maximized_tests.rs rename to src/session/prefs/maximized_tests.rs diff --git a/src/session/prefs/mod.rs b/src/session/prefs/mod.rs new file mode 100644 index 00000000..1f46a490 --- /dev/null +++ b/src/session/prefs/mod.rs @@ -0,0 +1,256 @@ +//! Preferences that follow the user rather than the browser they arrived in. +//! +//! Stored in `~/.nightcrow/viewer.json`. The accent is the session's (shared +//! with an attached TUI); `sidebar_width` and `upper_pct` are the viewer's alone +//! — the first has no TUI counterpart, and the second is deliberately not shared +//! because a percentage means different things on a terminal vs a browser window. + +pub mod maximized; +pub use maximized::{MaximizedPanel, RepoMaximized}; + +use crate::config::Accent; +use serde::{Deserialize, Serialize}; +use std::path::{Path, PathBuf}; +use std::sync::Mutex; + +/// Default sidebar width before the divider was dragged. +pub const DEFAULT_SIDEBAR_WIDTH: u32 = 460; +/// Bounds the stored sidebar width so both panes stay usable. +pub const MIN_SIDEBAR_WIDTH: u32 = 280; +pub const MAX_SIDEBAR_WIDTH: u32 = 720; + +/// Default split share for the diff panel (matches the TUI's `layout.upper_pct`). +pub const DEFAULT_UPPER_PCT: u32 = 55; +/// Bounds the split so neither half becomes a sliver. +pub const MIN_UPPER_PCT: u32 = 20; +pub const MAX_UPPER_PCT: u32 = 85; + +/// Everything the viewer remembers for its clients. +/// +/// Preferences are shared across clients, with `maximized` the single exception. +/// Repo ids are only stable for the process lifetime, so per-repo keys are stored +/// by **path** instead — the same reason `active_repo` is a path. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(default)] +pub struct ViewerPrefs { + /// Index into the accent cycle (`config::Accent::ALL`). + pub accent: usize, + /// File-sidebar width in CSS px, clamped to `[MIN, MAX]`. + pub sidebar_width: u32, + /// Share of the vertical split given to the diff panel, in percent. + /// + /// The viewer's own, not the session's — unlike the accent. A percentage + /// means different things on a terminal vs a browser window, so sharing with + /// the TUI's `layout.upper_pct` was rejected. + pub upper_pct: u32, + /// Absolute worktree path of the last-selected project. + /// + /// A **path**, not the repo id: ids only live as long as the process, so a + /// stored id would name nothing after a restart. The server translates; the + /// client never learns the path. `None` until a client selects a project. + pub active_repo: Option, + /// Which panel each project was left maximized in, most recently set first. + pub maximized: Vec, +} + +impl Default for ViewerPrefs { + fn default() -> Self { + Self { + accent: 0, + sidebar_width: DEFAULT_SIDEBAR_WIDTH, + upper_pct: DEFAULT_UPPER_PCT, + active_repo: None, + maximized: Vec::new(), + } + } +} + +/// The stored preferences plus the file they are written to. A missing or +/// corrupt file yields defaults. +pub struct PrefsStore { + /// `None` when the home directory cannot be determined (preferences apply + /// for the process lifetime but are not persisted). + path: Option, + state: Mutex, +} + +impl PrefsStore { + /// Load from `~/.nightcrow/viewer.json`, starting at `seed_accent` when + /// there is no file to read. The seed is `[theme] name` — only a missing or + /// unreadable file takes it; once the file exists it records a choice. + pub fn load_seeded(seed_accent: usize) -> Self { + let seeded = seeded_prefs(seed_accent); + match default_path() { + Some(path) => Self::at_or(path, seeded), + None => Self { + path: None, + state: Mutex::new(seeded), + }, + } + } + + /// Load from an explicit path (tests). + #[cfg(test)] + pub fn at(path: PathBuf) -> Self { + Self::at_or(path, ViewerPrefs::default()) + } + + /// Load from `path`, falling back to `absent` when there is nothing to read. + /// Clamps widths and splits on load so `get` never serves a value the write + /// path would have rejected. + fn at_or(path: PathBuf, absent: ViewerPrefs) -> Self { + let mut state = read(&path).unwrap_or(absent); + state.sidebar_width = state + .sidebar_width + .clamp(MIN_SIDEBAR_WIDTH, MAX_SIDEBAR_WIDTH); + state.upper_pct = state.upper_pct.clamp(MIN_UPPER_PCT, MAX_UPPER_PCT); + maximized::normalize(&mut state.maximized); + Self { + path: Some(path), + state: Mutex::new(state), + } + } + + pub fn get(&self) -> ViewerPrefs { + self.state.lock().expect("prefs poisoned").clone() + } + + /// Apply `change` and persist the result under the lock, so file and memory + /// stay in step. A failed write is logged and the in-memory value still + /// applies. + fn mutate(&self, change: impl FnOnce(&mut ViewerPrefs)) -> ViewerPrefs { + let mut state = self.state.lock().expect("prefs poisoned"); + change(&mut state); + if let Some(path) = &self.path { + write(path, &state); + } + state.clone() + } + + /// Apply any subset of the preferences in one locked write. `None` leaves a + /// field as it is. Accent wraps into range; width clamps. + pub fn update(&self, change: PrefsUpdate) -> ViewerPrefs { + self.mutate(|state| { + if let Some(accent) = change.accent { + state.accent = accent % Accent::ALL.len(); + } + if let Some(width) = change.sidebar_width { + state.sidebar_width = width.clamp(MIN_SIDEBAR_WIDTH, MAX_SIDEBAR_WIDTH); + } + if let Some(pct) = change.upper_pct { + state.upper_pct = pct.clamp(MIN_UPPER_PCT, MAX_UPPER_PCT); + } + if let Some(path) = change.active_repo { + state.active_repo = Some(path); + } + if let Some(change) = change.maximized { + maximized::remember(&mut state.maximized, &change.repo, change.panel); + } + }) + } + + /// Record how one project's screen is arranged. `None` un-maximizes. + #[cfg(test)] + pub fn set_maximized(&self, repo: String, panel: Option) -> ViewerPrefs { + self.update(PrefsUpdate { + maximized: Some(MaximizedUpdate { repo, panel }), + ..PrefsUpdate::default() + }) + } + + /// Store `accent` alone. + pub fn set_accent(&self, accent: usize) -> ViewerPrefs { + self.update(PrefsUpdate { + accent: Some(accent), + ..PrefsUpdate::default() + }) + } + + /// Store the sidebar width alone. + #[cfg(test)] + pub fn set_sidebar_width(&self, width: u32) -> ViewerPrefs { + self.update(PrefsUpdate { + sidebar_width: Some(width), + ..PrefsUpdate::default() + }) + } + + /// Store the split percentage alone. + #[cfg(test)] + pub fn set_upper_pct(&self, pct: u32) -> ViewerPrefs { + self.update(PrefsUpdate { + upper_pct: Some(pct), + ..PrefsUpdate::default() + }) + } + + /// Store the active project's absolute path alone. There is deliberately no + /// way to clear it: closing the last project leaves no path worth recording, + /// and keeping the old one means it is still the selection when that project + /// is opened again. + pub fn set_active_repo(&self, path: String) -> ViewerPrefs { + self.update(PrefsUpdate { + active_repo: Some(path), + ..PrefsUpdate::default() + }) + } +} + +/// The preferences a single write may carry, each `None` when the request left +/// it alone. A struct rather than positional arguments because several fields +/// share a type — two adjacent `Option` at a call site would swap silently. +#[derive(Debug, Clone, Default)] +pub struct PrefsUpdate { + pub accent: Option, + pub sidebar_width: Option, + pub upper_pct: Option, + pub active_repo: Option, + pub maximized: Option, +} + +/// One project's arrangement, as a write carries it. Two `Option`s deep because +/// the outer one means "this request said nothing about maximizing" and the +/// inner one means "this project is no longer maximized" — collapsing them +/// would make un-maximizing indistinguishable from not mentioning it. +#[derive(Debug, Clone)] +pub struct MaximizedUpdate { + /// Absolute worktree path, resolved by the caller from a live repository. + pub repo: String, + pub panel: Option, +} + +/// Defaults with the accent a config seed asks for. Wrapped into the cycle here +/// rather than trusted: `[theme]` is a hand-edited file, and an index with no +/// colour behind it would reach every reader of the prefs. +fn seeded_prefs(accent: usize) -> ViewerPrefs { + ViewerPrefs { + accent: accent % Accent::ALL.len(), + ..ViewerPrefs::default() + } +} + +fn default_path() -> Option { + dirs::home_dir().map(|h| h.join(".nightcrow").join("viewer.json")) +} + +fn read(path: &Path) -> Option { + match crate::persistence::read_json(path) { + Ok(prefs) => prefs, + Err(e) => { + tracing::warn!("corrupted viewer prefs file, ignoring: {e}"); + None + } + } +} + +/// Write via a temporary file and rename, so a crash mid-write leaves the +/// previous preferences rather than a truncated file — the same handling +/// `session.rs` gives the workspace. +fn write(path: &Path, prefs: &ViewerPrefs) { + if let Err(e) = crate::persistence::write_json(path, prefs) { + tracing::warn!("failed to save viewer prefs: {e:#}"); + } +} + +#[cfg(test)] +mod tests; diff --git a/src/web/viewer/prefs/tests.rs b/src/session/prefs/tests.rs similarity index 100% rename from src/web/viewer/prefs/tests.rs rename to src/session/prefs/tests.rs diff --git a/src/web/viewer/reload.rs b/src/session/reload.rs similarity index 84% rename from src/web/viewer/reload.rs rename to src/session/reload.rs index 393bfaab..98c2fc9e 100644 --- a/src/web/viewer/reload.rs +++ b/src/session/reload.rs @@ -4,32 +4,26 @@ //! Sits beside [`session`](super::session) and for the same reason: the browser //! reaches this over HTTP and an attached terminal over the daemon socket, and //! both must land on exactly the same state change. Neither transport -//! authenticates here — deciding who may ask is theirs, and they answer it -//! differently (a session cookie on one side, being the user who owns a 0600 -//! socket on the other). +//! authenticates here — deciding who may ask is theirs. //! //! **What a reload is, and what it is not.** It re-reads two tables and nothing //! else. `[[plugin]]` reaches even the repositories that are already open, //! because a plugin is a child process and replacing one costs the session //! nothing. `[[startup_command]]` reaches only the repositories opened -//! afterwards: a hub creates its startup panes once for its life, and the panes a -//! running repository already spent that list on are live children — an agent -//! CLI mid-task — that no file edit may replace. Everything else in the file -//! (the listener's address and password, logging, the client-owned layout and -//! input sections) is read once at startup and still needs a restart. +//! afterwards: a hub creates its startup panes once for its life, and the panes +//! a running repository already spent that list on are live children that no +//! file edit may replace. Everything else in the file is read once at startup +//! and still needs a restart. //! //! **It does not half-apply.** The whole file is parsed and validated first, so a -//! typo anywhere leaves the session exactly as it was and the error goes back to -//! whoever asked. +//! typo anywhere leaves the session exactly as it was. -use super::server::ViewerState; +use super::SessionState; /// Why a reload could not be carried out. The session is untouched in every case. #[derive(Debug)] pub enum ReloadError { - /// The file could not be read, did not parse, or failed validation. Carries - /// the message as written for the operator — these name the offending key, - /// which is the whole value of reporting it. + /// The file could not be read, did not parse, or failed validation. Config(anyhow::Error), } @@ -53,9 +47,7 @@ pub struct ReloadReport { /// Repositories whose plugins were re-applied. pub repos: usize, /// Repositories that could not even be asked, because their terminal worker - /// was too far behind to take the request. Counted separately and said out - /// loud: those keep the plugin children they had, and a report that called - /// that a success would be claiming a change the session did not make. + /// was too far behind to take the request. pub unreachable: usize, } @@ -96,12 +88,12 @@ impl ReloadReport { /// Re-read the config file and apply the two tables it owns. /// -/// Serialized against itself by [`ViewerState::reload_lock`]: two clients +/// Serialized against itself by [`SessionState::reload_lock`]: two clients /// pressing at once would otherwise interleave a table swap with another's /// fan-out, and the hubs could end up told about different files. The second /// caller waits and then re-reads, so it applies the same file rather than a /// stale copy of it. -pub fn reload_config(state: &ViewerState) -> Result { +pub fn reload_config(state: &SessionState) -> Result { let path = crate::config::config_file_path().map_err(ReloadError::Config)?; reload_config_at(state, &path) } @@ -109,7 +101,7 @@ pub fn reload_config(state: &ViewerState) -> Result { /// Path-explicit core of [`reload_config`], so the behaviour is testable against /// a temp file rather than the caller's real `~/.nightcrow/config.toml`. pub fn reload_config_at( - state: &ViewerState, + state: &SessionState, path: &std::path::Path, ) -> Result { let _serialized = state diff --git a/src/web/viewer/reload_tests.rs b/src/session/reload_tests.rs similarity index 100% rename from src/web/viewer/reload_tests.rs rename to src/session/reload_tests.rs diff --git a/src/web/viewer/runtime/mod.rs b/src/session/runtime/mod.rs similarity index 80% rename from src/web/viewer/runtime/mod.rs rename to src/session/runtime/mod.rs index 2656144d..fd2dfc13 100644 --- a/src/web/viewer/runtime/mod.rs +++ b/src/session/runtime/mod.rs @@ -2,25 +2,18 @@ //! //! The thread owns that repository's [`SnapshotChannel`], reduces each snapshot //! to a wire payload once, and hands it to every subscribed SSE client. -//! -//! `SnapshotChannel` is a single-consumer `mpsc`, so the viewer cannot share -//! the TUI's — it spawns its own. That is the cost of the server never -//! referencing `App`, which is also what lets it run headless. +//! `SnapshotChannel` is a single-consumer `mpsc`, so the viewer spawns its own — +//! the cost of the server never referencing `App`, which is also what lets it +//! run headless. //! //! **Fan-out is conflated, not queued.** A subscriber that writes slowly gets //! the newest status, never a backlog of stale ones: each holds a single slot //! that the publisher overwrites, plus a one-deep wakeup channel that coalesces. -//! Both are bounded by construction, so a stalled client costs one payload of -//! memory rather than growing without limit. -//! -//! No lock here is ever held across socket I/O: the publisher writes slots and -//! returns, and each client thread takes its update out of its own slot before -//! writing to the network. +//! No lock here is ever held across socket I/O. use crate::git::diff::RepoSnapshot; use crate::runtime::snapshot::{SnapshotChannel, SnapshotMsg, SnapshotWatch}; -use crate::web::viewer::dto::{Envelope, StatusDto}; -use crate::web::viewer::limits; +use crate::session::StatusEncoder; use std::collections::HashMap; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::mpsc::{self, TrySendError}; @@ -48,32 +41,32 @@ pub struct RepoRuntime { watch: SnapshotWatch, latest: Mutex>, subscribers: Mutex>, - next_seq: AtomicU64, next_subscriber_id: AtomicU64, stop: Arc, worker: Mutex>>, + encoder: StatusEncoder, } impl RepoRuntime { /// Start a runtime that watches `repo_path`. - pub fn spawn(repo_path: &str) -> Arc { + pub fn spawn(repo_path: &str, encoder: StatusEncoder) -> Arc { // Asleep from the start, not put to sleep after starting: a repository // is opened before anyone looks at it, and the browser subscribes when a // page does. let channel = SnapshotChannel::spawn_asleep(repo_path); - Self::start(channel, repo_path.to_string()) + Self::start(channel, repo_path.to_string(), encoder) } - fn start(channel: SnapshotChannel, label: String) -> Arc { + fn start(channel: SnapshotChannel, label: String, encoder: StatusEncoder) -> Arc { let runtime = Arc::new(Self { path: label.clone(), watch: channel.watch(), latest: Mutex::new(None), subscribers: Mutex::new(Vec::new()), - next_seq: AtomicU64::new(0), next_subscriber_id: AtomicU64::new(0), stop: Arc::new(AtomicBool::new(false)), worker: Mutex::new(None), + encoder, }); let worker_runtime = Arc::clone(&runtime); @@ -109,43 +102,20 @@ impl RepoRuntime { /// Reduce a snapshot to its wire form and hand it to every subscriber. fn publish(&self, snapshot: &RepoSnapshot, mtimes: &HashMap) { - let dto = StatusDto::from_snapshot( - &snapshot.files, - snapshot.tracking.as_ref(), - snapshot.head_oid, - snapshot.branch_name.as_deref(), - mtimes, - ); - let Ok(json) = serde_json::to_string(&Envelope::new(dto)) else { - tracing::warn!("viewer: status payload failed to serialize"); + let Some(json) = (self.encoder)(snapshot, mtimes) else { return; }; - if json.len() > limits::MAX_SSE_PAYLOAD_BYTES { - // Already capped by the DTO; this only fires if the ceilings drift - // apart, and dropping beats emitting a payload no client will read. - tracing::warn!(bytes = json.len(), "viewer: status payload over ceiling"); - return; - } - // One hold of `latest` from the check to the fan-out. Three threads - // publish — the runtime's, a REST handler calling `refresh_now`, and a - // first subscriber taking its own reading — and split into separate - // critical sections they interleave: two readings each pass the check, - // take sequence numbers in one order, and store them in the other, so - // the older of the two is what `latest` and every subscriber keep. The - // browser then shows a status that has already been superseded, and - // `seq` goes backwards under a name that means "newer". + // Keep duplicate detection, storage, and fan-out under one `latest` + // hold so the cache and every subscriber move to the same update. let mut latest = self.latest.lock().expect("latest slot poisoned"); - // The reader publishes only what changed, but `refresh_now` reads on - // demand and most of those find nothing new. Publishing them would burn - // a sequence number apiece, making `seq` useless for "did anything - // happen". Only a real change is an event. + // Periodic and on-demand reads often produce the same payload. Only a + // real change should wake subscribers. if latest.as_ref().is_some_and(|prev| *prev.json == json) { return; } let update = StatusUpdate { - seq: self.next_seq.fetch_add(1, Ordering::AcqRel), json: Arc::new(json), }; *latest = Some(update.clone()); diff --git a/src/session/runtime/runtime_tests.rs b/src/session/runtime/runtime_tests.rs new file mode 100644 index 00000000..f17c446f --- /dev/null +++ b/src/session/runtime/runtime_tests.rs @@ -0,0 +1,55 @@ +use super::*; +use crate::git::diff::{ChangedFile, StatusKind}; + +mod lifecycle; +mod publishing; +mod subscriptions; + +const TEST_PAYLOAD_VERSION: u32 = 1; + +fn encode_test_status(snapshot: &RepoSnapshot, _: &HashMap) -> Option { + serde_json::to_string(&serde_json::json!({ + "version": TEST_PAYLOAD_VERSION, + "branch": snapshot.branch_name, + "files": snapshot.files.iter().map(|file| &file.path).collect::>(), + })) + .ok() +} + +fn test_runtime() -> (Arc, mpsc::Sender) { + let (tx, rx) = mpsc::channel(); + let channel = SnapshotChannel::from_endpoints(rx); + ( + RepoRuntime::start(channel, "test".to_string(), encode_test_status), + tx, + ) +} + +fn snapshot(branch: &str, files: usize) -> RepoSnapshot { + RepoSnapshot { + files: (0..files) + .map(|i| { + ChangedFile::from_status_columns( + format!("f{i}.rs"), + None, + StatusKind::Modified, + StatusKind::Unmodified, + ) + }) + .collect(), + tracking: None, + head_oid: None, + branch_name: Some(branch.to_string()), + refs_fingerprint: 0, + } +} + +fn wait_for(mut check: impl FnMut() -> bool) -> bool { + for _ in 0..100 { + if check() { + return true; + } + thread::sleep(Duration::from_millis(20)); + } + false +} diff --git a/src/session/runtime/runtime_tests/lifecycle.rs b/src/session/runtime/runtime_tests/lifecycle.rs new file mode 100644 index 00000000..5b047a61 --- /dev/null +++ b/src/session/runtime/runtime_tests/lifecycle.rs @@ -0,0 +1,59 @@ +use super::*; + +#[test] +fn stop_is_idempotent() { + let (runtime, _tx) = test_runtime(); + runtime.stop(); + runtime.stop(); +} + +#[test] +fn a_repository_nobody_is_watching_is_not_walked() { + let (repo, path) = crate::test_util::make_repo(); + let runtime = RepoRuntime::spawn(&path, encode_test_status); + + assert!(!runtime.is_watching(), "opening is not reading"); + thread::sleep(Duration::from_millis(300)); + assert!( + runtime.latest().is_none(), + "nothing should publish without a subscriber" + ); + + let subscription = runtime.subscribe(); + assert!(runtime.is_watching(), "a subscriber starts the watch"); + assert!( + runtime.latest().is_some(), + "the first subscriber is answered immediately" + ); + + drop(subscription); + assert!( + !runtime.is_watching(), + "the last subscriber stops the watch" + ); + runtime.stop(); + drop(repo); +} + +#[test] +fn a_second_subscriber_does_not_disturb_the_watch() { + let (repo, path) = crate::test_util::make_repo(); + let runtime = RepoRuntime::spawn(&path, encode_test_status); + let first = runtime.subscribe(); + let before = runtime.latest().expect("the first subscriber read once"); + + let second = runtime.subscribe(); + + let after = runtime.latest().expect("status remains available"); + assert!( + Arc::ptr_eq(&after.json, &before.json), + "nothing was republished" + ); + assert!(runtime.is_watching()); + drop(second); + assert!(runtime.is_watching(), "one subscriber is still reading"); + drop(first); + assert!(!runtime.is_watching()); + runtime.stop(); + drop(repo); +} diff --git a/src/session/runtime/runtime_tests/publishing.rs b/src/session/runtime/runtime_tests/publishing.rs new file mode 100644 index 00000000..3256e374 --- /dev/null +++ b/src/session/runtime/runtime_tests/publishing.rs @@ -0,0 +1,49 @@ +use super::*; + +#[test] +fn runtime_publishes_a_snapshot_as_a_versioned_payload() { + let (runtime, tx) = test_runtime(); + tx.send(SnapshotMsg::Ok(snapshot("main", 2), Default::default())) + .unwrap(); + + assert!( + wait_for(|| runtime.latest().is_some()), + "no status published" + ); + let update = runtime.latest().unwrap(); + let value: serde_json::Value = serde_json::from_str(&update.json).unwrap(); + assert_eq!(value["version"], TEST_PAYLOAD_VERSION); + assert_eq!(value["branch"], "main"); + assert_eq!(value["files"].as_array().unwrap().len(), 2); + runtime.stop(); +} + +#[test] +fn an_unchanged_snapshot_is_not_republished() { + let (runtime, tx) = test_runtime(); + tx.send(SnapshotMsg::Ok(snapshot("main", 1), Default::default())) + .unwrap(); + assert!(wait_for(|| runtime.latest().is_some())); + let first = runtime.latest().unwrap(); + let subscription = runtime.subscribe(); + assert!( + subscription + .next_update(Duration::from_millis(50)) + .is_some() + ); + + for _ in 0..3 { + tx.send(SnapshotMsg::Ok(snapshot("main", 1), Default::default())) + .unwrap(); + } + thread::sleep(Duration::from_millis(200)); + + assert!(Arc::ptr_eq(&runtime.latest().unwrap().json, &first.json)); + assert!( + subscription + .next_update(Duration::from_millis(50)) + .is_none(), + "an unchanged snapshot must not wake subscribers" + ); + runtime.stop(); +} diff --git a/src/session/runtime/runtime_tests/subscriptions.rs b/src/session/runtime/runtime_tests/subscriptions.rs new file mode 100644 index 00000000..201bf3aa --- /dev/null +++ b/src/session/runtime/runtime_tests/subscriptions.rs @@ -0,0 +1,70 @@ +use super::*; + +#[test] +fn a_subscriber_is_seeded_with_the_current_status() { + let (runtime, tx) = test_runtime(); + tx.send(SnapshotMsg::Ok(snapshot("main", 1), Default::default())) + .unwrap(); + assert!(wait_for(|| runtime.latest().is_some())); + + let subscription = runtime.subscribe(); + assert!( + subscription + .next_update(Duration::from_millis(50)) + .is_some(), + "a fresh subscriber must replay the latest" + ); + runtime.stop(); +} + +#[test] +fn a_slow_subscriber_gets_the_newest_status_not_a_backlog() { + let (runtime, tx) = test_runtime(); + let subscription = runtime.subscribe(); + + for i in 0..5 { + tx.send(SnapshotMsg::Ok( + snapshot(&format!("b{i}"), 1), + Default::default(), + )) + .unwrap(); + assert!(wait_for(|| runtime + .latest() + .is_some_and(|u| u.json.contains(&format!("b{i}"))))); + } + + let update = subscription.next_update(Duration::from_millis(50)).unwrap(); + assert!(update.json.contains("b4"), "got: {}", update.json); + assert!( + subscription + .next_update(Duration::from_millis(50)) + .is_none(), + "no stale backlog may remain" + ); + runtime.stop(); +} + +#[test] +fn dropping_a_subscription_unregisters_it() { + let (runtime, _tx) = test_runtime(); + + let subscription = runtime.subscribe(); + assert_eq!(runtime.subscriber_count(), 1); + drop(subscription); + + assert_eq!(runtime.subscriber_count(), 0); + runtime.stop(); +} + +#[test] +fn next_update_times_out_when_nothing_changes() { + let (runtime, _tx) = test_runtime(); + let subscription = runtime.subscribe(); + + assert!( + subscription + .next_update(Duration::from_millis(30)) + .is_none() + ); + runtime.stop(); +} diff --git a/src/web/viewer/runtime/subscription.rs b/src/session/runtime/subscription.rs similarity index 94% rename from src/web/viewer/runtime/subscription.rs rename to src/session/runtime/subscription.rs index 91d62ff6..dba38a53 100644 --- a/src/web/viewer/runtime/subscription.rs +++ b/src/session/runtime/subscription.rs @@ -13,9 +13,6 @@ use std::time::Duration; /// One published status, already serialized so N subscribers cost one encode. #[derive(Debug, Clone)] pub struct StatusUpdate { - /// Monotonic per repository. Lets a client tell a replayed snapshot from a - /// newer one after a reconnect. - pub seq: u64, pub json: Arc, } diff --git a/src/web/viewer/size_owner.rs b/src/session/size_owner.rs similarity index 75% rename from src/web/viewer/size_owner.rs rename to src/session/size_owner.rs index 2d3f75b4..e8aedf31 100644 --- a/src/web/viewer/size_owner.rs +++ b/src/session/size_owner.rs @@ -1,28 +1,24 @@ //! Which screen the session's PTYs are fitted to. //! -//! A PTY is a contract with a child process, not data: the child draws for the -//! width it was told, and nothing can re-flow an alternate-screen program -//! afterwards. So the size is one value with one owner — the most recent viewer -//! to arrive (tmux's `window-size latest`), until another takes it. +//! A PTY is a contract with a child process: the child draws for the width it +//! was told, and nothing can re-flow an alternate-screen program afterwards. So +//! the size is one value with one owner — the most recent viewer to arrive +//! (tmux's `window-size latest`), until another takes it. //! //! **Why this is the session's and not each hub's.** Which repository is in -//! front is shared by the whole session ([`ClientMessage::FocusRepo`]), so every -//! client shows the same one. "Which screen is this session fitted to" is -//! therefore one question, not one per repository. Asked per hub, it was -//! re-answered from scratch on every switch — a browser's terminal socket is -//! tied to the repository it shows, so moving tabs made every attached page -//! reconnect at once and the sizing fell to whichever handshake finished last. +//! front is shared by the whole session, so "which screen is this session fitted +//! to" is one question, not one per repository. Asked per hub, it was re-answered +//! from scratch on every switch — a browser's terminal socket is tied to the +//! repository it shows, so moving tabs made every attached page reconnect at once +//! and the sizing fell to whichever handshake finished last. //! -//! **A viewer is not a connection.** `connect = the owner arrived` reads an -//! intention out of a socket opening, and a socket opens for reasons that are -//! not a person sitting down: a repository switch, a page reload, a network blip. -//! So a viewer names itself ([`ViewerId`]) and says outright whether it is newly -//! arrived; the session never infers it. Connections then come and go beneath a +//! **A viewer is not a connection.** A socket opens for reasons that are not a +//! person sitting down: a repository switch, a page reload, a network blip. So a +//! viewer names itself ([`ViewerId`]) and says outright whether it is newly +//! arrived; the session never infers it. Connections come and go beneath a //! viewer without moving anything. -//! -//! [`ClientMessage::FocusRepo`]: crate::daemon::protocol::ClientMessage::FocusRepo -use crate::web::viewer::terminal::frame::{ServerMessage, TerminalFrame}; +use crate::session::terminal::frame::{ServerMessage, TerminalFrame}; use std::collections::HashMap; use std::sync::Mutex; use std::sync::mpsc::SyncSender; @@ -33,25 +29,19 @@ use std::time::{Duration, Instant}; /// Switching repositories closes one terminal socket and opens another, and for /// the moment in between the owner is not connected to anything. Handing the /// sizing away there and back again would re-fit every pane twice for a viewer -/// that never went anywhere — and for an alternate-screen program that is a -/// forced repaint each way. -/// -/// Only the *release* is delayed; nothing claims by waiting. A viewer that has -/// really gone costs the next one this long before its panes fit, which is the -/// cheaper of the two mistakes. +/// that never went anywhere. Only the *release* is delayed; nothing claims by +/// waiting. pub const RELEASE_GRACE: Duration = Duration::from_secs(2); /// Who a client is, across however many connections it holds. /// -/// Two variants rather than one opaque string so a browser cannot name itself an -/// attached terminal: the browser half is client-supplied, the other is minted -/// by the daemon from its own client counter. +/// Two variants so a browser cannot name itself an attached terminal: the +/// browser half is client-supplied, the other is minted by the daemon. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ViewerId { /// One browser tab, by the id it generated for itself. Browser(String), - /// One attached TUI, by its daemon client id. Its subscriptions to every - /// open repository share this — they are one viewer, not one per tab. + /// One attached TUI, by its daemon client id. Attached(u64), } @@ -63,15 +53,12 @@ struct Announce { #[derive(Default)] struct Inner { - /// How many connections each present viewer holds. A viewer is present - /// while this is non-zero. + /// How many connections each present viewer holds. present: HashMap, - /// Present viewers in the order they arrived, so the sizing can pass to the - /// most recent one still here. Newest last. + /// Present viewers in arrival order, newest last. arrival: Vec, owner: Option, - /// When the owner's last connection went, if it has none now. `Some` is what - /// [`SizeOwnership::settle`] is counting down. + /// When the owner's last connection went, if it has none now. owner_absent_since: Option, /// Every live connection, by the id the registrar handed out. announce: HashMap, @@ -85,16 +72,17 @@ pub struct SizeOwnership { } /// A connection's registration. Dropping it is not enough — the holder calls -/// [`SizeOwnership::leave`], because a hub also has its own client list to -/// unwind at the same moment. +/// [`SizeOwnership::leave`]. pub struct Registration { /// The key to unregister with. pub connection: u64, /// Whether this connection's viewer owns the sizing right now. + #[cfg(test)] pub owned: bool, } impl SizeOwnership { + #[cfg(test)] pub fn new() -> Self { Self::default() } @@ -103,8 +91,7 @@ impl SizeOwnership { /// /// `arriving` is the client's own word for "a person just sat down here" — /// a page opening rather than a repository switch or a reconnect. Only that - /// takes the sizing. A viewer already present adds a connection and nothing - /// moves, which is what makes switching repositories free. + /// takes the sizing. pub fn join( &self, viewer: ViewerId, @@ -150,8 +137,13 @@ impl SizeOwnership { // because only it does not know yet. inner.tell_one(connection, inner.owner.as_ref() == Some(&viewer)); } + #[cfg(test)] let owned = inner.owner.as_ref() == Some(&viewer); - Registration { connection, owned } + Registration { + connection, + #[cfg(test)] + owned, + } } /// Drop a connection. The sizing moves only when its viewer has no other, @@ -179,8 +171,7 @@ impl SizeOwnership { } /// Take the sizing at a viewer's own request — the fit button, or the TUI's - /// chord. Unlike arriving, this is unconditional: it is the way a client - /// that has been here all along says the panes should fit *its* screen. + /// chord. Unlike arriving, this is unconditional. pub fn claim(&self, connection: u64, now: Instant) { let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner()); inner.expire_absent_owner(now); @@ -212,8 +203,7 @@ impl SizeOwnership { /// Hand the sizing on if its owner has been gone past the grace. /// /// Called from the hubs' worker tick rather than a timer of its own: the - /// grace only has to end promptly while someone is there to notice, and a - /// session with no hub running has nobody to fit. + /// grace only has to end promptly while someone is there to notice. pub fn settle(&self, now: Instant) { // Cheap on the common path: the owner is present, so nothing is pending. let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner()); diff --git a/src/web/viewer/size_owner_tests.rs b/src/session/size_owner_tests.rs similarity index 99% rename from src/web/viewer/size_owner_tests.rs rename to src/session/size_owner_tests.rs index 51ebd1f4..ee553271 100644 --- a/src/web/viewer/size_owner_tests.rs +++ b/src/session/size_owner_tests.rs @@ -5,7 +5,7 @@ //! nobody was told about would not move a single PTY. use super::*; -use crate::web::viewer::terminal::frame::TerminalFrame; +use crate::session::terminal::frame::TerminalFrame; use std::sync::mpsc::{Receiver, SyncSender, sync_channel}; fn viewer(name: &str) -> ViewerId { diff --git a/src/session/state.rs b/src/session/state.rs new file mode 100644 index 00000000..3cf6be37 --- /dev/null +++ b/src/session/state.rs @@ -0,0 +1,82 @@ +use super::catalog::Catalog; +use super::prefs::PrefsStore; +use crate::git::diff::RepoSnapshot; +use std::collections::HashMap; +use std::sync::Mutex; +use std::time::SystemTime; + +/// Turns one repository snapshot into the payload cached for status clients. +/// +/// Encoding belongs to the consuming surface, not the session. Passing the +/// encoder in keeps the status worker transport-neutral while preserving the +/// existing encode-once fan-out behavior. +pub type StatusEncoder = fn(&RepoSnapshot, &HashMap) -> Option; + +#[cfg(test)] +pub fn test_status_encoder(_: &RepoSnapshot, _: &HashMap) -> Option { + Some("{}".to_string()) +} + +/// Everything required to construct the state one daemon owns. +pub struct SessionOptions { + pub repos: Vec, + pub persist: bool, + pub startup_commands: Vec, + pub cli_startup: Vec, + pub shell: crate::config::ShellConfig, + pub prefs: PrefsStore, + pub status_encoder: StatusEncoder, +} + +/// Repository, terminal, and shared-preference state independent of transport. +pub struct SessionState { + pub(super) catalog: Catalog, + pub(super) persist: bool, + pub(super) prefs: PrefsStore, + pub(super) reload_lock: Mutex<()>, +} + +impl SessionState { + #[cfg(test)] + pub fn new(options: SessionOptions) -> Self { + Self::with_plugins(options, Vec::new()) + } + + pub fn with_plugins( + options: SessionOptions, + plugins: Vec, + ) -> Self { + let catalog = Catalog::with_startup_plugins_exec_and_shell( + options.startup_commands, + plugins, + options.cli_startup, + options.shell, + options.status_encoder, + ); + catalog.set_paths(&options.repos); + Self { + catalog, + persist: options.persist, + prefs: options.prefs, + reload_lock: Mutex::new(()), + } + } + + pub fn catalog(&self) -> &Catalog { + &self.catalog + } + + pub fn prefs(&self) -> &PrefsStore { + &self.prefs + } + + pub fn shutdown(&self) { + self.catalog.shutdown(); + } +} + +impl Drop for SessionState { + fn drop(&mut self) { + self.catalog.shutdown(); + } +} diff --git a/src/session/terminal/frame.rs b/src/session/terminal/frame.rs new file mode 100644 index 00000000..13ce238c --- /dev/null +++ b/src/session/terminal/frame.rs @@ -0,0 +1,239 @@ +use crate::backend::PaneId; +use crate::session::limits; +use serde::{Deserialize, Serialize}; + +/// A control message from a client. Output travels as binary frames to avoid +/// JSON escaping or base64 expansion. Serialized as well as deserialized so the +/// daemon can relay them between Rust clients. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "lowercase")] +pub enum ClientMessage { + Create { + rows: u16, + cols: u16, + }, + Input { + pane: PaneId, + data: String, + }, + Resize { + pane: PaneId, + rows: u16, + cols: u16, + }, + Close { + pane: PaneId, + }, + /// A client's desired pane order after a drag. The hub reconciles it + /// (see [`super::TerminalHub::reorder_panes`]). + Reorder { + order: Vec, + }, + /// Fill the terminal panel with one pane, or `None` to go back to the grid. + /// The last client to ask wins — unlike pane order, there is nothing to merge. + Zoom { + pane: Option, + }, + /// Take over sizing this repository's panes (see [`ServerMessage::SizeOwner`]). + /// A PTY has one size, so one client at a time decides it. Attaching takes + /// it; this is how a client already attached takes it back on a keystroke. + #[serde(rename = "claim_size")] + ClaimSize, + /// Give up on whatever recovery is pending for `pane`. The person's decision + /// outranks the plugin: the hold on the pane's slot is dropped and the slot + /// retired, so nothing can be relaunched into it afterwards. + #[serde(rename = "cancel_recovery")] + CancelRecovery { + pane: PaneId, + }, + /// The sizes to give the startup terminals, answering [`ServerMessage::Pending`]. + /// One entry per pending pane, in order. A short list leaves the rest at the + /// default, so a client that could only measure some still gets terminals. + Start { + sizes: Vec, + }, + /// How a `Ctrl+L` a client just forwarded came to be — see + /// [`hub_diag`](super::hub_diag) for why anyone is asking. Carries only the + /// provenance of one byte. Logged and otherwise ignored. + #[serde(rename = "clear_key_report")] + ClearKeyReport { + pane: PaneId, + /// `None` when no key event preceded the byte — a paste, an input method, + /// or a script writing straight into the terminal. + key: Option, + }, +} + +/// What the browser said about the key event behind a forwarded `Ctrl+L`. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct ClearKeyFacts { + /// `KeyboardEvent.isTrusted`: false means a script dispatched it. + pub trusted: bool, + /// `KeyboardEvent.repeat`: the key is being held down. + pub repeat: bool, + /// `KeyboardEvent.code`, e.g. `KeyL`. Sanitized before logging. + pub code: String, + /// Milliseconds between that key event and the byte it produced. + pub since_ms: u32, +} + +/// One pane's size, as the client measured its cell. +#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] +pub struct PaneSize { + pub rows: u16, + pub cols: u16, +} + +impl PaneSize { + /// Bring a size the client sent inside [`limits`]' bounds. Every path that + /// reaches `openpty` goes through here — `create`, `resize`, and each entry + /// of `start` — because they all arrive from the same untrusted side. + pub fn clamped(self) -> Self { + Self { + rows: self + .rows + .clamp(limits::MIN_PANE_DIMENSION, limits::MAX_PANE_ROWS), + cols: self + .cols + .clamp(limits::MIN_PANE_DIMENSION, limits::MAX_PANE_COLS), + } + } +} + +/// A control message to a client. Deserialized as well as serialized so the +/// daemon can read one back off a hub session and relay it to an attached client. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(tag = "type", rename_all = "lowercase")] +pub enum ServerMessage { + /// A pane exists, along with the size its PTY is currently set to. The size + /// rides along because the client is not the only source of it: a pane + /// replayed to a reconnecting page, or one another device sized, already has + /// a size this client never chose. Without it the client must assume nothing + /// and send its own size on attach, costing the child a full repaint. + Created { + pane: PaneId, + rows: u16, + cols: u16, + /// Which client asked for this pane, in the id space of the connection + /// the frame is going out on. Each recipient compares it against its own + /// id on that connection. `None` means nobody there asked: a replayed + /// pane, one another client opened, or a startup terminal. + #[serde(default, skip_serializing_if = "Option::is_none")] + client: Option, + /// What the session calls this pane, when it has a name of its own — a + /// startup terminal opened under a configured name. Absent for a pane a + /// client asked for, and for one nothing has named. + #[serde(default, skip_serializing_if = "Option::is_none")] + title: Option, + }, + Exited { + pane: PaneId, + }, + /// The size a pane's PTY is now set to. Broadcast, not answered to whoever + /// asked: a PTY has one size and every client renders the same grid, so a + /// client that is not the one sizing it still has to follow. + Resized { + pane: PaneId, + rows: u16, + cols: u16, + }, + /// Who this client is, in the id space [`Created::client`] is stamped in. + /// Addressed, and the first thing a connection is told. A connection's id, + /// not a viewer's: minted per connection and a reconnect gets a new one. + /// + /// [`Created::client`]: Self::Created::client + Hello { + client: u64, + /// How many `Created` frames the replay is about to deliver. Exact, + /// because `connect` queues the whole replay under the hub's lock and + /// only registers the client afterwards. A client that knows the count + /// can lay its grid out for the panes it is *going* to have rather than + /// the ones it has so far. + panes: usize, + }, + /// Whether *this* client is the one whose layout sets the pane sizes. + /// Addressed rather than broadcast. The size follows the most recent arrival + /// (tmux's `window-size latest`); others become spectators until one takes + /// it back with [`ClientMessage::ClaimSize`]. + #[serde(rename = "size_owner")] + SizeOwner { + owned: bool, + }, + Error { + message: String, + }, + /// The canonical pane order after a reorder, broadcast to every client. + Reordered { + order: Vec, + }, + /// Which pane now fills the terminal panel, `None` for none. Broadcast like + /// [`Reordered`](Self::Reordered) and replayed on connect. `null` is sent + /// rather than omitted: "nothing is zoomed" is a state a client has to be + /// told, not only one it starts in. + Zoomed { + pane: Option, + }, + /// This many startup terminals are waiting to be sized before they are + /// created. The client answers with [`ClientMessage::Start`]. Sent to every + /// client that connects while they are still unclaimed, so one that drops + /// mid-handshake does not leave the hub terminal-less forever. + Pending { + count: usize, + }, + /// What a plugin reports about a pane it is nursing back, relayed verbatim. + /// + /// Pane metadata rather than screen content: nothing here is drawn into a + /// terminal grid, and a client that ignores it renders exactly as before. + /// `state` is the plugin's own short label; the hub neither interprets it nor + /// keeps it, so this is a broadcast of the latest word and not a state + /// machine. The one label the hub itself sends is + /// [`RECOVERY_CANCELLED`](super::hub_recovery::RECOVERY_CANCELLED), which a + /// client treats as "there is nothing pending any more". + Recovery { + pane: PaneId, + state: String, + /// A short human line, absent when the plugin gave none. Never carries + /// transcript or payload text. + #[serde(default, skip_serializing_if = "Option::is_none")] + detail: Option, + /// When the wait ends, in **unix epoch seconds**, absent when the plugin + /// is not waiting on a clock. A client renders it in its own local zone; + /// an absent one must render nothing rather than a guess. + #[serde(default, skip_serializing_if = "Option::is_none")] + deadline_epoch: Option, + attempt: u32, + }, +} + +/// One frame queued for a connected client. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TerminalFrame { + /// Raw PTY bytes for `pane`. Sent as a binary WebSocket frame with the + /// pane id prefixed, so one socket multiplexes every terminal losslessly. + Output { pane: PaneId, data: Vec }, + /// A JSON control frame. + Control(String), +} + +/// Encode an output frame: 4-byte little-endian pane id, then the raw bytes. +/// +/// Binary rather than JSON because PTY output is not guaranteed valid UTF-8 — +/// a multi-byte sequence is routinely split across reads, and lossy decoding +/// would corrupt it before xterm.js ever reassembles it. +pub fn encode_output(pane: PaneId, data: &[u8]) -> Vec { + let mut out = Vec::with_capacity(data.len() + 4); + out.extend_from_slice(&pane.to_le_bytes()); + out.extend_from_slice(data); + out +} + +/// Decode an output frame produced by [`encode_output`]. +#[cfg(test)] +pub fn decode_output(frame: &[u8]) -> Option<(PaneId, &[u8])> { + if frame.len() < 4 { + return None; + } + let (id_bytes, rest) = frame.split_at(4); + let pane = PaneId::from_le_bytes(id_bytes.try_into().ok()?); + Some((pane, rest)) +} diff --git a/src/web/viewer/terminal/hub_connect.rs b/src/session/terminal/hub_connect.rs similarity index 97% rename from src/web/viewer/terminal/hub_connect.rs rename to src/session/terminal/hub_connect.rs index 615f6272..888b0b25 100644 --- a/src/web/viewer/terminal/hub_connect.rs +++ b/src/session/terminal/hub_connect.rs @@ -1,15 +1,12 @@ //! Clients coming and going: the replay that puts the current terminals in //! front of one, and the two sends that address a single client. -//! -//! Split out of `mod.rs` so that file stays about the hub itself; the behaviour -//! is unchanged. use super::frame::{ServerMessage, TerminalFrame}; use super::hub_helpers::{Command, Replayed, replay_pane}; use super::session::{Client, ReportBudget}; use super::{CLIENT_QUEUE_DEPTH, TerminalHub, TerminalSession}; use crate::backend::PaneId; -use crate::web::viewer::size_owner::ViewerId; +use crate::session::size_owner::ViewerId; use std::sync::Arc; use std::sync::atomic::Ordering; use std::sync::mpsc; @@ -33,7 +30,7 @@ impl TerminalHub { /// `viewer` names who this connection belongs to and `arriving` says whether /// a person just sat down at it — a page opening rather than a repository /// switch or a reconnect. Only the second takes the sizing; see - /// [`SizeOwnership`](crate::web::viewer::size_owner::SizeOwnership). + /// [`SizeOwnership`](crate::session::size_owner::SizeOwnership). /// /// `socket` is a handle on the connection to end if this client stops /// keeping up, and `None` for one that has no socket here at all — see diff --git a/src/web/viewer/terminal/hub_diag.rs b/src/session/terminal/hub_diag.rs similarity index 82% rename from src/web/viewer/terminal/hub_diag.rs rename to src/session/terminal/hub_diag.rs index aba08bb0..c42ae78f 100644 --- a/src/web/viewer/terminal/hub_diag.rs +++ b/src/session/terminal/hub_diag.rs @@ -1,22 +1,20 @@ //! Recording where a pane's screen-clearing input came from. //! //! This exists because of a specific unexplained event: a pane running Claude -//! Code had its conversation cleared fourteen times in five seconds. In -//! fullscreen rendering Claude Code runs `/clear` when it receives `Ctrl+L` -//! twice within two seconds, and the transcript showed the clears arriving as a -//! shortcut rather than as typed input — so `0x0c` reached the pane about thirty -//! times, at a machine-like cadence, and nobody knows what sent it. nightcrow -//! itself does not: the only bytes it synthesizes are scroll and mouse reports -//! and a plugin's `continue`, and that one is logged where it happens. That -//! leaves a client's own input, which arrives here. +//! Code had its conversation cleared fourteen times in five seconds. Claude Code +//! runs `/clear` when it receives `Ctrl+L` twice within two seconds, and the +//! transcript showed the clears arriving as a shortcut rather than as typed +//! input — so `0x0c` reached the pane about thirty times, at a machine-like +//! cadence, and nobody knows what sent it. nightcrow itself does not: the only +//! bytes it synthesizes are scroll and mouse reports and a plugin's `continue`, +//! and that one is logged where it happens. That leaves a client's own input. //! //! So this notes the arrival and its shape, and the client says what produced it //! (`ClientMessage::ClearKeyReport`, logged in `session.rs`). Between them, the //! next occurrence names its source instead of being reconstructed afterwards. //! //! **No input content is logged, ever** — only the byte's count, how much else -//! rode with it, and the timing. What someone types into a pane is not -//! diagnostic data. +//! rode with it, and the timing. use crate::backend::PaneId; use std::collections::HashMap; @@ -27,14 +25,11 @@ use std::time::{Duration, Instant}; pub(super) const CLEAR_SCREEN: u8 = 0x0c; /// Quiet gap that ends a burst. Two seconds is Claude Code's own window for -/// treating a second `Ctrl+L` as `/clear`, so anything inside it is what would -/// have counted as one run. +/// treating a second `Ctrl+L` as `/clear`. pub(super) const BURST_GAP: Duration = Duration::from_secs(2); /// Lines one burst may write before the rest are counted silently. A held key -/// repeats tens of times a second; the shape is clear long before that, and a -/// log that scrolls itself away is worse than one that says how much it left -/// out. +/// repeats tens of times a second; the shape is clear long before that. pub(super) const MAX_LINES_PER_BURST: u32 = 40; struct Burst { @@ -50,8 +45,7 @@ pub(super) struct ClearWatch { } impl ClearWatch { - /// Note an input frame on its way to `pane` and log what it says. Called for - /// every frame, so the no-clear path is a scan and nothing else. + /// Note an input frame on its way to `pane` and log what it says. pub(super) fn note_input(&mut self, pane: PaneId, client: u64, data: &[u8], now: Instant) { let Some(note) = self.record(pane, data, now) else { return; diff --git a/src/web/viewer/terminal/hub_diag_tests.rs b/src/session/terminal/hub_diag_tests.rs similarity index 100% rename from src/web/viewer/terminal/hub_diag_tests.rs rename to src/session/terminal/hub_diag_tests.rs diff --git a/src/web/viewer/terminal/hub_events.rs b/src/session/terminal/hub_events.rs similarity index 89% rename from src/web/viewer/terminal/hub_events.rs rename to src/session/terminal/hub_events.rs index e7e3abcf..236c95eb 100644 --- a/src/web/viewer/terminal/hub_events.rs +++ b/src/session/terminal/hub_events.rs @@ -1,8 +1,6 @@ //! Turning pane activity into plugin events, and plugin commands into judged -//! actions. -//! -//! Every function here is a no-op for a pane no plugin owns, so the ordinary -//! terminal path pays one hash lookup and nothing else. +//! actions. Every function here is a no-op for a pane no plugin owns, so the +//! ordinary terminal path pays one hash lookup and nothing else. use super::hub_plugins::{MAX_COMMANDS_PER_TICK, PANE_IDLE_THRESHOLD, Plugins}; use crate::backend::{PaneGeneration, PaneId, PaneToken, PtyBackend}; @@ -13,11 +11,8 @@ use std::time::Instant; impl Plugins { /// Send one pane-scoped event to whichever plugin owns `pane`, stamped with - /// the pane's current identity. - /// - /// A dropped event is logged and nothing else: a plugin that cannot keep up - /// must never stall the pane it is watching, which is why - /// [`crate::plugin::PluginHost::send`] does not block. + /// the pane's current identity. A dropped event is logged and nothing else — + /// a plugin that cannot keep up must never stall the pane it is watching. fn send_for( &self, backend: &PtyBackend, @@ -40,7 +35,7 @@ impl Plugins { } /// Announce a pane a plugin has just been given, including one that a - /// relaunch has just put back — the generation is what tells them apart. + /// relaunch has just put back — the generation tells them apart. pub(super) fn pane_opened(&self, backend: &PtyBackend, pane: PaneId, title: Option<&str>) { let command = backend .slot(pane) @@ -56,12 +51,11 @@ impl Plugins { }); } - /// Feed a pane's output to its plugin as plain text. - /// - /// Stripped per chunk, which makes it best-effort: an escape sequence split - /// across two reads survives in fragments. Acceptable because output text is - /// only ever a fallback signal — nothing happens to a pane unless the plugin - /// asks, and every ask goes through the guard. + /// Feed a pane's output to its plugin as plain text. Stripped per chunk, + /// which makes it best-effort: an escape sequence split across two reads + /// survives in fragments. Acceptable because output text is only ever a + /// fallback signal — nothing happens to a pane unless the plugin asks, and + /// every ask goes through the guard. pub(super) fn pane_output(&mut self, backend: &PtyBackend, pane: PaneId, data: &[u8]) { if !self.owners.contains_key(&pane) { return; diff --git a/src/web/viewer/terminal/hub_helpers.rs b/src/session/terminal/hub_helpers.rs similarity index 73% rename from src/web/viewer/terminal/hub_helpers.rs rename to src/session/terminal/hub_helpers.rs index e25263f9..7baf44ac 100644 --- a/src/web/viewer/terminal/hub_helpers.rs +++ b/src/session/terminal/hub_helpers.rs @@ -1,7 +1,7 @@ use super::frame::{ServerMessage, TerminalFrame}; use crate::backend::PaneId; use crate::runtime::emulator::PaneModes; -use crate::web::viewer::limits; +use crate::session::limits; use std::collections::VecDeque; use std::sync::mpsc::{SyncSender, TrySendError}; @@ -16,34 +16,17 @@ pub enum Command { client: u64, command: Option, }, - /// Every startup pane in one command, so *queueing* the set is - /// all-or-nothing: sending them one by one could spend the claim on some - /// and lose the rest with nothing left to retry from. - /// - /// `reserved` is how many cap slots [`Shared::reserved`] is holding for - /// this batch, released as the panes take them. Every connection has its - /// own handler thread, so between the claim and this command reaching the - /// queue another client can enqueue creates that the worker would serve - /// first — the reservation is what keeps those from taking slots the - /// configured set already claimed. - /// - /// The set can still come up short of what was configured, because the - /// reservation only holds what was free at claim time: terminals already - /// open when the claim happened are not displaced, and any one `openpty` - /// can still fail. The claim is spent by then, so a command lost that way - /// does not run until the hub restarts; the client is told which it was - /// (`terminal limit reached`, `could not start a terminal`). Going further - /// would mean deciding that a configured command outranks a terminal the - /// user already opened, which is a question about what the cap means - /// rather than a race to close. + /// Every startup pane in one command, so queueing the set is all-or-nothing. + /// `reserved` is how many cap slots [`Shared::reserved`] is holding for this + /// batch, released as the panes take them. The reservation keeps other + /// clients' creates from taking slots the configured set already claimed. CreateStartup { panes: Vec, client: u64, reserved: usize, }, /// `client` rides along for the arrival log only (see - /// [`hub_diag`](super::hub_diag)); input is honoured from whoever sends it, - /// unlike a resize. + /// [`hub_diag`](super::hub_diag)); input is honoured from whoever sends it. Input { pane: PaneId, data: Vec, @@ -64,8 +47,7 @@ pub enum Command { order: Vec, }, /// Abandon a pane's pending relaunch. On the worker queue because carrying it - /// out needs the backend and the plugin bookkeeping, both of which are - /// worker-local. + /// out needs the backend and the plugin bookkeeping, both worker-local. CancelRecovery { pane: PaneId, }, @@ -78,8 +60,7 @@ pub enum Command { }, /// Bring this hub's plugin children in line with a re-read `[[plugin]]` /// table. On the queue because every plugin host is worker-local — a plugin - /// can drive a pane's keyboard, so nothing outside the worker may touch one - /// (see [`hub_reload`](super::hub_reload)). + /// can drive a pane's keyboard, so nothing outside the worker may touch one. ReloadPlugins { plugins: Vec, }, @@ -88,25 +69,23 @@ pub enum Command { /// One startup terminal: the command to run, at the size a client measured, under /// the name it was configured with. pub struct StartupPane { - pub(super) size: crate::web::viewer::terminal::frame::PaneSize, + pub(super) size: crate::session::terminal::frame::PaneSize, pub(super) command: Option, pub(super) title: Option, /// The `[[plugin]]` this pane's configuration handed it to, if any. Carried - /// only as far as the worker, which records it in a map of its own — it is - /// deliberately absent from [`PaneState`] and from every `ServerMessage`, so - /// no client learns which panes a plugin can act on. + /// only as far as the worker — deliberately absent from [`PaneState`] and + /// from every `ServerMessage`, so no client learns which panes a plugin can + /// act on. pub(super) plugin: Option, } /// A live terminal and the recent raw bytes it has produced, kept so a client -/// that connects (or reconnects after a refresh) can be replayed the current -/// screen. Bounded by [`limits::MAX_TERMINAL_SCROLLBACK_BYTES`]. +/// that connects can be replayed the current screen. Bounded by +/// [`limits::MAX_TERMINAL_SCROLLBACK_BYTES`]. pub(super) struct PaneState { pub(super) id: PaneId, /// The name the session gave it, if any — a configured startup terminal has - /// one before it runs. Kept so a client that connects later is told it too, - /// rather than showing "shell 1" for a pane every other client calls - /// something else. + /// one before it runs. Kept so a client that connects later is told it too. pub(super) title: Option, pub(super) scrollback: VecDeque, /// The size the PTY is currently set to, tracked so a connecting client @@ -115,40 +94,30 @@ pub(super) struct PaneState { pub(super) cols: u16, /// The terminal state the pane's program has established, kept because the /// bytes that established it are not in `scrollback` any more (see - /// [`PaneModeTracker`](super::hub_modes::PaneModeTracker)). A pane that has - /// printed nothing yet is a freshly opened terminal. + /// [`PaneModeTracker`](super::hub_modes::PaneModeTracker)). pub(super) modes: PaneModes, } /// Hub state shared between the worker thread (which mutates panes and /// broadcasts) and connection threads (which register/unregister clients and /// snapshot scrollback on connect). Held under one mutex so a connecting -/// client's replay is atomic with the worker's append-and-broadcast: it sees -/// each pane's scrollback exactly once, with no gap before or duplicate of the -/// live output that follows. +/// client's replay is atomic with the worker's append-and-broadcast. pub struct Shared { pub(super) clients: Vec, pub(super) panes: Vec, /// Cap slots held for startup panes that are claimed but not created yet. - /// /// Counted against the same cap rather than exempt from it, so the ceiling - /// on real processes per repository stays what it says it is — the - /// reservation decides *who* gets a slot, never how many there are. + /// on real processes per repository stays what it says it is. pub(super) reserved: usize, /// The pane filling the panel, when one is (see [`hub_zoom`](super::hub_zoom)). - /// - /// Beside `panes` and under the same lock because the two have to agree: a - /// zoom naming a pane that is not in the list is a client rendering nothing. + /// Beside `panes` and under the same lock because the two have to agree. pub(super) zoomed: Option, } /// Queue a frame for every client, dropping any that has fallen too far behind. /// Terminal bytes cannot be skipped, so an overfull client is disconnected -/// rather than served a corrupted stream — and disconnected outright, socket and -/// all (see [`Client::cut_off`]), because leaving it off the list alone would -/// leave it holding a panel that has quietly stopped being true. Operates on an -/// already-locked client list so the caller can pair it with a pane mutation -/// atomically. +/// outright (see [`Client::cut_off`]). Operates on an already-locked client list +/// so the caller can pair it with a pane mutation atomically. pub(super) fn broadcast_locked(clients: &mut Vec, frame: TerminalFrame) { clients.retain(|client| match client.tx.try_send(frame.clone()) { Ok(()) => true, @@ -164,9 +133,9 @@ pub(super) fn broadcast_locked(clients: &mut Vec, frame: TerminalFrame) /// The canonical pane order for a reorder request: the requested ids that are /// actually live, in the requested order, followed by any live pane the request -/// omitted, in its current order. Unknown requested ids are dropped. The result -/// is always a permutation of `current`, which is what makes a reorder safe -/// against a create or close that raced the request. +/// omitted, in its current order. The result is always a permutation of +/// `current`, which is what makes a reorder safe against a create or close that +/// raced the request. pub(super) fn canonical_order(current: &[PaneId], requested: &[PaneId]) -> Vec { let mut out: Vec = Vec::with_capacity(current.len()); // Requested ids first: live, and each taken once (a repeated id would make diff --git a/src/web/viewer/terminal/hub_layout.rs b/src/session/terminal/hub_layout.rs similarity index 80% rename from src/web/viewer/terminal/hub_layout.rs rename to src/session/terminal/hub_layout.rs index 411407d2..31c0b83d 100644 --- a/src/web/viewer/terminal/hub_layout.rs +++ b/src/session/terminal/hub_layout.rs @@ -1,8 +1,6 @@ //! Pane geometry and ordering: the two worker operations that change how the -//! panes are arranged rather than what is in them. -//! -//! Split out of `hub_run.rs` so the worker loop stays readable; the behaviour is -//! unchanged. +//! panes are arranged rather than what is in them. Split out of `hub_run.rs` so +//! the worker loop stays readable. use super::TerminalHub; use super::frame::{ServerMessage, TerminalFrame}; @@ -11,8 +9,7 @@ use crate::backend::{PaneId, PtyBackend, TerminalBackend}; impl TerminalHub { /// The size a pane's PTY is recorded as having, or `None` once the pane is - /// gone. The one reader of that record outside the resize path itself, shared - /// by everything that has to act on the size a pane actually has. + /// gone. pub(super) fn pane_size(&self, pane: PaneId) -> Option<(u16, u16)> { let state = self.state.lock().expect("terminal state poisoned"); state @@ -22,15 +19,12 @@ impl TerminalHub { .map(|p| (p.rows, p.cols)) } - /// Resize a live pane's PTY at the sizing owner's request, record the size it - /// is now set to, and tell every client what it is. - /// - /// All under one lock, and the liveness check with them. `connect` reports - /// each pane's size from this record and the client caches it as "already - /// applied"; a client that slipped between the two would be told the old - /// size for a PTY that has the new one, and would then skip the resize that - /// would have corrected it. The `resize` itself is an ioctl on the master — - /// far cheaper than the broadcast this lock already covers. + /// Resize a live pane's PTY at the sizing owner's request, record the size, + /// and tell every client what it is. All under one lock, with the liveness + /// check — `connect` reports each pane's size from this record and the + /// client caches it as "already applied"; a client that slipped between the + /// two would be told the old size for a PTY that has the new one, and would + /// then skip the resize that would have corrected it. pub(super) fn resize_pane( &self, backend: &mut PtyBackend, @@ -46,14 +40,13 @@ impl TerminalHub { return; }; // Not this client's to set. Dropped rather than refused: a client can - // lose the sizing between laying out a frame and this arriving, which is - // ordinary rather than an error worth interrupting anyone over. + // lose the sizing between laying out a frame and this arriving. if !self.owns_size(connection) { return; } let mut state = self.state.lock().expect("terminal state poisoned"); // An unknown pane is ignored rather than errored: a client racing a - // pane exit is normal, not an attack. + // pane exit is normal. let Some(p) = state.panes.iter_mut().find(|p| p.id == pane) else { return; }; diff --git a/src/web/viewer/terminal/hub_modes.rs b/src/session/terminal/hub_modes.rs similarity index 100% rename from src/web/viewer/terminal/hub_modes.rs rename to src/session/terminal/hub_modes.rs diff --git a/src/web/viewer/terminal/hub_panes.rs b/src/session/terminal/hub_panes.rs similarity index 99% rename from src/web/viewer/terminal/hub_panes.rs rename to src/session/terminal/hub_panes.rs index 08ad7d75..4bc67aad 100644 --- a/src/web/viewer/terminal/hub_panes.rs +++ b/src/session/terminal/hub_panes.rs @@ -13,7 +13,7 @@ use super::hub_helpers::{PaneState, broadcast_locked, push_scrollback}; use super::hub_zoom::clear_zoom_locked; use crate::backend::PaneId; use crate::runtime::emulator::PaneModes; -use crate::web::viewer::limits; +use crate::session::limits; use std::collections::VecDeque; impl TerminalHub { diff --git a/src/web/viewer/terminal/hub_plugins.rs b/src/session/terminal/hub_plugins.rs similarity index 77% rename from src/web/viewer/terminal/hub_plugins.rs rename to src/session/terminal/hub_plugins.rs index 2ca759fe..39465c65 100644 --- a/src/web/viewer/terminal/hub_plugins.rs +++ b/src/session/terminal/hub_plugins.rs @@ -8,11 +8,9 @@ //! //! A pane appears here only two ways: its `[[startup_command]]` named a plugin //! by hand, or a plugin asked for it by quoting the pane's own token and the -//! guard allowed it (see `plugin::guard_watch`). Neither can be reached -//! by a plugin enumerating panes, because nothing ever tells a plugin what panes -//! exist — which is what keeps "an arbitrary shell is never plugin-controlled" a -//! property of the code. A shell stays untouched by doing what a shell does: -//! saying nothing to any plugin. +//! guard allowed it. Neither can be reached by a plugin enumerating panes, +//! because nothing ever tells a plugin what panes exist — which is what keeps +//! "an arbitrary shell is never plugin-controlled" a property of the code. use crate::backend::PaneId; use crate::config::{PluginConfig, StartupCommand}; @@ -21,49 +19,35 @@ use std::collections::{HashMap, HashSet}; use std::time::Duration; /// How long a pane must be quiet before its plugin is told it is idle. -/// /// Deliberately the same value the guard requires before it will let a plugin -/// type into that pane: announcing idleness any earlier would only invite -/// commands the guard is bound to refuse. Ten seconds is well past the pause a -/// CLI takes mid-answer and well short of a wait a person would notice. +/// type into that pane. Ten seconds is well past the pause a CLI takes +/// mid-answer and well short of a wait a person would notice. pub(super) const PANE_IDLE_THRESHOLD: Duration = Duration::from_secs(10); -/// Commands taken from any one plugin per loop iteration. -/// -/// One thread serves every pane in the repository, so a plugin that writes -/// without pause must not be able to hold it. Eight per 8 ms tick is a thousand -/// a second — far past anything a legitimate plugin needs, and bounded. +/// Commands taken from any one plugin per loop iteration. One thread serves +/// every pane in the repository, so a plugin that writes without pause must not +/// be able to hold it. Eight per 8 ms tick is a thousand a second — far past +/// anything a legitimate plugin needs, and bounded. pub(super) const MAX_COMMANDS_PER_TICK: usize = 8; pub(super) struct Plugins { - /// Live plugin children, by configured name. A plugin that failed to launch - /// is absent, and its panes are therefore never adopted below. + /// Live plugin children, by configured name. pub(super) hosts: HashMap, /// What each live host was launched from, so a config reload can tell a - /// plugin whose process must be replaced from one whose rules merely - /// changed. Kept beside the hosts rather than read back off the child, which - /// knows only its pipes. + /// plugin whose process must be replaced from one whose rules merely changed. pub(super) launched: HashMap, /// Which plugin owns which pane. The authority for `opted_in`. pub(super) owners: HashMap, /// Which plugin a pane's *configuration* named, recorded whether or not that - /// plugin had a host at the time. - /// - /// Separate from `owners` because it grants nothing: a pane is only ever - /// acted on through `owners`, so an entry here with no host behind it puts - /// the pane on no relaunch path and sends no events — the reason `adopt` - /// refuses such an association in the first place. What it is for is a config - /// reload: enabling a plugin whose panes were created while it was off has to - /// be able to find them, and the opt-in is the only record that they were - /// ever meant for it. A pane closing takes its entry with it, so this is - /// bounded by the live panes and never by the session's history. + /// plugin had a host at the time. Separate from `owners` because it grants + /// nothing: a pane is only ever acted on through `owners`. What it is for is + /// a config reload — enabling a plugin whose panes were created while it was + /// off has to be able to find them. pub(super) intended: HashMap, /// Each plugin's `allowed_resume_flags`, as the guard needs them. pub(super) allowed_flags: HashMap>, /// The plugins whose config set `watch_on_signal`: those the operator allowed - /// to be given a pane they were never named by. Held as the set of names - /// rather than looked up in the config list, so the judgement reads it as a - /// hash probe on the same footing as every other fact it gathers. + /// to be given a pane they were never named by. pub(super) watch_on_signal: HashSet, pub(super) guard: Guard, pub(super) pending: HashMap, diff --git a/src/web/viewer/terminal/hub_plugins_slots.rs b/src/session/terminal/hub_plugins_slots.rs similarity index 94% rename from src/web/viewer/terminal/hub_plugins_slots.rs rename to src/session/terminal/hub_plugins_slots.rs index 9e1c0990..e44dcd5b 100644 --- a/src/web/viewer/terminal/hub_plugins_slots.rs +++ b/src/session/terminal/hub_plugins_slots.rs @@ -1,9 +1,8 @@ -//! Which pane belongs to which plugin, and how long an exited one's slot is kept. -//! -//! Split from the host lifecycle beside it because the two are bounded by -//! different things. Whether a plugin child runs is decided by the config; whether -//! a pane is still reachable is decided by that pane's own process and by the -//! window a relaunch has left, which is what everything here measures. +//! Which pane belongs to which plugin, and how long an exited one's slot is +//! kept. Split from the host lifecycle beside it because the two are bounded by +//! different things: whether a plugin child runs is decided by the config; +//! whether a pane is still reachable is decided by that pane's own process and +//! by the window a relaunch has left. use super::hub_helpers::PaneState; use super::hub_plugins::Plugins; diff --git a/src/web/viewer/terminal/hub_recovery.rs b/src/session/terminal/hub_recovery.rs similarity index 70% rename from src/web/viewer/terminal/hub_recovery.rs rename to src/session/terminal/hub_recovery.rs index c43b3c1d..036156c1 100644 --- a/src/web/viewer/terminal/hub_recovery.rs +++ b/src/session/terminal/hub_recovery.rs @@ -2,8 +2,7 @@ //! //! The hub keeps no recovery state of its own: a report is broadcast as it //! arrives and forgotten. What the hub *does* own is the hold on an exited -//! pane's slot, and that is the thing a person can take away — so cancelling is -//! the one place here that touches the backend. +//! pane's slot — cancelling is the one place here that touches the backend. use super::TerminalHub; use super::frame::{ServerMessage, TerminalFrame}; @@ -12,23 +11,19 @@ use super::hub_plugins::Plugins; use crate::backend::{PaneId, PtyBackend, TerminalBackend}; use std::time::Instant; -/// Whether `pane`'s process could be put back if it ended. -/// -/// A relaunch reproduces the pane's original invocation, so a pane the host -/// launched no command in has nothing to reproduce — which is exactly the pane a -/// plugin is given when its occupant asks to be watched. The guard refuses such a -/// relaunch outright, so the hold that exists solely to make one possible must not -/// be taken out for it either: that hold lasts days, and it would be days spent -/// keeping a shell's slot alive for a request that can never be granted. +/// Whether `pane`'s process could be put back if it ended. A relaunch +/// reproduces the pane's original invocation, so a pane the host launched no +/// command in has nothing to reproduce — the guard refuses such a relaunch +/// outright, so the hold that exists solely to make one possible must not be +/// taken out for it either. pub(super) fn is_relaunchable(backend: &PtyBackend, pane: PaneId) -> bool { backend .slot(pane) .is_some_and(|slot| slot.launch.command.is_some()) } -/// The `state` the hub itself sends when a pane's recovery is over without -/// having succeeded — cancelled by a person, or given up on when the hold ran -/// out. Every client reads it as "stop showing a deadline for this pane". +/// The `state` the hub sends when a pane's recovery is over without having +/// succeeded — cancelled by a person, or given up on when the hold ran out. pub(crate) const RECOVERY_CANCELLED: &str = "cancelled"; impl TerminalHub { @@ -57,22 +52,18 @@ impl TerminalHub { broadcast_locked(&mut state.clients, TerminalFrame::Control(json)); } - /// Tell every client there is nothing pending for `pane` any more. - /// - /// Sent wherever a hold ends without leaving one behind — cancelled, expired, - /// relaunched, or closed for good. Without it a client keeps the last report - /// it saw, and a deadline that has already come and gone stays on screen. + /// Tell every client there is nothing pending for `pane` any more. Sent + /// wherever a hold ends without leaving one behind — cancelled, expired, + /// relaunched, or closed for good. pub(super) fn end_recovery(&self, pane: PaneId) { self.broadcast_recovery(pane, RECOVERY_CANCELLED, None, None, 0); } - /// A pane's process ended. - /// - /// For a pane no plugin watches this is the long-standing path: destroy it - /// and tell everyone. For a watched one the slot has to survive, because its - /// token is the only thing a relaunch can reuse — so the process alone is let - /// go and the slot is held until the plugin acts or the window closes. Unless - /// there is nothing to put back: see [`is_relaunchable`]. + /// A pane's process ended. For a pane no plugin watches this is the + /// long-standing path: destroy it and tell everyone. For a watched one the + /// slot survives — the process alone is let go and the slot is held until + /// the plugin acts or the window closes. Unless there is nothing to put + /// back: see [`is_relaunchable`]. pub(super) fn pane_exited( &self, backend: &mut PtyBackend, @@ -91,8 +82,7 @@ impl TerminalHub { plugins.hold_for_relaunch(pane, spot, Instant::now()); plugins.pane_exited(backend, pane); } - // Nowhere to put a replacement, or nothing to put there: either way - // there is no reason to keep the slot alive for one. + // Nowhere to put a replacement, or nothing to put there. _ => { plugins.pane_closed(backend, pane); plugins.forget(backend, pane); diff --git a/src/web/viewer/terminal/hub_relaunch.rs b/src/session/terminal/hub_relaunch.rs similarity index 88% rename from src/web/viewer/terminal/hub_relaunch.rs rename to src/session/terminal/hub_relaunch.rs index ee6f2031..04086169 100644 --- a/src/web/viewer/terminal/hub_relaunch.rs +++ b/src/session/terminal/hub_relaunch.rs @@ -1,9 +1,7 @@ -//! Carrying out what a plugin was allowed to do. -//! -//! Everything here runs on the worker thread and acts only on an [`Approved`] -//! value. There is no path from a [`PluginCommand`](crate::plugin::protocol::PluginCommand) -//! to a pane that does not pass through [`Plugins::judge`] first, and a -//! [`Refused`] is logged and dropped — never retried, never acted on. +//! Carrying out what a plugin was allowed to do. Everything here runs on the +//! worker thread and acts only on an [`Approved`] value. There is no path from a +//! [`PluginCommand`](crate::plugin::protocol::PluginCommand) to a pane that does +//! not pass through [`Plugins::judge`] first. use super::TerminalHub; use super::frame::{ServerMessage, TerminalFrame}; @@ -90,14 +88,9 @@ impl TerminalHub { } /// Hand `pane` to `plugin` and announce it, so the plugin can start from the - /// same `PaneOpened` a configured pane begins with. - /// - /// Announcing is the whole point of the request: the association alone tells - /// the plugin nothing, and only a `PaneOpened` carries the generation every - /// later command has to name. The pane's history is not replayed — output - /// events carry fresh bytes only — so a plugin taken on mid-session sees the - /// pane from here forward, which is exactly what the signal that brought it - /// here is about. + /// same `PaneOpened` a configured pane begins with. The pane's history is not + /// replayed — output events carry fresh bytes only — so a plugin taken on + /// mid-session sees the pane from here forward. fn watch_pane_for_plugin( &self, backend: &PtyBackend, @@ -126,11 +119,9 @@ impl TerminalHub { plugins.pane_opened(backend, pane, title.as_deref()); } - /// Put a process back into the slot a plugin was holding. - /// - /// Only for a pane the hub is actually holding: that hold is the proof the - /// pane exited and is still inside its window, and without it the slot has - /// either been retired or never belonged to this flow at all. + /// Put a process back into the slot a plugin was holding. Only for a pane + /// the hub is actually holding: that hold is the proof the pane exited and + /// is still inside its window. fn relaunch_for_plugin( &self, backend: &mut PtyBackend, @@ -208,11 +199,8 @@ impl TerminalHub { } /// Put `pane` back at `index` in the client-visible order and tell every - /// client the result. - /// - /// The order *is* `Shared::panes`, so this is how a relaunched pane keeps - /// its predecessor's place without a new wire message: clients already - /// apply `Reordered`. + /// client the result. The order *is* `Shared::panes`, so this is how a + /// relaunched pane keeps its predecessor's place without a new wire message. fn restore_pane_index(&self, pane: PaneId, index: usize) { let mut state = self.state.lock().expect("terminal state poisoned"); let Some(from) = state.panes.iter().position(|p| p.id == pane) else { diff --git a/src/web/viewer/terminal/hub_reload.rs b/src/session/terminal/hub_reload.rs similarity index 78% rename from src/web/viewer/terminal/hub_reload.rs rename to src/session/terminal/hub_reload.rs index 9f2512a4..dea31188 100644 --- a/src/web/viewer/terminal/hub_reload.rs +++ b/src/session/terminal/hub_reload.rs @@ -2,16 +2,13 @@ //! //! A plugin is a child process rather than a pane, so unlike a startup terminal //! it can be replaced without costing the session anything a person was using. -//! That is the whole reason a config reload reaches this far: the panes stay, -//! their processes stay, and only the thing watching them is rebuilt. //! //! Three rules the diff below is written around. //! //! **The opt-ins are this hub's, not the new file's.** A hub creates its startup //! panes once for its life, so a `[[startup_command]]` added by the edit has no -//! pane here and will not get one — launching the plugin it names would be a -//! child process with nothing it could ever be given. What decides is the list -//! this hub was spawned with ([`TerminalHub::startup_commands`]). +//! pane here and will not get one. What decides is the list this hub was spawned +//! with ([`TerminalHub::startup_commands`]). //! //! **A plugin that is watching something stays.** Removing a pane's opt-in from //! the file does not remove the pane, and silently un-watching a live agent @@ -21,8 +18,7 @@ //! **The guard is never rebuilt.** Its relaunch budget is keyed by a pane's //! token, which is what bounds a plugin that answers every exit with another //! relaunch. Rebuilding it here would hand out a fresh allowance on every -//! reload, so the ceiling would never be reached — the same reasoning as -//! [`Plugins::take_over`](super::hub_plugins::Plugins::take_over). +//! reload, so the ceiling would never be reached. use super::TerminalHub; use super::hub_helpers::Command; @@ -41,8 +37,7 @@ pub(super) struct PluginReload { /// Same plugin, new process — its command, arguments or environment changed. pub(super) restarted: Vec, /// Panes whose held relaunch was given up because the plugin holding it is - /// gone. The caller tells the clients, which are counting down to a deadline - /// nothing will act on. + /// gone. pub(super) retired: Vec, } @@ -53,31 +48,22 @@ impl PluginReload { } impl TerminalHub { - /// Ask the worker to re-apply `plugins`. + /// Ask the worker to re-apply `plugins`. Queued rather than done here: a + /// plugin can drive a pane's keyboard, so every plugin host is worker-local + /// and the command queue is the only way in. A full queue drops the request + /// and says so — a hub that far behind is being hammered by a client, and + /// blocking a reload behind it would hold up every other repository. /// - /// Queued rather than done here: a plugin can drive a pane's keyboard, so - /// every plugin host is worker-local and the command queue is the only way in - /// (see [`super::hub_plugins`]). A full queue drops the request and says so — - /// a hub that far behind is being hammered by a client, and blocking a - /// reload behind it would hold up every other repository in the session. - /// - /// Reports whether the worker took it. The caller counts the refusals into - /// its report: this hub keeps the plugin children it had, so calling the - /// reload a success for it would claim a change that did not happen. Saying - /// so is the caller's job too — a hub does not keep its own path, and - /// "which repository" is the first thing a skipped one raises. + /// Reports whether the worker took it. pub(crate) fn reload_plugins(&self, plugins: Vec) -> bool { self.commands .try_send(Command::ReloadPlugins { plugins }) .is_ok() } - /// Carry out a queued reload on the worker thread. - /// - /// The pane titles are read here rather than inside the diff because they - /// live in the hub's shared state: a plugin that is (re)started is handed the - /// panes it owns again, and a pane announced without its title would show up - /// under a different name than the one the operator sees. + /// Carry out a queued reload on the worker thread. The pane titles are read + /// here rather than inside the diff because they live in the hub's shared + /// state. pub(super) fn reload_hub_plugins( &self, backend: &mut PtyBackend, diff --git a/src/web/viewer/terminal/hub_reload_hosts.rs b/src/session/terminal/hub_reload_hosts.rs similarity index 80% rename from src/web/viewer/terminal/hub_reload_hosts.rs rename to src/session/terminal/hub_reload_hosts.rs index 2c6aa826..c6b78477 100644 --- a/src/web/viewer/terminal/hub_reload_hosts.rs +++ b/src/session/terminal/hub_reload_hosts.rs @@ -1,8 +1,7 @@ -//! Starting and stopping one plugin's child during a reload. -//! -//! Split from the diff that decides *which* children should run, because these -//! carry the awkward part: what happens to the panes a plugin was watching, which -//! differs by whether a replacement is about to take its place. +//! Starting and stopping one plugin's child during a reload. Split from the +//! diff that decides *which* children should run, because these carry the +//! awkward part: what happens to the panes a plugin was watching, which differs +//! by whether a replacement is about to take its place. use super::hub_plugins::Plugins; use super::hub_reload::PluginReload; @@ -12,8 +11,7 @@ use std::collections::HashMap; impl Plugins { /// Launch `cfg`'s child and hand it the panes it owns. Reports whether the - /// child came up — a caller that stopped a predecessor for this one has to - /// know it never arrived. + /// child came up. pub(super) fn start_host( &mut self, cfg: &PluginConfig, @@ -66,14 +64,11 @@ impl Plugins { } } - /// Stop watching `pane` without forgetting what it opted into. - /// - /// The narrower half of [`Plugins::forget`], for a pane that is still there: - /// the association, any relaunch hold and the spent budget go, so the pane - /// becomes an ordinary terminal, but the opt-in stays. That is what makes - /// disabling a plugin and enabling it again land where enabling it the first - /// time would — the alternative is a switch that means something different - /// depending on which way it was last flipped. + /// Stop watching `pane` without forgetting what it opted into. The narrower + /// half of [`Plugins::forget`], for a pane that is still there: the + /// association, any relaunch hold and the spent budget go, but the opt-in + /// stays. That is what makes disabling a plugin and enabling it again land + /// where enabling it the first time would. fn release_pane(&mut self, backend: &PtyBackend, pane: PaneId) { self.owners.remove(&pane); self.pending.remove(&pane); @@ -117,16 +112,10 @@ impl Plugins { } /// Let go of everything `name` still holds: its rules, and the panes it was - /// watching. - /// - /// Reached two ways — a plugin the file dropped, and a replacement whose - /// child never came up. The second is why this is separate: [`stop_host`] - /// keeps the live panes when a successor is on the way, and if the spawn then - /// fails those panes are owned by a name with no host. Left that way, the - /// pane's next exit takes the relaunch path — its slot held open for a plugin - /// that cannot ask — and on a hub whose only plugin this was, nothing even - /// runs to expire the hold, so the client counts down to a deadline that - /// never arrives. + /// watching. Reached two ways — a plugin the file dropped, and a replacement + /// whose child never came up. The second is why this is separate: + /// [`stop_host`] keeps the live panes when a successor is on the way, and if + /// the spawn then fails those panes are owned by a name with no host. /// /// [`stop_host`]: Self::stop_host pub(super) fn abandon(&mut self, backend: &PtyBackend, name: &str, outcome: &mut PluginReload) { diff --git a/src/web/viewer/terminal/hub_repaint.rs b/src/session/terminal/hub_repaint.rs similarity index 79% rename from src/web/viewer/terminal/hub_repaint.rs rename to src/session/terminal/hub_repaint.rs index 362ba601..7c011ce6 100644 --- a/src/web/viewer/terminal/hub_repaint.rs +++ b/src/session/terminal/hub_repaint.rs @@ -11,24 +11,21 @@ //! PTY already has reaches nobody. Two `resize` calls back to back do not work //! either: both complete before the child's handler runs, so it reads the final //! size, sees no change, and a program that repaints on a *changed* size does -//! nothing (measured — the shell reported the same `$LINES` twice). So the pane -//! is made one row shorter, and put back a tick later, once the program has had -//! time to see it. That is one wasted intermediate repaint, on reattach only. +//! nothing. So the pane is made one row shorter, and put back a tick later, once +//! the program has had time to see it. //! -//! Clients are not told about the intermediate size. It exists for a tenth of a +//! Clients are not told about the intermediate size — it exists for a tenth of a //! second, the recorded size never changes, and the repaint that follows the -//! restore covers every row — telling them would cost two extra `resized` -//! round trips and a visible reflow for a size nobody chose. +//! restore covers every row. use super::TerminalHub; use crate::backend::{PaneId, PtyBackend, TerminalBackend}; -use crate::web::viewer::limits; +use crate::session::limits; use std::collections::HashMap; use std::time::{Duration, Instant}; /// How long the shorter size stays in effect. Long enough that the child's -/// `SIGWINCH` handler has run and read it (30 ms already sufficed in testing, -/// with the margin here for a loaded machine), short enough to be over before +/// `SIGWINCH` handler has run and read it, short enough to be over before /// anyone has read a line of the screen. const RESTORE_AFTER: Duration = Duration::from_millis(100); @@ -101,12 +98,10 @@ impl TerminalHub { } } - /// Give back the size of every pane whose gap has elapsed. - /// - /// The size restored is whatever the pane's record says *now*, not what it - /// was when the repaint started: a client that took the sizing and resized - /// the pane in between has the last word, and putting the old size back - /// would undo a size somebody actually chose. + /// Give back the size of every pane whose gap has elapsed. The size + /// restored is whatever the pane's record says *now*, not what it was when + /// the repaint started — a client that resized the pane in between has the + /// last word. pub(super) fn finish_repaints( &self, backend: &mut PtyBackend, @@ -129,8 +124,7 @@ impl TerminalHub { } } - /// Drop a gone pane's repaint bookkeeping, so a shrink is never restored - /// onto a slot a relaunch has reused. + /// Drop a gone pane's repaint bookkeeping. pub(super) fn forget_repaints(&self, repaints: &mut Repaints, pane: PaneId) { repaints.forget(pane); } diff --git a/src/web/viewer/terminal/hub_run.rs b/src/session/terminal/hub_run.rs similarity index 94% rename from src/web/viewer/terminal/hub_run.rs rename to src/session/terminal/hub_run.rs index e1de2601..8a878244 100644 --- a/src/web/viewer/terminal/hub_run.rs +++ b/src/session/terminal/hub_run.rs @@ -17,13 +17,10 @@ const POLL_INTERVAL: Duration = Duration::from_millis(8); impl TerminalHub { pub(super) fn run(&self, cwd: &str, commands: Receiver, stop: Arc) { let mut backend = PtyBackend::new(cwd, self.shell.clone()); - // Before the loop, because a pane can be created on the first iteration - // and a plugin has to exist to be told about it. Only the plugins some - // configured pane opted into are launched (see `Plugins::start`). + // Only the plugins some configured pane opted into are launched. let mut plugins = Plugins::start(cwd, &self.plugins, &self.startup); // What each pane's program has done to its terminal, so a client that - // attaches later can be told rather than left to infer it from a replay - // the setup bytes fell out of. + // attaches later can be told rather than left to infer it from a replay. let mut modes = PaneModeTracker::default(); // Repaints asked for by attaching clients, and the sizes owed back. let mut repaints = Repaints::default(); @@ -39,17 +36,16 @@ impl TerminalHub { client, command, } => { - // Slots reserved for a claimed startup set count here: - // the configured set has first refusal on them. + // Slots reserved for a claimed startup set count here. if !self.has_free_slot() { self.send_error_to(client, "terminal limit reached"); continue; } match backend.open_pane(rows, cols, command.as_deref()) { // Unnamed: a pane a client asked for is that client's - // to name, and the hub has nothing to add. No plugin - // association either, ever — a shell a client opened - // is nobody's to drive but the person at it. + // to name. No plugin association either — a shell a + // client opened is nobody's to drive but the person + // at it. Ok(pane) => self.register_pane(pane, rows, cols, Some(client), None), Err(err) => { tracing::warn!(%err, "viewer: could not create a terminal"); diff --git a/src/web/viewer/terminal/hub_zoom.rs b/src/session/terminal/hub_zoom.rs similarity index 100% rename from src/web/viewer/terminal/hub_zoom.rs rename to src/session/terminal/hub_zoom.rs diff --git a/src/web/viewer/terminal/mod.rs b/src/session/terminal/mod.rs similarity index 67% rename from src/web/viewer/terminal/mod.rs rename to src/session/terminal/mod.rs index 42888b84..c1b80b15 100644 --- a/src/web/viewer/terminal/mod.rs +++ b/src/session/terminal/mod.rs @@ -1,22 +1,17 @@ //! Terminals owned by the viewer, one hub per repository. //! -//! These are **not** the TUI's panes. Sharing them would mean reaching into -//! `App`, which would break both the "server never touches App" rule and the -//! headless mode. The viewer owns its own [`PtyBackend`], so `nightcrow serve` -//! offers terminals with no TUI running at all. +//! These are **not** the TUI's panes — the viewer owns its own [`PtyBackend`], +//! so `nightcrow serve` offers terminals with no TUI running at all. //! -//! Raw PTY bytes go to the browser untouched — the hub renders no screen. -//! xterm.js is a terminal emulator already; the mirror only composes a grid -//! because it has to match a ratatui screen, and this has no such constraint. -//! The hub does parse the stream for one thing it cannot get any other way: the -//! modes each pane's program has set, which an attaching client has to be told -//! because the bytes that set them are long gone (see [`hub_modes`]). +//! Raw PTY bytes go to the browser untouched. The hub does parse the stream for +//! one thing it cannot get any other way: the modes each pane's program has set, +//! which an attaching client has to be told because the bytes that set them are +//! long gone (see [`hub_modes`]). //! //! **Output is queued, not conflated.** Status updates can drop intermediates //! because the newest is a complete picture; terminal bytes cannot — dropping //! any corrupts the stream. Each client gets a bounded queue and is -//! disconnected when it overflows, which is honest, where silently discarding -//! bytes would leave a subtly wrong screen. +//! disconnected when it overflows. pub mod frame; mod hub_connect; @@ -49,7 +44,7 @@ pub use frame::decode_output; pub use frame::{ClientMessage, PaneSize, TerminalFrame, encode_output}; pub use session::TerminalSession; -use crate::web::viewer::size_owner::SizeOwnership; +use crate::session::size_owner::SizeOwnership; use hub_helpers::{Command, Shared}; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::mpsc::{self, SyncSender}; @@ -59,9 +54,9 @@ use std::thread; /// Output frames a client may fall behind by before it is dropped. pub(crate) const CLIENT_QUEUE_DEPTH: usize = 256; -/// The size a pane is born at when no client measured one for it. Only reached -/// when a client answers `Pending` with fewer sizes than there are panes; the -/// first fit corrects it at the cost of one repaint. +/// Default pane size when no client measured one. Only reached when a client +/// answers `Pending` with fewer sizes than there are panes; the first fit +/// corrects it at the cost of one repaint. const DEFAULT_PANE_SIZE: PaneSize = PaneSize { rows: 24, cols: 80 }; pub struct TerminalHub { @@ -70,24 +65,20 @@ pub struct TerminalHub { next_client_id: AtomicU64, stop: Arc, worker: Mutex>>, - /// The terminals to open once a client has sized them, with the names they - /// were configured under. Empty means a single bare shell (matching the - /// TUI's default). + /// The terminals to open once a client has sized them. Empty means a single + /// bare shell (matching the TUI's default). startup: Vec, /// The `[[plugin]]` table. The worker launches a host for each entry that is - /// enabled *and* that some `startup` entry opted into, and nothing else — a - /// plugin no pane named is never started, so declaring one costs nothing - /// until a pane hands itself over. + /// enabled *and* that some `startup` entry opted into — a plugin no pane + /// named is never started. plugins: Vec, /// The shell every terminal pane is spawned with. shell: crate::config::ShellConfig, /// Set when a client claims the startup terminals by answering with their - /// sizes, so they are created exactly once for the hub's life rather than - /// on every (re)connection. See [`TerminalHub::claim_startup`]. + /// sizes, so they are created exactly once for the hub's life. started: AtomicBool, - /// Which screen the session's panes are fitted to. The session's rather than - /// this hub's, because every client shows the same repository — see - /// [`SizeOwnership`]. + /// Which screen the session's panes are fitted to — shared with every other + /// hub because the answer is one per session, not one per repository. ownership: Arc, } @@ -96,9 +87,7 @@ impl TerminalHub { /// commands to launch when the first client connects (empty = one shell), /// and `plugins` the configured plugin table those commands may opt into. /// - /// `ownership` is the session's, shared with every other hub: which screen - /// the panes are fitted to is one answer for the whole session, not one per - /// repository. + /// `ownership` is the session's, shared with every other hub. pub fn spawn( cwd: &str, startup: Vec, @@ -136,12 +125,8 @@ impl TerminalHub { hub } - /// The startup panes this hub was spawned with. - /// - /// Fixed for its life: the panes are created once and a config reload does - /// not replace them, so this stays what the repository was opened under. - /// Read by the plugin reload, which decides what a plugin may see on *this* - /// hub from the opt-ins this list carries rather than from the new file's. + /// The startup panes this hub was spawned with. Fixed for its life: the + /// panes are created once and a config reload does not replace them. pub(crate) fn startup_commands(&self) -> &[crate::config::StartupCommand] { &self.startup } @@ -161,6 +146,7 @@ impl TerminalHub { state.reserved = state.reserved.saturating_sub(count); } + #[cfg(test)] pub fn client_count(&self) -> usize { self.state .lock() diff --git a/src/web/viewer/terminal/session.rs b/src/session/terminal/session.rs similarity index 75% rename from src/web/viewer/terminal/session.rs rename to src/session/terminal/session.rs index ad959533..a9127156 100644 --- a/src/web/viewer/terminal/session.rs +++ b/src/session/terminal/session.rs @@ -9,36 +9,20 @@ use std::time::{Duration, Instant}; pub(super) struct Client { pub(super) id: u64, pub(super) tx: SyncSender, - /// A second handle on this client's socket, only ever used to end the - /// connection when its queue overflows. - /// - /// Dropping it from the broadcast list alone stops the frames but leaves the - /// connection thread parked in its read, so the client goes quiet while - /// still believing it is attached — a panel frozen mid-session, with no - /// close for the page's reconnect to fire on. Closing the socket is what - /// turns that into the disconnect it already claims to be. The same handle - /// the daemon keeps on an attached client, for the same reason - /// (`daemon::clients`). - /// - /// `None` for a client with no socket of its own: the daemon's bridge reads - /// this hub from a thread and hands frames on without blocking, so it cannot - /// fall behind here — the backpressure that matters to it is applied a layer - /// out, where it does own a socket. + /// Used to close the socket when this client's queue overflows. Dropping + /// from the broadcast list alone stops frames but leaves the connection + /// thread parked — closing the socket is what turns that into a real + /// disconnect. `None` for a client with no socket of its own (e.g. the + /// daemon's bridge, whose backpressure is applied a layer out). pub(super) socket: Option, - /// This connection's registration with the session's size ownership. Kept - /// beside the hub's own id because the two answer different questions: the - /// id names a connection to *this* repository, the registration names the - /// screen behind it, which the session tracks across every repository. + /// Registration with the session's size ownership — names the screen behind + /// this connection, tracked across every repository. pub(super) connection: u64, } impl Client { - /// End this client's connection, because it stopped keeping up. - /// - /// Only for that: a session leaving of its own accord is already tearing its - /// socket down, and shutting one it no longer owns down is not this side's - /// business. Errors are ignored — a socket that is already gone is the - /// outcome this is asking for. + /// End this client's connection because it stopped keeping up. Errors are + /// ignored — a socket that is already gone is the outcome this is asking for. pub(super) fn cut_off(&self) { if let Some(socket) = &self.socket { let _ = socket.shutdown(std::net::Shutdown::Both); @@ -53,18 +37,14 @@ pub struct TerminalSession { /// See [`Client::connection`]. pub(super) connection: u64, /// Behind a lock so the session can be shared: the daemon reads frames on - /// one thread while requests are dispatched from another. Uncontended in - /// practice — only the reader ever takes it. + /// one thread while requests are dispatched from another. pub(super) rx: std::sync::Mutex>, - /// How many diagnostic notes this client has left this window (see - /// [`TerminalSession::log_clear_key`]). + /// How many diagnostic notes this client has left this window. pub(super) reports: std::sync::Mutex, } impl TerminalSession { - /// This session's client id, as the hub stamps it on the panes this session - /// asked for. The daemon reads it to tell its own client's panes from - /// another's while relaying (see the daemon's `TerminalBridges`). + /// This session's client id, as stamped on the panes this session asked for. pub fn client_id(&self) -> u64 { self.id } @@ -107,19 +87,18 @@ impl TerminalSession { ClientMessage::Close { pane } => Command::Close { pane }, ClientMessage::Reorder { order } => Command::Reorder { order }, ClientMessage::CancelRecovery { pane } => Command::CancelRecovery { pane }, - // Off the worker queue like `claim_size`: it rearranges the panel - // and never reaches a PTY, so the queue that serializes work against - // the backend has nothing to offer it — and a backed-up hub must not - // drop the message, or the client stays laid out one way while every - // other client is laid out the other. + // Off the worker queue: it rearranges the panel and never reaches a + // PTY, so the queue that serializes work against the backend has + // nothing to offer it — and a backed-up hub must not drop the + // message, or the client stays laid out one way while every other + // client is laid out the other. ClientMessage::Zoom { pane } => { self.hub.set_zoom(pane); return; } // Off the worker queue for the same reason `start` is: it decides // who may resize, and a backed-up hub must not drop the message that - // hands the sizing over — the client would then be a spectator with - // no way to find out. + // hands the sizing over. ClientMessage::ClaimSize => { self.hub.claim_size(self.connection); return; diff --git a/src/web/viewer/terminal/session_tests.rs b/src/session/terminal/session_tests.rs similarity index 100% rename from src/web/viewer/terminal/session_tests.rs rename to src/session/terminal/session_tests.rs diff --git a/src/web/viewer/terminal/size_owner.rs b/src/session/terminal/size_owner.rs similarity index 96% rename from src/web/viewer/terminal/size_owner.rs rename to src/session/terminal/size_owner.rs index aeb1f47d..be778180 100644 --- a/src/web/viewer/terminal/size_owner.rs +++ b/src/session/terminal/size_owner.rs @@ -1,7 +1,7 @@ //! The hub's end of the session's size ownership. //! //! The rules and the state live one level up, in -//! [`crate::web::viewer::size_owner`]: which screen the panes are fitted to is +//! [`crate::session::size_owner`]: which screen the panes are fitted to is //! the session's answer, because every client shows the same repository. What is //! left here is the two places a hub touches it — a client asking for the sizing, //! and the worker letting a departed owner's grace run out. diff --git a/src/web/viewer/terminal/startup.rs b/src/session/terminal/startup.rs similarity index 68% rename from src/web/viewer/terminal/startup.rs rename to src/session/terminal/startup.rs index 26299cc3..7059ab5a 100644 --- a/src/web/viewer/terminal/startup.rs +++ b/src/session/terminal/startup.rs @@ -3,26 +3,23 @@ //! Not on the hub's own initiative: a PTY created before any client has measured //! its cell is born at a size nobody chose, and correcting it costs the child a //! full repaint. So the hub offers them and creates them at the size the first -//! client to answer reports — exactly once for its life, which is what the claim -//! below is for. +//! client to answer reports — exactly once for its life. use super::frame::{ServerMessage, TerminalFrame}; use super::hub_helpers::{self, Command, StartupPane}; use super::{DEFAULT_PANE_SIZE, PaneSize, TerminalHub}; -use crate::web::viewer::limits; +use crate::session::limits; use std::sync::atomic::Ordering; impl TerminalHub { /// Create the startup terminals at the sizes a client measured, if nobody /// has claimed them yet. /// - /// The claim is what makes this once-per-hub-life. It is taken here rather - /// than when a client connects because a client that never answers must - /// not consume it — the next one to connect is offered the panes again, - /// which is what keeps a dropped handshake from being fatal. Two clients - /// answering at once is normal (both were offered): the first to arrive - /// wins the exchange and the second is ignored, so the panes are created - /// exactly once. + /// The claim is taken here rather than when a client connects because a + /// client that never answers must not consume it — the next one to connect + /// is offered the panes again. Two clients answering at once is normal + /// (both were offered): the first to arrive wins and the second is ignored, + /// so the panes are created exactly once. pub(super) fn claim_startup(&self, client: u64, sizes: &[PaneSize]) { if self .started @@ -42,19 +39,17 @@ impl TerminalHub { .enumerate() .map(|(index, configured)| StartupPane { // A client that could only measure some cells still gets the - // rest; they are simply born at the old default and corrected - // by the first fit, which is where every pane started before. + // rest; they are born at the default and corrected by the + // first fit. size: sizes.get(index).copied().unwrap_or(DEFAULT_PANE_SIZE), // The name it was configured under, else the command text: what - // the operator would recognise the pane by. A program that emits - // OSC 0/2 still renames it afterwards. + // the operator would recognise the pane by. title: configured.as_ref().map(|sc| { sc.name .clone() .unwrap_or_else(|| sc.command.trim().to_string()) }), - // The opt-in, carried through so the worker can record it. Only - // a configured pane can have one; nothing else ever gains it. + // The opt-in, carried through so the worker can record it. plugin: configured.as_ref().and_then(|sc| sc.plugin.clone()), command: configured.map(|sc| sc.command), }) @@ -62,9 +57,8 @@ impl TerminalHub { // Hold the free cap slots before the command is even queued. Another // connection's handler thread can enqueue creates between here and the - // worker reaching this batch, and the worker would serve those first; - // the reservation is what stops them taking slots this set claimed. - // Only what is free — terminals already open are not displaced. + // worker reaching this batch; the reservation stops them taking slots + // this set claimed. let reserved = { let mut state = self.state.lock().expect("terminal state poisoned"); let free = limits::MAX_PTYS_PER_REPO.saturating_sub(state.panes.len() + state.reserved); @@ -77,8 +71,7 @@ impl TerminalHub { // `try_send` because a full queue means backpressure the connection // thread must not block on — and as a single message that is // all-or-nothing, so there is no state where some startup terminals - // were accepted and the rest were silently lost with the claim already - // spent. + // were accepted and the rest were silently lost. if self .commands .try_send(Command::CreateStartup { @@ -90,18 +83,16 @@ impl TerminalHub { { self.release_reserved(reserved); // Hand the claim back and offer again, or the hub would hold - // `started` with no terminals to show for it — the one outcome - // this handshake exists to rule out. The re-offer matters as much - // as the release: this client has already cleared its pending - // state and will not ask again on its own. + // `started` with no terminals to show for it. The re-offer matters + // as much as the release: this client has already cleared its + // pending state and will not ask again on its own. tracing::warn!("viewer: terminal command queue full, startup deferred"); self.started.store(false, Ordering::Release); // To everyone, not just whoever answered. The offer belongs to // whichever client replies first, and the one that just did may be // gone by now — or another may have answered while the claim was - // held and had its `start` ignored, clearing its pending state on - // the way. Re-offering to only one leaves the rest with no reason - // to ask again. + // held and had its `start` ignored. Re-offering to only one leaves + // the rest with no reason to ask again. self.broadcast_pending(); } } diff --git a/src/web/viewer/terminal/startup_run.rs b/src/session/terminal/startup_run.rs similarity index 100% rename from src/web/viewer/terminal/startup_run.rs rename to src/session/terminal/startup_run.rs diff --git a/src/web/viewer/terminal/tests/backpressure.rs b/src/session/terminal/tests/backpressure.rs similarity index 86% rename from src/web/viewer/terminal/tests/backpressure.rs rename to src/session/terminal/tests/backpressure.rs index b26c2537..b5309193 100644 --- a/src/web/viewer/terminal/tests/backpressure.rs +++ b/src/session/terminal/tests/backpressure.rs @@ -1,8 +1,8 @@ //! What happens to a client that stops draining its queue. use super::{attach_over_socket, created_pane, next_matching, spawn_hub}; -use crate::web::viewer::terminal::CLIENT_QUEUE_DEPTH; -use crate::web::viewer::terminal::frame::ClientMessage; +use crate::session::terminal::CLIENT_QUEUE_DEPTH; +use crate::session::terminal::frame::ClientMessage; use std::io::Read; #[test] @@ -57,9 +57,8 @@ fn an_evicted_client_still_releases_the_sizing_when_its_session_ends() { // connection leaves. let dir = tempfile::TempDir::new().unwrap(); let cwd = dir.path().to_string_lossy().to_string(); - let ownership: std::sync::Arc = - Default::default(); - let hub = crate::web::viewer::terminal::TerminalHub::spawn( + let ownership: std::sync::Arc = Default::default(); + let hub = crate::session::terminal::TerminalHub::spawn( &cwd, Vec::new(), Vec::new(), @@ -67,7 +66,7 @@ fn an_evicted_client_still_releases_the_sizing_when_its_session_ends() { ownership.clone(), ); - let evicted = crate::web::viewer::size_owner::ViewerId::Browser("evicted".to_string()); + let evicted = crate::session::size_owner::ViewerId::Browser("evicted".to_string()); let session = hub.connect(evicted.clone(), true, None); assert_eq!(ownership.owner(), Some(evicted.clone()), "it arrived last"); @@ -81,10 +80,10 @@ fn an_evicted_client_still_releases_the_sizing_when_its_session_ends() { } // Whoever is left takes the sizing once the departed owner's grace runs out. - let waiting = crate::web::viewer::size_owner::ViewerId::Browser("waiting".to_string()); + let waiting = crate::session::size_owner::ViewerId::Browser("waiting".to_string()); let _other = hub.connect(waiting.clone(), false, None); drop(session); - ownership.settle(std::time::Instant::now() + crate::web::viewer::size_owner::RELEASE_GRACE * 2); + ownership.settle(std::time::Instant::now() + crate::session::size_owner::RELEASE_GRACE * 2); assert_eq!( ownership.owner(), diff --git a/src/web/viewer/terminal/tests/behavior.rs b/src/session/terminal/tests/behavior.rs similarity index 98% rename from src/web/viewer/terminal/tests/behavior.rs rename to src/session/terminal/tests/behavior.rs index 2ae3dd8d..4863c82c 100644 --- a/src/web/viewer/terminal/tests/behavior.rs +++ b/src/session/terminal/tests/behavior.rs @@ -3,8 +3,8 @@ use super::{ resized_size, spawn_hub, }; use crate::backend::PaneId; -use crate::web::viewer::limits; -use crate::web::viewer::terminal::frame::{ClientMessage, PaneSize, TerminalFrame}; +use crate::session::limits; +use crate::session::terminal::frame::{ClientMessage, PaneSize, TerminalFrame}; #[test] #[cfg(unix)] diff --git a/src/web/viewer/terminal/tests/identity.rs b/src/session/terminal/tests/identity.rs similarity index 99% rename from src/web/viewer/terminal/tests/identity.rs rename to src/session/terminal/tests/identity.rs index 17f12429..f30301d4 100644 --- a/src/web/viewer/terminal/tests/identity.rs +++ b/src/session/terminal/tests/identity.rs @@ -4,7 +4,7 @@ use super::{ attach, collect_created, created_pane, created_requester, exited_pane, hello_client, hello_panes, next_matching, spawn_hub, }; -use crate::web::viewer::terminal::frame::ClientMessage; +use crate::session::terminal::frame::ClientMessage; #[test] fn a_connecting_client_is_told_its_own_id_first() { diff --git a/src/web/viewer/terminal/tests/mod.rs b/src/session/terminal/tests/mod.rs similarity index 97% rename from src/web/viewer/terminal/tests/mod.rs rename to src/session/terminal/tests/mod.rs index bf217f16..0f77a412 100644 --- a/src/web/viewer/terminal/tests/mod.rs +++ b/src/session/terminal/tests/mod.rs @@ -19,7 +19,7 @@ mod wire; mod zoom; use crate::backend::PaneId; -use crate::web::viewer::terminal::frame::TerminalFrame; +use crate::session::terminal::frame::TerminalFrame; use std::thread; use std::time::{Duration, Instant}; @@ -244,7 +244,7 @@ pub(super) fn spawn_hub( pub(super) fn attach(hub: &std::sync::Arc) -> TerminalSession { use std::sync::atomic::{AtomicU64, Ordering}; static NEXT: AtomicU64 = AtomicU64::new(0); - let id = crate::web::viewer::size_owner::ViewerId::Browser(format!( + let id = crate::session::size_owner::ViewerId::Browser(format!( "test-{}", NEXT.fetch_add(1, Ordering::Relaxed) )); @@ -268,7 +268,7 @@ pub(super) fn attach_over_socket( let addr = listener.local_addr().expect("no local address"); let peer = std::net::TcpStream::connect(addr).expect("could not dial the listener"); let (served, _) = listener.accept().expect("no inbound connection"); - let id = crate::web::viewer::size_owner::ViewerId::Browser(format!( + let id = crate::session::size_owner::ViewerId::Browser(format!( "socket-{}", NEXT.fetch_add(1, Ordering::Relaxed) )); diff --git a/src/web/viewer/terminal/tests/plugin_reload.rs b/src/session/terminal/tests/plugin_reload.rs similarity index 98% rename from src/web/viewer/terminal/tests/plugin_reload.rs rename to src/session/terminal/tests/plugin_reload.rs index 491414d6..59be40e9 100644 --- a/src/web/viewer/terminal/tests/plugin_reload.rs +++ b/src/session/terminal/tests/plugin_reload.rs @@ -18,8 +18,8 @@ use super::plugins::{Fixture, LOG_ENV, fixture, logged, logged_event, shell_plugin}; use super::{attach, collect_created, spawn_hub, wait_for}; use crate::config::{PluginConfig, StartupCommand}; -use crate::web::viewer::terminal::TerminalHub; -use crate::web::viewer::terminal::frame::ClientMessage; +use crate::session::terminal::TerminalHub; +use crate::session::terminal::frame::ClientMessage; use std::path::{Path, PathBuf}; use std::sync::Arc; diff --git a/src/web/viewer/terminal/tests/plugin_reload_panes.rs b/src/session/terminal/tests/plugin_reload_panes.rs similarity index 98% rename from src/web/viewer/terminal/tests/plugin_reload_panes.rs rename to src/session/terminal/tests/plugin_reload_panes.rs index 2bc6e30c..d2d1e468 100644 --- a/src/web/viewer/terminal/tests/plugin_reload_panes.rs +++ b/src/session/terminal/tests/plugin_reload_panes.rs @@ -13,7 +13,7 @@ use super::plugin_reload::{plugin_with_log, respawning}; use super::plugins::fixture; use crate::backend::{PtyBackend, TerminalBackend}; use crate::config::{PluginConfig, ShellConfig}; -use crate::web::viewer::terminal::hub_plugins::Plugins; +use crate::session::terminal::hub_plugins::Plugins; use std::collections::HashMap; /// The arm of the decision that only a `watch_on_signal` plugin can reach: @@ -68,7 +68,7 @@ fn a_plugin_no_startup_pane_names_is_kept_while_it_watches_a_live_pane() { #[test] fn a_replaced_plugin_does_not_leave_a_relaunch_hold_behind() { use super::plugin_rules::{COLS, LONG_RUNNING, ROWS, opt_in, token_of}; - use crate::web::viewer::terminal::hub_plugins_slots::PaneSpot; + use crate::session::terminal::hub_plugins_slots::PaneSpot; use std::time::Instant; let f = fixture(); diff --git a/src/web/viewer/terminal/tests/plugin_rules.rs b/src/session/terminal/tests/plugin_rules.rs similarity index 98% rename from src/web/viewer/terminal/tests/plugin_rules.rs rename to src/session/terminal/tests/plugin_rules.rs index b5ae14a7..092714ab 100644 --- a/src/web/viewer/terminal/tests/plugin_rules.rs +++ b/src/session/terminal/tests/plugin_rules.rs @@ -15,7 +15,7 @@ use crate::backend::{PaneId, PaneToken, PtyBackend, TerminalBackend}; use crate::config::{ShellConfig, StartupCommand}; use crate::plugin::Refused; use crate::plugin::protocol::{PROTOCOL_VERSION, PluginCommand}; -use crate::web::viewer::terminal::hub_plugins::{PANE_IDLE_THRESHOLD, Plugins}; +use crate::session::terminal::hub_plugins::{PANE_IDLE_THRESHOLD, Plugins}; use std::time::Instant; pub(super) const ROWS: u16 = 24; diff --git a/src/web/viewer/terminal/tests/plugin_slots.rs b/src/session/terminal/tests/plugin_slots.rs similarity index 97% rename from src/web/viewer/terminal/tests/plugin_slots.rs rename to src/session/terminal/tests/plugin_slots.rs index 00319b80..be656bbb 100644 --- a/src/web/viewer/terminal/tests/plugin_slots.rs +++ b/src/session/terminal/tests/plugin_slots.rs @@ -16,8 +16,8 @@ use super::plugins::{fixture, logged, logged_event, recorder}; use crate::backend::{PtyBackend, TerminalBackend}; use crate::config::ShellConfig; use crate::plugin::{Approved, RateLimits, Refused}; -use crate::web::viewer::terminal::hub_plugins::Plugins; -use crate::web::viewer::terminal::hub_plugins_slots::{PENDING_RELAUNCH_TTL, PaneSpot}; +use crate::session::terminal::hub_plugins::Plugins; +use crate::session::terminal::hub_plugins_slots::{PENDING_RELAUNCH_TTL, PaneSpot}; use std::time::Instant; #[test] diff --git a/src/web/viewer/terminal/tests/plugin_watch.rs b/src/session/terminal/tests/plugin_watch.rs similarity index 98% rename from src/web/viewer/terminal/tests/plugin_watch.rs rename to src/session/terminal/tests/plugin_watch.rs index 4beef410..3d6ee57a 100644 --- a/src/web/viewer/terminal/tests/plugin_watch.rs +++ b/src/session/terminal/tests/plugin_watch.rs @@ -16,8 +16,8 @@ use crate::backend::{PaneId, PaneToken, PtyBackend, TerminalBackend}; use crate::config::{PluginConfig, ShellConfig}; use crate::plugin::protocol::{PROTOCOL_VERSION, PluginCommand}; use crate::plugin::{Approved, Refused}; -use crate::web::viewer::terminal::hub_plugins::Plugins; -use crate::web::viewer::terminal::hub_recovery::is_relaunchable; +use crate::session::terminal::hub_plugins::Plugins; +use crate::session::terminal::hub_recovery::is_relaunchable; use std::path::Path; use std::time::Instant; diff --git a/src/web/viewer/terminal/tests/plugins.rs b/src/session/terminal/tests/plugins.rs similarity index 99% rename from src/web/viewer/terminal/tests/plugins.rs rename to src/session/terminal/tests/plugins.rs index bfffa878..22edd2a2 100644 --- a/src/web/viewer/terminal/tests/plugins.rs +++ b/src/session/terminal/tests/plugins.rs @@ -15,7 +15,7 @@ use super::{ }; use crate::config::{PluginConfig, StartupCommand}; use crate::plugin::protocol::PROTOCOL_VERSION; -use crate::web::viewer::terminal::frame::{ClientMessage, PaneSize, TerminalFrame}; +use crate::session::terminal::frame::{ClientMessage, PaneSize, TerminalFrame}; use std::path::{Path, PathBuf}; /// Env var the fake plugin appends the events it receives to. diff --git a/src/web/viewer/terminal/tests/reattach.rs b/src/session/terminal/tests/reattach.rs similarity index 87% rename from src/web/viewer/terminal/tests/reattach.rs rename to src/session/terminal/tests/reattach.rs index 82f5cb0f..045fcb5a 100644 --- a/src/web/viewer/terminal/tests/reattach.rs +++ b/src/session/terminal/tests/reattach.rs @@ -10,11 +10,11 @@ //! sequences that require a Unix shell. #![cfg(unix)] -use super::{attach, created_pane, next_matching, spawn_hub}; +use super::{attach, created_pane, next_matching}; use crate::backend::PaneId; -use crate::web::viewer::terminal::TerminalHub; -use crate::web::viewer::terminal::TerminalSession; -use crate::web::viewer::terminal::frame::{ClientMessage, TerminalFrame}; +use crate::session::terminal::TerminalHub; +use crate::session::terminal::TerminalSession; +use crate::session::terminal::frame::{ClientMessage, TerminalFrame}; use std::time::Duration; /// Long enough for anything the hub has already queued to be read, short enough @@ -62,7 +62,19 @@ struct Running { fn pane_running(sequences: &str) -> Running { let dir = tempfile::TempDir::new().unwrap(); - let hub = spawn_hub(&dir.path().to_string_lossy(), Vec::new(), Vec::new()); + // These assertions exercise the hub's repaint protocol, not the user's rc + // files or shell-specific trap syntax. Bash is present on both supported + // Unix targets and gives the fixture one deterministic WINCH handler. + let hub = TerminalHub::spawn( + &dir.path().to_string_lossy(), + Vec::new(), + Vec::new(), + crate::config::ShellConfig { + program: Some("bash".to_string()), + command_args: Vec::new(), + }, + Default::default(), + ); let session = attach(&hub); session.dispatch(ClientMessage::Create { rows: ROWS, @@ -135,14 +147,11 @@ fn a_reattaching_client_makes_an_alternate_screen_program_draw_again() { let (hub, pane) = (&running.hub, running.pane); let second = attach(hub); - let redrew = next_matching(&second, |f| { - matches!(f, TerminalFrame::Output { pane: p, data } - if *p == pane && String::from_utf8_lossy(data).contains("REDREW")) - }); + let redrew = String::from_utf8_lossy(&output_for(&second, pane)).to_string(); assert!( - redrew.is_some(), - "attaching must make the pane's program draw its screen again" + redrew.contains("REDREW"), + "attaching must make the pane's program draw its screen again, got: {redrew:?}" ); hub.stop(); } diff --git a/src/web/viewer/terminal/tests/recovery.rs b/src/session/terminal/tests/recovery.rs similarity index 96% rename from src/web/viewer/terminal/tests/recovery.rs rename to src/session/terminal/tests/recovery.rs index f0e88d68..d6165df0 100644 --- a/src/web/viewer/terminal/tests/recovery.rs +++ b/src/session/terminal/tests/recovery.rs @@ -12,11 +12,11 @@ use super::plugins::{Fixture, fixture, logged_event, shell_plugin}; use super::{attach, collect_created, created_pane, next_matching, spawn_hub}; use crate::backend::{PaneId, PtyBackend}; use crate::config::{PluginConfig, ShellConfig, StartupCommand}; -use crate::web::viewer::terminal::TerminalSession; -use crate::web::viewer::terminal::frame::{ClientMessage, TerminalFrame}; -use crate::web::viewer::terminal::hub_plugins::Plugins; -use crate::web::viewer::terminal::hub_plugins_slots::{PENDING_RELAUNCH_TTL, PaneSpot}; -use crate::web::viewer::terminal::hub_recovery::RECOVERY_CANCELLED; +use crate::session::terminal::TerminalSession; +use crate::session::terminal::frame::{ClientMessage, TerminalFrame}; +use crate::session::terminal::hub_plugins::Plugins; +use crate::session::terminal::hub_plugins_slots::{PENDING_RELAUNCH_TTL, PaneSpot}; +use crate::session::terminal::hub_recovery::RECOVERY_CANCELLED; use std::path::Path; use std::time::{Duration, Instant}; diff --git a/src/web/viewer/terminal/tests/scrollback_depth.rs b/src/session/terminal/tests/scrollback_depth.rs similarity index 97% rename from src/web/viewer/terminal/tests/scrollback_depth.rs rename to src/session/terminal/tests/scrollback_depth.rs index a236493a..0ae9ec04 100644 --- a/src/web/viewer/terminal/tests/scrollback_depth.rs +++ b/src/session/terminal/tests/scrollback_depth.rs @@ -14,8 +14,8 @@ use crate::runtime::emulator::PaneEmulator; use crate::runtime::terminal::SCROLLBACK_LINES; -use crate::web::viewer::limits; -use crate::web::viewer::terminal::hub_helpers::push_scrollback; +use crate::session::limits; +use crate::session::terminal::hub_helpers::push_scrollback; use std::collections::VecDeque; /// Rows and columns of the pane the replay is measured into. The width decides diff --git a/src/web/viewer/terminal/tests/size_owner.rs b/src/session/terminal/tests/size_owner.rs similarity index 96% rename from src/web/viewer/terminal/tests/size_owner.rs rename to src/session/terminal/tests/size_owner.rs index 5c31e999..08d9ad54 100644 --- a/src/web/viewer/terminal/tests/size_owner.rs +++ b/src/session/terminal/tests/size_owner.rs @@ -6,8 +6,8 @@ use super::{attach, next_matching, resized_size, spawn_hub}; use crate::config::ShellConfig; -use crate::web::viewer::terminal::frame::{ClientMessage, PaneSize, TerminalFrame}; -use crate::web::viewer::terminal::{TerminalHub, TerminalSession}; +use crate::session::terminal::frame::{ClientMessage, PaneSize, TerminalFrame}; +use crate::session::terminal::{TerminalHub, TerminalSession}; /// Whether a frame says this session owns the sizing. fn owned(frame: &TerminalFrame) -> Option { @@ -104,8 +104,7 @@ fn the_sizing_passes_to_the_newest_client_still_attached() { fn one_answer_covers_every_repository_in_the_session() { let dir = tempfile::TempDir::new().unwrap(); let cwd = dir.path().to_string_lossy().to_string(); - let ownership: std::sync::Arc = - Default::default(); + let ownership: std::sync::Arc = Default::default(); let one = TerminalHub::spawn( &cwd, Vec::new(), @@ -123,7 +122,7 @@ fn one_answer_covers_every_repository_in_the_session() { // An attached terminal subscribes to every open repository at once. Those // are one screen, so only its first subscription is an arrival. - let tui = crate::web::viewer::size_owner::ViewerId::Attached(1); + let tui = crate::session::size_owner::ViewerId::Attached(1); let tui_one = one.connect(tui.clone(), true, None); let tui_two = two.connect(tui, false, None); assert!(verdict(&tui_one)); diff --git a/src/web/viewer/terminal/tests/startup.rs b/src/session/terminal/tests/startup.rs similarity index 99% rename from src/web/viewer/terminal/tests/startup.rs rename to src/session/terminal/tests/startup.rs index 39adb88b..340287a9 100644 --- a/src/web/viewer/terminal/tests/startup.rs +++ b/src/session/terminal/tests/startup.rs @@ -10,8 +10,8 @@ use super::{ pending_count, spawn_hub, }; use crate::config::StartupCommand; -use crate::web::viewer::limits; -use crate::web::viewer::terminal::frame::{ClientMessage, PaneSize, TerminalFrame}; +use crate::session::limits; +use crate::session::terminal::frame::{ClientMessage, PaneSize, TerminalFrame}; /// A startup terminal configured with no name of its own, which is what most of /// these tests care about — they assert on panes, not on what they are called. diff --git a/src/web/viewer/terminal/tests/wire.rs b/src/session/terminal/tests/wire.rs similarity index 98% rename from src/web/viewer/terminal/tests/wire.rs rename to src/session/terminal/tests/wire.rs index 15d7cc8c..db397778 100644 --- a/src/web/viewer/terminal/tests/wire.rs +++ b/src/session/terminal/tests/wire.rs @@ -1,11 +1,11 @@ //! Contracts that need no hub: the wire encodings, the size clamp, and the two //! pure helpers the worker leans on. -use crate::web::viewer::limits; -use crate::web::viewer::terminal::frame::{ +use crate::session::limits; +use crate::session::terminal::frame::{ ClearKeyFacts, ClientMessage, PaneSize, ServerMessage, decode_output, encode_output, }; -use crate::web::viewer::terminal::hub_helpers::{canonical_order, push_scrollback}; +use crate::session::terminal::hub_helpers::{canonical_order, push_scrollback}; use std::collections::VecDeque; #[test] diff --git a/src/web/viewer/terminal/tests/zoom.rs b/src/session/terminal/tests/zoom.rs similarity index 97% rename from src/web/viewer/terminal/tests/zoom.rs rename to src/session/terminal/tests/zoom.rs index d8f9590b..e80f64c2 100644 --- a/src/web/viewer/terminal/tests/zoom.rs +++ b/src/session/terminal/tests/zoom.rs @@ -1,8 +1,8 @@ //! Which pane fills the panel: the hub's answer, broadcast and replayed. use super::{attach, collect_created, created_pane, next_matching, spawn_hub, zoomed_pane}; -use crate::web::viewer::terminal::TerminalSession; -use crate::web::viewer::terminal::frame::{ClientMessage, PaneSize}; +use crate::session::terminal::TerminalSession; +use crate::session::terminal::frame::{ClientMessage, PaneSize}; /// The zoom `session` is told about next. fn next_zoom(session: &TerminalSession) -> Option> { diff --git a/src/test_util.rs b/src/test_util.rs index 8c1c75b5..7a975f1c 100644 --- a/src/test_util.rs +++ b/src/test_util.rs @@ -2,8 +2,7 @@ //! //! Several modules drive a real `git` binary against a `TempDir` to verify //! repository discovery, commit walking, etc. Centralizing the setup here -//! keeps the helpers in one place instead of duplicating them per test -//! module. +//! keeps the helpers in one place. #![cfg(test)] @@ -54,7 +53,7 @@ pub fn make_repo() -> (TempDir, String) { /// /// Returns both temporaries — the main repository's and the one holding the /// worktree — and the worktree's path. Both must be kept alive for the duration -/// of the test: the worktree cannot be read without the repository it points at. +/// of the test. pub fn make_linked_worktree() -> (TempDir, TempDir, String) { let (main, main_path) = make_repo(); // `git worktree add` needs a commit to base the new tree on. @@ -78,46 +77,32 @@ pub fn make_linked_worktree() -> (TempDir, TempDir, String) { /// `prefs_dir` is required, and must be the caller's own temporary directory. /// The accent and the active project *are* preferences, so a session driven from /// a test writes this file — and every session pointed at one path is one -/// session: a test would begin in the colour whichever test ran last chose, and -/// asking for that same colour is no change at all, which the watcher answers -/// with silence rather than a frame. Pass the directory that already holds the -/// test's socket and the file goes away with the test. -/// -/// This replaced a single fixed path outside any temporary directory, chosen -/// because it was expected to be unwritable. It is unwritable for an ordinary -/// user, which is what CI runs as; a test suite running as root — the default in -/// a bare container — created it and shared it, and the accent tests then -/// depended on each other's order across whole runs. +/// session. Pass the directory that already holds the test's socket and the +/// file goes away with the test. pub fn session_state( repos: &[String], prefs_dir: &Path, -) -> std::sync::Arc { - std::sync::Arc::new(crate::web::viewer::server::ViewerState::new( - crate::web::viewer::server::ViewerOptions { - bind: "127.0.0.1".parse().unwrap(), - port: 0, - auth: crate::web::common::auth::Auth::from_plaintext("swordfish").unwrap(), +) -> std::sync::Arc { + std::sync::Arc::new(crate::session::SessionState::new( + crate::session::SessionOptions { repos: repos.to_vec(), persist: false, startup_commands: Vec::new(), cli_startup: Vec::new(), shell: crate::config::ShellConfig::default(), - hot: crate::config::AgentIndicatorConfig::default(), - prefs: crate::web::viewer::prefs::PrefsStore::at(prefs_dir.join("viewer.json")), + prefs: crate::session::prefs::PrefsStore::at(prefs_dir.join("viewer.json")), + status_encoder: crate::session::test_status_encoder, }, )) } /// In-memory `TerminalBackend` for tests: spawns nothing, just records the -/// command each `create_pane` was asked to run so pane-creation logic can be -/// asserted deterministically without a real PTY or shell. +/// command each `create_pane` was asked to run. #[derive(Default)] pub struct FakeBackend { next_id: crate::backend::PaneId, pub launched: Vec>, - /// Byte payloads passed to `send_input`, in call order. Lets input tests - /// assert the exact bytes forwarded to the PTY (pass-through, literal - /// leader) without a real terminal. + /// Byte payloads passed to `send_input`, in call order. pub sent: Vec>, /// Events handed out by the next `drain_events` call. Shared handle so a /// test can keep a clone and inject synthetic pane output/exit after the diff --git a/src/ui/commit_list/row.rs b/src/ui/commit_list/row.rs index 26cb4cf7..4ee210f0 100644 --- a/src/ui/commit_list/row.rs +++ b/src/ui/commit_list/row.rs @@ -46,11 +46,11 @@ pub(super) fn format_relative_time(ts: i64) -> String { } } -/// Divergence glyph (`↑` ahead of upstream, `v` behind) and shape glyph +/// Divergence glyph (`^` ahead of upstream, `v` behind) and shape glyph /// (`*` HEAD, `Y` merge). Two fixed cells so rows stay column-aligned. fn glyphs(entry: &CommitEntry, decorations: &LogDecorations) -> (Span<'static>, Span<'static>) { let divergence = if decorations.is_ahead(entry.oid) { - Span::styled("↑", Style::default().fg(Color::Green)) + Span::styled("^", Style::default().fg(Color::Green)) } else if decorations.is_behind(entry.oid) { Span::styled("v", Style::default().fg(Color::Yellow)) } else { diff --git a/src/ui/diff_viewer/gutter.rs b/src/ui/diff_viewer/gutter.rs index 5eb4dbc1..d8527838 100644 --- a/src/ui/diff_viewer/gutter.rs +++ b/src/ui/diff_viewer/gutter.rs @@ -6,9 +6,8 @@ use ratatui::{ widgets::{Paragraph, Wrap}, }; -/// Minimum digits reserved for one line-number column. Keeps the gutter — and -/// with it the body's left edge — from twitching between a 1-digit and a -/// 2-digit file, the way `delta`/`diff-so-fancy` reserve a fixed column. +/// Minimum digits reserved for one line-number column. Keeps the gutter from +/// twitching between a 1-digit and a 2-digit file. const MIN_LINENO_DIGITS: usize = 3; /// One padding space on each side of a number column: it lifts the digits off @@ -30,13 +29,10 @@ pub(crate) fn digits_for(max_lineno: usize) -> usize { /// Gutter digit count for a whole loaded diff: the widest line number that /// appears on either side of any hunk. Derived from the loaded hunks, never -/// from the visible window, so scrolling cannot change the gutter width and -/// shift the body sideways mid-scroll. +/// from the visible window, so scrolling cannot change the gutter width. /// /// Recomputed per frame instead of cached: it is one allocation-free pass over -/// the same lines `ensure_highlight_cache` already walks for its fingerprint, -/// and a cache would need invalidating at every `DiffPane::hunks` mutation — -/// a missed one renders numbers against the wrong column width. +/// the same lines `ensure_highlight_cache` already walks for its fingerprint. pub(crate) fn lineno_digits(hunks: &[DiffHunk]) -> usize { let max = hunks .iter() @@ -83,15 +79,13 @@ fn lineno_text(no: Option) -> String { /// /// The two are separate `Paragraph`s: `Paragraph::scroll` shifts the whole /// line, so a gutter span living in the body's paragraph would slide off the -/// left edge as soon as `scroll_x > 0`. Vertical scroll is instead expressed -/// by *which* lines the caller collected, so passing windows built from the -/// same rows is what keeps numbers aligned with their content. +/// left edge. Vertical scroll is instead expressed by *which* lines the caller +/// collected. /// -/// With `wrap` set that split is abandoned for the opposite reason: a wrapped -/// body line occupies several screen rows while its gutter line still occupies -/// one, which would desynchronise every row below it. The number is folded into -/// the body line instead, where wrapping carries it along. That is safe only -/// because wrapping and horizontal scrolling cannot both be active. +/// With `wrap` set that split is abandoned: a wrapped body line occupies +/// several screen rows while its gutter line still occupies one, which would +/// desynchronise every row below it. The number is folded into the body line +/// instead, where wrapping carries it along. pub(crate) fn render_gutter_and_body( frame: &mut Frame, inner: Rect, diff --git a/src/ui/helpers.rs b/src/ui/helpers.rs index 3c99ef07..848b876a 100644 --- a/src/ui/helpers.rs +++ b/src/ui/helpers.rs @@ -1,4 +1,4 @@ -use crate::app::App; +use crate::app::{App, leader_label_of}; use crate::git::diff::StatusKind; use ratatui::{ Frame, @@ -41,7 +41,7 @@ pub(crate) fn status_color(status: StatusKind) -> Color { /// as Ctrl+F1, and that misreading names a real binding — the bare F-keys /// select project tabs. Matches how the hint bar already writes `^F t`. pub(crate) fn jump_legend(app: &App, digit: char) -> String { - format!("{} {}", app.leader_label(), digit) + format!("{} {}", leader_label_of(app.interaction.leader), digit) } pub(crate) fn render_selectable_list( @@ -102,7 +102,7 @@ pub(crate) fn render_search_bar( // A blank in the dark half keeps the cell, so the row does not shift. let cursor = match (is_active, caret_lit(blink_phase())) { (false, _) => "", - (true, true) => "█", + (true, true) => "|", (true, false) => " ", }; let style = if is_active { diff --git a/src/ui/hint_bar.rs b/src/ui/hint_bar.rs index 7d8c3ee5..a541c870 100644 --- a/src/ui/hint_bar.rs +++ b/src/ui/hint_bar.rs @@ -1,4 +1,4 @@ -use crate::app::App; +use crate::app::{App, leader_label_of}; use crate::ui::chrome::{Chrome, chrome_rows}; use crate::ui::hint_text::{ EMPTY_HINT, EMPTY_HINT_ARMED, PREFIX_CHIP, normal_hint_literal, prefix_armed_hint_text, @@ -45,14 +45,10 @@ pub(crate) fn segment_click(keyspec: &str) -> Option { } /// Build the styled spans for a hint legend, inverting (`REVERSED`) every -/// clickable segment — the whole `key: description` label, matching the click -/// target exactly — so the bar itself shows which hints respond to a click. +/// clickable segment so the bar itself shows which hints respond to a click. /// Consumes the same literal and `" | "` segmentation as `hint_click_at` and /// decides clickability with the same `segment_click`, so an inverted label -/// can never disagree with the hit test. Only styles change; the rendered -/// text (and thus every column offset) stays identical. `mark_clickable` is -/// `[mouse] enabled`: with capture off a click can never arrive, so no label -/// may advertise one. +/// can never disagree with the hit test. `mark_clickable` is `[mouse] enabled`. pub(crate) fn hint_spans(text: &str, leader: &str, mark_clickable: bool) -> Vec> { let base = Style::default().fg(Color::DarkGray); let inverted = base.add_modifier(Modifier::REVERSED); @@ -84,13 +80,10 @@ pub(crate) fn hint_spans(text: &str, leader: &str, mark_clickable: bool) -> Vec< spans } -/// The dialog's input line, with its keys spelled out after the caret. Nothing -/// else advertises them: the dialog replaces the hint legend entirely, so a key -/// missing from this line cannot be found anywhere on screen. -/// -/// The legend is dropped whole when the path leaves no room for it, rather than -/// clipped — the caret has to stay visible, and half a legend reads as a glitch. -/// `width` is the hint row's; 0 means "unknown", which keeps the legend. +/// The dialog's input line, with its keys spelled out after the caret. +/// The legend is dropped whole when the path leaves no room for it, rather +/// than clipped — the caret has to stay visible. `width` is the hint row's; +/// 0 means "unknown", which keeps the legend. pub(crate) fn repo_input_line<'a>( repo_input: &'a RepoInput, accent: Color, @@ -98,14 +91,14 @@ pub(crate) fn repo_input_line<'a>( ) -> Line<'a> { const PROMPT: &str = "repo: "; let legend = if repo_input.picker.is_some() { - " ↓↑/jk: move | →: open | ←: up | enter: select | esc: back" + " up/dn/jk: move | right: open | left: up | enter: select | esc: back" } else { - " ↓: browse | tab: complete | enter: open | esc: cancel" + " down: browse | tab: complete | enter: open | esc: cancel" }; let mut spans = vec![ Span::styled(PROMPT, Style::default().fg(accent)), Span::raw(repo_input.buf.as_str()), - Span::styled("█", Style::default().fg(accent)), + Span::styled("|", Style::default().fg(accent)), ]; // Display columns, not bytes: a path can hold wide or combining characters. let used: usize = spans.iter().map(Span::width).sum(); @@ -126,7 +119,8 @@ pub(crate) fn render_hint_bar<'a>( // stays the input line plus its own legend. return Paragraph::new(repo_input_line(chrome.repo_input, accent, width)); } - if app.prefix_armed() { + let leader = leader_label_of(app.interaction.leader); + if app.interaction.prefix_armed { let mut spans = vec![Span::styled( PREFIX_CHIP, Style::default() @@ -136,12 +130,12 @@ pub(crate) fn render_hint_bar<'a>( )]; spans.extend(hint_spans( &prefix_armed_hint_text(app), - &app.leader_label(), - app.mouse_enabled, + &leader, + app.interaction.mouse_enabled, )); return Paragraph::new(Line::from(spans)); } - if app.awaiting_swap_target() { + if app.interaction.awaiting_swap_target { // The swap-target digits follow the same layout-aware mapping as the // focus jumps: `1-8` while the terminal fills the body, `3-9,0` in // the split view. @@ -168,8 +162,8 @@ pub(crate) fn render_hint_bar<'a>( // footer names the actual key to press rather than an abstract word. Paragraph::new(Line::from(hint_spans( normal_hint_literal(app), - &app.leader_label(), - app.mouse_enabled, + &leader, + app.interaction.mouse_enabled, ))) } @@ -233,7 +227,7 @@ pub(crate) fn hint_click_at( // With mouse capture off no click can reach us anyway, but the bar also // renders no inverted labels (`hint_spans`) — keep the affordance and the // hit test in agreement rather than relying on the caller. - if !app.mouse_enabled { + if !app.interaction.mouse_enabled { return None; } let hint_area = chrome_rows(screen_area).hint; @@ -245,15 +239,15 @@ pub(crate) fn hint_click_at( // click would resolve against a row the user isn't looking at. let (chip, text) = if chrome.repo_input.active { return None; - } else if app.prefix_armed() { + } else if app.interaction.prefix_armed { (PREFIX_CHIP, prefix_armed_hint_text(app)) - } else if app.awaiting_swap_target() { + } else if app.interaction.awaiting_swap_target { return None; } else { ("", normal_hint_literal(app).to_string()) }; - let leader = app.leader_label(); + let leader = leader_label_of(app.interaction.leader); let mut cursor = hint_area.x + Span::raw(chip).width() as u16; for (i, segment) in text.split(" | ").enumerate() { if i > 0 { diff --git a/src/ui/hint_text.rs b/src/ui/hint_text.rs index b52e8f39..2010dcd7 100644 --- a/src/ui/hint_text.rs +++ b/src/ui/hint_text.rs @@ -64,13 +64,13 @@ pub(crate) fn normal_hint_literal(app: &App) -> &'static str { // would look different from Grid; otherwise the cycle skips Zoom and // `f` exits. TerminalFullscreen::Grid if app.terminal.zoom_distinct_from_grid() => { - return " : leader | shift+↑/↓: scroll | shift+pgup/dn: page scroll | shift+←/→: cycle pane | f: zoom active pane | t: new pane | w: close pane | q: detach"; + return " : leader | shift+up/dn: scroll | shift+pgup/dn: page scroll | shift+left/right: cycle pane | f: zoom active pane | t: new pane | w: close pane | q: detach"; } TerminalFullscreen::Grid => { - return " : leader | shift+↑/↓: scroll | shift+pgup/dn: page scroll | f: exit fullscreen | t: new pane | w: close pane | q: detach"; + return " : leader | shift+up/dn: scroll | shift+pgup/dn: page scroll | f: exit fullscreen | t: new pane | w: close pane | q: detach"; } TerminalFullscreen::Zoom => { - return " : leader | shift+↑/↓: scroll | shift+pgup/dn: page scroll | shift+←/→: cycle pane | f: exit fullscreen | t: new pane | w: close pane | q: detach"; + return " : leader | shift+up/dn: scroll | shift+pgup/dn: page scroll | shift+left/right: cycle pane | f: exit fullscreen | t: new pane | w: close pane | q: detach"; } TerminalFullscreen::Off => {} } @@ -112,7 +112,7 @@ pub(crate) fn normal_hint_literal(app: &App) -> &'static str { " f: exit zoom | j/k: navigate | /: search | l: log view | b: tree view | q: detach" } ViewMode::Tree => { - " f: exit zoom | j/k: navigate | /: search | →: expand | ←: collapse | enter: open file | b: status view | l: log view | q: detach" + " f: exit zoom | j/k: navigate | /: search | right: expand | left: collapse | enter: open file | b: status view | l: log view | q: detach" } }; return hint; @@ -121,9 +121,9 @@ pub(crate) fn normal_hint_literal(app: &App) -> &'static str { // The `l` toggle names its destination: from Log mode it returns to // the status view, from Status/Tree it enters the log view. return if app.mode == ViewMode::Log { - " : leader | shift+↑/↓: scroll | shift+pgup/dn: page scroll | shift+←/→: cycle | t: new pane | w: close pane | f: fullscreen | l: status view | o: open project | q: detach" + " : leader | shift+up/dn: scroll | shift+pgup/dn: page scroll | shift+left/right: cycle | t: new pane | w: close pane | f: fullscreen | l: status view | o: open project | q: detach" } else { - " : leader | shift+↑/↓: scroll | shift+pgup/dn: page scroll | shift+←/→: cycle | t: new pane | w: close pane | f: fullscreen | l: log view | o: open project | q: detach" + " : leader | shift+up/dn: scroll | shift+pgup/dn: page scroll | shift+left/right: cycle | t: new pane | w: close pane | f: fullscreen | l: log view | o: open project | q: detach" }; } match app.focus { @@ -131,16 +131,16 @@ pub(crate) fn normal_hint_literal(app: &App) -> &'static str { Focus::FileList => match app.mode { ViewMode::Log => { if app.log_view.drill_down { - " esc: back to commits | j/k: navigate files | shift+←/→: cycle | q: detach" + " esc: back to commits | j/k: navigate files | shift+left/right: cycle | q: detach" } else { - " shift+←/→: cycle | j/k: navigate commits | enter: view files | t: new pane | f: fullscreen | l: status view | b: tree view | o: open project | q: detach" + " shift+left/right: cycle | j/k: navigate commits | enter: view files | t: new pane | f: fullscreen | l: status view | b: tree view | o: open project | q: detach" } } ViewMode::Status => { - " shift+←/→: cycle | j/k: navigate | /: search | t: new pane | f: fullscreen | l: log view | b: tree view | o: open project | q: detach" + " shift+left/right: cycle | j/k: navigate | /: search | t: new pane | f: fullscreen | l: log view | b: tree view | o: open project | q: detach" } ViewMode::Tree => { - " shift+←/→: cycle | j/k: navigate | /: search | →: expand | ←: collapse | enter: open file | b: status view | l: log view | q: detach" + " shift+left/right: cycle | j/k: navigate | /: search | right: expand | left: collapse | enter: open file | b: status view | l: log view | q: detach" } }, Focus::DiffViewer => { @@ -152,12 +152,12 @@ pub(crate) fn normal_hint_literal(app: &App) -> &'static str { // Tree mode's right pane is permanently the file view — `v` // can't leave it, so don't advertise a no-op. if app.mode == ViewMode::Tree { - " j/k: scroll | pgup/pgdn: page | w: wrap | /: search | shift+←/→: cycle | q: detach" + " j/k: scroll | pgup/pgdn: page | w: wrap | /: search | shift+left/right: cycle | q: detach" } else { - " v: back to diff | j/k: scroll | pgup/pgdn: page | w: wrap | /: search | shift+←/→: cycle | q: detach" + " v: back to diff | j/k: scroll | pgup/pgdn: page | w: wrap | /: search | shift+left/right: cycle | q: detach" } } else if app.diff.view == DiffPaneView::Split { - " s: unified diff | j/k: scroll | pgup/pgdn: page | shift+←/→: cycle | f: zoom | q: detach" + " s: unified diff | j/k: scroll | pgup/pgdn: page | shift+left/right: cycle | f: zoom | q: detach" } else if app.diff.search.active { " type to search | enter: confirm | esc: cancel" } else if !app.diff.search.query.is_empty() { @@ -166,16 +166,16 @@ pub(crate) fn normal_hint_literal(app: &App) -> &'static str { // The `l` toggle names its destination (Tree mode never reaches // these arms — its right pane is always the file view). if app.mode == ViewMode::Log { - " shift+←/→: cycle | j/k: scroll | pgup/pgdn: scroll | tab: view | w: wrap | v: view file | s: split | /: search | t: new pane | f: zoom | l: status view | b: tree view | o: open project | q: detach" + " shift+left/right: cycle | j/k: scroll | pgup/pgdn: scroll | tab: view | w: wrap | v: view file | s: split | /: search | t: new pane | f: zoom | l: status view | b: tree view | o: open project | q: detach" } else { - " shift+←/→: cycle | j/k: scroll | pgup/pgdn: scroll | tab: view | w: wrap | v: view file | s: split | /: search | t: new pane | f: zoom | l: log view | b: tree view | o: open project | q: detach" + " shift+left/right: cycle | j/k: scroll | pgup/pgdn: scroll | tab: view | w: wrap | v: view file | s: split | /: search | t: new pane | f: zoom | l: log view | b: tree view | o: open project | q: detach" } } else { // No file target for `v` — a hint for a no-op key would lie. if app.mode == ViewMode::Log { - " shift+←/→: cycle | j/k: scroll | pgup/pgdn: scroll | tab: view | w: wrap | s: split | /: search | t: new pane | f: zoom | l: status view | b: tree view | o: open project | q: detach" + " shift+left/right: cycle | j/k: scroll | pgup/pgdn: scroll | tab: view | w: wrap | s: split | /: search | t: new pane | f: zoom | l: status view | b: tree view | o: open project | q: detach" } else { - " shift+←/→: cycle | j/k: scroll | pgup/pgdn: scroll | tab: view | w: wrap | s: split | /: search | t: new pane | f: zoom | l: log view | b: tree view | o: open project | q: detach" + " shift+left/right: cycle | j/k: scroll | pgup/pgdn: scroll | tab: view | w: wrap | s: split | /: search | t: new pane | f: zoom | l: log view | b: tree view | o: open project | q: detach" } } } diff --git a/src/ui/log_view/mod.rs b/src/ui/log_view/mod.rs index c1bf7473..155ded23 100644 --- a/src/ui/log_view/mod.rs +++ b/src/ui/log_view/mod.rs @@ -12,25 +12,20 @@ pub struct LogView { pub file_selected: usize, pub commit_scroll_x: usize, pub file_scroll_x: usize, - /// Memoized longest-summary char width, keyed by `commits.len()`. See - /// `StatusView::path_width_cache` for the invalidation contract. + /// Memoized longest-summary char width, keyed by `commits.len()`. pub(crate) commit_width_cache: Cell>, /// Memoized longest-path char width for `commit_files`. pub(crate) commit_files_width_cache: Cell>, - /// Count of commits currently loaded. Kept as a discrete field (in lockstep - /// with `commits.len()`) so the worker channel can compare against an - /// expected `skip` when results arrive and drop stale pages. + /// Kept in lockstep with `commits.len()` so the worker channel can compare + /// against an expected `skip` and drop stale pages. pub(crate) loaded_count: usize, - /// A background page fetch is in flight. Guards against duplicate requests. + /// Guards against duplicate page-fetch requests. pub(crate) pending_fetch: bool, - /// The previous fetch returned fewer entries than requested, so no further - /// pages exist. Cleared by `reset_pagination`. + /// The previous fetch returned fewer entries than requested. pub(crate) fully_loaded: bool, - /// Commit-list incremental search. Mirrors `StatusView::search_query` / - /// `search_active` / `filter_cache`: the cache contains indices into - /// `commits` whose summary matches the lowercased query (or `0..len` - /// when the query is empty). Recomputed only when commits or the query - /// change. + /// Commit-list incremental search. The cache holds indices into `commits` + /// whose summary matches the lowercased query. Recomputed only when + /// commits or the query change. pub commit_search_query: SearchQuery, pub commit_search_active: bool, pub(crate) commits_filter_cache: Vec, @@ -42,8 +37,7 @@ pub struct LogView { } impl LogView { - /// Replace `commits` and invalidate the summary-width cache. See - /// `StatusView::set_files` for the same-length rationale. Also resets + /// Replace `commits` and invalidate the summary-width cache. Also resets /// pagination bookkeeping because `commits` is no longer the result of /// the previous page sequence. pub(crate) fn set_commits(&mut self, commits: Vec) { @@ -55,16 +49,14 @@ impl LogView { self.recompute_commit_filter(); } - /// Install a freshly-fetched first page. Computes `fully_loaded` from the - /// page length so the callsite doesn't repeat the short-page sentinel. + /// Install a freshly-fetched first page. pub(crate) fn set_commits_from_first_page(&mut self, page: Vec, page_size: usize) { let fully_loaded = page.len() < page_size; self.set_commits(page); self.fully_loaded = fully_loaded; } - /// Append a freshly-fetched page to the tail. A short result means we've - /// reached the end. + /// Append a freshly-fetched page to the tail. pub(crate) fn append_page(&mut self, mut page: Vec, page_size: usize) { let received = page.len(); if received > 0 { @@ -79,8 +71,7 @@ impl LogView { } } - /// Mark a fetch as in flight. Returns `false` if one was already pending - /// so the caller should not spawn another worker. + /// Mark a fetch as in flight. Returns `false` if one was already pending. pub(crate) fn mark_pending(&mut self) -> bool { if self.pending_fetch { return false; @@ -89,24 +80,19 @@ impl LogView { true } - /// Clear the pending flag without appending a page. Used when a worker - /// result is discarded (stale skip, repo switch). + /// Clear the pending flag without appending a page. pub(crate) fn clear_pending(&mut self) { self.pending_fetch = false; } - /// Replace `commit_files` and invalidate the file-width cache so a - /// same-length drill-in into a different commit doesn't reuse the - /// previous commit's max path width. + /// Replace `commit_files` and invalidate the file-width cache. pub(crate) fn set_commit_files(&mut self, files: Vec) { self.commit_files = files; self.commit_files_width_cache.set(None); self.recompute_file_filter(); } - /// Exit drill-down so the upper pane shows the commit list again. Clears - /// the file list and resets file-side cursors/scroll so a later drill-in - /// starts clean. + /// Exit drill-down so the upper pane shows the commit list again. pub fn reset_drill_down(&mut self) { self.drill_down = false; self.commit_files.clear(); @@ -121,8 +107,6 @@ impl LogView { } /// Refresh `commits_filter_cache` from `commits` and the current query. - /// Callers must invoke this after mutating `commits` or - /// `commit_search_query`; otherwise the cache will diverge. pub(crate) fn recompute_commit_filter(&mut self) { self.commits_filter_cache.clear(); if self.commit_search_query.is_empty() { @@ -158,9 +142,7 @@ impl LogView { self.commit_search_active = true; } - /// Exit the commit-list search bar and clear any active query. Always - /// recomputes the filter so the caller can refresh the diff against the - /// now-unfiltered list without inspecting prior state. + /// Exit the commit-list search bar and clear any active query. pub fn cancel_commit_search(&mut self) { self.commit_search_active = false; self.commit_search_query.clear(); @@ -168,8 +150,7 @@ impl LogView { } /// Hide the commit-list search bar. Returns `true` when the query was - /// empty and the call collapsed to a cancel (so the caller knows to - /// refresh the diff for the now-unfiltered list). + /// empty and the call collapsed to a cancel. pub fn confirm_commit_search(&mut self) -> bool { if self.commit_search_query.is_empty() { self.cancel_commit_search(); diff --git a/src/ui/notice.rs b/src/ui/notice.rs index 719ffed5..0edfaeb7 100644 --- a/src/ui/notice.rs +++ b/src/ui/notice.rs @@ -100,7 +100,7 @@ pub(crate) fn render_repo_header<'a>(app: &'a App, accent: Color) -> Paragraph<' && (t.ahead > 0 || t.behind > 0) { spans.push(Span::styled( - format!(" ↑{} ↓{} ", t.ahead, t.behind), + format!(" ^{} v{} ", t.ahead, t.behind), Style::default().fg(Color::Cyan), )); } @@ -140,13 +140,17 @@ fn recovery_chip(app: &App) -> Option { } pub(crate) fn home_relative_path(path: &str) -> String { - let trimmed = path.trim_end_matches('/'); - let display = crate::platform::paths::for_display(std::path::Path::new(trimmed)); - if let Some(home) = dirs::home_dir() { - let home_display = crate::platform::paths::for_display(&home); - if let Some(rest) = display.strip_prefix(home_display.as_ref()) { - return format!("~{rest}"); + // Rebuilding from components removes a cosmetic trailing separator without + // turning filesystem roots (`/`, `C:\\`, UNC shares) into different paths. + let normalized: std::path::PathBuf = std::path::Path::new(path).components().collect(); + let path = normalized.as_path(); + if let Some(home) = dirs::home_dir() + && let Ok(rest) = path.strip_prefix(home) + { + if rest.as_os_str().is_empty() { + return "~".to_owned(); } + return format!("~/{}", crate::platform::paths::for_display(rest)); } - display.into_owned() + crate::platform::paths::for_display(path).into_owned() } diff --git a/src/ui/project_tab/mod.rs b/src/ui/project_tab/mod.rs index 18eb36c1..4456f70e 100644 --- a/src/ui/project_tab/mod.rs +++ b/src/ui/project_tab/mod.rs @@ -1,9 +1,6 @@ -//! The project tab row across the top of the screen. Mirrors -//! `terminal_tab`'s tab bar: one `tab_segments` builder feeds both the -//! renderer and the click hit-test, so a label and its click box can never -//! disagree. The two rows differ only in what they address — panes below, -//! projects above — and in their key legends (leader digits for panes, bare -//! F-keys for projects). +//! The project tab row across the top of the screen. One `tab_segments` +//! builder feeds both the renderer and the click hit-test, so a label and its +//! click box can never disagree. use ratatui::{ layout::Rect, @@ -12,19 +9,15 @@ use ratatui::{ widgets::Paragraph, }; -/// Per-tab character budget for the project name. Shorter than the pane -/// budget: up to ten tabs share one row. +/// Per-tab character budget for the project name. const TAB_TITLE_MAX_CHARS: usize = 14; -/// Width of a `+N` overflow marker. One digit is enough: at most -/// `MAX_PROJECTS` (10) tabs exist, so at most 9 can be hidden on one side. +/// Width of a `+N` overflow marker. const MARKER_WIDTH: u16 = 4; -/// The name shown for a repo path — its final component, which distinguishes -/// sibling checkouts (`~/work/api` vs `~/work/web`). Goes through `Path` +/// The name shown for a repo path — its final component. Goes through `Path` /// rather than splitting on `/` so a Windows path (`C:\work\api`) yields -/// `api` too. Falls back to the path itself for a filesystem root, and to a -/// placeholder when empty — a blank tab would look unclickable. +/// `api` too. pub(crate) fn tab_label(repo_path: &str) -> String { let path = std::path::Path::new(repo_path); let name = path @@ -37,8 +30,7 @@ pub(crate) fn tab_label(repo_path: &str) -> String { truncate(&name, TAB_TITLE_MAX_CHARS) } -/// Truncate to at most `max` characters, appending `…` when cut. Char-based -/// rather than display-width, matching `terminal_tab::truncate_tab_title`. +/// Truncate to at most `max` characters, appending `…` when cut. fn truncate(s: &str, max: usize) -> String { if s.chars().count() <= max { return s.to_string(); diff --git a/src/ui/terminal_tab/mod.rs b/src/ui/terminal_tab/mod.rs index 17c14aae..0b8d29c6 100644 --- a/src/ui/terminal_tab/mod.rs +++ b/src/ui/terminal_tab/mod.rs @@ -9,7 +9,7 @@ mod tests; pub(crate) use cells::visible_pane_content_areas; pub(crate) use tab_bar::tab_target_at; -use crate::app::{App, Focus}; +use crate::app::{App, Focus, leader_label_of}; use crate::runtime::terminal::visible_range; use crate::ui::terminal_tab::cells::visible_pane_cells; use crate::ui::terminal_tab::layout::{TERMINAL_BORDERS, terminal_layout}; @@ -63,7 +63,10 @@ pub fn render(frame: &mut Frame, app: &App, area: Rect, accent: Color) { let cells = visible_pane_cells(app, content_area); if cells.is_empty() { let screen_lines = vec![Line::from(Span::styled( - format!(" No terminal — press {} t to open one ", app.leader_label()), + format!( + " No terminal — press {} t to open one ", + leader_label_of(app.interaction.leader) + ), Style::default().fg(Color::DarkGray), ))]; frame.render_widget(Paragraph::new(screen_lines), content_area); diff --git a/src/ui/terminal_tab/tab_bar.rs b/src/ui/terminal_tab/tab_bar.rs index 3a87aec2..bcbd5e61 100644 --- a/src/ui/terminal_tab/tab_bar.rs +++ b/src/ui/terminal_tab/tab_bar.rs @@ -1,4 +1,4 @@ -use crate::app::App; +use crate::app::{App, leader_label_of}; use crate::runtime::terminal::visible_range; use crate::ui::terminal_tab::layout::{JUMP_KEY_PANE_COUNT, terminal_layout}; use crate::ui::terminal_tab::recovery::pane_label; @@ -44,7 +44,10 @@ pub(crate) fn tab_segments( ) -> Vec<(String, TabSegment)> { if app.terminal.panes.is_empty() { return vec![( - format!(" {} t: new terminal ", app.leader_label()), + format!( + " {} t: new terminal ", + leader_label_of(app.interaction.leader) + ), TabSegment::Legend, )]; } @@ -80,7 +83,12 @@ pub(crate) fn tab_segments( } else { char::from_digit((i as u32 + 3) % 10, 10).unwrap_or('?') }; - format!(" {} {} {} ", app.leader_label(), digit, title) + format!( + " {} {} {} ", + leader_label_of(app.interaction.leader), + digit, + title + ) } else { format!(" {} ", title) }; diff --git a/src/ui/terminal_tab/tests/render_tests.rs b/src/ui/terminal_tab/tests/render_tests.rs index 7f4416dd..f492bbac 100644 --- a/src/ui/terminal_tab/tests/render_tests.rs +++ b/src/ui/terminal_tab/tests/render_tests.rs @@ -132,22 +132,6 @@ fn split_view_active_pane_matches_inactive_style_when_terminal_unfocused() { ); } -#[test] -fn tab_target_at_resolves_tabs_and_hidden_markers() { - let mut app = crate::app::tests::app_with_fake_backend(); - app.terminal.max_visible_normal = 2; - for i in 0..4 { - app.terminal - .create_pane_with_now(None, Some(&format!("P{i}"))) - .unwrap(); - } - // Creation leaves pane 3 active with a 2-pane window: [2, 4). - let area = Rect::new(0, 0, 80, 20); - let (tab_area, _) = terminal_layout(area).unwrap(); - let y = tab_area.y; - let _ = y; -} - #[test] fn a_grid_smaller_than_its_pane_is_padded_rather_than_stretched() { // What a spectator sees. The session's grid belongs to whichever client is diff --git a/src/ui/tests/chrome_tests.rs b/src/ui/tests/chrome_tests.rs index ec92cb0d..9a5f69d1 100644 --- a/src/ui/tests/chrome_tests.rs +++ b/src/ui/tests/chrome_tests.rs @@ -172,6 +172,21 @@ fn home_relative_keeps_paths_outside_home_unchanged() { assert_eq!(home_relative_path("/var/code"), "/var/code"); } +#[test] +fn home_relative_does_not_strip_a_sibling_with_the_same_text_prefix() { + let home = dirs::home_dir().expect("home dir for test host"); + let sibling = home.with_file_name(format!( + "{}-sibling", + home.file_name().unwrap().to_string_lossy() + )); + let sibling = sibling.join("repo"); + + assert_eq!( + home_relative_path(sibling.to_str().unwrap()), + crate::platform::paths::for_display(&sibling) + ); +} + #[test] fn main_content_split_preserves_lower_panel_at_high_upper_ratio() { let cfg = LayoutConfig { diff --git a/src/ui/tests/hint_armed_tests.rs b/src/ui/tests/hint_armed_tests.rs index 0e09b27c..14c4bf05 100644 --- a/src/ui/tests/hint_armed_tests.rs +++ b/src/ui/tests/hint_armed_tests.rs @@ -12,7 +12,7 @@ use ratatui::layout::Rect; #[test] fn prefix_hint_advertises_close_only_with_terminal_focus() { let mut upper = app_with_fake_backend(); - upper.arm_prefix(); + upper.interaction.prefix_armed = true; assert!( !hint_text(&upper).contains("w: close"), "armed row must not offer close without terminal focus" @@ -20,7 +20,7 @@ fn prefix_hint_advertises_close_only_with_terminal_focus() { let mut term = app_with_fake_backend(); term.focus = Focus::Terminal; - term.arm_prefix(); + term.interaction.prefix_armed = true; assert!( hint_text(&term).contains("w: close"), "armed row must offer close with terminal focus" @@ -45,14 +45,14 @@ fn armed_prefix_close_click_target_follows_terminal_focus() { let mut term = app_with_fake_backend(); term.focus = Focus::Terminal; - term.arm_prefix(); + term.interaction.prefix_armed = true; assert!( clicks(&term) > 0, "terminal-focused armed row must offer a close click target" ); let mut upper = app_with_fake_backend(); - upper.arm_prefix(); + upper.interaction.prefix_armed = true; assert_eq!( clicks(&upper), 0, @@ -69,7 +69,7 @@ fn prefix_hint_advertises_swap_only_when_a_swap_can_act() { upper.terminal.create_pane_now().unwrap(); upper.terminal.create_pane_now().unwrap(); upper.focus = Focus::FileList; - upper.arm_prefix(); + upper.interaction.prefix_armed = true; assert!( !hint_text(&upper).contains("s: swap pane"), "armed row must not offer swap without terminal focus" @@ -78,7 +78,7 @@ fn prefix_hint_advertises_swap_only_when_a_swap_can_act() { let mut single = app_with_fake_backend(); single.terminal.create_pane_now().unwrap(); single.focus = Focus::Terminal; - single.arm_prefix(); + single.interaction.prefix_armed = true; assert!( !hint_text(&single).contains("s: swap pane"), "armed row must not offer swap with a single pane" @@ -88,7 +88,7 @@ fn prefix_hint_advertises_swap_only_when_a_swap_can_act() { term.terminal.create_pane_now().unwrap(); term.terminal.create_pane_now().unwrap(); term.focus = Focus::Terminal; - term.arm_prefix(); + term.interaction.prefix_armed = true; assert!( hint_text(&term).contains("s: swap pane"), "armed row must offer swap with terminal focus and two panes" @@ -101,7 +101,7 @@ fn prefix_hint_advertises_swap_only_when_a_swap_can_act() { #[test] fn prefix_hint_names_view_toggle_destinations_by_mode() { let mut app = app_with_fake_backend(); - app.arm_prefix(); + app.interaction.prefix_armed = true; let text = hint_text(&app); assert!( diff --git a/src/ui/tests/hint_click_tests.rs b/src/ui/tests/hint_click_tests.rs index 6a321cdb..c4182164 100644 --- a/src/ui/tests/hint_click_tests.rs +++ b/src/ui/tests/hint_click_tests.rs @@ -9,12 +9,15 @@ use ratatui::{Terminal, backend::TestBackend, layout::Rect, style::Color, text:: /// x column where `needle` starts on the rendered hint row, measured in /// display cells over exactly the text the renderer draws. fn hint_x_of(app: &App, needle: &str) -> u16 { - let (chip, text) = if app.prefix_armed() { + let (chip, text) = if app.interaction.prefix_armed { (PREFIX_CHIP, prefix_armed_hint_text(app)) } else { ( "", - normal_hint_literal(app).replace("", &app.leader_label()), + normal_hint_literal(app).replace( + "", + &crate::app::leader_label_of(app.interaction.leader), + ), ) }; let full = format!("{chip}{text}"); @@ -137,7 +140,7 @@ fn hint_click_misses_off_the_hint_row() { #[test] fn hint_click_armed_row_resolves_bare_followups_after_the_chip() { let mut app = app_with_fake_backend(); - app.arm_prefix(); + app.interaction.prefix_armed = true; let x = hint_x_of(&app, "t: new pane"); assert_eq!( @@ -188,7 +191,7 @@ fn hint_click_armed_row_resolves_bare_followups_after_the_chip() { #[test] fn hint_click_none_on_modal_rows() { let mut swap = app_with_fake_backend(); - swap.begin_swap_target(); + swap.interaction.begin_swap_target(); assert!((0..HINT_TEST_SCREEN.width).all(|x| { hint_click_at( &swap, diff --git a/src/ui/tests/hint_legend_tests.rs b/src/ui/tests/hint_legend_tests.rs index b62d633f..b81dd8fd 100644 --- a/src/ui/tests/hint_legend_tests.rs +++ b/src/ui/tests/hint_legend_tests.rs @@ -19,7 +19,7 @@ fn hint_bar_inverts_only_clickable_key_labels() { #[test] fn armed_prefix_hint_advertises_the_project_keys() { let mut app = app_with_fake_backend(); - app.arm_prefix(); + app.interaction.prefix_armed = true; let text = hint_text(&app); @@ -46,13 +46,13 @@ fn bare_prefix_segment_resolves_to_an_arm_click() { #[test] fn armed_prefix_hint_bar_inverts_only_clickable_key_labels() { let mut app = app_with_fake_backend(); - app.arm_prefix(); + app.interaction.prefix_armed = true; assert_inverted_cells_are_clickable(&app); } #[test] fn hint_bar_inverts_nothing_when_mouse_capture_is_disabled() { let mut app = app_with_fake_backend(); - app.mouse_enabled = false; + app.interaction.mouse_enabled = false; let mut terminal = Terminal::new(TestBackend::new(200, 1)).unwrap(); terminal .draw(|frame| { @@ -84,7 +84,7 @@ fn hint_bar_inverts_nothing_when_mouse_capture_is_disabled() { fn hint_click_resolves_nothing_when_mouse_capture_is_disabled() { let mut app = app_with_fake_backend(); app.focus = Focus::Terminal; - app.mouse_enabled = false; + app.interaction.mouse_enabled = false; let screen = Rect::new(0, 0, 200, 3); for x in 0..200u16 { assert_eq!( @@ -97,7 +97,7 @@ fn hint_click_resolves_nothing_when_mouse_capture_is_disabled() { #[test] fn swap_hint_advertises_split_view_digits_by_default() { let mut app = app_with_fake_backend(); - app.begin_swap_target(); + app.interaction.begin_swap_target(); assert!( hint_text(&app).contains("3-9,0: swap active pane"), @@ -109,7 +109,7 @@ fn swap_hint_advertises_split_view_digits_by_default() { fn swap_hint_advertises_fullscreen_digits_when_terminal_fills_body() { let mut app = app_with_fake_backend(); app.terminal.fullscreen = TerminalFullscreen::Grid; - app.begin_swap_target(); + app.interaction.begin_swap_target(); let text = hint_text(&app); assert!( @@ -124,7 +124,7 @@ fn swap_hint_advertises_fullscreen_digits_when_terminal_fills_body() { #[test] fn prefix_hint_switches_pane_digit_legend_by_layout() { let mut split = app_with_fake_backend(); - split.arm_prefix(); + split.interaction.prefix_armed = true; assert!( hint_text(&split).contains("1-9: focus/pane"), "split view prefix hint must advertise focus/pane digits" @@ -132,7 +132,7 @@ fn prefix_hint_switches_pane_digit_legend_by_layout() { let mut full = app_with_fake_backend(); full.terminal.fullscreen = TerminalFullscreen::Grid; - full.arm_prefix(); + full.interaction.prefix_armed = true; let text = hint_text(&full); assert!( text.contains("1-8: pane"), diff --git a/src/ui/tests/notice_tests.rs b/src/ui/tests/notice_tests.rs index 027c5e01..54afcbcd 100644 --- a/src/ui/tests/notice_tests.rs +++ b/src/ui/tests/notice_tests.rs @@ -3,6 +3,12 @@ use crate::app::App; use crate::app::NoticeKind; use crate::app::tests::{app_with_fake_backend, app_with_files}; +#[cfg(windows)] +#[test] +fn repo_header_preserves_a_windows_drive_root() { + assert_eq!(crate::ui::notice::home_relative_path(r"C:\"), "C:/"); +} + #[test] fn repo_input_reports_a_rejected_path_on_the_notice_row() { let mut ws = test_workspace(); @@ -50,8 +56,8 @@ fn repo_input_notice_clears_once_the_path_is_edited() { #[test] fn notice_row_shows_notices_through_every_overlay() { for setup in [ - (|app: &mut App| app.arm_prefix()) as fn(&mut App), - |app: &mut App| app.begin_swap_target(), + (|app: &mut App| app.interaction.prefix_armed = true) as fn(&mut App), + |app: &mut App| app.interaction.begin_swap_target(), ] { let mut app = app_with_fake_backend(); setup(&mut app); diff --git a/src/ui/tests/repo_picker_tests.rs b/src/ui/tests/repo_picker_tests.rs index 6dfbc2f1..968ed7f0 100644 --- a/src/ui/tests/repo_picker_tests.rs +++ b/src/ui/tests/repo_picker_tests.rs @@ -71,7 +71,7 @@ fn the_dialog_advertises_its_keys_on_the_input_row() { assert!(line.contains("/repos/current"), "the path itself: {line}"); assert!( - line.contains("↓: browse"), + line.contains("down: browse"), "the way into the browser: {line}" ); assert!(line.contains("tab: complete"), "{line}"); @@ -84,8 +84,8 @@ fn the_browsers_own_keys_replace_the_fields_on_the_input_row() { let line = repo_input_line(&repo_input, Color::Yellow, 200).to_string(); assert!(line.contains("enter: select"), "not `enter: open`: {line}"); - assert!(line.contains("←: up"), "{line}"); - assert!(!line.contains("↓: browse"), "already browsing: {line}"); + assert!(line.contains("left: up"), "{line}"); + assert!(!line.contains("down: browse"), "already browsing: {line}"); } #[test] @@ -99,5 +99,5 @@ fn a_path_too_long_for_the_legend_drops_it_whole_and_keeps_the_caret() { !line.contains("browse"), "a half legend reads as a glitch: {line}" ); - assert!(line.ends_with('█'), "the caret has to survive: {line}"); + assert!(line.ends_with('|'), "the caret has to survive: {line}"); } diff --git a/src/ui/tree_view/mod.rs b/src/ui/tree_view/mod.rs index b041e8a6..02f76795 100644 --- a/src/ui/tree_view/mod.rs +++ b/src/ui/tree_view/mod.rs @@ -1,18 +1,14 @@ -//! State for the read-only file-tree navigator (`ViewMode::Tree`). -//! `TreeView` holds a per-directory child cache plus the set of expanded -//! directories; the visible row list is *derived* from those on demand -//! (`visible_rows`), so expansion state and the flattened view can never -//! drift. All directory I/O lives in `App` (`app/tree.rs`); this module is -//! pure given a populated cache, which keeps the flattening logic -//! unit-testable without a filesystem. +//! File-tree navigator state (`ViewMode::Tree`). The visible row list is +//! derived from a cache + expansion set so the two can never drift. All +//! directory I/O lives in `App`; this module is pure given a populated cache, +//! keeping the flattening logic unit-testable without a filesystem. use crate::git::tree::TreeEntry; use crate::ui::SearchQuery; use std::cell::Cell; use std::collections::{BTreeSet, HashMap, HashSet}; -/// One flattened, currently-visible tree row. `path` is repo-relative; -/// `expanded` is only ever `true` for directories. +/// One flattened, currently-visible tree row. #[derive(Debug, Clone, PartialEq, Eq)] pub struct VisibleRow { pub path: String, @@ -22,8 +18,8 @@ pub struct VisibleRow { pub expanded: bool, } -/// One entry in the flat filename-search index. Built once when search opens -/// (see `App::build_tree_index`) and discarded when it closes. +/// One entry in the flat filename-search index. Built when search opens, +/// discarded when it closes. #[derive(Debug, Clone)] pub(crate) struct TreeIndexEntry { pub path: String, @@ -33,26 +29,23 @@ pub(crate) struct TreeIndexEntry { #[derive(Default)] pub struct TreeView { pub selected: usize, - /// Horizontal scroll offset (chars). Reset to 0 when the selection moves. + /// Horizontal scroll offset (chars). pub scroll_x: usize, /// Repo-relative expanded directory paths. The root (`""`) is implicitly /// expanded and never stored here. pub expanded: BTreeSet, - /// Lazily-populated children, keyed by repo-relative directory path (`""` - /// for the root). Absent = unread; empty vec = read and genuinely empty. + /// Lazily-populated children, keyed by repo-relative directory path. + /// Absent = unread; empty vec = read and genuinely empty. pub cache: HashMap>, - /// Memoized longest visible-row char width, keyed by row count. Invalidated - /// implicitly because structural changes also change the row count. + /// Memoized longest visible-row char width, keyed by row count. pub(crate) row_width_cache: Cell>, - /// While active *and* the query is non-empty (`search_filtering`), - /// `visible_rows` returns the filtered tree instead of the expansion view. pub search_active: bool, pub search_query: SearchQuery, - /// Flat index of every entry under the root, built when search opens. + /// Flat index of every entry under the root. pub(crate) index: Vec, /// Repo-relative paths to display while filtering: matches plus ancestors. show_set: HashSet, - /// Count of entries matching the current query (`(m/n)` badge numerator). + /// Count of entries matching the current query. pub(crate) match_count: usize, } diff --git a/src/web/common/auth.rs b/src/web/common/auth.rs index 84549ae6..81e9f4fe 100644 --- a/src/web/common/auth.rs +++ b/src/web/common/auth.rs @@ -5,7 +5,7 @@ use anyhow::{Result, anyhow}; use argon2::password_hash::SaltString; use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier}; -use std::collections::HashSet; +use std::collections::{HashSet, VecDeque}; use std::sync::Mutex; use std::time::{Duration, Instant}; @@ -114,7 +114,7 @@ fn hex(bytes: &[u8]) -> String { /// 14 per hour (2/min baseline plus 12 additional/hour). Shared across all /// clients since there is a single password. pub struct RateLimiter { - attempts: Mutex>, + attempts: Mutex>, } const MAX_PER_MINUTE: usize = 2; @@ -123,7 +123,7 @@ const MAX_PER_HOUR: usize = 14; impl RateLimiter { pub fn new() -> Self { Self { - attempts: Mutex::new(Vec::new()), + attempts: Mutex::new(VecDeque::with_capacity(MAX_PER_HOUR)), } } @@ -138,7 +138,10 @@ impl RateLimiter { .filter(|t| now.duration_since(**t) < Duration::from_secs(60)) .count(); let allowed = last_minute < MAX_PER_MINUTE && attempts.len() < MAX_PER_HOUR; - attempts.push(now); + attempts.push_back(now); + if attempts.len() > MAX_PER_HOUR { + attempts.pop_front(); + } allowed } } @@ -242,4 +245,19 @@ mod tests { limiter.check_and_record(t0 + Duration::from_secs((MAX_PER_HOUR as u64) * 40)); assert!(!blocked, "the 15th attempt in an hour is blocked"); } + + #[test] + fn rejected_login_flood_keeps_bounded_history() { + let limiter = RateLimiter::new(); + let t0 = Instant::now(); + + let allowed = (0..4_000) + .map(|i| limiter.check_and_record(t0 + Duration::from_secs(i))) + .last() + .unwrap(); + + let attempts = limiter.attempts.lock().unwrap(); + assert!(!allowed, "a continuing flood must remain rate-limited"); + assert_eq!(attempts.len(), MAX_PER_HOUR); + } } diff --git a/src/web/common/conn.rs b/src/web/common/conn.rs index 46e61cb8..ada7983c 100644 --- a/src/web/common/conn.rs +++ b/src/web/common/conn.rs @@ -26,8 +26,8 @@ pub const MAX_BODY_BYTES: usize = 64 * 1024; pub const HEAD_READ_TIMEOUT: Duration = Duration::from_secs(15); /// Wall-clock budget for the *whole* request. The socket timeout above only /// bounds one `read`, and it re-arms on every byte — a client dribbling one -/// byte per timeout would otherwise hold a connection slot for days, and 64 of -/// those starve the accept loop. This is the deadline that actually ends it. +/// byte per timeout would otherwise hold a connection slot for days. This is +/// the deadline that actually ends it. pub const REQUEST_DEADLINE: Duration = Duration::from_secs(30); /// Read the request head (up to CRLFCRLF) plus any declared body. Both @@ -39,11 +39,8 @@ pub fn read_request(stream: &mut TcpStream) -> Result<(RequestHead, String)> { let mut buf = Vec::new(); let mut chunk = [0u8; 4096]; let head_end = loop { - if let Some(pos) = find_subsequence(&buf, b"\r\n\r\n") { - break pos + 4; - } - if buf.len() > MAX_HEAD_BYTES { - anyhow::bail!("request head exceeds {MAX_HEAD_BYTES} bytes"); + if let Some(head_end) = request_head_end(&buf)? { + break head_end; } if Instant::now() >= deadline { anyhow::bail!("request head did not arrive within {REQUEST_DEADLINE:?}"); @@ -77,13 +74,24 @@ pub fn read_request(stream: &mut TcpStream) -> Result<(RequestHead, String)> { Ok((head, String::from_utf8_lossy(&body).into_owned())) } +fn request_head_end(buf: &[u8]) -> Result> { + if let Some(pos) = find_subsequence(buf, b"\r\n\r\n") { + let head_end = pos + 4; + if head_end > MAX_HEAD_BYTES { + anyhow::bail!("request head exceeds {MAX_HEAD_BYTES} bytes"); + } + return Ok(Some(head_end)); + } + if buf.len() >= MAX_HEAD_BYTES { + anyhow::bail!("request head exceeds {MAX_HEAD_BYTES} bytes"); + } + Ok(None) +} + /// Whether a request's `Origin` is acceptable. /// -/// An absent Origin (a native client, which cannot carry a browser's cookie) -/// is allowed; a present one must match the request `Host` authority, else it -/// is a cross-site request and is refused. `SameSite=Strict` already keeps the -/// session cookie off cross-site requests, so a hijack fails auth anyway — -/// this refuses it outright. +/// An absent Origin (a native client) is allowed; a present one must match the +/// request `Host` authority, else it is a cross-site request and is refused. pub fn origin_allowed(head: &RequestHead) -> bool { match head.header("origin") { None => true, @@ -102,14 +110,12 @@ pub fn origin_allowed(head: &RequestHead) -> bool { /// [`origin_allowed`] only proves Origin and Host *agree*, which a DNS-rebound /// attacker satisfies trivially: they control both. Rebinding `evil.example` to /// 127.0.0.1 would otherwise give their page a same-origin position from which -/// to POST `/login` and read the reply — and a hit yields repository contents -/// plus interactive shells. +/// to POST `/login` and read the reply. /// -/// A loopback-bound server can only legitimately be addressed as loopback (or, -/// through a tunnel, as whatever the operator configured), so any other Host is -/// refused. When bound off-loopback the operator has taken responsibility for -/// the network path, and the check would reject legitimate proxied hosts, so it -/// does not apply. +/// A loopback-bound server can only legitimately be addressed as loopback, so +/// any other Host is refused. When bound off-loopback the operator has taken +/// responsibility for the network path, and the check would reject legitimate +/// proxied hosts, so it does not apply. pub fn host_allowed(head: &RequestHead, bound_loopback: bool) -> bool { if !bound_loopback { return true; @@ -215,85 +221,4 @@ pub fn websocket_handshake( } #[cfg(test)] -mod tests { - use super::*; - - fn head_with(headers: &[(&str, &str)]) -> RequestHead { - let mut text = String::from("GET / HTTP/1.1\r\n"); - for (name, value) in headers { - text.push_str(&format!("{name}: {value}\r\n")); - } - text.push_str("\r\n"); - http::parse_request_head(&text).unwrap() - } - - #[test] - fn host_allowed_accepts_loopback_spellings() { - for host in [ - "localhost:8091", - "LOCALHOST", - "127.0.0.1:8091", - "127.0.0.1", - "[::1]:8091", - "127.0.0.2", - ] { - assert!( - host_allowed(&head_with(&[("Host", host)]), true), - "{host} must be accepted" - ); - } - } - - #[test] - fn host_allowed_refuses_a_rebound_name_on_a_loopback_bind() { - // The attacker controls both headers, so origin_allowed passes; only - // the Host check stops the same-origin foothold. - let head = head_with(&[("Host", "evil.example"), ("Origin", "http://evil.example")]); - - assert!(origin_allowed(&head), "precondition: origin matches host"); - assert!(!host_allowed(&head, true), "a rebound name must be refused"); - } - - #[test] - fn host_allowed_defers_when_bound_off_loopback() { - // The operator chose the network path; rejecting their proxy's Host - // would break a legitimate deployment. - let head = head_with(&[("Host", "nightcrow.internal")]); - - assert!(!host_allowed(&head, true)); - assert!(host_allowed(&head, false)); - } - - #[test] - fn connection_slot_refuses_over_the_cap() { - let counter = Arc::new(AtomicUsize::new(0)); - - let held: Vec<_> = (0..2) - .map(|_| ConnectionSlot::acquire(&counter, 2).expect("under the cap")) - .collect(); - - assert!( - ConnectionSlot::acquire(&counter, 2).is_none(), - "a third connection must be refused" - ); - assert_eq!( - counter.load(Ordering::Acquire), - 2, - "a refused connection must not leak a slot" - ); - drop(held); - } - - #[test] - fn connection_slot_releases_on_drop() { - let counter = Arc::new(AtomicUsize::new(0)); - - drop(ConnectionSlot::acquire(&counter, 1).expect("under the cap")); - - assert_eq!(counter.load(Ordering::Acquire), 0); - assert!( - ConnectionSlot::acquire(&counter, 1).is_some(), - "the freed slot must be reusable" - ); - } -} +mod tests; diff --git a/src/web/common/conn/tests.rs b/src/web/common/conn/tests.rs new file mode 100644 index 00000000..614bc326 --- /dev/null +++ b/src/web/common/conn/tests.rs @@ -0,0 +1,76 @@ +use super::*; + +fn head_with(headers: &[(&str, &str)]) -> RequestHead { + let mut text = String::from("GET / HTTP/1.1\r\n"); + for (name, value) in headers { + text.push_str(&format!("{name}: {value}\r\n")); + } + text.push_str("\r\n"); + http::parse_request_head(&text).unwrap() +} + +#[test] +fn completed_head_over_the_limit_is_rejected() { + let mut bytes = vec![b'a'; MAX_HEAD_BYTES]; + bytes.extend_from_slice(b"\r\n\r\n"); + + let error = request_head_end(&bytes).unwrap_err(); + + assert!(error.to_string().contains("request head exceeds")); +} + +#[test] +fn host_allowed_accepts_loopback_spellings() { + for host in [ + "localhost:8091", + "LOCALHOST", + "127.0.0.1:8091", + "127.0.0.1", + "[::1]:8091", + "127.0.0.2", + ] { + assert!( + host_allowed(&head_with(&[("Host", host)]), true), + "{host} must be accepted" + ); + } +} + +#[test] +fn host_allowed_refuses_a_rebound_name_on_a_loopback_bind() { + let head = head_with(&[("Host", "evil.example"), ("Origin", "http://evil.example")]); + + assert!(origin_allowed(&head), "precondition: origin matches host"); + assert!(!host_allowed(&head, true), "a rebound name must be refused"); +} + +#[test] +fn host_allowed_defers_when_bound_off_loopback() { + let head = head_with(&[("Host", "nightcrow.internal")]); + + assert!(!host_allowed(&head, true)); + assert!(host_allowed(&head, false)); +} + +#[test] +fn connection_slot_refuses_over_the_cap() { + let counter = Arc::new(AtomicUsize::new(0)); + + let held: Vec<_> = (0..2) + .map(|_| ConnectionSlot::acquire(&counter, 2).expect("under the cap")) + .collect(); + + assert!(ConnectionSlot::acquire(&counter, 2).is_none()); + assert_eq!(counter.load(Ordering::Acquire), 2); + drop(held); +} + +#[test] +fn connection_slot_releases_on_drop() { + let counter = Arc::new(AtomicUsize::new(0)); + + drop(ConnectionSlot::acquire(&counter, 1).expect("under the cap")); + + assert_eq!(counter.load(Ordering::Acquire), 0); + assert!(ConnectionSlot::acquire(&counter, 1).is_some()); +} diff --git a/src/web/common/http.rs b/src/web/common/http.rs index 74811fa4..41e4b826 100644 --- a/src/web/common/http.rs +++ b/src/web/common/http.rs @@ -18,9 +18,6 @@ pub struct RequestHead { /// the target carried none. Read it through [`RequestHead::query_param`] /// rather than parsing it again at each call site. /// - /// The mirror's routes take no parameters; this exists for the viewer's - /// `?repo=&path=` routes. - #[allow(dead_code)] pub query: String, pub headers: Vec<(String, String)>, /// Declared body length from `Content-Length`, 0 when absent/invalid. @@ -43,7 +40,6 @@ impl RequestHead { /// The result is decoded exactly once. Callers that turn a parameter into a /// filesystem path must validate *this* value — decoding again afterwards /// would let `%252e%252e` become `..` after the check. - #[allow(dead_code)] // First caller is the viewer's git routes; see `query`. pub fn query_param(&self, name: &str) -> Option { parse_form(&self.query) .into_iter() diff --git a/src/web/common/sse.rs b/src/web/common/sse.rs index 8f4069c5..ba3d3d80 100644 --- a/src/web/common/sse.rs +++ b/src/web/common/sse.rs @@ -9,11 +9,6 @@ //! Generic over [`Write`] so the framing is unit-testable against a buffer //! and the same code drives a real `TcpStream`. //! -//! The framing landed ahead of its caller (the viewer's `/api/events`) so each -//! step stayed small, hence the blanket dead-code allowance; drop it once a -//! route constructs an `SseStream`. -#![allow(dead_code)] - use std::io::{self, Write}; /// Written when a client subscribes, before any event. diff --git a/src/web/viewer/assets.rs b/src/web/viewer/assets.rs index bd9eba98..95131813 100644 --- a/src/web/viewer/assets.rs +++ b/src/web/viewer/assets.rs @@ -93,6 +93,7 @@ pub fn serve(path: &str) -> Option> { /// Whether the frontend was built into this binary. A source checkout with no /// `dist` still compiles; the server then says so rather than 404ing blankly. +#[cfg(test)] pub fn is_present() -> bool { Assets::get("index.html").is_some() } diff --git a/src/web/viewer/dto/envelope.rs b/src/web/viewer/dto/envelope.rs index 278b7a46..9716e0bc 100644 --- a/src/web/viewer/dto/envelope.rs +++ b/src/web/viewer/dto/envelope.rs @@ -1,5 +1,5 @@ use super::status::server_now_millis; -use crate::web::viewer::prefs::ViewerPrefs; +use crate::session::prefs::ViewerPrefs; use serde::Serialize; /// Bumped whenever an existing field changes meaning or disappears. Adding a @@ -31,11 +31,20 @@ pub struct RepoDto { /// Final path component, for a tab label. pub name: String, /// Home-relative path for display (`~/code/app`). The absolute path is not - /// sent: the client never needs it, and it is the one field here that says - /// something about the machine rather than the repository. + /// sent: the client never needs it. pub display_path: String, } +impl From for RepoDto { + fn from(repo: crate::session::catalog::RepoInfo) -> Self { + Self { + id: repo.id, + name: repo.name, + display_path: repo.display_path, + } + } +} + /// The part of `[agent_indicator]` the browser can act on. `auto_follow` is /// omitted: it moves a TUI selection, and the viewer has no analogue. #[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)] @@ -51,12 +60,10 @@ pub struct HotConfigDto { /// repositories — `POST` opens one, `DELETE` closes one — but the `GET` grew /// into the session's bootstrap, because a client that already polls it every /// few seconds is the cheapest carrier for anything server-wide it must agree -/// with. The path stays as it is so opening and closing keep their home; this -/// type is where the payload's real job is written down. +/// with. /// /// Every field here belongs in `ViewerBootstrap` in `viewer-ui/src/api.ts` too. -/// Renaming or retyping one without doing so fails the fixture contract test; -/// a purely additive field does not, so add it to both while it is in hand. +/// Renaming or retyping one without doing so fails the fixture contract test. #[derive(Debug, Clone, Serialize)] pub struct ViewerBootstrapDto { pub repos: Vec, @@ -64,36 +71,31 @@ pub struct ViewerBootstrapDto { /// Index into the accent presets. The session's colour, stored server-side /// so every device — and every attached TUI — agrees. pub accent: usize, - /// File-sidebar width in CSS px, stored server-side like the accent so - /// every device opens at the same split. + /// File-sidebar width in CSS px, stored server-side like the accent. pub sidebar_width: u32, /// Percent of the vertical split given to the diff panel; the terminal - /// panel takes the rest. Shared between browsers like the sidebar width, - /// and like it not shared with the TUI — see `prefs::ViewerPrefs`. + /// panel takes the rest. Shared between browsers, not shared with the TUI — + /// see `prefs::ViewerPrefs`. pub upper_pct: u32, /// Id of the project a client last selected, so a reload lands there /// instead of on the first tab. `None` when nothing has been selected yet - /// or the remembered project is not currently served — the client then - /// falls back to the first tab. An id, not the path `prefs.rs` stores: - /// clients address repositories by id and never learn the path. + /// or the remembered project is not currently served. An id, not the path + /// `prefs.rs` stores: clients address repositories by id and never learn + /// the path. pub active_repo: Option, /// Which panel each *currently served* project was left maximized in, by /// id. Projects with no arrangement are absent, as are remembered ones this - /// session is not serving — ids, like `active_repo`, are resolved from the - /// same catalog snapshot as the list beside them - /// (`Catalog::list_with_active`), so every id here is one the list carries. + /// session is not serving. pub maximized: std::collections::HashMap, /// This server's wall clock, for dating [`super::ChangedFileDto::mtime`]. pub now_ms: u64, - /// Whether this server can clone: false when no `git` is on its PATH, so - /// the client disables the form instead of starting a job that must fail. + /// Whether this server can clone: false when no `git` is on its PATH. pub can_clone: bool, } impl ViewerBootstrapDto { /// Stamps `now_ms` at construction — the value is only useful as "the - /// server's time when this response was built", so no caller is given the - /// chance to supply a staler one. + /// server's time when this response was built". /// /// Takes the whole [`ViewerPrefs`] rather than the fields it needs: several /// of them are `u32`, and a positional list of those is a pair of arguments diff --git a/src/web/viewer/dto/tests/diff.rs b/src/web/viewer/dto/tests/diff.rs new file mode 100644 index 00000000..10332de6 --- /dev/null +++ b/src/web/viewer/dto/tests/diff.rs @@ -0,0 +1,129 @@ +use super::json; +use crate::git::diff::{DiffHunk, DiffLine, LineKind}; +use crate::web::viewer::dto::{DiffDto, FileDto}; +use crate::web::viewer::limits; + +fn hunk(header: &str, lines: usize, width: usize) -> DiffHunk { + DiffHunk { + header: header.to_string(), + file_path: None, + lines: (0..lines) + .map(|_| DiffLine { + kind: LineKind::Context, + content: "x".repeat(width), + old_lineno: None, + new_lineno: None, + }) + .collect(), + } +} + +fn sample_lines() -> Vec { + vec![ + DiffLine { + kind: LineKind::Added, + content: "new".into(), + old_lineno: None, + new_lineno: Some(1), + }, + DiffLine { + kind: LineKind::Removed, + content: "old".into(), + old_lineno: Some(1), + new_lineno: None, + }, + DiffLine { + kind: LineKind::Context, + content: "same".into(), + old_lineno: Some(2), + new_lineno: Some(2), + }, + ] +} + +#[test] +fn diff_dto_maps_line_kinds_to_wire_codes() { + let hunks = vec![DiffHunk { + header: "@@ -1 +1 @@".to_string(), + file_path: Some("a.rs".to_string()), + lines: sample_lines(), + }]; + + let dto = DiffDto::from_hunks("a.rs", &hunks); + + let kinds: Vec<_> = dto.hunks[0] + .lines + .iter() + .map(|line| line.kind.as_str()) + .collect(); + assert_eq!(kinds, vec!["+", "-", " "]); + assert!(!dto.truncated); +} + +#[test] +fn diff_dto_carries_the_line_numbers_of_each_side() { + let hunks = vec![DiffHunk { + header: "@@ -1,2 +1,2 @@".to_string(), + file_path: None, + lines: sample_lines(), + }]; + + let dto = DiffDto::from_hunks("a.rs", &hunks); + + let lines: Vec<_> = dto.hunks[0] + .lines + .iter() + .map(|line| (line.old_lineno, line.new_lineno)) + .collect(); + assert_eq!( + lines, + vec![(None, Some(1)), (Some(1), None), (Some(2), Some(2))] + ); +} + +#[test] +fn diff_dto_caps_across_hunks_not_within_one() { + let per_hunk = limits::MAX_DIFF_LINES / 2 + 10; + let hunks = vec![hunk("@@ a @@", per_hunk, 1), hunk("@@ b @@", per_hunk, 1)]; + + let dto = DiffDto::from_hunks("big.rs", &hunks); + + let total: usize = dto.hunks.iter().map(|hunk| hunk.lines.len()).sum(); + assert_eq!(total, limits::MAX_DIFF_LINES); + assert!(dto.truncated); +} + +#[test] +fn diff_dto_stops_on_the_byte_ceiling_before_the_line_ceiling() { + let hunks = vec![hunk("@@ a @@", 50, limits::MAX_DIFF_BYTES / 10)]; + + let dto = DiffDto::from_hunks("wide.rs", &hunks); + + let bytes: usize = dto.hunks[0] + .lines + .iter() + .flat_map(|line| &line.spans) + .map(|span| span.t.len()) + .sum(); + assert!(bytes <= limits::MAX_DIFF_BYTES); + assert!(dto.truncated); + assert!(dto.hunks[0].lines.len() < 50); +} + +#[test] +fn file_dto_caps_content_on_a_character_boundary() { + let content = "🦀".repeat(limits::MAX_DIFF_BYTES); + + let dto = FileDto::new("big.txt", &content); + + let served: String = dto + .lines + .iter() + .flatten() + .map(|span| span.t.as_str()) + .collect(); + assert!(dto.truncated); + assert!(served.len() <= limits::MAX_DIFF_BYTES); + assert!(content.starts_with(&served)); + assert!(json(&dto).is_object()); +} diff --git a/src/web/viewer/dto/tests/fixture.rs b/src/web/viewer/dto/tests/fixture.rs index 840d2364..91189edb 100644 --- a/src/web/viewer/dto/tests/fixture.rs +++ b/src/web/viewer/dto/tests/fixture.rs @@ -3,7 +3,7 @@ use super::super::{ DiffLineDto, FileDto, HotConfigDto, LogDto, PROTOCOL_VERSION, RepoDto, SpanDto, StatusDto, TrackingDto, TreeDto, TreeEntryDto, TreeMatchDto, TreeSearchDto, ViewerBootstrapDto, }; -use crate::web::viewer::prefs::MaximizedPanel; +use crate::session::prefs::MaximizedPanel; /// Where the wire fixture lives. At the `viewer-ui` root rather than under /// `viewer-ui/src` (which the published crate excludes) so it ships in the @@ -198,7 +198,7 @@ fn wire_fixture() -> serde_json::Value { // Built by `reload::ReloadReport::summary`, so the TUI's notice and this // toast cannot drift apart. "reloaded": serde_json::json!({ - "summary": crate::web::viewer::reload::ReloadReport { + "summary": crate::session::reload::ReloadReport { plugins: 1, startup_commands: 2, repos: 1, diff --git a/src/web/viewer/dto/tests/identity.rs b/src/web/viewer/dto/tests/identity.rs new file mode 100644 index 00000000..16861fbc --- /dev/null +++ b/src/web/viewer/dto/tests/identity.rs @@ -0,0 +1,63 @@ +use super::json; +use crate::git::diff::{ChangedFile, CommitEntry, StatusKind}; +use crate::web::viewer::dto::{ChangedFileDto, CommitDto, Envelope, PROTOCOL_VERSION, TreeDto}; + +#[test] +fn changed_file_dto_drops_the_tui_search_cache() { + let file = ChangedFile::from_status_columns( + "src/main.rs".to_string(), + None, + StatusKind::Modified, + StatusKind::Unmodified, + ); + assert!(!file.search_lower.is_empty(), "precondition"); + + let value = json(&ChangedFileDto::from(&file)); + + assert_eq!(value["path"], "src/main.rs"); + assert_eq!(value["index"], "M"); + assert_eq!(value["worktree"], " "); + assert!(value.get("search_lower").is_none()); + assert!(value.get("old_path").is_none()); +} + +#[test] +fn changed_file_dto_keeps_a_rename_source() { + let file = ChangedFile::from_status_columns( + "new.rs".to_string(), + Some("old.rs".to_string()), + StatusKind::Renamed, + StatusKind::Unmodified, + ); + + let value = json(&ChangedFileDto::from(&file)); + + assert_eq!(value["old_path"], "old.rs"); + assert_eq!(value["index"], "R"); +} + +#[test] +fn commit_dto_drops_the_summary_cache_and_hexes_the_oid() { + let entry = CommitEntry::new( + git2::Oid::from_str("1234567890abcdef1234567890abcdef12345678").unwrap(), + "1234567".to_string(), + "Fix The Bug".to_string(), + "Someone".to_string(), + 1_700_000_000, + ); + assert!(!entry.summary_lower.is_empty(), "precondition"); + + let value = json(&CommitDto::from(&entry)); + + assert_eq!(value["oid"], "1234567890abcdef1234567890abcdef12345678"); + assert_eq!(value["summary"], "Fix The Bug"); + assert!(value.get("summary_lower").is_none()); +} + +#[test] +fn envelope_carries_the_protocol_version_alongside_the_payload() { + let value = json(&Envelope::new(TreeDto::from_entries("src", &[]))); + + assert_eq!(value["version"], PROTOCOL_VERSION); + assert_eq!(value["path"], "src", "the payload is flattened"); +} diff --git a/src/web/viewer/dto/tests/mod.rs b/src/web/viewer/dto/tests/mod.rs index 22d7c55d..41908715 100644 --- a/src/web/viewer/dto/tests/mod.rs +++ b/src/web/viewer/dto/tests/mod.rs @@ -1,306 +1,10 @@ +mod diff; mod fixture; +mod identity; +mod status; -use super::*; -use crate::git::diff::{ChangedFile, CommitEntry, DiffHunk, LineKind, StatusKind}; -use crate::web::viewer::limits; use serde::Serialize; -use std::collections::HashMap; -use std::time::SystemTime; fn json(value: &T) -> serde_json::Value { serde_json::to_value(value).unwrap() } - -#[test] -fn changed_file_dto_drops_the_tui_search_cache() { - let file = ChangedFile::from_status_columns( - "src/main.rs".to_string(), - None, - StatusKind::Modified, - StatusKind::Unmodified, - ); - assert!( - !file.search_lower.is_empty(), - "precondition: the internal type carries a search cache" - ); - - let value = json(&ChangedFileDto::from(&file)); - - assert_eq!(value["path"], "src/main.rs"); - assert_eq!(value["index"], "M"); - assert_eq!(value["worktree"], " "); - assert!( - value.get("search_lower").is_none(), - "the filter cache must not reach the wire: {value}" - ); - assert!( - value.get("old_path").is_none(), - "an absent rename source is omitted, not null" - ); -} - -#[test] -fn changed_file_dto_keeps_a_rename_source() { - let file = ChangedFile::from_status_columns( - "new.rs".to_string(), - Some("old.rs".to_string()), - StatusKind::Renamed, - StatusKind::Unmodified, - ); - - let value = json(&ChangedFileDto::from(&file)); - - assert_eq!(value["old_path"], "old.rs"); - assert_eq!(value["index"], "R"); -} - -#[test] -fn commit_dto_drops_the_summary_cache_and_hexes_the_oid() { - let entry = CommitEntry::new( - git2::Oid::from_str("1234567890abcdef1234567890abcdef12345678").unwrap(), - "1234567".to_string(), - "Fix The Bug".to_string(), - "Someone".to_string(), - 1_700_000_000, - ); - assert!(!entry.summary_lower.is_empty(), "precondition"); - - let value = json(&CommitDto::from(&entry)); - - assert_eq!(value["oid"], "1234567890abcdef1234567890abcdef12345678"); - assert_eq!(value["summary"], "Fix The Bug"); - assert!( - value.get("summary_lower").is_none(), - "the filter cache must not reach the wire: {value}" - ); -} - -#[test] -fn envelope_carries_the_protocol_version_alongside_the_payload() { - let value = json(&Envelope::new(TreeDto::from_entries("src", &[]))); - - assert_eq!(value["version"], PROTOCOL_VERSION); - assert_eq!(value["path"], "src", "the payload is flattened, not nested"); -} - -#[test] -fn status_dto_reports_truncation_past_the_ceiling() { - let files: Vec<_> = (0..limits::MAX_STATUS_FILES + 5) - .map(|i| { - ChangedFile::from_status_columns( - format!("f{i}.rs"), - None, - StatusKind::Modified, - StatusKind::Unmodified, - ) - }) - .collect(); - - let dto = StatusDto::from_snapshot(&files, None, None, Some("main"), &HashMap::new()); - - assert_eq!(dto.files.len(), limits::MAX_STATUS_FILES); - assert!(dto.truncated); - assert_eq!(dto.branch.as_deref(), Some("main")); -} - -#[test] -fn status_dto_carries_mtime_in_millis_only_for_stated_files() { - let files = vec![ - ChangedFile::from_status_columns( - "hot.rs".to_string(), - None, - StatusKind::Modified, - StatusKind::Unmodified, - ), - ChangedFile::from_status_columns( - "gone.rs".to_string(), - None, - StatusKind::Deleted, - StatusKind::Unmodified, - ), - ]; - // Deleted files never make it into the worker's mtime map, so their - // rows must simply omit the field rather than carry a stand-in age. - let mtimes = HashMap::from([( - "hot.rs".to_string(), - SystemTime::UNIX_EPOCH + std::time::Duration::from_millis(1_500), - )]); - - let value = json(&StatusDto::from_snapshot(&files, None, None, None, &mtimes)); - - assert_eq!(value["files"][0]["mtime"], 1_500u64); - assert!(value["files"][1].get("mtime").is_none()); -} - -#[test] -fn commit_file_list_never_carries_an_mtime() { - // A commit's files describe history; the working tree's mtime would be - // unrelated to them, and a client must not be able to read one as - // "this commit touched the file just now". - let files = vec![ChangedFile::from_status_columns( - "a.rs".to_string(), - None, - StatusKind::Modified, - StatusKind::Unmodified, - )]; - - let value = json(&CommitFilesDto::from_entries(&files)); - - assert!(value["files"][0].get("mtime").is_none()); -} - -#[test] -fn status_dto_omits_absent_optional_fields() { - let value = json(&StatusDto::from_snapshot( - &[], - None, - None, - None, - &HashMap::new(), - )); - - assert!(value.get("branch").is_none()); - assert!(value.get("head").is_none()); - assert!(value.get("tracking").is_none()); - assert_eq!(value["truncated"], false); -} - -fn hunk(header: &str, lines: usize, width: usize) -> DiffHunk { - DiffHunk { - header: header.to_string(), - file_path: None, - lines: (0..lines) - .map(|_| crate::git::diff::DiffLine { - kind: LineKind::Context, - content: "x".repeat(width), - old_lineno: None, - new_lineno: None, - }) - .collect(), - } -} - -#[test] -fn diff_dto_maps_line_kinds_to_wire_codes() { - let hunks = vec![DiffHunk { - header: "@@ -1 +1 @@".to_string(), - file_path: Some("a.rs".to_string()), - lines: vec![ - crate::git::diff::DiffLine { - kind: LineKind::Added, - content: "new".into(), - old_lineno: None, - new_lineno: Some(1), - }, - crate::git::diff::DiffLine { - kind: LineKind::Removed, - content: "old".into(), - old_lineno: Some(1), - new_lineno: None, - }, - crate::git::diff::DiffLine { - kind: LineKind::Context, - content: "same".into(), - old_lineno: Some(2), - new_lineno: Some(2), - }, - ], - }]; - - let dto = DiffDto::from_hunks("a.rs", &hunks); - - let kinds: Vec<_> = dto.hunks[0].lines.iter().map(|l| l.kind.as_str()).collect(); - assert_eq!(kinds, vec!["+", "-", " "]); - assert!(!dto.truncated); -} - -#[test] -fn diff_dto_carries_the_line_numbers_of_the_side_each_line_exists_on() { - let hunks = vec![DiffHunk { - header: "@@ -1,2 +1,2 @@".to_string(), - file_path: None, - lines: vec![ - crate::git::diff::DiffLine { - kind: LineKind::Added, - content: "new".into(), - old_lineno: None, - new_lineno: Some(1), - }, - crate::git::diff::DiffLine { - kind: LineKind::Removed, - content: "old".into(), - old_lineno: Some(1), - new_lineno: None, - }, - crate::git::diff::DiffLine { - kind: LineKind::Context, - content: "same".into(), - old_lineno: Some(2), - new_lineno: Some(2), - }, - ], - }]; - - let dto = DiffDto::from_hunks("a.rs", &hunks); - - let linenos: Vec<_> = dto.hunks[0] - .lines - .iter() - .map(|l| (l.old_lineno, l.new_lineno)) - .collect(); - assert_eq!( - linenos, - vec![(None, Some(1)), (Some(1), None), (Some(2), Some(2))] - ); -} - -#[test] -fn diff_dto_caps_across_hunks_not_within_one() { - // Each hunk is under the ceiling alone; together they exceed it. A - // per-hunk cap would let the total through unbounded. - let per_hunk = limits::MAX_DIFF_LINES / 2 + 10; - let hunks = vec![hunk("@@ a @@", per_hunk, 1), hunk("@@ b @@", per_hunk, 1)]; - - let dto = DiffDto::from_hunks("big.rs", &hunks); - - let total: usize = dto.hunks.iter().map(|h| h.lines.len()).sum(); - assert_eq!(total, limits::MAX_DIFF_LINES); - assert!(dto.truncated); -} - -#[test] -fn diff_dto_stops_on_the_byte_ceiling_before_the_line_ceiling() { - // Few lines, each enormous: the byte ceiling has to bind first. - let hunks = vec![hunk("@@ a @@", 50, limits::MAX_DIFF_BYTES / 10)]; - - let dto = DiffDto::from_hunks("wide.rs", &hunks); - - let bytes: usize = dto.hunks[0] - .lines - .iter() - .flat_map(|l| &l.spans) - .map(|s| s.t.len()) - .sum(); - assert!(bytes <= limits::MAX_DIFF_BYTES); - assert!(dto.truncated); - assert!( - dto.hunks[0].lines.len() < 50, - "the byte ceiling must cut before the line count does" - ); -} - -#[test] -fn file_dto_caps_content_on_a_character_boundary() { - let content = "한".repeat(limits::MAX_DIFF_BYTES); - - let dto = FileDto::new("big.txt", &content); - - // Reconstruct the served text from its spans (one line here — no \n). - let served: String = dto.lines.iter().flatten().map(|s| s.t.as_str()).collect(); - assert!(dto.truncated); - assert!(served.len() <= limits::MAX_DIFF_BYTES); - assert!( - content.starts_with(&served), - "the cap must yield a clean prefix" - ); -} diff --git a/src/web/viewer/dto/tests/status.rs b/src/web/viewer/dto/tests/status.rs new file mode 100644 index 00000000..dff6ba33 --- /dev/null +++ b/src/web/viewer/dto/tests/status.rs @@ -0,0 +1,83 @@ +use super::json; +use crate::git::diff::{ChangedFile, StatusKind}; +use crate::web::viewer::dto::{CommitFilesDto, StatusDto}; +use crate::web::viewer::limits; +use std::collections::HashMap; +use std::time::{Duration, SystemTime}; + +#[test] +fn status_dto_reports_truncation_past_the_ceiling() { + let files: Vec<_> = (0..limits::MAX_STATUS_FILES + 5) + .map(|i| { + ChangedFile::from_status_columns( + format!("f{i}.rs"), + None, + StatusKind::Modified, + StatusKind::Unmodified, + ) + }) + .collect(); + + let dto = StatusDto::from_snapshot(&files, None, None, Some("main"), &HashMap::new()); + + assert_eq!(dto.files.len(), limits::MAX_STATUS_FILES); + assert!(dto.truncated); + assert_eq!(dto.branch.as_deref(), Some("main")); +} + +#[test] +fn status_dto_carries_mtime_only_for_stated_files() { + let files = vec![ + ChangedFile::from_status_columns( + "hot.rs".to_string(), + None, + StatusKind::Modified, + StatusKind::Unmodified, + ), + ChangedFile::from_status_columns( + "gone.rs".to_string(), + None, + StatusKind::Deleted, + StatusKind::Unmodified, + ), + ]; + let mtimes = HashMap::from([( + "hot.rs".to_string(), + SystemTime::UNIX_EPOCH + Duration::from_millis(1_500), + )]); + + let value = json(&StatusDto::from_snapshot(&files, None, None, None, &mtimes)); + + assert_eq!(value["files"][0]["mtime"], 1_500u64); + assert!(value["files"][1].get("mtime").is_none()); +} + +#[test] +fn commit_file_list_never_carries_an_mtime() { + let files = vec![ChangedFile::from_status_columns( + "a.rs".to_string(), + None, + StatusKind::Modified, + StatusKind::Unmodified, + )]; + + let value = json(&CommitFilesDto::from_entries(&files)); + + assert!(value["files"][0].get("mtime").is_none()); +} + +#[test] +fn status_dto_omits_absent_optional_fields() { + let value = json(&StatusDto::from_snapshot( + &[], + None, + None, + None, + &HashMap::new(), + )); + + assert!(value.get("branch").is_none()); + assert!(value.get("head").is_none()); + assert!(value.get("tracking").is_none()); + assert_eq!(value["truncated"], false); +} diff --git a/src/web/viewer/limits.rs b/src/web/viewer/limits.rs index 6d9a44d1..216df25b 100644 --- a/src/web/viewer/limits.rs +++ b/src/web/viewer/limits.rs @@ -1,21 +1,16 @@ //! Ceilings on everything the viewer will serialize or hold open. //! //! Every route reads from a repository whose size the server does not control. -//! Without a ceiling, one request turns into an unbounded allocation and a -//! response no browser can render. Each limit below is the point past which -//! more data stops being useful to a human reading a diff. -//! -//! Truncation is always reported — [`Capped::truncated`] rides along into the -//! DTO so the UI can say "showing the first N", never silently imply it showed -//! everything. +//! Without a ceiling, one request turns into an unbounded allocation. Truncation +//! is always reported -- [`Capped::truncated`] rides along into the DTO so the UI +//! can say "showing the first N". /// Commits returned by one page of `/api/log`. Matches the TUI's -/// `commit_log_page_size` default so both surfaces move through history at the -/// same pace. +/// `commit_log_page_size` default. pub const MAX_LOG_PAGE: usize = 100; // `/api/log?skip=` deliberately has no ceiling. A ceiling here would look // prudent and protect nothing: the skip feeds `Iterator::skip` on a revwalk, so -// a request walks at most `skip + page` *or* the whole history, whichever is +// a request walks at most `skip + page` or the whole history, whichever is // smaller. An absurd skip costs what walking the repository costs and no more, // while a ceiling would turn the deep end of a long history into a page the // client can see exists and can never fetch. @@ -23,16 +18,13 @@ pub const MAX_LOG_PAGE: usize = 100; pub const MAX_COMMIT_FILES: usize = 2_000; /// Entries returned for one directory level of `/api/tree`. pub const MAX_TREE_ENTRIES: usize = 2_000; -/// Depth cap for the recursive `/api/tree/search` walk. Matches the TUI tree's -/// `max_depth` (`config.rs`). +/// Depth cap for the recursive `/api/tree/search` walk. pub const MAX_TREE_SEARCH_DEPTH: usize = 64; -/// Entries one `/api/tree/search` walk may inspect before it stops and reports -/// the listing as incomplete. Bounds filesystem work per request. +/// Entries one `/api/tree/search` walk may inspect before it stops. pub const MAX_TREE_SEARCH_VISITS: usize = 100_000; /// Matches returned by one `/api/tree/search` request. pub const MAX_TREE_SEARCH_RESULTS: usize = 500; -/// Longest accepted `/api/tree/search` query. Anything past this is rejected -/// at the boundary rather than matched against every basename. +/// Longest accepted `/api/tree/search` query. pub const MAX_TREE_SEARCH_QUERY_BYTES: usize = 256; /// Changed files reported in one status payload. pub const MAX_STATUS_FILES: usize = 2_000; @@ -43,41 +35,6 @@ pub const MAX_DIFF_LINES: usize = 20_000; /// Bytes of a single SSE payload. Status is conflated to the latest value, so /// this bounds one snapshot, not a backlog. pub const MAX_SSE_PAYLOAD_BYTES: usize = 1024 * 1024; -/// Terminals one repository may hold open at once. Each is a real process. -pub const MAX_PTYS_PER_REPO: usize = 8; -/// Bounds on a PTY's size. The client measures these from its own layout, so -/// they are input from outside and are clamped rather than trusted: a zero -/// dimension gives the child a terminal it cannot draw in (and can fail -/// `openpty` outright), and the far end lets one message ask a full-screen -/// program to allocate a screen buffer of `rows * cols` cells. -/// -/// The ceilings are set just past the widest real display rather than at a -/// round large number, because the cost being bounded is the *child's* -/// allocation and it grows with the area. A 6K screen (6144px) at a 6px cell -/// is 1024 columns, and 4K of height (2160px) at a 6px line is 360 rows — so -/// a full-screen pane at an unreadable font still fits, while the worst case -/// one message can ask for is `MAX_PANE_ROWS * MAX_PANE_COLS` cells rather -/// than u16::MAX squared (four billion). -pub const MIN_PANE_DIMENSION: u16 = 1; -pub const MAX_PANE_ROWS: u16 = 500; -pub const MAX_PANE_COLS: u16 = 1_100; -/// Raw PTY bytes retained per terminal to replay to a (re)connecting client. -/// -/// A byte window is history, not a snapshot: whatever a program did before the -/// window is not in it. Two things fall out of it, and neither is left to this -/// cap to solve. The modes a program set at startup are tracked separately and -/// restored explicitly (`terminal::hub_modes`). The screen of a program drawing -/// on the alternate screen is not replayed from here at all — its bytes are cell -/// updates against a screen the new client does not have — and the program is -/// asked to draw it again instead (`terminal::hub_repaint`). -/// -/// This used to say a full-screen program repaints on the resize every client -/// sends right after connecting. It does not: a client that reconnects into the -/// same layout deliberately sends no resize (`usePaneSizes.ts`), and a resize to -/// the size the PTY already has signals nobody. What that left on screen was -/// fragments, and the redraw key people reach for then is `Ctrl+L` — which -/// Claude Code in fullscreen rendering runs `/clear` on, twice pressed. -pub const MAX_TERMINAL_SCROLLBACK_BYTES: usize = 256 * 1024; /// Live connections the viewer's accept loop will hold. Each one costs a /// thread, so without a ceiling anything that can reach the port can exhaust /// the process. @@ -100,14 +57,6 @@ impl Capped { } Self { items, truncated } } - - /// A list that fits by construction. - pub fn untruncated(items: Vec) -> Self { - Self { - items, - truncated: false, - } - } } /// Cut `text` to at most `max_bytes`, never splitting a UTF-8 character. The @@ -125,28 +74,6 @@ pub fn cap_text(text: &str, max_bytes: usize) -> (String, bool) { (text[..end].to_string(), true) } -/// Cut `text` to at most `max_lines` lines *and* `max_bytes` bytes, whichever -/// binds first. A single enormous line is as unrenderable as a million small -/// ones, so a diff needs both ceilings. -pub fn cap_diff(text: &str, max_lines: usize, max_bytes: usize) -> (String, bool) { - let mut kept = String::new(); - let mut truncated = false; - for (index, line) in text.lines().enumerate() { - if index >= max_lines { - truncated = true; - break; - } - // +1 for the newline this line will contribute. - if kept.len() + line.len() + 1 > max_bytes { - truncated = true; - break; - } - kept.push_str(line); - kept.push('\n'); - } - (kept, truncated) -} - #[cfg(test)] mod tests { use super::*; @@ -207,46 +134,4 @@ mod tests { assert_eq!(kept, ""); assert!(truncated); } - - #[test] - fn cap_diff_stops_at_the_line_ceiling() { - let text = (0..100).map(|i| format!("line{i}\n")).collect::(); - - let (kept, truncated) = cap_diff(&text, 10, usize::MAX); - - assert_eq!(kept.lines().count(), 10); - assert!(truncated); - assert!(kept.starts_with("line0\n")); - } - - #[test] - fn cap_diff_stops_at_the_byte_ceiling_on_one_huge_line() { - // A single line far past the byte ceiling must not be emitted just - // because the line count is fine. - let text = format!("{}\n", "x".repeat(10_000)); - - let (kept, truncated) = cap_diff(&text, 1_000, 100); - - assert!(kept.is_empty()); - assert!(truncated); - } - - #[test] - fn cap_diff_leaves_content_that_fits_both_ceilings() { - let text = "a\nb\nc\n"; - - let (kept, truncated) = cap_diff(text, 10, 1_000); - - assert_eq!(kept, "a\nb\nc\n"); - assert!(!truncated); - } - - #[test] - fn cap_diff_normalizes_a_missing_trailing_newline() { - // `lines()` drops the distinction, and the viewer renders line-wise, so - // the output is deliberately newline-terminated either way. - let (kept, truncated) = cap_diff("a\nb", 10, 1_000); - assert_eq!(kept, "a\nb\n"); - assert!(!truncated); - } } diff --git a/src/web/viewer/mod.rs b/src/web/viewer/mod.rs index a7e8a14d..98f8db4f 100644 --- a/src/web/viewer/mod.rs +++ b/src/web/viewer/mod.rs @@ -2,18 +2,10 @@ //! its own HTTP server, independent of the TUI. Nothing here touches `App`, //! `ui`, or `input`, which lets the server run headless (`nightcrow serve`). -#![allow(dead_code)] // Wired up at step 6; see the module docs above. - pub mod assets; -pub mod catalog; pub mod clone_jobs; pub mod dto; pub mod highlight; pub mod limits; -pub mod prefs; -pub mod reload; -pub mod runtime; pub mod server; -pub mod session; -pub mod size_owner; -pub mod terminal; +pub(crate) mod status_payload; diff --git a/src/web/viewer/prefs/mod.rs b/src/web/viewer/prefs/mod.rs deleted file mode 100644 index ee2e530a..00000000 --- a/src/web/viewer/prefs/mod.rs +++ /dev/null @@ -1,341 +0,0 @@ -//! Preferences that follow the user rather than the browser they arrived in. -//! -//! The accent lives here, not in `localStorage`, because a session is reached -//! from several places at once — phone, laptop, tablet, and the TUI itself — -//! and a per-surface copy means picking the colour again on each, then seeing -//! them disagree. It is stored in `~/.nightcrow/`, next to the workspace file, -//! so nothing is written inside a repository that is only being read. -//! -//! The accent is the session's, not the viewer's: an attached TUI reads and -//! writes this same value over the daemon socket (`web/viewer/session.rs`). -//! That crosses no security boundary — the viewer's separation from the TUI is -//! its own port, cookie, and password, and each transport still decides who may -//! ask before reaching the session at all. -//! -//! `sidebar_width` and `upper_pct` stay the viewer's alone: the first has no TUI -//! counterpart to share with, and the second has one (`layout.upper_pct`) that -//! it is deliberately not shared with — see the field's own comment. - -pub mod maximized; -pub use maximized::{MaximizedPanel, RepoMaximized}; - -use crate::config::Accent; -use serde::{Deserialize, Serialize}; -use std::path::{Path, PathBuf}; -use std::sync::Mutex; - -/// The sidebar width the layout used before it was adjustable; a client that -/// has never dragged the divider (or an older `viewer.json` without the field) -/// gets this. -pub const DEFAULT_SIDEBAR_WIDTH: u32 = 460; -/// Bounds the stored sidebar width so a value from any client keeps both panes -/// usable: never so narrow the status letters clip, never so wide the diff is -/// squeezed out. The browser additionally caps it to a share of the viewport -/// while dragging, which is the bound that actually bites on a small screen. -pub const MIN_SIDEBAR_WIDTH: u32 = 280; -pub const MAX_SIDEBAR_WIDTH: u32 = 720; - -/// The share of the viewer's split the diff panel had before it was -/// adjustable — the same 55 as the TUI's `layout.upper_pct` default, so both -/// surfaces still come up looking alike. -pub const DEFAULT_UPPER_PCT: u32 = 55; -/// Bounds the split so neither half becomes a sliver. On a typical 800 px -/// split area, 20 leaves the diff panel its header and several lines, and 85 -/// leaves the terminal about six rows — past either end the panel is no longer -/// something you read, and "all the way" is what the maximize buttons are for. -pub const MIN_UPPER_PCT: u32 = 20; -pub const MAX_UPPER_PCT: u32 = 85; - -/// Everything the viewer remembers for its clients. -/// -/// One shared value each, with `maximized` the single exception — and the -/// reason the others are not per repository is worth keeping in view: repo ids -/// are only stable for the process lifetime (`catalog.rs`), so a per-repo key -/// would drop the preference on restart. What makes the exception safe is that -/// it is keyed by **path**, as `active_repo` already is. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -#[serde(default)] -pub struct ViewerPrefs { - /// Index into the accent cycle, in the TUI's order (`config::Accent::ALL`). - pub accent: usize, - /// File-sidebar width in CSS px, clamped to - /// `[MIN_SIDEBAR_WIDTH, MAX_SIDEBAR_WIDTH]`. Shared across clients like the - /// accent so every device opens at the same split. - pub sidebar_width: u32, - /// Share of the viewer's vertical split given to the diff panel, in - /// percent, clamped to `[MIN_UPPER_PCT, MAX_UPPER_PCT]`. The terminal panel - /// takes the rest. - /// - /// **The viewer's own, not the session's** — unlike the accent, which an - /// attached TUI reads and writes too. The TUI has a counterpart in - /// `layout.upper_pct`, but sharing the two was rejected: a percentage means - /// a different thing on a 40-row terminal than in a 1400 px window, so - /// there is no single answer to converge on, and the PTY size it would - /// appear to govern is already decided elsewhere by one owning client - /// (`web/viewer/size_owner.rs`) — so a shared ratio would move a spectator's - /// panel without moving the grid inside it. It sits with `fullscreen`, - /// which is per-client for the same reason: how much room to give the - /// terminal is a question about the screen you are looking at. - pub upper_pct: u32, - /// Absolute worktree path of the project a client last selected, so a - /// reload lands where the user left off instead of on the first tab. - /// - /// A **path**, not the repo id the client speaks: ids only live as long as - /// the process (`catalog.rs`), so a stored id would name nothing after a - /// restart — which is exactly the case this field exists for. The server - /// translates in both directions; the client never learns the path. - /// - /// `None` until a client selects a project. Never cleared by the server: a - /// path that stops being served just stops resolving, and the client falls - /// back to its first tab — then records whichever project it landed on, so - /// what is on file is always somewhere a client actually was. - pub active_repo: Option, - /// Which panel each project was left maximized in, most recently set - /// first. Empty until someone maximizes something; see [`maximized`]. - pub maximized: Vec, -} - -impl Default for ViewerPrefs { - fn default() -> Self { - Self { - accent: 0, - sidebar_width: DEFAULT_SIDEBAR_WIDTH, - upper_pct: DEFAULT_UPPER_PCT, - active_repo: None, - maximized: Vec::new(), - } - } -} - -/// The stored preferences plus the file they are written to. A missing, -/// unreadable, or corrupt file yields defaults — a colour preference is never -/// worth failing a request over. -pub struct PrefsStore { - /// `None` when the home directory cannot be determined, in which case - /// preferences apply for the process lifetime but are not persisted. - path: Option, - state: Mutex, -} - -impl PrefsStore { - /// Load from `~/.nightcrow/viewer.json`. - pub fn load() -> Self { - Self::load_seeded(ViewerPrefs::default().accent) - } - - /// Load from `~/.nightcrow/viewer.json`, starting at `seed_accent` when - /// there is no file to read. - /// - /// The seed is `[theme] name` — the colour a session that has never been - /// given one comes up in. Only a missing or unreadable file takes it: once - /// the file exists it records a choice somebody made, and a later config - /// edit must not reach back and repaint the session behind them. - pub fn load_seeded(seed_accent: usize) -> Self { - let seeded = seeded_prefs(seed_accent); - match default_path() { - Some(path) => Self::at_or(path, seeded), - None => Self { - path: None, - state: Mutex::new(seeded), - }, - } - } - - /// Load from an explicit path (tests, and the only injection point). - pub fn at(path: PathBuf) -> Self { - Self::at_or(path, ViewerPrefs::default()) - } - - /// Load from `path`, falling back to `absent` when there is nothing to read. - /// A hand-edited file can carry a width or a split outside the bounds; clamp - /// them on load so `get` never serves a value the write path would have - /// rejected. The remembered arrangements are held to the same bound for the - /// same reason: a file that arrived with more than the cap — hand-edited, or - /// written by a build whose cap was higher — would otherwise stay that long - /// forever, since the write path only trims what it just pushed onto. - fn at_or(path: PathBuf, absent: ViewerPrefs) -> Self { - let mut state = read(&path).unwrap_or(absent); - state.sidebar_width = state - .sidebar_width - .clamp(MIN_SIDEBAR_WIDTH, MAX_SIDEBAR_WIDTH); - state.upper_pct = state.upper_pct.clamp(MIN_UPPER_PCT, MAX_UPPER_PCT); - maximized::normalize(&mut state.maximized); - Self { - path: Some(path), - state: Mutex::new(state), - } - } - - pub fn get(&self) -> ViewerPrefs { - self.state.lock().expect("prefs poisoned").clone() - } - - /// Apply `change` and persist the result, both while holding the lock, so a - /// second writer cannot race a stale snapshot to disk after this one: file - /// and memory stay in step. Returns the stored value so the caller echoes - /// back what was actually kept. A failed write is logged and the in-memory - /// value still applies — the change the user just made must not appear to do - /// nothing. - fn mutate(&self, change: impl FnOnce(&mut ViewerPrefs)) -> ViewerPrefs { - let mut state = self.state.lock().expect("prefs poisoned"); - change(&mut state); - if let Some(path) = &self.path { - write(path, &state); - } - state.clone() - } - - /// Apply any subset of the preferences in one locked write. A request may - /// carry several at once (`/api/prefs` accepts any subset), so they must - /// land together — otherwise a concurrent write could interleave and the - /// echo would describe a state no single POST produced. `None` leaves a - /// field as it is. Accent wraps into range as the TUI wraps it - /// (`Accent::from_index`); width clamps so a browser drag past the bounds - /// still yields a usable split. `active_repo` is taken as given — the - /// caller resolved it from a live repository, so there is no range to fold - /// it into. - pub fn update(&self, change: PrefsUpdate) -> ViewerPrefs { - self.mutate(|state| { - if let Some(accent) = change.accent { - state.accent = accent % Accent::ALL.len(); - } - if let Some(width) = change.sidebar_width { - state.sidebar_width = width.clamp(MIN_SIDEBAR_WIDTH, MAX_SIDEBAR_WIDTH); - } - if let Some(pct) = change.upper_pct { - state.upper_pct = pct.clamp(MIN_UPPER_PCT, MAX_UPPER_PCT); - } - if let Some(path) = change.active_repo { - state.active_repo = Some(path); - } - if let Some(change) = change.maximized { - maximized::remember(&mut state.maximized, &change.repo, change.panel); - } - }) - } - - /// Record how one project's screen is arranged. Thin wrapper over - /// [`update`], taking the absolute path the caller resolved from a live - /// repository. `None` un-maximizes. - pub fn set_maximized(&self, repo: String, panel: Option) -> ViewerPrefs { - self.update(PrefsUpdate { - maximized: Some(MaximizedUpdate { repo, panel }), - ..PrefsUpdate::default() - }) - } - - /// Store `accent` alone. Thin wrapper over [`update`] so the clamping lives - /// in one place. - pub fn set_accent(&self, accent: usize) -> ViewerPrefs { - self.update(PrefsUpdate { - accent: Some(accent), - ..PrefsUpdate::default() - }) - } - - /// Store the sidebar width alone. Thin wrapper over [`update`]. - pub fn set_sidebar_width(&self, width: u32) -> ViewerPrefs { - self.update(PrefsUpdate { - sidebar_width: Some(width), - ..PrefsUpdate::default() - }) - } - - /// Store the split percentage alone. Thin wrapper over [`update`]. - pub fn set_upper_pct(&self, pct: u32) -> ViewerPrefs { - self.update(PrefsUpdate { - upper_pct: Some(pct), - ..PrefsUpdate::default() - }) - } - - /// Store the active project's absolute path alone. Thin wrapper over - /// [`update`]. There is deliberately no way to clear it: closing the last - /// project leaves no path worth recording, and keeping the old one means it - /// is still the selection when that project is opened again. - pub fn set_active_repo(&self, path: String) -> ViewerPrefs { - self.update(PrefsUpdate { - active_repo: Some(path), - ..PrefsUpdate::default() - }) - } -} - -/// The preferences a single write may carry, each `None` when the request left -/// it alone. A struct rather than positional arguments because several fields -/// share a type — two adjacent `Option` at a call site would swap silently. -#[derive(Debug, Clone, Default)] -pub struct PrefsUpdate { - pub accent: Option, - pub sidebar_width: Option, - pub upper_pct: Option, - pub active_repo: Option, - pub maximized: Option, -} - -/// One project's arrangement, as a write carries it. Two `Option`s deep because -/// the outer one means "this request said nothing about maximizing" and the -/// inner one means "this project is no longer maximized" — collapsing them -/// would make un-maximizing indistinguishable from not mentioning it. -#[derive(Debug, Clone)] -pub struct MaximizedUpdate { - /// Absolute worktree path, resolved by the caller from a live repository. - pub repo: String, - pub panel: Option, -} - -/// Defaults with the accent a config seed asks for. Wrapped into the cycle here -/// rather than trusted: `[theme]` is a hand-edited file, and an index with no -/// colour behind it would reach every reader of the prefs. -fn seeded_prefs(accent: usize) -> ViewerPrefs { - ViewerPrefs { - accent: accent % Accent::ALL.len(), - ..ViewerPrefs::default() - } -} - -fn default_path() -> Option { - dirs::home_dir().map(|h| h.join(".nightcrow").join("viewer.json")) -} - -fn read(path: &Path) -> Option { - let text = std::fs::read_to_string(path).ok()?; - match serde_json::from_str(&text) { - Ok(prefs) => Some(prefs), - Err(e) => { - tracing::warn!("corrupted viewer prefs file, ignoring: {e}"); - None - } - } -} - -/// Write via a temporary file and rename, so a crash mid-write leaves the -/// previous preferences rather than a truncated file — the same handling -/// `session.rs` gives the workspace. -fn write(path: &Path, prefs: &ViewerPrefs) { - if let Some(dir) = path.parent() - && let Err(e) = std::fs::create_dir_all(dir) - { - tracing::warn!("failed to create viewer prefs directory: {e}"); - return; - } - let text = match serde_json::to_string(prefs) { - Ok(t) => t, - Err(e) => { - tracing::warn!("failed to serialize viewer prefs: {e}"); - return; - } - }; - let tmp_path = path.with_extension("json.tmp"); - if let Err(e) = std::fs::write(&tmp_path, &text) { - tracing::warn!("failed to write viewer prefs tmp: {e}"); - return; - } - if let Err(e) = std::fs::rename(&tmp_path, path) { - tracing::warn!("failed to rename viewer prefs tmp into place: {e}"); - let _ = std::fs::remove_file(&tmp_path); - } -} - -#[cfg(test)] -mod tests; diff --git a/src/web/viewer/runtime/runtime_tests.rs b/src/web/viewer/runtime/runtime_tests.rs deleted file mode 100644 index 154877c4..00000000 --- a/src/web/viewer/runtime/runtime_tests.rs +++ /dev/null @@ -1,366 +0,0 @@ -use super::*; -use crate::git::diff::{ChangedFile, StatusKind}; -use std::sync::mpsc::SyncSender as StdSyncSender; - -/// Build an inert runtime plus the sender that feeds it snapshots. -fn test_runtime() -> ( - Arc, - StdSyncSender, - StdSyncSender<()>, -) { - let (tx, rx) = mpsc::channel::(); - let (stop_tx, _stop_rx) = mpsc::sync_channel::<()>(0); - let channel = SnapshotChannel::from_endpoints(rx); - let runtime = RepoRuntime::start(channel, "test".to_string()); - // The mpsc Sender is not a SyncSender; wrap through a relay so the test - // helper returns one uniform type. - let (relay_tx, relay_rx) = mpsc::sync_channel::(16); - thread::spawn(move || { - while let Ok(msg) = relay_rx.recv() { - if tx.send(msg).is_err() { - break; - } - } - }); - (runtime, relay_tx, stop_tx) -} - -fn snapshot(branch: &str, files: usize) -> RepoSnapshot { - RepoSnapshot { - files: (0..files) - .map(|i| { - ChangedFile::from_status_columns( - format!("f{i}.rs"), - None, - StatusKind::Modified, - StatusKind::Unmodified, - ) - }) - .collect(), - tracking: None, - head_oid: None, - branch_name: Some(branch.to_string()), - refs_fingerprint: 0, - } -} - -/// Poll until `check` passes or the budget runs out. The runtime thread -/// wakes on its own schedule, so tests wait on the condition, not a sleep. -fn wait_for(mut check: impl FnMut() -> bool) -> bool { - for _ in 0..100 { - if check() { - return true; - } - thread::sleep(Duration::from_millis(20)); - } - false -} - -#[test] -fn runtime_publishes_a_snapshot_as_a_versioned_payload() { - let (runtime, tx, _stop) = test_runtime(); - - tx.send(SnapshotMsg::Ok(snapshot("main", 2), Default::default())) - .unwrap(); - - assert!( - wait_for(|| runtime.latest().is_some()), - "no status published" - ); - let update = runtime.latest().unwrap(); - let value: serde_json::Value = serde_json::from_str(&update.json).unwrap(); - assert_eq!(value["version"], crate::web::viewer::dto::PROTOCOL_VERSION); - assert_eq!(value["branch"], "main"); - assert_eq!(value["files"].as_array().unwrap().len(), 2); - runtime.stop(); -} - -#[test] -fn a_subscriber_is_seeded_with_the_current_status() { - let (runtime, tx, _stop) = test_runtime(); - tx.send(SnapshotMsg::Ok(snapshot("main", 1), Default::default())) - .unwrap(); - assert!(wait_for(|| runtime.latest().is_some())); - - // Subscribing after the fact must not wait for the next change. - let subscription = runtime.subscribe(); - let update = subscription.next_update(Duration::from_millis(50)); - - assert!( - update.is_some(), - "a fresh subscriber must replay the latest" - ); - runtime.stop(); -} - -#[test] -fn a_slow_subscriber_gets_the_newest_status_not_a_backlog() { - let (runtime, tx, _stop) = test_runtime(); - let subscription = runtime.subscribe(); - - // Publish several without ever reading, then read once. - for i in 0..5 { - tx.send(SnapshotMsg::Ok( - snapshot(&format!("b{i}"), 1), - Default::default(), - )) - .unwrap(); - assert!(wait_for(|| runtime - .latest() - .is_some_and(|u| u.json.contains(&format!("b{i}"))))); - } - - let update = subscription.next_update(Duration::from_millis(50)).unwrap(); - assert!( - update.json.contains("b4"), - "a slow reader must land on the newest, got: {}", - update.json - ); - assert!( - subscription - .next_update(Duration::from_millis(50)) - .is_none(), - "no stale backlog may remain" - ); - runtime.stop(); -} - -#[test] -fn an_unchanged_snapshot_is_not_republished() { - // The producer ticks on a timer; an idle repository must stay silent - // rather than re-emitting the same status every second. - let (runtime, tx, _stop) = test_runtime(); - tx.send(SnapshotMsg::Ok(snapshot("main", 1), Default::default())) - .unwrap(); - assert!(wait_for(|| runtime.latest().is_some())); - let first_seq = runtime.latest().unwrap().seq; - let subscription = runtime.subscribe(); - assert!( - subscription - .next_update(Duration::from_millis(50)) - .is_some() - ); - - for _ in 0..3 { - tx.send(SnapshotMsg::Ok(snapshot("main", 1), Default::default())) - .unwrap(); - } - thread::sleep(Duration::from_millis(200)); - - assert_eq!( - runtime.latest().unwrap().seq, - first_seq, - "an identical snapshot must not burn a sequence number" - ); - assert!( - subscription - .next_update(Duration::from_millis(50)) - .is_none(), - "an idle repository must not wake its subscribers" - ); - runtime.stop(); -} - -#[test] -fn sequence_numbers_increase_across_updates() { - let (runtime, tx, _stop) = test_runtime(); - - tx.send(SnapshotMsg::Ok(snapshot("one", 1), Default::default())) - .unwrap(); - assert!(wait_for(|| runtime.latest().is_some())); - let first = runtime.latest().unwrap().seq; - tx.send(SnapshotMsg::Ok(snapshot("two", 1), Default::default())) - .unwrap(); - assert!(wait_for(|| runtime.latest().is_some_and(|u| u.seq > first))); - - assert_eq!(runtime.latest().unwrap().seq, first + 1); - runtime.stop(); -} - -#[test] -fn dropping_a_subscription_unregisters_it() { - let (runtime, _tx, _stop) = test_runtime(); - - let subscription = runtime.subscribe(); - assert_eq!(runtime.subscriber_count(), 1); - drop(subscription); - - assert_eq!( - runtime.subscriber_count(), - 0, - "a dropped client must not keep receiving fan-out" - ); - runtime.stop(); -} - -#[test] -fn next_update_times_out_when_nothing_changes() { - let (runtime, _tx, _stop) = test_runtime(); - let subscription = runtime.subscribe(); - - // No snapshot has ever been published, so there is nothing to seed with. - assert!( - subscription - .next_update(Duration::from_millis(30)) - .is_none() - ); - runtime.stop(); -} - -#[test] -fn stop_is_idempotent() { - let (runtime, _tx, _stop) = test_runtime(); - runtime.stop(); - runtime.stop(); -} - -#[test] -fn a_repository_nobody_is_watching_is_not_walked() { - // The daemon holds every open repository, and each attached client polls the - // same trees for itself. Walking one that nothing is reading is the half of - // that cost with nothing to show for it. - let (repo, path) = crate::test_util::make_repo(); - let runtime = RepoRuntime::spawn(&path); - - assert!(!runtime.is_watching(), "opening is not reading"); - // Given time to happen, rather than checked before it could: the reader used - // to be started awake and put to sleep a moment later, and asking straight - // away only ever caught the losing side of that race. - thread::sleep(Duration::from_millis(300)); - assert!( - runtime.latest().is_none(), - "and nothing has been published yet" - ); - - let subscription = runtime.subscribe(); - - assert!(runtime.is_watching(), "a subscriber starts the watch"); - // Seeded from a reading taken on subscribe, not from the tick that has not - // happened yet: a page opened after a quiet night must not render what was - // true when the last client left. - assert!( - runtime.latest().is_some(), - "the first subscriber is answered with a reading" - ); - - drop(subscription); - - assert!( - !runtime.is_watching(), - "the last subscriber leaving stops it again" - ); - runtime.stop(); - drop(repo); -} - -#[test] -fn a_second_subscriber_does_not_disturb_the_watch() { - // Only the transitions matter; a client arriving while others are reading - // must not cost an extra walk on its request thread. - let (repo, path) = crate::test_util::make_repo(); - let runtime = RepoRuntime::spawn(&path); - let first = runtime.subscribe(); - let before = runtime.latest().expect("the first subscriber read once"); - - let second = runtime.subscribe(); - - assert_eq!( - runtime.latest().map(|update| update.seq), - Some(before.seq), - "nothing was republished" - ); - assert!(runtime.is_watching()); - drop(second); - assert!(runtime.is_watching(), "one subscriber is still reading"); - drop(first); - assert!(!runtime.is_watching()); - runtime.stop(); - drop(repo); -} - -#[test] -fn concurrent_publishers_never_move_the_latest_status_backwards() { - // Three threads publish — the runtime's own, a REST handler taking a - // reading on demand, and a first subscriber taking one for itself. Deciding - // what is new, numbering it, and storing it have to happen together: apart, - // two readings can be numbered in one order and stored in the other, and - // what everyone keeps is the older of the two under the higher number. - let (runtime, _tx, _stop) = test_runtime(); - let publishers: Vec<_> = (0..4) - .map(|thread| { - let runtime = Arc::clone(&runtime); - std::thread::spawn(move || { - for round in 0..300 { - runtime.publish( - &snapshot(&format!("b{thread}-{round}"), 1), - &Default::default(), - ); - } - }) - }) - .collect(); - - let watcher = { - let runtime = Arc::clone(&runtime); - std::thread::spawn(move || { - let mut highest = 0; - for _ in 0..20_000 { - if let Some(update) = runtime.latest() { - assert!( - update.seq >= highest, - "the newest status went back to {} from {highest}", - update.seq - ); - highest = update.seq; - } - } - }) - }; - - for publisher in publishers { - publisher.join().expect("a publisher panicked"); - } - watcher.join().expect("the newest status moved backwards"); - runtime.stop(); -} - -#[test] -fn concurrent_arrivals_and_departures_leave_the_watch_matching_the_list() { - // Two questions are asked of the same list — "am I the first?" and "am I - // the last?" — and the watch is whatever the last answer said. This pins - // the state they must agree on: a subscriber attached means the tree is - // read, and the subscriber count is not that fact. - // - // It does not reproduce the interleaving those answers must be taken - // together to survive: that window is the few instructions between reading - // the list and joining it, and nothing available here forces a thread into - // it. See the commit that made both take the lock once. - let (repo, path) = crate::test_util::make_repo(); - let runtime = RepoRuntime::spawn(&path); - - let churn: Vec<_> = (0..8) - .map(|_| { - let runtime = Arc::clone(&runtime); - thread::spawn(move || { - for _ in 0..400 { - let held = runtime.subscribe(); - drop(held); - } - }) - }) - .collect(); - let staying = runtime.subscribe(); - for thread in churn { - thread.join().expect("a churning subscriber panicked"); - } - - assert!( - runtime.watch.is_awake(), - "a subscriber is attached, so the tree is read" - ); - drop(staying); - assert!(!runtime.watch.is_awake(), "and stops when it goes"); - - runtime.stop(); - drop(repo); -} diff --git a/src/web/viewer/server/clone_routes.rs b/src/web/viewer/server/clone_routes.rs index 411672b5..d1dbd11f 100644 --- a/src/web/viewer/server/clone_routes.rs +++ b/src/web/viewer/server/clone_routes.rs @@ -15,7 +15,7 @@ use std::sync::Arc; #[derive(serde::Deserialize)] struct CloneRequest { - /// The directory to clone into — the one the picker is showing. + /// The directory to clone into. path: String, /// The remote address. Validated by `git::clone::validate_clone_url`. url: String, @@ -24,10 +24,9 @@ struct CloneRequest { /// Start a clone under the browsed directory and return its job id. /// /// The destination is derived from the URL, never supplied by the client, and -/// is a single plain segment by construction — so, as with `mkdir`, the clone -/// can only land directly under the canonicalized parent. The URL's scheme is -/// checked before `git` sees it: `ext::` executes a command, so an unfiltered -/// URL here would be remote code execution on the server. +/// is a single plain segment by construction. The URL's scheme is checked before +/// `git` sees it: `ext::` executes a command, so an unfiltered URL here would be +/// remote code execution on the server. pub(super) fn handle_clone(body: &str, state: &Arc) -> Vec { let request: CloneRequest = match serde_json::from_str(body) { Ok(request) => request, @@ -55,15 +54,13 @@ pub(super) fn handle_clone(body: &str, state: &Arc) -> Vec { }; let dest = base.join(&name); // One at a time, admitted atomically — a check followed by a separate - // insert would let parallel requests each see an idle registry and every - // one of them spawn a clone. + // insert would let parallel requests each see an idle registry. let Some(id) = state.clones.try_start() else { return json_error("409 Conflict", "a clone is already running"); }; // Claim the destination by creating it rather than testing `exists()` // first: `create_dir` is atomic and does not follow a symlink in the final - // component, so it cannot be raced into pointing outside `base`. git is - // content to clone into a directory it finds empty. + // component, so it cannot be raced into pointing outside `base`. if let Err(err) = std::fs::create_dir(&dest) { state.clones.finish( id, diff --git a/src/web/viewer/server/handlers.rs b/src/web/viewer/server/handlers.rs index 4c25424d..d03700ec 100644 --- a/src/web/viewer/server/handlers.rs +++ b/src/web/viewer/server/handlers.rs @@ -1,354 +1,10 @@ -use super::http_util::json_error; -use super::mutations::{lookup_repo, redact}; -use super::{SSE_HEARTBEAT, TERM_POLL_TIMEOUT, ViewerState}; -use crate::web::common::conn; -use crate::web::common::sse::SseStream; -use crate::web::viewer::catalog::RepoEntry; -use crate::web::viewer::dto::Envelope; -use crate::web::viewer::size_owner::ViewerId; -use crate::web::viewer::terminal::{self, ClientMessage, TerminalFrame}; -use anyhow::{Context, Result}; -use std::io::Write; -use std::net::TcpStream; -use std::time::Duration; -use tungstenite::Message; - -/// Look the repository up, validate any `path` parameter, then run `body`. -/// -/// Validation happens here rather than in each handler so no route can forget -/// it. Not every downstream touches the filesystem — `load_file_diff` passes -/// the path to git as a pathspec — but a route must not be safe only by -/// accident of which loader it happens to call. A traversal path is refused -/// uniformly, and never echoed back in a response. -pub(super) fn with_repo( - head: &crate::web::common::http::RequestHead, - state: &ViewerState, - body: impl FnOnce(&RepoEntry) -> Result>, -) -> Vec { - let entry = match lookup_repo(head, state) { - Ok(entry) => entry, - Err(response) => return response, - }; - // An absent or empty `path` means "the repository root" for the routes that - // accept one; anything else has to survive the gate. - if let Some(path) = head.query_param("path").filter(|p| !p.is_empty()) - && let Err(err) = - crate::git::path::resolve_in_workdir(std::path::Path::new(&entry.path), &path) - { - tracing::debug!(%err, route = %head.path, "viewer: rejected path parameter"); - return json_error("400 Bad Request", "invalid path"); - } - match body(&entry) { - Ok(response) => response, - Err(err) => redact(&head.path, &err), - } -} - -/// Variant of [`with_repo`] for a path inside a historical git object. -/// -/// A deleted commit path cannot be resolved in the current worktree, so this -/// validates its syntax without statting it. The route passes it only to an -/// exact git pathspec; it never opens a filesystem path. -pub(super) fn with_repo_commit_path( - head: &crate::web::common::http::RequestHead, - state: &ViewerState, - body: impl FnOnce(&RepoEntry, &str) -> Result>, -) -> Vec { - let entry = match lookup_repo(head, state) { - Ok(entry) => entry, - Err(response) => return response, - }; - let path = match required_path(head) { - Ok(path) => path, - Err(err) => return redact(&head.path, &err), - }; - if let Err(err) = crate::git::path::validate_commit_path(&path) { - tracing::debug!(%err, route = %head.path, "viewer: rejected historical path parameter"); - return json_error("400 Bad Request", "invalid path"); - } - match body(&entry, &path) { - Ok(response) => response, - Err(err) => redact(&head.path, &err), - } -} - -pub(super) fn required_path(head: &crate::web::common::http::RequestHead) -> Result { - head.query_param("path") - .filter(|p| !p.is_empty()) - .ok_or_else(|| anyhow::anyhow!("missing path parameter")) -} - -/// An oid query parameter that may be absent, but must parse when present. -/// -/// Absent and malformed are kept apart deliberately: silently walking from HEAD -/// after a typo would answer a different question than the one asked, and the -/// client pages against the value it gets back. -pub(super) fn optional_oid( - head: &crate::web::common::http::RequestHead, - name: &str, -) -> Result> { - match head.query_param(name) { - None => Ok(None), - Some(text) => git2::Oid::from_str(&text) - .map(Some) - .with_context(|| format!("malformed {name} parameter")), - } -} - -/// A non-negative count query parameter, defaulting to zero when absent. -/// Deliberately unbounded — see the note beside [`limits::MAX_LOG_PAGE`]. -pub(super) fn optional_count( - head: &crate::web::common::http::RequestHead, - name: &str, -) -> Result { - let Some(text) = head.query_param(name) else { - return Ok(0); - }; - text.parse() - .with_context(|| format!("malformed {name} parameter")) -} - -pub(super) fn required_oid(head: &crate::web::common::http::RequestHead) -> Result { - let oid_text = head - .query_param("oid") - .ok_or_else(|| anyhow::anyhow!("missing oid parameter"))?; - git2::Oid::from_str(&oid_text).context("malformed oid") -} - -pub(super) fn open_repo(path: &str) -> Result { - git2::Repository::discover(path).context("failed to open repository") -} - -pub(super) fn encode(payload: &T) -> Result { - serde_json::to_string(&Envelope::new(payload)).context("failed to encode payload") -} - -/// Hold the connection open and stream this repository's status. -pub(super) fn serve_events( - mut stream: TcpStream, - head: &crate::web::common::http::RequestHead, - state: &ViewerState, -) { - let entry = match lookup_repo(head, state) { - Ok(entry) => entry, - Err(response) => { - let _ = stream.write_all(&response); - return; - } - }; - // A stalled reader must not wedge the handler thread forever. - let _ = stream.set_write_timeout(Some(SSE_HEARTBEAT)); - - let subscription = entry.runtime.subscribe(); - let Ok(mut sse) = SseStream::start(stream) else { - return; - }; - loop { - match subscription.next_update(SSE_HEARTBEAT) { - Some(update) => { - if sse.send("status", &update.json).is_err() { - break; - } - } - // Nothing changed: prove the socket is still alive. This is the - // only way a closed tab is discovered. - None => { - if sse.heartbeat().is_err() { - break; - } - } - } - } - // `subscription` drops here, unregistering from the fan-out. -} - -/// The longest `viewer` id accepted. Long enough for a UUID with room to spare; -/// the value is only ever compared, never shown. -const MAX_VIEWER_ID: usize = 64; - -/// A page's identity for the session's size ownership, from what it called -/// itself. -/// -/// The page generates this once per tab and sends it on every socket, so its -/// connections can come and go — a repository switch, a reconnect — without the -/// session reading them as somebody new sitting down. -/// -/// A boundary input, so it is held to what an id can be: a short run of plain -/// characters. An id that is missing or malformed gets one of its own rather -/// than a refusal — the page still works, it simply behaves as it did before it -/// could name itself, and a stale cached bundle is the likely reason. -fn browser_viewer(head: &crate::web::common::http::RequestHead) -> ViewerId { - let named = head.query_param("viewer").filter(|id| { - !id.is_empty() - && id.len() <= MAX_VIEWER_ID - && id - .chars() - .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_') - }); - match named { - Some(id) => ViewerId::Browser(id), - None => { - tracing::debug!("viewer: a terminal socket did not name its page"); - ViewerId::Browser(anonymous_viewer()) - } - } -} - -fn anonymous_viewer() -> String { - use std::sync::atomic::{AtomicU64, Ordering}; - static NEXT: AtomicU64 = AtomicU64::new(0); - format!("conn-{}", NEXT.fetch_add(1, Ordering::Relaxed)) -} - -/// Hand this connection to the repository's terminal hub. -/// -/// Auth and Origin were already enforced by `handle_connection`, before the -/// repository was named — a terminal is effectively a shell, so the upgrade -/// must never be reachable ahead of those checks. -pub(super) fn serve_terminal( - stream: TcpStream, - head: &crate::web::common::http::RequestHead, - state: &ViewerState, -) { - let mut stream = stream; - let entry = match lookup_repo(head, state) { - Ok(entry) => entry, - Err(response) => { - let _ = stream.write_all(&response); - return; - } - }; - // Without a read timeout, `ws.read()` blocks and terminal output would - // only flush when the user happened to type. The timeout turns the loop - // into a poll that services both directions. - let _ = stream.set_read_timeout(Some(TERM_POLL_TIMEOUT)); - let _ = stream.set_write_timeout(Some(SSE_HEARTBEAT)); - // Taken before the stream is moved into the websocket, which owns it from - // there on. - let evict_handle = stream.try_clone(); - let Some(mut ws) = conn::websocket_handshake(stream, head) else { - return; - }; - // `claim` is the page saying a person just opened it, as opposed to a - // repository switch or a reconnect. Absent means no — a socket that does not - // say it arrived must not take the sizing off whoever is looking. - let arriving = head.query_param("claim").as_deref() == Some("1"); - // A second handle, kept by the hub only to end this connection if the page - // stops draining its queue: the loop below is then parked in `ws.read()` and - // nothing else would wake it. A clone that could not be made costs the hub - // that ability and nothing else. - let evict_handle = match evict_handle { - Ok(handle) => Some(handle), - Err(err) => { - // Degrades to what this did before there was a handle at all: the - // client is dropped from the broadcast list but its socket stays - // open. Logged rather than passed over, because the page then holds - // a panel that has stopped updating and nothing else says so — and - // because a clone that fails means descriptors are exhausted, which - // is worth knowing on its own. - tracing::warn!(%err, "viewer: a terminal socket cannot be cut off if it stalls"); - None - } - }; - let session = entry - .terminals - .connect(browser_viewer(head), arriving, evict_handle); - - loop { - // Drain everything queued for us before blocking on the socket, so - // output is not held back waiting for the client to say something. - let mut wrote = false; - while let Some(frame) = session.next_frame(Duration::from_millis(1)) { - let message = match frame { - TerminalFrame::Output { pane, data } => { - Message::Binary(terminal::encode_output(pane, &data).into()) - } - TerminalFrame::Control(json) => Message::Text(json.into()), - }; - if ws.send(message).is_err() { - return; - } - wrote = true; - } - if wrote && ws.flush().is_err() { - return; - } - - match ws.read() { - Ok(Message::Text(text)) => match serde_json::from_str::(&text) { - Ok(message) => session.dispatch(message), - // A malformed frame is dropped, not fatal: a client bug should - // not take the terminal down with it. - Err(err) => tracing::debug!(%err, "viewer: bad terminal message"), - }, - Ok(Message::Close(_)) => return, - Ok(_) => {} - // A poll timeout surfaces as WouldBlock on macOS and TimedOut on - // Linux; neither means the client is gone. - Err(tungstenite::Error::Io(err)) - if matches!( - err.kind(), - std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut - ) => {} - Err(_) => return, - } - } - // `session` drops here, unregistering from the hub. -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::web::common::http::RequestHead; - - fn head(query: &str) -> RequestHead { - crate::web::common::http::parse_request_head(&format!( - "GET /ws/term?{query} HTTP/1.1\r\nHost: h\r\n\r\n" - )) - .expect("a well-formed request line") - } - - #[test] - fn a_page_that_names_itself_is_taken_at_its_word() { - assert_eq!( - browser_viewer(&head("repo=r1&viewer=tab-9f2c")), - ViewerId::Browser("tab-9f2c".to_string()) - ); - } - - /// The whole point of the id is that the same page keeps it across sockets. - #[test] - fn the_same_page_is_the_same_viewer_on_every_socket() { - assert_eq!( - browser_viewer(&head("repo=r1&viewer=tab-1")), - browser_viewer(&head("repo=r2&viewer=tab-1")), - ); - } - - /// A stale bundle, or something that is not a page at all. It still works — - /// it just behaves as a browser did before it could name itself. - #[test] - fn a_socket_with_no_usable_id_gets_one_of_its_own() { - let long = "a".repeat(MAX_VIEWER_ID + 1); - for query in [ - "repo=r1".to_string(), - "repo=r1&viewer=".to_string(), - "repo=r1&viewer=has%20space".to_string(), - "repo=r1&viewer=semi;colon".to_string(), - format!("repo=r1&viewer={long}"), - ] { - let first = browser_viewer(&head(&query)); - let second = browser_viewer(&head(&query)); - assert_ne!(first, second, "each must be its own screen: {query}"); - } - } - - #[test] - fn an_id_at_the_cap_is_still_accepted() { - let id = "a".repeat(MAX_VIEWER_ID); - assert_eq!( - browser_viewer(&head(&format!("repo=r1&viewer={id}"))), - ViewerId::Browser(id) - ); - } -} +mod http; +mod repository; +mod sse; +mod terminal; + +pub(super) use super::http_util::encode; +pub(super) use http::{optional_count, optional_oid, required_oid, required_path}; +pub(super) use repository::{open_repo, with_repo, with_repo_commit_path}; +pub(super) use sse::serve_events; +pub(super) use terminal::serve_terminal; diff --git a/src/web/viewer/server/handlers/http.rs b/src/web/viewer/server/handlers/http.rs new file mode 100644 index 00000000..c58cdd92 --- /dev/null +++ b/src/web/viewer/server/handlers/http.rs @@ -0,0 +1,47 @@ +use anyhow::{Context, Result}; + +pub(in crate::web::viewer::server) fn required_path( + head: &crate::web::common::http::RequestHead, +) -> Result { + head.query_param("path") + .filter(|p| !p.is_empty()) + .ok_or_else(|| anyhow::anyhow!("missing path parameter")) +} + +/// An oid query parameter that may be absent, but must parse when present. +/// +/// Absent and malformed are kept apart deliberately: silently walking from HEAD +/// after a typo would answer a different question than the one asked. +pub(in crate::web::viewer::server) fn optional_oid( + head: &crate::web::common::http::RequestHead, + name: &str, +) -> Result> { + match head.query_param(name) { + None => Ok(None), + Some(text) => git2::Oid::from_str(&text) + .map(Some) + .with_context(|| format!("malformed {name} parameter")), + } +} + +/// A non-negative count query parameter, defaulting to zero when absent. +/// Deliberately unbounded -- see the note beside [`crate::web::viewer::limits::MAX_LOG_PAGE`]. +pub(in crate::web::viewer::server) fn optional_count( + head: &crate::web::common::http::RequestHead, + name: &str, +) -> Result { + let Some(text) = head.query_param(name) else { + return Ok(0); + }; + text.parse() + .with_context(|| format!("malformed {name} parameter")) +} + +pub(in crate::web::viewer::server) fn required_oid( + head: &crate::web::common::http::RequestHead, +) -> Result { + let oid_text = head + .query_param("oid") + .ok_or_else(|| anyhow::anyhow!("missing oid parameter"))?; + git2::Oid::from_str(&oid_text).context("malformed oid") +} diff --git a/src/web/viewer/server/handlers/repository.rs b/src/web/viewer/server/handlers/repository.rs new file mode 100644 index 00000000..7b07b4fa --- /dev/null +++ b/src/web/viewer/server/handlers/repository.rs @@ -0,0 +1,65 @@ +use super::super::ViewerState; +use super::super::http_util::json_error; +use super::super::mutations::{lookup_repo, redact}; +use super::http::required_path; +use crate::session::catalog::RepoEntry; +use anyhow::{Context, Result}; + +/// Look the repository up, validate any `path` parameter, then run `body`. +/// +/// Validation happens here rather than in each handler so no route can forget +/// it. A traversal path is refused uniformly, and never echoed back. +pub(in crate::web::viewer::server) fn with_repo( + head: &crate::web::common::http::RequestHead, + state: &ViewerState, + body: impl FnOnce(&RepoEntry) -> Result>, +) -> Vec { + let entry = match lookup_repo(head, state) { + Ok(entry) => entry, + Err(response) => return response, + }; + // An absent or empty `path` means "the repository root" for the routes that + // accept one; anything else has to survive the gate. + if let Some(path) = head.query_param("path").filter(|p| !p.is_empty()) + && let Err(err) = + crate::git::path::resolve_in_workdir(std::path::Path::new(&entry.path), &path) + { + tracing::debug!(%err, route = %head.path, "viewer: rejected path parameter"); + return json_error("400 Bad Request", "invalid path"); + } + match body(&entry) { + Ok(response) => response, + Err(err) => redact(&head.path, &err), + } +} + +/// Variant of [`with_repo`] for a path inside a historical git object. +/// +/// A deleted commit path cannot be resolved in the current worktree, so this +/// validates its syntax without statting it. +pub(in crate::web::viewer::server) fn with_repo_commit_path( + head: &crate::web::common::http::RequestHead, + state: &ViewerState, + body: impl FnOnce(&RepoEntry, &str) -> Result>, +) -> Vec { + let entry = match lookup_repo(head, state) { + Ok(entry) => entry, + Err(response) => return response, + }; + let path = match required_path(head) { + Ok(path) => path, + Err(err) => return redact(&head.path, &err), + }; + if let Err(err) = crate::git::path::validate_commit_path(&path) { + tracing::debug!(%err, route = %head.path, "viewer: rejected historical path parameter"); + return json_error("400 Bad Request", "invalid path"); + } + match body(&entry, &path) { + Ok(response) => response, + Err(err) => redact(&head.path, &err), + } +} + +pub(in crate::web::viewer::server) fn open_repo(path: &str) -> Result { + git2::Repository::discover(path).context("failed to open repository") +} diff --git a/src/web/viewer/server/handlers/sse.rs b/src/web/viewer/server/handlers/sse.rs new file mode 100644 index 00000000..5e7fb7ff --- /dev/null +++ b/src/web/viewer/server/handlers/sse.rs @@ -0,0 +1,44 @@ +use super::super::mutations::lookup_repo; +use super::super::{SSE_HEARTBEAT, ViewerState}; +use crate::web::common::sse::SseStream; +use std::io::Write; +use std::net::TcpStream; + +/// Hold the connection open and stream this repository's status. +pub(in crate::web::viewer::server) fn serve_events( + mut stream: TcpStream, + head: &crate::web::common::http::RequestHead, + state: &ViewerState, +) { + let entry = match lookup_repo(head, state) { + Ok(entry) => entry, + Err(response) => { + let _ = stream.write_all(&response); + return; + } + }; + // A stalled reader must not wedge the handler thread forever. + let _ = stream.set_write_timeout(Some(SSE_HEARTBEAT)); + + let subscription = entry.runtime.subscribe(); + let Ok(mut sse) = SseStream::start(stream) else { + return; + }; + loop { + match subscription.next_update(SSE_HEARTBEAT) { + Some(update) => { + if sse.send("status", &update.json).is_err() { + break; + } + } + // Nothing changed: prove the socket is still alive. This is the + // only way a closed tab is discovered. + None => { + if sse.heartbeat().is_err() { + break; + } + } + } + } + // `subscription` drops here, unregistering from the fan-out. +} diff --git a/src/web/viewer/server/handlers/terminal.rs b/src/web/viewer/server/handlers/terminal.rs new file mode 100644 index 00000000..ea468091 --- /dev/null +++ b/src/web/viewer/server/handlers/terminal.rs @@ -0,0 +1,199 @@ +use super::super::mutations::lookup_repo; +use super::super::{SSE_HEARTBEAT, TERM_POLL_TIMEOUT, ViewerState}; +use crate::session::size_owner::ViewerId; +use crate::session::terminal::{self, ClientMessage, TerminalFrame}; +use crate::web::common::conn; +use std::io::Write; +use std::net::TcpStream; +use std::time::Duration; +use tungstenite::Message; + +/// The longest `viewer` id accepted. Long enough for a UUID with room to spare. +const MAX_VIEWER_ID: usize = 64; + +/// A page's identity for the session's size ownership, from what it called +/// itself. +/// +/// The page generates this once per tab and sends it on every socket, so its +/// connections can come and go without the session reading them as somebody new +/// sitting down. +/// +/// A boundary input, so it is held to what an id can be: a short run of plain +/// characters. An id that is missing or malformed gets one of its own rather +/// than a refusal -- the page still works, it simply behaves as it did before it +/// could name itself. +fn browser_viewer(head: &crate::web::common::http::RequestHead) -> ViewerId { + let named = head.query_param("viewer").filter(|id| { + !id.is_empty() + && id.len() <= MAX_VIEWER_ID + && id + .chars() + .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_') + }); + match named { + Some(id) => ViewerId::Browser(id), + None => { + tracing::debug!("viewer: a terminal socket did not name its page"); + ViewerId::Browser(anonymous_viewer()) + } + } +} + +fn anonymous_viewer() -> String { + use std::sync::atomic::{AtomicU64, Ordering}; + static NEXT: AtomicU64 = AtomicU64::new(0); + format!("conn-{}", NEXT.fetch_add(1, Ordering::Relaxed)) +} + +/// Hand this connection to the repository's terminal hub. +/// +/// Auth and Origin were already enforced by `handle_connection`, before the +/// repository was named -- a terminal is effectively a shell, so the upgrade +/// must never be reachable ahead of those checks. +pub(in crate::web::viewer::server) fn serve_terminal( + stream: TcpStream, + head: &crate::web::common::http::RequestHead, + state: &ViewerState, +) { + let mut stream = stream; + let entry = match lookup_repo(head, state) { + Ok(entry) => entry, + Err(response) => { + let _ = stream.write_all(&response); + return; + } + }; + // Without a read timeout, `ws.read()` blocks and terminal output would + // only flush when the user happened to type. The timeout turns the loop + // into a poll that services both directions. + let _ = stream.set_read_timeout(Some(TERM_POLL_TIMEOUT)); + let _ = stream.set_write_timeout(Some(SSE_HEARTBEAT)); + // Taken before the stream is moved into the websocket, which owns it from + // there on. + let evict_handle = stream.try_clone(); + let Some(mut ws) = conn::websocket_handshake(stream, head) else { + return; + }; + // `claim` is the page saying a person just opened it, as opposed to a + // repository switch or a reconnect. Absent means no -- a socket that does not + // say it arrived must not take the sizing off whoever is looking. + let arriving = head.query_param("claim").as_deref() == Some("1"); + // A second handle, kept by the hub only to end this connection if the page + // stops draining its queue: the loop below is then parked in `ws.read()` and + // nothing else would wake it. A clone that could not be made costs the hub + // that ability and nothing else. + let evict_handle = match evict_handle { + Ok(handle) => Some(handle), + Err(err) => { + // Degrades to what this did before there was a handle at all: the + // client is dropped from the broadcast list but its socket stays + // open. Logged rather than passed over, because the page then holds + // a panel that has stopped updating and nothing else says so -- and + // because a clone that fails means descriptors are exhausted, which + // is worth knowing on its own. + tracing::warn!(%err, "viewer: a terminal socket cannot be cut off if it stalls"); + None + } + }; + let session = entry + .terminals + .connect(browser_viewer(head), arriving, evict_handle); + + loop { + // Drain everything queued for us before blocking on the socket, so + // output is not held back waiting for the client to say something. + let mut wrote = false; + while let Some(frame) = session.next_frame(Duration::from_millis(1)) { + let message = match frame { + TerminalFrame::Output { pane, data } => { + Message::Binary(terminal::encode_output(pane, &data).into()) + } + TerminalFrame::Control(json) => Message::Text(json.into()), + }; + if ws.send(message).is_err() { + return; + } + wrote = true; + } + if wrote && ws.flush().is_err() { + return; + } + + match ws.read() { + Ok(Message::Text(text)) => match serde_json::from_str::(&text) { + Ok(message) => session.dispatch(message), + // A malformed frame is dropped, not fatal: a client bug should + // not take the terminal down with it. + Err(err) => tracing::debug!(%err, "viewer: bad terminal message"), + }, + Ok(Message::Close(_)) => return, + Ok(_) => {} + // A poll timeout surfaces as WouldBlock on macOS and TimedOut on + // Linux; neither means the client is gone. + Err(tungstenite::Error::Io(err)) + if matches!( + err.kind(), + std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut + ) => {} + Err(_) => return, + } + } + // `session` drops here, unregistering from the hub. +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::web::common::http::RequestHead; + + fn head(query: &str) -> RequestHead { + crate::web::common::http::parse_request_head(&format!( + "GET /ws/term?{query} HTTP/1.1\r\nHost: h\r\n\r\n" + )) + .expect("a well-formed request line") + } + + #[test] + fn a_page_that_names_itself_is_taken_at_its_word() { + assert_eq!( + browser_viewer(&head("repo=r1&viewer=tab-9f2c")), + ViewerId::Browser("tab-9f2c".to_string()) + ); + } + + /// The whole point of the id is that the same page keeps it across sockets. + #[test] + fn the_same_page_is_the_same_viewer_on_every_socket() { + assert_eq!( + browser_viewer(&head("repo=r1&viewer=tab-1")), + browser_viewer(&head("repo=r2&viewer=tab-1")), + ); + } + + /// A stale bundle, or something that is not a page at all. It still works -- + /// it just behaves as a browser did before it could name itself. + #[test] + fn a_socket_with_no_usable_id_gets_one_of_its_own() { + let long = "a".repeat(MAX_VIEWER_ID + 1); + for query in [ + "repo=r1".to_string(), + "repo=r1&viewer=".to_string(), + "repo=r1&viewer=has%20space".to_string(), + "repo=r1&viewer=semi;colon".to_string(), + format!("repo=r1&viewer={long}"), + ] { + let first = browser_viewer(&head(&query)); + let second = browser_viewer(&head(&query)); + assert_ne!(first, second, "each must be its own screen: {query}"); + } + } + + #[test] + fn an_id_at_the_cap_is_still_accepted() { + let id = "a".repeat(MAX_VIEWER_ID); + assert_eq!( + browser_viewer(&head(&format!("repo=r1&viewer={id}"))), + ViewerId::Browser(id) + ); + } +} diff --git a/src/web/viewer/server/http_util.rs b/src/web/viewer/server/http_util.rs index 28e3c098..c76759dc 100644 --- a/src/web/viewer/server/http_util.rs +++ b/src/web/viewer/server/http_util.rs @@ -1,4 +1,10 @@ use crate::web::common::http; +use crate::web::viewer::dto::Envelope; +use anyhow::{Context, Result}; + +pub(super) fn encode(payload: &T) -> Result { + serde_json::to_string(&Envelope::new(payload)).context("failed to encode payload") +} pub(super) fn json_response(status: &str, body: &str, extra: &[(&str, &str)]) -> Vec { let mut headers = vec![("X-Content-Type-Options", "nosniff")]; diff --git a/src/web/viewer/server/mod.rs b/src/web/viewer/server/mod.rs index a0852ff5..ceaa7b58 100644 --- a/src/web/viewer/server/mod.rs +++ b/src/web/viewer/server/mod.rs @@ -1,30 +1,8 @@ -//! The viewer's HTTP server: read-only git routes plus a live status stream. +//! The viewer's HTTP server: authenticated routes over a shared session. //! -//! Runs on its own port with its own session cookie. The cookie is named for -//! this server rather than for nightcrow at large: two servers sharing a cookie -//! name on one host would let a session for one authenticate against the other. -//! -//! Request handling order is deliberate and load-bearing: -//! -//! 1. **Host, then Origin** — a rebound name or a cross-site request is -//! refused before anything else runs. Origin alone only proves Origin and -//! Host agree, which a DNS-rebinding attacker satisfies trivially. -//! 2. **Static assets** — the built bundle is public. It holds no repository -//! data and renders the login form, so gating it would leave no way in. -//! 3. **Auth** — checked *before* the repository is looked up, so an -//! unauthenticated request cannot probe which ids exist by comparing a 404 -//! against a 401. -//! 4. **Lookup** — an opaque id resolves to a repository, or 404s. -//! 5. **Path validation** — any `path` parameter goes through -//! [`crate::git::path::resolve_in_workdir`] before touching the filesystem. -//! -//! Each git request opens its own `git2::Repository`. Handler threads are -//! short-lived, so a per-thread cache would be discarded with the thread; the -//! per-repo runtime thread owns the long-lived watching instead. -//! -//! Errors are redacted: git and io messages carry absolute paths, symlink -//! targets, and file sizes, so handlers map them to a fixed public string and -//! log the detail server-side. +//! Request handling order is Host/Origin, static assets, authentication, +//! repository lookup, then path validation. Git and I/O details are redacted +//! before responses because they can contain absolute server paths. mod clone_routes; mod dispatch; @@ -33,58 +11,34 @@ mod http_util; mod mutations; mod routes; +use crate::session::prefs::PrefsStore; +use crate::session::{SessionOptions, SessionState}; use crate::web::common::auth::{Auth, RateLimiter, SessionStore}; -use crate::web::viewer::prefs::PrefsStore; use anyhow::{Context, Result}; use std::net::{IpAddr, SocketAddr, TcpListener}; use std::sync::Arc; use std::sync::atomic::AtomicUsize; use std::time::Duration; -/// Named for this server, not for nightcrow: another server on the same host -/// must not be able to authenticate with a session issued here. +/// Named for this server so another service on the host cannot authenticate +/// with a cookie issued here. pub const VIEWER_SESSION_COOKIE: &str = "nightcrow_viewer_session"; -/// How long an idle SSE stream waits before sending a heartbeat. A write is -/// the only way to find out a socket is dead. pub(super) const SSE_HEARTBEAT: Duration = Duration::from_secs(15); - -/// Read timeout on a terminal socket. Bounds how long queued output waits -/// behind a blocked read; terminal latency is felt directly. pub(super) const TERM_POLL_TIMEOUT: Duration = Duration::from_millis(10); +/// HTTP-only state. Repository and terminal ownership lives in SessionState, +/// which the daemon and this transport share. pub struct ViewerState { - pub(super) catalog: crate::web::viewer::catalog::Catalog, - /// Whether the listener is on a loopback address. Gates the Host check: - /// off-loopback, the operator owns the network path and may front this - /// with a proxy under any name. + pub(super) session: Arc, pub(super) bound_loopback: bool, pub(super) auth: Auth, pub(super) sessions: SessionStore, pub(super) limiter: RateLimiter, pub(super) connections: Arc, - /// Whether catalog changes are mirrored to the shared workspace file. On in - /// headless `serve` (so opens/closes are remembered), off alongside the TUI - /// (which owns that file). - pub(super) persist: bool, - /// The TUI's recently-touched settings, served to the client so the file - /// list fades on the same window the TUI does. `auto_follow` is not sent: - /// it moves the TUI's selection, which the viewer has no analogue for. pub(super) hot: crate::config::AgentIndicatorConfig, - /// Viewer preferences shared by every client (see `prefs.rs`). Always - /// written: the file is the viewer's own and no TUI owns it. - pub(super) prefs: PrefsStore, - /// In-flight and recently finished clones (see `clone_jobs.rs`). A clone - /// outlives the request that started it, so its result is polled. pub(super) clones: crate::web::viewer::clone_jobs::CloneJobs, - /// Whether `git` was on PATH at startup. Reported to clients so the clone - /// form is disabled up front rather than failing every job it starts. pub(super) git_available: bool, - /// Held for the length of a config reload (see - /// [`crate::web::viewer::reload`]). Two clients pressing at once would - /// otherwise interleave one's table swap with the other's fan-out to the - /// hubs, leaving the session's repositories told about different files. - pub(super) reload_lock: std::sync::Mutex<()>, } pub struct ViewerServer { @@ -92,107 +46,52 @@ pub struct ViewerServer { addr: SocketAddr, } -/// Everything [`ViewerServer::start`] needs. A struct rather than a parameter -/// list: the server is configured from three unrelated places (`[web_viewer]`, -/// `[agent_indicator]`, the CLI), and positional arguments of the same types -/// were becoming easy to transpose silently. +/// Inputs for a server whose authentication has already been constructed. pub struct ViewerOptions { pub bind: IpAddr, pub port: u16, pub auth: Auth, - /// Absolute repository paths seeding the catalog; may be empty. - pub repos: Vec, - /// Mirror catalog changes into the shared workspace file (headless - /// `serve` only — alongside the TUI, the TUI owns that file). + pub hot: crate::config::AgentIndicatorConfig, + pub session: SessionOptions, +} + +/// Named configuration boundary used by the daemon startup path. +pub struct ViewerLaunch<'a> { + pub viewer: &'a crate::config::WebViewerConfig, + pub agent_indicator: &'a crate::config::AgentIndicatorConfig, + pub theme: &'a crate::config::ThemeConfig, + pub shell: &'a crate::config::ShellConfig, + pub paths: &'a [String], pub persist: bool, pub startup_commands: Vec, - /// The `--exec` commands already merged into `startup_commands`, remembered - /// so a config reload can arrive at the same combined list. Empty for every - /// caller that has none. pub cli_startup: Vec, - pub shell: crate::config::ShellConfig, - pub hot: crate::config::AgentIndicatorConfig, - pub prefs: PrefsStore, + pub plugins: Vec, } impl ViewerState { - /// The served repositories, for a transport that needs to reach their - /// runtimes directly rather than through a route. - pub fn catalog(&self) -> &crate::web::viewer::catalog::Catalog { - &self.catalog + pub fn session(&self) -> &SessionState { + &self.session } - /// Build the served session without binding anything. - /// - /// Separate from [`ViewerServer::start`] because the session exists whether - /// or not a browser listener does: the daemon socket serves this same - /// state, and a test drives it without taking a port. - pub fn new(options: ViewerOptions) -> Self { - Self::with_plugins(options, Vec::new()) - } - - /// Like [`ViewerState::new`], with the `[[plugin]]` table the session's - /// startup panes may hand themselves to. - /// - /// Taken here rather than as a [`ViewerOptions`] field because it has to - /// reach the catalog before [`crate::web::viewer::catalog::Catalog::set_paths`] - /// spawns the first hub — a plugin association is decided when a pane is - /// created, not afterwards. pub fn with_plugins(options: ViewerOptions, plugins: Vec) -> Self { - let ViewerOptions { - bind, - port: _, - auth, - repos, - persist, - startup_commands, - cli_startup, - shell, - hot, - prefs, - } = options; - let state = Self { - catalog: crate::web::viewer::catalog::Catalog::with_startup_plugins_exec_and_shell( - startup_commands, - plugins, - cli_startup, - shell, - ), - bound_loopback: bind.is_loopback(), - auth, + Self { + bound_loopback: options.bind.is_loopback(), + auth: options.auth, sessions: SessionStore::new(), limiter: RateLimiter::new(), connections: Arc::new(AtomicUsize::new(0)), + hot: options.hot, clones: Default::default(), git_available: crate::git::clone::git_available(), - reload_lock: std::sync::Mutex::new(()), - persist, - hot, - prefs, - }; - state.catalog.set_paths(&repos); - state + session: Arc::new(SessionState::with_plugins(options.session, plugins)), + } } } impl ViewerServer { - /// Bind and start from `[web_viewer]`, building the password verifier from - /// either `hashed_password` or `password`. - /// - /// `plugins` is `config.toml`'s `[[plugin]]` table; an empty list means no - /// pane can be plugin-managed, which is the default. - #[allow(clippy::too_many_arguments)] - pub fn start_from_config( - viewer: &crate::config::WebViewerConfig, - agent_indicator: &crate::config::AgentIndicatorConfig, - theme: &crate::config::ThemeConfig, - shell: &crate::config::ShellConfig, - paths: &[String], - persist: bool, - startup_commands: Vec, - cli_startup: Vec, - plugins: Vec, - ) -> Result { + /// Build the password verifier, session, listener, and accept thread. + pub fn start_from_config(launch: ViewerLaunch<'_>) -> Result { + let viewer = launch.viewer; let auth = if let Some(hash) = viewer.hashed_password.as_deref() { Auth::from_hashed(hash)? } else if let Some(password) = viewer.password.as_deref().filter(|p| !p.is_empty()) { @@ -211,40 +110,36 @@ impl ViewerServer { bind, port: viewer.port, auth, - repos: paths.to_vec(), - persist, - startup_commands, - cli_startup, - shell: shell.clone(), - hot: agent_indicator.clone(), - // The session's accent outlives any one config edit, so `[theme]` - // only names the colour a session with no stored choice starts in. - prefs: PrefsStore::load_seeded(theme.preset_index()), + hot: launch.agent_indicator.clone(), + session: SessionOptions { + repos: launch.paths.to_vec(), + persist: launch.persist, + startup_commands: launch.startup_commands, + cli_startup: launch.cli_startup, + shell: launch.shell.clone(), + prefs: PrefsStore::load_seeded(launch.theme.preset_index()), + status_encoder: crate::web::viewer::status_payload::encode, + }, }, - plugins, + launch.plugins, ) } - /// Bind and start accepting. The seeded repositories may be replaced later - /// through [`ViewerServer::set_repos`]. + #[cfg(test)] pub fn start(options: ViewerOptions) -> Result { Self::start_with_plugins(options, Vec::new()) } - /// Like [`ViewerServer::start`], with the `[[plugin]]` table. pub fn start_with_plugins( options: ViewerOptions, plugins: Vec, ) -> Result { - // Copied out before the options are consumed: the listener is bound - // first so a port conflict fails before any repository is opened. let (bind, port) = (options.bind, options.port); let listener = TcpListener::bind((bind, port)) .with_context(|| format!("binding viewer server to {bind}:{port}"))?; let addr = listener .local_addr() .unwrap_or_else(|_| SocketAddr::new(bind, port)); - let state = Arc::new(ViewerState::with_plugins(options, plugins)); let accept_state = Arc::clone(&state); @@ -260,20 +155,12 @@ impl ViewerServer { self.addr } - /// The session this server serves, so another transport can serve the same - /// one. Shared rather than copied: one session is the whole point. - pub fn state(&self) -> Arc { - Arc::clone(&self.state) - } - - /// Replace the served repositories. Safe to call from the TUI main loop on - /// every tab open/close: unchanged paths keep their runtimes and clients. - pub fn set_repos(&self, paths: &[String]) { - self.state.catalog.set_paths(paths); + pub fn session_state(&self) -> Arc { + Arc::clone(&self.state.session) } pub fn shutdown(&self) { - self.state.catalog.shutdown(); + self.state.session.shutdown(); } } diff --git a/src/web/viewer/server/mutations.rs b/src/web/viewer/server/mutations.rs index ea51bfb7..c4c7628a 100644 --- a/src/web/viewer/server/mutations.rs +++ b/src/web/viewer/server/mutations.rs @@ -1,304 +1,21 @@ -use super::ViewerState; -use super::http_util::{json_error, json_response}; -use crate::web::common::http::RequestHead; -use crate::web::viewer::catalog::RepoEntry; -use crate::web::viewer::dto::Envelope; -use crate::web::viewer::prefs::{MaximizedPanel, MaximizedUpdate, PrefsUpdate}; -use crate::web::viewer::session; -use anyhow::Result; -use std::sync::Arc; - -#[derive(serde::Deserialize)] -struct OpenRequest { - path: String, -} - -#[derive(serde::Deserialize)] -struct ReorderRequest { - order: Vec, -} - -#[derive(serde::Deserialize)] -struct PrefsRequest { - /// Each preference is optional so one write touches one setting and leaves - /// the rest as they are; a body naming none is rejected rather than treated - /// as a silent no-op. - accent: Option, - sidebar_width: Option, - upper_pct: Option, - /// Repo **id**, as every other client-supplied repository reference is. - /// The server translates it to the path `prefs.rs` stores. - active_repo: Option, - /// How one project's screen is arranged. Names its own repository rather - /// than riding on `active_repo`: maximizing is about the project the client - /// is looking at, and inferring that from a second field would tie two - /// writes together that a client may well send separately. - maximized: Option, -} - -#[derive(serde::Deserialize)] -struct MaximizedRequest { - /// Repo id, translated to a path before it is stored. - repo: String, - /// `"files"`, `"terminal"`, or absent/null for nothing maximized. Absent - /// and "nothing" mean the same here, unlike in the enclosing request, where - /// absent means the write said nothing about maximizing at all. - panel: Option, -} +mod filesystem; +mod lookup; +mod preferences; +mod reload; +mod repository; + +pub(super) use filesystem::handle_mkdir; +pub(super) use lookup::{lookup_repo, redact}; +pub(super) use preferences::handle_set_prefs; +pub(super) use reload::handle_reload_config; +pub(super) use repository::{handle_close_repo, handle_open_repo, handle_reorder_repos}; -/// Store one or more viewer preferences and echo back the full stored set. -/// -/// A value with a range is wrapped or clamped into it rather than rejected — an -/// accent index past the end of the cycle gets a colour back (as the TUI does -/// with `Accent::from_index`), and a width past the bounds gets a usable split -/// back — so a client that drifts out of range self-corrects from the response. -/// `active_repo` is the exception: it names a repository rather than sitting in -/// a range, and there is no nearest valid project to fold an unknown id onto. -pub(super) fn handle_set_prefs(body: &str, state: &ViewerState) -> Vec { - let request: PrefsRequest = match serde_json::from_str(body) { - Ok(request) => request, - Err(_) => { - return json_error( - "400 Bad Request", - "expected a JSON body with a preference to store", - ); - } - }; - if request.accent.is_none() - && request.sidebar_width.is_none() - && request.upper_pct.is_none() - && request.active_repo.is_none() - && request.maximized.is_none() - { - return json_error("400 Bad Request", "no known preference in the body"); - } - // Resolved before the write, because what is stored is the path behind the - // id. An id no longer in the catalog is rejected rather than dropped: the - // only way to send one is to have raced a close on another device, and - // storing nothing while answering 200 would claim a selection was kept. - let active_path = match request.active_repo { - Some(id) => match state.catalog.get(&id) { - Some(entry) => Some(entry.path.clone()), - None => return json_error("400 Bad Request", "unknown repo"), - }, - None => None, - }; - // Resolved before the write for the same reason, and refused the same way: - // a panel this server cannot render is a client that has drifted, and - // storing nothing behind a 200 would claim an arrangement was kept. - let maximized = match request.maximized { - Some(change) => { - let Some(entry) = state.catalog.get(&change.repo) else { - return json_error("400 Bad Request", "unknown repo"); - }; - let panel = match change.panel.as_deref() { - None => None, - Some(name) => match MaximizedPanel::parse(name) { - Some(panel) => Some(panel), - None => return json_error("400 Bad Request", "unknown panel"), - }, - }; - Some(MaximizedUpdate { - repo: entry.path.clone(), - panel, - }) - } - None => None, - }; - // One locked write for whatever the body carried, so a request naming - // several preferences lands atomically rather than as racing updates. - let stored = state.prefs.update(PrefsUpdate { - accent: request.accent, - sidebar_width: request.sidebar_width, - upper_pct: request.upper_pct, - active_repo: active_path, - maximized, - }); - // One snapshot for both, as the bootstrap does: the two are read together - // by the client, and an id in one that is missing from the other describes - // a served set that never existed. - let served = state - .catalog - .list_with_active(stored.active_repo.as_deref(), &stored.maximized); - match serde_json::to_string(&Envelope::new(serde_json::json!({ - "accent": stored.accent, - "sidebar_width": stored.sidebar_width, - "upper_pct": stored.upper_pct, - "active_repo": served.active, - "maximized": served.maximized, - }))) { - Ok(json) => json_response("200 OK", &json, &[]), - Err(_) => json_error("500 Internal Server Error", "could not encode preferences"), - } -} - -/// Open a repository from the browser and add it to the served catalog. -/// -/// The path is user-supplied but the response is public, so a bad path yields a -/// generic message rather than echoing what was tried. -pub(super) fn handle_open_repo(body: &str, state: &ViewerState) -> Vec { - let request: OpenRequest = match serde_json::from_str(body) { - Ok(request) => request, - Err(_) => return json_error("400 Bad Request", "expected a JSON body with a path"), - }; - match session::open_repo(state, &request.path) { - Ok(repo) => { - match serde_json::to_string(&Envelope::new(serde_json::json!({ "repo": repo }))) { - Ok(json) => json_response("200 OK", &json, &[]), - Err(_) => json_error("500 Internal Server Error", "could not encode repository"), - } - } - Err(session::OpenError::EmptyPath) => json_error("400 Bad Request", "a path is required"), - Err(session::OpenError::NotADirectory) => { - json_error("400 Bad Request", "no such directory") - } - Err(session::OpenError::TooMany) => json_error( - "409 Conflict", - "the maximum number of repositories is already open", - ), - } -} - -#[derive(serde::Deserialize)] -struct MkdirRequest { - /// The directory to create the new folder inside — the one the picker is - /// currently showing. - path: String, - /// The new folder's name. Must be a single plain path segment. - name: String, -} - -/// Create a new folder inside a directory the picker is browsing. -/// -/// The parent is confined only as much as `browse` is (any directory an -/// authenticated user can already reach), but `name` is held to a single plain -/// segment: separators, `..`, a leading `.` (which also rules out `.git` and the -/// hidden entries the picker never lists), and NUL are all rejected. Combined -/// with canonicalizing the parent first, the created folder can only ever land -/// directly under the browsed directory, never escape it via a symlink or `..`. -pub(super) fn handle_mkdir(body: &str) -> Vec { - let request: MkdirRequest = match serde_json::from_str(body) { - Ok(request) => request, - Err(_) => { - return json_error( - "400 Bad Request", - "expected a JSON body with a path and a name", - ); - } - }; - let name = request.name.trim(); - if name.is_empty() { - return json_error("400 Bad Request", "a folder name is required"); - } - // A single plain segment only. This — not the parent — is what keeps the - // create confined: no traversal, no separators, no hidden/.git, no NUL. - if name.starts_with('.') || name.contains('/') || name.contains('\\') || name.contains('\0') { - return json_error("400 Bad Request", "invalid folder name"); - } - let parent = crate::platform::paths::expand_tilde(request.path.trim()); - // is_dir() follows symlinks and is false for a missing path — the same gate - // `open` uses for the directory it is handed. - if !parent.is_dir() { - return json_error("400 Bad Request", "no such directory"); - } - // Canonicalize first so a symlink in the supplied path cannot redirect the - // join; the validated single-segment name then stays under the real parent. - let base = match parent.canonicalize() { - Ok(base) => base, - Err(err) => return redact("mkdir canonicalize", &anyhow::Error::new(err)), - }; - let target = base.join(name); - match std::fs::create_dir(&target) { - Ok(()) => { - let path = crate::platform::paths::for_display(&target).into_owned(); - match serde_json::to_string(&Envelope::new(serde_json::json!({ "path": path }))) { - Ok(json) => json_response("200 OK", &json, &[]), - Err(_) => json_error("500 Internal Server Error", "could not encode the folder"), - } - } - Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => { - json_error("409 Conflict", "a folder with that name already exists") - } - Err(err) => redact("mkdir", &anyhow::Error::new(err)), - } -} - -/// Close a repository named by the `repo` id and return the updated set. -/// Idempotent from the client's view: an unknown id is a 404, a known one is -/// removed and its runtime/terminals stopped by the catalog rebuild. -pub(super) fn handle_close_repo(head: &RequestHead, state: &ViewerState) -> Vec { - let Some(id) = head.query_param("repo") else { - return json_error("400 Bad Request", "missing repo parameter"); - }; - match session::close_repo(state, &id) { - Ok(()) => { - let repos = session::list_repos(state); - match serde_json::to_string(&Envelope::new(serde_json::json!({ "repos": repos }))) { - Ok(json) => json_response("200 OK", &json, &[]), - Err(_) => json_error("500 Internal Server Error", "could not encode repositories"), - } - } - Err(session::CloseError::UnknownRepo) => json_error("404 Not Found", "unknown repository"), - } -} +use super::http_util::{json_error, json_response}; -pub(super) fn handle_reorder_repos(body: &str, state: &ViewerState) -> Vec { - let request: ReorderRequest = match serde_json::from_str(body) { - Ok(request) => request, - Err(_) => return json_error("400 Bad Request", "expected a JSON body with an order"), - }; - session::reorder_repos(state, &request.order); - let repos = session::list_repos(state); - match serde_json::to_string(&Envelope::new(serde_json::json!({ "repos": repos }))) { +/// Serialize one successful mutation response through the viewer envelope. +fn encode_response(payload: T, error: &'static str) -> Vec { + match super::http_util::encode(&payload) { Ok(json) => json_response("200 OK", &json, &[]), - Err(_) => json_error("500 Internal Server Error", "could not encode repositories"), - } -} - -/// Re-read `config.toml` and report what was applied. -/// -/// The body is ignored and no configuration is accepted from the request: the -/// file on the server's disk is what is read, so a browser cannot reconfigure the -/// session from something it made up. Deciding who may ask happened before this — -/// the route is behind the same session cookie as every other mutation. -/// -/// A refusal is the operator's own message, not a redacted one. These name the -/// offending key in their own config file, which is the whole value of showing it; -/// they carry no repository contents, no filesystem layout beyond the config path -/// the operator already knows, and no credential — the web password is never part -/// of what a reload reports. -pub(super) fn handle_reload_config(state: &ViewerState) -> Vec { - match crate::web::viewer::reload::reload_config(state) { - Ok(report) => { - let body = Envelope::new(serde_json::json!({ "summary": report.summary() })); - match serde_json::to_string(&body) { - Ok(json) => json_response("200 OK", &json, &[]), - Err(_) => json_error("500 Internal Server Error", "could not encode the report"), - } - } - // 422 rather than 400: the request was well-formed, and what could not be - // processed is the file it points the server at. - Err(err) => json_error("422 Unprocessable Entity", &err.to_string()), + Err(_) => json_error("500 Internal Server Error", error), } } - -/// Resolve the `repo` parameter to an entry, or produce the 404 response. -pub(super) fn lookup_repo( - head: &RequestHead, - state: &ViewerState, -) -> Result, Vec> { - let id = head - .query_param("repo") - .ok_or_else(|| json_error("400 Bad Request", "missing repo parameter"))?; - state - .catalog - .get(&id) - .ok_or_else(|| json_error("404 Not Found", "unknown repository")) -} - -/// Map an internal error to a fixed public message, logging the detail. -/// git and io errors name absolute paths, symlink targets, and file sizes. -pub(super) fn redact(context: &str, err: &anyhow::Error) -> Vec { - tracing::debug!(%err, context, "viewer: request failed"); - json_error("400 Bad Request", "request could not be served") -} diff --git a/src/web/viewer/server/mutations/filesystem.rs b/src/web/viewer/server/mutations/filesystem.rs new file mode 100644 index 00000000..02df491a --- /dev/null +++ b/src/web/viewer/server/mutations/filesystem.rs @@ -0,0 +1,57 @@ +use super::super::http_util::json_error; +use super::lookup::redact; + +#[derive(serde::Deserialize)] +struct MkdirRequest { + /// The directory to create the new folder inside. + path: String, + /// The new folder's name. Must be a single plain path segment. + name: String, +} + +/// Create a new folder inside a directory the picker is browsing. +/// +/// The parent is confined only as much as `browse` is, but `name` is held to a +/// single plain segment: separators, `..`, a leading `.` (which also rules out +/// `.git`), and NUL are all rejected. Combined with canonicalizing the parent +/// first, the created folder can only ever land directly under the browsed +/// directory. +pub(in crate::web::viewer::server) fn handle_mkdir(body: &str) -> Vec { + let request: MkdirRequest = match serde_json::from_str(body) { + Ok(request) => request, + Err(_) => { + return json_error( + "400 Bad Request", + "expected a JSON body with a path and a name", + ); + } + }; + let name = request.name.trim(); + if name.is_empty() { + return json_error("400 Bad Request", "a folder name is required"); + } + if name.starts_with('.') || name.contains('/') || name.contains('\\') || name.contains('\0') { + return json_error("400 Bad Request", "invalid folder name"); + } + let parent = crate::platform::paths::expand_tilde(request.path.trim()); + if !parent.is_dir() { + return json_error("400 Bad Request", "no such directory"); + } + let base = match parent.canonicalize() { + Ok(base) => base, + Err(err) => return redact("mkdir canonicalize", &anyhow::Error::new(err)), + }; + let target = base.join(name); + match std::fs::create_dir(&target) { + Ok(()) => super::encode_response( + serde_json::json!({ + "path": crate::platform::paths::for_display(&target).into_owned() + }), + "could not encode the folder", + ), + Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => { + json_error("409 Conflict", "a folder with that name already exists") + } + Err(err) => redact("mkdir", &anyhow::Error::new(err)), + } +} diff --git a/src/web/viewer/server/mutations/lookup.rs b/src/web/viewer/server/mutations/lookup.rs new file mode 100644 index 00000000..1890916d --- /dev/null +++ b/src/web/viewer/server/mutations/lookup.rs @@ -0,0 +1,28 @@ +use super::super::ViewerState; +use super::super::http_util::json_error; +use crate::session::catalog::RepoEntry; +use crate::web::common::http::RequestHead; +use anyhow::Result; +use std::sync::Arc; + +/// Resolve the `repo` parameter to an entry, or produce the 404 response. +pub(in crate::web::viewer::server) fn lookup_repo( + head: &RequestHead, + state: &ViewerState, +) -> Result, Vec> { + let id = head + .query_param("repo") + .ok_or_else(|| json_error("400 Bad Request", "missing repo parameter"))?; + state + .session + .catalog() + .get(&id) + .ok_or_else(|| json_error("404 Not Found", "unknown repository")) +} + +/// Map an internal error to a fixed public message, logging the detail. +/// Git and I/O errors may name absolute paths, symlink targets, and file sizes. +pub(in crate::web::viewer::server) fn redact(context: &str, err: &anyhow::Error) -> Vec { + tracing::debug!(%err, context, "viewer: request failed"); + json_error("400 Bad Request", "request could not be served") +} diff --git a/src/web/viewer/server/mutations/preferences.rs b/src/web/viewer/server/mutations/preferences.rs new file mode 100644 index 00000000..098f5cca --- /dev/null +++ b/src/web/viewer/server/mutations/preferences.rs @@ -0,0 +1,106 @@ +use super::super::ViewerState; +use super::super::http_util::json_error; +use crate::session::prefs::{MaximizedPanel, MaximizedUpdate, PrefsUpdate}; + +#[derive(serde::Deserialize)] +struct PrefsRequest { + /// Each preference is optional so one write touches one setting and leaves + /// the rest as they are. + accent: Option, + sidebar_width: Option, + upper_pct: Option, + /// Repo **id**, as every other client-supplied repository reference is. + /// The server translates it to the path `prefs.rs` stores. + active_repo: Option, + /// How one project's screen is arranged. Names its own repository rather + /// than riding on `active_repo`: maximizing is about the project the client + /// is looking at. + maximized: Option, +} + +#[derive(serde::Deserialize)] +struct MaximizedRequest { + /// Repo id, translated to a path before it is stored. + repo: String, + /// `"files"`, `"terminal"`, or absent/null for nothing maximized. + panel: Option, +} + +/// Store one or more viewer preferences and echo back the full stored set. +/// +/// A value with a range is wrapped or clamped into it rather than rejected. +/// `active_repo` is the exception: it names a repository rather than sitting in +/// a range, and there is no nearest valid project to fold an unknown id onto. +pub(in crate::web::viewer::server) fn handle_set_prefs(body: &str, state: &ViewerState) -> Vec { + let request: PrefsRequest = match serde_json::from_str(body) { + Ok(request) => request, + Err(_) => { + return json_error( + "400 Bad Request", + "expected a JSON body with a preference to store", + ); + } + }; + if request.accent.is_none() + && request.sidebar_width.is_none() + && request.upper_pct.is_none() + && request.active_repo.is_none() + && request.maximized.is_none() + { + return json_error("400 Bad Request", "no known preference in the body"); + } + + let active_path = match request.active_repo { + Some(id) => match state.session.catalog().get(&id) { + Some(entry) => Some(entry.path.clone()), + None => return json_error("400 Bad Request", "unknown repo"), + }, + None => None, + }; + let maximized = match request.maximized { + Some(change) => { + let Some(entry) = state.session.catalog().get(&change.repo) else { + return json_error("400 Bad Request", "unknown repo"); + }; + let panel = match change.panel.as_deref() { + None => None, + Some(name) => match MaximizedPanel::parse(name) { + Some(panel) => Some(panel), + None => return json_error("400 Bad Request", "unknown panel"), + }, + }; + Some(MaximizedUpdate { + repo: entry.path.clone(), + panel, + }) + } + None => None, + }; + + let stored = state.session.prefs().update(PrefsUpdate { + accent: request.accent, + sidebar_width: request.sidebar_width, + upper_pct: request.upper_pct, + active_repo: active_path, + maximized, + }); + let served = state + .session + .catalog() + .list_with_active(stored.active_repo.as_deref(), &stored.maximized); + let maximized: std::collections::HashMap<_, _> = served + .maximized + .into_iter() + .map(|(id, panel)| (id, panel.as_str())) + .collect(); + super::encode_response( + serde_json::json!({ + "accent": stored.accent, + "sidebar_width": stored.sidebar_width, + "upper_pct": stored.upper_pct, + "active_repo": served.active, + "maximized": maximized, + }), + "could not encode preferences", + ) +} diff --git a/src/web/viewer/server/mutations/reload.rs b/src/web/viewer/server/mutations/reload.rs new file mode 100644 index 00000000..0377ea75 --- /dev/null +++ b/src/web/viewer/server/mutations/reload.rs @@ -0,0 +1,20 @@ +use super::super::ViewerState; +use super::super::http_util::json_error; + +/// Re-read `config.toml` and report what was applied. +/// +/// The body is ignored and no configuration is accepted from the request: the +/// file on the server's disk is what is read. Deciding who may ask happened +/// before this — the route is behind the same session cookie as every other +/// mutation. +pub(in crate::web::viewer::server) fn handle_reload_config(state: &ViewerState) -> Vec { + match crate::session::reload::reload_config(state.session()) { + Ok(report) => super::encode_response( + serde_json::json!({ "summary": report.summary() }), + "could not encode the report", + ), + // A refusal is the operator's own message and names the offending key + // in their config. The request was valid; the on-disk file was not. + Err(err) => json_error("422 Unprocessable Entity", &err.to_string()), + } +} diff --git a/src/web/viewer/server/mutations/repository.rs b/src/web/viewer/server/mutations/repository.rs new file mode 100644 index 00000000..c6bfab5f --- /dev/null +++ b/src/web/viewer/server/mutations/repository.rs @@ -0,0 +1,76 @@ +use super::super::ViewerState; +use super::super::http_util::json_error; +use crate::session; +use crate::web::common::http::RequestHead; + +#[derive(serde::Deserialize)] +struct OpenRequest { + path: String, +} + +#[derive(serde::Deserialize)] +struct ReorderRequest { + order: Vec, +} + +/// Open a repository from the browser and add it to the served catalog. +/// +/// The path is user-supplied but the response is public, so a bad path yields a +/// generic message. +pub(in crate::web::viewer::server) fn handle_open_repo(body: &str, state: &ViewerState) -> Vec { + let request: OpenRequest = match serde_json::from_str(body) { + Ok(request) => request, + Err(_) => return json_error("400 Bad Request", "expected a JSON body with a path"), + }; + match session::open_repo(state.session(), &request.path) { + Ok(repo) => super::encode_response( + serde_json::json!({ "repo": crate::web::viewer::dto::RepoDto::from(repo) }), + "could not encode repository", + ), + Err(session::OpenError::EmptyPath) => json_error("400 Bad Request", "a path is required"), + Err(session::OpenError::NotADirectory) => { + json_error("400 Bad Request", "no such directory") + } + Err(session::OpenError::TooMany) => json_error( + "409 Conflict", + "the maximum number of repositories is already open", + ), + } +} + +/// Close a repository named by the `repo` id and return the updated set. +pub(in crate::web::viewer::server) fn handle_close_repo( + head: &RequestHead, + state: &ViewerState, +) -> Vec { + let Some(id) = head.query_param("repo") else { + return json_error("400 Bad Request", "missing repo parameter"); + }; + match session::close_repo(state.session(), &id) { + Ok(()) => encode_repos(state), + Err(session::CloseError::UnknownRepo) => json_error("404 Not Found", "unknown repository"), + } +} + +pub(in crate::web::viewer::server) fn handle_reorder_repos( + body: &str, + state: &ViewerState, +) -> Vec { + let request: ReorderRequest = match serde_json::from_str(body) { + Ok(request) => request, + Err(_) => return json_error("400 Bad Request", "expected a JSON body with an order"), + }; + session::reorder_repos(state.session(), &request.order); + encode_repos(state) +} + +fn encode_repos(state: &ViewerState) -> Vec { + let repos: Vec = session::list_repos(state.session()) + .into_iter() + .map(Into::into) + .collect(); + super::encode_response( + serde_json::json!({ "repos": repos }), + "could not encode repositories", + ) +} diff --git a/src/web/viewer/server/routes.rs b/src/web/viewer/server/routes.rs index 4257af82..99318a42 100644 --- a/src/web/viewer/server/routes.rs +++ b/src/web/viewer/server/routes.rs @@ -24,22 +24,27 @@ pub(super) fn route(head: &RequestHead, state: &ViewerState) -> Vec { // already polls it every few seconds, so a setting changed here // reaches every device within one interval, and `/api/status` — // a hot, deduplicated stream — stays free of configuration. - let prefs = state.prefs.get(); + let prefs = state.session.prefs().get(); // The remembered project is resolved to an id per response rather // than stored as one, and from the same snapshot as the list it // will be rendered against — see `Catalog::list_with_active`. let served = state - .catalog + .session + .catalog() .list_with_active(prefs.active_repo.as_deref(), &prefs.maximized); let bootstrap = ViewerBootstrapDto::new( - served.list, + served.list.into_iter().map(Into::into).collect(), HotConfigDto { enabled: state.hot.enabled, window_secs: state.hot.hot_window_secs, }, &prefs, served.active, - served.maximized, + served + .maximized + .into_iter() + .map(|(id, panel)| (id, panel.as_str())) + .collect(), state.git_available, ); match serde_json::to_string(&Envelope::new(bootstrap)) { diff --git a/src/web/viewer/server/tests/commit_routes.rs b/src/web/viewer/server/tests/commit_routes.rs index 8bd39988..ba41c653 100644 --- a/src/web/viewer/server/tests/commit_routes.rs +++ b/src/web/viewer/server/tests/commit_routes.rs @@ -52,7 +52,7 @@ fn commit_file_diff_returns_only_the_selected_path() { fn commit_file_diff_allows_a_deleted_path_without_worktree_lookup() { let (dir, server, token, id) = seeded_server(); let repo_path = { - let entry = server.state.catalog.get(&id).unwrap(); + let entry = server.state.session.catalog().get(&id).unwrap(); entry.path.clone() }; let gone = std::path::Path::new(&repo_path).join("gone.txt"); @@ -112,7 +112,7 @@ fn diff_returns_hunks_for_a_changed_file() { let (dir, server, token, id) = seeded_server(); // Mutate the committed file so a worktree diff exists. let repo_path = { - let entry = server.state.catalog.get(&id).unwrap(); + let entry = server.state.session.catalog().get(&id).unwrap(); entry.path.clone() }; std::fs::write( diff --git a/src/web/viewer/server/tests/mod.rs b/src/web/viewer/server/tests/mod.rs index 1ff8dc2b..cdd2556e 100644 --- a/src/web/viewer/server/tests/mod.rs +++ b/src/web/viewer/server/tests/mod.rs @@ -9,9 +9,9 @@ mod routes; mod terminals; use super::{VIEWER_SESSION_COOKIE, ViewerOptions, ViewerServer}; +use crate::session::prefs::PrefsStore; use crate::test_util::{make_repo, run_git}; use crate::web::common::auth::Auth; -use crate::web::viewer::prefs::PrefsStore; use std::io::{Read, Write}; use std::net::{SocketAddr, TcpStream}; use std::time::Duration; @@ -41,15 +41,16 @@ pub(super) fn server_with( bind: "127.0.0.1".parse().unwrap(), port: 0, auth: Auth::from_plaintext("swordfish").unwrap(), - repos: paths.to_vec(), - // Never persist from tests — they must not touch the real - // ~/.nightcrow/workspace.json. - persist: false, - startup_commands: Vec::new(), - cli_startup: Vec::new(), - shell: crate::config::ShellConfig::default(), hot, - prefs, + session: crate::session::SessionOptions { + repos: paths.to_vec(), + persist: false, + startup_commands: Vec::new(), + cli_startup: Vec::new(), + shell: crate::config::ShellConfig::default(), + prefs, + status_encoder: crate::web::viewer::status_payload::encode, + }, }) .unwrap() } diff --git a/src/web/viewer/server/tests/prefs.rs b/src/web/viewer/server/tests/prefs.rs index 56ac12ca..bc12b9d8 100644 --- a/src/web/viewer/server/tests/prefs.rs +++ b/src/web/viewer/server/tests/prefs.rs @@ -1,198 +1,10 @@ -use super::{body_of, get, login, post, server_with}; +use super::{ViewerServer, body_of, get, login, post, server_with}; -#[test] -fn a_stored_accent_is_served_to_every_later_client() { - // The point of storing it server-side: a second device (a second - // request, here) sees the choice without having made it. - let dir = tempfile::TempDir::new().unwrap(); - let server = server_with( - &[], - crate::config::AgentIndicatorConfig::default(), - Some(dir.path()), - ); - let token = login(server.addr()); - - let stored = post(server.addr(), "/api/prefs", "{\"accent\":3}", Some(&token)); - assert!(stored.starts_with("HTTP/1.1 200"), "got: {stored}"); - - let list = get(server.addr(), "/api/repos", Some(&token)); - let value: serde_json::Value = serde_json::from_str(body_of(&list)).unwrap(); - assert_eq!(value["accent"], 3); -} - -#[test] -fn a_stored_sidebar_width_is_clamped_and_served_to_every_later_client() { - let dir = tempfile::TempDir::new().unwrap(); - let server = server_with( - &[], - crate::config::AgentIndicatorConfig::default(), - Some(dir.path()), - ); - let token = login(server.addr()); - - // Past the ceiling: the write echoes the clamped value, and a later - // client's bootstrap carries the same clamped width — not the raw ask. - let stored = post( - server.addr(), - "/api/prefs", - "{\"sidebar_width\":5000}", - Some(&token), - ); - let echoed: serde_json::Value = serde_json::from_str(body_of(&stored)).unwrap(); - assert_eq!( - echoed["sidebar_width"], - crate::web::viewer::prefs::MAX_SIDEBAR_WIDTH - ); - - let list = get(server.addr(), "/api/repos", Some(&token)); - let value: serde_json::Value = serde_json::from_str(body_of(&list)).unwrap(); - assert_eq!( - value["sidebar_width"], - crate::web::viewer::prefs::MAX_SIDEBAR_WIDTH - ); -} - -#[test] -fn a_stored_upper_pct_is_clamped_and_served_to_every_later_client() { - let dir = tempfile::TempDir::new().unwrap(); - let server = server_with( - &[], - crate::config::AgentIndicatorConfig::default(), - Some(dir.path()), - ); - let token = login(server.addr()); - - // Past the ceiling: the write echoes the clamped percentage, and a later - // client's bootstrap opens at the same split rather than the raw ask. - let stored = post( - server.addr(), - "/api/prefs", - "{\"upper_pct\":99}", - Some(&token), - ); - let echoed: serde_json::Value = serde_json::from_str(body_of(&stored)).unwrap(); - assert_eq!( - echoed["upper_pct"], - crate::web::viewer::prefs::MAX_UPPER_PCT - ); - - let list = get(server.addr(), "/api/repos", Some(&token)); - let value: serde_json::Value = serde_json::from_str(body_of(&list)).unwrap(); - assert_eq!(value["upper_pct"], crate::web::viewer::prefs::MAX_UPPER_PCT); -} - -#[test] -fn mkdir_creates_a_folder_inside_the_browsed_directory() { - let dir = tempfile::TempDir::new().unwrap(); - let server = server_with( - &[], - crate::config::AgentIndicatorConfig::default(), - Some(dir.path()), - ); - let token = login(server.addr()); - - let body = serde_json::json!({ - "path": dir.path().to_str().unwrap(), - "name": "scratch", - }) - .to_string(); - let created = post(server.addr(), "/api/mkdir", &body, Some(&token)); - assert!(created.starts_with("HTTP/1.1 200"), "got: {created}"); - assert!( - dir.path().join("scratch").is_dir(), - "the folder must exist on disk" - ); - - let value: serde_json::Value = serde_json::from_str(body_of(&created)).unwrap(); - let path = value["path"].as_str().unwrap(); - assert!( - std::path::Path::new(path).ends_with("scratch"), - "the response names the new folder, got: {path}" - ); -} - -#[test] -fn mkdir_rejects_a_name_that_would_escape_the_browsed_directory() { - let dir = tempfile::TempDir::new().unwrap(); - let server = server_with( - &[], - crate::config::AgentIndicatorConfig::default(), - Some(dir.path()), - ); - let token = login(server.addr()); - - // Separators, traversal, and a leading dot (which also covers `.git` - // and hidden entries) are all refused so the create cannot leave the - // browsed directory. - for name in ["../escape", "a/b", "..", ".git", ".hidden"] { - let body = serde_json::json!({ - "path": dir.path().to_str().unwrap(), - "name": name, - }) - .to_string(); - let response = post(server.addr(), "/api/mkdir", &body, Some(&token)); - assert!( - response.starts_with("HTTP/1.1 400"), - "name {name:?} must be rejected, got: {response}" - ); - } - assert!( - !dir.path().parent().unwrap().join("escape").exists(), - "traversal must not create anything above the parent" - ); -} - -#[test] -fn mkdir_requires_authentication() { - let dir = tempfile::TempDir::new().unwrap(); - let server = server_with( - &[], - crate::config::AgentIndicatorConfig::default(), - Some(dir.path()), - ); - - let body = serde_json::json!({ - "path": dir.path().to_str().unwrap(), - "name": "nope", - }) - .to_string(); - let response = post(server.addr(), "/api/mkdir", &body, None); - assert!( - response.starts_with("HTTP/1.1 401"), - "an unauthenticated mkdir must be refused, got: {response}" - ); - assert!( - !dir.path().join("nope").exists(), - "nothing may be created without a session" - ); -} - -#[test] -fn setting_one_preference_leaves_the_other_untouched() { - let dir = tempfile::TempDir::new().unwrap(); - let server = server_with( - &[], - crate::config::AgentIndicatorConfig::default(), - Some(dir.path()), - ); - let token = login(server.addr()); - - post(server.addr(), "/api/prefs", "{\"accent\":3}", Some(&token)); - let stored = post( - server.addr(), - "/api/prefs", - "{\"sidebar_width\":500}", - Some(&token), - ); - - // The width write must not reset the accent stored a moment earlier. - let echoed: serde_json::Value = serde_json::from_str(body_of(&stored)).unwrap(); - assert_eq!(echoed["accent"], 3); - assert_eq!(echoed["sidebar_width"], 500); -} +mod arrangement; +mod mkdir; +mod preferences; -#[test] -fn a_preference_body_naming_nothing_known_is_rejected() { +fn prefs_server() -> (tempfile::TempDir, ViewerServer, String) { let dir = tempfile::TempDir::new().unwrap(); let server = server_with( &[], @@ -200,110 +12,5 @@ fn a_preference_body_naming_nothing_known_is_rejected() { Some(dir.path()), ); let token = login(server.addr()); - - let response = post(server.addr(), "/api/prefs", "{\"nope\":1}", Some(&token)); - - assert!(response.starts_with("HTTP/1.1 400"), "got: {response}"); -} - -#[test] -fn storing_a_preference_requires_authentication() { - let dir = tempfile::TempDir::new().unwrap(); - let server = server_with( - &[], - crate::config::AgentIndicatorConfig::default(), - Some(dir.path()), - ); - - let response = post(server.addr(), "/api/prefs", "{\"accent\":3}", None); - - assert!(response.starts_with("HTTP/1.1 401"), "got: {response}"); -} - -#[test] -fn a_malformed_preference_body_is_rejected_without_changing_anything() { - let dir = tempfile::TempDir::new().unwrap(); - let server = server_with( - &[], - crate::config::AgentIndicatorConfig::default(), - Some(dir.path()), - ); - let token = login(server.addr()); - - let response = post( - server.addr(), - "/api/prefs", - "{\"accent\":\"red\"}", - Some(&token), - ); - - assert!(response.starts_with("HTTP/1.1 400"), "got: {response}"); - let list = get(server.addr(), "/api/repos", Some(&token)); - let value: serde_json::Value = serde_json::from_str(body_of(&list)).unwrap(); - assert_eq!(value["accent"], 0); -} - -/// The point of the field: the arrangement outlives the page that set it. -#[test] -fn a_projects_arrangement_is_served_to_every_later_client() { - let prefs_dir = tempfile::TempDir::new().unwrap(); - let (repo_dir, repo) = crate::test_util::make_repo(); - let server = server_with( - std::slice::from_ref(&repo), - crate::config::AgentIndicatorConfig::default(), - Some(prefs_dir.path()), - ); - let token = login(server.addr()); - let list = get(server.addr(), "/api/repos", Some(&token)); - let value: serde_json::Value = serde_json::from_str(body_of(&list)).unwrap(); - let id = value["repos"][0]["id"].as_str().unwrap().to_string(); - assert_eq!(value["maximized"], serde_json::json!({}), "nothing yet"); - - let body = serde_json::json!({ "maximized": { "repo": id, "panel": "terminal" } }); - let stored = post(server.addr(), "/api/prefs", &body.to_string(), Some(&token)); - let echoed: serde_json::Value = serde_json::from_str(body_of(&stored)).unwrap(); - assert_eq!(echoed["maximized"][&id], "terminal"); - - // A later client — a refresh, or another device — opens arranged the same. - let again = get(server.addr(), "/api/repos", Some(&token)); - let value: serde_json::Value = serde_json::from_str(body_of(&again)).unwrap(); - assert_eq!(value["maximized"][&id], "terminal"); - - // And un-maximizing takes it back off, rather than storing a "none". - let body = serde_json::json!({ "maximized": { "repo": id, "panel": null } }); - let stored = post(server.addr(), "/api/prefs", &body.to_string(), Some(&token)); - let echoed: serde_json::Value = serde_json::from_str(body_of(&stored)).unwrap(); - assert_eq!(echoed["maximized"], serde_json::json!({})); - drop((repo_dir, prefs_dir)); -} - -#[test] -fn an_arrangement_naming_something_the_server_cannot_render_is_refused() { - let prefs_dir = tempfile::TempDir::new().unwrap(); - let (repo_dir, repo) = crate::test_util::make_repo(); - let server = server_with( - std::slice::from_ref(&repo), - crate::config::AgentIndicatorConfig::default(), - Some(prefs_dir.path()), - ); - let token = login(server.addr()); - let list = get(server.addr(), "/api/repos", Some(&token)); - let value: serde_json::Value = serde_json::from_str(body_of(&list)).unwrap(); - let id = value["repos"][0]["id"].as_str().unwrap().to_string(); - - // A panel that is not one of the two. Refused rather than stored, or a - // later client would be handed an arrangement it cannot apply. - let body = serde_json::json!({ "maximized": { "repo": id, "panel": "diff" } }); - let refused = post(server.addr(), "/api/prefs", &body.to_string(), Some(&token)); - assert!(refused.starts_with("HTTP/1.1 400"), "got: {refused}"); - - // A repository this session is not serving, the same way `active_repo` is. - let body = serde_json::json!({ "maximized": { "repo": "r9999", "panel": "files" } }); - let refused = post(server.addr(), "/api/prefs", &body.to_string(), Some(&token)); - assert!(refused.starts_with("HTTP/1.1 400"), "got: {refused}"); - - let again = get(server.addr(), "/api/repos", Some(&token)); - let value: serde_json::Value = serde_json::from_str(body_of(&again)).unwrap(); - assert_eq!(value["maximized"], serde_json::json!({}), "nothing stored"); - drop((repo_dir, prefs_dir)); + (dir, server, token) } diff --git a/src/web/viewer/server/tests/prefs/arrangement.rs b/src/web/viewer/server/tests/prefs/arrangement.rs new file mode 100644 index 00000000..62b20494 --- /dev/null +++ b/src/web/viewer/server/tests/prefs/arrangement.rs @@ -0,0 +1,58 @@ +use super::*; + +fn arrangement_server() -> ( + tempfile::TempDir, + tempfile::TempDir, + ViewerServer, + String, + String, +) { + let prefs_dir = tempfile::TempDir::new().unwrap(); + let (repo_dir, repo) = crate::test_util::make_repo(); + let server = server_with( + std::slice::from_ref(&repo), + crate::config::AgentIndicatorConfig::default(), + Some(prefs_dir.path()), + ); + let token = login(server.addr()); + let list = get(server.addr(), "/api/repos", Some(&token)); + let value: serde_json::Value = serde_json::from_str(body_of(&list)).unwrap(); + let id = value["repos"][0]["id"].as_str().unwrap().to_string(); + (prefs_dir, repo_dir, server, token, id) +} + +#[test] +fn a_projects_arrangement_is_served_to_later_clients() { + let (_prefs_dir, _repo_dir, server, token, id) = arrangement_server(); + let body = serde_json::json!({ "maximized": { "repo": id, "panel": "terminal" } }); + + let stored = post(server.addr(), "/api/prefs", &body.to_string(), Some(&token)); + let echoed: serde_json::Value = serde_json::from_str(body_of(&stored)).unwrap(); + assert_eq!(echoed["maximized"][&id], "terminal"); + + let again = get(server.addr(), "/api/repos", Some(&token)); + let value: serde_json::Value = serde_json::from_str(body_of(&again)).unwrap(); + assert_eq!(value["maximized"][&id], "terminal"); + + let body = serde_json::json!({ "maximized": { "repo": id, "panel": null } }); + let stored = post(server.addr(), "/api/prefs", &body.to_string(), Some(&token)); + let echoed: serde_json::Value = serde_json::from_str(body_of(&stored)).unwrap(); + assert_eq!(echoed["maximized"], serde_json::json!({})); +} + +#[test] +fn an_arrangement_naming_something_unrenderable_is_refused() { + let (_prefs_dir, _repo_dir, server, token, id) = arrangement_server(); + + for body in [ + serde_json::json!({ "maximized": { "repo": id, "panel": "diff" } }), + serde_json::json!({ "maximized": { "repo": "r9999", "panel": "files" } }), + ] { + let refused = post(server.addr(), "/api/prefs", &body.to_string(), Some(&token)); + assert!(refused.starts_with("HTTP/1.1 400"), "got: {refused}"); + } + + let again = get(server.addr(), "/api/repos", Some(&token)); + let value: serde_json::Value = serde_json::from_str(body_of(&again)).unwrap(); + assert_eq!(value["maximized"], serde_json::json!({})); +} diff --git a/src/web/viewer/server/tests/prefs/mkdir.rs b/src/web/viewer/server/tests/prefs/mkdir.rs new file mode 100644 index 00000000..ae0de78f --- /dev/null +++ b/src/web/viewer/server/tests/prefs/mkdir.rs @@ -0,0 +1,54 @@ +use super::*; + +#[test] +fn mkdir_creates_a_folder_inside_the_browsed_directory() { + let (dir, server, token) = prefs_server(); + let body = serde_json::json!({ + "path": dir.path().to_str().unwrap(), + "name": "scratch", + }) + .to_string(); + + let created = post(server.addr(), "/api/mkdir", &body, Some(&token)); + + assert!(created.starts_with("HTTP/1.1 200"), "got: {created}"); + assert!(dir.path().join("scratch").is_dir()); + let value: serde_json::Value = serde_json::from_str(body_of(&created)).unwrap(); + assert!(std::path::Path::new(value["path"].as_str().unwrap()).ends_with("scratch")); +} + +#[test] +fn mkdir_rejects_names_that_escape_the_browsed_directory() { + let (dir, server, token) = prefs_server(); + + for name in ["../escape", "a/b", "..", ".git", ".hidden"] { + let body = serde_json::json!({ + "path": dir.path().to_str().unwrap(), + "name": name, + }) + .to_string(); + let response = post(server.addr(), "/api/mkdir", &body, Some(&token)); + assert!(response.starts_with("HTTP/1.1 400"), "{name:?}: {response}"); + } + assert!(!dir.path().parent().unwrap().join("escape").exists()); +} + +#[test] +fn mkdir_requires_authentication() { + let dir = tempfile::TempDir::new().unwrap(); + let server = server_with( + &[], + crate::config::AgentIndicatorConfig::default(), + Some(dir.path()), + ); + let body = serde_json::json!({ + "path": dir.path().to_str().unwrap(), + "name": "nope", + }) + .to_string(); + + let response = post(server.addr(), "/api/mkdir", &body, None); + + assert!(response.starts_with("HTTP/1.1 401"), "got: {response}"); + assert!(!dir.path().join("nope").exists()); +} diff --git a/src/web/viewer/server/tests/prefs/preferences.rs b/src/web/viewer/server/tests/prefs/preferences.rs new file mode 100644 index 00000000..2c2b2ece --- /dev/null +++ b/src/web/viewer/server/tests/prefs/preferences.rs @@ -0,0 +1,86 @@ +use super::*; + +#[test] +fn a_stored_accent_is_served_to_later_clients() { + let (_dir, server, token) = prefs_server(); + + let stored = post(server.addr(), "/api/prefs", "{\"accent\":3}", Some(&token)); + assert!(stored.starts_with("HTTP/1.1 200"), "got: {stored}"); + + let list = get(server.addr(), "/api/repos", Some(&token)); + let value: serde_json::Value = serde_json::from_str(body_of(&list)).unwrap(); + assert_eq!(value["accent"], 3); +} + +#[test] +fn layout_bounds_are_clamped_and_persisted() { + let (_dir, server, token) = prefs_server(); + let stored = post( + server.addr(), + "/api/prefs", + "{\"sidebar_width\":5000,\"upper_pct\":99}", + Some(&token), + ); + let echoed: serde_json::Value = serde_json::from_str(body_of(&stored)).unwrap(); + assert_eq!( + echoed["sidebar_width"], + crate::session::prefs::MAX_SIDEBAR_WIDTH + ); + assert_eq!(echoed["upper_pct"], crate::session::prefs::MAX_UPPER_PCT); + + let list = get(server.addr(), "/api/repos", Some(&token)); + let value: serde_json::Value = serde_json::from_str(body_of(&list)).unwrap(); + assert_eq!( + value["sidebar_width"], + crate::session::prefs::MAX_SIDEBAR_WIDTH + ); + assert_eq!(value["upper_pct"], crate::session::prefs::MAX_UPPER_PCT); +} + +#[test] +fn setting_one_preference_leaves_the_other_untouched() { + let (_dir, server, token) = prefs_server(); + + post(server.addr(), "/api/prefs", "{\"accent\":3}", Some(&token)); + let stored = post( + server.addr(), + "/api/prefs", + "{\"sidebar_width\":500}", + Some(&token), + ); + + let echoed: serde_json::Value = serde_json::from_str(body_of(&stored)).unwrap(); + assert_eq!(echoed["accent"], 3); + assert_eq!(echoed["sidebar_width"], 500); +} + +#[test] +fn invalid_preference_bodies_are_rejected_without_mutation() { + let (_dir, server, token) = prefs_server(); + + for body in ["{\"nope\":1}", "{\"accent\":\"red\"}"] { + let response = post(server.addr(), "/api/prefs", body, Some(&token)); + assert!( + response.starts_with("HTTP/1.1 400"), + "body {body}: {response}" + ); + } + + let list = get(server.addr(), "/api/repos", Some(&token)); + let value: serde_json::Value = serde_json::from_str(body_of(&list)).unwrap(); + assert_eq!(value["accent"], 0); +} + +#[test] +fn storing_a_preference_requires_authentication() { + let dir = tempfile::TempDir::new().unwrap(); + let server = server_with( + &[], + crate::config::AgentIndicatorConfig::default(), + Some(dir.path()), + ); + + let response = post(server.addr(), "/api/prefs", "{\"accent\":3}", None); + + assert!(response.starts_with("HTTP/1.1 401"), "got: {response}"); +} diff --git a/src/web/viewer/server/tests/reload.rs b/src/web/viewer/server/tests/reload.rs index 09da28cf..3d7123b6 100644 --- a/src/web/viewer/server/tests/reload.rs +++ b/src/web/viewer/server/tests/reload.rs @@ -1,6 +1,6 @@ //! The browser's reload route. //! -//! What a reload *applies* is pinned in `web::viewer::reload`, against a temp +//! What a reload *applies* is pinned in `session::reload`, against a temp //! file. What is left here is the route: that it is gated, that it takes no //! configuration from the caller, and that a GET is not a way to trigger it. //! diff --git a/src/web/viewer/server/tests/terminals.rs b/src/web/viewer/server/tests/terminals.rs index a868ae5a..b663c24e 100644 --- a/src/web/viewer/server/tests/terminals.rs +++ b/src/web/viewer/server/tests/terminals.rs @@ -1,5 +1,5 @@ use super::{VIEWER_SESSION_COOKIE, get, request, seeded_server}; -use crate::web::viewer::terminal; +use crate::session::terminal; use std::io::{Read, Write}; use std::net::TcpStream; use std::time::Duration; diff --git a/src/web/viewer/status_payload.rs b/src/web/viewer/status_payload.rs new file mode 100644 index 00000000..38afdffb --- /dev/null +++ b/src/web/viewer/status_payload.rs @@ -0,0 +1,31 @@ +//! Browser status-payload encoding injected into the shared session runtime. + +use crate::git::diff::RepoSnapshot; +use crate::web::viewer::dto::{Envelope, StatusDto}; +use std::collections::HashMap; +use std::time::SystemTime; + +pub(crate) fn encode( + snapshot: &RepoSnapshot, + mtimes: &HashMap, +) -> Option { + let dto = StatusDto::from_snapshot( + &snapshot.files, + snapshot.tracking.as_ref(), + snapshot.head_oid, + snapshot.branch_name.as_deref(), + mtimes, + ); + let json = match serde_json::to_string(&Envelope::new(dto)) { + Ok(json) => json, + Err(err) => { + tracing::warn!(%err, "viewer: status payload failed to serialize"); + return None; + } + }; + if json.len() > crate::web::viewer::limits::MAX_SSE_PAYLOAD_BYTES { + tracing::warn!(bytes = json.len(), "viewer: status payload over ceiling"); + return None; + } + Some(json) +} diff --git a/src/web/viewer/terminal/frame.rs b/src/web/viewer/terminal/frame.rs deleted file mode 100644 index 0aee4ca2..00000000 --- a/src/web/viewer/terminal/frame.rs +++ /dev/null @@ -1,310 +0,0 @@ -use crate::backend::PaneId; -use crate::web::viewer::limits; -use serde::{Deserialize, Serialize}; - -/// A control message from a client. Output travels as binary frames instead, -/// so it never pays JSON escaping or base64 expansion. -/// -/// Serialized as well as deserialized: the browser only ever sends these, but -/// an attaching client is Rust on both ends of the same definition, and the -/// daemon relays them. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "type", rename_all = "lowercase")] -pub enum ClientMessage { - Create { - rows: u16, - cols: u16, - }, - Input { - pane: PaneId, - data: String, - }, - Resize { - pane: PaneId, - rows: u16, - cols: u16, - }, - Close { - pane: PaneId, - }, - /// A full desired sequence of the live pane ids, sent when a client drags a - /// pane to a new slot. The hub reconciles it (see [`super::TerminalHub::reorder_panes`]). - Reorder { - order: Vec, - }, - /// Fill the terminal panel with one pane, or `None` to go back to the grid. - /// - /// Which pane is zoomed is the repository's answer rather than each page's, - /// for the same reason the pane *order* is (see - /// [`super::TerminalHub::reorder_panes`]): every client shows the same - /// terminals, and one that laid them out differently from the rest is a - /// screen nobody asked for. Unlike the order, it is not a request to be - /// reconciled — the last one to ask wins outright, because there is nothing - /// to merge. - Zoom { - pane: Option, - }, - /// Take over sizing this repository's panes (see [`ServerMessage::SizeOwner`]). - /// - /// A PTY has one size, so one client at a time decides it. Attaching takes - /// it; this is how a client already attached takes it back, on a keystroke — - /// deliberately, rather than by the mere act of looking, which would make - /// glancing at a phone repaint everybody's screen. - #[serde(rename = "claim_size")] - ClaimSize, - /// Give up on whatever recovery is pending for `pane`. - /// - /// A person deciding the wait is over outranks the plugin still waiting: the - /// hold on the pane's slot is dropped and the slot retired, so nothing can - /// be relaunched into it afterwards. Harmless for a pane with no recovery in - /// flight — a client can be a beat behind the hold expiring. - #[serde(rename = "cancel_recovery")] - CancelRecovery { - pane: PaneId, - }, - /// The sizes to give the startup terminals, answering [`ServerMessage::Pending`]. - /// - /// One entry per pending pane, in the order they will be created. A short - /// list leaves the rest at the default, so a client that could only measure - /// some of them still gets terminals. - Start { - sizes: Vec, - }, - /// How a `Ctrl+L` a client just forwarded came to be — see - /// [`hub_diag`](super::hub_diag) for why anyone is asking. - /// - /// Carries no input, only the provenance of one byte: whether a real key - /// event produced it, whether that event was the browser's own or something - /// dispatched at the page, and whether it was a key being held down. Logged - /// and otherwise ignored; nothing in the hub reads it. - #[serde(rename = "clear_key_report")] - ClearKeyReport { - pane: PaneId, - /// `None` when no key event preceded the byte — a paste, an input method, - /// or a script writing straight into the terminal. - key: Option, - }, -} - -/// What the browser said about the key event behind a forwarded `Ctrl+L`. -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] -pub struct ClearKeyFacts { - /// `KeyboardEvent.isTrusted`: false means a script dispatched it, which no - /// keyboard can do. - pub trusted: bool, - /// `KeyboardEvent.repeat`: the key is being held down and the OS is - /// repeating it. - pub repeat: bool, - /// `KeyboardEvent.code`, e.g. `KeyL`. Sanitized before it is logged — it - /// arrives from the page like everything else on this socket. - pub code: String, - /// Milliseconds between that key event and the byte it produced. - pub since_ms: u32, -} - -/// One pane's size, as the client measured its cell. -#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] -pub struct PaneSize { - pub rows: u16, - pub cols: u16, -} - -impl PaneSize { - /// Bring a size the client sent inside [`limits`]' bounds. - /// - /// Every path that reaches `openpty` goes through here — `create`, - /// `resize`, and each entry of `start` — because they are all the same - /// thing arriving from the same untrusted side, and a clamp that only some - /// of them apply is the one the next path forgets. - pub fn clamped(self) -> Self { - Self { - rows: self - .rows - .clamp(limits::MIN_PANE_DIMENSION, limits::MAX_PANE_ROWS), - cols: self - .cols - .clamp(limits::MIN_PANE_DIMENSION, limits::MAX_PANE_COLS), - } - } -} - -/// A control message to a client. -/// -/// Deserialized as well as serialized so the daemon can read one back off a -/// hub session and hand it on to an attached client tagged with its -/// repository — one definition rather than a parallel set that can drift. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -#[serde(tag = "type", rename_all = "lowercase")] -pub enum ServerMessage { - /// A pane exists, along with the size its PTY is currently set to. - /// - /// The size rides along because the client is not the only source of it: - /// a pane replayed to a reconnecting page, or one another device sized, - /// already has a size this client never chose. Without it the client must - /// assume nothing and send its own size on attach, and every such resize - /// costs the child a full repaint — even when the two agree. - Created { - pane: PaneId, - rows: u16, - cols: u16, - /// Which client asked for this pane, in the id space of the connection - /// the frame is going out on — a hub client id for the browser, whose - /// socket *is* the hub session, and the attached client's id for a - /// daemon relay, which is one hop further out (see the daemon's - /// `TerminalBridges`). Each recipient compares it against its own id on - /// that connection. - /// - /// `None` means nobody there asked: a pane replayed to a connecting - /// client, one another client opened, or a startup terminal, which - /// belongs to the session rather than to whoever sized it. Omitted from - /// the wire when absent, so the frames the browser already reads are - /// unchanged. - #[serde(default, skip_serializing_if = "Option::is_none")] - client: Option, - /// What the session calls this pane, when it has a name of its own — a - /// startup terminal opened under a configured name. Absent for a pane a - /// client asked for, which that client names itself, and for one nothing - /// has named: a program emitting OSC 0/2 renames either afterwards. - #[serde(default, skip_serializing_if = "Option::is_none")] - title: Option, - }, - Exited { - pane: PaneId, - }, - /// The size a pane's PTY is now set to. - /// - /// Broadcast, not answered to whoever asked: a PTY has one size and every - /// client renders the same grid, so a client that is not the one sizing it - /// still has to follow — its emulator has to wrap where the child's does. - Resized { - pane: PaneId, - rows: u16, - cols: u16, - }, - /// Who this client is, in the id space [`Created::client`] is stamped in. - /// - /// Addressed, and the first thing a connection is told. Without it a client - /// cannot tell a pane it asked for from one that arrived while it was - /// asking: the id is on every `created`, but there was nothing to compare it - /// against, so the browser counted its outstanding creates instead and - /// credited itself with whichever pane came back first — another client's, - /// if that one won the race. - /// - /// A connection's id, not a viewer's: it is minted per connection - /// (`next_client_id`) and a reconnect gets a new one. That is the right - /// lifetime — panes from before the reconnect are replayed with no requester - /// at all, so nothing should match them. - /// - /// [`Created::client`]: Self::Created::client - Hello { - client: u64, - /// How many `Created` frames the replay is about to deliver. - /// - /// Exact, because `connect` queues the whole replay under the hub's lock - /// and only registers the client afterwards: no broadcast can land among - /// those frames, so the next `panes` of them are the replay and nothing - /// else. - /// - /// A client that knows the count can lay its grid out for the panes it - /// is *going* to have rather than the ones it has so far. Without it the - /// first pane is given the whole panel, fitted to it, and then shrunk - /// when the second arrives — a reflow and a PTY resize that the size - /// carried by `Created` exists to make unnecessary. - panes: usize, - }, - /// Whether *this* client is the one whose layout sets the pane sizes. - /// - /// Addressed rather than broadcast, so the answer needs no identity to - /// compare against — the hub knows who each client is, and "am I the owner" - /// is the only thing a client does with it. - /// - /// The size follows the most recent arrival (tmux's `window-size latest`), - /// because that is the client someone is looking at; the others become - /// spectators until one takes it back with [`ClientMessage::ClaimSize`]. - #[serde(rename = "size_owner")] - SizeOwner { - owned: bool, - }, - Error { - message: String, - }, - /// The canonical pane order after a reorder, broadcast to every client so - /// the sender and any other device converge on the same layout. - Reordered { - order: Vec, - }, - /// Which pane now fills the terminal panel, `None` for none. - /// - /// Broadcast like [`Reordered`](Self::Reordered) and replayed to a client on - /// connect, so a page that reloads comes back zoomed where it left off and - /// another device follows. `null` is sent rather than omitted: "nothing is - /// zoomed" is a state a client has to be able to be *told*, not only one it - /// starts in. - Zoomed { - pane: Option, - }, - /// This many startup terminals are waiting to be sized before they are - /// created. The client answers with [`ClientMessage::Start`]. - /// - /// Sent to every client that connects while they are still unclaimed, not - /// only the first: a client that disconnects mid-handshake would otherwise - /// leave the hub with no terminals and no way to ever get them. - Pending { - count: usize, - }, - /// What a plugin reports about a pane it is nursing back, relayed verbatim. - /// - /// Pane metadata rather than screen content: nothing here is drawn into a - /// terminal grid, and a client that ignores it renders exactly as before. - /// `state` is the plugin's own short label; the hub neither interprets it nor - /// keeps it, so this is a broadcast of the latest word and not a state - /// machine. The one label the hub itself sends is - /// [`RECOVERY_CANCELLED`](super::hub_recovery::RECOVERY_CANCELLED), which a - /// client treats as "there is nothing pending any more". - Recovery { - pane: PaneId, - state: String, - /// A short human line, absent when the plugin gave none. Never carries - /// transcript or payload text. - #[serde(default, skip_serializing_if = "Option::is_none")] - detail: Option, - /// When the wait ends, in **unix epoch seconds**, absent when the plugin - /// is not waiting on a clock. A client renders it in its own local zone; - /// an absent one must render nothing rather than a guess. - #[serde(default, skip_serializing_if = "Option::is_none")] - deadline_epoch: Option, - attempt: u32, - }, -} - -/// One frame queued for a connected client. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum TerminalFrame { - /// Raw PTY bytes for `pane`. Sent as a binary WebSocket frame with the - /// pane id prefixed, so one socket multiplexes every terminal losslessly. - Output { pane: PaneId, data: Vec }, - /// A JSON control frame. - Control(String), -} - -/// Encode an output frame: 4-byte little-endian pane id, then the raw bytes. -/// -/// Binary rather than JSON because PTY output is not guaranteed valid UTF-8 — -/// a multi-byte sequence is routinely split across reads, and lossy decoding -/// would corrupt it before xterm.js ever reassembles it. -pub fn encode_output(pane: PaneId, data: &[u8]) -> Vec { - let mut out = Vec::with_capacity(data.len() + 4); - out.extend_from_slice(&pane.to_le_bytes()); - out.extend_from_slice(data); - out -} - -/// Decode an output frame produced by [`encode_output`]. -pub fn decode_output(frame: &[u8]) -> Option<(PaneId, &[u8])> { - if frame.len() < 4 { - return None; - } - let (id_bytes, rest) = frame.split_at(4); - let pane = PaneId::from_le_bytes(id_bytes.try_into().ok()?); - Some((pane, rest)) -} diff --git a/src/workspace/mod.rs b/src/workspace/mod.rs index 31e4f9e0..051aa770 100644 --- a/src/workspace/mod.rs +++ b/src/workspace/mod.rs @@ -2,8 +2,7 @@ //! Closing a tab drops the `App`, which tears down its worker and panes — no //! field-by-field reset to keep in sync. The list may be empty, so `active()` //! yields an `Option` and the open-repo dialog lives here rather than on a -//! project. Every project drains its queues each tick whether or not it is on -//! screen; snapshots apply to the active one only. +//! project. mod accent; mod path_complete; @@ -21,8 +20,7 @@ use crate::app::{App, Notice, NoticeKind}; use crate::ui::status_view::RepoInput; use crossterm::event::KeyEvent; -/// Upper bound on open projects, matching the F1..F10 switch keys. A tab you -/// cannot reach by key is hard to find, so the key space sets the limit. +/// Upper bound on open projects, matching the F1..F10 switch keys. pub const MAX_PROJECTS: usize = 10; pub struct Workspace { @@ -39,17 +37,13 @@ pub struct Workspace { /// View state for repos not currently open. Open projects are read live /// off the `App` at save time rather than stored here. remembered: Vec, - /// The session's accent, adopted from the daemon rather than chosen here. - /// - /// On the workspace and not on a project because it is one colour for the - /// whole session — see the shared/per-client boundary in - /// `docs/architecture.md`. It also has to survive having no project open, - /// which is where the empty screen is drawn from. + /// The session's accent, adopted from the daemon. On the workspace and not + /// on a project because it is one colour for the whole session and has to + /// survive having no project open. accent_idx: usize, } impl Workspace { - /// Open a workspace with no projects. pub fn new(leader: KeyEvent) -> Self { Self { projects: Vec::new(), @@ -63,7 +57,7 @@ impl Workspace { } } - /// Seed the remembered view state, once, from the file read at startup. + /// Seed the remembered view state from the file read at startup. pub fn set_remembered(&mut self, sessions: Vec) { self.remembered = sessions; } @@ -76,14 +70,9 @@ impl Workspace { } /// Every repository's view state — open tabs and remembered ones — capped - /// and ordered for storage. - /// - /// Only this half. Which repositories are open, and which is active, belong - /// to the daemon; a client that wrote those would overwrite the session - /// with its own view of it. What is selected and where it is scrolled is - /// this client's alone (see the shared/per-client boundary in - /// `docs/architecture/session.md`), which is why it is saved here and not - /// there. + /// and ordered for storage. Only this half: which repos are open and which + /// is active belong to the daemon; what is selected and where it is scrolled + /// is this client's alone (see `docs/architecture/session.md`). /// /// Open projects go last so the least-recently-used eviction never drops a /// tab that is currently on screen — `remember` inserts at the front. @@ -99,7 +88,6 @@ impl Workspace { } /// Matches only the bare leader chord; any extra modifier passes through. - /// See `App::is_leader_key`. pub fn is_leader_key(&self, key: KeyEvent) -> bool { key.code == self.leader.code && key.modifiers == self.leader.modifiers } @@ -135,8 +123,7 @@ impl Workspace { (self.projects.get_mut(self.active), &self.repo_input) } - /// Raise a notice on the active project or the empty screen; callers need - /// not know which case they are in. + /// Raise a notice on the active project or the empty screen. pub fn raise_notice(&mut self, kind: NoticeKind, text: impl Into) { match self.projects.get_mut(self.active) { Some(project) => project.raise_notice(kind, text), @@ -161,7 +148,7 @@ impl Workspace { self.empty_notice.as_ref() } - /// All open projects in tab order, for the tab row and end-of-run saves. + /// All open projects in tab order. pub fn projects(&self) -> &[App] { &self.projects } @@ -175,14 +162,14 @@ impl Workspace { &mut self.projects } - /// Checked before *building* a project: construction spawns PTYs and runs + /// Checked before building a project: construction spawns PTYs and runs /// startup commands, which must not happen for a tab `add` will refuse. pub fn is_full(&self) -> bool { self.projects.len() >= MAX_PROJECTS } /// Open `project` in a new tab and make it active. Returns `false` at - /// `MAX_PROJECTS`, leaving the workspace untouched. + /// `MAX_PROJECTS`. pub fn add(&mut self, project: App) -> bool { if self.projects.len() >= MAX_PROJECTS { return false; @@ -239,11 +226,8 @@ impl Workspace { } /// Put the tabs in `order` (by repository path), keeping the same project - /// active. - /// - /// Paths not open are skipped and open tabs the order does not name keep - /// their relative position at the end, so a list that raced a close still - /// produces a sane arrangement rather than dropping tabs. + /// active. Paths not open are skipped and open tabs the order does not name + /// keep their relative position at the end. pub fn reorder_to(&mut self, order: &[&str]) { let active_path = self.projects.get(self.active).map(|p| p.repo_path.clone()); let mut arranged: Vec = Vec::with_capacity(self.projects.len()); diff --git a/src/workspace/path_complete.rs b/src/workspace/path_complete.rs index bf0e596b..7b447ea0 100644 --- a/src/workspace/path_complete.rs +++ b/src/workspace/path_complete.rs @@ -1,9 +1,7 @@ //! Tab completion for the repo dialog's path field. //! -//! One `read_dir` per Tab press, against the single directory the buffer names -//! — a deep tree is never walked. Directories only: the dialog opens a repo and -//! a file can never be one. -//! +//! One `read_dir` per Tab press, against the single directory the buffer names. +//! Directories only: the dialog opens a repo and a file can never be one. //! The dialog is not a shell, so only what `confirm_repo_input` itself accepts //! is understood here: `~`, `..`, and cwd-relative paths. No `$VAR`, no globs. @@ -14,22 +12,18 @@ pub(crate) struct PathCompletion { /// The buffer after completion — unchanged when nothing matched. pub buf: String, /// Directory names to offer. Empty when the completion was unambiguous, - /// when nothing matched, or when the buffer grew: a list is only worth - /// showing once typing can no longer narrow things down. + /// when nothing matched, or when the buffer grew. pub candidates: Vec, } /// Whether `c` ends a path component. `\` counts on Windows only — on Unix it -/// is a legal filename character, so treating it as a separator there would -/// split paths that contain one. +/// is a legal filename character. pub(crate) fn is_sep(c: char) -> bool { c == '/' || (cfg!(windows) && c == '\\') } /// Split a dialog buffer into the directory text (up to and including the last -/// separator) and the trailing component being completed. With no separator the -/// whole buffer is the component and the directory is empty, meaning the process -/// cwd — the same reading `confirm_repo_input` gives a bare relative path. +/// separator) and the trailing component being completed. pub(crate) fn split_dir(buf: &str) -> (&str, &str) { match buf.char_indices().rfind(|(_, c)| is_sep(*c)) { Some((i, c)) => (&buf[..i + c.len_utf8()], &buf[i + c.len_utf8()..]), @@ -37,10 +31,7 @@ pub(crate) fn split_dir(buf: &str) -> (&str, &str) { } } -/// Immediate sub-directory names of `dir`, sorted. A directory that cannot be -/// read yields nothing: mid-typing that is the normal state for the completer, -/// and for the browser an unreadable directory is simply one with nothing to -/// show. Directories only — the dialog opens a repo, and a file cannot be one. +/// Immediate sub-directory names of `dir`, sorted. Directories only. pub(crate) fn read_dir_names(dir: &Path, show_hidden: bool) -> Vec { let Ok(read) = std::fs::read_dir(dir) else { return Vec::new(); @@ -58,12 +49,9 @@ pub(crate) fn read_dir_names(dir: &Path, show_hidden: bool) -> Vec { } /// Complete the last component of `buf` against the directory the rest of it -/// names. See the module docs for the rules; the caller owns the length cap and -/// decides whether to apply the result. -/// -/// The user's own text is never rewritten: a leading `~` or a relative path is -/// expanded for reading only, and the returned buffer keeps the typed prefix -/// with just the completed component appended. +/// names. The user's own text is never rewritten: a leading `~` or a relative +/// path is expanded for reading only, and the returned buffer keeps the typed +/// prefix with just the completed component appended. pub(crate) fn complete_dir_path(buf: &str) -> PathCompletion { let unchanged = || PathCompletion { buf: buf.to_string(), diff --git a/src/workspace/path_tree.rs b/src/workspace/path_tree.rs index 90d0d517..4ce3fa7d 100644 --- a/src/workspace/path_tree.rs +++ b/src/workspace/path_tree.rs @@ -1,18 +1,12 @@ //! Directory browser for the repo dialog's path field. //! //! A flat row list, not a nested tree: expanding splices a directory's children -//! in after it and collapsing removes the rows below it, so the selection is a -//! plain index into what is on screen and no flatten pass runs per frame. -//! -//! The root moves: `←` on a collapsed depth-0 row re-roots to the parent. That -//! is the only way out, and why there is no `..` row — every row here answers -//! "this is the path I want", so a row meaning "go up" would split Enter. -//! -//! Directories only, and nothing here writes: the browser fills the field, and -//! the field's own Enter stays the single place a repo is actually opened. It -//! deliberately does not reuse `git::tree`, which requires a `git2::Repository` -//! and refuses paths outside a worktree — the browser has to walk directories -//! belonging to no repo, with possibly no project open at all. +//! in after it and collapsing removes the rows below it. The root moves: `←` on +//! a collapsed depth-0 row re-roots to the parent. Directories only, and nothing +//! here writes — the browser fills the field, and the field's own Enter stays the +//! single place a repo is actually opened. It deliberately does not reuse +//! `git::tree`, which requires a `git2::Repository` and refuses paths outside a +//! worktree. use super::path_complete::{is_sep, read_dir_names, split_dir}; use crate::platform::paths::expand_tilde; @@ -24,22 +18,20 @@ pub struct PathRow { pub name: String, pub depth: usize, /// Whether this row's children are spliced in below it. Set even when the - /// directory turned out to have none, so the marker shows it was read - /// rather than leaving the user pressing `→` at an unchanging row. + /// directory turned out to have none, so the marker shows it was read. pub expanded: bool, } #[derive(Debug, Clone)] pub struct PathTree { - /// The root exactly as the user typed it (`~/coding`, `..`, or `""` for the - /// cwd). Kept beside `root` so a picked path is assembled from the user's - /// own notation — the dialog never rewrites their `~` into an absolute path. + /// The root exactly as the user typed it. Kept beside `root` so a picked + /// path is assembled from the user's own notation — the dialog never + /// rewrites their `~` into an absolute path. root_text: String, /// The canonicalized root. Canonical because stepping the root up walks /// `parent()`, which yields nothing useful for a relative path like `.`. root: PathBuf, - /// Separator to assemble picked paths with: whatever the field already uses, - /// so a path typed with `/` on Windows doesn't come back with a `\` in it. + /// Separator to assemble picked paths with: whatever the field already uses. sep: char, rows: Vec, selected: usize, @@ -47,8 +39,7 @@ pub struct PathTree { impl PathTree { /// Open the browser on the directory the field currently names. `None` when - /// that cannot be read, which the caller reports on the notice row — with no - /// rows and no root there is nothing to draw. + /// that cannot be read. pub(crate) fn open(buf: &str) -> Option { let trimmed = buf.trim(); let sep = trimmed @@ -90,8 +81,7 @@ impl PathTree { }) } - /// The root as the user's own text, for the browser's title. `""` means the - /// cwd, which reads as `.` on screen. + /// The root as the user's own text, for the browser's title. pub fn root_label(&self) -> &str { if self.root_text.is_empty() { "." @@ -108,8 +98,7 @@ impl PathTree { self.selected } - /// Clamped rather than wrapping: the list is a path being narrowed down, and - /// wrapping from the last row back to the first loses the user's place. + /// Clamped rather than wrapping: the list is a path being narrowed down. pub(crate) fn move_selection(&mut self, down: bool) { if self.rows.is_empty() { return; @@ -121,9 +110,7 @@ impl PathTree { }; } - /// Read the selected directory's children and splice them in below it. One - /// `read_dir` per press, against that directory only — an unexpanded subtree - /// is never walked. + /// Read the selected directory's children and splice them in below it. pub(crate) fn expand(&mut self) { let Some(row) = self.rows.get(self.selected) else { return; @@ -170,8 +157,7 @@ impl PathTree { self.re_root(); } - /// The picked path in the user's own notation, with a trailing separator so - /// Tab can carry on descending from it once the field has it back. + /// The picked path in the user's own notation, with a trailing separator. pub(crate) fn selected_path(&self) -> String { let mut out = self.root_text.clone(); if !self.rows.is_empty() { @@ -193,10 +179,8 @@ impl PathTree { out } - /// Step the root up one level, so a browse that started deep in one checkout - /// can still reach a sibling. Expansion below is dropped — the rows are - /// rebuilt from the new root — and the directory just left is selected, so - /// the key reads as "step out" rather than "jump somewhere". + /// Step the root up one level. Expansion below is dropped — the rows are + /// rebuilt from the new root — and the directory just left is selected. fn re_root(&mut self) { let Some(parent) = self.root.parent().map(Path::to_path_buf) else { return; @@ -246,8 +230,7 @@ impl PathTree { } } -/// Hidden directories are left out, matching the completer's default: a home -/// directory full of dot-directories would bury the checkouts being looked for. +/// Hidden directories are left out, matching the completer's default. fn list_rows(dir: &Path, depth: usize) -> Vec { read_dir_names(dir, false) .into_iter() diff --git a/src/workspace/persistence.rs b/src/workspace/persistence.rs index 425b0f5c..26f7e24c 100644 --- a/src/workspace/persistence.rs +++ b/src/workspace/persistence.rs @@ -93,9 +93,8 @@ pub fn load_workspace() -> Option { } fn load_workspace_at(path: &Path) -> Option { - let text = std::fs::read_to_string(path).ok()?; - match serde_json::from_str(&text) { - Ok(state) => Some(state), + match crate::persistence::read_json(path) { + Ok(state) => state, Err(e) => { tracing::warn!("corrupted workspace file, ignoring: {e}"); None @@ -117,28 +116,8 @@ pub fn save_workspace(state: &WorkspaceState) { } fn save_workspace_at(path: &Path, state: &WorkspaceState) { - if let Some(dir) = path.parent() - && let Err(e) = std::fs::create_dir_all(dir) - { - tracing::warn!("failed to create workspace directory: {e}"); - return; - } - let text = match serde_json::to_string(state) { - Ok(t) => t, - Err(e) => { - tracing::warn!("failed to serialize workspace: {e}"); - return; - } - }; - // Atomic replace, same reasoning as `save_session`. - let tmp_path = path.with_extension("json.tmp"); - if let Err(e) = std::fs::write(&tmp_path, &text) { - tracing::warn!("failed to write workspace tmp: {e}"); - return; - } - if let Err(e) = std::fs::rename(&tmp_path, path) { - tracing::warn!("failed to rename workspace tmp into place: {e}"); - let _ = std::fs::remove_file(&tmp_path); + if let Err(e) = crate::persistence::write_json(path, state) { + tracing::warn!("failed to save workspace: {e:#}"); } } diff --git a/src/workspace/tests/workspace_tests.rs b/src/workspace/tests/workspace_tests.rs index 52837bd7..2e8e40f5 100644 --- a/src/workspace/tests/workspace_tests.rs +++ b/src/workspace/tests/workspace_tests.rs @@ -96,12 +96,12 @@ fn 프로젝트를_추가해도_이전_프로젝트의_press가_버려진다() { // `add` makes the new project active, so the outgoing project's press // is released in place — same reasoning as `switch`. let mut ws = workspace_on(&["/a"]); - ws.active_mut().unwrap().pending_mouse_press = + ws.active_mut().unwrap().interaction.pending_mouse_press = Some((1, crossterm::event::MouseButton::Left, 1, 1)); ws.add(project_at("/b")); - assert!(ws.projects()[0].pending_mouse_press.is_none()); + assert!(ws.projects()[0].interaction.pending_mouse_press.is_none()); } #[test] @@ -109,14 +109,14 @@ fn 전환하면_이전_프로젝트의_대기중인_마우스_press가_버려진 let mut ws = workspace_from(project_at("/a")); ws.add(project_at("/b")); ws.switch(0); - ws.active_mut().unwrap().pending_mouse_press = + ws.active_mut().unwrap().interaction.pending_mouse_press = Some((1, crossterm::event::MouseButton::Left, 1, 1)); ws.switch(1); // /a's press can never be paired now, so it is released where it // happened rather than left to match an unrelated release later. - assert!(ws.projects()[0].pending_mouse_press.is_none()); + assert!(ws.projects()[0].interaction.pending_mouse_press.is_none()); } #[test] @@ -124,11 +124,11 @@ fn 같은_인덱스로_전환하면_대기중인_press를_유지한다() { // A no-op switch must not disturb an in-flight press/release pair. let mut ws = workspace_from(project_at("/a")); let press = Some((1, crossterm::event::MouseButton::Left, 1, 1)); - ws.active_mut().unwrap().pending_mouse_press = press; + ws.active_mut().unwrap().interaction.pending_mouse_press = press; ws.switch(0); - assert_eq!(ws.active().unwrap().pending_mouse_press, press); + assert_eq!(ws.active().unwrap().interaction.pending_mouse_press, press); } #[test] diff --git a/viewer-ui/dist/assets/Html-S4u0H_mA.js b/viewer-ui/dist/assets/Html-Day0K_6r.js similarity index 69% rename from viewer-ui/dist/assets/Html-S4u0H_mA.js rename to viewer-ui/dist/assets/Html-Day0K_6r.js index d28fcdcb..c110fed6 100644 --- a/viewer-ui/dist/assets/Html-S4u0H_mA.js +++ b/viewer-ui/dist/assets/Html-Day0K_6r.js @@ -1 +1 @@ -import{a as e}from"./index-Bla_aIeg.js";var t=e();function n({source:e}){return(0,t.jsx)(`iframe`,{title:`HTML preview`,sandbox:``,srcDoc:e,className:`h-full w-full border-0 bg-white`})}export{n as HtmlView}; \ No newline at end of file +import{s as e}from"./index-C2FlDlUR.js";var t=e();function n({source:e}){return(0,t.jsx)(`iframe`,{title:`HTML preview`,sandbox:``,srcDoc:e,className:`h-full w-full border-0 bg-white`})}export{n as HtmlView}; \ No newline at end of file diff --git a/viewer-ui/dist/assets/Markdown-DgtyrrzA.js b/viewer-ui/dist/assets/Markdown-DGCrBeLL.js similarity index 99% rename from viewer-ui/dist/assets/Markdown-DgtyrrzA.js rename to viewer-ui/dist/assets/Markdown-DGCrBeLL.js index ebd97342..250a27db 100644 --- a/viewer-ui/dist/assets/Markdown-DgtyrrzA.js +++ b/viewer-ui/dist/assets/Markdown-DGCrBeLL.js @@ -1,5 +1,5 @@ -import{a as e,d as t,f as n,l as r,u as i}from"./index-Bla_aIeg.js";function a(e,t){let n=t||{};return(e[e.length-1]===``?[...e,``]:e).join((n.padRight?` `:``)+`,`+(n.padLeft===!1?``:` `)).trim()}var o=/^[$_\p{ID_Start}][$_\u{200C}\u{200D}\p{ID_Continue}]*$/u,s=/^[$_\p{ID_Start}][-$_\u{200C}\u{200D}\p{ID_Continue}]*$/u,c={};function l(e,t){return((t||c).jsx?s:o).test(e)}var u=/[ \t\n\f\r]/g;function d(e){return typeof e==`object`?e.type===`text`&&f(e.value):f(e)}function f(e){return e.replace(u,``)===``}var p=class{constructor(e,t,n){this.normal=t,this.property=e,n&&(this.space=n)}};p.prototype.normal={},p.prototype.property={},p.prototype.space=void 0;function m(e,t){let n={},r={};for(let t of e)Object.assign(n,t.property),Object.assign(r,t.normal);return new p(n,r,t)}function h(e){return e.toLowerCase()}var g=class{constructor(e,t){this.attribute=t,this.property=e}};g.prototype.attribute=``,g.prototype.booleanish=!1,g.prototype.boolean=!1,g.prototype.commaOrSpaceSeparated=!1,g.prototype.commaSeparated=!1,g.prototype.defined=!1,g.prototype.mustUseProperty=!1,g.prototype.number=!1,g.prototype.overloadedBoolean=!1,g.prototype.property=``,g.prototype.spaceSeparated=!1,g.prototype.space=void 0;var _=t({boolean:()=>y,booleanish:()=>b,commaOrSpaceSeparated:()=>T,commaSeparated:()=>w,number:()=>S,overloadedBoolean:()=>x,spaceSeparated:()=>C}),v=0,y=E(),b=E(),x=E(),S=E(),C=E(),w=E(),T=E();function E(){return 2**++v}var D=Object.keys(_),O=class extends g{constructor(e,t,n,r){let i=-1;if(super(e,t),k(this,`space`,r),typeof n==`number`)for(;++i4&&n.slice(0,4)===`data`&&re.test(t)){if(t.charAt(4)===`-`){let e=t.slice(5).replace(ne,oe);r=`data`+e.charAt(0).toUpperCase()+e.slice(1)}else{let e=t.slice(4);if(!ne.test(e)){let n=e.replace(te,ae);n.charAt(0)!==`-`&&(n=`-`+n),t=`data`+n}}i=O}return new i(r,t)}function ae(e){return`-`+e.toLowerCase()}function oe(e){return e.charAt(1).toUpperCase()}var se=m([j,P,I,L,R],`html`),ce=m([j,F,I,L,R],`svg`);function le(e){return e.join(` `).trim()}var ue=i(((e,t)=>{var n=/\/\*[^*]*\*+([^/*][^*]*\*+)*\//g,r=/\n/g,i=/^\s*/,a=/^(\*?[-#/*\\\w]+(\[[0-9a-z_-]+\])?)\s*/,o=/^:\s*/,s=/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*?\)|[^};])+)/,c=/^[;\s]*/,l=/^\s+|\s+$/g;function u(e,t){if(typeof e!=`string`)throw TypeError(`First argument must be a string`);if(!e)return[];t||={};var l=1,u=1;function f(e){var t=e.match(r);t&&(l+=t.length);var n=e.lastIndexOf(` -`);u=~n?e.length-n:u+e.length}function p(){var e={line:l,column:u};return function(t){return t.position=new m(e),_(),t}}function m(e){this.start=e,this.end={line:l,column:u},this.source=t.source}m.prototype.content=e;function h(n){var r=Error(t.source+`:`+l+`:`+u+`: `+n);if(r.reason=n,r.filename=t.source,r.line=l,r.column=u,r.source=e,!t.silent)throw r}function g(t){var n=t.exec(e);if(n){var r=n[0];return f(r),e=e.slice(r.length),n}}function _(){g(i)}function v(e){var t;for(e||=[];t=y();)t!==!1&&e.push(t);return e}function y(){var t=p();if(!(e.charAt(0)!=`/`||e.charAt(1)!=`*`)){for(var n=2;e.charAt(n)!=``&&(e.charAt(n)!=`*`||e.charAt(n+1)!=`/`);)++n;if(n+=2,e.charAt(n-1)===``)return h(`End of comment missing`);var r=e.slice(2,n-2);return u+=2,f(r),e=e.slice(n),u+=2,t({type:`comment`,comment:r})}}function b(){var e=p(),t=g(a);if(t){if(y(),!g(o))return h(`property missing ':'`);var r=g(s),i=e({type:`declaration`,property:d(t[0].replace(n,``)),value:r?d(r[0].replace(n,``)):``});return g(c),i}}function x(){var e=[];v(e);for(var t;t=b();)t!==!1&&(e.push(t),v(e));return e}return _(),x()}function d(e){return e?e.replace(l,``):``}t.exports=u})),de=i((e=>{var t=e&&e.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(e,"__esModule",{value:!0}),e.default=r;var n=t(ue());function r(e,t){let r=null;if(!e||typeof e!=`string`)return r;let i=(0,n.default)(e),a=typeof t==`function`;return i.forEach(e=>{if(e.type!==`declaration`)return;let{property:n,value:i}=e;a?t(n,i,e):i&&(r||={},r[n]=i)}),r}})),fe=i((e=>{Object.defineProperty(e,"__esModule",{value:!0}),e.camelCase=void 0;var t=/^--[a-zA-Z0-9_-]+$/,n=/-([a-z])/g,r=/^[^-]+$/,i=/^-(webkit|moz|ms|o|khtml)-/,a=/^-(ms)-/,o=function(e){return!e||r.test(e)||t.test(e)},s=function(e,t){return t.toUpperCase()},c=function(e,t){return`${t}-`};e.camelCase=function(e,t){return t===void 0&&(t={}),o(e)?e:(e=e.toLowerCase(),e=t.reactCompat?e.replace(a,c):e.replace(i,c),e.replace(n,s))}})),pe=i(((e,t)=>{var n=(e&&e.__importDefault||function(e){return e&&e.__esModule?e:{default:e}})(de()),r=fe();function i(e,t){var i={};return!e||typeof e!=`string`||(0,n.default)(e,function(e,n){e&&n&&(i[(0,r.camelCase)(e,t)]=n)}),i}i.default=i,t.exports=i})),me=he(`end`),z=he(`start`);function he(e){return t;function t(t){let n=t&&t.position&&t.position[e]||{};if(typeof n.line==`number`&&n.line>0&&typeof n.column==`number`&&n.column>0)return{line:n.line,column:n.column,offset:typeof n.offset==`number`&&n.offset>-1?n.offset:void 0}}}function ge(e){let t=z(e),n=me(e);if(t&&n)return{start:t,end:n}}function _e(e){return!e||typeof e!=`object`?``:`position`in e||`type`in e?ye(e.position):`start`in e||`end`in e?ye(e):`line`in e||`column`in e?ve(e):``}function ve(e){return be(e&&e.line)+`:`+be(e&&e.column)}function ye(e){return ve(e&&e.start)+`-`+ve(e&&e.end)}function be(e){return e&&typeof e==`number`?e:1}var B=class extends Error{constructor(e,t,n){super(),typeof t==`string`&&(n=t,t=void 0);let r=``,i={},a=!1;if(t&&(i=`line`in t&&`column`in t||`start`in t&&`end`in t?{place:t}:`type`in t?{ancestors:[t],place:t.position}:{...t}),typeof e==`string`?r=e:!i.cause&&e&&(a=!0,r=e.message,i.cause=e),!i.ruleId&&!i.source&&typeof n==`string`){let e=n.indexOf(`:`);e===-1?i.ruleId=n:(i.source=n.slice(0,e),i.ruleId=n.slice(e+1))}if(!i.place&&i.ancestors&&i.ancestors){let e=i.ancestors[i.ancestors.length-1];e&&(i.place=e.position)}let o=i.place&&`start`in i.place?i.place.start:i.place;this.ancestors=i.ancestors||void 0,this.cause=i.cause||void 0,this.column=o?o.column:void 0,this.fatal=void 0,this.file=``,this.message=r,this.line=o?o.line:void 0,this.name=_e(i.place)||`1:1`,this.place=i.place||void 0,this.reason=this.message,this.ruleId=i.ruleId||void 0,this.source=i.source||void 0,this.stack=a&&i.cause&&typeof i.cause.stack==`string`?i.cause.stack:``,this.actual=void 0,this.expected=void 0,this.note=void 0,this.url=void 0}};B.prototype.file=``,B.prototype.name=``,B.prototype.reason=``,B.prototype.message=``,B.prototype.stack=``,B.prototype.column=void 0,B.prototype.line=void 0,B.prototype.ancestors=void 0,B.prototype.cause=void 0,B.prototype.fatal=void 0,B.prototype.place=void 0,B.prototype.ruleId=void 0,B.prototype.source=void 0;var xe=n(pe(),1),Se={}.hasOwnProperty,Ce=new Map,we=/[A-Z]/g,Te=new Set([`table`,`tbody`,`thead`,`tfoot`,`tr`]),Ee=new Set([`td`,`th`]);function De(e,t){if(!t||t.Fragment===void 0)throw TypeError("Expected `Fragment` in options");let n=t.filePath||void 0,r;if(t.development){if(typeof t.jsxDEV!=`function`)throw TypeError("Expected `jsxDEV` in options when `development: true`");r=Re(n,t.jsxDEV)}else{if(typeof t.jsx!=`function`)throw TypeError("Expected `jsx` in production options");if(typeof t.jsxs!=`function`)throw TypeError("Expected `jsxs` in production options");r=Le(n,t.jsx,t.jsxs)}let i={Fragment:t.Fragment,ancestors:[],components:t.components||{},create:r,elementAttributeNameCase:t.elementAttributeNameCase||`react`,evaluater:t.createEvaluater?t.createEvaluater():void 0,filePath:n,ignoreInvalidStyle:t.ignoreInvalidStyle||!1,passKeys:t.passKeys!==!1,passNode:t.passNode||!1,schema:t.space===`svg`?ce:se,stylePropertyNameCase:t.stylePropertyNameCase||`dom`,tableCellAlignToStyle:t.tableCellAlignToStyle!==!1},a=Oe(i,e,void 0);return a&&typeof a!=`string`?a:i.create(e,i.Fragment,{children:a||void 0},void 0)}function Oe(e,t,n){if(t.type===`element`)return ke(e,t,n);if(t.type===`mdxFlowExpression`||t.type===`mdxTextExpression`)return Ae(e,t);if(t.type===`mdxJsxFlowElement`||t.type===`mdxJsxTextElement`)return Me(e,t,n);if(t.type===`mdxjsEsm`)return je(e,t);if(t.type===`root`)return Ne(e,t,n);if(t.type===`text`)return Pe(e,t)}function ke(e,t,n){let r=e.schema,i=r;t.tagName.toLowerCase()===`svg`&&r.space===`html`&&(i=ce,e.schema=i),e.ancestors.push(t);let a=We(e,t.tagName,!1),o=ze(e,t),s=Ve(e,t);return Te.has(t.tagName)&&(s=s.filter(function(e){return typeof e!=`string`||!d(e)})),Fe(e,o,a,t),Ie(o,s),e.ancestors.pop(),e.schema=r,e.create(t,a,o,n)}function Ae(e,t){if(t.data&&t.data.estree&&e.evaluater){let n=t.data.estree.body[0];return n.type,e.evaluater.evaluateExpression(n.expression)}Ge(e,t.position)}function je(e,t){if(t.data&&t.data.estree&&e.evaluater)return e.evaluater.evaluateProgram(t.data.estree);Ge(e,t.position)}function Me(e,t,n){let r=e.schema,i=r;t.name===`svg`&&r.space===`html`&&(i=ce,e.schema=i),e.ancestors.push(t);let a=t.name===null?e.Fragment:We(e,t.name,!0),o=Be(e,t),s=Ve(e,t);return Fe(e,o,a,t),Ie(o,s),e.ancestors.pop(),e.schema=r,e.create(t,a,o,n)}function Ne(e,t,n){let r={};return Ie(r,Ve(e,t)),e.create(t,e.Fragment,r,n)}function Pe(e,t){return t.value}function Fe(e,t,n,r){typeof n!=`string`&&n!==e.Fragment&&e.passNode&&(t.node=r)}function Ie(e,t){if(t.length>0){let n=t.length>1?t:t[0];n&&(e.children=n)}}function Le(e,t,n){return r;function r(e,r,i,a){let o=Array.isArray(i.children)?n:t;return a?o(r,i,a):o(r,i)}}function Re(e,t){return n;function n(n,r,i,a){let o=Array.isArray(i.children),s=z(n);return t(r,i,a,o,{columnNumber:s?s.column-1:void 0,fileName:e,lineNumber:s?s.line:void 0},void 0)}}function ze(e,t){let n={},r,i;for(i in t.properties)if(i!==`children`&&Se.call(t.properties,i)){let a=He(e,i,t.properties[i]);if(a){let[i,o]=a;e.tableCellAlignToStyle&&i===`align`&&typeof o==`string`&&Ee.has(t.tagName)?r=o:n[i]=o}}if(r){let t=n.style||={};t[e.stylePropertyNameCase===`css`?`text-align`:`textAlign`]=r}return n}function Be(e,t){let n={};for(let r of t.attributes)if(r.type===`mdxJsxExpressionAttribute`)if(r.data&&r.data.estree&&e.evaluater){let t=r.data.estree.body[0];t.type;let i=t.expression;i.type;let a=i.properties[0];a.type,Object.assign(n,e.evaluater.evaluateExpression(a.argument))}else Ge(e,t.position);else{let i=r.name,a;if(r.value&&typeof r.value==`object`)if(r.value.data&&r.value.data.estree&&e.evaluater){let t=r.value.data.estree.body[0];t.type,a=e.evaluater.evaluateExpression(t.expression)}else Ge(e,t.position);else a=r.value===null||r.value;n[i]=a}return n}function Ve(e,t){let n=[],r=-1,i=e.passKeys?new Map:Ce;for(;++ri?0:i+t:t>i?i:t,n=n>0?n:0,r.length<1e4)o=Array.from(r),o.unshift(t,n),e.splice(...o);else for(n&&e.splice(t,n);a0?(V(e,e.length,0,t),e):t}var rt={}.hasOwnProperty;function it(e){let t={},n=-1;for(;++n13&&n<32||n>126&&n<160||n>55295&&n<57344||n>64975&&n<65008||(n&65535)==65535||(n&65535)==65534||n>1114111?`�`:String.fromCodePoint(n)}function U(e){return e.replace(/[\t\n\r ]+/g,` `).replace(/^ | $/g,``).toLowerCase().toUpperCase()}var W=ht(/[A-Za-z]/),G=ht(/[\dA-Za-z]/),ct=ht(/[#-'*+\--9=?A-Z^-~]/);function lt(e){return e!==null&&(e<32||e===127)}var ut=ht(/\d/),dt=ht(/[\dA-Fa-f]/),ft=ht(/[!-/:-@[-`{-~]/);function K(e){return e!==null&&e<-2}function q(e){return e!==null&&(e<0||e===32)}function J(e){return e===-2||e===-1||e===32}var pt=ht(/\p{P}|\p{S}/u),mt=ht(/\s/);function ht(e){return t;function t(t){return t!==null&&t>-1&&e.test(String.fromCharCode(t))}}function gt(e){let t=[],n=-1,r=0,i=0;for(;++n55295&&a<57344){let t=e.charCodeAt(n+1);a<56320&&t>56319&&t<57344?(o=String.fromCharCode(a,t),i=1):o=`�`}else o=String.fromCharCode(a);o&&=(t.push(e.slice(r,n),encodeURIComponent(o)),r=n+i+1,``),i&&=(n+=i,0)}return t.join(``)+e.slice(r)}function Y(e,t,n,r){let i=r?r-1:1/0,a=0;return o;function o(r){return J(r)?(e.enter(n),s(r)):t(r)}function s(r){return J(r)&&a++o))return;let n=t.events.length,a=n,s,c;for(;a--;)if(t.events[a][0]===`exit`&&t.events[a][1].type===`chunkFlow`){if(s){c=t.events[a][1].end;break}s=!0}for(_(r),e=n;er;){let r=n[i];t.containerState=r[1],r[0].exit.call(t,e)}n.length=r}function v(){i.write([null]),a=void 0,i=void 0,t.containerState._closeFlow=void 0}}function St(e,t,n){return Y(e,e.attempt(this.parser.constructs.document,t,n),`linePrefix`,this.parser.constructs.disable.null.includes(`codeIndented`)?void 0:4)}function Ct(e){if(e===null||q(e)||mt(e))return 1;if(pt(e))return 2}function wt(e,t,n){let r=[],i=-1;for(;++i1&&e[n][1].end.offset-e[n][1].start.offset>1?2:1;let d={...e[r][1].end},f={...e[n][1].start};Ot(d,-c),Ot(f,c),o={type:c>1?`strongSequence`:`emphasisSequence`,start:d,end:{...e[r][1].end}},s={type:c>1?`strongSequence`:`emphasisSequence`,start:{...e[n][1].start},end:f},a={type:c>1?`strongText`:`emphasisText`,start:{...e[r][1].end},end:{...e[n][1].start}},i={type:c>1?`strong`:`emphasis`,start:{...o.start},end:{...s.end}},e[r][1].end={...o.start},e[n][1].start={...s.end},l=[],e[r][1].end.offset-e[r][1].start.offset&&(l=H(l,[[`enter`,e[r][1],t],[`exit`,e[r][1],t]])),l=H(l,[[`enter`,i,t],[`enter`,o,t],[`exit`,o,t],[`enter`,a,t]]),l=H(l,wt(t.parser.constructs.insideSpan.null,e.slice(r+1,n),t)),l=H(l,[[`exit`,a,t],[`enter`,s,t],[`exit`,s,t],[`exit`,i,t]]),e[n][1].end.offset-e[n][1].start.offset?(u=2,l=H(l,[[`enter`,e[n][1],t],[`exit`,e[n][1],t]])):u=0,V(e,r-1,n-r+3,l),n=r+l.length-u-2;break}}for(n=-1;++n0&&J(t)?Y(e,v,`linePrefix`,a+1)(t):v(t)}function v(t){return t===null||K(t)?e.check(Vt,h,b)(t):(e.enter(`codeFlowValue`),y(t))}function y(t){return t===null||K(t)?(e.exit(`codeFlowValue`),v(t)):(e.consume(t),y)}function b(n){return e.exit(`codeFenced`),t(n)}function x(e,t,n){let i=0;return a;function a(t){return e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),c}function c(t){return e.enter(`codeFencedFence`),J(t)?Y(e,l,`linePrefix`,r.parser.constructs.disable.null.includes(`codeIndented`)?void 0:4)(t):l(t)}function l(t){return t===s?(e.enter(`codeFencedFenceSequence`),u(t)):n(t)}function u(t){return t===s?(i++,e.consume(t),u):i>=o?(e.exit(`codeFencedFenceSequence`),J(t)?Y(e,d,`whitespace`)(t):d(t)):n(t)}function d(r){return r===null||K(r)?(e.exit(`codeFencedFence`),t(r)):n(r)}}}function Wt(e,t,n){let r=this;return i;function i(t){return t===null?n(t):(e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),a)}function a(e){return r.parser.lazy[r.now().line]?n(e):t(e)}}var Gt={name:`codeIndented`,tokenize:qt},Kt={partial:!0,tokenize:Jt};function qt(e,t,n){let r=this;return i;function i(t){return e.enter(`codeIndented`),Y(e,a,`linePrefix`,5)(t)}function a(e){let t=r.events[r.events.length-1];return t&&t[1].type===`linePrefix`&&t[2].sliceSerialize(t[1],!0).length>=4?o(e):n(e)}function o(t){return t===null?c(t):K(t)?e.attempt(Kt,o,c)(t):(e.enter(`codeFlowValue`),s(t))}function s(t){return t===null||K(t)?(e.exit(`codeFlowValue`),o(t)):(e.consume(t),s)}function c(n){return e.exit(`codeIndented`),t(n)}}function Jt(e,t,n){let r=this;return i;function i(t){return r.parser.lazy[r.now().line]?n(t):K(t)?(e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),i):Y(e,a,`linePrefix`,5)(t)}function a(e){let a=r.events[r.events.length-1];return a&&a[1].type===`linePrefix`&&a[2].sliceSerialize(a[1],!0).length>=4?t(e):K(e)?i(e):n(e)}}var Yt={name:`codeText`,previous:Zt,resolve:Xt,tokenize:Qt};function Xt(e){let t=e.length-4,n=3,r,i;if((e[n][1].type===`lineEnding`||e[n][1].type===`space`)&&(e[t][1].type===`lineEnding`||e[t][1].type===`space`)){for(r=n;++r=this.left.length+this.right.length)throw RangeError("Cannot access index `"+e+"` in a splice buffer of size `"+(this.left.length+this.right.length)+"`");return ethis.left.length?this.right.slice(this.right.length-n+this.left.length,this.right.length-e+this.left.length).reverse():this.left.slice(e).concat(this.right.slice(this.right.length-n+this.left.length).reverse())}splice(e,t,n){let r=t||0;this.setCursor(Math.trunc(e));let i=this.right.splice(this.right.length-r,1/0);return n&&en(this.left,n),i.reverse()}pop(){return this.setCursor(1/0),this.left.pop()}push(e){this.setCursor(1/0),this.left.push(e)}pushMany(e){this.setCursor(1/0),en(this.left,e)}unshift(e){this.setCursor(0),this.right.push(e)}unshiftMany(e){this.setCursor(0),en(this.right,e.reverse())}setCursor(e){if(!(e===this.left.length||e>this.left.length&&this.right.length===0||e<0&&this.left.length===0))if(e=4?t(i):e.interrupt(r.parser.constructs.flow,n,t)(i)}}function ln(e,t,n,r,i,a,o,s,c){let l=c||1/0,u=0;return d;function d(t){return t===60?(e.enter(r),e.enter(i),e.enter(a),e.consume(t),e.exit(a),f):t===null||t===32||t===41||lt(t)?n(t):(e.enter(r),e.enter(o),e.enter(s),e.enter(`chunkString`,{contentType:`string`}),h(t))}function f(n){return n===62?(e.enter(a),e.consume(n),e.exit(a),e.exit(i),e.exit(r),t):(e.enter(s),e.enter(`chunkString`,{contentType:`string`}),p(n))}function p(t){return t===62?(e.exit(`chunkString`),e.exit(s),f(t)):t===null||t===60||K(t)?n(t):(e.consume(t),t===92?m:p)}function m(t){return t===60||t===62||t===92?(e.consume(t),p):p(t)}function h(i){return!u&&(i===null||i===41||q(i))?(e.exit(`chunkString`),e.exit(s),e.exit(o),e.exit(r),t(i)):u999||l===null||l===91||l===93&&!c||l===94&&!s&&`_hiddenFootnoteSupport`in o.parser.constructs?n(l):l===93?(e.exit(a),e.enter(i),e.consume(l),e.exit(i),e.exit(r),t):K(l)?(e.enter(`lineEnding`),e.consume(l),e.exit(`lineEnding`),u):(e.enter(`chunkString`,{contentType:`string`}),d(l))}function d(t){return t===null||t===91||t===93||K(t)||s++>999?(e.exit(`chunkString`),u(t)):(e.consume(t),c||=!J(t),t===92?f:d)}function f(t){return t===91||t===92||t===93?(e.consume(t),s++,d):d(t)}}function dn(e,t,n,r,i,a){let o;return s;function s(t){return t===34||t===39||t===40?(e.enter(r),e.enter(i),e.consume(t),e.exit(i),o=t===40?41:t,c):n(t)}function c(n){return n===o?(e.enter(i),e.consume(n),e.exit(i),e.exit(r),t):(e.enter(a),l(n))}function l(t){return t===o?(e.exit(a),c(o)):t===null?n(t):K(t)?(e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),Y(e,l,`linePrefix`)):(e.enter(`chunkString`,{contentType:`string`}),u(t))}function u(t){return t===o||t===null||K(t)?(e.exit(`chunkString`),l(t)):(e.consume(t),t===92?d:u)}function d(t){return t===o||t===92?(e.consume(t),u):u(t)}}function fn(e,t){let n;return r;function r(i){return K(i)?(e.enter(`lineEnding`),e.consume(i),e.exit(`lineEnding`),n=!0,r):J(i)?Y(e,r,n?`linePrefix`:`lineSuffix`)(i):t(i)}}var pn={name:`definition`,tokenize:hn},mn={partial:!0,tokenize:gn};function hn(e,t,n){let r=this,i;return a;function a(t){return e.enter(`definition`),o(t)}function o(t){return un.call(r,e,s,n,`definitionLabel`,`definitionLabelMarker`,`definitionLabelString`)(t)}function s(t){return i=U(r.sliceSerialize(r.events[r.events.length-1][1]).slice(1,-1)),t===58?(e.enter(`definitionMarker`),e.consume(t),e.exit(`definitionMarker`),c):n(t)}function c(t){return q(t)?fn(e,l)(t):l(t)}function l(t){return ln(e,u,n,`definitionDestination`,`definitionDestinationLiteral`,`definitionDestinationLiteralMarker`,`definitionDestinationRaw`,`definitionDestinationString`)(t)}function u(t){return e.attempt(mn,d,d)(t)}function d(t){return J(t)?Y(e,f,`whitespace`)(t):f(t)}function f(a){return a===null||K(a)?(e.exit(`definition`),r.parser.defined.push(i),t(a)):n(a)}}function gn(e,t,n){return r;function r(t){return q(t)?fn(e,i)(t):n(t)}function i(t){return dn(e,a,n,`definitionTitle`,`definitionTitleMarker`,`definitionTitleString`)(t)}function a(t){return J(t)?Y(e,o,`whitespace`)(t):o(t)}function o(e){return e===null||K(e)?t(e):n(e)}}var _n={name:`hardBreakEscape`,tokenize:vn};function vn(e,t,n){return r;function r(t){return e.enter(`hardBreakEscape`),e.consume(t),i}function i(r){return K(r)?(e.exit(`hardBreakEscape`),t(r)):n(r)}}var yn={name:`headingAtx`,resolve:bn,tokenize:xn};function bn(e,t){let n=e.length-2,r=3,i,a;return e[r][1].type===`whitespace`&&(r+=2),n-2>r&&e[n][1].type===`whitespace`&&(n-=2),e[n][1].type===`atxHeadingSequence`&&(r===n-1||n-4>r&&e[n-2][1].type===`whitespace`)&&(n-=r+1===n?2:4),n>r&&(i={type:`atxHeadingText`,start:e[r][1].start,end:e[n][1].end},a={type:`chunkText`,start:e[r][1].start,end:e[n][1].end,contentType:`text`},V(e,r,n-r+1,[[`enter`,i,t],[`enter`,a,t],[`exit`,a,t],[`exit`,i,t]])),e}function xn(e,t,n){let r=0;return i;function i(t){return e.enter(`atxHeading`),a(t)}function a(t){return e.enter(`atxHeadingSequence`),o(t)}function o(t){return t===35&&r++<6?(e.consume(t),o):t===null||q(t)?(e.exit(`atxHeadingSequence`),s(t)):n(t)}function s(n){return n===35?(e.enter(`atxHeadingSequence`),c(n)):n===null||K(n)?(e.exit(`atxHeading`),t(n)):J(n)?Y(e,s,`whitespace`)(n):(e.enter(`atxHeadingText`),l(n))}function c(t){return t===35?(e.consume(t),c):(e.exit(`atxHeadingSequence`),s(t))}function l(t){return t===null||t===35||q(t)?(e.exit(`atxHeadingText`),s(t)):(e.consume(t),l)}}var Sn=`address.article.aside.base.basefont.blockquote.body.caption.center.col.colgroup.dd.details.dialog.dir.div.dl.dt.fieldset.figcaption.figure.footer.form.frame.frameset.h1.h2.h3.h4.h5.h6.head.header.hr.html.iframe.legend.li.link.main.menu.menuitem.nav.noframes.ol.optgroup.option.p.param.search.section.summary.table.tbody.td.tfoot.th.thead.title.tr.track.ul`.split(`.`),Cn=[`pre`,`script`,`style`,`textarea`],wn={concrete:!0,name:`htmlFlow`,resolveTo:Dn,tokenize:On},Tn={partial:!0,tokenize:An},En={partial:!0,tokenize:kn};function Dn(e){let t=e.length;for(;t--&&!(e[t][0]===`enter`&&e[t][1].type===`htmlFlow`););return t>1&&e[t-2][1].type===`linePrefix`&&(e[t][1].start=e[t-2][1].start,e[t+1][1].start=e[t-2][1].start,e.splice(t-2,2)),e}function On(e,t,n){let r=this,i,a,o,s,c;return l;function l(e){return u(e)}function u(t){return e.enter(`htmlFlow`),e.enter(`htmlFlowData`),e.consume(t),d}function d(s){return s===33?(e.consume(s),f):s===47?(e.consume(s),a=!0,h):s===63?(e.consume(s),i=3,r.interrupt?t:I):W(s)?(e.consume(s),o=String.fromCharCode(s),g):n(s)}function f(a){return a===45?(e.consume(a),i=2,p):a===91?(e.consume(a),i=5,s=0,m):W(a)?(e.consume(a),i=4,r.interrupt?t:I):n(a)}function p(i){return i===45?(e.consume(i),r.interrupt?t:I):n(i)}function m(i){return i===`CDATA[`.charCodeAt(s++)?(e.consume(i),s===6?r.interrupt?t:O:m):n(i)}function h(t){return W(t)?(e.consume(t),o=String.fromCharCode(t),g):n(t)}function g(s){if(s===null||s===47||s===62||q(s)){let c=s===47,l=o.toLowerCase();return!c&&!a&&Cn.includes(l)?(i=1,r.interrupt?t(s):O(s)):Sn.includes(o.toLowerCase())?(i=6,c?(e.consume(s),_):r.interrupt?t(s):O(s)):(i=7,r.interrupt&&!r.parser.lazy[r.now().line]?n(s):a?v(s):y(s))}return s===45||G(s)?(e.consume(s),o+=String.fromCharCode(s),g):n(s)}function _(i){return i===62?(e.consume(i),r.interrupt?t:O):n(i)}function v(t){return J(t)?(e.consume(t),v):E(t)}function y(t){return t===47?(e.consume(t),E):t===58||t===95||W(t)?(e.consume(t),b):J(t)?(e.consume(t),y):E(t)}function b(t){return t===45||t===46||t===58||t===95||G(t)?(e.consume(t),b):x(t)}function x(t){return t===61?(e.consume(t),S):J(t)?(e.consume(t),x):y(t)}function S(t){return t===null||t===60||t===61||t===62||t===96?n(t):t===34||t===39?(e.consume(t),c=t,C):J(t)?(e.consume(t),S):w(t)}function C(t){return t===c?(e.consume(t),c=null,T):t===null||K(t)?n(t):(e.consume(t),C)}function w(t){return t===null||t===34||t===39||t===47||t===60||t===61||t===62||t===96||q(t)?x(t):(e.consume(t),w)}function T(e){return e===47||e===62||J(e)?y(e):n(e)}function E(t){return t===62?(e.consume(t),D):n(t)}function D(t){return t===null||K(t)?O(t):J(t)?(e.consume(t),D):n(t)}function O(t){return t===45&&i===2?(e.consume(t),M):t===60&&i===1?(e.consume(t),N):t===62&&i===4?(e.consume(t),L):t===63&&i===3?(e.consume(t),I):t===93&&i===5?(e.consume(t),F):K(t)&&(i===6||i===7)?(e.exit(`htmlFlowData`),e.check(Tn,R,k)(t)):t===null||K(t)?(e.exit(`htmlFlowData`),k(t)):(e.consume(t),O)}function k(t){return e.check(En,A,R)(t)}function A(t){return e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),j}function j(t){return t===null||K(t)?k(t):(e.enter(`htmlFlowData`),O(t))}function M(t){return t===45?(e.consume(t),I):O(t)}function N(t){return t===47?(e.consume(t),o=``,P):O(t)}function P(t){if(t===62){let n=o.toLowerCase();return Cn.includes(n)?(e.consume(t),L):O(t)}return W(t)&&o.length<8?(e.consume(t),o+=String.fromCharCode(t),P):O(t)}function F(t){return t===93?(e.consume(t),I):O(t)}function I(t){return t===62?(e.consume(t),L):t===45&&i===2?(e.consume(t),I):O(t)}function L(t){return t===null||K(t)?(e.exit(`htmlFlowData`),R(t)):(e.consume(t),L)}function R(n){return e.exit(`htmlFlow`),t(n)}}function kn(e,t,n){let r=this;return i;function i(t){return K(t)?(e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),a):n(t)}function a(e){return r.parser.lazy[r.now().line]?n(e):t(e)}}function An(e,t,n){return r;function r(r){return e.enter(`lineEnding`),e.consume(r),e.exit(`lineEnding`),e.attempt(jt,t,n)}}var jn={name:`htmlText`,tokenize:Mn};function Mn(e,t,n){let r=this,i,a,o;return s;function s(t){return e.enter(`htmlText`),e.enter(`htmlTextData`),e.consume(t),c}function c(t){return t===33?(e.consume(t),l):t===47?(e.consume(t),x):t===63?(e.consume(t),y):W(t)?(e.consume(t),w):n(t)}function l(t){return t===45?(e.consume(t),u):t===91?(e.consume(t),a=0,m):W(t)?(e.consume(t),v):n(t)}function u(t){return t===45?(e.consume(t),p):n(t)}function d(t){return t===null?n(t):t===45?(e.consume(t),f):K(t)?(o=d,N(t)):(e.consume(t),d)}function f(t){return t===45?(e.consume(t),p):d(t)}function p(e){return e===62?M(e):e===45?f(e):d(e)}function m(t){return t===`CDATA[`.charCodeAt(a++)?(e.consume(t),a===6?h:m):n(t)}function h(t){return t===null?n(t):t===93?(e.consume(t),g):K(t)?(o=h,N(t)):(e.consume(t),h)}function g(t){return t===93?(e.consume(t),_):h(t)}function _(t){return t===62?M(t):t===93?(e.consume(t),_):h(t)}function v(t){return t===null||t===62?M(t):K(t)?(o=v,N(t)):(e.consume(t),v)}function y(t){return t===null?n(t):t===63?(e.consume(t),b):K(t)?(o=y,N(t)):(e.consume(t),y)}function b(e){return e===62?M(e):y(e)}function x(t){return W(t)?(e.consume(t),S):n(t)}function S(t){return t===45||G(t)?(e.consume(t),S):C(t)}function C(t){return K(t)?(o=C,N(t)):J(t)?(e.consume(t),C):M(t)}function w(t){return t===45||G(t)?(e.consume(t),w):t===47||t===62||q(t)?T(t):n(t)}function T(t){return t===47?(e.consume(t),M):t===58||t===95||W(t)?(e.consume(t),E):K(t)?(o=T,N(t)):J(t)?(e.consume(t),T):M(t)}function E(t){return t===45||t===46||t===58||t===95||G(t)?(e.consume(t),E):D(t)}function D(t){return t===61?(e.consume(t),O):K(t)?(o=D,N(t)):J(t)?(e.consume(t),D):T(t)}function O(t){return t===null||t===60||t===61||t===62||t===96?n(t):t===34||t===39?(e.consume(t),i=t,k):K(t)?(o=O,N(t)):J(t)?(e.consume(t),O):(e.consume(t),A)}function k(t){return t===i?(e.consume(t),i=void 0,j):t===null?n(t):K(t)?(o=k,N(t)):(e.consume(t),k)}function A(t){return t===null||t===34||t===39||t===60||t===61||t===96?n(t):t===47||t===62||q(t)?T(t):(e.consume(t),A)}function j(e){return e===47||e===62||q(e)?T(e):n(e)}function M(r){return r===62?(e.consume(r),e.exit(`htmlTextData`),e.exit(`htmlText`),t):n(r)}function N(t){return e.exit(`htmlTextData`),e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),P}function P(t){return J(t)?Y(e,F,`linePrefix`,r.parser.constructs.disable.null.includes(`codeIndented`)?void 0:4)(t):F(t)}function F(t){return e.enter(`htmlTextData`),o(t)}}var Nn={name:`labelEnd`,resolveAll:Ln,resolveTo:Rn,tokenize:zn},Pn={tokenize:Bn},Fn={tokenize:Vn},In={tokenize:Hn};function Ln(e){let t=-1,n=[];for(;++t=3&&(a===null||K(a))?(e.exit(`thematicBreak`),t(a)):n(a)}function c(t){return t===i?(e.consume(t),r++,c):(e.exit(`thematicBreakSequence`),J(t)?Y(e,s,`whitespace`)(t):s(t))}}var X={continuation:{tokenize:er},exit:nr,name:`list`,tokenize:$n},Zn={partial:!0,tokenize:rr},Qn={partial:!0,tokenize:tr};function $n(e,t,n){let r=this,i=r.events[r.events.length-1],a=i&&i[1].type===`linePrefix`?i[2].sliceSerialize(i[1],!0).length:0,o=0;return s;function s(t){let i=r.containerState.type||(t===42||t===43||t===45?`listUnordered`:`listOrdered`);if(i===`listUnordered`?!r.containerState.marker||t===r.containerState.marker:ut(t)){if(r.containerState.type||(r.containerState.type=i,e.enter(i,{_container:!0})),i===`listUnordered`)return e.enter(`listItemPrefix`),t===42||t===45?e.check(Yn,n,l)(t):l(t);if(!r.interrupt||t===49)return e.enter(`listItemPrefix`),e.enter(`listItemValue`),c(t)}return n(t)}function c(t){return ut(t)&&++o<10?(e.consume(t),c):(!r.interrupt||o<2)&&(r.containerState.marker?t===r.containerState.marker:t===41||t===46)?(e.exit(`listItemValue`),l(t)):n(t)}function l(t){return e.enter(`listItemMarker`),e.consume(t),e.exit(`listItemMarker`),r.containerState.marker=r.containerState.marker||t,e.check(jt,r.interrupt?n:u,e.attempt(Zn,f,d))}function u(e){return r.containerState.initialBlankLine=!0,a++,f(e)}function d(t){return J(t)?(e.enter(`listItemPrefixWhitespace`),e.consume(t),e.exit(`listItemPrefixWhitespace`),f):n(t)}function f(n){return r.containerState.size=a+r.sliceSerialize(e.exit(`listItemPrefix`),!0).length,t(n)}}function er(e,t,n){let r=this;return r.containerState._closeFlow=void 0,e.check(jt,i,a);function i(n){return r.containerState.furtherBlankLines=r.containerState.furtherBlankLines||r.containerState.initialBlankLine,Y(e,t,`listItemIndent`,r.containerState.size+1)(n)}function a(n){return r.containerState.furtherBlankLines||!J(n)?(r.containerState.furtherBlankLines=void 0,r.containerState.initialBlankLine=void 0,o(n)):(r.containerState.furtherBlankLines=void 0,r.containerState.initialBlankLine=void 0,e.attempt(Qn,t,o)(n))}function o(i){return r.containerState._closeFlow=!0,r.interrupt=void 0,Y(e,e.attempt(X,t,n),`linePrefix`,r.parser.constructs.disable.null.includes(`codeIndented`)?void 0:4)(i)}}function tr(e,t,n){let r=this;return Y(e,i,`listItemIndent`,r.containerState.size+1);function i(e){let i=r.events[r.events.length-1];return i&&i[1].type===`listItemIndent`&&i[2].sliceSerialize(i[1],!0).length===r.containerState.size?t(e):n(e)}}function nr(e){e.exit(this.containerState.type)}function rr(e,t,n){let r=this;return Y(e,i,`listItemPrefixWhitespace`,r.parser.constructs.disable.null.includes(`codeIndented`)?void 0:5);function i(e){let i=r.events[r.events.length-1];return!J(e)&&i&&i[1].type===`listItemPrefixWhitespace`?t(e):n(e)}}var ir={name:`setextUnderline`,resolveTo:ar,tokenize:or};function ar(e,t){let n=e.length,r,i,a;for(;n--;)if(e[n][0]===`enter`){if(e[n][1].type===`content`){r=n;break}e[n][1].type===`paragraph`&&(i=n)}else e[n][1].type===`content`&&e.splice(n,1),!a&&e[n][1].type===`definition`&&(a=n);let o={type:`setextHeading`,start:{...e[r][1].start},end:{...e[e.length-1][1].end}};return e[i][1].type=`setextHeadingText`,a?(e.splice(i,0,[`enter`,o,t]),e.splice(a+1,0,[`exit`,e[r][1],t]),e[r][1].end={...e[a][1].end}):e[r][1]=o,e.push([`exit`,o,t]),e}function or(e,t,n){let r=this,i;return a;function a(t){let a=r.events.length,s;for(;a--;)if(r.events[a][1].type!==`lineEnding`&&r.events[a][1].type!==`linePrefix`&&r.events[a][1].type!==`content`){s=r.events[a][1].type===`paragraph`;break}return!r.parser.lazy[r.now().line]&&(r.interrupt||s)?(e.enter(`setextHeadingLine`),i=t,o(t)):n(t)}function o(t){return e.enter(`setextHeadingLineSequence`),s(t)}function s(t){return t===i?(e.consume(t),s):(e.exit(`setextHeadingLineSequence`),J(t)?Y(e,c,`lineSuffix`)(t):c(t))}function c(r){return r===null||K(r)?(e.exit(`setextHeadingLine`),t(r)):n(r)}}var sr={tokenize:cr};function cr(e){let t=this,n=e.attempt(jt,r,e.attempt(this.parser.constructs.flowInitial,i,Y(e,e.attempt(this.parser.constructs.flow,i,e.attempt(rn,i)),`linePrefix`)));return n;function r(r){if(r===null){e.consume(r);return}return e.enter(`lineEndingBlank`),e.consume(r),e.exit(`lineEndingBlank`),t.currentConstruct=void 0,n}function i(r){if(r===null){e.consume(r);return}return e.enter(`lineEnding`),e.consume(r),e.exit(`lineEnding`),t.currentConstruct=void 0,n}}var lr={resolveAll:pr()},ur=fr(`string`),dr=fr(`text`);function fr(e){return{resolveAll:pr(e===`text`?mr:void 0),tokenize:t};function t(t){let n=this,r=this.parser.constructs[e],i=t.attempt(r,a,o);return a;function a(e){return c(e)?i(e):o(e)}function o(e){if(e===null){t.consume(e);return}return t.enter(`data`),t.consume(e),s}function s(e){return c(e)?(t.exit(`data`),i(e)):(t.consume(e),s)}function c(e){if(e===null)return!0;let t=r[e],i=-1;if(t)for(;++iCr,contentInitial:()=>_r,disable:()=>wr,document:()=>gr,flow:()=>yr,flowInitial:()=>vr,insideSpan:()=>Sr,string:()=>br,text:()=>xr}),gr={42:X,43:X,45:X,48:X,49:X,50:X,51:X,52:X,53:X,54:X,55:X,56:X,57:X,62:Nt},_r={91:pn},vr={[-2]:Gt,[-1]:Gt,32:Gt},yr={35:yn,42:Yn,45:[ir,Yn],60:wn,61:ir,95:Yn,96:Ht,126:Ht},br={38:zt,92:Lt},xr={[-5]:qn,[-4]:qn,[-3]:qn,33:Un,38:zt,42:Tt,60:[kt,jn],91:Gn,92:[_n,Lt],93:Nn,95:Tt,96:Yt},Sr={null:[Tt,lr]},Cr={null:[42,95]},wr={null:[]};function Tr(e,t,n){let r={_bufferIndex:-1,_index:0,line:n&&n.line||1,column:n&&n.column||1,offset:n&&n.offset||0},i={},a=[],o=[],s=[],c={attempt:C(x),check:C(S),consume:v,enter:y,exit:b,interrupt:C(S,{interrupt:!0})},l={code:null,containerState:{},defineSkip:h,events:[],now:m,parser:e,previous:null,sliceSerialize:f,sliceStream:p,write:d},u=t.tokenize.call(l,c);return t.resolveAll&&a.push(t),l;function d(e){return o=H(o,e),g(),o[o.length-1]===null?(w(t,0),l.events=wt(a,l.events,l),l.events):[]}function f(e,t){return Dr(p(e),t)}function p(e){return Er(o,e)}function m(){let{_bufferIndex:e,_index:t,line:n,column:i,offset:a}=r;return{_bufferIndex:e,_index:t,line:n,column:i,offset:a}}function h(e){i[e.line]=e.column,E()}function g(){let e;for(;r._index-1){let e=o[0];typeof e==`string`?o[0]=e.slice(r):o.shift()}a>0&&o.push(e[i].slice(0,a))}return o}function Dr(e,t){let n=-1,r=[],i;for(;++ny,booleanish:()=>b,commaOrSpaceSeparated:()=>T,commaSeparated:()=>w,number:()=>S,overloadedBoolean:()=>x,spaceSeparated:()=>C}),v=0,y=E(),b=E(),x=E(),S=E(),C=E(),w=E(),T=E();function E(){return 2**++v}var D=Object.keys(_),O=class extends g{constructor(e,t,n,r){let i=-1;if(super(e,t),k(this,`space`,r),typeof n==`number`)for(;++i4&&n.slice(0,4)===`data`&&re.test(t)){if(t.charAt(4)===`-`){let e=t.slice(5).replace(ne,oe);r=`data`+e.charAt(0).toUpperCase()+e.slice(1)}else{let e=t.slice(4);if(!ne.test(e)){let n=e.replace(te,ae);n.charAt(0)!==`-`&&(n=`-`+n),t=`data`+n}}i=O}return new i(r,t)}function ae(e){return`-`+e.toLowerCase()}function oe(e){return e.charAt(1).toUpperCase()}var se=m([j,P,I,L,R],`html`),ce=m([j,F,I,L,R],`svg`);function le(e){return e.join(` `).trim()}var ue=i(((e,t)=>{var n=/\/\*[^*]*\*+([^/*][^*]*\*+)*\//g,r=/\n/g,i=/^\s*/,a=/^(\*?[-#/*\\\w]+(\[[0-9a-z_-]+\])?)\s*/,o=/^:\s*/,s=/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*?\)|[^};])+)/,c=/^[;\s]*/,l=/^\s+|\s+$/g;function u(e,t){if(typeof e!=`string`)throw TypeError(`First argument must be a string`);if(!e)return[];t||={};var l=1,u=1;function f(e){var t=e.match(r);t&&(l+=t.length);var n=e.lastIndexOf(` +`);u=~n?e.length-n:u+e.length}function p(){var e={line:l,column:u};return function(t){return t.position=new m(e),_(),t}}function m(e){this.start=e,this.end={line:l,column:u},this.source=t.source}m.prototype.content=e;function h(n){var r=Error(t.source+`:`+l+`:`+u+`: `+n);if(r.reason=n,r.filename=t.source,r.line=l,r.column=u,r.source=e,!t.silent)throw r}function g(t){var n=t.exec(e);if(n){var r=n[0];return f(r),e=e.slice(r.length),n}}function _(){g(i)}function v(e){var t;for(e||=[];t=y();)t!==!1&&e.push(t);return e}function y(){var t=p();if(!(e.charAt(0)!=`/`||e.charAt(1)!=`*`)){for(var n=2;e.charAt(n)!=``&&(e.charAt(n)!=`*`||e.charAt(n+1)!=`/`);)++n;if(n+=2,e.charAt(n-1)===``)return h(`End of comment missing`);var r=e.slice(2,n-2);return u+=2,f(r),e=e.slice(n),u+=2,t({type:`comment`,comment:r})}}function b(){var e=p(),t=g(a);if(t){if(y(),!g(o))return h(`property missing ':'`);var r=g(s),i=e({type:`declaration`,property:d(t[0].replace(n,``)),value:r?d(r[0].replace(n,``)):``});return g(c),i}}function x(){var e=[];v(e);for(var t;t=b();)t!==!1&&(e.push(t),v(e));return e}return _(),x()}function d(e){return e?e.replace(l,``):``}t.exports=u})),de=i((e=>{var t=e&&e.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(e,"__esModule",{value:!0}),e.default=r;var n=t(ue());function r(e,t){let r=null;if(!e||typeof e!=`string`)return r;let i=(0,n.default)(e),a=typeof t==`function`;return i.forEach(e=>{if(e.type!==`declaration`)return;let{property:n,value:i}=e;a?t(n,i,e):i&&(r||={},r[n]=i)}),r}})),fe=i((e=>{Object.defineProperty(e,"__esModule",{value:!0}),e.camelCase=void 0;var t=/^--[a-zA-Z0-9_-]+$/,n=/-([a-z])/g,r=/^[^-]+$/,i=/^-(webkit|moz|ms|o|khtml)-/,a=/^-(ms)-/,o=function(e){return!e||r.test(e)||t.test(e)},s=function(e,t){return t.toUpperCase()},c=function(e,t){return`${t}-`};e.camelCase=function(e,t){return t===void 0&&(t={}),o(e)?e:(e=e.toLowerCase(),e=t.reactCompat?e.replace(a,c):e.replace(i,c),e.replace(n,s))}})),pe=i(((e,t)=>{var n=(e&&e.__importDefault||function(e){return e&&e.__esModule?e:{default:e}})(de()),r=fe();function i(e,t){var i={};return!e||typeof e!=`string`||(0,n.default)(e,function(e,n){e&&n&&(i[(0,r.camelCase)(e,t)]=n)}),i}i.default=i,t.exports=i})),me=he(`end`),z=he(`start`);function he(e){return t;function t(t){let n=t&&t.position&&t.position[e]||{};if(typeof n.line==`number`&&n.line>0&&typeof n.column==`number`&&n.column>0)return{line:n.line,column:n.column,offset:typeof n.offset==`number`&&n.offset>-1?n.offset:void 0}}}function ge(e){let t=z(e),n=me(e);if(t&&n)return{start:t,end:n}}function _e(e){return!e||typeof e!=`object`?``:`position`in e||`type`in e?ye(e.position):`start`in e||`end`in e?ye(e):`line`in e||`column`in e?ve(e):``}function ve(e){return be(e&&e.line)+`:`+be(e&&e.column)}function ye(e){return ve(e&&e.start)+`-`+ve(e&&e.end)}function be(e){return e&&typeof e==`number`?e:1}var B=class extends Error{constructor(e,t,n){super(),typeof t==`string`&&(n=t,t=void 0);let r=``,i={},a=!1;if(t&&(i=`line`in t&&`column`in t||`start`in t&&`end`in t?{place:t}:`type`in t?{ancestors:[t],place:t.position}:{...t}),typeof e==`string`?r=e:!i.cause&&e&&(a=!0,r=e.message,i.cause=e),!i.ruleId&&!i.source&&typeof n==`string`){let e=n.indexOf(`:`);e===-1?i.ruleId=n:(i.source=n.slice(0,e),i.ruleId=n.slice(e+1))}if(!i.place&&i.ancestors&&i.ancestors){let e=i.ancestors[i.ancestors.length-1];e&&(i.place=e.position)}let o=i.place&&`start`in i.place?i.place.start:i.place;this.ancestors=i.ancestors||void 0,this.cause=i.cause||void 0,this.column=o?o.column:void 0,this.fatal=void 0,this.file=``,this.message=r,this.line=o?o.line:void 0,this.name=_e(i.place)||`1:1`,this.place=i.place||void 0,this.reason=this.message,this.ruleId=i.ruleId||void 0,this.source=i.source||void 0,this.stack=a&&i.cause&&typeof i.cause.stack==`string`?i.cause.stack:``,this.actual=void 0,this.expected=void 0,this.note=void 0,this.url=void 0}};B.prototype.file=``,B.prototype.name=``,B.prototype.reason=``,B.prototype.message=``,B.prototype.stack=``,B.prototype.column=void 0,B.prototype.line=void 0,B.prototype.ancestors=void 0,B.prototype.cause=void 0,B.prototype.fatal=void 0,B.prototype.place=void 0,B.prototype.ruleId=void 0,B.prototype.source=void 0;var xe=t(pe(),1),Se={}.hasOwnProperty,Ce=new Map,we=/[A-Z]/g,Te=new Set([`table`,`tbody`,`thead`,`tfoot`,`tr`]),Ee=new Set([`td`,`th`]);function De(e,t){if(!t||t.Fragment===void 0)throw TypeError("Expected `Fragment` in options");let n=t.filePath||void 0,r;if(t.development){if(typeof t.jsxDEV!=`function`)throw TypeError("Expected `jsxDEV` in options when `development: true`");r=Re(n,t.jsxDEV)}else{if(typeof t.jsx!=`function`)throw TypeError("Expected `jsx` in production options");if(typeof t.jsxs!=`function`)throw TypeError("Expected `jsxs` in production options");r=Le(n,t.jsx,t.jsxs)}let i={Fragment:t.Fragment,ancestors:[],components:t.components||{},create:r,elementAttributeNameCase:t.elementAttributeNameCase||`react`,evaluater:t.createEvaluater?t.createEvaluater():void 0,filePath:n,ignoreInvalidStyle:t.ignoreInvalidStyle||!1,passKeys:t.passKeys!==!1,passNode:t.passNode||!1,schema:t.space===`svg`?ce:se,stylePropertyNameCase:t.stylePropertyNameCase||`dom`,tableCellAlignToStyle:t.tableCellAlignToStyle!==!1},a=Oe(i,e,void 0);return a&&typeof a!=`string`?a:i.create(e,i.Fragment,{children:a||void 0},void 0)}function Oe(e,t,n){if(t.type===`element`)return ke(e,t,n);if(t.type===`mdxFlowExpression`||t.type===`mdxTextExpression`)return Ae(e,t);if(t.type===`mdxJsxFlowElement`||t.type===`mdxJsxTextElement`)return Me(e,t,n);if(t.type===`mdxjsEsm`)return je(e,t);if(t.type===`root`)return Ne(e,t,n);if(t.type===`text`)return Pe(e,t)}function ke(e,t,n){let r=e.schema,i=r;t.tagName.toLowerCase()===`svg`&&r.space===`html`&&(i=ce,e.schema=i),e.ancestors.push(t);let a=We(e,t.tagName,!1),o=ze(e,t),s=Ve(e,t);return Te.has(t.tagName)&&(s=s.filter(function(e){return typeof e!=`string`||!d(e)})),Fe(e,o,a,t),Ie(o,s),e.ancestors.pop(),e.schema=r,e.create(t,a,o,n)}function Ae(e,t){if(t.data&&t.data.estree&&e.evaluater){let n=t.data.estree.body[0];return n.type,e.evaluater.evaluateExpression(n.expression)}Ge(e,t.position)}function je(e,t){if(t.data&&t.data.estree&&e.evaluater)return e.evaluater.evaluateProgram(t.data.estree);Ge(e,t.position)}function Me(e,t,n){let r=e.schema,i=r;t.name===`svg`&&r.space===`html`&&(i=ce,e.schema=i),e.ancestors.push(t);let a=t.name===null?e.Fragment:We(e,t.name,!0),o=Be(e,t),s=Ve(e,t);return Fe(e,o,a,t),Ie(o,s),e.ancestors.pop(),e.schema=r,e.create(t,a,o,n)}function Ne(e,t,n){let r={};return Ie(r,Ve(e,t)),e.create(t,e.Fragment,r,n)}function Pe(e,t){return t.value}function Fe(e,t,n,r){typeof n!=`string`&&n!==e.Fragment&&e.passNode&&(t.node=r)}function Ie(e,t){if(t.length>0){let n=t.length>1?t:t[0];n&&(e.children=n)}}function Le(e,t,n){return r;function r(e,r,i,a){let o=Array.isArray(i.children)?n:t;return a?o(r,i,a):o(r,i)}}function Re(e,t){return n;function n(n,r,i,a){let o=Array.isArray(i.children),s=z(n);return t(r,i,a,o,{columnNumber:s?s.column-1:void 0,fileName:e,lineNumber:s?s.line:void 0},void 0)}}function ze(e,t){let n={},r,i;for(i in t.properties)if(i!==`children`&&Se.call(t.properties,i)){let a=He(e,i,t.properties[i]);if(a){let[i,o]=a;e.tableCellAlignToStyle&&i===`align`&&typeof o==`string`&&Ee.has(t.tagName)?r=o:n[i]=o}}if(r){let t=n.style||={};t[e.stylePropertyNameCase===`css`?`text-align`:`textAlign`]=r}return n}function Be(e,t){let n={};for(let r of t.attributes)if(r.type===`mdxJsxExpressionAttribute`)if(r.data&&r.data.estree&&e.evaluater){let t=r.data.estree.body[0];t.type;let i=t.expression;i.type;let a=i.properties[0];a.type,Object.assign(n,e.evaluater.evaluateExpression(a.argument))}else Ge(e,t.position);else{let i=r.name,a;if(r.value&&typeof r.value==`object`)if(r.value.data&&r.value.data.estree&&e.evaluater){let t=r.value.data.estree.body[0];t.type,a=e.evaluater.evaluateExpression(t.expression)}else Ge(e,t.position);else a=r.value===null||r.value;n[i]=a}return n}function Ve(e,t){let n=[],r=-1,i=e.passKeys?new Map:Ce;for(;++ri?0:i+t:t>i?i:t,n=n>0?n:0,r.length<1e4)o=Array.from(r),o.unshift(t,n),e.splice(...o);else for(n&&e.splice(t,n);a0?(V(e,e.length,0,t),e):t}var rt={}.hasOwnProperty;function it(e){let t={},n=-1;for(;++n13&&n<32||n>126&&n<160||n>55295&&n<57344||n>64975&&n<65008||(n&65535)==65535||(n&65535)==65534||n>1114111?`�`:String.fromCodePoint(n)}function U(e){return e.replace(/[\t\n\r ]+/g,` `).replace(/^ | $/g,``).toLowerCase().toUpperCase()}var W=ht(/[A-Za-z]/),G=ht(/[\dA-Za-z]/),ct=ht(/[#-'*+\--9=?A-Z^-~]/);function lt(e){return e!==null&&(e<32||e===127)}var ut=ht(/\d/),dt=ht(/[\dA-Fa-f]/),ft=ht(/[!-/:-@[-`{-~]/);function K(e){return e!==null&&e<-2}function q(e){return e!==null&&(e<0||e===32)}function J(e){return e===-2||e===-1||e===32}var pt=ht(/\p{P}|\p{S}/u),mt=ht(/\s/);function ht(e){return t;function t(t){return t!==null&&t>-1&&e.test(String.fromCharCode(t))}}function gt(e){let t=[],n=-1,r=0,i=0;for(;++n55295&&a<57344){let t=e.charCodeAt(n+1);a<56320&&t>56319&&t<57344?(o=String.fromCharCode(a,t),i=1):o=`�`}else o=String.fromCharCode(a);o&&=(t.push(e.slice(r,n),encodeURIComponent(o)),r=n+i+1,``),i&&=(n+=i,0)}return t.join(``)+e.slice(r)}function Y(e,t,n,r){let i=r?r-1:1/0,a=0;return o;function o(r){return J(r)?(e.enter(n),s(r)):t(r)}function s(r){return J(r)&&a++o))return;let n=t.events.length,a=n,s,c;for(;a--;)if(t.events[a][0]===`exit`&&t.events[a][1].type===`chunkFlow`){if(s){c=t.events[a][1].end;break}s=!0}for(_(r),e=n;er;){let r=n[i];t.containerState=r[1],r[0].exit.call(t,e)}n.length=r}function v(){i.write([null]),a=void 0,i=void 0,t.containerState._closeFlow=void 0}}function St(e,t,n){return Y(e,e.attempt(this.parser.constructs.document,t,n),`linePrefix`,this.parser.constructs.disable.null.includes(`codeIndented`)?void 0:4)}function Ct(e){if(e===null||q(e)||mt(e))return 1;if(pt(e))return 2}function wt(e,t,n){let r=[],i=-1;for(;++i1&&e[n][1].end.offset-e[n][1].start.offset>1?2:1;let d={...e[r][1].end},f={...e[n][1].start};Ot(d,-c),Ot(f,c),o={type:c>1?`strongSequence`:`emphasisSequence`,start:d,end:{...e[r][1].end}},s={type:c>1?`strongSequence`:`emphasisSequence`,start:{...e[n][1].start},end:f},a={type:c>1?`strongText`:`emphasisText`,start:{...e[r][1].end},end:{...e[n][1].start}},i={type:c>1?`strong`:`emphasis`,start:{...o.start},end:{...s.end}},e[r][1].end={...o.start},e[n][1].start={...s.end},l=[],e[r][1].end.offset-e[r][1].start.offset&&(l=H(l,[[`enter`,e[r][1],t],[`exit`,e[r][1],t]])),l=H(l,[[`enter`,i,t],[`enter`,o,t],[`exit`,o,t],[`enter`,a,t]]),l=H(l,wt(t.parser.constructs.insideSpan.null,e.slice(r+1,n),t)),l=H(l,[[`exit`,a,t],[`enter`,s,t],[`exit`,s,t],[`exit`,i,t]]),e[n][1].end.offset-e[n][1].start.offset?(u=2,l=H(l,[[`enter`,e[n][1],t],[`exit`,e[n][1],t]])):u=0,V(e,r-1,n-r+3,l),n=r+l.length-u-2;break}}for(n=-1;++n0&&J(t)?Y(e,v,`linePrefix`,a+1)(t):v(t)}function v(t){return t===null||K(t)?e.check(Vt,h,b)(t):(e.enter(`codeFlowValue`),y(t))}function y(t){return t===null||K(t)?(e.exit(`codeFlowValue`),v(t)):(e.consume(t),y)}function b(n){return e.exit(`codeFenced`),t(n)}function x(e,t,n){let i=0;return a;function a(t){return e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),c}function c(t){return e.enter(`codeFencedFence`),J(t)?Y(e,l,`linePrefix`,r.parser.constructs.disable.null.includes(`codeIndented`)?void 0:4)(t):l(t)}function l(t){return t===s?(e.enter(`codeFencedFenceSequence`),u(t)):n(t)}function u(t){return t===s?(i++,e.consume(t),u):i>=o?(e.exit(`codeFencedFenceSequence`),J(t)?Y(e,d,`whitespace`)(t):d(t)):n(t)}function d(r){return r===null||K(r)?(e.exit(`codeFencedFence`),t(r)):n(r)}}}function Wt(e,t,n){let r=this;return i;function i(t){return t===null?n(t):(e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),a)}function a(e){return r.parser.lazy[r.now().line]?n(e):t(e)}}var Gt={name:`codeIndented`,tokenize:qt},Kt={partial:!0,tokenize:Jt};function qt(e,t,n){let r=this;return i;function i(t){return e.enter(`codeIndented`),Y(e,a,`linePrefix`,5)(t)}function a(e){let t=r.events[r.events.length-1];return t&&t[1].type===`linePrefix`&&t[2].sliceSerialize(t[1],!0).length>=4?o(e):n(e)}function o(t){return t===null?c(t):K(t)?e.attempt(Kt,o,c)(t):(e.enter(`codeFlowValue`),s(t))}function s(t){return t===null||K(t)?(e.exit(`codeFlowValue`),o(t)):(e.consume(t),s)}function c(n){return e.exit(`codeIndented`),t(n)}}function Jt(e,t,n){let r=this;return i;function i(t){return r.parser.lazy[r.now().line]?n(t):K(t)?(e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),i):Y(e,a,`linePrefix`,5)(t)}function a(e){let a=r.events[r.events.length-1];return a&&a[1].type===`linePrefix`&&a[2].sliceSerialize(a[1],!0).length>=4?t(e):K(e)?i(e):n(e)}}var Yt={name:`codeText`,previous:Zt,resolve:Xt,tokenize:Qt};function Xt(e){let t=e.length-4,n=3,r,i;if((e[n][1].type===`lineEnding`||e[n][1].type===`space`)&&(e[t][1].type===`lineEnding`||e[t][1].type===`space`)){for(r=n;++r=this.left.length+this.right.length)throw RangeError("Cannot access index `"+e+"` in a splice buffer of size `"+(this.left.length+this.right.length)+"`");return ethis.left.length?this.right.slice(this.right.length-n+this.left.length,this.right.length-e+this.left.length).reverse():this.left.slice(e).concat(this.right.slice(this.right.length-n+this.left.length).reverse())}splice(e,t,n){let r=t||0;this.setCursor(Math.trunc(e));let i=this.right.splice(this.right.length-r,1/0);return n&&en(this.left,n),i.reverse()}pop(){return this.setCursor(1/0),this.left.pop()}push(e){this.setCursor(1/0),this.left.push(e)}pushMany(e){this.setCursor(1/0),en(this.left,e)}unshift(e){this.setCursor(0),this.right.push(e)}unshiftMany(e){this.setCursor(0),en(this.right,e.reverse())}setCursor(e){if(!(e===this.left.length||e>this.left.length&&this.right.length===0||e<0&&this.left.length===0))if(e=4?t(i):e.interrupt(r.parser.constructs.flow,n,t)(i)}}function ln(e,t,n,r,i,a,o,s,c){let l=c||1/0,u=0;return d;function d(t){return t===60?(e.enter(r),e.enter(i),e.enter(a),e.consume(t),e.exit(a),f):t===null||t===32||t===41||lt(t)?n(t):(e.enter(r),e.enter(o),e.enter(s),e.enter(`chunkString`,{contentType:`string`}),h(t))}function f(n){return n===62?(e.enter(a),e.consume(n),e.exit(a),e.exit(i),e.exit(r),t):(e.enter(s),e.enter(`chunkString`,{contentType:`string`}),p(n))}function p(t){return t===62?(e.exit(`chunkString`),e.exit(s),f(t)):t===null||t===60||K(t)?n(t):(e.consume(t),t===92?m:p)}function m(t){return t===60||t===62||t===92?(e.consume(t),p):p(t)}function h(i){return!u&&(i===null||i===41||q(i))?(e.exit(`chunkString`),e.exit(s),e.exit(o),e.exit(r),t(i)):u999||l===null||l===91||l===93&&!c||l===94&&!s&&`_hiddenFootnoteSupport`in o.parser.constructs?n(l):l===93?(e.exit(a),e.enter(i),e.consume(l),e.exit(i),e.exit(r),t):K(l)?(e.enter(`lineEnding`),e.consume(l),e.exit(`lineEnding`),u):(e.enter(`chunkString`,{contentType:`string`}),d(l))}function d(t){return t===null||t===91||t===93||K(t)||s++>999?(e.exit(`chunkString`),u(t)):(e.consume(t),c||=!J(t),t===92?f:d)}function f(t){return t===91||t===92||t===93?(e.consume(t),s++,d):d(t)}}function dn(e,t,n,r,i,a){let o;return s;function s(t){return t===34||t===39||t===40?(e.enter(r),e.enter(i),e.consume(t),e.exit(i),o=t===40?41:t,c):n(t)}function c(n){return n===o?(e.enter(i),e.consume(n),e.exit(i),e.exit(r),t):(e.enter(a),l(n))}function l(t){return t===o?(e.exit(a),c(o)):t===null?n(t):K(t)?(e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),Y(e,l,`linePrefix`)):(e.enter(`chunkString`,{contentType:`string`}),u(t))}function u(t){return t===o||t===null||K(t)?(e.exit(`chunkString`),l(t)):(e.consume(t),t===92?d:u)}function d(t){return t===o||t===92?(e.consume(t),u):u(t)}}function fn(e,t){let n;return r;function r(i){return K(i)?(e.enter(`lineEnding`),e.consume(i),e.exit(`lineEnding`),n=!0,r):J(i)?Y(e,r,n?`linePrefix`:`lineSuffix`)(i):t(i)}}var pn={name:`definition`,tokenize:hn},mn={partial:!0,tokenize:gn};function hn(e,t,n){let r=this,i;return a;function a(t){return e.enter(`definition`),o(t)}function o(t){return un.call(r,e,s,n,`definitionLabel`,`definitionLabelMarker`,`definitionLabelString`)(t)}function s(t){return i=U(r.sliceSerialize(r.events[r.events.length-1][1]).slice(1,-1)),t===58?(e.enter(`definitionMarker`),e.consume(t),e.exit(`definitionMarker`),c):n(t)}function c(t){return q(t)?fn(e,l)(t):l(t)}function l(t){return ln(e,u,n,`definitionDestination`,`definitionDestinationLiteral`,`definitionDestinationLiteralMarker`,`definitionDestinationRaw`,`definitionDestinationString`)(t)}function u(t){return e.attempt(mn,d,d)(t)}function d(t){return J(t)?Y(e,f,`whitespace`)(t):f(t)}function f(a){return a===null||K(a)?(e.exit(`definition`),r.parser.defined.push(i),t(a)):n(a)}}function gn(e,t,n){return r;function r(t){return q(t)?fn(e,i)(t):n(t)}function i(t){return dn(e,a,n,`definitionTitle`,`definitionTitleMarker`,`definitionTitleString`)(t)}function a(t){return J(t)?Y(e,o,`whitespace`)(t):o(t)}function o(e){return e===null||K(e)?t(e):n(e)}}var _n={name:`hardBreakEscape`,tokenize:vn};function vn(e,t,n){return r;function r(t){return e.enter(`hardBreakEscape`),e.consume(t),i}function i(r){return K(r)?(e.exit(`hardBreakEscape`),t(r)):n(r)}}var yn={name:`headingAtx`,resolve:bn,tokenize:xn};function bn(e,t){let n=e.length-2,r=3,i,a;return e[r][1].type===`whitespace`&&(r+=2),n-2>r&&e[n][1].type===`whitespace`&&(n-=2),e[n][1].type===`atxHeadingSequence`&&(r===n-1||n-4>r&&e[n-2][1].type===`whitespace`)&&(n-=r+1===n?2:4),n>r&&(i={type:`atxHeadingText`,start:e[r][1].start,end:e[n][1].end},a={type:`chunkText`,start:e[r][1].start,end:e[n][1].end,contentType:`text`},V(e,r,n-r+1,[[`enter`,i,t],[`enter`,a,t],[`exit`,a,t],[`exit`,i,t]])),e}function xn(e,t,n){let r=0;return i;function i(t){return e.enter(`atxHeading`),a(t)}function a(t){return e.enter(`atxHeadingSequence`),o(t)}function o(t){return t===35&&r++<6?(e.consume(t),o):t===null||q(t)?(e.exit(`atxHeadingSequence`),s(t)):n(t)}function s(n){return n===35?(e.enter(`atxHeadingSequence`),c(n)):n===null||K(n)?(e.exit(`atxHeading`),t(n)):J(n)?Y(e,s,`whitespace`)(n):(e.enter(`atxHeadingText`),l(n))}function c(t){return t===35?(e.consume(t),c):(e.exit(`atxHeadingSequence`),s(t))}function l(t){return t===null||t===35||q(t)?(e.exit(`atxHeadingText`),s(t)):(e.consume(t),l)}}var Sn=`address.article.aside.base.basefont.blockquote.body.caption.center.col.colgroup.dd.details.dialog.dir.div.dl.dt.fieldset.figcaption.figure.footer.form.frame.frameset.h1.h2.h3.h4.h5.h6.head.header.hr.html.iframe.legend.li.link.main.menu.menuitem.nav.noframes.ol.optgroup.option.p.param.search.section.summary.table.tbody.td.tfoot.th.thead.title.tr.track.ul`.split(`.`),Cn=[`pre`,`script`,`style`,`textarea`],wn={concrete:!0,name:`htmlFlow`,resolveTo:Dn,tokenize:On},Tn={partial:!0,tokenize:An},En={partial:!0,tokenize:kn};function Dn(e){let t=e.length;for(;t--&&!(e[t][0]===`enter`&&e[t][1].type===`htmlFlow`););return t>1&&e[t-2][1].type===`linePrefix`&&(e[t][1].start=e[t-2][1].start,e[t+1][1].start=e[t-2][1].start,e.splice(t-2,2)),e}function On(e,t,n){let r=this,i,a,o,s,c;return l;function l(e){return u(e)}function u(t){return e.enter(`htmlFlow`),e.enter(`htmlFlowData`),e.consume(t),d}function d(s){return s===33?(e.consume(s),f):s===47?(e.consume(s),a=!0,h):s===63?(e.consume(s),i=3,r.interrupt?t:I):W(s)?(e.consume(s),o=String.fromCharCode(s),g):n(s)}function f(a){return a===45?(e.consume(a),i=2,p):a===91?(e.consume(a),i=5,s=0,m):W(a)?(e.consume(a),i=4,r.interrupt?t:I):n(a)}function p(i){return i===45?(e.consume(i),r.interrupt?t:I):n(i)}function m(i){return i===`CDATA[`.charCodeAt(s++)?(e.consume(i),s===6?r.interrupt?t:O:m):n(i)}function h(t){return W(t)?(e.consume(t),o=String.fromCharCode(t),g):n(t)}function g(s){if(s===null||s===47||s===62||q(s)){let c=s===47,l=o.toLowerCase();return!c&&!a&&Cn.includes(l)?(i=1,r.interrupt?t(s):O(s)):Sn.includes(o.toLowerCase())?(i=6,c?(e.consume(s),_):r.interrupt?t(s):O(s)):(i=7,r.interrupt&&!r.parser.lazy[r.now().line]?n(s):a?v(s):y(s))}return s===45||G(s)?(e.consume(s),o+=String.fromCharCode(s),g):n(s)}function _(i){return i===62?(e.consume(i),r.interrupt?t:O):n(i)}function v(t){return J(t)?(e.consume(t),v):E(t)}function y(t){return t===47?(e.consume(t),E):t===58||t===95||W(t)?(e.consume(t),b):J(t)?(e.consume(t),y):E(t)}function b(t){return t===45||t===46||t===58||t===95||G(t)?(e.consume(t),b):x(t)}function x(t){return t===61?(e.consume(t),S):J(t)?(e.consume(t),x):y(t)}function S(t){return t===null||t===60||t===61||t===62||t===96?n(t):t===34||t===39?(e.consume(t),c=t,C):J(t)?(e.consume(t),S):w(t)}function C(t){return t===c?(e.consume(t),c=null,T):t===null||K(t)?n(t):(e.consume(t),C)}function w(t){return t===null||t===34||t===39||t===47||t===60||t===61||t===62||t===96||q(t)?x(t):(e.consume(t),w)}function T(e){return e===47||e===62||J(e)?y(e):n(e)}function E(t){return t===62?(e.consume(t),D):n(t)}function D(t){return t===null||K(t)?O(t):J(t)?(e.consume(t),D):n(t)}function O(t){return t===45&&i===2?(e.consume(t),M):t===60&&i===1?(e.consume(t),N):t===62&&i===4?(e.consume(t),L):t===63&&i===3?(e.consume(t),I):t===93&&i===5?(e.consume(t),F):K(t)&&(i===6||i===7)?(e.exit(`htmlFlowData`),e.check(Tn,R,k)(t)):t===null||K(t)?(e.exit(`htmlFlowData`),k(t)):(e.consume(t),O)}function k(t){return e.check(En,A,R)(t)}function A(t){return e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),j}function j(t){return t===null||K(t)?k(t):(e.enter(`htmlFlowData`),O(t))}function M(t){return t===45?(e.consume(t),I):O(t)}function N(t){return t===47?(e.consume(t),o=``,P):O(t)}function P(t){if(t===62){let n=o.toLowerCase();return Cn.includes(n)?(e.consume(t),L):O(t)}return W(t)&&o.length<8?(e.consume(t),o+=String.fromCharCode(t),P):O(t)}function F(t){return t===93?(e.consume(t),I):O(t)}function I(t){return t===62?(e.consume(t),L):t===45&&i===2?(e.consume(t),I):O(t)}function L(t){return t===null||K(t)?(e.exit(`htmlFlowData`),R(t)):(e.consume(t),L)}function R(n){return e.exit(`htmlFlow`),t(n)}}function kn(e,t,n){let r=this;return i;function i(t){return K(t)?(e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),a):n(t)}function a(e){return r.parser.lazy[r.now().line]?n(e):t(e)}}function An(e,t,n){return r;function r(r){return e.enter(`lineEnding`),e.consume(r),e.exit(`lineEnding`),e.attempt(jt,t,n)}}var jn={name:`htmlText`,tokenize:Mn};function Mn(e,t,n){let r=this,i,a,o;return s;function s(t){return e.enter(`htmlText`),e.enter(`htmlTextData`),e.consume(t),c}function c(t){return t===33?(e.consume(t),l):t===47?(e.consume(t),x):t===63?(e.consume(t),y):W(t)?(e.consume(t),w):n(t)}function l(t){return t===45?(e.consume(t),u):t===91?(e.consume(t),a=0,m):W(t)?(e.consume(t),v):n(t)}function u(t){return t===45?(e.consume(t),p):n(t)}function d(t){return t===null?n(t):t===45?(e.consume(t),f):K(t)?(o=d,N(t)):(e.consume(t),d)}function f(t){return t===45?(e.consume(t),p):d(t)}function p(e){return e===62?M(e):e===45?f(e):d(e)}function m(t){return t===`CDATA[`.charCodeAt(a++)?(e.consume(t),a===6?h:m):n(t)}function h(t){return t===null?n(t):t===93?(e.consume(t),g):K(t)?(o=h,N(t)):(e.consume(t),h)}function g(t){return t===93?(e.consume(t),_):h(t)}function _(t){return t===62?M(t):t===93?(e.consume(t),_):h(t)}function v(t){return t===null||t===62?M(t):K(t)?(o=v,N(t)):(e.consume(t),v)}function y(t){return t===null?n(t):t===63?(e.consume(t),b):K(t)?(o=y,N(t)):(e.consume(t),y)}function b(e){return e===62?M(e):y(e)}function x(t){return W(t)?(e.consume(t),S):n(t)}function S(t){return t===45||G(t)?(e.consume(t),S):C(t)}function C(t){return K(t)?(o=C,N(t)):J(t)?(e.consume(t),C):M(t)}function w(t){return t===45||G(t)?(e.consume(t),w):t===47||t===62||q(t)?T(t):n(t)}function T(t){return t===47?(e.consume(t),M):t===58||t===95||W(t)?(e.consume(t),E):K(t)?(o=T,N(t)):J(t)?(e.consume(t),T):M(t)}function E(t){return t===45||t===46||t===58||t===95||G(t)?(e.consume(t),E):D(t)}function D(t){return t===61?(e.consume(t),O):K(t)?(o=D,N(t)):J(t)?(e.consume(t),D):T(t)}function O(t){return t===null||t===60||t===61||t===62||t===96?n(t):t===34||t===39?(e.consume(t),i=t,k):K(t)?(o=O,N(t)):J(t)?(e.consume(t),O):(e.consume(t),A)}function k(t){return t===i?(e.consume(t),i=void 0,j):t===null?n(t):K(t)?(o=k,N(t)):(e.consume(t),k)}function A(t){return t===null||t===34||t===39||t===60||t===61||t===96?n(t):t===47||t===62||q(t)?T(t):(e.consume(t),A)}function j(e){return e===47||e===62||q(e)?T(e):n(e)}function M(r){return r===62?(e.consume(r),e.exit(`htmlTextData`),e.exit(`htmlText`),t):n(r)}function N(t){return e.exit(`htmlTextData`),e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),P}function P(t){return J(t)?Y(e,F,`linePrefix`,r.parser.constructs.disable.null.includes(`codeIndented`)?void 0:4)(t):F(t)}function F(t){return e.enter(`htmlTextData`),o(t)}}var Nn={name:`labelEnd`,resolveAll:Ln,resolveTo:Rn,tokenize:zn},Pn={tokenize:Bn},Fn={tokenize:Vn},In={tokenize:Hn};function Ln(e){let t=-1,n=[];for(;++t=3&&(a===null||K(a))?(e.exit(`thematicBreak`),t(a)):n(a)}function c(t){return t===i?(e.consume(t),r++,c):(e.exit(`thematicBreakSequence`),J(t)?Y(e,s,`whitespace`)(t):s(t))}}var X={continuation:{tokenize:er},exit:nr,name:`list`,tokenize:$n},Zn={partial:!0,tokenize:rr},Qn={partial:!0,tokenize:tr};function $n(e,t,n){let r=this,i=r.events[r.events.length-1],a=i&&i[1].type===`linePrefix`?i[2].sliceSerialize(i[1],!0).length:0,o=0;return s;function s(t){let i=r.containerState.type||(t===42||t===43||t===45?`listUnordered`:`listOrdered`);if(i===`listUnordered`?!r.containerState.marker||t===r.containerState.marker:ut(t)){if(r.containerState.type||(r.containerState.type=i,e.enter(i,{_container:!0})),i===`listUnordered`)return e.enter(`listItemPrefix`),t===42||t===45?e.check(Yn,n,l)(t):l(t);if(!r.interrupt||t===49)return e.enter(`listItemPrefix`),e.enter(`listItemValue`),c(t)}return n(t)}function c(t){return ut(t)&&++o<10?(e.consume(t),c):(!r.interrupt||o<2)&&(r.containerState.marker?t===r.containerState.marker:t===41||t===46)?(e.exit(`listItemValue`),l(t)):n(t)}function l(t){return e.enter(`listItemMarker`),e.consume(t),e.exit(`listItemMarker`),r.containerState.marker=r.containerState.marker||t,e.check(jt,r.interrupt?n:u,e.attempt(Zn,f,d))}function u(e){return r.containerState.initialBlankLine=!0,a++,f(e)}function d(t){return J(t)?(e.enter(`listItemPrefixWhitespace`),e.consume(t),e.exit(`listItemPrefixWhitespace`),f):n(t)}function f(n){return r.containerState.size=a+r.sliceSerialize(e.exit(`listItemPrefix`),!0).length,t(n)}}function er(e,t,n){let r=this;return r.containerState._closeFlow=void 0,e.check(jt,i,a);function i(n){return r.containerState.furtherBlankLines=r.containerState.furtherBlankLines||r.containerState.initialBlankLine,Y(e,t,`listItemIndent`,r.containerState.size+1)(n)}function a(n){return r.containerState.furtherBlankLines||!J(n)?(r.containerState.furtherBlankLines=void 0,r.containerState.initialBlankLine=void 0,o(n)):(r.containerState.furtherBlankLines=void 0,r.containerState.initialBlankLine=void 0,e.attempt(Qn,t,o)(n))}function o(i){return r.containerState._closeFlow=!0,r.interrupt=void 0,Y(e,e.attempt(X,t,n),`linePrefix`,r.parser.constructs.disable.null.includes(`codeIndented`)?void 0:4)(i)}}function tr(e,t,n){let r=this;return Y(e,i,`listItemIndent`,r.containerState.size+1);function i(e){let i=r.events[r.events.length-1];return i&&i[1].type===`listItemIndent`&&i[2].sliceSerialize(i[1],!0).length===r.containerState.size?t(e):n(e)}}function nr(e){e.exit(this.containerState.type)}function rr(e,t,n){let r=this;return Y(e,i,`listItemPrefixWhitespace`,r.parser.constructs.disable.null.includes(`codeIndented`)?void 0:5);function i(e){let i=r.events[r.events.length-1];return!J(e)&&i&&i[1].type===`listItemPrefixWhitespace`?t(e):n(e)}}var ir={name:`setextUnderline`,resolveTo:ar,tokenize:or};function ar(e,t){let n=e.length,r,i,a;for(;n--;)if(e[n][0]===`enter`){if(e[n][1].type===`content`){r=n;break}e[n][1].type===`paragraph`&&(i=n)}else e[n][1].type===`content`&&e.splice(n,1),!a&&e[n][1].type===`definition`&&(a=n);let o={type:`setextHeading`,start:{...e[r][1].start},end:{...e[e.length-1][1].end}};return e[i][1].type=`setextHeadingText`,a?(e.splice(i,0,[`enter`,o,t]),e.splice(a+1,0,[`exit`,e[r][1],t]),e[r][1].end={...e[a][1].end}):e[r][1]=o,e.push([`exit`,o,t]),e}function or(e,t,n){let r=this,i;return a;function a(t){let a=r.events.length,s;for(;a--;)if(r.events[a][1].type!==`lineEnding`&&r.events[a][1].type!==`linePrefix`&&r.events[a][1].type!==`content`){s=r.events[a][1].type===`paragraph`;break}return!r.parser.lazy[r.now().line]&&(r.interrupt||s)?(e.enter(`setextHeadingLine`),i=t,o(t)):n(t)}function o(t){return e.enter(`setextHeadingLineSequence`),s(t)}function s(t){return t===i?(e.consume(t),s):(e.exit(`setextHeadingLineSequence`),J(t)?Y(e,c,`lineSuffix`)(t):c(t))}function c(r){return r===null||K(r)?(e.exit(`setextHeadingLine`),t(r)):n(r)}}var sr={tokenize:cr};function cr(e){let t=this,n=e.attempt(jt,r,e.attempt(this.parser.constructs.flowInitial,i,Y(e,e.attempt(this.parser.constructs.flow,i,e.attempt(rn,i)),`linePrefix`)));return n;function r(r){if(r===null){e.consume(r);return}return e.enter(`lineEndingBlank`),e.consume(r),e.exit(`lineEndingBlank`),t.currentConstruct=void 0,n}function i(r){if(r===null){e.consume(r);return}return e.enter(`lineEnding`),e.consume(r),e.exit(`lineEnding`),t.currentConstruct=void 0,n}}var lr={resolveAll:pr()},ur=fr(`string`),dr=fr(`text`);function fr(e){return{resolveAll:pr(e===`text`?mr:void 0),tokenize:t};function t(t){let n=this,r=this.parser.constructs[e],i=t.attempt(r,a,o);return a;function a(e){return c(e)?i(e):o(e)}function o(e){if(e===null){t.consume(e);return}return t.enter(`data`),t.consume(e),s}function s(e){return c(e)?(t.exit(`data`),i(e)):(t.consume(e),s)}function c(e){if(e===null)return!0;let t=r[e],i=-1;if(t)for(;++iCr,contentInitial:()=>_r,disable:()=>wr,document:()=>gr,flow:()=>yr,flowInitial:()=>vr,insideSpan:()=>Sr,string:()=>br,text:()=>xr}),gr={42:X,43:X,45:X,48:X,49:X,50:X,51:X,52:X,53:X,54:X,55:X,56:X,57:X,62:Nt},_r={91:pn},vr={[-2]:Gt,[-1]:Gt,32:Gt},yr={35:yn,42:Yn,45:[ir,Yn],60:wn,61:ir,95:Yn,96:Ht,126:Ht},br={38:zt,92:Lt},xr={[-5]:qn,[-4]:qn,[-3]:qn,33:Un,38:zt,42:Tt,60:[kt,jn],91:Gn,92:[_n,Lt],93:Nn,95:Tt,96:Yt},Sr={null:[Tt,lr]},Cr={null:[42,95]},wr={null:[]};function Tr(e,t,n){let r={_bufferIndex:-1,_index:0,line:n&&n.line||1,column:n&&n.column||1,offset:n&&n.offset||0},i={},a=[],o=[],s=[],c={attempt:C(x),check:C(S),consume:v,enter:y,exit:b,interrupt:C(S,{interrupt:!0})},l={code:null,containerState:{},defineSkip:h,events:[],now:m,parser:e,previous:null,sliceSerialize:f,sliceStream:p,write:d},u=t.tokenize.call(l,c);return t.resolveAll&&a.push(t),l;function d(e){return o=H(o,e),g(),o[o.length-1]===null?(w(t,0),l.events=wt(a,l.events,l),l.events):[]}function f(e,t){return Dr(p(e),t)}function p(e){return Er(o,e)}function m(){let{_bufferIndex:e,_index:t,line:n,column:i,offset:a}=r;return{_bufferIndex:e,_index:t,line:n,column:i,offset:a}}function h(e){i[e.line]=e.column,E()}function g(){let e;for(;r._index-1){let e=o[0];typeof e==`string`?o[0]=e.slice(r):o.shift()}a>0&&o.push(e[i].slice(0,a))}return o}function Dr(e,t){let n=-1,r=[],i;for(;++n0){let e=a.tokenStack[a.tokenStack.length-1];(e[1]||Vr).call(a,void 0,e[0])}for(r.position={start:Rr(e.length>0?e[0][1].start:{line:1,column:1,offset:0}),end:Rr(e.length>0?e[e.length-2][1].end:{line:1,column:1,offset:0})},d=-1;++d0&&n.push({type:`text`,value:` `}),n}function ta(e){let t=0,n=e.charCodeAt(t);for(;n===9||n===32;)t++,n=e.charCodeAt(t);return e.slice(t)}function na(e,t){let n=Xi(e,t),r=n.one(e,void 0),i=Fi(n),a=Array.isArray(r)?{type:`root`,children:r}:r||{type:`root`,children:[]};return i&&(`children`in a,a.children.push({type:`text`,value:` -`},i)),a}function ra(e,t){return e&&`run`in e?async function(n,r){let i=na(n,{file:r,...t});await e.run(i,r)}:function(n,r){return na(n,{file:r,...e||t})}}function ia(e){if(e)throw e}var aa=i(((e,t)=>{var n=Object.prototype.hasOwnProperty,r=Object.prototype.toString,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=function(e){return typeof Array.isArray==`function`?Array.isArray(e):r.call(e)===`[object Array]`},s=function(e){if(!e||r.call(e)!==`[object Object]`)return!1;var t=n.call(e,`constructor`),i=e.constructor&&e.constructor.prototype&&n.call(e.constructor.prototype,`isPrototypeOf`);if(e.constructor&&!t&&!i)return!1;for(var a in e);return a===void 0||n.call(e,a)},c=function(e,t){i&&t.name===`__proto__`?i(e,t.name,{enumerable:!0,configurable:!0,value:t.newValue,writable:!0}):e[t.name]=t.newValue},l=function(e,t){if(t===`__proto__`){if(!n.call(e,t))return;if(a)return a(e,t).value}return e[t]};t.exports=function e(){var t,n,r,i,a,u,d=arguments[0],f=1,p=arguments.length,m=!1;for(typeof d==`boolean`&&(m=d,d=arguments[1]||{},f=2),(d==null||typeof d!=`object`&&typeof d!=`function`)&&(d={});ft.length,o;r&&t.push(i);try{o=e.apply(this,t)}catch(e){let t=e;if(r&&n)throw t;return i(t)}r||(o&&o.then&&typeof o.then==`function`?o.then(a,i):o instanceof Error?i(o):a(o))}function i(e,...r){n||(n=!0,t(e,...r))}function a(e){i(null,e)}}var Z={basename:la,dirname:ua,extname:da,join:fa,sep:`/`};function la(e,t){if(t!==void 0&&typeof t!=`string`)throw TypeError(`"ext" argument must be a string`);ha(e);let n=0,r=-1,i=e.length,a;if(t===void 0||t.length===0||t.length>e.length){for(;i--;)if(e.codePointAt(i)===47){if(a){n=i+1;break}}else r<0&&(a=!0,r=i+1);return r<0?``:e.slice(n,r)}if(t===e)return``;let o=-1,s=t.length-1;for(;i--;)if(e.codePointAt(i)===47){if(a){n=i+1;break}}else o<0&&(a=!0,o=i+1),s>-1&&(e.codePointAt(i)===t.codePointAt(s--)?s<0&&(r=i):(s=-1,r=o));return n===r?r=o:r<0&&(r=e.length),e.slice(n,r)}function ua(e){if(ha(e),e.length===0)return`.`;let t=-1,n=e.length,r;for(;--n;)if(e.codePointAt(n)===47){if(r){t=n;break}}else r||=!0;return t<0?e.codePointAt(0)===47?`/`:`.`:t===1&&e.codePointAt(0)===47?`//`:e.slice(0,t)}function da(e){ha(e);let t=e.length,n=-1,r=0,i=-1,a=0,o;for(;t--;){let s=e.codePointAt(t);if(s===47){if(o){r=t+1;break}continue}n<0&&(o=!0,n=t+1),s===46?i<0?i=t:a!==1&&(a=1):i>-1&&(a=-1)}return i<0||n<0||a===0||a===1&&i===n-1&&i===r+1?``:e.slice(i,n)}function fa(...e){let t=-1,n;for(;++t0&&e.codePointAt(e.length-1)===47&&(n+=`/`),t?`/`+n:n}function ma(e,t){let n=``,r=0,i=-1,a=0,o=-1,s,c;for(;++o<=e.length;){if(o2){if(c=n.lastIndexOf(`/`),c!==n.length-1){c<0?(n=``,r=0):(n=n.slice(0,c),r=n.length-1-n.lastIndexOf(`/`)),i=o,a=0;continue}}else if(n.length>0){n=``,r=0,i=o,a=0;continue}}t&&(n=n.length>0?n+`/..`:`..`,r=2)}else n.length>0?n+=`/`+e.slice(i+1,o):n=e.slice(i+1,o),r=o-i-1;i=o,a=0}else s===46&&a>-1?a++:a=-1}return n}function ha(e){if(typeof e!=`string`)throw TypeError(`Path must be a string. Received `+JSON.stringify(e))}var ga={cwd:_a};function _a(){return`/`}function va(e){return!!(typeof e==`object`&&e&&`href`in e&&e.href&&`protocol`in e&&e.protocol&&e.auth===void 0)}function ya(e){if(typeof e==`string`)e=new URL(e);else if(!va(e)){let t=TypeError('The "path" argument must be of type string or an instance of URL. Received `'+e+"`");throw t.code=`ERR_INVALID_ARG_TYPE`,t}if(e.protocol!==`file:`){let e=TypeError(`The URL must be of scheme file`);throw e.code=`ERR_INVALID_URL_SCHEME`,e}return ba(e)}function ba(e){if(e.hostname!==``){let e=TypeError(`File URL host must be "localhost" or empty on darwin`);throw e.code=`ERR_INVALID_FILE_URL_HOST`,e}let t=e.pathname,n=-1;for(;++n0){let[r,...a]=t,o=n[i][1];oa(o)&&oa(r)&&(r=(0,Oa.default)(!0,o,r)),n[i]=[e,r,...a]}}}}().freeze();function ja(e,t){if(typeof t!=`function`)throw TypeError("Cannot `"+e+"` without `parser`")}function Ma(e,t){if(typeof t!=`function`)throw TypeError("Cannot `"+e+"` without `compiler`")}function Na(e,t){if(t)throw Error("Cannot call `"+e+"` on a frozen processor.\nCreate a new processor first, by calling it: use `processor()` instead of `processor`.")}function Pa(e){if(!oa(e)||typeof e.type!=`string`)throw TypeError("Expected node, got `"+e+"`")}function Fa(e,t,n){if(!n)throw Error("`"+e+"` finished async. Use `"+t+"` instead")}function Ia(e){return La(e)?e:new Sa(e)}function La(e){return!!(e&&typeof e==`object`&&`message`in e&&`messages`in e)}function Ra(e){return typeof e==`string`||za(e)}function za(e){return!!(e&&typeof e==`object`&&`byteLength`in e&&`byteOffset`in e)}var Ba=e();r();var Va=[],Ha={allowDangerousHtml:!0},Ua=/^(https?|ircs?|mailto|xmpp)$/i,Wa=[{from:`astPlugins`,id:`remove-buggy-html-in-markdown-parser`},{from:`allowDangerousHtml`,id:`remove-buggy-html-in-markdown-parser`},{from:`allowNode`,id:`replace-allownode-allowedtypes-and-disallowedtypes`,to:`allowElement`},{from:`allowedTypes`,id:`replace-allownode-allowedtypes-and-disallowedtypes`,to:`allowedElements`},{from:`className`,id:`remove-classname`},{from:`disallowedTypes`,id:`replace-allownode-allowedtypes-and-disallowedtypes`,to:`disallowedElements`},{from:`escapeHtml`,id:`remove-buggy-html-in-markdown-parser`},{from:`includeElementIndex`,id:`#remove-includeelementindex`},{from:`includeNodeIndex`,id:`change-includenodeindex-to-includeelementindex`},{from:`linkTarget`,id:`remove-linktarget`},{from:`plugins`,id:`change-plugins-to-remarkplugins`,to:`remarkPlugins`},{from:`rawSourcePos`,id:`#remove-rawsourcepos`},{from:`renderers`,id:`change-renderers-to-components`,to:`components`},{from:`source`,id:`change-source-to-children`,to:`children`},{from:`sourcePos`,id:`#remove-sourcepos`},{from:`transformImageUri`,id:`#add-urltransform`,to:`urlTransform`},{from:`transformLinkUri`,id:`#add-urltransform`,to:`urlTransform`}];function Ga(e){let t=Ka(e),n=qa(e);return Ja(t.runSync(t.parse(n),n),e)}function Ka(e){let t=e.rehypePlugins||Va,n=e.remarkPlugins||Va,r=e.remarkRehypeOptions?{...e.remarkRehypeOptions,...Ha}:Ha;return Aa().use(Hr).use(n).use(ra,r).use(t)}function qa(e){let t=e.children||``,n=new Sa;return typeof t==`string`?n.value=t:``+t,n}function Ja(e,t){let n=t.allowedElements,r=t.allowElement,i=t.components,a=t.disallowedElements,o=t.skipHtml,s=t.unwrapDisallowed,c=t.urlTransform||Ya;for(let e of Wa)Object.hasOwn(t,e.from)&&``+e.from+(e.to?"use `"+e.to+"` instead":`remove it`)+e.id;return qi(e,l),De(e,{Fragment:Ba.Fragment,components:i,ignoreInvalidStyle:!0,jsx:Ba.jsx,jsxs:Ba.jsxs,passKeys:!0,passNode:!0});function l(e,t,i){if(e.type===`raw`&&i&&typeof t==`number`)return o?i.children.splice(t,1):i.children[t]={type:`text`,value:e.value},t;if(e.type===`element`){let t;for(t in Ye)if(Object.hasOwn(Ye,t)&&Object.hasOwn(e.properties,t)){let n=e.properties[t],r=Ye[t];(r===null||r.includes(e.tagName))&&(e.properties[t]=c(String(n||``),t,e))}}if(e.type===`element`){let o=n?!n.includes(e.tagName):a?a.includes(e.tagName):!1;if(!o&&r&&typeof t==`number`&&(o=!r(e,t,i)),o&&i&&typeof t==`number`)return s&&e.children?i.children.splice(t,1,...e.children):i.children.splice(t,1),t}}}function Ya(e){let t=e.indexOf(`:`),n=e.indexOf(`?`),r=e.indexOf(`#`),i=e.indexOf(`/`);return t===-1||i!==-1&&t>i||n!==-1&&t>n||r!==-1&&t>r||Ua.test(e.slice(0,t))?e:``}function Xa(e,t){let n=String(e);if(typeof t!=`string`)throw TypeError(`Expected character`);let r=0,i=n.indexOf(t);for(;i!==-1;)r++,i=n.indexOf(t,i+t.length);return r}function Za(e){if(typeof e!=`string`)throw TypeError(`Expected a string`);return e.replace(/[|\\{}()[\]^$+*?.]/g,`\\$&`).replace(/-/g,`\\x2d`)}function Qa(e,t,n){let r=Ii((n||{}).ignore||[]),i=$a(t),a=-1;for(;++a0?{type:`text`,value:a}:void 0),a===!1?r.lastIndex=n+1:(s!==n&&u.push({type:`text`,value:e.value.slice(s,n)}),Array.isArray(a)?u.push(...a):a&&u.push(a),s=n+d[0].length,l=!0),!r.global)break;d=r.exec(e.value)}return l?(s?\]}]+$/.exec(e);if(!t)return[e,void 0];e=e.slice(0,t.index);let n=t[0],r=n.indexOf(`)`),i=Xa(e,`(`),a=Xa(e,`)`);for(;r!==-1&&i>a;)e+=n.slice(0,r+1),n=n.slice(r+1),r=n.indexOf(`)`),a++;return[e,n]}function vo(e,t){let n=e.input.charCodeAt(e.index-1);return(e.index===0||mt(n)||pt(n))&&(!t||n!==47)}Oo.peek=Do;function yo(){this.buffer()}function bo(e){this.enter({type:`footnoteReference`,identifier:``,label:``},e)}function xo(){this.buffer()}function So(e){this.enter({type:`footnoteDefinition`,identifier:``,label:``,children:[]},e)}function Co(e){let t=this.resume(),n=this.stack[this.stack.length-1];n.type,n.identifier=U(this.sliceSerialize(e)).toLowerCase(),n.label=t}function wo(e){this.exit(e)}function To(e){let t=this.resume(),n=this.stack[this.stack.length-1];n.type,n.identifier=U(this.sliceSerialize(e)).toLowerCase(),n.label=t}function Eo(e){this.exit(e)}function Do(){return`[`}function Oo(e,t,n,r){let i=n.createTracker(r),a=i.move(`[^`),o=n.enter(`footnoteReference`),s=n.enter(`reference`);return a+=i.move(n.safe(n.associationId(e),{after:`]`,before:a})),s(),o(),a+=i.move(`]`),a}function ko(){return{enter:{gfmFootnoteCallString:yo,gfmFootnoteCall:bo,gfmFootnoteDefinitionLabelString:xo,gfmFootnoteDefinition:So},exit:{gfmFootnoteCallString:Co,gfmFootnoteCall:wo,gfmFootnoteDefinitionLabelString:To,gfmFootnoteDefinition:Eo}}}function Ao(e){let t=!1;return e&&e.firstLineBlank&&(t=!0),{handlers:{footnoteDefinition:n,footnoteReference:Oo},unsafe:[{character:`[`,inConstruct:[`label`,`phrasing`,`reference`]}]};function n(e,n,r,i){let a=r.createTracker(i),o=a.move(`[^`),s=r.enter(`footnoteDefinition`),c=r.enter(`label`);return o+=a.move(r.safe(r.associationId(e),{before:o,after:`]`})),c(),o+=a.move(`]:`),e.children&&e.children.length>0&&(a.shift(4),o+=a.move((t?` +`},i)),a}function ra(e,t){return e&&`run`in e?async function(n,r){let i=na(n,{file:r,...t});await e.run(i,r)}:function(n,r){return na(n,{file:r,...e||t})}}function ia(e){if(e)throw e}var aa=i(((e,t)=>{var n=Object.prototype.hasOwnProperty,r=Object.prototype.toString,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=function(e){return typeof Array.isArray==`function`?Array.isArray(e):r.call(e)===`[object Array]`},s=function(e){if(!e||r.call(e)!==`[object Object]`)return!1;var t=n.call(e,`constructor`),i=e.constructor&&e.constructor.prototype&&n.call(e.constructor.prototype,`isPrototypeOf`);if(e.constructor&&!t&&!i)return!1;for(var a in e);return a===void 0||n.call(e,a)},c=function(e,t){i&&t.name===`__proto__`?i(e,t.name,{enumerable:!0,configurable:!0,value:t.newValue,writable:!0}):e[t.name]=t.newValue},l=function(e,t){if(t===`__proto__`){if(!n.call(e,t))return;if(a)return a(e,t).value}return e[t]};t.exports=function e(){var t,n,r,i,a,u,d=arguments[0],f=1,p=arguments.length,m=!1;for(typeof d==`boolean`&&(m=d,d=arguments[1]||{},f=2),(d==null||typeof d!=`object`&&typeof d!=`function`)&&(d={});ft.length,o;r&&t.push(i);try{o=e.apply(this,t)}catch(e){let t=e;if(r&&n)throw t;return i(t)}r||(o&&o.then&&typeof o.then==`function`?o.then(a,i):o instanceof Error?i(o):a(o))}function i(e,...r){n||(n=!0,t(e,...r))}function a(e){i(null,e)}}var Z={basename:la,dirname:ua,extname:da,join:fa,sep:`/`};function la(e,t){if(t!==void 0&&typeof t!=`string`)throw TypeError(`"ext" argument must be a string`);ha(e);let n=0,r=-1,i=e.length,a;if(t===void 0||t.length===0||t.length>e.length){for(;i--;)if(e.codePointAt(i)===47){if(a){n=i+1;break}}else r<0&&(a=!0,r=i+1);return r<0?``:e.slice(n,r)}if(t===e)return``;let o=-1,s=t.length-1;for(;i--;)if(e.codePointAt(i)===47){if(a){n=i+1;break}}else o<0&&(a=!0,o=i+1),s>-1&&(e.codePointAt(i)===t.codePointAt(s--)?s<0&&(r=i):(s=-1,r=o));return n===r?r=o:r<0&&(r=e.length),e.slice(n,r)}function ua(e){if(ha(e),e.length===0)return`.`;let t=-1,n=e.length,r;for(;--n;)if(e.codePointAt(n)===47){if(r){t=n;break}}else r||=!0;return t<0?e.codePointAt(0)===47?`/`:`.`:t===1&&e.codePointAt(0)===47?`//`:e.slice(0,t)}function da(e){ha(e);let t=e.length,n=-1,r=0,i=-1,a=0,o;for(;t--;){let s=e.codePointAt(t);if(s===47){if(o){r=t+1;break}continue}n<0&&(o=!0,n=t+1),s===46?i<0?i=t:a!==1&&(a=1):i>-1&&(a=-1)}return i<0||n<0||a===0||a===1&&i===n-1&&i===r+1?``:e.slice(i,n)}function fa(...e){let t=-1,n;for(;++t0&&e.codePointAt(e.length-1)===47&&(n+=`/`),t?`/`+n:n}function ma(e,t){let n=``,r=0,i=-1,a=0,o=-1,s,c;for(;++o<=e.length;){if(o2){if(c=n.lastIndexOf(`/`),c!==n.length-1){c<0?(n=``,r=0):(n=n.slice(0,c),r=n.length-1-n.lastIndexOf(`/`)),i=o,a=0;continue}}else if(n.length>0){n=``,r=0,i=o,a=0;continue}}t&&(n=n.length>0?n+`/..`:`..`,r=2)}else n.length>0?n+=`/`+e.slice(i+1,o):n=e.slice(i+1,o),r=o-i-1;i=o,a=0}else s===46&&a>-1?a++:a=-1}return n}function ha(e){if(typeof e!=`string`)throw TypeError(`Path must be a string. Received `+JSON.stringify(e))}var ga={cwd:_a};function _a(){return`/`}function va(e){return!!(typeof e==`object`&&e&&`href`in e&&e.href&&`protocol`in e&&e.protocol&&e.auth===void 0)}function ya(e){if(typeof e==`string`)e=new URL(e);else if(!va(e)){let t=TypeError('The "path" argument must be of type string or an instance of URL. Received `'+e+"`");throw t.code=`ERR_INVALID_ARG_TYPE`,t}if(e.protocol!==`file:`){let e=TypeError(`The URL must be of scheme file`);throw e.code=`ERR_INVALID_URL_SCHEME`,e}return ba(e)}function ba(e){if(e.hostname!==``){let e=TypeError(`File URL host must be "localhost" or empty on darwin`);throw e.code=`ERR_INVALID_FILE_URL_HOST`,e}let t=e.pathname,n=-1;for(;++n0){let[r,...a]=t,o=n[i][1];oa(o)&&oa(r)&&(r=(0,Oa.default)(!0,o,r)),n[i]=[e,r,...a]}}}}().freeze();function ja(e,t){if(typeof t!=`function`)throw TypeError("Cannot `"+e+"` without `parser`")}function Ma(e,t){if(typeof t!=`function`)throw TypeError("Cannot `"+e+"` without `compiler`")}function Na(e,t){if(t)throw Error("Cannot call `"+e+"` on a frozen processor.\nCreate a new processor first, by calling it: use `processor()` instead of `processor`.")}function Pa(e){if(!oa(e)||typeof e.type!=`string`)throw TypeError("Expected node, got `"+e+"`")}function Fa(e,t,n){if(!n)throw Error("`"+e+"` finished async. Use `"+t+"` instead")}function Ia(e){return La(e)?e:new Sa(e)}function La(e){return!!(e&&typeof e==`object`&&`message`in e&&`messages`in e)}function Ra(e){return typeof e==`string`||za(e)}function za(e){return!!(e&&typeof e==`object`&&`byteLength`in e&&`byteOffset`in e)}var Ba=r();n();var Va=[],Ha={allowDangerousHtml:!0},Ua=/^(https?|ircs?|mailto|xmpp)$/i,Wa=[{from:`astPlugins`,id:`remove-buggy-html-in-markdown-parser`},{from:`allowDangerousHtml`,id:`remove-buggy-html-in-markdown-parser`},{from:`allowNode`,id:`replace-allownode-allowedtypes-and-disallowedtypes`,to:`allowElement`},{from:`allowedTypes`,id:`replace-allownode-allowedtypes-and-disallowedtypes`,to:`allowedElements`},{from:`className`,id:`remove-classname`},{from:`disallowedTypes`,id:`replace-allownode-allowedtypes-and-disallowedtypes`,to:`disallowedElements`},{from:`escapeHtml`,id:`remove-buggy-html-in-markdown-parser`},{from:`includeElementIndex`,id:`#remove-includeelementindex`},{from:`includeNodeIndex`,id:`change-includenodeindex-to-includeelementindex`},{from:`linkTarget`,id:`remove-linktarget`},{from:`plugins`,id:`change-plugins-to-remarkplugins`,to:`remarkPlugins`},{from:`rawSourcePos`,id:`#remove-rawsourcepos`},{from:`renderers`,id:`change-renderers-to-components`,to:`components`},{from:`source`,id:`change-source-to-children`,to:`children`},{from:`sourcePos`,id:`#remove-sourcepos`},{from:`transformImageUri`,id:`#add-urltransform`,to:`urlTransform`},{from:`transformLinkUri`,id:`#add-urltransform`,to:`urlTransform`}];function Ga(e){let t=Ka(e),n=qa(e);return Ja(t.runSync(t.parse(n),n),e)}function Ka(e){let t=e.rehypePlugins||Va,n=e.remarkPlugins||Va,r=e.remarkRehypeOptions?{...e.remarkRehypeOptions,...Ha}:Ha;return Aa().use(Hr).use(n).use(ra,r).use(t)}function qa(e){let t=e.children||``,n=new Sa;return typeof t==`string`?n.value=t:``+t,n}function Ja(e,t){let n=t.allowedElements,r=t.allowElement,i=t.components,a=t.disallowedElements,o=t.skipHtml,s=t.unwrapDisallowed,c=t.urlTransform||Ya;for(let e of Wa)Object.hasOwn(t,e.from)&&``+e.from+(e.to?"use `"+e.to+"` instead":`remove it`)+e.id;return qi(e,l),De(e,{Fragment:Ba.Fragment,components:i,ignoreInvalidStyle:!0,jsx:Ba.jsx,jsxs:Ba.jsxs,passKeys:!0,passNode:!0});function l(e,t,i){if(e.type===`raw`&&i&&typeof t==`number`)return o?i.children.splice(t,1):i.children[t]={type:`text`,value:e.value},t;if(e.type===`element`){let t;for(t in Ye)if(Object.hasOwn(Ye,t)&&Object.hasOwn(e.properties,t)){let n=e.properties[t],r=Ye[t];(r===null||r.includes(e.tagName))&&(e.properties[t]=c(String(n||``),t,e))}}if(e.type===`element`){let o=n?!n.includes(e.tagName):a?a.includes(e.tagName):!1;if(!o&&r&&typeof t==`number`&&(o=!r(e,t,i)),o&&i&&typeof t==`number`)return s&&e.children?i.children.splice(t,1,...e.children):i.children.splice(t,1),t}}}function Ya(e){let t=e.indexOf(`:`),n=e.indexOf(`?`),r=e.indexOf(`#`),i=e.indexOf(`/`);return t===-1||i!==-1&&t>i||n!==-1&&t>n||r!==-1&&t>r||Ua.test(e.slice(0,t))?e:``}function Xa(e,t){let n=String(e);if(typeof t!=`string`)throw TypeError(`Expected character`);let r=0,i=n.indexOf(t);for(;i!==-1;)r++,i=n.indexOf(t,i+t.length);return r}function Za(e){if(typeof e!=`string`)throw TypeError(`Expected a string`);return e.replace(/[|\\{}()[\]^$+*?.]/g,`\\$&`).replace(/-/g,`\\x2d`)}function Qa(e,t,n){let r=Ii((n||{}).ignore||[]),i=$a(t),a=-1;for(;++a0?{type:`text`,value:a}:void 0),a===!1?r.lastIndex=n+1:(s!==n&&u.push({type:`text`,value:e.value.slice(s,n)}),Array.isArray(a)?u.push(...a):a&&u.push(a),s=n+d[0].length,l=!0),!r.global)break;d=r.exec(e.value)}return l?(s?\]}]+$/.exec(e);if(!t)return[e,void 0];e=e.slice(0,t.index);let n=t[0],r=n.indexOf(`)`),i=Xa(e,`(`),a=Xa(e,`)`);for(;r!==-1&&i>a;)e+=n.slice(0,r+1),n=n.slice(r+1),r=n.indexOf(`)`),a++;return[e,n]}function vo(e,t){let n=e.input.charCodeAt(e.index-1);return(e.index===0||mt(n)||pt(n))&&(!t||n!==47)}Oo.peek=Do;function yo(){this.buffer()}function bo(e){this.enter({type:`footnoteReference`,identifier:``,label:``},e)}function xo(){this.buffer()}function So(e){this.enter({type:`footnoteDefinition`,identifier:``,label:``,children:[]},e)}function Co(e){let t=this.resume(),n=this.stack[this.stack.length-1];n.type,n.identifier=U(this.sliceSerialize(e)).toLowerCase(),n.label=t}function wo(e){this.exit(e)}function To(e){let t=this.resume(),n=this.stack[this.stack.length-1];n.type,n.identifier=U(this.sliceSerialize(e)).toLowerCase(),n.label=t}function Eo(e){this.exit(e)}function Do(){return`[`}function Oo(e,t,n,r){let i=n.createTracker(r),a=i.move(`[^`),o=n.enter(`footnoteReference`),s=n.enter(`reference`);return a+=i.move(n.safe(n.associationId(e),{after:`]`,before:a})),s(),o(),a+=i.move(`]`),a}function ko(){return{enter:{gfmFootnoteCallString:yo,gfmFootnoteCall:bo,gfmFootnoteDefinitionLabelString:xo,gfmFootnoteDefinition:So},exit:{gfmFootnoteCallString:Co,gfmFootnoteCall:wo,gfmFootnoteDefinitionLabelString:To,gfmFootnoteDefinition:Eo}}}function Ao(e){let t=!1;return e&&e.firstLineBlank&&(t=!0),{handlers:{footnoteDefinition:n,footnoteReference:Oo},unsafe:[{character:`[`,inConstruct:[`label`,`phrasing`,`reference`]}]};function n(e,n,r,i){let a=r.createTracker(i),o=a.move(`[^`),s=r.enter(`footnoteDefinition`),c=r.enter(`label`);return o+=a.move(r.safe(r.associationId(e),{before:o,after:`]`})),c(),o+=a.move(`]:`),e.children&&e.children.length>0&&(a.shift(4),o+=a.move((t?` `:` `)+r.indentLines(r.containerFlow(e,a.current()),t?Mo:jo))),s(),o}}function jo(e,t,n){return t===0?e:Mo(e,t,n)}function Mo(e,t,n){return(n?``:` `)+e}var No=[`autolink`,`destinationLiteral`,`destinationRaw`,`reference`,`titleQuote`,`titleApostrophe`];Ro.peek=zo;function Po(){return{canContainEols:[`delete`],enter:{strikethrough:Io},exit:{strikethrough:Lo}}}function Fo(){return{unsafe:[{character:`~`,inConstruct:`phrasing`,notInConstruct:No}],handlers:{delete:Ro}}}function Io(e){this.enter({type:`delete`,children:[]},e)}function Lo(e){this.exit(e)}function Ro(e,t,n,r){let i=n.createTracker(r),a=n.enter(`strikethrough`),o=i.move(`~~`);return o+=n.containerPhrasing(e,{...i.current(),before:o,after:`~`}),o+=i.move(`~~`),a(),o}function zo(){return`~`}function Bo(e){return e.length}function Vo(e,t){let n=t||{},r=(n.align||[]).concat(),i=n.stringLength||Bo,a=[],o=[],s=[],c=[],l=0,u=-1;for(;++ul&&(l=e[u].length);++ac[a])&&(c[a]=e)}t.push(o)}o[u]=t,s[u]=r}let d=-1;if(typeof r==`object`&&`length`in r)for(;++dc[d]&&(c[d]=i),p[d]=i),f[d]=o}o.splice(1,0,f),s.splice(1,0,p),u=-1;let m=[];for(;++u `),a.shift(2);let o=n.indentLines(n.containerFlow(e,a.current()),Go);return i(),o}function Go(e,t,n){return`>`+(n?``:` `)+e}function Ko(e,t){return qo(e,t.inConstruct,!0)&&!qo(e,t.notInConstruct,!1)}function qo(e,t,n){if(typeof t==`string`&&(t=[t]),!t||t.length===0)return n;let r=-1;for(;++r]+>`)+`)`,a={className:`type`,begin:`\\b[a-z\\d_]*_t\\b`},o={className:`string`,variants:[{begin:`(u8?|U|L)?"`,end:`"`,illegal:`\\n`,contains:[e.BACKSLASH_ESCAPE]},{begin:`(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)`,end:`'`,illegal:`.`},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},s={className:`number`,variants:[{begin:`[+-]?(?:(?:[0-9](?:'?[0-9])*\\.(?:[0-9](?:'?[0-9])*)?|\\.[0-9](?:'?[0-9])*)(?:[Ee][+-]?[0-9](?:'?[0-9])*)?|[0-9](?:'?[0-9])*[Ee][+-]?[0-9](?:'?[0-9])*|0[Xx](?:[0-9A-Fa-f](?:'?[0-9A-Fa-f])*(?:\\.(?:[0-9A-Fa-f](?:'?[0-9A-Fa-f])*)?)?|\\.[0-9A-Fa-f](?:'?[0-9A-Fa-f])*)[Pp][+-]?[0-9](?:'?[0-9])*)(?:[Ff](?:16|32|64|128)?|(BF|bf)16|[Ll]|)`},{begin:`[+-]?\\b(?:0[Bb][01](?:'?[01])*|0[Xx][0-9A-Fa-f](?:'?[0-9A-Fa-f])*|0(?:'?[0-7])*|[1-9](?:'?[0-9])*)(?:[Uu](?:LL?|ll?)|[Uu][Zz]?|(?:LL?|ll?)[Uu]?|[Zz][Uu]|)`}],relevance:0},c={className:`meta`,begin:/#\s*[a-z]+\b/,end:/$/,keywords:{keyword:`if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include`},contains:[{begin:/\\\n/,relevance:0},e.inherit(o,{className:`string`}),{className:`string`,begin:/<.*?>/},n,e.C_BLOCK_COMMENT_MODE]},l={className:`title`,begin:t.optional(r)+e.IDENT_RE,relevance:0},u=t.optional(r)+e.IDENT_RE+`\\s*\\(`,d=`alignas.alignof.and.and_eq.asm.atomic_cancel.atomic_commit.atomic_noexcept.auto.bitand.bitor.break.case.catch.class.co_await.co_return.co_yield.compl.concept.const_cast|10.consteval.constexpr.constinit.continue.decltype.default.delete.do.dynamic_cast|10.else.enum.explicit.export.extern.false.final.for.friend.goto.if.import.inline.module.mutable.namespace.new.noexcept.not.not_eq.nullptr.operator.or.or_eq.override.private.protected.public.reflexpr.register.reinterpret_cast|10.requires.return.sizeof.static_assert.static_cast|10.struct.switch.synchronized.template.this.thread_local.throw.transaction_safe.transaction_safe_dynamic.true.try.typedef.typeid.typename.union.using.virtual.volatile.while.xor.xor_eq`.split(`.`),f=[`bool`,`char`,`char16_t`,`char32_t`,`char8_t`,`double`,`float`,`int`,`long`,`short`,`void`,`wchar_t`,`unsigned`,`signed`,`const`,`static`],p=`any.auto_ptr.barrier.binary_semaphore.bitset.complex.condition_variable.condition_variable_any.counting_semaphore.deque.false_type.flat_map.flat_set.future.imaginary.initializer_list.istringstream.jthread.latch.lock_guard.multimap.multiset.mutex.optional.ostringstream.packaged_task.pair.promise.priority_queue.queue.recursive_mutex.recursive_timed_mutex.scoped_lock.set.shared_future.shared_lock.shared_mutex.shared_timed_mutex.shared_ptr.stack.string_view.stringstream.timed_mutex.thread.true_type.tuple.unique_lock.unique_ptr.unordered_map.unordered_multimap.unordered_multiset.unordered_set.variant.vector.weak_ptr.wstring.wstring_view`.split(`.`),m=`abort.abs.acos.apply.as_const.asin.atan.atan2.calloc.ceil.cerr.cin.clog.cos.cosh.cout.declval.endl.exchange.exit.exp.fabs.floor.fmod.forward.fprintf.fputs.free.frexp.fscanf.future.invoke.isalnum.isalpha.iscntrl.isdigit.isgraph.islower.isprint.ispunct.isspace.isupper.isxdigit.labs.launder.ldexp.log.log10.make_pair.make_shared.make_shared_for_overwrite.make_tuple.make_unique.malloc.memchr.memcmp.memcpy.memset.modf.move.pow.printf.putchar.puts.realloc.scanf.sin.sinh.snprintf.sprintf.sqrt.sscanf.std.stderr.stdin.stdout.strcat.strchr.strcmp.strcpy.strcspn.strlen.strncat.strncmp.strncpy.strpbrk.strrchr.strspn.strstr.swap.tan.tanh.terminate.to_underlying.tolower.toupper.vfprintf.visit.vprintf.vsprintf`.split(`.`),h={type:f,keyword:d,literal:[`NULL`,`false`,`nullopt`,`nullptr`,`true`],built_in:[`_Pragma`],_type_hints:p},g={className:`function.dispatch`,relevance:0,keywords:{_hint:m},begin:t.concat(/\b/,/(?!decltype)/,/(?!if)/,/(?!for)/,/(?!switch)/,/(?!while)/,e.IDENT_RE,t.lookahead(/(<[^<>]+>|)\s*\(/))},_=[g,c,a,n,e.C_BLOCK_COMMENT_MODE,s,o],v={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:`new throw return else`,end:/;/}],keywords:h,contains:_.concat([{begin:/\(/,end:/\)/,keywords:h,contains:_.concat([`self`]),relevance:0}]),relevance:0},y={className:`function`,begin:`(`+i+`[\\*&\\s]+)+`+u,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:h,illegal:/[^\w\s\*&:<>.]/,contains:[{begin:`decltype\\(auto\\)`,keywords:h,relevance:0},{begin:u,returnBegin:!0,contains:[l],relevance:0},{begin:/::/,relevance:0},{begin:/:/,endsWithParent:!0,contains:[o,s]},{relevance:0,match:/,/},{className:`params`,begin:/\(/,end:/\)/,keywords:h,relevance:0,contains:[n,e.C_BLOCK_COMMENT_MODE,o,s,a,{begin:/\(/,end:/\)/,keywords:h,relevance:0,contains:[`self`,n,e.C_BLOCK_COMMENT_MODE,o,s,a]}]},a,n,e.C_BLOCK_COMMENT_MODE,c]};return{name:`C++`,aliases:[`cc`,`c++`,`h++`,`hpp`,`hh`,`hxx`,`cxx`],keywords:h,illegal:``,keywords:h,contains:[`self`,a]},{begin:e.IDENT_RE+`::`,keywords:h},{match:[/\b(?:enum(?:\s+(?:class|struct))?|class|struct|union)/,/\s+/,/\w+/],className:{1:`keyword`,3:`title.class`}}])}}function Cl(e){let t={type:[`boolean`,`byte`,`word`,`String`],built_in:`KeyboardController.MouseController.SoftwareSerial.EthernetServer.EthernetClient.LiquidCrystal.RobotControl.GSMVoiceCall.EthernetUDP.EsploraTFT.HttpClient.RobotMotor.WiFiClient.GSMScanner.FileSystem.Scheduler.GSMServer.YunClient.YunServer.IPAddress.GSMClient.GSMModem.Keyboard.Ethernet.Console.GSMBand.Esplora.Stepper.Process.WiFiUDP.GSM_SMS.Mailbox.USBHost.Firmata.PImage.Client.Server.GSMPIN.FileIO.Bridge.Serial.EEPROM.Stream.Mouse.Audio.Servo.File.Task.GPRS.WiFi.Wire.TFT.GSM.SPI.SD`.split(`.`),_hints:`setup.loop.runShellCommandAsynchronously.analogWriteResolution.retrieveCallingNumber.printFirmwareVersion.analogReadResolution.sendDigitalPortPair.noListenOnLocalhost.readJoystickButton.setFirmwareVersion.readJoystickSwitch.scrollDisplayRight.getVoiceCallStatus.scrollDisplayLeft.writeMicroseconds.delayMicroseconds.beginTransmission.getSignalStrength.runAsynchronously.getAsynchronously.listenOnLocalhost.getCurrentCarrier.readAccelerometer.messageAvailable.sendDigitalPorts.lineFollowConfig.countryNameWrite.runShellCommand.readStringUntil.rewindDirectory.readTemperature.setClockDivider.readLightSensor.endTransmission.analogReference.detachInterrupt.countryNameRead.attachInterrupt.encryptionType.readBytesUntil.robotNameWrite.readMicrophone.robotNameRead.cityNameWrite.userNameWrite.readJoystickY.readJoystickX.mouseReleased.openNextFile.scanNetworks.noInterrupts.digitalWrite.beginSpeaker.mousePressed.isActionDone.mouseDragged.displayLogos.noAutoscroll.addParameter.remoteNumber.getModifiers.keyboardRead.userNameRead.waitContinue.processInput.parseCommand.printVersion.readNetworks.writeMessage.blinkVersion.cityNameRead.readMessage.setDataMode.parsePacket.isListening.setBitOrder.beginPacket.isDirectory.motorsWrite.drawCompass.digitalRead.clearScreen.serialEvent.rightToLeft.setTextSize.leftToRight.requestFrom.keyReleased.compassRead.analogWrite.interrupts.WiFiServer.disconnect.playMelody.parseFloat.autoscroll.getPINUsed.setPINUsed.setTimeout.sendAnalog.readSlider.analogRead.beginWrite.createChar.motorsStop.keyPressed.tempoWrite.readButton.subnetMask.debugPrint.macAddress.writeGreen.randomSeed.attachGPRS.readString.sendString.remotePort.releaseAll.mouseMoved.background.getXChange.getYChange.answerCall.getResult.voiceCall.endPacket.constrain.getSocket.writeJSON.getButton.available.connected.findUntil.readBytes.exitValue.readGreen.writeBlue.startLoop.IPAddress.isPressed.sendSysex.pauseMode.gatewayIP.setCursor.getOemKey.tuneWrite.noDisplay.loadImage.switchPIN.onRequest.onReceive.changePIN.playFile.noBuffer.parseInt.overflow.checkPIN.knobRead.beginTFT.bitClear.updateIR.bitWrite.position.writeRGB.highByte.writeRed.setSpeed.readBlue.noStroke.remoteIP.transfer.shutdown.hangCall.beginSMS.endWrite.attached.maintain.noCursor.checkReg.checkPUK.shiftOut.isValid.shiftIn.pulseIn.connect.println.localIP.pinMode.getIMEI.display.noBlink.process.getBand.running.beginSD.drawBMP.lowByte.setBand.release.bitRead.prepare.pointTo.readRed.setMode.noFill.remove.listen.stroke.detach.attach.noTone.exists.buffer.height.bitSet.circle.config.cursor.random.IRread.setDNS.endSMS.getKey.micros.millis.begin.print.write.ready.flush.width.isPIN.blink.clear.press.mkdir.rmdir.close.point.yield.image.BSSID.click.delay.read.text.move.peek.beep.rect.line.open.seek.fill.size.turn.stop.home.find.step.tone.sqrt.RSSI.SSID.end.bit.tan.cos.sin.pow.map.abs.max.min.get.run.put`.split(`.`),literal:[`DIGITAL_MESSAGE`,`FIRMATA_STRING`,`ANALOG_MESSAGE`,`REPORT_DIGITAL`,`REPORT_ANALOG`,`INPUT_PULLUP`,`SET_PIN_MODE`,`INTERNAL2V56`,`SYSTEM_RESET`,`LED_BUILTIN`,`INTERNAL1V1`,`SYSEX_START`,`INTERNAL`,`EXTERNAL`,`DEFAULT`,`OUTPUT`,`INPUT`,`HIGH`,`LOW`]},n=Sl(e),r=n.keywords;return r.type=[...r.type,...t.type],r.literal=[...r.literal,...t.literal],r.built_in=[...r.built_in,...t.built_in],r._hints=t._hints,n.name=`Arduino`,n.aliases=[`ino`],n.supersetOf=`cpp`,n}function wl(e){let t=e.regex,n={},r={begin:/\$\{/,end:/\}/,contains:[`self`,{begin:/:-/,contains:[n]}]};Object.assign(n,{className:`variable`,variants:[{begin:t.concat(/\$[\w\d#@][\w\d_]*/,`(?![\\w\\d])(?![$])`)},r]});let i={className:`subst`,begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},a=e.inherit(e.COMMENT(),{match:[/(^|\s)/,/#.*$/],scope:{2:`comment`}}),o={begin:/<<-?\s*(?=\w+)/,starts:{contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,className:`string`})]}},s={className:`string`,begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,n,i]};i.contains.push(s);let c={match:/\\"/},l={className:`string`,begin:/'/,end:/'/},u={match:/\\'/},d={begin:/\$?\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:`number`},e.NUMBER_MODE,n]},f=e.SHEBANG({binary:`(${[`fish`,`bash`,`zsh`,`sh`,`csh`,`ksh`,`tcsh`,`dash`,`scsh`].join(`|`)})`,relevance:10}),p={className:`function`,begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0},m=[`if`,`then`,`else`,`elif`,`fi`,`time`,`for`,`while`,`until`,`in`,`do`,`done`,`case`,`esac`,`coproc`,`function`,`select`],h=[`true`,`false`],g={match:/(\/[a-z._-]+)+/},_=[`break`,`cd`,`continue`,`eval`,`exec`,`exit`,`export`,`getopts`,`hash`,`pwd`,`readonly`,`return`,`shift`,`test`,`times`,`trap`,`umask`,`unset`],v=[`alias`,`bind`,`builtin`,`caller`,`command`,`declare`,`echo`,`enable`,`help`,`let`,`local`,`logout`,`mapfile`,`printf`,`read`,`readarray`,`source`,`sudo`,`type`,`typeset`,`ulimit`,`unalias`],y=`autoload.bg.bindkey.bye.cap.chdir.clone.comparguments.compcall.compctl.compdescribe.compfiles.compgroups.compquote.comptags.comptry.compvalues.dirs.disable.disown.echotc.echoti.emulate.fc.fg.float.functions.getcap.getln.history.integer.jobs.kill.limit.log.noglob.popd.print.pushd.pushln.rehash.sched.setcap.setopt.stat.suspend.ttyctl.unfunction.unhash.unlimit.unsetopt.vared.wait.whence.where.which.zcompile.zformat.zftp.zle.zmodload.zparseopts.zprof.zpty.zregexparse.zsocket.zstyle.ztcp`.split(`.`),b=`chcon.chgrp.chown.chmod.cp.dd.df.dir.dircolors.ln.ls.mkdir.mkfifo.mknod.mktemp.mv.realpath.rm.rmdir.shred.sync.touch.truncate.vdir.b2sum.base32.base64.cat.cksum.comm.csplit.cut.expand.fmt.fold.head.join.md5sum.nl.numfmt.od.paste.ptx.pr.sha1sum.sha224sum.sha256sum.sha384sum.sha512sum.shuf.sort.split.sum.tac.tail.tr.tsort.unexpand.uniq.wc.arch.basename.chroot.date.dirname.du.echo.env.expr.factor.groups.hostid.id.link.logname.nice.nohup.nproc.pathchk.pinky.printenv.printf.pwd.readlink.runcon.seq.sleep.stat.stdbuf.stty.tee.test.timeout.tty.uname.unlink.uptime.users.who.whoami.yes`.split(`.`);return{name:`Bash`,aliases:[`sh`,`zsh`],keywords:{$pattern:/\b[a-z][a-z0-9._-]+\b/,keyword:m,literal:h,built_in:[..._,...v,`set`,`shopt`,...y,...b]},contains:[f,e.SHEBANG(),p,d,a,o,g,s,c,l,u,n]}}function Tl(e){let t=e.regex,n=e.COMMENT(`//`,`$`,{contains:[{begin:/\\\n/}]}),r=`[a-zA-Z_]\\w*::`,i=`(decltype\\(auto\\)|`+t.optional(r)+`[a-zA-Z_]\\w*`+t.optional(`<[^<>]+>`)+`)`,a={className:`type`,variants:[{begin:`\\b[a-z\\d_]*_t\\b`},{match:/\batomic_[a-z]{3,6}\b/}]},o={className:`string`,variants:[{begin:`(u8?|U|L)?"`,end:`"`,illegal:`\\n`,contains:[e.BACKSLASH_ESCAPE]},{begin:`(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)`,end:`'`,illegal:`.`},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},s={className:`number`,variants:[{match:/\b(0b[01']+)/},{match:/(-?)\b([\d']+(\.[\d']*)?|\.[\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)/},{match:/(-?)\b(0[xX][a-fA-F0-9]+(?:'[a-fA-F0-9]+)*(?:\.[a-fA-F0-9]*(?:'[a-fA-F0-9]*)*)?(?:[pP][-+]?[0-9]+)?(l|L)?(u|U)?)/},{match:/(-?)\b\d+(?:'\d+)*(?:\.\d*(?:'\d*)*)?(?:[eE][-+]?\d+)?/}],relevance:0},c={className:`meta`,begin:/#\s*[a-z]+\b/,end:/$/,keywords:{keyword:`if else elif endif define undef warning error line pragma _Pragma ifdef ifndef elifdef elifndef include`},contains:[{begin:/\\\n/,relevance:0},e.inherit(o,{className:`string`}),{className:`string`,begin:/<.*?>/},n,e.C_BLOCK_COMMENT_MODE]},l={className:`title`,begin:t.optional(r)+e.IDENT_RE,relevance:0},u=t.optional(r)+e.IDENT_RE+`\\s*\\(`,d={keyword:`asm.auto.break.case.continue.default.do.else.enum.extern.for.fortran.goto.if.inline.register.restrict.return.sizeof.typeof.typeof_unqual.struct.switch.typedef.union.volatile.while._Alignas._Alignof._Atomic._Generic._Noreturn._Static_assert._Thread_local.alignas.alignof.noreturn.static_assert.thread_local._Pragma`.split(`.`),type:`float.double.signed.unsigned.int.short.long.char.void._Bool._BitInt._Complex._Imaginary._Decimal32._Decimal64._Decimal96._Decimal128._Decimal64x._Decimal128x._Float16._Float32._Float64._Float128._Float32x._Float64x._Float128x.const.static.constexpr.complex.bool.imaginary`.split(`.`),literal:`true false NULL`,built_in:`std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr`},f=[c,a,n,e.C_BLOCK_COMMENT_MODE,s,o],p={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:`new throw return else`,end:/;/}],keywords:d,contains:f.concat([{begin:/\(/,end:/\)/,keywords:d,contains:f.concat([`self`]),relevance:0}]),relevance:0},m={begin:`(`+i+`[\\*&\\s]+)+`+u,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:d,illegal:/[^\w\s\*&:<>.]/,contains:[{begin:`decltype\\(auto\\)`,keywords:d,relevance:0},{begin:u,returnBegin:!0,contains:[e.inherit(l,{className:`title.function`})],relevance:0},{relevance:0,match:/,/},{className:`params`,begin:/\(/,end:/\)/,keywords:d,relevance:0,contains:[n,e.C_BLOCK_COMMENT_MODE,o,s,a,{begin:/\(/,end:/\)/,keywords:d,relevance:0,contains:[`self`,n,e.C_BLOCK_COMMENT_MODE,o,s,a]}]},a,n,e.C_BLOCK_COMMENT_MODE,c]};return{name:`C`,aliases:[`h`],keywords:d,disableAutodetect:!0,illegal:`=]/,contains:[{beginKeywords:`final class struct`},e.TITLE_MODE]}]),exports:{preprocessor:c,strings:o,keywords:d}}}function El(e){let t=e.regex,n=e.COMMENT(`//`,`$`,{contains:[{begin:/\\\n/}]}),r=`[a-zA-Z_]\\w*::`,i=`(?!struct)(decltype\\(auto\\)|`+t.optional(r)+`[a-zA-Z_]\\w*`+t.optional(`<[^<>]+>`)+`)`,a={className:`type`,begin:`\\b[a-z\\d_]*_t\\b`},o={className:`string`,variants:[{begin:`(u8?|U|L)?"`,end:`"`,illegal:`\\n`,contains:[e.BACKSLASH_ESCAPE]},{begin:`(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)`,end:`'`,illegal:`.`},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},s={className:`number`,variants:[{begin:`[+-]?(?:(?:[0-9](?:'?[0-9])*\\.(?:[0-9](?:'?[0-9])*)?|\\.[0-9](?:'?[0-9])*)(?:[Ee][+-]?[0-9](?:'?[0-9])*)?|[0-9](?:'?[0-9])*[Ee][+-]?[0-9](?:'?[0-9])*|0[Xx](?:[0-9A-Fa-f](?:'?[0-9A-Fa-f])*(?:\\.(?:[0-9A-Fa-f](?:'?[0-9A-Fa-f])*)?)?|\\.[0-9A-Fa-f](?:'?[0-9A-Fa-f])*)[Pp][+-]?[0-9](?:'?[0-9])*)(?:[Ff](?:16|32|64|128)?|(BF|bf)16|[Ll]|)`},{begin:`[+-]?\\b(?:0[Bb][01](?:'?[01])*|0[Xx][0-9A-Fa-f](?:'?[0-9A-Fa-f])*|0(?:'?[0-7])*|[1-9](?:'?[0-9])*)(?:[Uu](?:LL?|ll?)|[Uu][Zz]?|(?:LL?|ll?)[Uu]?|[Zz][Uu]|)`}],relevance:0},c={className:`meta`,begin:/#\s*[a-z]+\b/,end:/$/,keywords:{keyword:`if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include`},contains:[{begin:/\\\n/,relevance:0},e.inherit(o,{className:`string`}),{className:`string`,begin:/<.*?>/},n,e.C_BLOCK_COMMENT_MODE]},l={className:`title`,begin:t.optional(r)+e.IDENT_RE,relevance:0},u=t.optional(r)+e.IDENT_RE+`\\s*\\(`,d=`alignas.alignof.and.and_eq.asm.atomic_cancel.atomic_commit.atomic_noexcept.auto.bitand.bitor.break.case.catch.class.co_await.co_return.co_yield.compl.concept.const_cast|10.consteval.constexpr.constinit.continue.decltype.default.delete.do.dynamic_cast|10.else.enum.explicit.export.extern.false.final.for.friend.goto.if.import.inline.module.mutable.namespace.new.noexcept.not.not_eq.nullptr.operator.or.or_eq.override.private.protected.public.reflexpr.register.reinterpret_cast|10.requires.return.sizeof.static_assert.static_cast|10.struct.switch.synchronized.template.this.thread_local.throw.transaction_safe.transaction_safe_dynamic.true.try.typedef.typeid.typename.union.using.virtual.volatile.while.xor.xor_eq`.split(`.`),f=[`bool`,`char`,`char16_t`,`char32_t`,`char8_t`,`double`,`float`,`int`,`long`,`short`,`void`,`wchar_t`,`unsigned`,`signed`,`const`,`static`],p=`any.auto_ptr.barrier.binary_semaphore.bitset.complex.condition_variable.condition_variable_any.counting_semaphore.deque.false_type.flat_map.flat_set.future.imaginary.initializer_list.istringstream.jthread.latch.lock_guard.multimap.multiset.mutex.optional.ostringstream.packaged_task.pair.promise.priority_queue.queue.recursive_mutex.recursive_timed_mutex.scoped_lock.set.shared_future.shared_lock.shared_mutex.shared_timed_mutex.shared_ptr.stack.string_view.stringstream.timed_mutex.thread.true_type.tuple.unique_lock.unique_ptr.unordered_map.unordered_multimap.unordered_multiset.unordered_set.variant.vector.weak_ptr.wstring.wstring_view`.split(`.`),m=`abort.abs.acos.apply.as_const.asin.atan.atan2.calloc.ceil.cerr.cin.clog.cos.cosh.cout.declval.endl.exchange.exit.exp.fabs.floor.fmod.forward.fprintf.fputs.free.frexp.fscanf.future.invoke.isalnum.isalpha.iscntrl.isdigit.isgraph.islower.isprint.ispunct.isspace.isupper.isxdigit.labs.launder.ldexp.log.log10.make_pair.make_shared.make_shared_for_overwrite.make_tuple.make_unique.malloc.memchr.memcmp.memcpy.memset.modf.move.pow.printf.putchar.puts.realloc.scanf.sin.sinh.snprintf.sprintf.sqrt.sscanf.std.stderr.stdin.stdout.strcat.strchr.strcmp.strcpy.strcspn.strlen.strncat.strncmp.strncpy.strpbrk.strrchr.strspn.strstr.swap.tan.tanh.terminate.to_underlying.tolower.toupper.vfprintf.visit.vprintf.vsprintf`.split(`.`),h={type:f,keyword:d,literal:[`NULL`,`false`,`nullopt`,`nullptr`,`true`],built_in:[`_Pragma`],_type_hints:p},g={className:`function.dispatch`,relevance:0,keywords:{_hint:m},begin:t.concat(/\b/,/(?!decltype)/,/(?!if)/,/(?!for)/,/(?!switch)/,/(?!while)/,e.IDENT_RE,t.lookahead(/(<[^<>]+>|)\s*\(/))},_=[g,c,a,n,e.C_BLOCK_COMMENT_MODE,s,o],v={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:`new throw return else`,end:/;/}],keywords:h,contains:_.concat([{begin:/\(/,end:/\)/,keywords:h,contains:_.concat([`self`]),relevance:0}]),relevance:0},y={className:`function`,begin:`(`+i+`[\\*&\\s]+)+`+u,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:h,illegal:/[^\w\s\*&:<>.]/,contains:[{begin:`decltype\\(auto\\)`,keywords:h,relevance:0},{begin:u,returnBegin:!0,contains:[l],relevance:0},{begin:/::/,relevance:0},{begin:/:/,endsWithParent:!0,contains:[o,s]},{relevance:0,match:/,/},{className:`params`,begin:/\(/,end:/\)/,keywords:h,relevance:0,contains:[n,e.C_BLOCK_COMMENT_MODE,o,s,a,{begin:/\(/,end:/\)/,keywords:h,relevance:0,contains:[`self`,n,e.C_BLOCK_COMMENT_MODE,o,s,a]}]},a,n,e.C_BLOCK_COMMENT_MODE,c]};return{name:`C++`,aliases:[`cc`,`c++`,`h++`,`hpp`,`hh`,`hxx`,`cxx`],keywords:h,illegal:``,keywords:h,contains:[`self`,a]},{begin:e.IDENT_RE+`::`,keywords:h},{match:[/\b(?:enum(?:\s+(?:class|struct))?|class|struct|union)/,/\s+/,/\w+/],className:{1:`keyword`,3:`title.class`}}])}}function Dl(e){let t=[`bool`,`byte`,`char`,`decimal`,`delegate`,`double`,`dynamic`,`enum`,`float`,`int`,`long`,`nint`,`nuint`,`object`,`sbyte`,`short`,`string`,`ulong`,`uint`,`ushort`],n=[`public`,`private`,`protected`,`static`,`internal`,`protected`,`abstract`,`async`,`extern`,`override`,`unsafe`,`virtual`,`new`,`sealed`,`partial`],r={keyword:`abstract.as.base.break.case.catch.class.const.continue.do.else.event.explicit.extern.finally.fixed.for.foreach.goto.if.implicit.in.interface.internal.is.lock.namespace.new.operator.out.override.params.private.protected.public.readonly.record.ref.return.scoped.sealed.sizeof.stackalloc.static.struct.switch.this.throw.try.typeof.unchecked.unsafe.using.virtual.void.volatile.while`.split(`.`).concat(`add.alias.and.ascending.args.async.await.by.descending.dynamic.equals.file.from.get.global.group.init.into.join.let.nameof.not.notnull.on.or.orderby.partial.record.remove.required.scoped.select.set.unmanaged.value|0.var.when.where.with.yield`.split(`.`)),built_in:t,literal:[`default`,`false`,`null`,`true`]},i=e.inherit(e.TITLE_MODE,{begin:`[a-zA-Z](\\.?\\w)*`}),a={className:`number`,variants:[{begin:`\\b(0b[01']+)`},{begin:`(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)`},{begin:`(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)`}],relevance:0},o={className:`string`,begin:/"""("*)(?!")(.|\n)*?"""\1/,relevance:1},s={className:`string`,begin:`@"`,end:`"`,contains:[{begin:`""`}]},c=e.inherit(s,{illegal:/\n/}),l={className:`subst`,begin:/\{/,end:/\}/,keywords:r},u=e.inherit(l,{illegal:/\n/}),d={className:`string`,begin:/\$"/,end:`"`,illegal:/\n/,contains:[{begin:/\{\{/},{begin:/\}\}/},e.BACKSLASH_ESCAPE,u]},f={className:`string`,begin:/\$@"/,end:`"`,contains:[{begin:/\{\{/},{begin:/\}\}/},{begin:`""`},l]},p=e.inherit(f,{illegal:/\n/,contains:[{begin:/\{\{/},{begin:/\}\}/},{begin:`""`},u]});l.contains=[f,d,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE],u.contains=[p,d,c,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{illegal:/\n/})];let m={variants:[o,f,d,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},h={begin:`<`,end:`>`,contains:[{beginKeywords:`in out`},i]},g=e.IDENT_RE+`(<`+e.IDENT_RE+`(\\s*,\\s*`+e.IDENT_RE+`)*>)?(\\[\\])?`,_={begin:`@`+e.IDENT_RE,relevance:0};return{name:`C#`,aliases:[`cs`,`c#`],keywords:r,illegal:/::/,contains:[e.COMMENT(`///`,`$`,{returnBegin:!0,contains:[{className:`doctag`,variants:[{begin:`///`,relevance:0},{begin:``},{begin:``}]}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:`meta`,begin:`#`,end:`$`,keywords:{keyword:`if else elif endif define undef warning error line region endregion pragma checksum`}},m,a,{beginKeywords:`class interface`,relevance:0,end:/[{;=]/,illegal:/[^\s:,]/,contains:[{beginKeywords:`where class`},i,h,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:`namespace`,relevance:0,end:/[{;=]/,illegal:/[^\s:]/,contains:[i,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:`record`,relevance:0,end:/[{;=]/,illegal:/[^\s:]/,contains:[i,h,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:`meta`,begin:`^\\s*\\[(?=[\\w])`,excludeBegin:!0,end:`\\]`,excludeEnd:!0,contains:[{className:`string`,begin:/"/,end:/"/}]},{beginKeywords:`new return throw await else`,relevance:0},{className:`function`,begin:`(`+g+`\\s+)+`+e.IDENT_RE+`\\s*(<[^=]+>\\s*)?\\(`,returnBegin:!0,end:/\s*[{;=]/,excludeEnd:!0,keywords:r,contains:[{beginKeywords:n.join(` `),relevance:0},{begin:e.IDENT_RE+`\\s*(<[^=]+>\\s*)?\\(`,returnBegin:!0,contains:[e.TITLE_MODE,h],relevance:0},{match:/\(\)/},{className:`params`,begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:r,relevance:0,contains:[m,a,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},_]}}var Ol=e=>({IMPORTANT:{scope:`meta`,begin:`!important`},BLOCK_COMMENT:e.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:`number`,begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},FUNCTION_DISPATCH:{className:`built_in`,begin:/[\w-]+(?=\()/},ATTRIBUTE_SELECTOR_MODE:{scope:`selector-attr`,begin:/\[/,end:/\]/,illegal:`$`,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{scope:`number`,begin:e.NUMBER_RE+`(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?`,relevance:0},CSS_VARIABLE:{className:`attr`,begin:/--[A-Za-z_][A-Za-z0-9_-]*/}}),kl=`a.abbr.address.article.aside.audio.b.blockquote.body.button.canvas.caption.cite.code.dd.del.details.dfn.div.dl.dt.em.fieldset.figcaption.figure.footer.form.h1.h2.h3.h4.h5.h6.header.hgroup.html.i.iframe.img.input.ins.kbd.label.legend.li.main.mark.menu.nav.object.ol.optgroup.option.p.picture.q.quote.samp.section.select.source.span.strong.summary.sup.table.tbody.td.textarea.tfoot.th.thead.time.tr.ul.var.video`.split(`.`),Al=`defs.g.marker.mask.pattern.svg.switch.symbol.feBlend.feColorMatrix.feComponentTransfer.feComposite.feConvolveMatrix.feDiffuseLighting.feDisplacementMap.feFlood.feGaussianBlur.feImage.feMerge.feMorphology.feOffset.feSpecularLighting.feTile.feTurbulence.linearGradient.radialGradient.stop.circle.ellipse.image.line.path.polygon.polyline.rect.text.use.textPath.tspan.foreignObject.clipPath`.split(`.`),jl=[...kl,...Al],Ml=`any-hover.any-pointer.aspect-ratio.color.color-gamut.color-index.device-aspect-ratio.device-height.device-width.display-mode.forced-colors.grid.height.hover.inverted-colors.monochrome.orientation.overflow-block.overflow-inline.pointer.prefers-color-scheme.prefers-contrast.prefers-reduced-motion.prefers-reduced-transparency.resolution.scan.scripting.update.width.min-width.max-width.min-height.max-height`.split(`.`).sort().reverse(),Nl=`active.any-link.blank.checked.current.default.defined.dir.disabled.drop.empty.enabled.first.first-child.first-of-type.fullscreen.future.focus.focus-visible.focus-within.has.host.host-context.hover.indeterminate.in-range.invalid.is.lang.last-child.last-of-type.left.link.local-link.not.nth-child.nth-col.nth-last-child.nth-last-col.nth-last-of-type.nth-of-type.only-child.only-of-type.optional.out-of-range.past.placeholder-shown.read-only.read-write.required.right.root.scope.target.target-within.user-invalid.valid.visited.where`.split(`.`).sort().reverse(),Pl=[`after`,`backdrop`,`before`,`cue`,`cue-region`,`first-letter`,`first-line`,`grammar-error`,`marker`,`part`,`placeholder`,`selection`,`slotted`,`spelling-error`].sort().reverse(),Fl=`accent-color.align-content.align-items.align-self.alignment-baseline.all.anchor-name.animation.animation-composition.animation-delay.animation-direction.animation-duration.animation-fill-mode.animation-iteration-count.animation-name.animation-play-state.animation-range.animation-range-end.animation-range-start.animation-timeline.animation-timing-function.appearance.aspect-ratio.backdrop-filter.backface-visibility.background.background-attachment.background-blend-mode.background-clip.background-color.background-image.background-origin.background-position.background-position-x.background-position-y.background-repeat.background-size.baseline-shift.block-size.border.border-block.border-block-color.border-block-end.border-block-end-color.border-block-end-style.border-block-end-width.border-block-start.border-block-start-color.border-block-start-style.border-block-start-width.border-block-style.border-block-width.border-bottom.border-bottom-color.border-bottom-left-radius.border-bottom-right-radius.border-bottom-style.border-bottom-width.border-collapse.border-color.border-end-end-radius.border-end-start-radius.border-image.border-image-outset.border-image-repeat.border-image-slice.border-image-source.border-image-width.border-inline.border-inline-color.border-inline-end.border-inline-end-color.border-inline-end-style.border-inline-end-width.border-inline-start.border-inline-start-color.border-inline-start-style.border-inline-start-width.border-inline-style.border-inline-width.border-left.border-left-color.border-left-style.border-left-width.border-radius.border-right.border-right-color.border-right-style.border-right-width.border-spacing.border-start-end-radius.border-start-start-radius.border-style.border-top.border-top-color.border-top-left-radius.border-top-right-radius.border-top-style.border-top-width.border-width.bottom.box-align.box-decoration-break.box-direction.box-flex.box-flex-group.box-lines.box-ordinal-group.box-orient.box-pack.box-shadow.box-sizing.break-after.break-before.break-inside.caption-side.caret-color.clear.clip.clip-path.clip-rule.color.color-interpolation.color-interpolation-filters.color-profile.color-rendering.color-scheme.column-count.column-fill.column-gap.column-rule.column-rule-color.column-rule-style.column-rule-width.column-span.column-width.columns.contain.contain-intrinsic-block-size.contain-intrinsic-height.contain-intrinsic-inline-size.contain-intrinsic-size.contain-intrinsic-width.container.container-name.container-type.content.content-visibility.counter-increment.counter-reset.counter-set.cue.cue-after.cue-before.cursor.cx.cy.direction.display.dominant-baseline.empty-cells.enable-background.field-sizing.fill.fill-opacity.fill-rule.filter.flex.flex-basis.flex-direction.flex-flow.flex-grow.flex-shrink.flex-wrap.float.flood-color.flood-opacity.flow.font.font-display.font-family.font-feature-settings.font-kerning.font-language-override.font-optical-sizing.font-palette.font-size.font-size-adjust.font-smooth.font-smoothing.font-stretch.font-style.font-synthesis.font-synthesis-position.font-synthesis-small-caps.font-synthesis-style.font-synthesis-weight.font-variant.font-variant-alternates.font-variant-caps.font-variant-east-asian.font-variant-emoji.font-variant-ligatures.font-variant-numeric.font-variant-position.font-variation-settings.font-weight.forced-color-adjust.gap.glyph-orientation-horizontal.glyph-orientation-vertical.grid.grid-area.grid-auto-columns.grid-auto-flow.grid-auto-rows.grid-column.grid-column-end.grid-column-start.grid-gap.grid-row.grid-row-end.grid-row-start.grid-template.grid-template-areas.grid-template-columns.grid-template-rows.hanging-punctuation.height.hyphenate-character.hyphenate-limit-chars.hyphens.icon.image-orientation.image-rendering.image-resolution.ime-mode.initial-letter.initial-letter-align.inline-size.inset.inset-area.inset-block.inset-block-end.inset-block-start.inset-inline.inset-inline-end.inset-inline-start.isolation.justify-content.justify-items.justify-self.kerning.left.letter-spacing.lighting-color.line-break.line-height.line-height-step.list-style.list-style-image.list-style-position.list-style-type.margin.margin-block.margin-block-end.margin-block-start.margin-bottom.margin-inline.margin-inline-end.margin-inline-start.margin-left.margin-right.margin-top.margin-trim.marker.marker-end.marker-mid.marker-start.marks.mask.mask-border.mask-border-mode.mask-border-outset.mask-border-repeat.mask-border-slice.mask-border-source.mask-border-width.mask-clip.mask-composite.mask-image.mask-mode.mask-origin.mask-position.mask-repeat.mask-size.mask-type.masonry-auto-flow.math-depth.math-shift.math-style.max-block-size.max-height.max-inline-size.max-width.min-block-size.min-height.min-inline-size.min-width.mix-blend-mode.nav-down.nav-index.nav-left.nav-right.nav-up.none.normal.object-fit.object-position.offset.offset-anchor.offset-distance.offset-path.offset-position.offset-rotate.opacity.order.orphans.outline.outline-color.outline-offset.outline-style.outline-width.overflow.overflow-anchor.overflow-block.overflow-clip-margin.overflow-inline.overflow-wrap.overflow-x.overflow-y.overlay.overscroll-behavior.overscroll-behavior-block.overscroll-behavior-inline.overscroll-behavior-x.overscroll-behavior-y.padding.padding-block.padding-block-end.padding-block-start.padding-bottom.padding-inline.padding-inline-end.padding-inline-start.padding-left.padding-right.padding-top.page.page-break-after.page-break-before.page-break-inside.paint-order.pause.pause-after.pause-before.perspective.perspective-origin.place-content.place-items.place-self.pointer-events.position.position-anchor.position-visibility.print-color-adjust.quotes.r.resize.rest.rest-after.rest-before.right.rotate.row-gap.ruby-align.ruby-position.scale.scroll-behavior.scroll-margin.scroll-margin-block.scroll-margin-block-end.scroll-margin-block-start.scroll-margin-bottom.scroll-margin-inline.scroll-margin-inline-end.scroll-margin-inline-start.scroll-margin-left.scroll-margin-right.scroll-margin-top.scroll-padding.scroll-padding-block.scroll-padding-block-end.scroll-padding-block-start.scroll-padding-bottom.scroll-padding-inline.scroll-padding-inline-end.scroll-padding-inline-start.scroll-padding-left.scroll-padding-right.scroll-padding-top.scroll-snap-align.scroll-snap-stop.scroll-snap-type.scroll-timeline.scroll-timeline-axis.scroll-timeline-name.scrollbar-color.scrollbar-gutter.scrollbar-width.shape-image-threshold.shape-margin.shape-outside.shape-rendering.speak.speak-as.src.stop-color.stop-opacity.stroke.stroke-dasharray.stroke-dashoffset.stroke-linecap.stroke-linejoin.stroke-miterlimit.stroke-opacity.stroke-width.tab-size.table-layout.text-align.text-align-all.text-align-last.text-anchor.text-combine-upright.text-decoration.text-decoration-color.text-decoration-line.text-decoration-skip.text-decoration-skip-ink.text-decoration-style.text-decoration-thickness.text-emphasis.text-emphasis-color.text-emphasis-position.text-emphasis-style.text-indent.text-justify.text-orientation.text-overflow.text-rendering.text-shadow.text-size-adjust.text-transform.text-underline-offset.text-underline-position.text-wrap.text-wrap-mode.text-wrap-style.timeline-scope.top.touch-action.transform.transform-box.transform-origin.transform-style.transition.transition-behavior.transition-delay.transition-duration.transition-property.transition-timing-function.translate.unicode-bidi.user-modify.user-select.vector-effect.vertical-align.view-timeline.view-timeline-axis.view-timeline-inset.view-timeline-name.view-transition-name.visibility.voice-balance.voice-duration.voice-family.voice-pitch.voice-range.voice-rate.voice-stress.voice-volume.white-space.white-space-collapse.widows.width.will-change.word-break.word-spacing.word-wrap.writing-mode.x.y.z-index.zoom`.split(`.`).sort().reverse();function Il(e){let t=e.regex,n=Ol(e),r={begin:/-(webkit|moz|ms|o)-(?=[a-z])/},i=/@-?\w[\w]*(-\w+)*/,a=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE];return{name:`CSS`,case_insensitive:!0,illegal:/[=|'\$]/,keywords:{keyframePosition:`from to`},classNameAliases:{keyframePosition:`selector-tag`},contains:[n.BLOCK_COMMENT,r,n.CSS_NUMBER_MODE,{className:`selector-id`,begin:/#[A-Za-z0-9_-]+/,relevance:0},{className:`selector-class`,begin:`\\.[a-zA-Z-][a-zA-Z0-9_-]*`,relevance:0},n.ATTRIBUTE_SELECTOR_MODE,{className:`selector-pseudo`,variants:[{begin:`:(`+Nl.join(`|`)+`)`},{begin:`:(:)?(`+Pl.join(`|`)+`)`}]},n.CSS_VARIABLE,{className:`attribute`,begin:`\\b(`+Fl.join(`|`)+`)\\b`},{begin:/:/,end:/[;}{]/,contains:[n.BLOCK_COMMENT,n.HEXCOLOR,n.IMPORTANT,n.CSS_NUMBER_MODE,...a,{begin:/(url|data-uri)\(/,end:/\)/,relevance:0,keywords:{built_in:`url data-uri`},contains:[...a,{className:`string`,begin:/[^)]/,endsWithParent:!0,excludeEnd:!0}]},n.FUNCTION_DISPATCH]},{begin:t.lookahead(/@/),end:`[{;]`,relevance:0,illegal:/:/,contains:[{className:`keyword`,begin:i},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:{$pattern:/[a-z-]+/,keyword:`and or not only`,attribute:Ml.join(` `)},contains:[{begin:/[a-z-]+(?=:)/,className:`attribute`},...a,n.CSS_NUMBER_MODE]}]},{className:`selector-tag`,begin:`\\b(`+jl.join(`|`)+`)\\b`}]}}function Ll(e){let t=e.regex;return{name:`Diff`,aliases:[`patch`],contains:[{className:`meta`,relevance:10,match:t.either(/^@@ +-\d+,\d+ +\+\d+,\d+ +@@/,/^\*\*\* +\d+,\d+ +\*\*\*\*$/,/^--- +\d+,\d+ +----$/)},{className:`comment`,variants:[{begin:t.either(/Index: /,/^index/,/={3,}/,/^-{3}/,/^\*{3} /,/^\+{3}/,/^diff --git/),end:/$/},{match:/^\*{15}$/}]},{className:`addition`,begin:/^\+/,end:/$/},{className:`deletion`,begin:/^-/,end:/$/},{className:`addition`,begin:/^!/,end:/$/}]}}function Rl(e){let t={keyword:[`break`,`case`,`chan`,`const`,`continue`,`default`,`defer`,`else`,`fallthrough`,`for`,`func`,`go`,`goto`,`if`,`import`,`interface`,`map`,`package`,`range`,`return`,`select`,`struct`,`switch`,`type`,`var`],type:[`bool`,`byte`,`complex64`,`complex128`,`error`,`float32`,`float64`,`int8`,`int16`,`int32`,`int64`,`string`,`uint8`,`uint16`,`uint32`,`uint64`,`int`,`uint`,`uintptr`,`rune`],literal:[`true`,`false`,`iota`,`nil`],built_in:[`append`,`cap`,`close`,`complex`,`copy`,`imag`,`len`,`make`,`new`,`panic`,`print`,`println`,`real`,`recover`,`delete`]};return{name:`Go`,aliases:[`golang`],keywords:t,illegal:`Gl(e,t,n-1))}function Kl(e){let t=e.regex,n=`[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*`,r=n+Gl(`(?:<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*~~~(?:\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*~~~)*>)?`,/~~~/g,2),i={keyword:`synchronized.abstract.private.var.static.if.const .for.while.strictfp.finally.protected.import.native.final.void.enum.else.break.transient.catch.instanceof.volatile.case.assert.package.default.public.try.switch.continue.throws.protected.public.private.module.requires.exports.do.sealed.yield.permits.goto.when`.split(`.`),literal:[`false`,`true`,`null`],type:[`char`,`boolean`,`long`,`float`,`int`,`byte`,`short`,`double`],built_in:[`super`,`this`]},a={className:`meta`,begin:`@[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*`,contains:[{begin:/\(/,end:/\)/,contains:[`self`]}]},o={className:`params`,begin:/\(/,end:/\)/,keywords:i,relevance:0,contains:[e.C_BLOCK_COMMENT_MODE],endsParent:!0};return{name:`Java`,aliases:[`jsp`],keywords:i,illegal:/<\/|#/,contains:[e.COMMENT(`/\\*\\*`,`\\*/`,{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:`doctag`,begin:`@[A-Za-z]+`}]}),{begin:/import java\.[a-z]+\./,keywords:`import`,relevance:2},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{begin:/"""/,end:/"""/,className:`string`,contains:[e.BACKSLASH_ESCAPE]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{match:[/\b(?:class|interface|enum|extends|implements|new)/,/\s+/,n],className:{1:`keyword`,3:`title.class`}},{match:/non-sealed/,scope:`keyword`},{begin:[t.concat(/(?!else)/,n),/\s+/,n,/\s+/,/=(?!=)/],className:{1:`type`,3:`variable`,5:`operator`}},{begin:[/record/,/\s+/,n],className:{1:`keyword`,3:`title.class`},contains:[o,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:`new throw return else`,relevance:0},{begin:[`(?:`+r+`\\s+)`,e.UNDERSCORE_IDENT_RE,/\s*(?=\()/],className:{2:`title.function`},keywords:i,contains:[{className:`params`,begin:/\(/,end:/\)/,keywords:i,relevance:0,contains:[a,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,Wl,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},Wl,a]}}var ql=`[A-Za-z$_][0-9A-Za-z$_]*`,Jl=`as.in.of.if.for.while.finally.var.new.function.do.return.void.else.break.catch.instanceof.with.throw.case.default.try.switch.continue.typeof.delete.let.yield.const.class.debugger.async.await.static.import.from.export.extends.using`.split(`.`),Yl=[`true`,`false`,`null`,`undefined`,`NaN`,`Infinity`],Xl=`Object.Function.Boolean.Symbol.Math.Date.Number.BigInt.String.RegExp.Array.Float32Array.Float64Array.Int8Array.Uint8Array.Uint8ClampedArray.Int16Array.Int32Array.Uint16Array.Uint32Array.BigInt64Array.BigUint64Array.Set.Map.WeakSet.WeakMap.ArrayBuffer.SharedArrayBuffer.Atomics.DataView.JSON.Promise.Generator.GeneratorFunction.AsyncFunction.Reflect.Proxy.Intl.WebAssembly`.split(`.`),Zl=[`Error`,`EvalError`,`InternalError`,`RangeError`,`ReferenceError`,`SyntaxError`,`TypeError`,`URIError`],Ql=[`setInterval`,`setTimeout`,`clearInterval`,`clearTimeout`,`require`,`exports`,`eval`,`isFinite`,`isNaN`,`parseFloat`,`parseInt`,`decodeURI`,`decodeURIComponent`,`encodeURI`,`encodeURIComponent`,`escape`,`unescape`],$l=[`arguments`,`this`,`super`,`console`,`window`,`document`,`localStorage`,`sessionStorage`,`module`,`global`],eu=[].concat(Ql,Xl,Zl);function tu(e){let t=e.regex,n=(e,{after:t})=>{let n=``,end:``},a=/<[A-Za-z0-9\\._:-]+\s*\/>/,o={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,t)=>{let r=e[0].length+e.index,i=e.input[r];if(i===`<`||i===`,`){t.ignoreMatch();return}i===`>`&&(n(e,{after:r})||t.ignoreMatch());let a,o=e.input.substring(r);if(a=o.match(/^\s*=/)){t.ignoreMatch();return}if((a=o.match(/^\s+extends\s+/))&&a.index===0){t.ignoreMatch();return}}},s={$pattern:ql,keyword:Jl,literal:Yl,built_in:eu,"variable.language":$l},c=`[0-9](_?[0-9])*`,l=`\\.(${c})`,u=`0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*`,d={className:`number`,variants:[{begin:`(\\b(${u})((${l})|\\.)?|(${l}))[eE][+-]?(${c})\\b`},{begin:`\\b(${u})\\b((${l})\\b|\\.)?|(${l})\\b`},{begin:`\\b(0|[1-9](_?[0-9])*)n\\b`},{begin:`\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b`},{begin:`\\b0[bB][0-1](_?[0-1])*n?\\b`},{begin:`\\b0[oO][0-7](_?[0-7])*n?\\b`},{begin:`\\b0[0-7]+n?\\b`}],relevance:0},f={className:`subst`,begin:`\\$\\{`,end:`\\}`,keywords:s,contains:[]},p={begin:".?html`",end:``,starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,f],subLanguage:`xml`}},m={begin:".?css`",end:``,starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,f],subLanguage:`css`}},h={begin:".?gql`",end:``,starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,f],subLanguage:`graphql`}},g={className:`string`,begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE,f]},_={className:`comment`,variants:[e.COMMENT(/\/\*\*(?!\/)/,`\\*/`,{relevance:0,contains:[{begin:`(?=@[A-Za-z]+)`,relevance:0,contains:[{className:`doctag`,begin:`@[A-Za-z]+`},{className:`type`,begin:`\\{`,end:`\\}`,excludeEnd:!0,excludeBegin:!0,relevance:0},{className:`variable`,begin:`[A-Za-z$_][0-9A-Za-z$_]*(?=\\s*(-)|$)`,endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),e.C_BLOCK_COMMENT_MODE,e.C_LINE_COMMENT_MODE]},v=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,p,m,h,g,{match:/\$\d+/},d];f.contains=v.concat({begin:/\{/,end:/\}/,keywords:s,contains:[`self`].concat(v)});let y=[].concat(_,f.contains),b=y.concat([{begin:/(\s*)\(/,end:/\)/,keywords:s,contains:[`self`].concat(y)}]),x={className:`params`,begin:/(\s*)\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:s,contains:b},S={variants:[{match:[/class/,/\s+/,r,/\s+/,/extends/,/\s+/,t.concat(r,`(`,t.concat(/\./,r),`)*`)],scope:{1:`keyword`,3:`title.class`,5:`keyword`,7:`title.class.inherited`}},{match:[/class/,/\s+/,r],scope:{1:`keyword`,3:`title.class`}}]},C={relevance:0,match:t.either(/\bJSON/,/\b[A-Z][a-z]+([A-Z][a-z]*|\d)*/,/\b[A-Z]{2,}([A-Z][a-z]+|\d)+([A-Z][a-z]*)*/,/\b[A-Z]{2,}[a-z]+([A-Z][a-z]+|\d)*([A-Z][a-z]*)*/),className:`title.class`,keywords:{_:[...Xl,...Zl]}},w={label:`use_strict`,className:`meta`,relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},T={variants:[{match:[/function/,/\s+/,r,/(?=\s*\()/]},{match:[/function/,/\s*(?=\()/]}],className:{1:`keyword`,3:`title.function`},label:`func.def`,contains:[x],illegal:/%/},E={relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/,className:`variable.constant`};function D(e){return t.concat(`(?!`,e.join(`|`),`)`)}let O={match:t.concat(/\b/,D([...Ql,`super`,`import`].map(e=>`${e}\\s*\\(`)),r,t.lookahead(/\s*\(/)),className:`title.function`,relevance:0},k={begin:t.concat(/\./,t.lookahead(t.concat(r,/(?![0-9A-Za-z$_(])/))),end:r,excludeBegin:!0,keywords:`prototype`,className:`property`,relevance:0},A={match:[/get|set/,/\s+/,r,/(?=\()/],className:{1:`keyword`,3:`title.function`},contains:[{begin:/\(\)/},x]},j=`(\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|`+e.UNDERSCORE_IDENT_RE+`)\\s*=>`,M={match:[/const|var|let/,/\s+/,r,/\s*/,/=\s*/,/(async\s*)?/,t.lookahead(j)],keywords:`async`,className:{1:`keyword`,3:`title.function`},contains:[x]};return{name:`JavaScript`,aliases:[`js`,`jsx`,`mjs`,`cjs`],keywords:s,exports:{PARAMS_CONTAINS:b,CLASS_REFERENCE:C},illegal:/#(?![$_A-z])/,contains:[e.SHEBANG({label:`shebang`,binary:`node`,relevance:5}),w,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,p,m,h,g,_,{match:/\$\d+/},d,C,{scope:`attr`,match:r+t.lookahead(`:`),relevance:0},M,{begin:`(`+e.RE_STARTERS_RE+`|\\b(case|return|throw)\\b)\\s*`,keywords:`return throw case`,relevance:0,contains:[_,e.REGEXP_MODE,{className:`function`,begin:j,returnBegin:!0,end:`\\s*=>`,contains:[{className:`params`,variants:[{begin:e.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/(\s*)\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:s,contains:b}]}]},{begin:/,/,relevance:0},{match:/\s+/,relevance:0},{variants:[{begin:i.begin,end:i.end},{match:a},{begin:o.begin,"on:begin":o.isTrulyOpeningTag,end:o.end}],subLanguage:`xml`,contains:[{begin:o.begin,end:o.end,skip:!0,contains:[`self`]}]}]},T,{beginKeywords:`while if switch catch for`},{begin:`\\b(?!function)`+e.UNDERSCORE_IDENT_RE+`\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{`,returnBegin:!0,label:`func.def`,contains:[x,e.inherit(e.TITLE_MODE,{begin:r,className:`title.function`})]},{match:/\.\.\./,relevance:0},k,{match:`\\$[A-Za-z$_][0-9A-Za-z$_]*`,relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:`title.function`},contains:[x]},O,E,S,A,{match:/\$[(.]/}]}}function nu(e){let t={className:`attr`,begin:/"(\\.|[^\\"\r\n])*"(?=\s*:)/,relevance:1.01},n={match:/[{}[\],:]/,className:`punctuation`,relevance:0},r=[`true`,`false`,`null`],i={scope:`literal`,beginKeywords:r.join(` `)};return{name:`JSON`,aliases:[`jsonc`],keywords:{literal:r},contains:[t,n,e.QUOTE_STRING_MODE,i,e.C_NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE],illegal:`\\S`}}var ru=`[0-9](_*[0-9])*`,iu=`\\.(${ru})`,au=`[0-9a-fA-F](_*[0-9a-fA-F])*`,ou={className:`number`,variants:[{begin:`(\\b(${ru})((${iu})|\\.)?|(${iu}))[eE][+-]?(${ru})[fFdD]?\\b`},{begin:`\\b(${ru})((${iu})[fFdD]?\\b|\\.([fFdD]\\b)?)`},{begin:`(${iu})[fFdD]?\\b`},{begin:`\\b(${ru})[fFdD]\\b`},{begin:`\\b0[xX]((${au})\\.?|(${au})?\\.(${au}))[pP][+-]?(${ru})[fFdD]?\\b`},{begin:`\\b(0|[1-9](_*[0-9])*)[lL]?\\b`},{begin:`\\b0[xX](${au})[lL]?\\b`},{begin:`\\b0(_*[0-7])*[lL]?\\b`},{begin:`\\b0[bB][01](_*[01])*[lL]?\\b`}],relevance:0};function su(e){let t={keyword:`abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual`,built_in:`Byte Short Char Int Long Boolean Float Double Void Unit Nothing`,literal:`true false null`},n={className:`keyword`,begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:`symbol`,begin:/@\w+/}]}},r={className:`symbol`,begin:e.UNDERSCORE_IDENT_RE+`@`},i={className:`subst`,begin:/\$\{/,end:/\}/,contains:[e.C_NUMBER_MODE]},a={className:`variable`,begin:`\\$`+e.UNDERSCORE_IDENT_RE},o={className:`string`,variants:[{begin:`"""`,end:`"""(?=[^"])`,contains:[a,i]},{begin:`'`,end:`'`,illegal:/\n/,contains:[e.BACKSLASH_ESCAPE]},{begin:`"`,end:`"`,illegal:/\n/,contains:[e.BACKSLASH_ESCAPE,a,i]}]};i.contains.push(o);let s={className:`meta`,begin:`@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*`+e.UNDERSCORE_IDENT_RE+`)?`},c={className:`meta`,begin:`@`+e.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/,end:/\)/,contains:[e.inherit(o,{className:`string`}),`self`]}]},l=ou,u=e.COMMENT(`/\\*`,`\\*/`,{contains:[e.C_BLOCK_COMMENT_MODE]}),d={variants:[{className:`type`,begin:e.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/,contains:[]}]},f=d;return f.variants[1].contains=[d],d.variants[1].contains=[f],{name:`Kotlin`,aliases:[`kt`,`kts`],keywords:t,contains:[e.COMMENT(`/\\*\\*`,`\\*/`,{relevance:0,contains:[{className:`doctag`,begin:`@[A-Za-z]+`}]}),e.C_LINE_COMMENT_MODE,u,n,r,s,c,{className:`function`,beginKeywords:`fun`,end:`[(]|$`,returnBegin:!0,excludeEnd:!0,keywords:t,relevance:5,contains:[{begin:e.UNDERSCORE_IDENT_RE+`\\s*\\(`,returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:`type`,begin://,keywords:`reified`,relevance:0},{className:`params`,begin:/\(/,end:/\)/,endsParent:!0,keywords:t,relevance:0,contains:[{begin:/:/,end:/[=,\/]/,endsWithParent:!0,contains:[d,e.C_LINE_COMMENT_MODE,u],relevance:0},e.C_LINE_COMMENT_MODE,u,s,c,o,e.C_NUMBER_MODE]},u]},{begin:[/class|interface|trait/,/\s+/,e.UNDERSCORE_IDENT_RE],beginScope:{3:`title.class`},keywords:`class interface trait`,end:/[:\{(]|$/,excludeEnd:!0,illegal:`extends implements`,contains:[{beginKeywords:`public protected internal private constructor`},e.UNDERSCORE_TITLE_MODE,{className:`type`,begin://,excludeBegin:!0,excludeEnd:!0,relevance:0},{className:`type`,begin:/[,:]\s*/,end:/[<\(,){\s]|$/,excludeBegin:!0,returnEnd:!0},s,c]},o,{className:`meta`,begin:`^#!/usr/bin/env`,end:`$`,illegal:` `},l]}}var cu=e=>({IMPORTANT:{scope:`meta`,begin:`!important`},BLOCK_COMMENT:e.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:`number`,begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},FUNCTION_DISPATCH:{className:`built_in`,begin:/[\w-]+(?=\()/},ATTRIBUTE_SELECTOR_MODE:{scope:`selector-attr`,begin:/\[/,end:/\]/,illegal:`$`,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{scope:`number`,begin:e.NUMBER_RE+`(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?`,relevance:0},CSS_VARIABLE:{className:`attr`,begin:/--[A-Za-z_][A-Za-z0-9_-]*/}}),lu=`a.abbr.address.article.aside.audio.b.blockquote.body.button.canvas.caption.cite.code.dd.del.details.dfn.div.dl.dt.em.fieldset.figcaption.figure.footer.form.h1.h2.h3.h4.h5.h6.header.hgroup.html.i.iframe.img.input.ins.kbd.label.legend.li.main.mark.menu.nav.object.ol.optgroup.option.p.picture.q.quote.samp.section.select.source.span.strong.summary.sup.table.tbody.td.textarea.tfoot.th.thead.time.tr.ul.var.video`.split(`.`),uu=`defs.g.marker.mask.pattern.svg.switch.symbol.feBlend.feColorMatrix.feComponentTransfer.feComposite.feConvolveMatrix.feDiffuseLighting.feDisplacementMap.feFlood.feGaussianBlur.feImage.feMerge.feMorphology.feOffset.feSpecularLighting.feTile.feTurbulence.linearGradient.radialGradient.stop.circle.ellipse.image.line.path.polygon.polyline.rect.text.use.textPath.tspan.foreignObject.clipPath`.split(`.`),du=[...lu,...uu],fu=`any-hover.any-pointer.aspect-ratio.color.color-gamut.color-index.device-aspect-ratio.device-height.device-width.display-mode.forced-colors.grid.height.hover.inverted-colors.monochrome.orientation.overflow-block.overflow-inline.pointer.prefers-color-scheme.prefers-contrast.prefers-reduced-motion.prefers-reduced-transparency.resolution.scan.scripting.update.width.min-width.max-width.min-height.max-height`.split(`.`).sort().reverse(),pu=`active.any-link.blank.checked.current.default.defined.dir.disabled.drop.empty.enabled.first.first-child.first-of-type.fullscreen.future.focus.focus-visible.focus-within.has.host.host-context.hover.indeterminate.in-range.invalid.is.lang.last-child.last-of-type.left.link.local-link.not.nth-child.nth-col.nth-last-child.nth-last-col.nth-last-of-type.nth-of-type.only-child.only-of-type.optional.out-of-range.past.placeholder-shown.read-only.read-write.required.right.root.scope.target.target-within.user-invalid.valid.visited.where`.split(`.`).sort().reverse(),mu=[`after`,`backdrop`,`before`,`cue`,`cue-region`,`first-letter`,`first-line`,`grammar-error`,`marker`,`part`,`placeholder`,`selection`,`slotted`,`spelling-error`].sort().reverse(),hu=`accent-color.align-content.align-items.align-self.alignment-baseline.all.anchor-name.animation.animation-composition.animation-delay.animation-direction.animation-duration.animation-fill-mode.animation-iteration-count.animation-name.animation-play-state.animation-range.animation-range-end.animation-range-start.animation-timeline.animation-timing-function.appearance.aspect-ratio.backdrop-filter.backface-visibility.background.background-attachment.background-blend-mode.background-clip.background-color.background-image.background-origin.background-position.background-position-x.background-position-y.background-repeat.background-size.baseline-shift.block-size.border.border-block.border-block-color.border-block-end.border-block-end-color.border-block-end-style.border-block-end-width.border-block-start.border-block-start-color.border-block-start-style.border-block-start-width.border-block-style.border-block-width.border-bottom.border-bottom-color.border-bottom-left-radius.border-bottom-right-radius.border-bottom-style.border-bottom-width.border-collapse.border-color.border-end-end-radius.border-end-start-radius.border-image.border-image-outset.border-image-repeat.border-image-slice.border-image-source.border-image-width.border-inline.border-inline-color.border-inline-end.border-inline-end-color.border-inline-end-style.border-inline-end-width.border-inline-start.border-inline-start-color.border-inline-start-style.border-inline-start-width.border-inline-style.border-inline-width.border-left.border-left-color.border-left-style.border-left-width.border-radius.border-right.border-right-color.border-right-style.border-right-width.border-spacing.border-start-end-radius.border-start-start-radius.border-style.border-top.border-top-color.border-top-left-radius.border-top-right-radius.border-top-style.border-top-width.border-width.bottom.box-align.box-decoration-break.box-direction.box-flex.box-flex-group.box-lines.box-ordinal-group.box-orient.box-pack.box-shadow.box-sizing.break-after.break-before.break-inside.caption-side.caret-color.clear.clip.clip-path.clip-rule.color.color-interpolation.color-interpolation-filters.color-profile.color-rendering.color-scheme.column-count.column-fill.column-gap.column-rule.column-rule-color.column-rule-style.column-rule-width.column-span.column-width.columns.contain.contain-intrinsic-block-size.contain-intrinsic-height.contain-intrinsic-inline-size.contain-intrinsic-size.contain-intrinsic-width.container.container-name.container-type.content.content-visibility.counter-increment.counter-reset.counter-set.cue.cue-after.cue-before.cursor.cx.cy.direction.display.dominant-baseline.empty-cells.enable-background.field-sizing.fill.fill-opacity.fill-rule.filter.flex.flex-basis.flex-direction.flex-flow.flex-grow.flex-shrink.flex-wrap.float.flood-color.flood-opacity.flow.font.font-display.font-family.font-feature-settings.font-kerning.font-language-override.font-optical-sizing.font-palette.font-size.font-size-adjust.font-smooth.font-smoothing.font-stretch.font-style.font-synthesis.font-synthesis-position.font-synthesis-small-caps.font-synthesis-style.font-synthesis-weight.font-variant.font-variant-alternates.font-variant-caps.font-variant-east-asian.font-variant-emoji.font-variant-ligatures.font-variant-numeric.font-variant-position.font-variation-settings.font-weight.forced-color-adjust.gap.glyph-orientation-horizontal.glyph-orientation-vertical.grid.grid-area.grid-auto-columns.grid-auto-flow.grid-auto-rows.grid-column.grid-column-end.grid-column-start.grid-gap.grid-row.grid-row-end.grid-row-start.grid-template.grid-template-areas.grid-template-columns.grid-template-rows.hanging-punctuation.height.hyphenate-character.hyphenate-limit-chars.hyphens.icon.image-orientation.image-rendering.image-resolution.ime-mode.initial-letter.initial-letter-align.inline-size.inset.inset-area.inset-block.inset-block-end.inset-block-start.inset-inline.inset-inline-end.inset-inline-start.isolation.justify-content.justify-items.justify-self.kerning.left.letter-spacing.lighting-color.line-break.line-height.line-height-step.list-style.list-style-image.list-style-position.list-style-type.margin.margin-block.margin-block-end.margin-block-start.margin-bottom.margin-inline.margin-inline-end.margin-inline-start.margin-left.margin-right.margin-top.margin-trim.marker.marker-end.marker-mid.marker-start.marks.mask.mask-border.mask-border-mode.mask-border-outset.mask-border-repeat.mask-border-slice.mask-border-source.mask-border-width.mask-clip.mask-composite.mask-image.mask-mode.mask-origin.mask-position.mask-repeat.mask-size.mask-type.masonry-auto-flow.math-depth.math-shift.math-style.max-block-size.max-height.max-inline-size.max-width.min-block-size.min-height.min-inline-size.min-width.mix-blend-mode.nav-down.nav-index.nav-left.nav-right.nav-up.none.normal.object-fit.object-position.offset.offset-anchor.offset-distance.offset-path.offset-position.offset-rotate.opacity.order.orphans.outline.outline-color.outline-offset.outline-style.outline-width.overflow.overflow-anchor.overflow-block.overflow-clip-margin.overflow-inline.overflow-wrap.overflow-x.overflow-y.overlay.overscroll-behavior.overscroll-behavior-block.overscroll-behavior-inline.overscroll-behavior-x.overscroll-behavior-y.padding.padding-block.padding-block-end.padding-block-start.padding-bottom.padding-inline.padding-inline-end.padding-inline-start.padding-left.padding-right.padding-top.page.page-break-after.page-break-before.page-break-inside.paint-order.pause.pause-after.pause-before.perspective.perspective-origin.place-content.place-items.place-self.pointer-events.position.position-anchor.position-visibility.print-color-adjust.quotes.r.resize.rest.rest-after.rest-before.right.rotate.row-gap.ruby-align.ruby-position.scale.scroll-behavior.scroll-margin.scroll-margin-block.scroll-margin-block-end.scroll-margin-block-start.scroll-margin-bottom.scroll-margin-inline.scroll-margin-inline-end.scroll-margin-inline-start.scroll-margin-left.scroll-margin-right.scroll-margin-top.scroll-padding.scroll-padding-block.scroll-padding-block-end.scroll-padding-block-start.scroll-padding-bottom.scroll-padding-inline.scroll-padding-inline-end.scroll-padding-inline-start.scroll-padding-left.scroll-padding-right.scroll-padding-top.scroll-snap-align.scroll-snap-stop.scroll-snap-type.scroll-timeline.scroll-timeline-axis.scroll-timeline-name.scrollbar-color.scrollbar-gutter.scrollbar-width.shape-image-threshold.shape-margin.shape-outside.shape-rendering.speak.speak-as.src.stop-color.stop-opacity.stroke.stroke-dasharray.stroke-dashoffset.stroke-linecap.stroke-linejoin.stroke-miterlimit.stroke-opacity.stroke-width.tab-size.table-layout.text-align.text-align-all.text-align-last.text-anchor.text-combine-upright.text-decoration.text-decoration-color.text-decoration-line.text-decoration-skip.text-decoration-skip-ink.text-decoration-style.text-decoration-thickness.text-emphasis.text-emphasis-color.text-emphasis-position.text-emphasis-style.text-indent.text-justify.text-orientation.text-overflow.text-rendering.text-shadow.text-size-adjust.text-transform.text-underline-offset.text-underline-position.text-wrap.text-wrap-mode.text-wrap-style.timeline-scope.top.touch-action.transform.transform-box.transform-origin.transform-style.transition.transition-behavior.transition-delay.transition-duration.transition-property.transition-timing-function.translate.unicode-bidi.user-modify.user-select.vector-effect.vertical-align.view-timeline.view-timeline-axis.view-timeline-inset.view-timeline-name.view-transition-name.visibility.voice-balance.voice-duration.voice-family.voice-pitch.voice-range.voice-rate.voice-stress.voice-volume.white-space.white-space-collapse.widows.width.will-change.word-break.word-spacing.word-wrap.writing-mode.x.y.z-index.zoom`.split(`.`).sort().reverse(),gu=pu.concat(mu).sort().reverse();function _u(e){let t=cu(e),n=gu,r=`([\\w-]+|@\\{[\\w-]+\\})`,i=[],a=[],o=function(e){return{className:`string`,begin:`~?`+e+`.*?`+e}},s=function(e,t,n){return{className:e,begin:t,relevance:n}},c={$pattern:/[a-z-]+/,keyword:`and or not only`,attribute:fu.join(` `)},l={begin:`\\(`,end:`\\)`,contains:a,keywords:c,relevance:0};a.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,o(`'`),o(`"`),t.CSS_NUMBER_MODE,{begin:`(url|data-uri)\\(`,starts:{className:`string`,end:`[\\)\\n]`,excludeEnd:!0}},t.HEXCOLOR,l,s(`variable`,`@@?[\\w-]+`,10),s(`variable`,`@\\{[\\w-]+\\}`),s(`built_in`,"~?`[^`]*?`"),{className:`attribute`,begin:`[\\w-]+\\s*:`,end:`:`,returnBegin:!0,excludeEnd:!0},t.IMPORTANT,{beginKeywords:`and not`},t.FUNCTION_DISPATCH);let u=a.concat({begin:/\{/,end:/\}/,contains:i}),d={beginKeywords:`when`,endsWithParent:!0,contains:[{beginKeywords:`and not`}].concat(a)},f={begin:`([\\w-]+|@\\{[\\w-]+\\})\\s*:`,returnBegin:!0,end:/[;}]/,relevance:0,contains:[{begin:/-(webkit|moz|ms|o)-/},t.CSS_VARIABLE,{className:`attribute`,begin:`\\b(`+hu.join(`|`)+`)\\b`,end:/(?=:)/,starts:{endsWithParent:!0,illegal:`[<=$]`,relevance:0,contains:a}}]},p={className:`keyword`,begin:`@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b`,starts:{end:`[;{}]`,keywords:c,returnEnd:!0,contains:a,relevance:0}},m={className:`variable`,variants:[{begin:`@[\\w-]+\\s*:`,relevance:15},{begin:`@[\\w-]+`}],starts:{end:`[;}]`,returnEnd:!0,contains:u}},h={variants:[{begin:`[\\.#:&\\[>]`,end:`[;{}]`},{begin:r,end:/\{/}],returnBegin:!0,returnEnd:!0,illegal:`[<='$"]`,relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,d,s(`keyword`,`all\\b`),s(`variable`,`@\\{[\\w-]+\\}`),{begin:`\\b(`+du.join(`|`)+`)\\b`,className:`selector-tag`},t.CSS_NUMBER_MODE,s(`selector-tag`,r,0),s(`selector-id`,`#([\\w-]+|@\\{[\\w-]+\\})`),s(`selector-class`,`\\.([\\w-]+|@\\{[\\w-]+\\})`,0),s(`selector-tag`,`&`,0),t.ATTRIBUTE_SELECTOR_MODE,{className:`selector-pseudo`,begin:`:(`+pu.join(`|`)+`)`},{className:`selector-pseudo`,begin:`:(:)?(`+mu.join(`|`)+`)`},{begin:/\(/,end:/\)/,relevance:0,contains:u},{begin:`!important`},t.FUNCTION_DISPATCH]},g={begin:`[\\w-]+:(:)?(${n.join(`|`)})`,returnBegin:!0,contains:[h]};return i.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,p,m,g,f,h,d,t.FUNCTION_DISPATCH),{name:`Less`,case_insensitive:!0,illegal:`[=>'/<($"]`,contains:i}}function vu(e){let t=`\\[=*\\[`,n=`\\]=*\\]`,r={begin:t,end:n,contains:[`self`]},i=[e.COMMENT(`--(?!\\[=*\\[)`,`$`),e.COMMENT(`--\\[=*\\[`,n,{contains:[r],relevance:10})];return{name:`Lua`,aliases:[`pluto`],keywords:{$pattern:e.UNDERSCORE_IDENT_RE,literal:`true false nil`,keyword:`and break do else elseif end for goto if in local not or repeat return then until while`,built_in:`_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove`},contains:i.concat([{className:`function`,beginKeywords:`function`,end:`\\)`,contains:[e.inherit(e.TITLE_MODE,{begin:`([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*`}),{className:`params`,begin:`\\(`,endsWithParent:!0,contains:i}].concat(i)},e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:`string`,begin:t,end:n,contains:[r],relevance:5}])}}function yu(e){let t={className:`variable`,variants:[{begin:`\\$\\(`+e.UNDERSCORE_IDENT_RE+`\\)`,contains:[e.BACKSLASH_ESCAPE]},{begin:/\$[@%`,subLanguage:`xml`,relevance:0},r={begin:`^[-\\*]{3,}`,end:`$`},i={className:`code`,variants:[{begin:"(`{3,})[^`](.|\\n)*?\\1`*[ ]*"},{begin:`(~{3,})[^~](.|\\n)*?\\1~*[ ]*`},{begin:"```",end:"```+[ ]*$"},{begin:`~~~`,end:`~~~+[ ]*$`},{begin:"`.+?`"},{begin:`(?=^( {4}|\\t))`,contains:[{begin:`^( {4}|\\t)`,end:`(\\n)$`}],relevance:0}]},a={className:`bullet`,begin:`^[ ]*([*+-]|(\\d+\\.))(?=\\s+)`,end:`\\s+`,excludeEnd:!0},o={begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{className:`symbol`,begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{className:`link`,begin:/:\s*/,end:/$/,excludeBegin:!0}]},s={variants:[{begin:/\[.+?\]\[.*?\]/,relevance:0},{begin:/\[.+?\]\(((data|javascript|mailto):|(?:http|ftp)s?:\/\/).*?\)/,relevance:2},{begin:t.concat(/\[.+?\]\(/,/[A-Za-z][A-Za-z0-9+.-]*/,/:\/\/.*?\)/),relevance:2},{begin:/\[.+?\]\([./?&#].*?\)/,relevance:1},{begin:/\[.*?\]\(.*?\)/,relevance:0}],returnBegin:!0,contains:[{match:/\[(?=\])/},{className:`string`,relevance:0,begin:`\\[`,end:`\\]`,excludeBegin:!0,returnEnd:!0},{className:`link`,relevance:0,begin:`\\]\\(`,end:`\\)`,excludeBegin:!0,excludeEnd:!0},{className:`symbol`,relevance:0,begin:`\\]\\[`,end:`\\]`,excludeBegin:!0,excludeEnd:!0}]},c={className:`strong`,contains:[],variants:[{begin:/_{2}(?!\s)/,end:/_{2}/},{begin:/\*{2}(?!\s)/,end:/\*{2}/}]},l={className:`emphasis`,contains:[],variants:[{begin:/\*(?![*\s])/,end:/\*/},{begin:/_(?![_\s])/,end:/_/,relevance:0}]},u=e.inherit(c,{contains:[]}),d=e.inherit(l,{contains:[]});c.contains.push(d),l.contains.push(u);let f=[n,s];return[c,l,u,d].forEach(e=>{e.contains=e.contains.concat(f)}),f=f.concat(c,l),{name:`Markdown`,aliases:[`md`,`mkdown`,`mkd`],contains:[{className:`section`,variants:[{begin:`^#{1,6}`,end:`$`,contains:f},{begin:`(?=^.+?\\n[=-]{2,}$)`,contains:[{begin:`^[=-]*$`},{begin:`^`,end:`\\n`,contains:f}]}]},n,a,c,l,{className:`quote`,begin:`^>\\s+`,contains:f,end:`$`},i,r,s,o,{scope:`literal`,match:/&([a-zA-Z0-9]+|#[0-9]{1,7}|#[Xx][0-9a-fA-F]{1,6});/}]}}function xu(e){let t={className:`built_in`,begin:`\\b(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)\\w+`},n=/[a-zA-Z@][a-zA-Z0-9_]*/,r={"variable.language":[`this`,`super`],$pattern:n,keyword:`while.export.sizeof.typedef.const.struct.for.union.volatile.static.mutable.if.do.return.goto.enum.else.break.extern.asm.case.default.register.explicit.typename.switch.continue.inline.readonly.assign.readwrite.self.@synchronized.id.typeof.nonatomic.IBOutlet.IBAction.strong.weak.copy.in.out.inout.bycopy.byref.oneway.__strong.__weak.__block.__autoreleasing.@private.@protected.@public.@try.@property.@end.@throw.@catch.@finally.@autoreleasepool.@synthesize.@dynamic.@selector.@optional.@required.@encode.@package.@import.@defs.@compatibility_alias.__bridge.__bridge_transfer.__bridge_retained.__bridge_retain.__covariant.__contravariant.__kindof._Nonnull._Nullable._Null_unspecified.__FUNCTION__.__PRETTY_FUNCTION__.__attribute__.getter.setter.retain.unsafe_unretained.nonnull.nullable.null_unspecified.null_resettable.class.instancetype.NS_DESIGNATED_INITIALIZER.NS_UNAVAILABLE.NS_REQUIRES_SUPER.NS_RETURNS_INNER_POINTER.NS_INLINE.NS_AVAILABLE.NS_DEPRECATED.NS_ENUM.NS_OPTIONS.NS_SWIFT_UNAVAILABLE.NS_ASSUME_NONNULL_BEGIN.NS_ASSUME_NONNULL_END.NS_REFINED_FOR_SWIFT.NS_SWIFT_NAME.NS_SWIFT_NOTHROW.NS_DURING.NS_HANDLER.NS_ENDHANDLER.NS_VALUERETURN.NS_VOIDRETURN`.split(`.`),literal:[`false`,`true`,`FALSE`,`TRUE`,`nil`,`YES`,`NO`,`NULL`],built_in:[`dispatch_once_t`,`dispatch_queue_t`,`dispatch_sync`,`dispatch_async`,`dispatch_once`],type:[`int`,`float`,`char`,`unsigned`,`signed`,`short`,`long`,`double`,`wchar_t`,`unichar`,`void`,`bool`,`BOOL`,`id|0`,`_Bool`]},i={$pattern:n,keyword:[`@interface`,`@class`,`@protocol`,`@implementation`]};return{name:`Objective-C`,aliases:[`mm`,`objc`,`obj-c`,`obj-c++`,`objective-c++`],keywords:r,illegal:`/,end:/$/,illegal:`\\n`},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:`class`,begin:`(`+i.keyword.join(`|`)+`)\\b`,end:/(\{|$)/,excludeEnd:!0,keywords:i,contains:[e.UNDERSCORE_TITLE_MODE]},{begin:`\\.`+e.UNDERSCORE_IDENT_RE,relevance:0}]}}function Su(e){let t=e.regex,n=`abs.accept.alarm.and.atan2.bind.binmode.bless.break.caller.chdir.chmod.chomp.chop.chown.chr.chroot.class.close.closedir.connect.continue.cos.crypt.dbmclose.dbmopen.defined.delete.die.do.dump.each.else.elsif.endgrent.endhostent.endnetent.endprotoent.endpwent.endservent.eof.eval.exec.exists.exit.exp.fcntl.field.fileno.flock.for.foreach.fork.format.formline.getc.getgrent.getgrgid.getgrnam.gethostbyaddr.gethostbyname.gethostent.getlogin.getnetbyaddr.getnetbyname.getnetent.getpeername.getpgrp.getpriority.getprotobyname.getprotobynumber.getprotoent.getpwent.getpwnam.getpwuid.getservbyname.getservbyport.getservent.getsockname.getsockopt.given.glob.gmtime.goto.grep.gt.hex.if.index.int.ioctl.join.keys.kill.last.lc.lcfirst.length.link.listen.local.localtime.log.lstat.lt.ma.map.method.mkdir.msgctl.msgget.msgrcv.msgsnd.my.ne.next.no.not.oct.open.opendir.or.ord.our.pack.package.pipe.pop.pos.print.printf.prototype.push.q|0.qq.quotemeta.qw.qx.rand.read.readdir.readline.readlink.readpipe.recv.redo.ref.rename.require.reset.return.reverse.rewinddir.rindex.rmdir.say.scalar.seek.seekdir.select.semctl.semget.semop.send.setgrent.sethostent.setnetent.setpgrp.setpriority.setprotoent.setpwent.setservent.setsockopt.shift.shmctl.shmget.shmread.shmwrite.shutdown.sin.sleep.socket.socketpair.sort.splice.split.sprintf.sqrt.srand.stat.state.study.sub.substr.symlink.syscall.sysopen.sysread.sysseek.system.syswrite.tell.telldir.tie.tied.time.times.tr.truncate.uc.ucfirst.umask.undef.unless.unlink.unpack.unshift.untie.until.use.utime.values.vec.wait.waitpid.wantarray.warn.when.while.write.x|0.xor.y|0`.split(`.`),r=/[dualxmsipngr]{0,12}/,i={$pattern:/[\w.]+/,keyword:n.join(` `)},a={className:`subst`,begin:`[$@]\\{`,end:`\\}`,keywords:i},o={begin:/->\{/,end:/\}/},s={scope:`attr`,match:/\s+:\s*\w+(\s*\(.*?\))?/},c={scope:`variable`,variants:[{begin:/\$\d/},{begin:t.concat(/[$%@](?!")(\^\w\b|#\w+(::\w+)*|\{\w+\}|\w+(::\w*)*)/,`(?![A-Za-z])(?![@$%])`)},{begin:/[$%@](?!")[^\s\w{=]|\$=/,relevance:0}],contains:[s]},l={className:`number`,variants:[{match:/0?\.[0-9][0-9_]+\b/},{match:/\bv?(0|[1-9][0-9_]*(\.[0-9_]+)?|[1-9][0-9_]*)\b/},{match:/\b0[0-7][0-7_]*\b/},{match:/\b0x[0-9a-fA-F][0-9a-fA-F_]*\b/},{match:/\b0b[0-1][0-1_]*\b/}],relevance:0},u=[e.BACKSLASH_ESCAPE,a,c],d=[/!/,/\//,/\|/,/\?/,/'/,/"/,/#/],f=(e,n,i=`\\1`)=>{let a=i===`\\1`?i:t.concat(i,n);return t.concat(t.concat(`(?:`,e,`)`),n,/(?:\\.|[^\\\/])*?/,a,/(?:\\.|[^\\\/])*?/,i,r)},p=(e,n,i)=>t.concat(t.concat(`(?:`,e,`)`),n,/(?:\\.|[^\\\/])*?/,i,r),m=[c,e.HASH_COMMENT_MODE,e.COMMENT(/^=\w/,/=cut/,{endsWithParent:!0}),o,{className:`string`,contains:u,variants:[{begin:`q[qwxr]?\\s*\\(`,end:`\\)`,relevance:5},{begin:`q[qwxr]?\\s*\\[`,end:`\\]`,relevance:5},{begin:`q[qwxr]?\\s*\\{`,end:`\\}`,relevance:5},{begin:`q[qwxr]?\\s*\\|`,end:`\\|`,relevance:5},{begin:`q[qwxr]?\\s*<`,end:`>`,relevance:5},{begin:`qw\\s+q`,end:`q`,relevance:5},{begin:`'`,end:`'`,contains:[e.BACKSLASH_ESCAPE]},{begin:`"`,end:`"`},{begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE]},{begin:/\{\w+\}/,relevance:0},{begin:`-?\\w+\\s*=>`,relevance:0}]},l,{begin:`(\\/\\/|`+e.RE_STARTERS_RE+`|\\b(split|return|print|reverse|grep)\\b)\\s*`,keywords:`split return print reverse grep`,relevance:0,contains:[e.HASH_COMMENT_MODE,{className:`regexp`,variants:[{begin:f(`s|tr|y`,t.either(...d,{capture:!0}))},{begin:f(`s|tr|y`,`\\(`,`\\)`)},{begin:f(`s|tr|y`,`\\[`,`\\]`)},{begin:f(`s|tr|y`,`\\{`,`\\}`)}],relevance:2},{className:`regexp`,variants:[{begin:/(m|qr)\/\//,relevance:0},{begin:p(`(?:m|qr)?`,/\//,/\//)},{begin:p(`m|qr`,t.either(...d,{capture:!0}),/\1/)},{begin:p(`m|qr`,/\(/,/\)/)},{begin:p(`m|qr`,/\[/,/\]/)},{begin:p(`m|qr`,/\{/,/\}/)}]}]},{className:`function`,beginKeywords:`sub method`,end:`(\\s*\\(.*?\\))?[;{]`,excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE,s]},{className:`class`,beginKeywords:`class`,end:`[;{]`,excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE,s,l]},{begin:`-\\w\\b`,relevance:0},{begin:`^__DATA__$`,end:`^__END__$`,subLanguage:`mojolicious`,contains:[{begin:`^@@.*`,end:`$`,className:`comment`}]}];return a.contains=m,o.contains=m,{name:`Perl`,aliases:[`pl`,`pm`],keywords:i,contains:m}}function Cu(e){let t=e.regex,n=/(?![A-Za-z0-9])(?![$])/,r=t.concat(/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/,n),i=t.concat(/(\\?[A-Z][a-z0-9_\x7f-\xff]+|\\?[A-Z]+(?=[A-Z][a-z0-9_\x7f-\xff])){1,}/,n),a=t.concat(/[A-Z]+/,n),o={scope:`variable`,match:`\\$+`+r},s={scope:`meta`,variants:[{begin:/<\?php/,relevance:10},{begin:/<\?=/},{begin:/<\?/,relevance:.1},{begin:/\?>/}]},c={scope:`subst`,variants:[{begin:/\$\w+/},{begin:/\{\$/,end:/\}/}]},l=e.inherit(e.APOS_STRING_MODE,{illegal:null}),u=e.inherit(e.QUOTE_STRING_MODE,{illegal:null,contains:e.QUOTE_STRING_MODE.contains.concat(c)}),d={begin:/<<<[ \t]*(?:(\w+)|"(\w+)")\n/,end:/[ \t]*(\w+)\b/,contains:e.QUOTE_STRING_MODE.contains.concat(c),"on:begin":(e,t)=>{t.data._beginMatch=e[1]||e[2]},"on:end":(e,t)=>{t.data._beginMatch!==e[1]&&t.ignoreMatch()}},f=e.END_SAME_AS_BEGIN({begin:/<<<[ \t]*'(\w+)'\n/,end:/[ \t]*(\w+)\b/}),p=`[ -]`,m={scope:`string`,variants:[u,l,d,f]},h={scope:`number`,variants:[{begin:`\\b0[bB][01]+(?:_[01]+)*\\b`},{begin:`\\b0[oO][0-7]+(?:_[0-7]+)*\\b`},{begin:`\\b0[xX][\\da-fA-F]+(?:_[\\da-fA-F]+)*\\b`},{begin:`(?:\\b\\d+(?:_\\d+)*(\\.(?:\\d+(?:_\\d+)*))?|\\B\\.\\d+)(?:[eE][+-]?\\d+)?`}],relevance:0},g=[`false`,`null`,`true`],_=`__CLASS__.__DIR__.__FILE__.__FUNCTION__.__COMPILER_HALT_OFFSET__.__LINE__.__METHOD__.__NAMESPACE__.__TRAIT__.die.echo.exit.include.include_once.print.require.require_once.array.abstract.and.as.binary.bool.boolean.break.callable.case.catch.class.clone.const.continue.declare.default.do.double.else.elseif.empty.enddeclare.endfor.endforeach.endif.endswitch.endwhile.enum.eval.extends.final.finally.float.for.foreach.from.global.goto.if.implements.instanceof.insteadof.int.integer.interface.isset.iterable.list.match|0.mixed.new.never.object.or.private.protected.public.readonly.real.return.string.switch.throw.trait.try.unset.use.var.void.while.xor.yield`.split(`.`),v=`Error|0.AppendIterator.ArgumentCountError.ArithmeticError.ArrayIterator.ArrayObject.AssertionError.BadFunctionCallException.BadMethodCallException.CachingIterator.CallbackFilterIterator.CompileError.Countable.DirectoryIterator.DivisionByZeroError.DomainException.EmptyIterator.ErrorException.Exception.FilesystemIterator.FilterIterator.GlobIterator.InfiniteIterator.InvalidArgumentException.IteratorIterator.LengthException.LimitIterator.LogicException.MultipleIterator.NoRewindIterator.OutOfBoundsException.OutOfRangeException.OuterIterator.OverflowException.ParentIterator.ParseError.RangeException.RecursiveArrayIterator.RecursiveCachingIterator.RecursiveCallbackFilterIterator.RecursiveDirectoryIterator.RecursiveFilterIterator.RecursiveIterator.RecursiveIteratorIterator.RecursiveRegexIterator.RecursiveTreeIterator.RegexIterator.RuntimeException.SeekableIterator.SplDoublyLinkedList.SplFileInfo.SplFileObject.SplFixedArray.SplHeap.SplMaxHeap.SplMinHeap.SplObjectStorage.SplObserver.SplPriorityQueue.SplQueue.SplStack.SplSubject.SplTempFileObject.TypeError.UnderflowException.UnexpectedValueException.UnhandledMatchError.ArrayAccess.BackedEnum.Closure.Fiber.Generator.Iterator.IteratorAggregate.Serializable.Stringable.Throwable.Traversable.UnitEnum.WeakReference.WeakMap.Directory.__PHP_Incomplete_Class.parent.php_user_filter.self.static.stdClass`.split(`.`),y={keyword:_,literal:(e=>{let t=[];return e.forEach(e=>{t.push(e),e.toLowerCase()===e?t.push(e.toUpperCase()):t.push(e.toLowerCase())}),t})(g),built_in:v},b=e=>e.map(e=>e.replace(/\|\d+$/,``)),x={variants:[{match:[/new/,t.concat(p,`+`),t.concat(`(?!`,b(v).join(`\\b|`),`\\b)`),i],scope:{1:`keyword`,4:`title.class`}}]},S=t.concat(r,`\\b(?!\\()`),C={variants:[{match:[t.concat(/::/,t.lookahead(/(?!class\b)/)),S],scope:{2:`variable.constant`}},{match:[/::/,/class/],scope:{2:`variable.language`}},{match:[i,t.concat(/::/,t.lookahead(/(?!class\b)/)),S],scope:{1:`title.class`,3:`variable.constant`}},{match:[i,t.concat(`::`,t.lookahead(/(?!class\b)/))],scope:{1:`title.class`}},{match:[i,/::/,/class/],scope:{1:`title.class`,3:`variable.language`}}]},w={scope:`attr`,match:t.concat(r,t.lookahead(`:`),t.lookahead(/(?!::)/))},T={relevance:0,begin:/\(/,end:/\)/,keywords:y,contains:[w,o,C,e.C_BLOCK_COMMENT_MODE,m,h,x]},E={relevance:0,match:[/\b/,t.concat(`(?!fn\\b|function\\b|`,b(_).join(`\\b|`),`|`,b(v).join(`\\b|`),`\\b)`),r,t.concat(p,`*`),t.lookahead(/(?=\()/)],scope:{3:`title.function.invoke`},contains:[T]};T.contains.push(E);let D=[w,C,e.C_BLOCK_COMMENT_MODE,m,h,x],O={begin:t.concat(/#\[\s*\\?/,t.either(i,a)),beginScope:`meta`,end:/]/,endScope:`meta`,keywords:{literal:g,keyword:[`new`,`array`]},contains:[{begin:/\[/,end:/]/,keywords:{literal:g,keyword:[`new`,`array`]},contains:[`self`,...D]},...D,{scope:`meta`,variants:[{match:i},{match:a}]}]};return{case_insensitive:!1,keywords:y,contains:[O,e.HASH_COMMENT_MODE,e.COMMENT(`//`,`$`),e.COMMENT(`/\\*`,`\\*/`,{contains:[{scope:`doctag`,match:`@[A-Za-z]+`}]}),{match:/__halt_compiler\(\);/,keywords:`__halt_compiler`,starts:{scope:`comment`,end:e.MATCH_NOTHING_RE,contains:[{match:/\?>/,scope:`meta`,endsParent:!0}]}},s,{scope:`variable.language`,match:/\$this\b/},o,E,C,{match:[/const/,/\s/,r],scope:{1:`keyword`,3:`variable.constant`}},x,{scope:`function`,relevance:0,beginKeywords:`fn function`,end:/[;{]/,excludeEnd:!0,illegal:`[$%\\[]`,contains:[{beginKeywords:`use`},e.UNDERSCORE_TITLE_MODE,{begin:`=>`,endsParent:!0},{scope:`params`,begin:`\\(`,end:`\\)`,excludeBegin:!0,excludeEnd:!0,keywords:y,contains:[`self`,O,o,C,e.C_BLOCK_COMMENT_MODE,m,h]}]},{scope:`class`,variants:[{beginKeywords:`enum`,illegal:/[($"]/},{beginKeywords:`class interface trait`,illegal:/[:($"]/}],relevance:0,end:/\{/,excludeEnd:!0,contains:[{beginKeywords:`extends implements`},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:`namespace`,relevance:0,end:`;`,illegal:/[.']/,contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{scope:`title.class`})]},{beginKeywords:`use`,relevance:0,end:`;`,contains:[{match:/\b(as|const|function)\b/,scope:`keyword`},e.UNDERSCORE_TITLE_MODE]},m,h]}}function wu(e){return{name:`PHP template`,subLanguage:`xml`,contains:[{begin:/<\?(php|=)?/,end:/\?>/,subLanguage:`php`,contains:[{begin:`/\\*`,end:`\\*/`,skip:!0},{begin:`b"`,end:`"`,skip:!0},{begin:`b'`,end:`'`,skip:!0},e.inherit(e.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]}]}}function Tu(e){return{name:`Plain text`,aliases:[`text`,`txt`],disableAutodetect:!0}}function Eu(e){let t=e.regex,n=/[\p{XID_Start}_]\p{XID_Continue}*/u,r=`and.as.assert.async.await.break.case.class.continue.def.del.elif.else.except.finally.for.from.global.if.import.in.is.lambda.match.nonlocal|10.not.or.pass.raise.return.try.while.with.yield`.split(`.`),i={$pattern:/[A-Za-z]\w+|__\w+__/,keyword:r,built_in:`__import__.abs.all.any.ascii.bin.bool.breakpoint.bytearray.bytes.callable.chr.classmethod.compile.complex.delattr.dict.dir.divmod.enumerate.eval.exec.filter.float.format.frozenset.getattr.globals.hasattr.hash.help.hex.id.input.int.isinstance.issubclass.iter.len.list.locals.map.max.memoryview.min.next.object.oct.open.ord.pow.print.property.range.repr.reversed.round.set.setattr.slice.sorted.staticmethod.str.sum.super.tuple.type.vars.zip`.split(`.`),literal:[`__debug__`,`Ellipsis`,`False`,`None`,`NotImplemented`,`True`],type:[`Any`,`Callable`,`Coroutine`,`Dict`,`List`,`Literal`,`Generic`,`Optional`,`Sequence`,`Set`,`Tuple`,`Type`,`Union`]},a={className:`meta`,begin:/^(>>>|\.\.\.) /},o={className:`subst`,begin:/\{/,end:/\}/,keywords:i,illegal:/#/},s={begin:/\{\{/,relevance:0},c={className:`string`,contains:[e.BACKSLASH_ESCAPE],variants:[{begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/([fF][rR]|[rR][fF]|[fF])'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a,s,o]},{begin:/([fF][rR]|[rR][fF]|[fF])"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a,s,o]},{begin:/([uU]|[rR])'/,end:/'/,relevance:10},{begin:/([uU]|[rR])"/,end:/"/,relevance:10},{begin:/([bB]|[bB][rR]|[rR][bB])'/,end:/'/},{begin:/([bB]|[bB][rR]|[rR][bB])"/,end:/"/},{begin:/([fF][rR]|[rR][fF]|[fF])'/,end:/'/,contains:[e.BACKSLASH_ESCAPE,s,o]},{begin:/([fF][rR]|[rR][fF]|[fF])"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,o]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},l=`[0-9](_?[0-9])*`,u=`(\\b(${l}))?\\.(${l})|\\b(${l})\\.`,d=`\\b|${r.join(`|`)}`,f={className:`number`,relevance:0,variants:[{begin:`(\\b(${l})|(${u}))[eE][+-]?(${l})[jJ]?(?=${d})`},{begin:`(${u})[jJ]?`},{begin:`\\b([1-9](_?[0-9])*|0+(_?0)*)[lLjJ]?(?=${d})`},{begin:`\\b0[bB](_?[01])+[lL]?(?=${d})`},{begin:`\\b0[oO](_?[0-7])+[lL]?(?=${d})`},{begin:`\\b0[xX](_?[0-9a-fA-F])+[lL]?(?=${d})`},{begin:`\\b(${l})[jJ](?=${d})`}]},p={className:`comment`,begin:t.lookahead(/# type:/),end:/$/,keywords:i,contains:[{begin:/# type:/},{begin:/#/,end:/\b\B/,endsWithParent:!0}]},m={className:`params`,variants:[{className:``,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:i,contains:[`self`,a,f,c,e.HASH_COMMENT_MODE]}]};return o.contains=[c,f,a],{name:`Python`,aliases:[`py`,`gyp`,`ipython`],unicodeRegex:!0,keywords:i,illegal:/(<\/|\?)|=>/,contains:[a,f,{scope:`variable.language`,match:/\bself\b/},{beginKeywords:`if`,relevance:0},{match:/\bor\b/,scope:`keyword`},c,p,e.HASH_COMMENT_MODE,{match:[/\bdef/,/\s+/,n],scope:{1:`keyword`,3:`title.function`},contains:[m]},{variants:[{match:[/\bclass/,/\s+/,n,/\s*/,/\(\s*/,n,/\s*\)/]},{match:[/\bclass/,/\s+/,n]}],scope:{1:`keyword`,3:`title.class`,6:`title.class.inherited`}},{className:`meta`,begin:/^[\t ]*@/,end:/(?=#)|$/,contains:[f,m,c]}]}}function Du(e){return{aliases:[`pycon`],contains:[{className:`meta.prompt`,starts:{end:/ |$/,starts:{end:`$`,subLanguage:`python`}},variants:[{begin:/^>>>(?=[ ]|$)/},{begin:/^\.\.\.(?=[ ]|$)/}]}]}}function Ou(e){let t=e.regex,n=/(?:(?:[a-zA-Z]|\.[._a-zA-Z])[._a-zA-Z0-9]*)|\.(?!\d)/,r=t.either(/0[xX][0-9a-fA-F]+\.[0-9a-fA-F]*[pP][+-]?\d+i?/,/0[xX][0-9a-fA-F]+(?:[pP][+-]?\d+)?[Li]?/,/(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?[Li]?/),i=/[=!<>:]=|\|\||&&|:::?|<-|<<-|->>|->|\|>|[-+*\/?!$&|:<=>@^~]|\*\*/,a=t.either(/[()]/,/[{}]/,/\[\[/,/[[\]]/,/\\/,/,/);return{name:`R`,keywords:{$pattern:n,keyword:`function if in break next repeat else for while`,literal:`NULL NA TRUE FALSE Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10`,built_in:`LETTERS letters month.abb month.name pi T F abs acos acosh all any anyNA Arg as.call as.character as.complex as.double as.environment as.integer as.logical as.null.default as.numeric as.raw asin asinh atan atanh attr attributes baseenv browser c call ceiling class Conj cos cosh cospi cummax cummin cumprod cumsum digamma dim dimnames emptyenv exp expression floor forceAndCall gamma gc.time globalenv Im interactive invisible is.array is.atomic is.call is.character is.complex is.double is.environment is.expression is.finite is.function is.infinite is.integer is.language is.list is.logical is.matrix is.na is.name is.nan is.null is.numeric is.object is.pairlist is.raw is.recursive is.single is.symbol lazyLoadDBfetch length lgamma list log max min missing Mod names nargs nzchar oldClass on.exit pos.to.env proc.time prod quote range Re rep retracemem return round seq_along seq_len seq.int sign signif sin sinh sinpi sqrt standardGeneric substitute sum switch tan tanh tanpi tracemem trigamma trunc unclass untracemem UseMethod xtfrm`},contains:[e.COMMENT(/#'/,/$/,{contains:[{scope:`doctag`,match:/@examples/,starts:{end:t.lookahead(t.either(/\n^#'\s*(?=@[a-zA-Z]+)/,/\n^(?!#')/)),endsParent:!0}},{scope:`doctag`,begin:`@param`,end:/$/,contains:[{scope:`variable`,variants:[{match:n},{match:/`(?:\\.|[^`\\])+`/}],endsParent:!0}]},{scope:`doctag`,match:/@[a-zA-Z]+/},{scope:`keyword`,match:/\\[a-zA-Z]+/}]}),e.HASH_COMMENT_MODE,{scope:`string`,contains:[e.BACKSLASH_ESCAPE],variants:[e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\(/,end:/\)(-*)"/}),e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\{/,end:/\}(-*)"/}),e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\[/,end:/\](-*)"/}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\(/,end:/\)(-*)'/}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\{/,end:/\}(-*)'/}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\[/,end:/\](-*)'/}),{begin:`"`,end:`"`,relevance:0},{begin:`'`,end:`'`,relevance:0}]},{relevance:0,variants:[{scope:{1:`operator`,2:`number`},match:[i,r]},{scope:{1:`operator`,2:`number`},match:[/%[^%]*%/,r]},{scope:{1:`punctuation`,2:`number`},match:[a,r]},{scope:{2:`number`},match:[/[^a-zA-Z0-9._]|^/,r]}]},{scope:{3:`operator`},match:[n,/\s+/,/<-/,/\s+/]},{scope:`operator`,relevance:0,variants:[{match:i},{match:/%[^%]*%/}]},{scope:`punctuation`,relevance:0,match:a},{begin:"`",end:"`",contains:[{begin:/\\./}]}]}}function ku(e){let t=e.regex,n="([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)",r=t.either(/\b([A-Z]+[a-z0-9]+)+/,/\b([A-Z]+[a-z0-9]+)+[A-Z]+/),i=t.concat(r,/(::\w+)*/),a={"variable.constant":[`__FILE__`,`__LINE__`,`__ENCODING__`],"variable.language":[`self`,`super`],keyword:`alias.and.begin.BEGIN.break.case.class.defined.do.else.elsif.end.END.ensure.for.if.in.module.next.not.or.redo.require.rescue.retry.return.then.undef.unless.until.when.while.yield.include.extend.prepend.public.private.protected.raise.throw`.split(`.`),built_in:[`proc`,`lambda`,`attr_accessor`,`attr_reader`,`attr_writer`,`define_method`,`private_constant`,`module_function`],literal:[`true`,`false`,`nil`]},o={className:`doctag`,begin:`@[A-Za-z]+`},s={begin:`#<`,end:`>`},c=[e.COMMENT(`#`,`$`,{contains:[o]}),e.COMMENT(`^=begin`,`^=end`,{contains:[o],relevance:10}),e.COMMENT(`^__END__`,e.MATCH_NOTHING_RE)],l={className:`subst`,begin:/#\{/,end:/\}/,keywords:a},u={className:`string`,contains:[e.BACKSLASH_ESCAPE,l],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:/%[qQwWx]?\(/,end:/\)/},{begin:/%[qQwWx]?\[/,end:/\]/},{begin:/%[qQwWx]?\{/,end:/\}/},{begin:/%[qQwWx]?/},{begin:/%[qQwWx]?\//,end:/\//},{begin:/%[qQwWx]?%/,end:/%/},{begin:/%[qQwWx]?-/,end:/-/},{begin:/%[qQwWx]?\|/,end:/\|/},{begin:/\B\?(\\\d{1,3})/},{begin:/\B\?(\\x[A-Fa-f0-9]{1,2})/},{begin:/\B\?(\\u\{?[A-Fa-f0-9]{1,6}\}?)/},{begin:/\B\?(\\M-\\C-|\\M-\\c|\\c\\M-|\\M-|\\C-\\M-)[\x20-\x7e]/},{begin:/\B\?\\(c|C-)[\x20-\x7e]/},{begin:/\B\?\\?\S/},{begin:t.concat(/<<[-~]?'?/,t.lookahead(/(\w+)(?=\W)[^\n]*\n(?:[^\n]*\n)*?\s*\1\b/)),contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,contains:[e.BACKSLASH_ESCAPE,l]})]}]},d=`[0-9](_?[0-9])*`,f={className:`number`,relevance:0,variants:[{begin:`\\b([1-9](_?[0-9])*|0)(\\.(${d}))?([eE][+-]?(${d})|r)?i?\\b`},{begin:`\\b0[dD][0-9](_?[0-9])*r?i?\\b`},{begin:`\\b0[bB][0-1](_?[0-1])*r?i?\\b`},{begin:`\\b0[oO][0-7](_?[0-7])*r?i?\\b`},{begin:`\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*r?i?\\b`},{begin:`\\b0(_?[0-7])+r?i?\\b`}]},p={variants:[{match:/\(\)/},{className:`params`,begin:/\(/,end:/(?=\))/,excludeBegin:!0,endsParent:!0,keywords:a}]},m=[u,{variants:[{match:[/class\s+/,i,/\s+<\s+/,i]},{match:[/\b(class|module)\s+/,i]}],scope:{2:`title.class`,4:`title.class.inherited`},keywords:a},{match:[/(include|extend)\s+/,i],scope:{2:`title.class`},keywords:a},{relevance:0,match:[i,/\.new[. (]/],scope:{1:`title.class`}},{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/,className:`variable.constant`},{relevance:0,match:r,scope:`title.class`},{match:[/def/,/\s+/,n],scope:{1:`keyword`,3:`title.function`},contains:[p]},{begin:e.IDENT_RE+`::`},{className:`symbol`,begin:e.UNDERSCORE_IDENT_RE+`(!|\\?)?:`,relevance:0},{className:`symbol`,begin:`:(?!\\s)`,contains:[u,{begin:n}],relevance:0},f,{className:`variable`,begin:`(\\$\\W)|((\\$|@@?)(\\w+))(?=[^@$?])(?![A-Za-z])(?![@$?'])`},{className:`params`,begin:/\|(?!=)/,end:/\|/,excludeBegin:!0,excludeEnd:!0,relevance:0,keywords:a},{begin:`(`+e.RE_STARTERS_RE+`|unless)\\s*`,keywords:`unless`,contains:[{className:`regexp`,contains:[e.BACKSLASH_ESCAPE,l],illegal:/\n/,variants:[{begin:`/`,end:`/[a-z]*`},{begin:/%r\{/,end:/\}[a-z]*/},{begin:`%r\\(`,end:`\\)[a-z]*`},{begin:`%r!`,end:`![a-z]*`},{begin:`%r\\[`,end:`\\][a-z]*`}]}].concat(s,c),relevance:0}].concat(s,c);l.contains=m,p.contains=m;let h=[{begin:/^\s*=>/,starts:{end:`$`,contains:m}},{className:`meta.prompt`,begin:`^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+[>*]|(\\w+-)?\\d+\\.\\d+\\.\\d+(p\\d+)?[^\\d][^>]+>)(?=[ ])`,starts:{end:`$`,keywords:a,contains:m}}];return c.unshift(s),{name:`Ruby`,aliases:[`rb`,`gemspec`,`podspec`,`thor`,`irb`],keywords:a,illegal:/\/\*/,contains:[e.SHEBANG({binary:`ruby`})].concat(h,c,m)}}function Au(e){let t=e.regex,n=/(r#)?/,r=t.concat(n,e.UNDERSCORE_IDENT_RE),i=t.concat(n,e.IDENT_RE),a={className:`title.function.invoke`,relevance:0,begin:t.concat(/\b/,/(?!let|for|while|if|else|match\b)/,i,t.lookahead(/\s*\(/))},o=`abstract.as.async.await.become.box.break.const.continue.crate.do.dyn.else.enum.extern.false.final.fn.for.if.impl.in.let.loop.macro.match.mod.move.mut.override.priv.pub.ref.return.self.Self.static.struct.super.trait.true.try.type.typeof.union.unsafe.unsized.use.virtual.where.while.yield`.split(`.`),s=[`true`,`false`,`Some`,`None`,`Ok`,`Err`],c=`drop .Copy.Send.Sized.Sync.Drop.Fn.FnMut.FnOnce.ToOwned.Clone.Debug.PartialEq.PartialOrd.Eq.Ord.AsRef.AsMut.Into.From.Default.Iterator.Extend.IntoIterator.DoubleEndedIterator.ExactSizeIterator.SliceConcatExt.ToString.assert!.assert_eq!.bitflags!.bytes!.cfg!.col!.concat!.concat_idents!.debug_assert!.debug_assert_eq!.env!.eprintln!.panic!.file!.format!.format_args!.include_bytes!.include_str!.line!.local_data_key!.module_path!.option_env!.print!.println!.select!.stringify!.try!.unimplemented!.unreachable!.vec!.write!.writeln!.macro_rules!.assert_ne!.debug_assert_ne!`.split(`.`),l=[`i8`,`i16`,`i32`,`i64`,`i128`,`isize`,`u8`,`u16`,`u32`,`u64`,`u128`,`usize`,`f32`,`f64`,`str`,`char`,`bool`,`Box`,`Option`,`Result`,`String`,`Vec`];return{name:`Rust`,aliases:[`rs`],keywords:{$pattern:e.IDENT_RE+`!?`,type:l,keyword:o,literal:s,built_in:c},illegal:``},a]}}var ju=e=>({IMPORTANT:{scope:`meta`,begin:`!important`},BLOCK_COMMENT:e.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:`number`,begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},FUNCTION_DISPATCH:{className:`built_in`,begin:/[\w-]+(?=\()/},ATTRIBUTE_SELECTOR_MODE:{scope:`selector-attr`,begin:/\[/,end:/\]/,illegal:`$`,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{scope:`number`,begin:e.NUMBER_RE+`(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?`,relevance:0},CSS_VARIABLE:{className:`attr`,begin:/--[A-Za-z_][A-Za-z0-9_-]*/}}),Mu=`a.abbr.address.article.aside.audio.b.blockquote.body.button.canvas.caption.cite.code.dd.del.details.dfn.div.dl.dt.em.fieldset.figcaption.figure.footer.form.h1.h2.h3.h4.h5.h6.header.hgroup.html.i.iframe.img.input.ins.kbd.label.legend.li.main.mark.menu.nav.object.ol.optgroup.option.p.picture.q.quote.samp.section.select.source.span.strong.summary.sup.table.tbody.td.textarea.tfoot.th.thead.time.tr.ul.var.video`.split(`.`),Nu=`defs.g.marker.mask.pattern.svg.switch.symbol.feBlend.feColorMatrix.feComponentTransfer.feComposite.feConvolveMatrix.feDiffuseLighting.feDisplacementMap.feFlood.feGaussianBlur.feImage.feMerge.feMorphology.feOffset.feSpecularLighting.feTile.feTurbulence.linearGradient.radialGradient.stop.circle.ellipse.image.line.path.polygon.polyline.rect.text.use.textPath.tspan.foreignObject.clipPath`.split(`.`),Pu=[...Mu,...Nu],Fu=`any-hover.any-pointer.aspect-ratio.color.color-gamut.color-index.device-aspect-ratio.device-height.device-width.display-mode.forced-colors.grid.height.hover.inverted-colors.monochrome.orientation.overflow-block.overflow-inline.pointer.prefers-color-scheme.prefers-contrast.prefers-reduced-motion.prefers-reduced-transparency.resolution.scan.scripting.update.width.min-width.max-width.min-height.max-height`.split(`.`).sort().reverse(),Iu=`active.any-link.blank.checked.current.default.defined.dir.disabled.drop.empty.enabled.first.first-child.first-of-type.fullscreen.future.focus.focus-visible.focus-within.has.host.host-context.hover.indeterminate.in-range.invalid.is.lang.last-child.last-of-type.left.link.local-link.not.nth-child.nth-col.nth-last-child.nth-last-col.nth-last-of-type.nth-of-type.only-child.only-of-type.optional.out-of-range.past.placeholder-shown.read-only.read-write.required.right.root.scope.target.target-within.user-invalid.valid.visited.where`.split(`.`).sort().reverse(),Lu=[`after`,`backdrop`,`before`,`cue`,`cue-region`,`first-letter`,`first-line`,`grammar-error`,`marker`,`part`,`placeholder`,`selection`,`slotted`,`spelling-error`].sort().reverse(),Ru=`accent-color.align-content.align-items.align-self.alignment-baseline.all.anchor-name.animation.animation-composition.animation-delay.animation-direction.animation-duration.animation-fill-mode.animation-iteration-count.animation-name.animation-play-state.animation-range.animation-range-end.animation-range-start.animation-timeline.animation-timing-function.appearance.aspect-ratio.backdrop-filter.backface-visibility.background.background-attachment.background-blend-mode.background-clip.background-color.background-image.background-origin.background-position.background-position-x.background-position-y.background-repeat.background-size.baseline-shift.block-size.border.border-block.border-block-color.border-block-end.border-block-end-color.border-block-end-style.border-block-end-width.border-block-start.border-block-start-color.border-block-start-style.border-block-start-width.border-block-style.border-block-width.border-bottom.border-bottom-color.border-bottom-left-radius.border-bottom-right-radius.border-bottom-style.border-bottom-width.border-collapse.border-color.border-end-end-radius.border-end-start-radius.border-image.border-image-outset.border-image-repeat.border-image-slice.border-image-source.border-image-width.border-inline.border-inline-color.border-inline-end.border-inline-end-color.border-inline-end-style.border-inline-end-width.border-inline-start.border-inline-start-color.border-inline-start-style.border-inline-start-width.border-inline-style.border-inline-width.border-left.border-left-color.border-left-style.border-left-width.border-radius.border-right.border-right-color.border-right-style.border-right-width.border-spacing.border-start-end-radius.border-start-start-radius.border-style.border-top.border-top-color.border-top-left-radius.border-top-right-radius.border-top-style.border-top-width.border-width.bottom.box-align.box-decoration-break.box-direction.box-flex.box-flex-group.box-lines.box-ordinal-group.box-orient.box-pack.box-shadow.box-sizing.break-after.break-before.break-inside.caption-side.caret-color.clear.clip.clip-path.clip-rule.color.color-interpolation.color-interpolation-filters.color-profile.color-rendering.color-scheme.column-count.column-fill.column-gap.column-rule.column-rule-color.column-rule-style.column-rule-width.column-span.column-width.columns.contain.contain-intrinsic-block-size.contain-intrinsic-height.contain-intrinsic-inline-size.contain-intrinsic-size.contain-intrinsic-width.container.container-name.container-type.content.content-visibility.counter-increment.counter-reset.counter-set.cue.cue-after.cue-before.cursor.cx.cy.direction.display.dominant-baseline.empty-cells.enable-background.field-sizing.fill.fill-opacity.fill-rule.filter.flex.flex-basis.flex-direction.flex-flow.flex-grow.flex-shrink.flex-wrap.float.flood-color.flood-opacity.flow.font.font-display.font-family.font-feature-settings.font-kerning.font-language-override.font-optical-sizing.font-palette.font-size.font-size-adjust.font-smooth.font-smoothing.font-stretch.font-style.font-synthesis.font-synthesis-position.font-synthesis-small-caps.font-synthesis-style.font-synthesis-weight.font-variant.font-variant-alternates.font-variant-caps.font-variant-east-asian.font-variant-emoji.font-variant-ligatures.font-variant-numeric.font-variant-position.font-variation-settings.font-weight.forced-color-adjust.gap.glyph-orientation-horizontal.glyph-orientation-vertical.grid.grid-area.grid-auto-columns.grid-auto-flow.grid-auto-rows.grid-column.grid-column-end.grid-column-start.grid-gap.grid-row.grid-row-end.grid-row-start.grid-template.grid-template-areas.grid-template-columns.grid-template-rows.hanging-punctuation.height.hyphenate-character.hyphenate-limit-chars.hyphens.icon.image-orientation.image-rendering.image-resolution.ime-mode.initial-letter.initial-letter-align.inline-size.inset.inset-area.inset-block.inset-block-end.inset-block-start.inset-inline.inset-inline-end.inset-inline-start.isolation.justify-content.justify-items.justify-self.kerning.left.letter-spacing.lighting-color.line-break.line-height.line-height-step.list-style.list-style-image.list-style-position.list-style-type.margin.margin-block.margin-block-end.margin-block-start.margin-bottom.margin-inline.margin-inline-end.margin-inline-start.margin-left.margin-right.margin-top.margin-trim.marker.marker-end.marker-mid.marker-start.marks.mask.mask-border.mask-border-mode.mask-border-outset.mask-border-repeat.mask-border-slice.mask-border-source.mask-border-width.mask-clip.mask-composite.mask-image.mask-mode.mask-origin.mask-position.mask-repeat.mask-size.mask-type.masonry-auto-flow.math-depth.math-shift.math-style.max-block-size.max-height.max-inline-size.max-width.min-block-size.min-height.min-inline-size.min-width.mix-blend-mode.nav-down.nav-index.nav-left.nav-right.nav-up.none.normal.object-fit.object-position.offset.offset-anchor.offset-distance.offset-path.offset-position.offset-rotate.opacity.order.orphans.outline.outline-color.outline-offset.outline-style.outline-width.overflow.overflow-anchor.overflow-block.overflow-clip-margin.overflow-inline.overflow-wrap.overflow-x.overflow-y.overlay.overscroll-behavior.overscroll-behavior-block.overscroll-behavior-inline.overscroll-behavior-x.overscroll-behavior-y.padding.padding-block.padding-block-end.padding-block-start.padding-bottom.padding-inline.padding-inline-end.padding-inline-start.padding-left.padding-right.padding-top.page.page-break-after.page-break-before.page-break-inside.paint-order.pause.pause-after.pause-before.perspective.perspective-origin.place-content.place-items.place-self.pointer-events.position.position-anchor.position-visibility.print-color-adjust.quotes.r.resize.rest.rest-after.rest-before.right.rotate.row-gap.ruby-align.ruby-position.scale.scroll-behavior.scroll-margin.scroll-margin-block.scroll-margin-block-end.scroll-margin-block-start.scroll-margin-bottom.scroll-margin-inline.scroll-margin-inline-end.scroll-margin-inline-start.scroll-margin-left.scroll-margin-right.scroll-margin-top.scroll-padding.scroll-padding-block.scroll-padding-block-end.scroll-padding-block-start.scroll-padding-bottom.scroll-padding-inline.scroll-padding-inline-end.scroll-padding-inline-start.scroll-padding-left.scroll-padding-right.scroll-padding-top.scroll-snap-align.scroll-snap-stop.scroll-snap-type.scroll-timeline.scroll-timeline-axis.scroll-timeline-name.scrollbar-color.scrollbar-gutter.scrollbar-width.shape-image-threshold.shape-margin.shape-outside.shape-rendering.speak.speak-as.src.stop-color.stop-opacity.stroke.stroke-dasharray.stroke-dashoffset.stroke-linecap.stroke-linejoin.stroke-miterlimit.stroke-opacity.stroke-width.tab-size.table-layout.text-align.text-align-all.text-align-last.text-anchor.text-combine-upright.text-decoration.text-decoration-color.text-decoration-line.text-decoration-skip.text-decoration-skip-ink.text-decoration-style.text-decoration-thickness.text-emphasis.text-emphasis-color.text-emphasis-position.text-emphasis-style.text-indent.text-justify.text-orientation.text-overflow.text-rendering.text-shadow.text-size-adjust.text-transform.text-underline-offset.text-underline-position.text-wrap.text-wrap-mode.text-wrap-style.timeline-scope.top.touch-action.transform.transform-box.transform-origin.transform-style.transition.transition-behavior.transition-delay.transition-duration.transition-property.transition-timing-function.translate.unicode-bidi.user-modify.user-select.vector-effect.vertical-align.view-timeline.view-timeline-axis.view-timeline-inset.view-timeline-name.view-transition-name.visibility.voice-balance.voice-duration.voice-family.voice-pitch.voice-range.voice-rate.voice-stress.voice-volume.white-space.white-space-collapse.widows.width.will-change.word-break.word-spacing.word-wrap.writing-mode.x.y.z-index.zoom`.split(`.`).sort().reverse();function zu(e){let t=ju(e),n=Lu,r=Iu,i=`@[a-z-]+`,a={className:`variable`,begin:`(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b`,relevance:0};return{name:`SCSS`,case_insensitive:!0,illegal:`[=/|']`,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t.CSS_NUMBER_MODE,{className:`selector-id`,begin:`#[A-Za-z0-9_-]+`,relevance:0},{className:`selector-class`,begin:`\\.[A-Za-z0-9_-]+`,relevance:0},t.ATTRIBUTE_SELECTOR_MODE,{className:`selector-tag`,begin:`\\b(`+Pu.join(`|`)+`)\\b`,relevance:0},{className:`selector-pseudo`,begin:`:(`+r.join(`|`)+`)`},{className:`selector-pseudo`,begin:`:(:)?(`+n.join(`|`)+`)`},a,{begin:/\(/,end:/\)/,contains:[t.CSS_NUMBER_MODE]},t.CSS_VARIABLE,{className:`attribute`,begin:`\\b(`+Ru.join(`|`)+`)\\b`},{begin:`\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b`},{begin:/:/,end:/[;}{]/,relevance:0,contains:[t.BLOCK_COMMENT,a,t.HEXCOLOR,t.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,t.IMPORTANT,t.FUNCTION_DISPATCH]},{begin:`@(page|font-face)`,keywords:{$pattern:i,keyword:`@page @font-face`}},{begin:`@`,end:`[{;]`,returnBegin:!0,keywords:{$pattern:/[a-z-]+/,keyword:`and or not only`,attribute:Fu.join(` `)},contains:[{begin:i,className:`keyword`},{begin:/[a-z-]+(?=:)/,className:`attribute`},a,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,t.HEXCOLOR,t.CSS_NUMBER_MODE]},t.FUNCTION_DISPATCH]}}function Bu(e){return{name:`Shell Session`,aliases:[`console`,`shellsession`],contains:[{className:`meta.prompt`,begin:/^\s{0,3}[/~\w\d[\]()@-]*[>%$#][ ]?/,starts:{end:/[^\\](?=\s*$)/,subLanguage:`bash`}}]}}function Vu(e){let t=e.regex,n=e.COMMENT(`--`,`$`),r={scope:`string`,variants:[{begin:/'/,end:/'/,contains:[{match:/''/}]}]},i={begin:/"/,end:/"/,contains:[{match:/""/}]},a=[`true`,`false`,`unknown`],o=[`double precision`,`large object`,`with timezone`,`without timezone`],s=`bigint.binary.blob.boolean.char.character.clob.date.dec.decfloat.decimal.float.int.integer.interval.nchar.nclob.national.numeric.real.row.smallint.time.timestamp.varchar.varying.varbinary`.split(`.`),c=[`add`,`asc`,`collation`,`desc`,`final`,`first`,`last`,`view`],l=`abs.acos.all.allocate.alter.and.any.are.array.array_agg.array_max_cardinality.as.asensitive.asin.asymmetric.at.atan.atomic.authorization.avg.begin.begin_frame.begin_partition.between.bigint.binary.blob.boolean.both.by.call.called.cardinality.cascaded.case.cast.ceil.ceiling.char.char_length.character.character_length.check.classifier.clob.close.coalesce.collate.collect.column.commit.condition.connect.constraint.contains.convert.copy.corr.corresponding.cos.cosh.count.covar_pop.covar_samp.create.cross.cube.cume_dist.current.current_catalog.current_date.current_default_transform_group.current_path.current_role.current_row.current_schema.current_time.current_timestamp.current_path.current_role.current_transform_group_for_type.current_user.cursor.cycle.date.day.deallocate.dec.decimal.decfloat.declare.default.define.delete.dense_rank.deref.describe.deterministic.disconnect.distinct.double.drop.dynamic.each.element.else.empty.end.end_frame.end_partition.end-exec.equals.escape.every.except.exec.execute.exists.exp.external.extract.false.fetch.filter.first_value.float.floor.for.foreign.frame_row.free.from.full.function.fusion.get.global.grant.group.grouping.groups.having.hold.hour.identity.in.indicator.initial.inner.inout.insensitive.insert.int.integer.intersect.intersection.interval.into.is.join.json_array.json_arrayagg.json_exists.json_object.json_objectagg.json_query.json_table.json_table_primitive.json_value.lag.language.large.last_value.lateral.lead.leading.left.like.like_regex.listagg.ln.local.localtime.localtimestamp.log.log10.lower.match.match_number.match_recognize.matches.max.member.merge.method.min.minute.mod.modifies.module.month.multiset.national.natural.nchar.nclob.new.no.none.normalize.not.nth_value.ntile.null.nullif.numeric.octet_length.occurrences_regex.of.offset.old.omit.on.one.only.open.or.order.out.outer.over.overlaps.overlay.parameter.partition.pattern.per.percent.percent_rank.percentile_cont.percentile_disc.period.portion.position.position_regex.power.precedes.precision.prepare.primary.procedure.ptf.range.rank.reads.real.recursive.ref.references.referencing.regr_avgx.regr_avgy.regr_count.regr_intercept.regr_r2.regr_slope.regr_sxx.regr_sxy.regr_syy.release.result.return.returns.revoke.right.rollback.rollup.row.row_number.rows.running.savepoint.scope.scroll.search.second.seek.select.sensitive.session_user.set.show.similar.sin.sinh.skip.smallint.some.specific.specifictype.sql.sqlexception.sqlstate.sqlwarning.sqrt.start.static.stddev_pop.stddev_samp.submultiset.subset.substring.substring_regex.succeeds.sum.symmetric.system.system_time.system_user.table.tablesample.tan.tanh.then.time.timestamp.timezone_hour.timezone_minute.to.trailing.translate.translate_regex.translation.treat.trigger.trim.trim_array.true.truncate.uescape.union.unique.unknown.unnest.update.upper.user.using.value.values.value_of.var_pop.var_samp.varbinary.varchar.varying.versioning.when.whenever.where.width_bucket.window.with.within.without.year`.split(`.`),u=`abs.acos.array_agg.asin.atan.avg.cast.ceil.ceiling.coalesce.corr.cos.cosh.count.covar_pop.covar_samp.cume_dist.dense_rank.deref.element.exp.extract.first_value.floor.json_array.json_arrayagg.json_exists.json_object.json_objectagg.json_query.json_table.json_table_primitive.json_value.lag.last_value.lead.listagg.ln.log.log10.lower.max.min.mod.nth_value.ntile.nullif.percent_rank.percentile_cont.percentile_disc.position.position_regex.power.rank.regr_avgx.regr_avgy.regr_count.regr_intercept.regr_r2.regr_slope.regr_sxx.regr_sxy.regr_syy.row_number.sin.sinh.sqrt.stddev_pop.stddev_samp.substring.substring_regex.sum.tan.tanh.translate.translate_regex.treat.trim.trim_array.unnest.upper.value_of.var_pop.var_samp.width_bucket`.split(`.`),d=[`current_catalog`,`current_date`,`current_default_transform_group`,`current_path`,`current_role`,`current_schema`,`current_transform_group_for_type`,`current_user`,`session_user`,`system_time`,`system_user`,`current_time`,`localtime`,`current_timestamp`,`localtimestamp`],f=[`create table`,`insert into`,`primary key`,`foreign key`,`not null`,`alter table`,`add constraint`,`grouping sets`,`on overflow`,`character set`,`respect nulls`,`ignore nulls`,`nulls first`,`nulls last`,`depth first`,`breadth first`],p=u,m=[...l,...c].filter(e=>!u.includes(e)),h={scope:`variable`,match:/@[a-z0-9][a-z0-9_]*/},g={scope:`operator`,match:/[-+*/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?/,relevance:0},_={match:t.concat(/\b/,t.either(...p),/\s*\(/),relevance:0,keywords:{built_in:p}};function v(e){return t.concat(/\b/,t.either(...e.map(e=>e.replace(/\s+/,`\\s+`))),/\b/)}let y={scope:`keyword`,match:v(f),relevance:0};function b(e,{exceptions:t,when:n}={}){let r=n;return t||=[],e.map(e=>e.match(/\|\d+$/)||t.includes(e)?e:r(e)?`${e}|0`:e)}return{name:`SQL`,case_insensitive:!0,illegal:/[{}]|<\//,keywords:{$pattern:/\b[\w\.]+/,keyword:b(m,{when:e=>e.length<3}),literal:a,type:s,built_in:d},contains:[{scope:`type`,match:v(o)},y,_,h,r,i,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,n,g]}}function Hu(e){return e?typeof e==`string`?e:e.source:null}function Uu(e){return Q(`(?=`,e,`)`)}function Q(...e){return e.map(e=>Hu(e)).join(``)}function Wu(e){let t=e[e.length-1];return typeof t==`object`&&t.constructor===Object?(e.splice(e.length-1,1),t):{}}function $(...e){return`(`+(Wu(e).capture?``:`?:`)+e.map(e=>Hu(e)).join(`|`)+`)`}var Gu=e=>Q(/\b/,e,/\w$/.test(e)?/\b/:/\B/),Ku=[`Protocol`,`Type`].map(Gu),qu=[`init`,`self`].map(Gu),Ju=[`Any`,`Self`],Yu=[`actor`,`any`,`associatedtype`,`async`,`await`,/as\?/,/as!/,`as`,`borrowing`,`break`,`case`,`catch`,`class`,`consume`,`consuming`,`continue`,`convenience`,`copy`,`default`,`defer`,`deinit`,`didSet`,`distributed`,`do`,`dynamic`,`each`,`else`,`enum`,`extension`,`fallthrough`,/fileprivate\(set\)/,`fileprivate`,`final`,`for`,`func`,`get`,`guard`,`if`,`import`,`indirect`,`infix`,/init\?/,/init!/,`inout`,/internal\(set\)/,`internal`,`in`,`is`,`isolated`,`nonisolated`,`lazy`,`let`,`macro`,`mutating`,`nonmutating`,/open\(set\)/,`open`,`operator`,`optional`,`override`,`package`,`postfix`,`precedencegroup`,`prefix`,/private\(set\)/,`private`,`protocol`,/public\(set\)/,`public`,`repeat`,`required`,`rethrows`,`return`,`set`,`some`,`static`,`struct`,`subscript`,`super`,`switch`,`throws`,`throw`,/try\?/,/try!/,`try`,`typealias`,/unowned\(safe\)/,/unowned\(unsafe\)/,`unowned`,`var`,`weak`,`where`,`while`,`willSet`],Xu=[`false`,`nil`,`true`],Zu=[`assignment`,`associativity`,`higherThan`,`left`,`lowerThan`,`none`,`right`],Qu=[`#colorLiteral`,`#column`,`#dsohandle`,`#else`,`#elseif`,`#endif`,`#error`,`#file`,`#fileID`,`#fileLiteral`,`#filePath`,`#function`,`#if`,`#imageLiteral`,`#keyPath`,`#line`,`#selector`,`#sourceLocation`,`#warning`],$u=`abs.all.any.assert.assertionFailure.debugPrint.dump.fatalError.getVaList.isKnownUniquelyReferenced.max.min.numericCast.pointwiseMax.pointwiseMin.precondition.preconditionFailure.print.readLine.repeatElement.sequence.stride.swap.swift_unboxFromSwiftValueWithType.transcode.type.unsafeBitCast.unsafeDowncast.withExtendedLifetime.withUnsafeMutablePointer.withUnsafePointer.withVaList.withoutActuallyEscaping.zip`.split(`.`),ed=$(/[/=\-+!*%<>&|^~?]/,/[\u00A1-\u00A7]/,/[\u00A9\u00AB]/,/[\u00AC\u00AE]/,/[\u00B0\u00B1]/,/[\u00B6\u00BB\u00BF\u00D7\u00F7]/,/[\u2016-\u2017]/,/[\u2020-\u2027]/,/[\u2030-\u203E]/,/[\u2041-\u2053]/,/[\u2055-\u205E]/,/[\u2190-\u23FF]/,/[\u2500-\u2775]/,/[\u2794-\u2BFF]/,/[\u2E00-\u2E7F]/,/[\u3001-\u3003]/,/[\u3008-\u3020]/,/[\u3030]/),td=$(ed,/[\u0300-\u036F]/,/[\u1DC0-\u1DFF]/,/[\u20D0-\u20FF]/,/[\uFE00-\uFE0F]/,/[\uFE20-\uFE2F]/),nd=Q(ed,td,`*`),rd=$(/[a-zA-Z_]/,/[\u00A8\u00AA\u00AD\u00AF\u00B2-\u00B5\u00B7-\u00BA]/,/[\u00BC-\u00BE\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF]/,/[\u0100-\u02FF\u0370-\u167F\u1681-\u180D\u180F-\u1DBF]/,/[\u1E00-\u1FFF]/,/[\u200B-\u200D\u202A-\u202E\u203F-\u2040\u2054\u2060-\u206F]/,/[\u2070-\u20CF\u2100-\u218F\u2460-\u24FF\u2776-\u2793]/,/[\u2C00-\u2DFF\u2E80-\u2FFF]/,/[\u3004-\u3007\u3021-\u302F\u3031-\u303F\u3040-\uD7FF]/,/[\uF900-\uFD3D\uFD40-\uFDCF\uFDF0-\uFE1F\uFE30-\uFE44]/,/[\uFE47-\uFEFE\uFF00-\uFFFD]/),id=$(rd,/\d/,/[\u0300-\u036F\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F]/),ad=Q(rd,id,`*`),od=Q(/[A-Z]/,id,`*`),sd=[`attached`,`autoclosure`,Q(/convention\(/,$(`swift`,`block`,`c`),/\)/),`discardableResult`,`dynamicCallable`,`dynamicMemberLookup`,`escaping`,`freestanding`,`frozen`,`GKInspectable`,`IBAction`,`IBDesignable`,`IBInspectable`,`IBOutlet`,`IBSegueAction`,`inlinable`,`main`,`nonobjc`,`NSApplicationMain`,`NSCopying`,`NSManaged`,Q(/objc\(/,ad,/\)/),`objc`,`objcMembers`,`propertyWrapper`,`requires_stored_property_inits`,`resultBuilder`,`Sendable`,`testable`,`UIApplicationMain`,`unchecked`,`unknown`,`usableFromInline`,`warn_unqualified_access`],cd=[`iOS`,`iOSApplicationExtension`,`macOS`,`macOSApplicationExtension`,`macCatalyst`,`macCatalystApplicationExtension`,`watchOS`,`watchOSApplicationExtension`,`tvOS`,`tvOSApplicationExtension`,`swift`];function ld(e){let t={match:/\s+/,relevance:0},n=e.COMMENT(`/\\*`,`\\*/`,{contains:[`self`]}),r=[e.C_LINE_COMMENT_MODE,n],i={match:[/\./,$(...Ku,...qu)],className:{2:`keyword`}},a={match:Q(/\./,$(...Yu)),relevance:0},o=Yu.filter(e=>typeof e==`string`).concat([`_|0`]),s={variants:[{className:`keyword`,match:$(...Yu.filter(e=>typeof e!=`string`).concat(Ju).map(Gu),...qu)}]},c={$pattern:$(/\b\w+/,/#\w+/),keyword:o.concat(Qu),literal:Xu},l=[i,a,s],u=[{match:Q(/\./,$(...$u)),relevance:0},{className:`built_in`,match:Q(/\b/,$(...$u),/(?=\()/)}],d={match:/->/,relevance:0},f=[d,{className:`operator`,relevance:0,variants:[{match:nd},{match:`\\.(\\.|${td})+`}]}],p=`([0-9]_*)+`,m=`([0-9a-fA-F]_*)+`,h={className:`number`,relevance:0,variants:[{match:`\\b(${p})(\\.(${p}))?([eE][+-]?(${p}))?\\b`},{match:`\\b0x(${m})(\\.(${m}))?([pP][+-]?(${p}))?\\b`},{match:/\b0o([0-7]_*)+\b/},{match:/\b0b([01]_*)+\b/}]},g=(e=``)=>({className:`subst`,variants:[{match:Q(/\\/,e,/[0\\tnr"']/)},{match:Q(/\\/,e,/u\{[0-9a-fA-F]{1,8}\}/)}]}),_=(e=``)=>({className:`subst`,match:Q(/\\/,e,/[\t ]*(?:[\r\n]|\r\n)/)}),v=(e=``)=>({className:`subst`,label:`interpol`,begin:Q(/\\/,e,/\(/),end:/\)/}),y=(e=``)=>({begin:Q(e,/"""/),end:Q(/"""/,e),contains:[g(e),_(e),v(e)]}),b=(e=``)=>({begin:Q(e,/"/),end:Q(/"/,e),contains:[g(e),v(e)]}),x={className:`string`,variants:[y(),y(`#`),y(`##`),y(`###`),b(),b(`#`),b(`##`),b(`###`)]},S=[e.BACKSLASH_ESCAPE,{begin:/\[/,end:/\]/,relevance:0,contains:[e.BACKSLASH_ESCAPE]}],C={begin:/\/[^\s](?=[^/\n]*\/)/,end:/\//,contains:S},w=e=>{let t=Q(e,/\//),n=Q(/\//,e);return{begin:t,end:n,contains:[...S,{scope:`comment`,begin:`#(?!.*${n})`,end:/$/}]}},T={scope:`regexp`,variants:[w(`###`),w(`##`),w(`#`),C]},E={match:Q(/`/,ad,/`/)},D=[E,{className:`variable`,match:/\$\d+/},{className:`variable`,match:`\\$${id}+`}],O=[{match:/(@|#(un)?)available/,scope:`keyword`,starts:{contains:[{begin:/\(/,end:/\)/,keywords:cd,contains:[...f,h,x]}]}},{scope:`keyword`,match:Q(/@/,$(...sd),Uu($(/\(/,/\s+/)))},{scope:`meta`,match:Q(/@/,ad)}],k={match:Uu(/\b[A-Z]/),relevance:0,contains:[{className:`type`,match:Q(/(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)/,id,`+`)},{className:`type`,match:od,relevance:0},{match:/[?!]+/,relevance:0},{match:/\.\.\./,relevance:0},{match:Q(/\s+&\s+/,Uu(od)),relevance:0}]},A={begin://,keywords:c,contains:[...r,...l,...O,d,k]};k.contains.push(A);let j={begin:/\(/,end:/\)/,relevance:0,keywords:c,contains:[`self`,{match:Q(ad,/\s*:/),keywords:`_|0`,relevance:0},...r,T,...l,...u,...f,h,x,...D,...O,k]},M={begin://,keywords:`repeat each`,contains:[...r,k]},N={begin:/\(/,end:/\)/,keywords:c,contains:[{begin:$(Uu(Q(ad,/\s*:/)),Uu(Q(ad,/\s+/,ad,/\s*:/))),end:/:/,relevance:0,contains:[{className:`keyword`,match:/\b_\b/},{className:`params`,match:ad}]},...r,...l,...f,h,x,...O,k,j],endsParent:!0,illegal:/["']/},P={match:[/(func|macro)/,/\s+/,$(E.match,ad,nd)],className:{1:`keyword`,3:`title.function`},contains:[M,N,t],illegal:[/\[/,/%/]},F={match:[/\b(?:subscript|init[?!]?)/,/\s*(?=[<(])/],className:{1:`keyword`},contains:[M,N,t],illegal:/\[|%/},I={match:[/operator/,/\s+/,nd],className:{1:`keyword`,3:`title`}},L={begin:[/precedencegroup/,/\s+/,od],className:{1:`keyword`,3:`title`},contains:[k],keywords:[...Zu,...Xu],end:/}/},R={match:[/class\b/,/\s+/,/func\b/,/\s+/,/\b[A-Za-z_][A-Za-z0-9_]*\b/],scope:{1:`keyword`,3:`keyword`,5:`title.function`}},ee={match:[/class\b/,/\s+/,/var\b/],scope:{1:`keyword`,3:`keyword`}},te={begin:[/(struct|protocol|class|extension|enum|actor)/,/\s+/,ad,/\s*/],beginScope:{1:`keyword`,3:`title.class`},keywords:c,contains:[M,...l,{begin:/:/,end:/\{/,keywords:c,contains:[{scope:`title.class.inherited`,match:od},...l],relevance:0}]};for(let e of x.variants){let t=e.contains.find(e=>e.label===`interpol`);t.keywords=c;let n=[...l,...u,...f,h,x,...D];t.contains=[...n,{begin:/\(/,end:/\)/,contains:[`self`,...n]}]}return{name:`Swift`,keywords:c,contains:[...r,P,F,R,ee,te,I,L,{beginKeywords:`import`,end:/$/,contains:[...r],relevance:0},T,...l,...u,...f,h,x,...D,...O,k,j]}}var ud=`[A-Za-z$_][0-9A-Za-z$_]*`,dd=`as.in.of.if.for.while.finally.var.new.function.do.return.void.else.break.catch.instanceof.with.throw.case.default.try.switch.continue.typeof.delete.let.yield.const.class.debugger.async.await.static.import.from.export.extends.using`.split(`.`),fd=[`true`,`false`,`null`,`undefined`,`NaN`,`Infinity`],pd=`Object.Function.Boolean.Symbol.Math.Date.Number.BigInt.String.RegExp.Array.Float32Array.Float64Array.Int8Array.Uint8Array.Uint8ClampedArray.Int16Array.Int32Array.Uint16Array.Uint32Array.BigInt64Array.BigUint64Array.Set.Map.WeakSet.WeakMap.ArrayBuffer.SharedArrayBuffer.Atomics.DataView.JSON.Promise.Generator.GeneratorFunction.AsyncFunction.Reflect.Proxy.Intl.WebAssembly`.split(`.`),md=[`Error`,`EvalError`,`InternalError`,`RangeError`,`ReferenceError`,`SyntaxError`,`TypeError`,`URIError`],hd=[`setInterval`,`setTimeout`,`clearInterval`,`clearTimeout`,`require`,`exports`,`eval`,`isFinite`,`isNaN`,`parseFloat`,`parseInt`,`decodeURI`,`decodeURIComponent`,`encodeURI`,`encodeURIComponent`,`escape`,`unescape`],gd=[`arguments`,`this`,`super`,`console`,`window`,`document`,`localStorage`,`sessionStorage`,`module`,`global`],_d=[].concat(hd,pd,md);function vd(e){let t=e.regex,n=(e,{after:t})=>{let n=``,end:``},a=/<[A-Za-z0-9\\._:-]+\s*\/>/,o={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,t)=>{let r=e[0].length+e.index,i=e.input[r];if(i===`<`||i===`,`){t.ignoreMatch();return}i===`>`&&(n(e,{after:r})||t.ignoreMatch());let a,o=e.input.substring(r);if(a=o.match(/^\s*=/)){t.ignoreMatch();return}if((a=o.match(/^\s+extends\s+/))&&a.index===0){t.ignoreMatch();return}}},s={$pattern:ud,keyword:dd,literal:fd,built_in:_d,"variable.language":gd},c=`[0-9](_?[0-9])*`,l=`\\.(${c})`,u=`0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*`,d={className:`number`,variants:[{begin:`(\\b(${u})((${l})|\\.)?|(${l}))[eE][+-]?(${c})\\b`},{begin:`\\b(${u})\\b((${l})\\b|\\.)?|(${l})\\b`},{begin:`\\b(0|[1-9](_?[0-9])*)n\\b`},{begin:`\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b`},{begin:`\\b0[bB][0-1](_?[0-1])*n?\\b`},{begin:`\\b0[oO][0-7](_?[0-7])*n?\\b`},{begin:`\\b0[0-7]+n?\\b`}],relevance:0},f={className:`subst`,begin:`\\$\\{`,end:`\\}`,keywords:s,contains:[]},p={begin:".?html`",end:``,starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,f],subLanguage:`xml`}},m={begin:".?css`",end:``,starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,f],subLanguage:`css`}},h={begin:".?gql`",end:``,starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,f],subLanguage:`graphql`}},g={className:`string`,begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE,f]},_={className:`comment`,variants:[e.COMMENT(/\/\*\*(?!\/)/,`\\*/`,{relevance:0,contains:[{begin:`(?=@[A-Za-z]+)`,relevance:0,contains:[{className:`doctag`,begin:`@[A-Za-z]+`},{className:`type`,begin:`\\{`,end:`\\}`,excludeEnd:!0,excludeBegin:!0,relevance:0},{className:`variable`,begin:`[A-Za-z$_][0-9A-Za-z$_]*(?=\\s*(-)|$)`,endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),e.C_BLOCK_COMMENT_MODE,e.C_LINE_COMMENT_MODE]},v=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,p,m,h,g,{match:/\$\d+/},d];f.contains=v.concat({begin:/\{/,end:/\}/,keywords:s,contains:[`self`].concat(v)});let y=[].concat(_,f.contains),b=y.concat([{begin:/(\s*)\(/,end:/\)/,keywords:s,contains:[`self`].concat(y)}]),x={className:`params`,begin:/(\s*)\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:s,contains:b},S={variants:[{match:[/class/,/\s+/,r,/\s+/,/extends/,/\s+/,t.concat(r,`(`,t.concat(/\./,r),`)*`)],scope:{1:`keyword`,3:`title.class`,5:`keyword`,7:`title.class.inherited`}},{match:[/class/,/\s+/,r],scope:{1:`keyword`,3:`title.class`}}]},C={relevance:0,match:t.either(/\bJSON/,/\b[A-Z][a-z]+([A-Z][a-z]*|\d)*/,/\b[A-Z]{2,}([A-Z][a-z]+|\d)+([A-Z][a-z]*)*/,/\b[A-Z]{2,}[a-z]+([A-Z][a-z]+|\d)*([A-Z][a-z]*)*/),className:`title.class`,keywords:{_:[...pd,...md]}},w={label:`use_strict`,className:`meta`,relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},T={variants:[{match:[/function/,/\s+/,r,/(?=\s*\()/]},{match:[/function/,/\s*(?=\()/]}],className:{1:`keyword`,3:`title.function`},label:`func.def`,contains:[x],illegal:/%/},E={relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/,className:`variable.constant`};function D(e){return t.concat(`(?!`,e.join(`|`),`)`)}let O={match:t.concat(/\b/,D([...hd,`super`,`import`].map(e=>`${e}\\s*\\(`)),r,t.lookahead(/\s*\(/)),className:`title.function`,relevance:0},k={begin:t.concat(/\./,t.lookahead(t.concat(r,/(?![0-9A-Za-z$_(])/))),end:r,excludeBegin:!0,keywords:`prototype`,className:`property`,relevance:0},A={match:[/get|set/,/\s+/,r,/(?=\()/],className:{1:`keyword`,3:`title.function`},contains:[{begin:/\(\)/},x]},j=`(\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|`+e.UNDERSCORE_IDENT_RE+`)\\s*=>`,M={match:[/const|var|let/,/\s+/,r,/\s*/,/=\s*/,/(async\s*)?/,t.lookahead(j)],keywords:`async`,className:{1:`keyword`,3:`title.function`},contains:[x]};return{name:`JavaScript`,aliases:[`js`,`jsx`,`mjs`,`cjs`],keywords:s,exports:{PARAMS_CONTAINS:b,CLASS_REFERENCE:C},illegal:/#(?![$_A-z])/,contains:[e.SHEBANG({label:`shebang`,binary:`node`,relevance:5}),w,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,p,m,h,g,_,{match:/\$\d+/},d,C,{scope:`attr`,match:r+t.lookahead(`:`),relevance:0},M,{begin:`(`+e.RE_STARTERS_RE+`|\\b(case|return|throw)\\b)\\s*`,keywords:`return throw case`,relevance:0,contains:[_,e.REGEXP_MODE,{className:`function`,begin:j,returnBegin:!0,end:`\\s*=>`,contains:[{className:`params`,variants:[{begin:e.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/(\s*)\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:s,contains:b}]}]},{begin:/,/,relevance:0},{match:/\s+/,relevance:0},{variants:[{begin:i.begin,end:i.end},{match:a},{begin:o.begin,"on:begin":o.isTrulyOpeningTag,end:o.end}],subLanguage:`xml`,contains:[{begin:o.begin,end:o.end,skip:!0,contains:[`self`]}]}]},T,{beginKeywords:`while if switch catch for`},{begin:`\\b(?!function)`+e.UNDERSCORE_IDENT_RE+`\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{`,returnBegin:!0,label:`func.def`,contains:[x,e.inherit(e.TITLE_MODE,{begin:r,className:`title.function`})]},{match:/\.\.\./,relevance:0},k,{match:`\\$[A-Za-z$_][0-9A-Za-z$_]*`,relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:`title.function`},contains:[x]},O,E,S,A,{match:/\$[(.]/}]}}function yd(e){let t=e.regex,n=vd(e),r=ud,i=[`any`,`void`,`number`,`boolean`,`string`,`object`,`never`,`symbol`,`bigint`,`unknown`],a={begin:[/namespace/,/\s+/,e.IDENT_RE],beginScope:{1:`keyword`,3:`title.class`}},o={beginKeywords:`interface`,end:/\{/,excludeEnd:!0,keywords:{keyword:`interface extends`,built_in:i},contains:[n.exports.CLASS_REFERENCE]},s={className:`meta`,relevance:10,begin:/^\s*['"]use strict['"]/},c={$pattern:ud,keyword:dd.concat([`type`,`interface`,`public`,`private`,`protected`,`implements`,`declare`,`abstract`,`readonly`,`enum`,`override`,`satisfies`]),literal:fd,built_in:_d.concat(i),"variable.language":gd},l={className:`meta`,begin:`@[A-Za-z$_][0-9A-Za-z$_]*`},u=(e,t,n)=>{let r=e.contains.findIndex(e=>e.label===t);if(r===-1)throw Error(`can not find mode to replace`);e.contains.splice(r,1,n)};Object.assign(n.keywords,c),n.exports.PARAMS_CONTAINS.push(l);let d=n.contains.find(e=>e.scope===`attr`),f=Object.assign({},d,{match:t.concat(r,t.lookahead(/\s*\?:/))});n.exports.PARAMS_CONTAINS.push([n.exports.CLASS_REFERENCE,d,f]),n.contains=n.contains.concat([l,a,o,f]),u(n,`shebang`,e.SHEBANG()),u(n,`use_strict`,s);let p=n.contains.find(e=>e.label===`func.def`);return p.relevance=0,Object.assign(n,{name:`TypeScript`,aliases:[`ts`,`tsx`,`mts`,`cts`]}),n}function bd(e){let t=e.regex,n={className:`string`,begin:/"(""|[^/n])"C\b/},r={className:`string`,begin:/"/,end:/"/,illegal:/\n/,contains:[{begin:/""/}]},i=/\d{1,2}\/\d{1,2}\/\d{4}/,a=/\d{4}-\d{1,2}-\d{1,2}/,o=/(\d|1[012])(:\d+){0,2} *(AM|PM)/,s=/\d{1,2}(:\d{1,2}){1,2}/,c={className:`literal`,variants:[{begin:t.concat(/# */,t.either(a,i),/ *#/)},{begin:t.concat(/# */,s,/ *#/)},{begin:t.concat(/# */,o,/ *#/)},{begin:t.concat(/# */,t.either(a,i),/ +/,t.either(o,s),/ *#/)}]},l={className:`number`,relevance:0,variants:[{begin:/\b\d[\d_]*((\.[\d_]+(E[+-]?[\d_]+)?)|(E[+-]?[\d_]+))[RFD@!#]?/},{begin:/\b\d[\d_]*((U?[SIL])|[%&])?/},{begin:/&H[\dA-F_]+((U?[SIL])|[%&])?/},{begin:/&O[0-7_]+((U?[SIL])|[%&])?/},{begin:/&B[01_]+((U?[SIL])|[%&])?/}]},u={className:`label`,begin:/^\w+:/},d=e.COMMENT(/'''/,/$/,{contains:[{className:`doctag`,begin:/<\/?/,end:/>/}]}),f=e.COMMENT(null,/$/,{variants:[{begin:/'/},{begin:/([\t ]|^)REM(?=\s)/}]});return{name:`Visual Basic .NET`,aliases:[`vb`],case_insensitive:!0,classNameAliases:{label:`symbol`},keywords:{keyword:`addhandler alias aggregate ansi as async assembly auto binary by byref byval call case catch class compare const continue custom declare default delegate dim distinct do each equals else elseif end enum erase error event exit explicit finally for friend from function get global goto group handles if implements imports in inherits interface into iterator join key let lib loop me mid module mustinherit mustoverride mybase myclass namespace narrowing new next notinheritable notoverridable of off on operator option optional order overloads overridable overrides paramarray partial preserve private property protected public raiseevent readonly redim removehandler resume return select set shadows shared skip static step stop structure strict sub synclock take text then throw to try unicode until using when where while widening with withevents writeonly yield`,built_in:`addressof and andalso await directcast gettype getxmlnamespace is isfalse isnot istrue like mod nameof new not or orelse trycast typeof xor cbool cbyte cchar cdate cdbl cdec cint clng cobj csbyte cshort csng cstr cuint culng cushort`,type:`boolean byte char date decimal double integer long object sbyte short single string uinteger ulong ushort`,literal:`true false nothing`},illegal:`//|\\{|\\}|endif|gosub|variant|wend|^\\$ `,contains:[n,r,c,l,u,d,f,{className:`meta`,begin:/[\t ]*#(const|disable|else|elseif|enable|end|externalsource|if|region)\b/,end:/$/,keywords:{keyword:`const disable else elseif enable end externalsource if region then`},contains:[f]}]}}function xd(e){e.regex;let t=e.COMMENT(/\(;/,/;\)/);return t.contains.push(`self`),{name:`WebAssembly`,keywords:{$pattern:/[\w.]+/,keyword:`anyfunc,block,br,br_if,br_table,call,call_indirect,data,drop,elem,else,end,export,func,global.get,global.set,local.get,local.set,local.tee,get_global,get_local,global,if,import,local,loop,memory,memory.grow,memory.size,module,mut,nop,offset,param,result,return,select,set_global,set_local,start,table,tee_local,then,type,unreachable`.split(`,`)},contains:[e.COMMENT(/;;/,/$/),t,{match:[/(?:offset|align)/,/\s*/,/=/],className:{1:`keyword`,3:`operator`}},{className:`variable`,begin:/\$[\w_]+/},{match:/(\((?!;)|\))+/,className:`punctuation`,relevance:0},{begin:[/(?:func|call|call_indirect)/,/\s+/,/\$[^\s)]+/],className:{1:`keyword`,3:`title.function`}},e.QUOTE_STRING_MODE,{match:/(i32|i64|f32|f64)(?!\.)/,className:`type`},{className:`keyword`,match:/\b(f32|f64|i32|i64)(?:\.(?:abs|add|and|ceil|clz|const|convert_[su]\/i(?:32|64)|copysign|ctz|demote\/f64|div(?:_[su])?|eqz?|extend_[su]\/i32|floor|ge(?:_[su])?|gt(?:_[su])?|le(?:_[su])?|load(?:(?:8|16|32)_[su])?|lt(?:_[su])?|max|min|mul|nearest|neg?|or|popcnt|promote\/f32|reinterpret\/[fi](?:32|64)|rem_[su]|rot[lr]|shl|shr_[su]|store(?:8|16|32)?|sqrt|sub|trunc(?:_[su]\/f(?:32|64))?|wrap\/i64|xor))\b/},{className:`number`,relevance:0,match:/[+-]?\b(?:\d(?:_?\d)*(?:\.\d(?:_?\d)*)?(?:[eE][+-]?\d(?:_?\d)*)?|0x[\da-fA-F](?:_?[\da-fA-F])*(?:\.[\da-fA-F](?:_?[\da-fA-D])*)?(?:[pP][+-]?\d(?:_?\d)*)?)\b|\binf\b|\bnan(?::0x[\da-fA-F](?:_?[\da-fA-D])*)?\b/}]}}function Sd(e){let t=e.regex,n=t.concat(/[\p{L}_]/u,t.optional(/[\p{L}0-9_.-]*:/u),/[\p{L}0-9_.-]*/u),r=/[\p{L}0-9._:-]+/u,i={className:`symbol`,begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},a={begin:/\s/,contains:[{className:`keyword`,begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]},o=e.inherit(a,{begin:/\(/,end:/\)/}),s=e.inherit(e.APOS_STRING_MODE,{className:`string`}),c=e.inherit(e.QUOTE_STRING_MODE,{className:`string`}),l={endsWithParent:!0,illegal:/`]+/}]}]}]};return{name:`HTML, XML`,aliases:[`html`,`xhtml`,`rss`,`atom`,`xjb`,`xsd`,`xsl`,`plist`,`wsf`,`svg`],case_insensitive:!0,unicodeRegex:!0,contains:[{className:`meta`,begin://,relevance:10,contains:[a,c,s,o,{begin:/\[/,end:/\]/,contains:[{className:`meta`,begin://,contains:[a,o,c,s]}]}]},e.COMMENT(//,{relevance:10}),{begin://,relevance:10},i,{className:`meta`,end:/\?>/,variants:[{begin:/<\?xml/,relevance:10,contains:[c]},{begin:/<\?[a-z][a-z0-9]+/}]},{className:`tag`,begin:/)/,end:/>/,keywords:{name:`style`},contains:[l],starts:{end:/<\/style>/,returnEnd:!0,subLanguage:[`css`,`xml`]}},{className:`tag`,begin:/)/,end:/>/,keywords:{name:`script`},contains:[l],starts:{end:/<\/script>/,returnEnd:!0,subLanguage:[`javascript`,`handlebars`,`xml`]}},{className:`tag`,begin:/<>|<\/>/},{className:`tag`,begin:t.concat(//,/>/,/\s/)))),end:/\/?>/,contains:[{className:`name`,begin:n,relevance:0,starts:l}]},{className:`tag`,begin:t.concat(/<\//,t.lookahead(t.concat(n,/>/))),contains:[{className:`name`,begin:n,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}}function Cd(e){let t=`true false yes no null`,n={className:`attr`,variants:[{begin:/[\w*@][\w*@ :()\./-]*:(?=[ \t]|$)/},{begin:/"[\w*@][\w*@ :()\./-]*":(?=[ \t]|$)/},{begin:/'[\w*@][\w*@ :()\./-]*':(?=[ \t]|$)/}]},r={className:`template-variable`,variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]},i={className:`string`,relevance:0,begin:/'/,end:/'/,contains:[{match:/''/,scope:`char.escape`,relevance:0}]},a={className:`string`,relevance:0,variants:[{begin:/"/,end:/"/},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,r]},o=e.inherit(a,{variants:[{begin:/'/,end:/'/,contains:[{begin:/''/,relevance:0}]},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),s={className:`number`,begin:`\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b`},c={end:`,`,endsWithParent:!0,excludeEnd:!0,keywords:t,relevance:0},l={begin:/\{/,end:/\}/,contains:[c],illegal:`\\n`,relevance:0},u={begin:`\\[`,end:`\\]`,contains:[c],illegal:`\\n`,relevance:0},d=[n,{className:`meta`,begin:`^---\\s*$`,relevance:10},{className:`string`,begin:`[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*`},{begin:`<%[%=-]?`,end:`[%-]?%>`,subLanguage:`ruby`,excludeBegin:!0,excludeEnd:!0,relevance:0},{className:`type`,begin:`!\\w+![\\w#;/?:@&=+$,.~*'()[\\]]+`},{className:`type`,begin:`!<[\\w#;/?:@&=+$,.~*'()[\\]]+>`},{className:`type`,begin:`![\\w#;/?:@&=+$,.~*'()[\\]]+`},{className:`type`,begin:`!![\\w#;/?:@&=+$,.~*'()[\\]]+`},{className:`meta`,begin:`&`+e.UNDERSCORE_IDENT_RE+`$`},{className:`meta`,begin:`\\*`+e.UNDERSCORE_IDENT_RE+`$`},{className:`bullet`,begin:`-(?=[ ]|$)`,relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:t,keywords:{literal:t}},s,{className:`number`,begin:e.C_NUMBER_RE+`\\b`,relevance:0},l,u,i,a],f=[...d];return f.pop(),f.push(o),c.contains=f,{name:`YAML`,case_insensitive:!0,aliases:[`yml`],contains:d}}var wd={arduino:Cl,bash:wl,c:Tl,cpp:El,csharp:Dl,css:Il,diff:Ll,go:Rl,graphql:zl,ini:Bl,java:Kl,javascript:tu,json:nu,kotlin:su,less:_u,lua:vu,makefile:yu,markdown:bu,objectivec:xu,perl:Su,php:Cu,"php-template":wu,plaintext:Tu,python:Eu,"python-repl":Du,r:Ou,ruby:ku,rust:Au,scss:zu,shell:Bu,sql:Vu,swift:ld,typescript:yd,vbnet:bd,wasm:xd,xml:Sd,yaml:Cd},Td=n(i(((e,t)=>{function n(e){return e instanceof Map?e.clear=e.delete=e.set=function(){throw Error(`map is read-only`)}:e instanceof Set&&(e.add=e.clear=e.delete=function(){throw Error(`set is read-only`)}),Object.freeze(e),Object.getOwnPropertyNames(e).forEach(t=>{let r=e[t],i=typeof r;(i===`object`||i===`function`)&&!Object.isFrozen(r)&&n(r)}),e}var r=class{constructor(e){e.data===void 0&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1}ignoreMatch(){this.isMatchIgnored=!0}};function i(e){return e.replace(/&/g,`&`).replace(//g,`>`).replace(/"/g,`"`).replace(/'/g,`'`)}function a(e,...t){let n=Object.create(null);for(let t in e)n[t]=e[t];return t.forEach(function(e){for(let t in e)n[t]=e[t]}),n}var o=``,s=e=>!!e.scope,c=(e,{prefix:t})=>{if(e.startsWith(`language:`))return e.replace(`language:`,`language-`);if(e.includes(`.`)){let n=e.split(`.`);return[`${t}${n.shift()}`,...n.map((e,t)=>`${e}${`_`.repeat(t+1)}`)].join(` `)}return`${t}${e}`},l=class{constructor(e,t){this.buffer=``,this.classPrefix=t.classPrefix,e.walk(this)}addText(e){this.buffer+=i(e)}openNode(e){if(!s(e))return;let t=c(e.scope,{prefix:this.classPrefix});this.span(t)}closeNode(e){s(e)&&(this.buffer+=o)}value(){return this.buffer}span(e){this.buffer+=``}},u=(e={})=>{let t={children:[]};return Object.assign(t,e),t},d=class e{constructor(){this.rootNode=u(),this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){let t=u({scope:e});this.add(t),this.stack.push(t)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){return typeof t==`string`?e.addText(t):t.children&&(e.openNode(t),t.children.forEach(t=>this._walk(e,t)),e.closeNode(t)),e}static _collapse(t){typeof t!=`string`&&t.children&&(t.children.every(e=>typeof e==`string`)?t.children=[t.children.join(``)]:t.children.forEach(t=>{e._collapse(t)}))}},f=class extends d{constructor(e){super(),this.options=e}addText(e){e!==``&&this.add(e)}startScope(e){this.openNode(e)}endScope(){this.closeNode()}__addSublanguage(e,t){let n=e.root;t&&(n.scope=`language:${t}`),this.add(n)}toHTML(){return new l(this,this.options).value()}finalize(){return this.closeAllNodes(),!0}};function p(e){return e?typeof e==`string`?e:e.source:null}function m(e){return _(`(?=`,e,`)`)}function h(e){return _(`(?:`,e,`)*`)}function g(e){return _(`(?:`,e,`)?`)}function _(...e){return e.map(e=>p(e)).join(``)}function v(e){let t=e[e.length-1];return typeof t==`object`&&t.constructor===Object?(e.splice(e.length-1,1),t):{}}function y(...e){return`(`+(v(e).capture?``:`?:`)+e.map(e=>p(e)).join(`|`)+`)`}function b(e){return RegExp(e.toString()+`|`).exec(``).length-1}function x(e,t){let n=e&&e.exec(t);return n&&n.index===0}var S=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;function C(e,{joinWith:t}){let n=0;return e.map(e=>{n+=1;let t=n,r=p(e),i=``;for(;r.length>0;){let e=S.exec(r);if(!e){i+=r;break}i+=r.substring(0,e.index),r=r.substring(e.index+e[0].length),e[0][0]===`\\`&&e[1]?i+=`\\`+String(Number(e[1])+t):(i+=e[0],e[0]===`(`&&n++)}return i}).map(e=>`(${e})`).join(t)}var w=/\b\B/,T=`[a-zA-Z]\\w*`,E=`[a-zA-Z_]\\w*`,D=`\\b\\d+(\\.\\d+)?`,O=`(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)`,k=`\\b(0b[01]+)`,A=`!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~`,j=(e={})=>{let t=/^#![ ]*\//;return e.binary&&(e.begin=_(t,/.*\b/,e.binary,/\b.*/)),a({scope:`meta`,begin:t,end:/$/,relevance:0,"on:begin":(e,t)=>{e.index!==0&&t.ignoreMatch()}},e)},M={begin:`\\\\[\\s\\S]`,relevance:0},N={scope:`string`,begin:`'`,end:`'`,illegal:`\\n`,contains:[M]},P={scope:`string`,begin:`"`,end:`"`,illegal:`\\n`,contains:[M]},F={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},I=function(e,t,n={}){let r=a({scope:`comment`,begin:e,end:t,contains:[]},n);r.contains.push({scope:`doctag`,begin:`[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)`,end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0});let i=y(`I`,`a`,`is`,`so`,`us`,`to`,`at`,`if`,`in`,`it`,`on`,/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/);return r.contains.push({begin:_(/[ ]+/,`(`,i,/[.]?[:]?([.][ ]|[ ])/,`){3}`)}),r},L=I(`//`,`$`),R=I(`/\\*`,`\\*/`),ee=I(`#`,`$`),te=Object.freeze({__proto__:null,APOS_STRING_MODE:N,BACKSLASH_ESCAPE:M,BINARY_NUMBER_MODE:{scope:`number`,begin:k,relevance:0},BINARY_NUMBER_RE:k,COMMENT:I,C_BLOCK_COMMENT_MODE:R,C_LINE_COMMENT_MODE:L,C_NUMBER_MODE:{scope:`number`,begin:O,relevance:0},C_NUMBER_RE:O,END_SAME_AS_BEGIN:function(e){return Object.assign(e,{"on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{t.data._beginMatch!==e[1]&&t.ignoreMatch()}})},HASH_COMMENT_MODE:ee,IDENT_RE:T,MATCH_NOTHING_RE:w,METHOD_GUARD:{begin:`\\.\\s*[a-zA-Z_]\\w*`,relevance:0},NUMBER_MODE:{scope:`number`,begin:D,relevance:0},NUMBER_RE:D,PHRASAL_WORDS_MODE:F,QUOTE_STRING_MODE:P,REGEXP_MODE:{scope:`regexp`,begin:/\/(?=[^/\n]*\/)/,end:/\/[gimuy]*/,contains:[M,{begin:/\[/,end:/\]/,relevance:0,contains:[M]}]},RE_STARTERS_RE:A,SHEBANG:j,TITLE_MODE:{scope:`title`,begin:T,relevance:0},UNDERSCORE_IDENT_RE:E,UNDERSCORE_TITLE_MODE:{scope:`title`,begin:E,relevance:0}});function ne(e,t){e.input[e.index-1]===`.`&&t.ignoreMatch()}function re(e,t){e.className!==void 0&&(e.scope=e.className,delete e.className)}function ie(e,t){t&&e.beginKeywords&&(e.begin=`\\b(`+e.beginKeywords.split(` `).join(`|`)+`)(?!\\.)(?=\\b|\\s)`,e.__beforeBegin=ne,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords,e.relevance===void 0&&(e.relevance=0))}function ae(e,t){Array.isArray(e.illegal)&&(e.illegal=y(...e.illegal))}function oe(e,t){if(e.match){if(e.begin||e.end)throw Error(`begin & end are not supported with match`);e.begin=e.match,delete e.match}}function se(e,t){e.relevance===void 0&&(e.relevance=1)}var ce=(e,t)=>{if(!e.beforeMatch)return;if(e.starts)throw Error(`beforeMatch cannot be used with starts`);let n=Object.assign({},e);Object.keys(e).forEach(t=>{delete e[t]}),e.keywords=n.keywords,e.begin=_(n.beforeMatch,m(n.begin)),e.starts={relevance:0,contains:[Object.assign(n,{endsParent:!0})]},e.relevance=0,delete n.beforeMatch},le=[`of`,`and`,`for`,`in`,`not`,`or`,`if`,`then`,`parent`,`list`,`value`],ue=`keyword`;function de(e,t,n=ue){let r=Object.create(null);return typeof e==`string`?i(n,e.split(` `)):Array.isArray(e)?i(n,e):Object.keys(e).forEach(function(n){Object.assign(r,de(e[n],t,n))}),r;function i(e,n){t&&(n=n.map(e=>e.toLowerCase())),n.forEach(function(t){let n=t.split(`|`);r[n[0]]=[e,fe(n[0],n[1])]})}}function fe(e,t){return t?Number(t):+!pe(e)}function pe(e){return le.includes(e.toLowerCase())}var me={},z=e=>{console.error(e)},he=(e,...t)=>{console.log(`WARN: ${e}`,...t)},ge=(e,t)=>{me[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),me[`${e}/${t}`]=!0)},_e=Error();function ve(e,t,{key:n}){let r=0,i=e[n],a={},o={};for(let e=1;e<=t.length;e++)o[e+r]=i[e],a[e+r]=!0,r+=b(t[e-1]);e[n]=o,e[n]._emit=a,e[n]._multi=!0}function ye(e){if(Array.isArray(e.begin)){if(e.skip||e.excludeBegin||e.returnBegin)throw z(`skip, excludeBegin, returnBegin not compatible with beginScope: {}`),_e;if(typeof e.beginScope!=`object`||e.beginScope===null)throw z(`beginScope must be object`),_e;ve(e,e.begin,{key:`beginScope`}),e.begin=C(e.begin,{joinWith:``})}}function be(e){if(Array.isArray(e.end)){if(e.skip||e.excludeEnd||e.returnEnd)throw z(`skip, excludeEnd, returnEnd not compatible with endScope: {}`),_e;if(typeof e.endScope!=`object`||e.endScope===null)throw z(`endScope must be object`),_e;ve(e,e.end,{key:`endScope`}),e.end=C(e.end,{joinWith:``})}}function B(e){e.scope&&typeof e.scope==`object`&&e.scope!==null&&(e.beginScope=e.scope,delete e.scope)}function xe(e){B(e),typeof e.beginScope==`string`&&(e.beginScope={_wrap:e.beginScope}),typeof e.endScope==`string`&&(e.endScope={_wrap:e.endScope}),ye(e),be(e)}function Se(e){function t(t,n){return new RegExp(p(t),`m`+(e.case_insensitive?`i`:``)+(e.unicodeRegex?`u`:``)+(n?`g`:``))}class n{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,t){t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]),this.matchAt+=b(e)+1}compile(){this.regexes.length===0&&(this.exec=()=>null);let e=this.regexes.map(e=>e[1]);this.matcherRe=t(C(e,{joinWith:`|`}),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;let t=this.matcherRe.exec(e);if(!t)return null;let n=t.findIndex((e,t)=>t>0&&e!==void 0),r=this.matchIndexes[n];return t.splice(0,n),Object.assign(t,r)}}class r{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];let t=new n;return this.rules.slice(e).forEach(([e,n])=>t.addRule(e,n)),t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){return this.regexIndex!==0}considerAll(){this.regexIndex=0}addRule(e,t){this.rules.push([e,t]),t.type===`begin`&&this.count++}exec(e){let t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex;let n=t.exec(e);if(this.resumingScanAtSamePosition()&&!(n&&n.index===this.lastIndex)){let t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)}return n&&(this.regexIndex+=n.position+1,this.regexIndex===this.count&&this.considerAll()),n}}function i(e){let t=new r;return e.contains.forEach(e=>t.addRule(e.begin,{rule:e,type:`begin`})),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:`end`}),e.illegal&&t.addRule(e.illegal,{type:`illegal`}),t}function o(n,r){let a=n;if(n.isCompiled)return a;[re,oe,xe,ce].forEach(e=>e(n,r)),e.compilerExtensions.forEach(e=>e(n,r)),n.__beforeBegin=null,[ie,ae,se].forEach(e=>e(n,r)),n.isCompiled=!0;let s=null;return typeof n.keywords==`object`&&n.keywords.$pattern&&(n.keywords=Object.assign({},n.keywords),s=n.keywords.$pattern,delete n.keywords.$pattern),s||=/\w+/,n.keywords&&=de(n.keywords,e.case_insensitive),a.keywordPatternRe=t(s,!0),r&&(n.begin||=/\B|\b/,a.beginRe=t(a.begin),!n.end&&!n.endsWithParent&&(n.end=/\B|\b/),n.end&&(a.endRe=t(a.end)),a.terminatorEnd=p(a.end)||``,n.endsWithParent&&r.terminatorEnd&&(a.terminatorEnd+=(n.end?`|`:``)+r.terminatorEnd)),n.illegal&&(a.illegalRe=t(n.illegal)),n.contains||=[],n.contains=[].concat(...n.contains.map(function(e){return we(e===`self`?n:e)})),n.contains.forEach(function(e){o(e,a)}),n.starts&&o(n.starts,r),a.matcher=i(a),a}if(e.compilerExtensions||=[],e.contains&&e.contains.includes(`self`))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return e.classNameAliases=a(e.classNameAliases||{}),o(e)}function Ce(e){return e?e.endsWithParent||Ce(e.starts):!1}function we(e){return e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map(function(t){return a(e,{variants:null},t)})),e.cachedVariants?e.cachedVariants:Ce(e)?a(e,{starts:e.starts?a(e.starts):null}):Object.isFrozen(e)?a(e):e}var Te=`11.11.1`,Ee=class extends Error{constructor(e,t){super(e),this.name=`HTMLInjectionError`,this.html=t}},De=i,Oe=a,ke=Symbol(`nomatch`),Ae=7,je=function(e){let t=Object.create(null),i=Object.create(null),a=[],o=!0,s=`Could not find the language '{}', did you forget to load/include a language module?`,c={disableAutodetect:!0,name:`Plain text`,contains:[]},l={ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:`hljs-`,cssSelector:`pre code`,languages:null,__emitter:f};function u(e){return l.noHighlightRe.test(e)}function d(e){let t=e.className+` `;t+=e.parentNode?e.parentNode.className:``;let n=l.languageDetectRe.exec(t);if(n){let t=N(n[1]);return t||(he(s.replace(`{}`,n[1])),he(`Falling back to no-highlight mode for this block.`,e)),t?n[1]:`no-highlight`}return t.split(/\s+/).find(e=>u(e)||N(e))}function p(e,t,n){let r=``,i=``;typeof t==`object`?(r=e,n=t.ignoreIllegals,i=t.language):(ge(`10.7.0`,`highlight(lang, code, ...args) has been deprecated.`),ge(`10.7.0`,`Please use highlight(code, options) instead. +]`,m={scope:`string`,variants:[u,l,d,f]},h={scope:`number`,variants:[{begin:`\\b0[bB][01]+(?:_[01]+)*\\b`},{begin:`\\b0[oO][0-7]+(?:_[0-7]+)*\\b`},{begin:`\\b0[xX][\\da-fA-F]+(?:_[\\da-fA-F]+)*\\b`},{begin:`(?:\\b\\d+(?:_\\d+)*(\\.(?:\\d+(?:_\\d+)*))?|\\B\\.\\d+)(?:[eE][+-]?\\d+)?`}],relevance:0},g=[`false`,`null`,`true`],_=`__CLASS__.__DIR__.__FILE__.__FUNCTION__.__COMPILER_HALT_OFFSET__.__LINE__.__METHOD__.__NAMESPACE__.__TRAIT__.die.echo.exit.include.include_once.print.require.require_once.array.abstract.and.as.binary.bool.boolean.break.callable.case.catch.class.clone.const.continue.declare.default.do.double.else.elseif.empty.enddeclare.endfor.endforeach.endif.endswitch.endwhile.enum.eval.extends.final.finally.float.for.foreach.from.global.goto.if.implements.instanceof.insteadof.int.integer.interface.isset.iterable.list.match|0.mixed.new.never.object.or.private.protected.public.readonly.real.return.string.switch.throw.trait.try.unset.use.var.void.while.xor.yield`.split(`.`),v=`Error|0.AppendIterator.ArgumentCountError.ArithmeticError.ArrayIterator.ArrayObject.AssertionError.BadFunctionCallException.BadMethodCallException.CachingIterator.CallbackFilterIterator.CompileError.Countable.DirectoryIterator.DivisionByZeroError.DomainException.EmptyIterator.ErrorException.Exception.FilesystemIterator.FilterIterator.GlobIterator.InfiniteIterator.InvalidArgumentException.IteratorIterator.LengthException.LimitIterator.LogicException.MultipleIterator.NoRewindIterator.OutOfBoundsException.OutOfRangeException.OuterIterator.OverflowException.ParentIterator.ParseError.RangeException.RecursiveArrayIterator.RecursiveCachingIterator.RecursiveCallbackFilterIterator.RecursiveDirectoryIterator.RecursiveFilterIterator.RecursiveIterator.RecursiveIteratorIterator.RecursiveRegexIterator.RecursiveTreeIterator.RegexIterator.RuntimeException.SeekableIterator.SplDoublyLinkedList.SplFileInfo.SplFileObject.SplFixedArray.SplHeap.SplMaxHeap.SplMinHeap.SplObjectStorage.SplObserver.SplPriorityQueue.SplQueue.SplStack.SplSubject.SplTempFileObject.TypeError.UnderflowException.UnexpectedValueException.UnhandledMatchError.ArrayAccess.BackedEnum.Closure.Fiber.Generator.Iterator.IteratorAggregate.Serializable.Stringable.Throwable.Traversable.UnitEnum.WeakReference.WeakMap.Directory.__PHP_Incomplete_Class.parent.php_user_filter.self.static.stdClass`.split(`.`),y={keyword:_,literal:(e=>{let t=[];return e.forEach(e=>{t.push(e),e.toLowerCase()===e?t.push(e.toUpperCase()):t.push(e.toLowerCase())}),t})(g),built_in:v},b=e=>e.map(e=>e.replace(/\|\d+$/,``)),x={variants:[{match:[/new/,t.concat(p,`+`),t.concat(`(?!`,b(v).join(`\\b|`),`\\b)`),i],scope:{1:`keyword`,4:`title.class`}}]},S=t.concat(r,`\\b(?!\\()`),C={variants:[{match:[t.concat(/::/,t.lookahead(/(?!class\b)/)),S],scope:{2:`variable.constant`}},{match:[/::/,/class/],scope:{2:`variable.language`}},{match:[i,t.concat(/::/,t.lookahead(/(?!class\b)/)),S],scope:{1:`title.class`,3:`variable.constant`}},{match:[i,t.concat(`::`,t.lookahead(/(?!class\b)/))],scope:{1:`title.class`}},{match:[i,/::/,/class/],scope:{1:`title.class`,3:`variable.language`}}]},w={scope:`attr`,match:t.concat(r,t.lookahead(`:`),t.lookahead(/(?!::)/))},T={relevance:0,begin:/\(/,end:/\)/,keywords:y,contains:[w,o,C,e.C_BLOCK_COMMENT_MODE,m,h,x]},E={relevance:0,match:[/\b/,t.concat(`(?!fn\\b|function\\b|`,b(_).join(`\\b|`),`|`,b(v).join(`\\b|`),`\\b)`),r,t.concat(p,`*`),t.lookahead(/(?=\()/)],scope:{3:`title.function.invoke`},contains:[T]};T.contains.push(E);let D=[w,C,e.C_BLOCK_COMMENT_MODE,m,h,x],O={begin:t.concat(/#\[\s*\\?/,t.either(i,a)),beginScope:`meta`,end:/]/,endScope:`meta`,keywords:{literal:g,keyword:[`new`,`array`]},contains:[{begin:/\[/,end:/]/,keywords:{literal:g,keyword:[`new`,`array`]},contains:[`self`,...D]},...D,{scope:`meta`,variants:[{match:i},{match:a}]}]};return{case_insensitive:!1,keywords:y,contains:[O,e.HASH_COMMENT_MODE,e.COMMENT(`//`,`$`),e.COMMENT(`/\\*`,`\\*/`,{contains:[{scope:`doctag`,match:`@[A-Za-z]+`}]}),{match:/__halt_compiler\(\);/,keywords:`__halt_compiler`,starts:{scope:`comment`,end:e.MATCH_NOTHING_RE,contains:[{match:/\?>/,scope:`meta`,endsParent:!0}]}},s,{scope:`variable.language`,match:/\$this\b/},o,E,C,{match:[/const/,/\s/,r],scope:{1:`keyword`,3:`variable.constant`}},x,{scope:`function`,relevance:0,beginKeywords:`fn function`,end:/[;{]/,excludeEnd:!0,illegal:`[$%\\[]`,contains:[{beginKeywords:`use`},e.UNDERSCORE_TITLE_MODE,{begin:`=>`,endsParent:!0},{scope:`params`,begin:`\\(`,end:`\\)`,excludeBegin:!0,excludeEnd:!0,keywords:y,contains:[`self`,O,o,C,e.C_BLOCK_COMMENT_MODE,m,h]}]},{scope:`class`,variants:[{beginKeywords:`enum`,illegal:/[($"]/},{beginKeywords:`class interface trait`,illegal:/[:($"]/}],relevance:0,end:/\{/,excludeEnd:!0,contains:[{beginKeywords:`extends implements`},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:`namespace`,relevance:0,end:`;`,illegal:/[.']/,contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{scope:`title.class`})]},{beginKeywords:`use`,relevance:0,end:`;`,contains:[{match:/\b(as|const|function)\b/,scope:`keyword`},e.UNDERSCORE_TITLE_MODE]},m,h]}}function wu(e){return{name:`PHP template`,subLanguage:`xml`,contains:[{begin:/<\?(php|=)?/,end:/\?>/,subLanguage:`php`,contains:[{begin:`/\\*`,end:`\\*/`,skip:!0},{begin:`b"`,end:`"`,skip:!0},{begin:`b'`,end:`'`,skip:!0},e.inherit(e.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]}]}}function Tu(e){return{name:`Plain text`,aliases:[`text`,`txt`],disableAutodetect:!0}}function Eu(e){let t=e.regex,n=/[\p{XID_Start}_]\p{XID_Continue}*/u,r=`and.as.assert.async.await.break.case.class.continue.def.del.elif.else.except.finally.for.from.global.if.import.in.is.lambda.match.nonlocal|10.not.or.pass.raise.return.try.while.with.yield`.split(`.`),i={$pattern:/[A-Za-z]\w+|__\w+__/,keyword:r,built_in:`__import__.abs.all.any.ascii.bin.bool.breakpoint.bytearray.bytes.callable.chr.classmethod.compile.complex.delattr.dict.dir.divmod.enumerate.eval.exec.filter.float.format.frozenset.getattr.globals.hasattr.hash.help.hex.id.input.int.isinstance.issubclass.iter.len.list.locals.map.max.memoryview.min.next.object.oct.open.ord.pow.print.property.range.repr.reversed.round.set.setattr.slice.sorted.staticmethod.str.sum.super.tuple.type.vars.zip`.split(`.`),literal:[`__debug__`,`Ellipsis`,`False`,`None`,`NotImplemented`,`True`],type:[`Any`,`Callable`,`Coroutine`,`Dict`,`List`,`Literal`,`Generic`,`Optional`,`Sequence`,`Set`,`Tuple`,`Type`,`Union`]},a={className:`meta`,begin:/^(>>>|\.\.\.) /},o={className:`subst`,begin:/\{/,end:/\}/,keywords:i,illegal:/#/},s={begin:/\{\{/,relevance:0},c={className:`string`,contains:[e.BACKSLASH_ESCAPE],variants:[{begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/([fF][rR]|[rR][fF]|[fF])'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a,s,o]},{begin:/([fF][rR]|[rR][fF]|[fF])"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a,s,o]},{begin:/([uU]|[rR])'/,end:/'/,relevance:10},{begin:/([uU]|[rR])"/,end:/"/,relevance:10},{begin:/([bB]|[bB][rR]|[rR][bB])'/,end:/'/},{begin:/([bB]|[bB][rR]|[rR][bB])"/,end:/"/},{begin:/([fF][rR]|[rR][fF]|[fF])'/,end:/'/,contains:[e.BACKSLASH_ESCAPE,s,o]},{begin:/([fF][rR]|[rR][fF]|[fF])"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,o]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},l=`[0-9](_?[0-9])*`,u=`(\\b(${l}))?\\.(${l})|\\b(${l})\\.`,d=`\\b|${r.join(`|`)}`,f={className:`number`,relevance:0,variants:[{begin:`(\\b(${l})|(${u}))[eE][+-]?(${l})[jJ]?(?=${d})`},{begin:`(${u})[jJ]?`},{begin:`\\b([1-9](_?[0-9])*|0+(_?0)*)[lLjJ]?(?=${d})`},{begin:`\\b0[bB](_?[01])+[lL]?(?=${d})`},{begin:`\\b0[oO](_?[0-7])+[lL]?(?=${d})`},{begin:`\\b0[xX](_?[0-9a-fA-F])+[lL]?(?=${d})`},{begin:`\\b(${l})[jJ](?=${d})`}]},p={className:`comment`,begin:t.lookahead(/# type:/),end:/$/,keywords:i,contains:[{begin:/# type:/},{begin:/#/,end:/\b\B/,endsWithParent:!0}]},m={className:`params`,variants:[{className:``,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:i,contains:[`self`,a,f,c,e.HASH_COMMENT_MODE]}]};return o.contains=[c,f,a],{name:`Python`,aliases:[`py`,`gyp`,`ipython`],unicodeRegex:!0,keywords:i,illegal:/(<\/|\?)|=>/,contains:[a,f,{scope:`variable.language`,match:/\bself\b/},{beginKeywords:`if`,relevance:0},{match:/\bor\b/,scope:`keyword`},c,p,e.HASH_COMMENT_MODE,{match:[/\bdef/,/\s+/,n],scope:{1:`keyword`,3:`title.function`},contains:[m]},{variants:[{match:[/\bclass/,/\s+/,n,/\s*/,/\(\s*/,n,/\s*\)/]},{match:[/\bclass/,/\s+/,n]}],scope:{1:`keyword`,3:`title.class`,6:`title.class.inherited`}},{className:`meta`,begin:/^[\t ]*@/,end:/(?=#)|$/,contains:[f,m,c]}]}}function Du(e){return{aliases:[`pycon`],contains:[{className:`meta.prompt`,starts:{end:/ |$/,starts:{end:`$`,subLanguage:`python`}},variants:[{begin:/^>>>(?=[ ]|$)/},{begin:/^\.\.\.(?=[ ]|$)/}]}]}}function Ou(e){let t=e.regex,n=/(?:(?:[a-zA-Z]|\.[._a-zA-Z])[._a-zA-Z0-9]*)|\.(?!\d)/,r=t.either(/0[xX][0-9a-fA-F]+\.[0-9a-fA-F]*[pP][+-]?\d+i?/,/0[xX][0-9a-fA-F]+(?:[pP][+-]?\d+)?[Li]?/,/(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?[Li]?/),i=/[=!<>:]=|\|\||&&|:::?|<-|<<-|->>|->|\|>|[-+*\/?!$&|:<=>@^~]|\*\*/,a=t.either(/[()]/,/[{}]/,/\[\[/,/[[\]]/,/\\/,/,/);return{name:`R`,keywords:{$pattern:n,keyword:`function if in break next repeat else for while`,literal:`NULL NA TRUE FALSE Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10`,built_in:`LETTERS letters month.abb month.name pi T F abs acos acosh all any anyNA Arg as.call as.character as.complex as.double as.environment as.integer as.logical as.null.default as.numeric as.raw asin asinh atan atanh attr attributes baseenv browser c call ceiling class Conj cos cosh cospi cummax cummin cumprod cumsum digamma dim dimnames emptyenv exp expression floor forceAndCall gamma gc.time globalenv Im interactive invisible is.array is.atomic is.call is.character is.complex is.double is.environment is.expression is.finite is.function is.infinite is.integer is.language is.list is.logical is.matrix is.na is.name is.nan is.null is.numeric is.object is.pairlist is.raw is.recursive is.single is.symbol lazyLoadDBfetch length lgamma list log max min missing Mod names nargs nzchar oldClass on.exit pos.to.env proc.time prod quote range Re rep retracemem return round seq_along seq_len seq.int sign signif sin sinh sinpi sqrt standardGeneric substitute sum switch tan tanh tanpi tracemem trigamma trunc unclass untracemem UseMethod xtfrm`},contains:[e.COMMENT(/#'/,/$/,{contains:[{scope:`doctag`,match:/@examples/,starts:{end:t.lookahead(t.either(/\n^#'\s*(?=@[a-zA-Z]+)/,/\n^(?!#')/)),endsParent:!0}},{scope:`doctag`,begin:`@param`,end:/$/,contains:[{scope:`variable`,variants:[{match:n},{match:/`(?:\\.|[^`\\])+`/}],endsParent:!0}]},{scope:`doctag`,match:/@[a-zA-Z]+/},{scope:`keyword`,match:/\\[a-zA-Z]+/}]}),e.HASH_COMMENT_MODE,{scope:`string`,contains:[e.BACKSLASH_ESCAPE],variants:[e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\(/,end:/\)(-*)"/}),e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\{/,end:/\}(-*)"/}),e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\[/,end:/\](-*)"/}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\(/,end:/\)(-*)'/}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\{/,end:/\}(-*)'/}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\[/,end:/\](-*)'/}),{begin:`"`,end:`"`,relevance:0},{begin:`'`,end:`'`,relevance:0}]},{relevance:0,variants:[{scope:{1:`operator`,2:`number`},match:[i,r]},{scope:{1:`operator`,2:`number`},match:[/%[^%]*%/,r]},{scope:{1:`punctuation`,2:`number`},match:[a,r]},{scope:{2:`number`},match:[/[^a-zA-Z0-9._]|^/,r]}]},{scope:{3:`operator`},match:[n,/\s+/,/<-/,/\s+/]},{scope:`operator`,relevance:0,variants:[{match:i},{match:/%[^%]*%/}]},{scope:`punctuation`,relevance:0,match:a},{begin:"`",end:"`",contains:[{begin:/\\./}]}]}}function ku(e){let t=e.regex,n="([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)",r=t.either(/\b([A-Z]+[a-z0-9]+)+/,/\b([A-Z]+[a-z0-9]+)+[A-Z]+/),i=t.concat(r,/(::\w+)*/),a={"variable.constant":[`__FILE__`,`__LINE__`,`__ENCODING__`],"variable.language":[`self`,`super`],keyword:`alias.and.begin.BEGIN.break.case.class.defined.do.else.elsif.end.END.ensure.for.if.in.module.next.not.or.redo.require.rescue.retry.return.then.undef.unless.until.when.while.yield.include.extend.prepend.public.private.protected.raise.throw`.split(`.`),built_in:[`proc`,`lambda`,`attr_accessor`,`attr_reader`,`attr_writer`,`define_method`,`private_constant`,`module_function`],literal:[`true`,`false`,`nil`]},o={className:`doctag`,begin:`@[A-Za-z]+`},s={begin:`#<`,end:`>`},c=[e.COMMENT(`#`,`$`,{contains:[o]}),e.COMMENT(`^=begin`,`^=end`,{contains:[o],relevance:10}),e.COMMENT(`^__END__`,e.MATCH_NOTHING_RE)],l={className:`subst`,begin:/#\{/,end:/\}/,keywords:a},u={className:`string`,contains:[e.BACKSLASH_ESCAPE,l],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:/%[qQwWx]?\(/,end:/\)/},{begin:/%[qQwWx]?\[/,end:/\]/},{begin:/%[qQwWx]?\{/,end:/\}/},{begin:/%[qQwWx]?/},{begin:/%[qQwWx]?\//,end:/\//},{begin:/%[qQwWx]?%/,end:/%/},{begin:/%[qQwWx]?-/,end:/-/},{begin:/%[qQwWx]?\|/,end:/\|/},{begin:/\B\?(\\\d{1,3})/},{begin:/\B\?(\\x[A-Fa-f0-9]{1,2})/},{begin:/\B\?(\\u\{?[A-Fa-f0-9]{1,6}\}?)/},{begin:/\B\?(\\M-\\C-|\\M-\\c|\\c\\M-|\\M-|\\C-\\M-)[\x20-\x7e]/},{begin:/\B\?\\(c|C-)[\x20-\x7e]/},{begin:/\B\?\\?\S/},{begin:t.concat(/<<[-~]?'?/,t.lookahead(/(\w+)(?=\W)[^\n]*\n(?:[^\n]*\n)*?\s*\1\b/)),contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,contains:[e.BACKSLASH_ESCAPE,l]})]}]},d=`[0-9](_?[0-9])*`,f={className:`number`,relevance:0,variants:[{begin:`\\b([1-9](_?[0-9])*|0)(\\.(${d}))?([eE][+-]?(${d})|r)?i?\\b`},{begin:`\\b0[dD][0-9](_?[0-9])*r?i?\\b`},{begin:`\\b0[bB][0-1](_?[0-1])*r?i?\\b`},{begin:`\\b0[oO][0-7](_?[0-7])*r?i?\\b`},{begin:`\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*r?i?\\b`},{begin:`\\b0(_?[0-7])+r?i?\\b`}]},p={variants:[{match:/\(\)/},{className:`params`,begin:/\(/,end:/(?=\))/,excludeBegin:!0,endsParent:!0,keywords:a}]},m=[u,{variants:[{match:[/class\s+/,i,/\s+<\s+/,i]},{match:[/\b(class|module)\s+/,i]}],scope:{2:`title.class`,4:`title.class.inherited`},keywords:a},{match:[/(include|extend)\s+/,i],scope:{2:`title.class`},keywords:a},{relevance:0,match:[i,/\.new[. (]/],scope:{1:`title.class`}},{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/,className:`variable.constant`},{relevance:0,match:r,scope:`title.class`},{match:[/def/,/\s+/,n],scope:{1:`keyword`,3:`title.function`},contains:[p]},{begin:e.IDENT_RE+`::`},{className:`symbol`,begin:e.UNDERSCORE_IDENT_RE+`(!|\\?)?:`,relevance:0},{className:`symbol`,begin:`:(?!\\s)`,contains:[u,{begin:n}],relevance:0},f,{className:`variable`,begin:`(\\$\\W)|((\\$|@@?)(\\w+))(?=[^@$?])(?![A-Za-z])(?![@$?'])`},{className:`params`,begin:/\|(?!=)/,end:/\|/,excludeBegin:!0,excludeEnd:!0,relevance:0,keywords:a},{begin:`(`+e.RE_STARTERS_RE+`|unless)\\s*`,keywords:`unless`,contains:[{className:`regexp`,contains:[e.BACKSLASH_ESCAPE,l],illegal:/\n/,variants:[{begin:`/`,end:`/[a-z]*`},{begin:/%r\{/,end:/\}[a-z]*/},{begin:`%r\\(`,end:`\\)[a-z]*`},{begin:`%r!`,end:`![a-z]*`},{begin:`%r\\[`,end:`\\][a-z]*`}]}].concat(s,c),relevance:0}].concat(s,c);l.contains=m,p.contains=m;let h=[{begin:/^\s*=>/,starts:{end:`$`,contains:m}},{className:`meta.prompt`,begin:`^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+[>*]|(\\w+-)?\\d+\\.\\d+\\.\\d+(p\\d+)?[^\\d][^>]+>)(?=[ ])`,starts:{end:`$`,keywords:a,contains:m}}];return c.unshift(s),{name:`Ruby`,aliases:[`rb`,`gemspec`,`podspec`,`thor`,`irb`],keywords:a,illegal:/\/\*/,contains:[e.SHEBANG({binary:`ruby`})].concat(h,c,m)}}function Au(e){let t=e.regex,n=/(r#)?/,r=t.concat(n,e.UNDERSCORE_IDENT_RE),i=t.concat(n,e.IDENT_RE),a={className:`title.function.invoke`,relevance:0,begin:t.concat(/\b/,/(?!let|for|while|if|else|match\b)/,i,t.lookahead(/\s*\(/))},o=`abstract.as.async.await.become.box.break.const.continue.crate.do.dyn.else.enum.extern.false.final.fn.for.if.impl.in.let.loop.macro.match.mod.move.mut.override.priv.pub.ref.return.self.Self.static.struct.super.trait.true.try.type.typeof.union.unsafe.unsized.use.virtual.where.while.yield`.split(`.`),s=[`true`,`false`,`Some`,`None`,`Ok`,`Err`],c=`drop .Copy.Send.Sized.Sync.Drop.Fn.FnMut.FnOnce.ToOwned.Clone.Debug.PartialEq.PartialOrd.Eq.Ord.AsRef.AsMut.Into.From.Default.Iterator.Extend.IntoIterator.DoubleEndedIterator.ExactSizeIterator.SliceConcatExt.ToString.assert!.assert_eq!.bitflags!.bytes!.cfg!.col!.concat!.concat_idents!.debug_assert!.debug_assert_eq!.env!.eprintln!.panic!.file!.format!.format_args!.include_bytes!.include_str!.line!.local_data_key!.module_path!.option_env!.print!.println!.select!.stringify!.try!.unimplemented!.unreachable!.vec!.write!.writeln!.macro_rules!.assert_ne!.debug_assert_ne!`.split(`.`),l=[`i8`,`i16`,`i32`,`i64`,`i128`,`isize`,`u8`,`u16`,`u32`,`u64`,`u128`,`usize`,`f32`,`f64`,`str`,`char`,`bool`,`Box`,`Option`,`Result`,`String`,`Vec`];return{name:`Rust`,aliases:[`rs`],keywords:{$pattern:e.IDENT_RE+`!?`,type:l,keyword:o,literal:s,built_in:c},illegal:``},a]}}var ju=e=>({IMPORTANT:{scope:`meta`,begin:`!important`},BLOCK_COMMENT:e.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:`number`,begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},FUNCTION_DISPATCH:{className:`built_in`,begin:/[\w-]+(?=\()/},ATTRIBUTE_SELECTOR_MODE:{scope:`selector-attr`,begin:/\[/,end:/\]/,illegal:`$`,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{scope:`number`,begin:e.NUMBER_RE+`(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?`,relevance:0},CSS_VARIABLE:{className:`attr`,begin:/--[A-Za-z_][A-Za-z0-9_-]*/}}),Mu=`a.abbr.address.article.aside.audio.b.blockquote.body.button.canvas.caption.cite.code.dd.del.details.dfn.div.dl.dt.em.fieldset.figcaption.figure.footer.form.h1.h2.h3.h4.h5.h6.header.hgroup.html.i.iframe.img.input.ins.kbd.label.legend.li.main.mark.menu.nav.object.ol.optgroup.option.p.picture.q.quote.samp.section.select.source.span.strong.summary.sup.table.tbody.td.textarea.tfoot.th.thead.time.tr.ul.var.video`.split(`.`),Nu=`defs.g.marker.mask.pattern.svg.switch.symbol.feBlend.feColorMatrix.feComponentTransfer.feComposite.feConvolveMatrix.feDiffuseLighting.feDisplacementMap.feFlood.feGaussianBlur.feImage.feMerge.feMorphology.feOffset.feSpecularLighting.feTile.feTurbulence.linearGradient.radialGradient.stop.circle.ellipse.image.line.path.polygon.polyline.rect.text.use.textPath.tspan.foreignObject.clipPath`.split(`.`),Pu=[...Mu,...Nu],Fu=`any-hover.any-pointer.aspect-ratio.color.color-gamut.color-index.device-aspect-ratio.device-height.device-width.display-mode.forced-colors.grid.height.hover.inverted-colors.monochrome.orientation.overflow-block.overflow-inline.pointer.prefers-color-scheme.prefers-contrast.prefers-reduced-motion.prefers-reduced-transparency.resolution.scan.scripting.update.width.min-width.max-width.min-height.max-height`.split(`.`).sort().reverse(),Iu=`active.any-link.blank.checked.current.default.defined.dir.disabled.drop.empty.enabled.first.first-child.first-of-type.fullscreen.future.focus.focus-visible.focus-within.has.host.host-context.hover.indeterminate.in-range.invalid.is.lang.last-child.last-of-type.left.link.local-link.not.nth-child.nth-col.nth-last-child.nth-last-col.nth-last-of-type.nth-of-type.only-child.only-of-type.optional.out-of-range.past.placeholder-shown.read-only.read-write.required.right.root.scope.target.target-within.user-invalid.valid.visited.where`.split(`.`).sort().reverse(),Lu=[`after`,`backdrop`,`before`,`cue`,`cue-region`,`first-letter`,`first-line`,`grammar-error`,`marker`,`part`,`placeholder`,`selection`,`slotted`,`spelling-error`].sort().reverse(),Ru=`accent-color.align-content.align-items.align-self.alignment-baseline.all.anchor-name.animation.animation-composition.animation-delay.animation-direction.animation-duration.animation-fill-mode.animation-iteration-count.animation-name.animation-play-state.animation-range.animation-range-end.animation-range-start.animation-timeline.animation-timing-function.appearance.aspect-ratio.backdrop-filter.backface-visibility.background.background-attachment.background-blend-mode.background-clip.background-color.background-image.background-origin.background-position.background-position-x.background-position-y.background-repeat.background-size.baseline-shift.block-size.border.border-block.border-block-color.border-block-end.border-block-end-color.border-block-end-style.border-block-end-width.border-block-start.border-block-start-color.border-block-start-style.border-block-start-width.border-block-style.border-block-width.border-bottom.border-bottom-color.border-bottom-left-radius.border-bottom-right-radius.border-bottom-style.border-bottom-width.border-collapse.border-color.border-end-end-radius.border-end-start-radius.border-image.border-image-outset.border-image-repeat.border-image-slice.border-image-source.border-image-width.border-inline.border-inline-color.border-inline-end.border-inline-end-color.border-inline-end-style.border-inline-end-width.border-inline-start.border-inline-start-color.border-inline-start-style.border-inline-start-width.border-inline-style.border-inline-width.border-left.border-left-color.border-left-style.border-left-width.border-radius.border-right.border-right-color.border-right-style.border-right-width.border-spacing.border-start-end-radius.border-start-start-radius.border-style.border-top.border-top-color.border-top-left-radius.border-top-right-radius.border-top-style.border-top-width.border-width.bottom.box-align.box-decoration-break.box-direction.box-flex.box-flex-group.box-lines.box-ordinal-group.box-orient.box-pack.box-shadow.box-sizing.break-after.break-before.break-inside.caption-side.caret-color.clear.clip.clip-path.clip-rule.color.color-interpolation.color-interpolation-filters.color-profile.color-rendering.color-scheme.column-count.column-fill.column-gap.column-rule.column-rule-color.column-rule-style.column-rule-width.column-span.column-width.columns.contain.contain-intrinsic-block-size.contain-intrinsic-height.contain-intrinsic-inline-size.contain-intrinsic-size.contain-intrinsic-width.container.container-name.container-type.content.content-visibility.counter-increment.counter-reset.counter-set.cue.cue-after.cue-before.cursor.cx.cy.direction.display.dominant-baseline.empty-cells.enable-background.field-sizing.fill.fill-opacity.fill-rule.filter.flex.flex-basis.flex-direction.flex-flow.flex-grow.flex-shrink.flex-wrap.float.flood-color.flood-opacity.flow.font.font-display.font-family.font-feature-settings.font-kerning.font-language-override.font-optical-sizing.font-palette.font-size.font-size-adjust.font-smooth.font-smoothing.font-stretch.font-style.font-synthesis.font-synthesis-position.font-synthesis-small-caps.font-synthesis-style.font-synthesis-weight.font-variant.font-variant-alternates.font-variant-caps.font-variant-east-asian.font-variant-emoji.font-variant-ligatures.font-variant-numeric.font-variant-position.font-variation-settings.font-weight.forced-color-adjust.gap.glyph-orientation-horizontal.glyph-orientation-vertical.grid.grid-area.grid-auto-columns.grid-auto-flow.grid-auto-rows.grid-column.grid-column-end.grid-column-start.grid-gap.grid-row.grid-row-end.grid-row-start.grid-template.grid-template-areas.grid-template-columns.grid-template-rows.hanging-punctuation.height.hyphenate-character.hyphenate-limit-chars.hyphens.icon.image-orientation.image-rendering.image-resolution.ime-mode.initial-letter.initial-letter-align.inline-size.inset.inset-area.inset-block.inset-block-end.inset-block-start.inset-inline.inset-inline-end.inset-inline-start.isolation.justify-content.justify-items.justify-self.kerning.left.letter-spacing.lighting-color.line-break.line-height.line-height-step.list-style.list-style-image.list-style-position.list-style-type.margin.margin-block.margin-block-end.margin-block-start.margin-bottom.margin-inline.margin-inline-end.margin-inline-start.margin-left.margin-right.margin-top.margin-trim.marker.marker-end.marker-mid.marker-start.marks.mask.mask-border.mask-border-mode.mask-border-outset.mask-border-repeat.mask-border-slice.mask-border-source.mask-border-width.mask-clip.mask-composite.mask-image.mask-mode.mask-origin.mask-position.mask-repeat.mask-size.mask-type.masonry-auto-flow.math-depth.math-shift.math-style.max-block-size.max-height.max-inline-size.max-width.min-block-size.min-height.min-inline-size.min-width.mix-blend-mode.nav-down.nav-index.nav-left.nav-right.nav-up.none.normal.object-fit.object-position.offset.offset-anchor.offset-distance.offset-path.offset-position.offset-rotate.opacity.order.orphans.outline.outline-color.outline-offset.outline-style.outline-width.overflow.overflow-anchor.overflow-block.overflow-clip-margin.overflow-inline.overflow-wrap.overflow-x.overflow-y.overlay.overscroll-behavior.overscroll-behavior-block.overscroll-behavior-inline.overscroll-behavior-x.overscroll-behavior-y.padding.padding-block.padding-block-end.padding-block-start.padding-bottom.padding-inline.padding-inline-end.padding-inline-start.padding-left.padding-right.padding-top.page.page-break-after.page-break-before.page-break-inside.paint-order.pause.pause-after.pause-before.perspective.perspective-origin.place-content.place-items.place-self.pointer-events.position.position-anchor.position-visibility.print-color-adjust.quotes.r.resize.rest.rest-after.rest-before.right.rotate.row-gap.ruby-align.ruby-position.scale.scroll-behavior.scroll-margin.scroll-margin-block.scroll-margin-block-end.scroll-margin-block-start.scroll-margin-bottom.scroll-margin-inline.scroll-margin-inline-end.scroll-margin-inline-start.scroll-margin-left.scroll-margin-right.scroll-margin-top.scroll-padding.scroll-padding-block.scroll-padding-block-end.scroll-padding-block-start.scroll-padding-bottom.scroll-padding-inline.scroll-padding-inline-end.scroll-padding-inline-start.scroll-padding-left.scroll-padding-right.scroll-padding-top.scroll-snap-align.scroll-snap-stop.scroll-snap-type.scroll-timeline.scroll-timeline-axis.scroll-timeline-name.scrollbar-color.scrollbar-gutter.scrollbar-width.shape-image-threshold.shape-margin.shape-outside.shape-rendering.speak.speak-as.src.stop-color.stop-opacity.stroke.stroke-dasharray.stroke-dashoffset.stroke-linecap.stroke-linejoin.stroke-miterlimit.stroke-opacity.stroke-width.tab-size.table-layout.text-align.text-align-all.text-align-last.text-anchor.text-combine-upright.text-decoration.text-decoration-color.text-decoration-line.text-decoration-skip.text-decoration-skip-ink.text-decoration-style.text-decoration-thickness.text-emphasis.text-emphasis-color.text-emphasis-position.text-emphasis-style.text-indent.text-justify.text-orientation.text-overflow.text-rendering.text-shadow.text-size-adjust.text-transform.text-underline-offset.text-underline-position.text-wrap.text-wrap-mode.text-wrap-style.timeline-scope.top.touch-action.transform.transform-box.transform-origin.transform-style.transition.transition-behavior.transition-delay.transition-duration.transition-property.transition-timing-function.translate.unicode-bidi.user-modify.user-select.vector-effect.vertical-align.view-timeline.view-timeline-axis.view-timeline-inset.view-timeline-name.view-transition-name.visibility.voice-balance.voice-duration.voice-family.voice-pitch.voice-range.voice-rate.voice-stress.voice-volume.white-space.white-space-collapse.widows.width.will-change.word-break.word-spacing.word-wrap.writing-mode.x.y.z-index.zoom`.split(`.`).sort().reverse();function zu(e){let t=ju(e),n=Lu,r=Iu,i=`@[a-z-]+`,a={className:`variable`,begin:`(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b`,relevance:0};return{name:`SCSS`,case_insensitive:!0,illegal:`[=/|']`,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t.CSS_NUMBER_MODE,{className:`selector-id`,begin:`#[A-Za-z0-9_-]+`,relevance:0},{className:`selector-class`,begin:`\\.[A-Za-z0-9_-]+`,relevance:0},t.ATTRIBUTE_SELECTOR_MODE,{className:`selector-tag`,begin:`\\b(`+Pu.join(`|`)+`)\\b`,relevance:0},{className:`selector-pseudo`,begin:`:(`+r.join(`|`)+`)`},{className:`selector-pseudo`,begin:`:(:)?(`+n.join(`|`)+`)`},a,{begin:/\(/,end:/\)/,contains:[t.CSS_NUMBER_MODE]},t.CSS_VARIABLE,{className:`attribute`,begin:`\\b(`+Ru.join(`|`)+`)\\b`},{begin:`\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b`},{begin:/:/,end:/[;}{]/,relevance:0,contains:[t.BLOCK_COMMENT,a,t.HEXCOLOR,t.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,t.IMPORTANT,t.FUNCTION_DISPATCH]},{begin:`@(page|font-face)`,keywords:{$pattern:i,keyword:`@page @font-face`}},{begin:`@`,end:`[{;]`,returnBegin:!0,keywords:{$pattern:/[a-z-]+/,keyword:`and or not only`,attribute:Fu.join(` `)},contains:[{begin:i,className:`keyword`},{begin:/[a-z-]+(?=:)/,className:`attribute`},a,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,t.HEXCOLOR,t.CSS_NUMBER_MODE]},t.FUNCTION_DISPATCH]}}function Bu(e){return{name:`Shell Session`,aliases:[`console`,`shellsession`],contains:[{className:`meta.prompt`,begin:/^\s{0,3}[/~\w\d[\]()@-]*[>%$#][ ]?/,starts:{end:/[^\\](?=\s*$)/,subLanguage:`bash`}}]}}function Vu(e){let t=e.regex,n=e.COMMENT(`--`,`$`),r={scope:`string`,variants:[{begin:/'/,end:/'/,contains:[{match:/''/}]}]},i={begin:/"/,end:/"/,contains:[{match:/""/}]},a=[`true`,`false`,`unknown`],o=[`double precision`,`large object`,`with timezone`,`without timezone`],s=`bigint.binary.blob.boolean.char.character.clob.date.dec.decfloat.decimal.float.int.integer.interval.nchar.nclob.national.numeric.real.row.smallint.time.timestamp.varchar.varying.varbinary`.split(`.`),c=[`add`,`asc`,`collation`,`desc`,`final`,`first`,`last`,`view`],l=`abs.acos.all.allocate.alter.and.any.are.array.array_agg.array_max_cardinality.as.asensitive.asin.asymmetric.at.atan.atomic.authorization.avg.begin.begin_frame.begin_partition.between.bigint.binary.blob.boolean.both.by.call.called.cardinality.cascaded.case.cast.ceil.ceiling.char.char_length.character.character_length.check.classifier.clob.close.coalesce.collate.collect.column.commit.condition.connect.constraint.contains.convert.copy.corr.corresponding.cos.cosh.count.covar_pop.covar_samp.create.cross.cube.cume_dist.current.current_catalog.current_date.current_default_transform_group.current_path.current_role.current_row.current_schema.current_time.current_timestamp.current_path.current_role.current_transform_group_for_type.current_user.cursor.cycle.date.day.deallocate.dec.decimal.decfloat.declare.default.define.delete.dense_rank.deref.describe.deterministic.disconnect.distinct.double.drop.dynamic.each.element.else.empty.end.end_frame.end_partition.end-exec.equals.escape.every.except.exec.execute.exists.exp.external.extract.false.fetch.filter.first_value.float.floor.for.foreign.frame_row.free.from.full.function.fusion.get.global.grant.group.grouping.groups.having.hold.hour.identity.in.indicator.initial.inner.inout.insensitive.insert.int.integer.intersect.intersection.interval.into.is.join.json_array.json_arrayagg.json_exists.json_object.json_objectagg.json_query.json_table.json_table_primitive.json_value.lag.language.large.last_value.lateral.lead.leading.left.like.like_regex.listagg.ln.local.localtime.localtimestamp.log.log10.lower.match.match_number.match_recognize.matches.max.member.merge.method.min.minute.mod.modifies.module.month.multiset.national.natural.nchar.nclob.new.no.none.normalize.not.nth_value.ntile.null.nullif.numeric.octet_length.occurrences_regex.of.offset.old.omit.on.one.only.open.or.order.out.outer.over.overlaps.overlay.parameter.partition.pattern.per.percent.percent_rank.percentile_cont.percentile_disc.period.portion.position.position_regex.power.precedes.precision.prepare.primary.procedure.ptf.range.rank.reads.real.recursive.ref.references.referencing.regr_avgx.regr_avgy.regr_count.regr_intercept.regr_r2.regr_slope.regr_sxx.regr_sxy.regr_syy.release.result.return.returns.revoke.right.rollback.rollup.row.row_number.rows.running.savepoint.scope.scroll.search.second.seek.select.sensitive.session_user.set.show.similar.sin.sinh.skip.smallint.some.specific.specifictype.sql.sqlexception.sqlstate.sqlwarning.sqrt.start.static.stddev_pop.stddev_samp.submultiset.subset.substring.substring_regex.succeeds.sum.symmetric.system.system_time.system_user.table.tablesample.tan.tanh.then.time.timestamp.timezone_hour.timezone_minute.to.trailing.translate.translate_regex.translation.treat.trigger.trim.trim_array.true.truncate.uescape.union.unique.unknown.unnest.update.upper.user.using.value.values.value_of.var_pop.var_samp.varbinary.varchar.varying.versioning.when.whenever.where.width_bucket.window.with.within.without.year`.split(`.`),u=`abs.acos.array_agg.asin.atan.avg.cast.ceil.ceiling.coalesce.corr.cos.cosh.count.covar_pop.covar_samp.cume_dist.dense_rank.deref.element.exp.extract.first_value.floor.json_array.json_arrayagg.json_exists.json_object.json_objectagg.json_query.json_table.json_table_primitive.json_value.lag.last_value.lead.listagg.ln.log.log10.lower.max.min.mod.nth_value.ntile.nullif.percent_rank.percentile_cont.percentile_disc.position.position_regex.power.rank.regr_avgx.regr_avgy.regr_count.regr_intercept.regr_r2.regr_slope.regr_sxx.regr_sxy.regr_syy.row_number.sin.sinh.sqrt.stddev_pop.stddev_samp.substring.substring_regex.sum.tan.tanh.translate.translate_regex.treat.trim.trim_array.unnest.upper.value_of.var_pop.var_samp.width_bucket`.split(`.`),d=[`current_catalog`,`current_date`,`current_default_transform_group`,`current_path`,`current_role`,`current_schema`,`current_transform_group_for_type`,`current_user`,`session_user`,`system_time`,`system_user`,`current_time`,`localtime`,`current_timestamp`,`localtimestamp`],f=[`create table`,`insert into`,`primary key`,`foreign key`,`not null`,`alter table`,`add constraint`,`grouping sets`,`on overflow`,`character set`,`respect nulls`,`ignore nulls`,`nulls first`,`nulls last`,`depth first`,`breadth first`],p=u,m=[...l,...c].filter(e=>!u.includes(e)),h={scope:`variable`,match:/@[a-z0-9][a-z0-9_]*/},g={scope:`operator`,match:/[-+*/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?/,relevance:0},_={match:t.concat(/\b/,t.either(...p),/\s*\(/),relevance:0,keywords:{built_in:p}};function v(e){return t.concat(/\b/,t.either(...e.map(e=>e.replace(/\s+/,`\\s+`))),/\b/)}let y={scope:`keyword`,match:v(f),relevance:0};function b(e,{exceptions:t,when:n}={}){let r=n;return t||=[],e.map(e=>e.match(/\|\d+$/)||t.includes(e)?e:r(e)?`${e}|0`:e)}return{name:`SQL`,case_insensitive:!0,illegal:/[{}]|<\//,keywords:{$pattern:/\b[\w\.]+/,keyword:b(m,{when:e=>e.length<3}),literal:a,type:s,built_in:d},contains:[{scope:`type`,match:v(o)},y,_,h,r,i,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,n,g]}}function Hu(e){return e?typeof e==`string`?e:e.source:null}function Uu(e){return Q(`(?=`,e,`)`)}function Q(...e){return e.map(e=>Hu(e)).join(``)}function Wu(e){let t=e[e.length-1];return typeof t==`object`&&t.constructor===Object?(e.splice(e.length-1,1),t):{}}function $(...e){return`(`+(Wu(e).capture?``:`?:`)+e.map(e=>Hu(e)).join(`|`)+`)`}var Gu=e=>Q(/\b/,e,/\w$/.test(e)?/\b/:/\B/),Ku=[`Protocol`,`Type`].map(Gu),qu=[`init`,`self`].map(Gu),Ju=[`Any`,`Self`],Yu=[`actor`,`any`,`associatedtype`,`async`,`await`,/as\?/,/as!/,`as`,`borrowing`,`break`,`case`,`catch`,`class`,`consume`,`consuming`,`continue`,`convenience`,`copy`,`default`,`defer`,`deinit`,`didSet`,`distributed`,`do`,`dynamic`,`each`,`else`,`enum`,`extension`,`fallthrough`,/fileprivate\(set\)/,`fileprivate`,`final`,`for`,`func`,`get`,`guard`,`if`,`import`,`indirect`,`infix`,/init\?/,/init!/,`inout`,/internal\(set\)/,`internal`,`in`,`is`,`isolated`,`nonisolated`,`lazy`,`let`,`macro`,`mutating`,`nonmutating`,/open\(set\)/,`open`,`operator`,`optional`,`override`,`package`,`postfix`,`precedencegroup`,`prefix`,/private\(set\)/,`private`,`protocol`,/public\(set\)/,`public`,`repeat`,`required`,`rethrows`,`return`,`set`,`some`,`static`,`struct`,`subscript`,`super`,`switch`,`throws`,`throw`,/try\?/,/try!/,`try`,`typealias`,/unowned\(safe\)/,/unowned\(unsafe\)/,`unowned`,`var`,`weak`,`where`,`while`,`willSet`],Xu=[`false`,`nil`,`true`],Zu=[`assignment`,`associativity`,`higherThan`,`left`,`lowerThan`,`none`,`right`],Qu=[`#colorLiteral`,`#column`,`#dsohandle`,`#else`,`#elseif`,`#endif`,`#error`,`#file`,`#fileID`,`#fileLiteral`,`#filePath`,`#function`,`#if`,`#imageLiteral`,`#keyPath`,`#line`,`#selector`,`#sourceLocation`,`#warning`],$u=`abs.all.any.assert.assertionFailure.debugPrint.dump.fatalError.getVaList.isKnownUniquelyReferenced.max.min.numericCast.pointwiseMax.pointwiseMin.precondition.preconditionFailure.print.readLine.repeatElement.sequence.stride.swap.swift_unboxFromSwiftValueWithType.transcode.type.unsafeBitCast.unsafeDowncast.withExtendedLifetime.withUnsafeMutablePointer.withUnsafePointer.withVaList.withoutActuallyEscaping.zip`.split(`.`),ed=$(/[/=\-+!*%<>&|^~?]/,/[\u00A1-\u00A7]/,/[\u00A9\u00AB]/,/[\u00AC\u00AE]/,/[\u00B0\u00B1]/,/[\u00B6\u00BB\u00BF\u00D7\u00F7]/,/[\u2016-\u2017]/,/[\u2020-\u2027]/,/[\u2030-\u203E]/,/[\u2041-\u2053]/,/[\u2055-\u205E]/,/[\u2190-\u23FF]/,/[\u2500-\u2775]/,/[\u2794-\u2BFF]/,/[\u2E00-\u2E7F]/,/[\u3001-\u3003]/,/[\u3008-\u3020]/,/[\u3030]/),td=$(ed,/[\u0300-\u036F]/,/[\u1DC0-\u1DFF]/,/[\u20D0-\u20FF]/,/[\uFE00-\uFE0F]/,/[\uFE20-\uFE2F]/),nd=Q(ed,td,`*`),rd=$(/[a-zA-Z_]/,/[\u00A8\u00AA\u00AD\u00AF\u00B2-\u00B5\u00B7-\u00BA]/,/[\u00BC-\u00BE\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF]/,/[\u0100-\u02FF\u0370-\u167F\u1681-\u180D\u180F-\u1DBF]/,/[\u1E00-\u1FFF]/,/[\u200B-\u200D\u202A-\u202E\u203F-\u2040\u2054\u2060-\u206F]/,/[\u2070-\u20CF\u2100-\u218F\u2460-\u24FF\u2776-\u2793]/,/[\u2C00-\u2DFF\u2E80-\u2FFF]/,/[\u3004-\u3007\u3021-\u302F\u3031-\u303F\u3040-\uD7FF]/,/[\uF900-\uFD3D\uFD40-\uFDCF\uFDF0-\uFE1F\uFE30-\uFE44]/,/[\uFE47-\uFEFE\uFF00-\uFFFD]/),id=$(rd,/\d/,/[\u0300-\u036F\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F]/),ad=Q(rd,id,`*`),od=Q(/[A-Z]/,id,`*`),sd=[`attached`,`autoclosure`,Q(/convention\(/,$(`swift`,`block`,`c`),/\)/),`discardableResult`,`dynamicCallable`,`dynamicMemberLookup`,`escaping`,`freestanding`,`frozen`,`GKInspectable`,`IBAction`,`IBDesignable`,`IBInspectable`,`IBOutlet`,`IBSegueAction`,`inlinable`,`main`,`nonobjc`,`NSApplicationMain`,`NSCopying`,`NSManaged`,Q(/objc\(/,ad,/\)/),`objc`,`objcMembers`,`propertyWrapper`,`requires_stored_property_inits`,`resultBuilder`,`Sendable`,`testable`,`UIApplicationMain`,`unchecked`,`unknown`,`usableFromInline`,`warn_unqualified_access`],cd=[`iOS`,`iOSApplicationExtension`,`macOS`,`macOSApplicationExtension`,`macCatalyst`,`macCatalystApplicationExtension`,`watchOS`,`watchOSApplicationExtension`,`tvOS`,`tvOSApplicationExtension`,`swift`];function ld(e){let t={match:/\s+/,relevance:0},n=e.COMMENT(`/\\*`,`\\*/`,{contains:[`self`]}),r=[e.C_LINE_COMMENT_MODE,n],i={match:[/\./,$(...Ku,...qu)],className:{2:`keyword`}},a={match:Q(/\./,$(...Yu)),relevance:0},o=Yu.filter(e=>typeof e==`string`).concat([`_|0`]),s={variants:[{className:`keyword`,match:$(...Yu.filter(e=>typeof e!=`string`).concat(Ju).map(Gu),...qu)}]},c={$pattern:$(/\b\w+/,/#\w+/),keyword:o.concat(Qu),literal:Xu},l=[i,a,s],u=[{match:Q(/\./,$(...$u)),relevance:0},{className:`built_in`,match:Q(/\b/,$(...$u),/(?=\()/)}],d={match:/->/,relevance:0},f=[d,{className:`operator`,relevance:0,variants:[{match:nd},{match:`\\.(\\.|${td})+`}]}],p=`([0-9]_*)+`,m=`([0-9a-fA-F]_*)+`,h={className:`number`,relevance:0,variants:[{match:`\\b(${p})(\\.(${p}))?([eE][+-]?(${p}))?\\b`},{match:`\\b0x(${m})(\\.(${m}))?([pP][+-]?(${p}))?\\b`},{match:/\b0o([0-7]_*)+\b/},{match:/\b0b([01]_*)+\b/}]},g=(e=``)=>({className:`subst`,variants:[{match:Q(/\\/,e,/[0\\tnr"']/)},{match:Q(/\\/,e,/u\{[0-9a-fA-F]{1,8}\}/)}]}),_=(e=``)=>({className:`subst`,match:Q(/\\/,e,/[\t ]*(?:[\r\n]|\r\n)/)}),v=(e=``)=>({className:`subst`,label:`interpol`,begin:Q(/\\/,e,/\(/),end:/\)/}),y=(e=``)=>({begin:Q(e,/"""/),end:Q(/"""/,e),contains:[g(e),_(e),v(e)]}),b=(e=``)=>({begin:Q(e,/"/),end:Q(/"/,e),contains:[g(e),v(e)]}),x={className:`string`,variants:[y(),y(`#`),y(`##`),y(`###`),b(),b(`#`),b(`##`),b(`###`)]},S=[e.BACKSLASH_ESCAPE,{begin:/\[/,end:/\]/,relevance:0,contains:[e.BACKSLASH_ESCAPE]}],C={begin:/\/[^\s](?=[^/\n]*\/)/,end:/\//,contains:S},w=e=>{let t=Q(e,/\//),n=Q(/\//,e);return{begin:t,end:n,contains:[...S,{scope:`comment`,begin:`#(?!.*${n})`,end:/$/}]}},T={scope:`regexp`,variants:[w(`###`),w(`##`),w(`#`),C]},E={match:Q(/`/,ad,/`/)},D=[E,{className:`variable`,match:/\$\d+/},{className:`variable`,match:`\\$${id}+`}],O=[{match:/(@|#(un)?)available/,scope:`keyword`,starts:{contains:[{begin:/\(/,end:/\)/,keywords:cd,contains:[...f,h,x]}]}},{scope:`keyword`,match:Q(/@/,$(...sd),Uu($(/\(/,/\s+/)))},{scope:`meta`,match:Q(/@/,ad)}],k={match:Uu(/\b[A-Z]/),relevance:0,contains:[{className:`type`,match:Q(/(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)/,id,`+`)},{className:`type`,match:od,relevance:0},{match:/[?!]+/,relevance:0},{match:/\.\.\./,relevance:0},{match:Q(/\s+&\s+/,Uu(od)),relevance:0}]},A={begin://,keywords:c,contains:[...r,...l,...O,d,k]};k.contains.push(A);let j={begin:/\(/,end:/\)/,relevance:0,keywords:c,contains:[`self`,{match:Q(ad,/\s*:/),keywords:`_|0`,relevance:0},...r,T,...l,...u,...f,h,x,...D,...O,k]},M={begin://,keywords:`repeat each`,contains:[...r,k]},N={begin:/\(/,end:/\)/,keywords:c,contains:[{begin:$(Uu(Q(ad,/\s*:/)),Uu(Q(ad,/\s+/,ad,/\s*:/))),end:/:/,relevance:0,contains:[{className:`keyword`,match:/\b_\b/},{className:`params`,match:ad}]},...r,...l,...f,h,x,...O,k,j],endsParent:!0,illegal:/["']/},P={match:[/(func|macro)/,/\s+/,$(E.match,ad,nd)],className:{1:`keyword`,3:`title.function`},contains:[M,N,t],illegal:[/\[/,/%/]},F={match:[/\b(?:subscript|init[?!]?)/,/\s*(?=[<(])/],className:{1:`keyword`},contains:[M,N,t],illegal:/\[|%/},I={match:[/operator/,/\s+/,nd],className:{1:`keyword`,3:`title`}},L={begin:[/precedencegroup/,/\s+/,od],className:{1:`keyword`,3:`title`},contains:[k],keywords:[...Zu,...Xu],end:/}/},R={match:[/class\b/,/\s+/,/func\b/,/\s+/,/\b[A-Za-z_][A-Za-z0-9_]*\b/],scope:{1:`keyword`,3:`keyword`,5:`title.function`}},ee={match:[/class\b/,/\s+/,/var\b/],scope:{1:`keyword`,3:`keyword`}},te={begin:[/(struct|protocol|class|extension|enum|actor)/,/\s+/,ad,/\s*/],beginScope:{1:`keyword`,3:`title.class`},keywords:c,contains:[M,...l,{begin:/:/,end:/\{/,keywords:c,contains:[{scope:`title.class.inherited`,match:od},...l],relevance:0}]};for(let e of x.variants){let t=e.contains.find(e=>e.label===`interpol`);t.keywords=c;let n=[...l,...u,...f,h,x,...D];t.contains=[...n,{begin:/\(/,end:/\)/,contains:[`self`,...n]}]}return{name:`Swift`,keywords:c,contains:[...r,P,F,R,ee,te,I,L,{beginKeywords:`import`,end:/$/,contains:[...r],relevance:0},T,...l,...u,...f,h,x,...D,...O,k,j]}}var ud=`[A-Za-z$_][0-9A-Za-z$_]*`,dd=`as.in.of.if.for.while.finally.var.new.function.do.return.void.else.break.catch.instanceof.with.throw.case.default.try.switch.continue.typeof.delete.let.yield.const.class.debugger.async.await.static.import.from.export.extends.using`.split(`.`),fd=[`true`,`false`,`null`,`undefined`,`NaN`,`Infinity`],pd=`Object.Function.Boolean.Symbol.Math.Date.Number.BigInt.String.RegExp.Array.Float32Array.Float64Array.Int8Array.Uint8Array.Uint8ClampedArray.Int16Array.Int32Array.Uint16Array.Uint32Array.BigInt64Array.BigUint64Array.Set.Map.WeakSet.WeakMap.ArrayBuffer.SharedArrayBuffer.Atomics.DataView.JSON.Promise.Generator.GeneratorFunction.AsyncFunction.Reflect.Proxy.Intl.WebAssembly`.split(`.`),md=[`Error`,`EvalError`,`InternalError`,`RangeError`,`ReferenceError`,`SyntaxError`,`TypeError`,`URIError`],hd=[`setInterval`,`setTimeout`,`clearInterval`,`clearTimeout`,`require`,`exports`,`eval`,`isFinite`,`isNaN`,`parseFloat`,`parseInt`,`decodeURI`,`decodeURIComponent`,`encodeURI`,`encodeURIComponent`,`escape`,`unescape`],gd=[`arguments`,`this`,`super`,`console`,`window`,`document`,`localStorage`,`sessionStorage`,`module`,`global`],_d=[].concat(hd,pd,md);function vd(e){let t=e.regex,n=(e,{after:t})=>{let n=``,end:``},a=/<[A-Za-z0-9\\._:-]+\s*\/>/,o={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,t)=>{let r=e[0].length+e.index,i=e.input[r];if(i===`<`||i===`,`){t.ignoreMatch();return}i===`>`&&(n(e,{after:r})||t.ignoreMatch());let a,o=e.input.substring(r);if(a=o.match(/^\s*=/)){t.ignoreMatch();return}if((a=o.match(/^\s+extends\s+/))&&a.index===0){t.ignoreMatch();return}}},s={$pattern:ud,keyword:dd,literal:fd,built_in:_d,"variable.language":gd},c=`[0-9](_?[0-9])*`,l=`\\.(${c})`,u=`0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*`,d={className:`number`,variants:[{begin:`(\\b(${u})((${l})|\\.)?|(${l}))[eE][+-]?(${c})\\b`},{begin:`\\b(${u})\\b((${l})\\b|\\.)?|(${l})\\b`},{begin:`\\b(0|[1-9](_?[0-9])*)n\\b`},{begin:`\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b`},{begin:`\\b0[bB][0-1](_?[0-1])*n?\\b`},{begin:`\\b0[oO][0-7](_?[0-7])*n?\\b`},{begin:`\\b0[0-7]+n?\\b`}],relevance:0},f={className:`subst`,begin:`\\$\\{`,end:`\\}`,keywords:s,contains:[]},p={begin:".?html`",end:``,starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,f],subLanguage:`xml`}},m={begin:".?css`",end:``,starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,f],subLanguage:`css`}},h={begin:".?gql`",end:``,starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,f],subLanguage:`graphql`}},g={className:`string`,begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE,f]},_={className:`comment`,variants:[e.COMMENT(/\/\*\*(?!\/)/,`\\*/`,{relevance:0,contains:[{begin:`(?=@[A-Za-z]+)`,relevance:0,contains:[{className:`doctag`,begin:`@[A-Za-z]+`},{className:`type`,begin:`\\{`,end:`\\}`,excludeEnd:!0,excludeBegin:!0,relevance:0},{className:`variable`,begin:`[A-Za-z$_][0-9A-Za-z$_]*(?=\\s*(-)|$)`,endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),e.C_BLOCK_COMMENT_MODE,e.C_LINE_COMMENT_MODE]},v=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,p,m,h,g,{match:/\$\d+/},d];f.contains=v.concat({begin:/\{/,end:/\}/,keywords:s,contains:[`self`].concat(v)});let y=[].concat(_,f.contains),b=y.concat([{begin:/(\s*)\(/,end:/\)/,keywords:s,contains:[`self`].concat(y)}]),x={className:`params`,begin:/(\s*)\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:s,contains:b},S={variants:[{match:[/class/,/\s+/,r,/\s+/,/extends/,/\s+/,t.concat(r,`(`,t.concat(/\./,r),`)*`)],scope:{1:`keyword`,3:`title.class`,5:`keyword`,7:`title.class.inherited`}},{match:[/class/,/\s+/,r],scope:{1:`keyword`,3:`title.class`}}]},C={relevance:0,match:t.either(/\bJSON/,/\b[A-Z][a-z]+([A-Z][a-z]*|\d)*/,/\b[A-Z]{2,}([A-Z][a-z]+|\d)+([A-Z][a-z]*)*/,/\b[A-Z]{2,}[a-z]+([A-Z][a-z]+|\d)*([A-Z][a-z]*)*/),className:`title.class`,keywords:{_:[...pd,...md]}},w={label:`use_strict`,className:`meta`,relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},T={variants:[{match:[/function/,/\s+/,r,/(?=\s*\()/]},{match:[/function/,/\s*(?=\()/]}],className:{1:`keyword`,3:`title.function`},label:`func.def`,contains:[x],illegal:/%/},E={relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/,className:`variable.constant`};function D(e){return t.concat(`(?!`,e.join(`|`),`)`)}let O={match:t.concat(/\b/,D([...hd,`super`,`import`].map(e=>`${e}\\s*\\(`)),r,t.lookahead(/\s*\(/)),className:`title.function`,relevance:0},k={begin:t.concat(/\./,t.lookahead(t.concat(r,/(?![0-9A-Za-z$_(])/))),end:r,excludeBegin:!0,keywords:`prototype`,className:`property`,relevance:0},A={match:[/get|set/,/\s+/,r,/(?=\()/],className:{1:`keyword`,3:`title.function`},contains:[{begin:/\(\)/},x]},j=`(\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|`+e.UNDERSCORE_IDENT_RE+`)\\s*=>`,M={match:[/const|var|let/,/\s+/,r,/\s*/,/=\s*/,/(async\s*)?/,t.lookahead(j)],keywords:`async`,className:{1:`keyword`,3:`title.function`},contains:[x]};return{name:`JavaScript`,aliases:[`js`,`jsx`,`mjs`,`cjs`],keywords:s,exports:{PARAMS_CONTAINS:b,CLASS_REFERENCE:C},illegal:/#(?![$_A-z])/,contains:[e.SHEBANG({label:`shebang`,binary:`node`,relevance:5}),w,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,p,m,h,g,_,{match:/\$\d+/},d,C,{scope:`attr`,match:r+t.lookahead(`:`),relevance:0},M,{begin:`(`+e.RE_STARTERS_RE+`|\\b(case|return|throw)\\b)\\s*`,keywords:`return throw case`,relevance:0,contains:[_,e.REGEXP_MODE,{className:`function`,begin:j,returnBegin:!0,end:`\\s*=>`,contains:[{className:`params`,variants:[{begin:e.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/(\s*)\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:s,contains:b}]}]},{begin:/,/,relevance:0},{match:/\s+/,relevance:0},{variants:[{begin:i.begin,end:i.end},{match:a},{begin:o.begin,"on:begin":o.isTrulyOpeningTag,end:o.end}],subLanguage:`xml`,contains:[{begin:o.begin,end:o.end,skip:!0,contains:[`self`]}]}]},T,{beginKeywords:`while if switch catch for`},{begin:`\\b(?!function)`+e.UNDERSCORE_IDENT_RE+`\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{`,returnBegin:!0,label:`func.def`,contains:[x,e.inherit(e.TITLE_MODE,{begin:r,className:`title.function`})]},{match:/\.\.\./,relevance:0},k,{match:`\\$[A-Za-z$_][0-9A-Za-z$_]*`,relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:`title.function`},contains:[x]},O,E,S,A,{match:/\$[(.]/}]}}function yd(e){let t=e.regex,n=vd(e),r=ud,i=[`any`,`void`,`number`,`boolean`,`string`,`object`,`never`,`symbol`,`bigint`,`unknown`],a={begin:[/namespace/,/\s+/,e.IDENT_RE],beginScope:{1:`keyword`,3:`title.class`}},o={beginKeywords:`interface`,end:/\{/,excludeEnd:!0,keywords:{keyword:`interface extends`,built_in:i},contains:[n.exports.CLASS_REFERENCE]},s={className:`meta`,relevance:10,begin:/^\s*['"]use strict['"]/},c={$pattern:ud,keyword:dd.concat([`type`,`interface`,`public`,`private`,`protected`,`implements`,`declare`,`abstract`,`readonly`,`enum`,`override`,`satisfies`]),literal:fd,built_in:_d.concat(i),"variable.language":gd},l={className:`meta`,begin:`@[A-Za-z$_][0-9A-Za-z$_]*`},u=(e,t,n)=>{let r=e.contains.findIndex(e=>e.label===t);if(r===-1)throw Error(`can not find mode to replace`);e.contains.splice(r,1,n)};Object.assign(n.keywords,c),n.exports.PARAMS_CONTAINS.push(l);let d=n.contains.find(e=>e.scope===`attr`),f=Object.assign({},d,{match:t.concat(r,t.lookahead(/\s*\?:/))});n.exports.PARAMS_CONTAINS.push([n.exports.CLASS_REFERENCE,d,f]),n.contains=n.contains.concat([l,a,o,f]),u(n,`shebang`,e.SHEBANG()),u(n,`use_strict`,s);let p=n.contains.find(e=>e.label===`func.def`);return p.relevance=0,Object.assign(n,{name:`TypeScript`,aliases:[`ts`,`tsx`,`mts`,`cts`]}),n}function bd(e){let t=e.regex,n={className:`string`,begin:/"(""|[^/n])"C\b/},r={className:`string`,begin:/"/,end:/"/,illegal:/\n/,contains:[{begin:/""/}]},i=/\d{1,2}\/\d{1,2}\/\d{4}/,a=/\d{4}-\d{1,2}-\d{1,2}/,o=/(\d|1[012])(:\d+){0,2} *(AM|PM)/,s=/\d{1,2}(:\d{1,2}){1,2}/,c={className:`literal`,variants:[{begin:t.concat(/# */,t.either(a,i),/ *#/)},{begin:t.concat(/# */,s,/ *#/)},{begin:t.concat(/# */,o,/ *#/)},{begin:t.concat(/# */,t.either(a,i),/ +/,t.either(o,s),/ *#/)}]},l={className:`number`,relevance:0,variants:[{begin:/\b\d[\d_]*((\.[\d_]+(E[+-]?[\d_]+)?)|(E[+-]?[\d_]+))[RFD@!#]?/},{begin:/\b\d[\d_]*((U?[SIL])|[%&])?/},{begin:/&H[\dA-F_]+((U?[SIL])|[%&])?/},{begin:/&O[0-7_]+((U?[SIL])|[%&])?/},{begin:/&B[01_]+((U?[SIL])|[%&])?/}]},u={className:`label`,begin:/^\w+:/},d=e.COMMENT(/'''/,/$/,{contains:[{className:`doctag`,begin:/<\/?/,end:/>/}]}),f=e.COMMENT(null,/$/,{variants:[{begin:/'/},{begin:/([\t ]|^)REM(?=\s)/}]});return{name:`Visual Basic .NET`,aliases:[`vb`],case_insensitive:!0,classNameAliases:{label:`symbol`},keywords:{keyword:`addhandler alias aggregate ansi as async assembly auto binary by byref byval call case catch class compare const continue custom declare default delegate dim distinct do each equals else elseif end enum erase error event exit explicit finally for friend from function get global goto group handles if implements imports in inherits interface into iterator join key let lib loop me mid module mustinherit mustoverride mybase myclass namespace narrowing new next notinheritable notoverridable of off on operator option optional order overloads overridable overrides paramarray partial preserve private property protected public raiseevent readonly redim removehandler resume return select set shadows shared skip static step stop structure strict sub synclock take text then throw to try unicode until using when where while widening with withevents writeonly yield`,built_in:`addressof and andalso await directcast gettype getxmlnamespace is isfalse isnot istrue like mod nameof new not or orelse trycast typeof xor cbool cbyte cchar cdate cdbl cdec cint clng cobj csbyte cshort csng cstr cuint culng cushort`,type:`boolean byte char date decimal double integer long object sbyte short single string uinteger ulong ushort`,literal:`true false nothing`},illegal:`//|\\{|\\}|endif|gosub|variant|wend|^\\$ `,contains:[n,r,c,l,u,d,f,{className:`meta`,begin:/[\t ]*#(const|disable|else|elseif|enable|end|externalsource|if|region)\b/,end:/$/,keywords:{keyword:`const disable else elseif enable end externalsource if region then`},contains:[f]}]}}function xd(e){e.regex;let t=e.COMMENT(/\(;/,/;\)/);return t.contains.push(`self`),{name:`WebAssembly`,keywords:{$pattern:/[\w.]+/,keyword:`anyfunc,block,br,br_if,br_table,call,call_indirect,data,drop,elem,else,end,export,func,global.get,global.set,local.get,local.set,local.tee,get_global,get_local,global,if,import,local,loop,memory,memory.grow,memory.size,module,mut,nop,offset,param,result,return,select,set_global,set_local,start,table,tee_local,then,type,unreachable`.split(`,`)},contains:[e.COMMENT(/;;/,/$/),t,{match:[/(?:offset|align)/,/\s*/,/=/],className:{1:`keyword`,3:`operator`}},{className:`variable`,begin:/\$[\w_]+/},{match:/(\((?!;)|\))+/,className:`punctuation`,relevance:0},{begin:[/(?:func|call|call_indirect)/,/\s+/,/\$[^\s)]+/],className:{1:`keyword`,3:`title.function`}},e.QUOTE_STRING_MODE,{match:/(i32|i64|f32|f64)(?!\.)/,className:`type`},{className:`keyword`,match:/\b(f32|f64|i32|i64)(?:\.(?:abs|add|and|ceil|clz|const|convert_[su]\/i(?:32|64)|copysign|ctz|demote\/f64|div(?:_[su])?|eqz?|extend_[su]\/i32|floor|ge(?:_[su])?|gt(?:_[su])?|le(?:_[su])?|load(?:(?:8|16|32)_[su])?|lt(?:_[su])?|max|min|mul|nearest|neg?|or|popcnt|promote\/f32|reinterpret\/[fi](?:32|64)|rem_[su]|rot[lr]|shl|shr_[su]|store(?:8|16|32)?|sqrt|sub|trunc(?:_[su]\/f(?:32|64))?|wrap\/i64|xor))\b/},{className:`number`,relevance:0,match:/[+-]?\b(?:\d(?:_?\d)*(?:\.\d(?:_?\d)*)?(?:[eE][+-]?\d(?:_?\d)*)?|0x[\da-fA-F](?:_?[\da-fA-F])*(?:\.[\da-fA-F](?:_?[\da-fA-D])*)?(?:[pP][+-]?\d(?:_?\d)*)?)\b|\binf\b|\bnan(?::0x[\da-fA-F](?:_?[\da-fA-D])*)?\b/}]}}function Sd(e){let t=e.regex,n=t.concat(/[\p{L}_]/u,t.optional(/[\p{L}0-9_.-]*:/u),/[\p{L}0-9_.-]*/u),r=/[\p{L}0-9._:-]+/u,i={className:`symbol`,begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},a={begin:/\s/,contains:[{className:`keyword`,begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]},o=e.inherit(a,{begin:/\(/,end:/\)/}),s=e.inherit(e.APOS_STRING_MODE,{className:`string`}),c=e.inherit(e.QUOTE_STRING_MODE,{className:`string`}),l={endsWithParent:!0,illegal:/`]+/}]}]}]};return{name:`HTML, XML`,aliases:[`html`,`xhtml`,`rss`,`atom`,`xjb`,`xsd`,`xsl`,`plist`,`wsf`,`svg`],case_insensitive:!0,unicodeRegex:!0,contains:[{className:`meta`,begin://,relevance:10,contains:[a,c,s,o,{begin:/\[/,end:/\]/,contains:[{className:`meta`,begin://,contains:[a,o,c,s]}]}]},e.COMMENT(//,{relevance:10}),{begin://,relevance:10},i,{className:`meta`,end:/\?>/,variants:[{begin:/<\?xml/,relevance:10,contains:[c]},{begin:/<\?[a-z][a-z0-9]+/}]},{className:`tag`,begin:/)/,end:/>/,keywords:{name:`style`},contains:[l],starts:{end:/<\/style>/,returnEnd:!0,subLanguage:[`css`,`xml`]}},{className:`tag`,begin:/)/,end:/>/,keywords:{name:`script`},contains:[l],starts:{end:/<\/script>/,returnEnd:!0,subLanguage:[`javascript`,`handlebars`,`xml`]}},{className:`tag`,begin:/<>|<\/>/},{className:`tag`,begin:t.concat(//,/>/,/\s/)))),end:/\/?>/,contains:[{className:`name`,begin:n,relevance:0,starts:l}]},{className:`tag`,begin:t.concat(/<\//,t.lookahead(t.concat(n,/>/))),contains:[{className:`name`,begin:n,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}}function Cd(e){let t=`true false yes no null`,n={className:`attr`,variants:[{begin:/[\w*@][\w*@ :()\./-]*:(?=[ \t]|$)/},{begin:/"[\w*@][\w*@ :()\./-]*":(?=[ \t]|$)/},{begin:/'[\w*@][\w*@ :()\./-]*':(?=[ \t]|$)/}]},r={className:`template-variable`,variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]},i={className:`string`,relevance:0,begin:/'/,end:/'/,contains:[{match:/''/,scope:`char.escape`,relevance:0}]},a={className:`string`,relevance:0,variants:[{begin:/"/,end:/"/},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,r]},o=e.inherit(a,{variants:[{begin:/'/,end:/'/,contains:[{begin:/''/,relevance:0}]},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),s={className:`number`,begin:`\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b`},c={end:`,`,endsWithParent:!0,excludeEnd:!0,keywords:t,relevance:0},l={begin:/\{/,end:/\}/,contains:[c],illegal:`\\n`,relevance:0},u={begin:`\\[`,end:`\\]`,contains:[c],illegal:`\\n`,relevance:0},d=[n,{className:`meta`,begin:`^---\\s*$`,relevance:10},{className:`string`,begin:`[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*`},{begin:`<%[%=-]?`,end:`[%-]?%>`,subLanguage:`ruby`,excludeBegin:!0,excludeEnd:!0,relevance:0},{className:`type`,begin:`!\\w+![\\w#;/?:@&=+$,.~*'()[\\]]+`},{className:`type`,begin:`!<[\\w#;/?:@&=+$,.~*'()[\\]]+>`},{className:`type`,begin:`![\\w#;/?:@&=+$,.~*'()[\\]]+`},{className:`type`,begin:`!![\\w#;/?:@&=+$,.~*'()[\\]]+`},{className:`meta`,begin:`&`+e.UNDERSCORE_IDENT_RE+`$`},{className:`meta`,begin:`\\*`+e.UNDERSCORE_IDENT_RE+`$`},{className:`bullet`,begin:`-(?=[ ]|$)`,relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:t,keywords:{literal:t}},s,{className:`number`,begin:e.C_NUMBER_RE+`\\b`,relevance:0},l,u,i,a],f=[...d];return f.pop(),f.push(o),c.contains=f,{name:`YAML`,case_insensitive:!0,aliases:[`yml`],contains:d}}var wd={arduino:Cl,bash:wl,c:Tl,cpp:El,csharp:Dl,css:Il,diff:Ll,go:Rl,graphql:zl,ini:Bl,java:Kl,javascript:tu,json:nu,kotlin:su,less:_u,lua:vu,makefile:yu,markdown:bu,objectivec:xu,perl:Su,php:Cu,"php-template":wu,plaintext:Tu,python:Eu,"python-repl":Du,r:Ou,ruby:ku,rust:Au,scss:zu,shell:Bu,sql:Vu,swift:ld,typescript:yd,vbnet:bd,wasm:xd,xml:Sd,yaml:Cd},Td=t(i(((e,t)=>{function n(e){return e instanceof Map?e.clear=e.delete=e.set=function(){throw Error(`map is read-only`)}:e instanceof Set&&(e.add=e.clear=e.delete=function(){throw Error(`set is read-only`)}),Object.freeze(e),Object.getOwnPropertyNames(e).forEach(t=>{let r=e[t],i=typeof r;(i===`object`||i===`function`)&&!Object.isFrozen(r)&&n(r)}),e}var r=class{constructor(e){e.data===void 0&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1}ignoreMatch(){this.isMatchIgnored=!0}};function i(e){return e.replace(/&/g,`&`).replace(//g,`>`).replace(/"/g,`"`).replace(/'/g,`'`)}function a(e,...t){let n=Object.create(null);for(let t in e)n[t]=e[t];return t.forEach(function(e){for(let t in e)n[t]=e[t]}),n}var o=``,s=e=>!!e.scope,c=(e,{prefix:t})=>{if(e.startsWith(`language:`))return e.replace(`language:`,`language-`);if(e.includes(`.`)){let n=e.split(`.`);return[`${t}${n.shift()}`,...n.map((e,t)=>`${e}${`_`.repeat(t+1)}`)].join(` `)}return`${t}${e}`},l=class{constructor(e,t){this.buffer=``,this.classPrefix=t.classPrefix,e.walk(this)}addText(e){this.buffer+=i(e)}openNode(e){if(!s(e))return;let t=c(e.scope,{prefix:this.classPrefix});this.span(t)}closeNode(e){s(e)&&(this.buffer+=o)}value(){return this.buffer}span(e){this.buffer+=``}},u=(e={})=>{let t={children:[]};return Object.assign(t,e),t},d=class e{constructor(){this.rootNode=u(),this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){let t=u({scope:e});this.add(t),this.stack.push(t)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){return typeof t==`string`?e.addText(t):t.children&&(e.openNode(t),t.children.forEach(t=>this._walk(e,t)),e.closeNode(t)),e}static _collapse(t){typeof t!=`string`&&t.children&&(t.children.every(e=>typeof e==`string`)?t.children=[t.children.join(``)]:t.children.forEach(t=>{e._collapse(t)}))}},f=class extends d{constructor(e){super(),this.options=e}addText(e){e!==``&&this.add(e)}startScope(e){this.openNode(e)}endScope(){this.closeNode()}__addSublanguage(e,t){let n=e.root;t&&(n.scope=`language:${t}`),this.add(n)}toHTML(){return new l(this,this.options).value()}finalize(){return this.closeAllNodes(),!0}};function p(e){return e?typeof e==`string`?e:e.source:null}function m(e){return _(`(?=`,e,`)`)}function h(e){return _(`(?:`,e,`)*`)}function g(e){return _(`(?:`,e,`)?`)}function _(...e){return e.map(e=>p(e)).join(``)}function v(e){let t=e[e.length-1];return typeof t==`object`&&t.constructor===Object?(e.splice(e.length-1,1),t):{}}function y(...e){return`(`+(v(e).capture?``:`?:`)+e.map(e=>p(e)).join(`|`)+`)`}function b(e){return RegExp(e.toString()+`|`).exec(``).length-1}function x(e,t){let n=e&&e.exec(t);return n&&n.index===0}var S=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;function C(e,{joinWith:t}){let n=0;return e.map(e=>{n+=1;let t=n,r=p(e),i=``;for(;r.length>0;){let e=S.exec(r);if(!e){i+=r;break}i+=r.substring(0,e.index),r=r.substring(e.index+e[0].length),e[0][0]===`\\`&&e[1]?i+=`\\`+String(Number(e[1])+t):(i+=e[0],e[0]===`(`&&n++)}return i}).map(e=>`(${e})`).join(t)}var w=/\b\B/,T=`[a-zA-Z]\\w*`,E=`[a-zA-Z_]\\w*`,D=`\\b\\d+(\\.\\d+)?`,O=`(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)`,k=`\\b(0b[01]+)`,A=`!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~`,j=(e={})=>{let t=/^#![ ]*\//;return e.binary&&(e.begin=_(t,/.*\b/,e.binary,/\b.*/)),a({scope:`meta`,begin:t,end:/$/,relevance:0,"on:begin":(e,t)=>{e.index!==0&&t.ignoreMatch()}},e)},M={begin:`\\\\[\\s\\S]`,relevance:0},N={scope:`string`,begin:`'`,end:`'`,illegal:`\\n`,contains:[M]},P={scope:`string`,begin:`"`,end:`"`,illegal:`\\n`,contains:[M]},F={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},I=function(e,t,n={}){let r=a({scope:`comment`,begin:e,end:t,contains:[]},n);r.contains.push({scope:`doctag`,begin:`[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)`,end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0});let i=y(`I`,`a`,`is`,`so`,`us`,`to`,`at`,`if`,`in`,`it`,`on`,/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/);return r.contains.push({begin:_(/[ ]+/,`(`,i,/[.]?[:]?([.][ ]|[ ])/,`){3}`)}),r},L=I(`//`,`$`),R=I(`/\\*`,`\\*/`),ee=I(`#`,`$`),te=Object.freeze({__proto__:null,APOS_STRING_MODE:N,BACKSLASH_ESCAPE:M,BINARY_NUMBER_MODE:{scope:`number`,begin:k,relevance:0},BINARY_NUMBER_RE:k,COMMENT:I,C_BLOCK_COMMENT_MODE:R,C_LINE_COMMENT_MODE:L,C_NUMBER_MODE:{scope:`number`,begin:O,relevance:0},C_NUMBER_RE:O,END_SAME_AS_BEGIN:function(e){return Object.assign(e,{"on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{t.data._beginMatch!==e[1]&&t.ignoreMatch()}})},HASH_COMMENT_MODE:ee,IDENT_RE:T,MATCH_NOTHING_RE:w,METHOD_GUARD:{begin:`\\.\\s*[a-zA-Z_]\\w*`,relevance:0},NUMBER_MODE:{scope:`number`,begin:D,relevance:0},NUMBER_RE:D,PHRASAL_WORDS_MODE:F,QUOTE_STRING_MODE:P,REGEXP_MODE:{scope:`regexp`,begin:/\/(?=[^/\n]*\/)/,end:/\/[gimuy]*/,contains:[M,{begin:/\[/,end:/\]/,relevance:0,contains:[M]}]},RE_STARTERS_RE:A,SHEBANG:j,TITLE_MODE:{scope:`title`,begin:T,relevance:0},UNDERSCORE_IDENT_RE:E,UNDERSCORE_TITLE_MODE:{scope:`title`,begin:E,relevance:0}});function ne(e,t){e.input[e.index-1]===`.`&&t.ignoreMatch()}function re(e,t){e.className!==void 0&&(e.scope=e.className,delete e.className)}function ie(e,t){t&&e.beginKeywords&&(e.begin=`\\b(`+e.beginKeywords.split(` `).join(`|`)+`)(?!\\.)(?=\\b|\\s)`,e.__beforeBegin=ne,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords,e.relevance===void 0&&(e.relevance=0))}function ae(e,t){Array.isArray(e.illegal)&&(e.illegal=y(...e.illegal))}function oe(e,t){if(e.match){if(e.begin||e.end)throw Error(`begin & end are not supported with match`);e.begin=e.match,delete e.match}}function se(e,t){e.relevance===void 0&&(e.relevance=1)}var ce=(e,t)=>{if(!e.beforeMatch)return;if(e.starts)throw Error(`beforeMatch cannot be used with starts`);let n=Object.assign({},e);Object.keys(e).forEach(t=>{delete e[t]}),e.keywords=n.keywords,e.begin=_(n.beforeMatch,m(n.begin)),e.starts={relevance:0,contains:[Object.assign(n,{endsParent:!0})]},e.relevance=0,delete n.beforeMatch},le=[`of`,`and`,`for`,`in`,`not`,`or`,`if`,`then`,`parent`,`list`,`value`],ue=`keyword`;function de(e,t,n=ue){let r=Object.create(null);return typeof e==`string`?i(n,e.split(` `)):Array.isArray(e)?i(n,e):Object.keys(e).forEach(function(n){Object.assign(r,de(e[n],t,n))}),r;function i(e,n){t&&(n=n.map(e=>e.toLowerCase())),n.forEach(function(t){let n=t.split(`|`);r[n[0]]=[e,fe(n[0],n[1])]})}}function fe(e,t){return t?Number(t):+!pe(e)}function pe(e){return le.includes(e.toLowerCase())}var me={},z=e=>{console.error(e)},he=(e,...t)=>{console.log(`WARN: ${e}`,...t)},ge=(e,t)=>{me[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),me[`${e}/${t}`]=!0)},_e=Error();function ve(e,t,{key:n}){let r=0,i=e[n],a={},o={};for(let e=1;e<=t.length;e++)o[e+r]=i[e],a[e+r]=!0,r+=b(t[e-1]);e[n]=o,e[n]._emit=a,e[n]._multi=!0}function ye(e){if(Array.isArray(e.begin)){if(e.skip||e.excludeBegin||e.returnBegin)throw z(`skip, excludeBegin, returnBegin not compatible with beginScope: {}`),_e;if(typeof e.beginScope!=`object`||e.beginScope===null)throw z(`beginScope must be object`),_e;ve(e,e.begin,{key:`beginScope`}),e.begin=C(e.begin,{joinWith:``})}}function be(e){if(Array.isArray(e.end)){if(e.skip||e.excludeEnd||e.returnEnd)throw z(`skip, excludeEnd, returnEnd not compatible with endScope: {}`),_e;if(typeof e.endScope!=`object`||e.endScope===null)throw z(`endScope must be object`),_e;ve(e,e.end,{key:`endScope`}),e.end=C(e.end,{joinWith:``})}}function B(e){e.scope&&typeof e.scope==`object`&&e.scope!==null&&(e.beginScope=e.scope,delete e.scope)}function xe(e){B(e),typeof e.beginScope==`string`&&(e.beginScope={_wrap:e.beginScope}),typeof e.endScope==`string`&&(e.endScope={_wrap:e.endScope}),ye(e),be(e)}function Se(e){function t(t,n){return new RegExp(p(t),`m`+(e.case_insensitive?`i`:``)+(e.unicodeRegex?`u`:``)+(n?`g`:``))}class n{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,t){t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]),this.matchAt+=b(e)+1}compile(){this.regexes.length===0&&(this.exec=()=>null);let e=this.regexes.map(e=>e[1]);this.matcherRe=t(C(e,{joinWith:`|`}),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;let t=this.matcherRe.exec(e);if(!t)return null;let n=t.findIndex((e,t)=>t>0&&e!==void 0),r=this.matchIndexes[n];return t.splice(0,n),Object.assign(t,r)}}class r{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];let t=new n;return this.rules.slice(e).forEach(([e,n])=>t.addRule(e,n)),t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){return this.regexIndex!==0}considerAll(){this.regexIndex=0}addRule(e,t){this.rules.push([e,t]),t.type===`begin`&&this.count++}exec(e){let t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex;let n=t.exec(e);if(this.resumingScanAtSamePosition()&&!(n&&n.index===this.lastIndex)){let t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)}return n&&(this.regexIndex+=n.position+1,this.regexIndex===this.count&&this.considerAll()),n}}function i(e){let t=new r;return e.contains.forEach(e=>t.addRule(e.begin,{rule:e,type:`begin`})),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:`end`}),e.illegal&&t.addRule(e.illegal,{type:`illegal`}),t}function o(n,r){let a=n;if(n.isCompiled)return a;[re,oe,xe,ce].forEach(e=>e(n,r)),e.compilerExtensions.forEach(e=>e(n,r)),n.__beforeBegin=null,[ie,ae,se].forEach(e=>e(n,r)),n.isCompiled=!0;let s=null;return typeof n.keywords==`object`&&n.keywords.$pattern&&(n.keywords=Object.assign({},n.keywords),s=n.keywords.$pattern,delete n.keywords.$pattern),s||=/\w+/,n.keywords&&=de(n.keywords,e.case_insensitive),a.keywordPatternRe=t(s,!0),r&&(n.begin||=/\B|\b/,a.beginRe=t(a.begin),!n.end&&!n.endsWithParent&&(n.end=/\B|\b/),n.end&&(a.endRe=t(a.end)),a.terminatorEnd=p(a.end)||``,n.endsWithParent&&r.terminatorEnd&&(a.terminatorEnd+=(n.end?`|`:``)+r.terminatorEnd)),n.illegal&&(a.illegalRe=t(n.illegal)),n.contains||=[],n.contains=[].concat(...n.contains.map(function(e){return we(e===`self`?n:e)})),n.contains.forEach(function(e){o(e,a)}),n.starts&&o(n.starts,r),a.matcher=i(a),a}if(e.compilerExtensions||=[],e.contains&&e.contains.includes(`self`))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return e.classNameAliases=a(e.classNameAliases||{}),o(e)}function Ce(e){return e?e.endsWithParent||Ce(e.starts):!1}function we(e){return e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map(function(t){return a(e,{variants:null},t)})),e.cachedVariants?e.cachedVariants:Ce(e)?a(e,{starts:e.starts?a(e.starts):null}):Object.isFrozen(e)?a(e):e}var Te=`11.11.1`,Ee=class extends Error{constructor(e,t){super(e),this.name=`HTMLInjectionError`,this.html=t}},De=i,Oe=a,ke=Symbol(`nomatch`),Ae=7,je=function(e){let t=Object.create(null),i=Object.create(null),a=[],o=!0,s=`Could not find the language '{}', did you forget to load/include a language module?`,c={disableAutodetect:!0,name:`Plain text`,contains:[]},l={ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:`hljs-`,cssSelector:`pre code`,languages:null,__emitter:f};function u(e){return l.noHighlightRe.test(e)}function d(e){let t=e.className+` `;t+=e.parentNode?e.parentNode.className:``;let n=l.languageDetectRe.exec(t);if(n){let t=N(n[1]);return t||(he(s.replace(`{}`,n[1])),he(`Falling back to no-highlight mode for this block.`,e)),t?n[1]:`no-highlight`}return t.split(/\s+/).find(e=>u(e)||N(e))}function p(e,t,n){let r=``,i=``;typeof t==`object`?(r=e,n=t.ignoreIllegals,i=t.language):(ge(`10.7.0`,`highlight(lang, code, ...args) has been deprecated.`),ge(`10.7.0`,`Please use highlight(code, options) instead. https://github.com/highlightjs/highlight.js/issues/2277`),i=e,r=t),n===void 0&&(n=!0);let a={code:r,language:i};ee(`before:highlight`,a);let o=a.result?a.result:v(a.language,a.code,n);return o.code=a.code,ee(`after:highlight`,o),o}function v(e,n,i,a){let c=Object.create(null);function u(e,t){return e.keywords[t]}function d(){if(!A.keywords){M.addText(P);return}let e=0;A.keywordPatternRe.lastIndex=0;let t=A.keywordPatternRe.exec(P),n=``;for(;t;){n+=P.substring(e,t.index);let r=D.case_insensitive?t[0].toLowerCase():t[0],i=u(A,r);if(i){let[e,a]=i;if(M.addText(n),n=``,c[r]=(c[r]||0)+1,c[r]<=Ae&&(F+=a),e.startsWith(`_`))n+=t[0];else{let n=D.classNameAliases[e]||e;m(t[0],n)}}else n+=t[0];e=A.keywordPatternRe.lastIndex,t=A.keywordPatternRe.exec(P)}n+=P.substring(e),M.addText(n)}function f(){if(P===``)return;let e=null;if(typeof A.subLanguage==`string`){if(!t[A.subLanguage]){M.addText(P);return}e=v(A.subLanguage,P,!0,j[A.subLanguage]),j[A.subLanguage]=e._top}else e=S(P,A.subLanguage.length?A.subLanguage:null);A.relevance>0&&(F+=e.relevance),M.__addSublanguage(e._emitter,e.language)}function p(){A.subLanguage==null?d():f(),P=``}function m(e,t){e!==``&&(M.startScope(t),M.addText(e),M.endScope())}function h(e,t){let n=1,r=t.length-1;for(;n<=r;){if(!e._emit[n]){n++;continue}let r=D.classNameAliases[e[n]]||e[n],i=t[n];r?m(i,r):(P=i,d(),P=``),n++}}function g(e,t){return e.scope&&typeof e.scope==`string`&&M.openNode(D.classNameAliases[e.scope]||e.scope),e.beginScope&&(e.beginScope._wrap?(m(P,D.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap),P=``):e.beginScope._multi&&(h(e.beginScope,t),P=``)),A=Object.create(e,{parent:{value:A}}),A}function _(e,t,n){let i=x(e.endRe,n);if(i){if(e[`on:end`]){let n=new r(e);e[`on:end`](t,n),n.isMatchIgnored&&(i=!1)}if(i){for(;e.endsParent&&e.parent;)e=e.parent;return e}}if(e.endsWithParent)return _(e.parent,t,n)}function y(e){return A.matcher.regexIndex===0?(P+=e[0],1):(R=!0,0)}function b(e){let t=e[0],n=e.rule,i=new r(n),a=[n.__beforeBegin,n[`on:begin`]];for(let n of a)if(n&&(n(e,i),i.isMatchIgnored))return y(t);return n.skip?P+=t:(n.excludeBegin&&(P+=t),p(),!n.returnBegin&&!n.excludeBegin&&(P=t)),g(n,e),n.returnBegin?0:t.length}function C(e){let t=e[0],r=n.substring(e.index),i=_(A,e,r);if(!i)return ke;let a=A;A.endScope&&A.endScope._wrap?(p(),m(t,A.endScope._wrap)):A.endScope&&A.endScope._multi?(p(),h(A.endScope,e)):a.skip?P+=t:(a.returnEnd||a.excludeEnd||(P+=t),p(),a.excludeEnd&&(P=t));do A.scope&&M.closeNode(),!A.skip&&!A.subLanguage&&(F+=A.relevance),A=A.parent;while(A!==i.parent);return i.starts&&g(i.starts,e),a.returnEnd?0:t.length}function w(){let e=[];for(let t=A;t!==D;t=t.parent)t.scope&&e.unshift(t.scope);e.forEach(e=>M.openNode(e))}let T={};function E(t,r){let a=r&&r[0];if(P+=t,a==null)return p(),0;if(T.type===`begin`&&r.type===`end`&&T.index===r.index&&a===``){if(P+=n.slice(r.index,r.index+1),!o){let t=Error(`0 width match regex (${e})`);throw t.languageName=e,t.badRule=T.rule,t}return 1}if(T=r,r.type===`begin`)return b(r);if(r.type===`illegal`&&!i){let e=Error(`Illegal lexeme "`+a+`" for mode "`+(A.scope||``)+`"`);throw e.mode=A,e}else if(r.type===`end`){let e=C(r);if(e!==ke)return e}if(r.type===`illegal`&&a===``)return P+=` `,1;if(L>1e5&&L>r.index*3)throw Error(`potential infinite loop, way more iterations than matches`);return P+=a,a.length}let D=N(e);if(!D)throw z(s.replace(`{}`,e)),Error(`Unknown language: "`+e+`"`);let O=Se(D),k=``,A=a||O,j={},M=new l.__emitter(l);w();let P=``,F=0,I=0,L=0,R=!1;try{if(D.__emitTokens)D.__emitTokens(n,M);else{for(A.matcher.considerAll();;){L++,R?R=!1:A.matcher.considerAll(),A.matcher.lastIndex=I;let e=A.matcher.exec(n);if(!e)break;let t=E(n.substring(I,e.index),e);I=e.index+t}E(n.substring(I))}return M.finalize(),k=M.toHTML(),{language:e,value:k,relevance:F,illegal:!1,_emitter:M,_top:A}}catch(t){if(t.message&&t.message.includes(`Illegal`))return{language:e,value:De(n),illegal:!0,relevance:0,_illegalBy:{message:t.message,index:I,context:n.slice(I-100,I+100),mode:t.mode,resultSoFar:k},_emitter:M};if(o)return{language:e,value:De(n),illegal:!1,relevance:0,errorRaised:t,_emitter:M,_top:A};throw t}}function b(e){let t={value:De(e),illegal:!1,relevance:0,_top:c,_emitter:new l.__emitter(l)};return t._emitter.addText(e),t}function S(e,n){n=n||l.languages||Object.keys(t);let r=b(e),i=n.filter(N).filter(F).map(t=>v(t,e,!1));i.unshift(r);let[a,o]=i.sort((e,t)=>{if(e.relevance!==t.relevance)return t.relevance-e.relevance;if(e.language&&t.language){if(N(e.language).supersetOf===t.language)return 1;if(N(t.language).supersetOf===e.language)return-1}return 0}),s=a;return s.secondBest=o,s}function C(e,t,n){let r=t&&i[t]||n;e.classList.add(`hljs`),e.classList.add(`language-${r}`)}function w(e){let t=null,n=d(e);if(u(n))return;if(ee(`before:highlightElement`,{el:e,language:n}),e.dataset.highlighted){console.log("Element previously highlighted. To highlight again, first unset `dataset.highlighted`.",e);return}if(e.children.length>0&&(l.ignoreUnescapedHTML||(console.warn(`One of your code blocks includes unescaped HTML. This is a potentially serious security risk.`),console.warn(`https://github.com/highlightjs/highlight.js/wiki/security`),console.warn(`The element with unescaped HTML:`),console.warn(e)),l.throwUnescapedHTML))throw new Ee(`One of your code blocks includes unescaped HTML.`,e.innerHTML);t=e;let r=t.textContent,i=n?p(r,{language:n,ignoreIllegals:!0}):S(r);e.innerHTML=i.value,e.dataset.highlighted=`yes`,C(e,n,i.language),e.result={language:i.language,re:i.relevance,relevance:i.relevance},i.secondBest&&(e.secondBest={language:i.secondBest.language,relevance:i.secondBest.relevance}),ee(`after:highlightElement`,{el:e,result:i,text:r})}function T(e){l=Oe(l,e)}let E=()=>{k(),ge(`10.6.0`,`initHighlighting() deprecated. Use highlightAll() now.`)};function D(){k(),ge(`10.6.0`,`initHighlightingOnLoad() deprecated. Use highlightAll() now.`)}let O=!1;function k(){function e(){k()}if(document.readyState===`loading`){O||window.addEventListener(`DOMContentLoaded`,e,!1),O=!0;return}document.querySelectorAll(l.cssSelector).forEach(w)}function A(n,r){let i=null;try{i=r(e)}catch(e){if(z(`Language definition for '{}' could not be registered.`.replace(`{}`,n)),o)z(e);else throw e;i=c}i.name||=n,t[n]=i,i.rawDefinition=r.bind(null,e),i.aliases&&P(i.aliases,{languageName:n})}function j(e){delete t[e];for(let t of Object.keys(i))i[t]===e&&delete i[t]}function M(){return Object.keys(t)}function N(e){return e=(e||``).toLowerCase(),t[e]||t[i[e]]}function P(e,{languageName:t}){typeof e==`string`&&(e=[e]),e.forEach(e=>{i[e.toLowerCase()]=t})}function F(e){let t=N(e);return t&&!t.disableAutodetect}function I(e){e[`before:highlightBlock`]&&!e[`before:highlightElement`]&&(e[`before:highlightElement`]=t=>{e[`before:highlightBlock`](Object.assign({block:t.el},t))}),e[`after:highlightBlock`]&&!e[`after:highlightElement`]&&(e[`after:highlightElement`]=t=>{e[`after:highlightBlock`](Object.assign({block:t.el},t))})}function L(e){I(e),a.push(e)}function R(e){let t=a.indexOf(e);t!==-1&&a.splice(t,1)}function ee(e,t){let n=e;a.forEach(function(e){e[n]&&e[n](t)})}function ne(e){return ge(`10.7.0`,`highlightBlock will be removed entirely in v12.0`),ge(`10.7.0`,`Please use highlightElement now.`),w(e)}Object.assign(e,{highlight:p,highlightAuto:S,highlightAll:k,highlightElement:w,highlightBlock:ne,configure:T,initHighlighting:E,initHighlightingOnLoad:D,registerLanguage:A,unregisterLanguage:j,listLanguages:M,getLanguage:N,registerAliases:P,autoDetection:F,inherit:Oe,addPlugin:L,removePlugin:R}),e.debugMode=function(){o=!1},e.safeMode=function(){o=!0},e.versionString=Te,e.regex={concat:_,lookahead:m,either:y,optional:g,anyNumberOfTimes:h};for(let e in te)typeof te[e]==`object`&&n(te[e]);return Object.assign(e,te),e},Me=je({});Me.newInstance=()=>je({}),t.exports=Me,Me.HighlightJS=Me,Me.default=Me}))()).default,Ed={},Dd=`hljs-`;function Od(e){let t=Td.newInstance();return e&&a(e),{highlight:n,highlightAuto:r,listLanguages:i,register:a,registerAlias:o,registered:s};function n(e,n,r){let i=r||Ed,a=typeof i.prefix==`string`?i.prefix:Dd;if(!t.getLanguage(e))throw Error("Unknown language: `"+e+"` is not registered");t.configure({__emitter:kd,classPrefix:a});let o=t.highlight(n,{ignoreIllegals:!0,language:e});if(o.errorRaised)throw Error("Could not highlight with `Highlight.js`",{cause:o.errorRaised});let s=o._emitter.root,c=s.data;return c.language=o.language,c.relevance=o.relevance,s}function r(e,r){let a=(r||Ed).subset||i(),o=-1,s=0,c;for(;++os&&(s=l.data.relevance,c=l)}return c||{type:`root`,children:[],data:{language:void 0,relevance:s}}}function i(){return t.listLanguages()}function a(e,n){if(typeof e==`string`)t.registerLanguage(e,n);else{let n;for(n in e)Object.hasOwn(e,n)&&t.registerLanguage(n,e[n])}}function o(e,n){if(typeof e==`string`)t.registerAliases(typeof n==`string`?n:[...n],{languageName:e});else{let n;for(n in e)if(Object.hasOwn(e,n)){let r=e[n];t.registerAliases(typeof r==`string`?r:[...r],{languageName:n})}}}function s(e){return!!t.getLanguage(e)}}var kd=class{constructor(e){this.options=e,this.root={type:`root`,children:[],data:{language:void 0,relevance:0}},this.stack=[this.root]}addText(e){if(e===``)return;let t=this.stack[this.stack.length-1],n=t.children[t.children.length-1];n&&n.type===`text`?n.value+=e:t.children.push({type:`text`,value:e})}startScope(e){this.openNode(String(e))}endScope(){this.closeNode()}__addSublanguage(e,t){let n=this.stack[this.stack.length-1],r=e.root.children;t?n.children.push({type:`element`,tagName:`span`,properties:{className:[t]},children:r}):n.children.push(...r)}openNode(e){let t=this,n=e.split(`.`).map(function(e,n){return n?e+`_`.repeat(n):t.options.classPrefix+e}),r=this.stack[this.stack.length-1],i={type:`element`,tagName:`span`,properties:{className:n},children:[]};r.children.push(i),this.stack.push(i)}closeNode(){this.stack.pop()}finalize(){}toHTML(){return``}},Ad={};function jd(e){let t=e||Ad,n=t.aliases,r=t.detect||!1,i=t.languages||wd,a=t.plainText,o=t.prefix,s=t.subset,c=`hljs`,l=Od(i);if(n&&l.registerAlias(n),o){let e=o.indexOf(`-`);c=e===-1?o:o.slice(0,e)}return function(e,t){qi(e,`element`,function(e,n,i){if(e.tagName!==`code`||!i||i.type!==`element`||i.tagName!==`pre`)return;let u=Md(e);if(u===!1||!u&&!r||u&&a&&a.includes(u))return;Array.isArray(e.properties.className)||(e.properties.className=[]),e.properties.className.includes(c)||e.properties.className.unshift(c);let d=fl(e,{whitespace:`pre`}),f;try{f=u?l.highlight(u,d,{prefix:o}):l.highlightAuto(d,{prefix:o,subset:s})}catch(n){let r=n;if(u&&/Unknown language/.test(r.message)){t.message("Cannot highlight as `"+u+"`, it’s not registered",{ancestors:[i,e],cause:r,place:e.position,ruleId:`missing-language`,source:`rehype-highlight`});return}throw r}!u&&f.data&&f.data.language&&e.properties.className.push(`language-`+f.data.language),f.children.length>0&&(e.children=f.children)})}}function Md(e){let t=e.properties.className,n=-1;if(!Array.isArray(t))return;let r;for(;++n(0,Ba.jsx)(`a`,{...t,target:`_blank`,rel:`noopener noreferrer`})},children:e})})}export{Nd as MarkdownView}; \ No newline at end of file diff --git a/viewer-ui/dist/assets/Terminal-DmlNBLUk.js b/viewer-ui/dist/assets/Terminal-DmlNBLUk.js deleted file mode 100644 index 1eccc9b6..00000000 --- a/viewer-ui/dist/assets/Terminal-DmlNBLUk.js +++ /dev/null @@ -1,35 +0,0 @@ -import{a as e,c as t,i as n,l as r,n as i,o as a,r as o,s,t as c}from"./index-Bla_aIeg.js";var l=r();function u(e,t){for(;t;)[e,t]=[t,e%t];return e}function d(e,t){switch(e){case 1:return[1];case 2:return t?[2]:[1,1];case 3:return[2,1];case 4:return[2,2];case 5:return[3,2];case 6:return[3,3];case 7:return[4,3];default:return[4,4]}}function f(e,t){let n=d(e,t),r=n.reduce((e,t)=>e*t/u(e,t),1),i=[];return n.forEach((e,t)=>{let n=r/e;for(let r=0;r=4352&&e<=4447||e>=11904&&e<=12350||e>=12353&&e<=13311||e>=13312&&e<=19903||e>=19968&&e<=40959||e>=40960&&e<=42191||e>=44032&&e<=55203||e>=63744&&e<=64255||e>=65072&&e<=65103||e>=65280&&e<=65376||e>=65504&&e<=65510||e>=127744&&e<=129791||e>=131072&&e<=262141}function m(e,t){let n=0;for(let t of e)n+=p(t.codePointAt(0)??0)?2:1;if(n<=t)return e;let r=0,i=``;for(let n of e){let e=p(n.codePointAt(0)??0)?2:1;if(r+e>t-1)break;i+=n,r+=e}return`${i}…`}function h({panes:e,zoomed:t,onFocus:n,onReorder:r}){let i=(0,l.useRef)(null),a=(0,l.useRef)(null),o=(0,l.useRef)(null),c=(0,l.useRef)(!1),[u,d]=(0,l.useState)(null),[f,p]=(0,l.useState)(null),m=t===null&&e.length>1,h=()=>{i.current=null,a.current=null,o.current=null,c.current=!1,d(null),p(null)};return{draggingPane:u,dragOverPane:f,reorderable:m,endPaneDrag:h,onPaneDragStart:(e,t)=>{e.target.closest(`button`)||(n(t),!(e.button!==0||!m)&&(i.current=t,a.current={x:e.clientX,y:e.clientY},c.current=!1,e.currentTarget.setPointerCapture(e.pointerId)))},onPaneDragMove:e=>{let t=i.current,n=a.current;if(t===null||n===null||!c.current&&Math.hypot(e.clientX-n.x,e.clientY-n.y)<4)return;c.current=!0,d(t);let r=document.elementFromPoint(e.clientX,e.clientY)?.closest(`[data-pane-id]`),s=r?Number(r.getAttribute(`data-pane-id`)):null,l=s!==null&&s!==t?s:null;o.current=l,p(l)},onPaneDragEnd:()=>{let t=i.current,n=o.current;t!==null&&c.current&&n!==null&&r(s(e,t,n)),h()}}}function g(e,t){return t.state===`cancelled`?_(e,t.pane):{...e,[t.pane]:{state:t.state,detail:t.detail,deadlineEpoch:t.deadline_epoch,attempt:t.attempt}}}function _(e,t){if(!(t in e))return e;let n={...e};return delete n[t],n}function v(e,t){return Object.keys(e).map(Number).filter(e=>!t.includes(e)).sort((e,t)=>e-t)}function ee(e){if(e===void 0||!Number.isFinite(e))return;let t=new Date(e*1e3);if(!Number.isNaN(t.getTime()))return`${String(t.getHours()).padStart(2,`0`)}:${String(t.getMinutes()).padStart(2,`0`)}`}function y(e){let t=ee(e.deadlineEpoch),n=[e.state];return t&&n.push(`until ${t}`),e.attempt>0&&n.push(`attempt ${e.attempt}`),n.join(` · `)}function te(e){return JSON.stringify({type:`cancel_recovery`,pane:e})}function b(e,t){e?.send(te(t))}var ne=`nightcrow.viewer`;function re(){return globalThis.crypto?.randomUUID?.()||`tab-${Math.floor(Math.random()*2**48).toString(36)}`}function ie(){try{let e=sessionStorage.getItem(ne);if(e)return e;let t=re();return sessionStorage.setItem(ne,t),t}catch{return re()}}var ae=!1;function x(){return ae?!1:(ae=!0,!0)}function oe({repo:e,socketRef:n,viewsRef:r,pendingRef:i,sentSizesRef:o,lastActiveByRepoRef:s,zoomAskedRef:c,setPending:u,setReplayLeft:d,setPanes:f,setActive:p,setZoomed:m,setTitles:h,setOwnsSize:_,setRecovery:v}){let ee=(0,l.useRef)(null);(0,l.useLayoutEffect)(()=>{let l=!1,y,te=()=>{r.current.forEach(e=>e.term.dispose()),r.current.clear(),i.current.clear(),o.current.clear()},b=()=>{ee.current=null,d(0),c.current=void 0,u(null),f([]),p(null),m(null),h({});let ne=x();ne&&_(!0),v({}),te();let re=location.protocol===`https:`?`wss:`:`ws:`,ae=new URLSearchParams({repo:e,viewer:ie()});ne&&ae.set(`claim`,`1`);let oe=new WebSocket(`${re}//${location.host}/ws/term?${ae}`);oe.binaryType=`arraybuffer`,n.current=oe,oe.onmessage=l=>{if(n.current!==oe)return;if(typeof l.data==`string`){let n=JSON.parse(l.data);if(n.type===`hello`)ee.current=n.client,d(n.panes);else if(n.type===`pending`)u(n.count);else if(n.type===`created`){let t=n.pane;o.current.set(t,{rows:n.rows,cols:n.cols}),typeof n.title==`string`&&n.title&&h(e=>({...e,[t]:n.title})),f(e=>[...e,t]),d(e=>e>0?e-1:0),n.client!=null&&n.client===ee.current?(p(t),s.current.set(e,t)):s.current.get(e)===t&&p(t)}else n.type===`exited`?(f(e=>e.filter(e=>e!==n.pane)),p(e=>e===n.pane?null:e),i.current.delete(n.pane),o.current.delete(n.pane),h(e=>{if(!(n.pane in e))return e;let t={...e};return delete t[n.pane],t})):n.type===`resized`?(o.current.set(n.pane,{rows:n.rows,cols:n.cols}),r.current.get(n.pane)?.term.resize(n.cols,n.rows)):n.type===`recovery`?v(e=>g(e,n)):n.type===`size_owner`?_(n.owned):n.type===`reordered`?f(e=>a(e,n.order)):n.type===`zoomed`?(c.current=void 0,m(n.pane??null)):n.type===`error`&&t.error(n.message);return}let y=new Uint8Array(l.data);if(y.length<4)return;let te=new DataView(y.buffer).getUint32(0,!0),b=y.subarray(4),ne=r.current.get(te);if(ne)ne.term.write(b);else{let e=i.current.get(te)??[];e.push(b),i.current.set(te,e)}},oe.onclose=()=>{l||(y=setTimeout(b,1e3))}};return b(),()=>{l=!0,y&&clearTimeout(y),n.current?.close(),te()}},[e])}var S=Object.defineProperty,se=Object.getOwnPropertyDescriptor,ce=(e,t)=>{for(var n in t)S(e,n,{get:t[n],enumerable:!0})},C=(e,t,n,r)=>{for(var i=r>1?void 0:r?se(t,n):t,a=e.length-1,o;a>=0;a--)(o=e[a])&&(i=(r?o(t,n,i):o(i))||i);return r&&i&&S(t,n,i),i},w=(e,t)=>(n,r)=>t(n,r,e),le=`Terminal input`,ue={get:()=>le,set:e=>le=e},de=`Too much output to announce, navigate to rows manually to read`,fe={get:()=>de,set:e=>de=e};function T(e){return e.replace(/\r?\n/g,`\r`)}function pe(e,t){return t?`\x1B[200~`+e+`\x1B[201~`:e}function me(e,t){e.clipboardData&&e.clipboardData.setData(`text/plain`,t.selectionText),e.preventDefault()}function he(e,t,n,r){e.stopPropagation(),e.clipboardData&&ge(e.clipboardData.getData(`text/plain`),t,n,r)}function ge(e,t,n,r){e=T(e),e=pe(e,n.decPrivateModes.bracketedPasteMode&&r.rawOptions.ignoreBracketedPasteMode!==!0),n.triggerDataEvent(e,!0),t.value=``}function _e(e,t,n){let r=n.getBoundingClientRect(),i=e.clientX-r.left-10,a=e.clientY-r.top-10;t.style.width=`20px`,t.style.height=`20px`,t.style.left=`${i}px`,t.style.top=`${a}px`,t.style.zIndex=`1000`,t.focus()}function ve(e,t,n,r,i){_e(e,t,n),i&&r.rightClickSelect(e),t.value=r.selectionText,t.select()}function ye(e){return e>65535?(e-=65536,String.fromCharCode((e>>10)+55296)+String.fromCharCode(e%1024+56320)):String.fromCharCode(e)}function be(e,t=0,n=e.length){let r=``;for(let i=t;i65535?(t-=65536,r+=String.fromCharCode((t>>10)+55296)+String.fromCharCode(t%1024+56320)):r+=String.fromCharCode(t)}return r}var xe=class{constructor(){this._interim=0}clear(){this._interim=0}decode(e,t){let n=e.length;if(!n)return 0;let r=0,i=0;if(this._interim){let n=e.charCodeAt(i++);56320<=n&&n<=57343?t[r++]=(this._interim-55296)*1024+n-56320+65536:(t[r++]=this._interim,t[r++]=n),this._interim=0}for(let a=i;a=n)return this._interim=i,r;let o=e.charCodeAt(a);56320<=o&&o<=57343?t[r++]=(i-55296)*1024+o-56320+65536:(t[r++]=i,t[r++]=o);continue}i!==65279&&(t[r++]=i)}return r}},Se=class{constructor(){this.interim=new Uint8Array(3)}clear(){this.interim.fill(0)}decode(e,t){let n=e.length;if(!n)return 0;let r=0,i,a,o,s,c=0,l=0;if(this.interim[0]){let i=!1,a=this.interim[0];a&=(a&224)==192?31:(a&240)==224?15:7;let o=0,s;for(;(s=this.interim[++o]&63)&&o<4;)a<<=6,a|=s;let c=(this.interim[0]&224)==192?2:(this.interim[0]&240)==224?3:4,u=c-o;for(;l=n)return 0;if(s=e[l++],(s&192)!=128){l--,i=!0;break}else this.interim[o++]=s,a<<=6,a|=s&63}i||(c===2?a<128?l--:t[r++]=a:c===3?a<2048||a>=55296&&a<=57343||a===65279||(t[r++]=a):a<65536||a>1114111||(t[r++]=a)),this.interim.fill(0)}let u=n-4,d=l;for(;d=n)return this.interim[0]=i,r;if(a=e[d++],(a&192)!=128){d--;continue}if(c=(i&31)<<6|a&63,c<128){d--;continue}t[r++]=c}else if((i&240)==224){if(d>=n)return this.interim[0]=i,r;if(a=e[d++],(a&192)!=128){d--;continue}if(d>=n)return this.interim[0]=i,this.interim[1]=a,r;if(o=e[d++],(o&192)!=128){d--;continue}if(c=(i&15)<<12|(a&63)<<6|o&63,c<2048||c>=55296&&c<=57343||c===65279)continue;t[r++]=c}else if((i&248)==240){if(d>=n)return this.interim[0]=i,r;if(a=e[d++],(a&192)!=128){d--;continue}if(d>=n)return this.interim[0]=i,this.interim[1]=a,r;if(o=e[d++],(o&192)!=128){d--;continue}if(d>=n)return this.interim[0]=i,this.interim[1]=a,this.interim[2]=o,r;if(s=e[d++],(s&192)!=128){d--;continue}if(c=(i&7)<<18|(a&63)<<12|(o&63)<<6|s&63,c<65536||c>1114111)continue;t[r++]=c}}return r}},Ce=``,we=` `,Te=class e{constructor(){this.fg=0,this.bg=0,this.extended=new Ee}static toColorRGB(e){return[e>>>16&255,e>>>8&255,e&255]}static fromColorRGB(e){return(e[0]&255)<<16|(e[1]&255)<<8|e[2]&255}clone(){let t=new e;return t.fg=this.fg,t.bg=this.bg,t.extended=this.extended.clone(),t}isInverse(){return this.fg&67108864}isBold(){return this.fg&134217728}isUnderline(){return this.hasExtendedAttrs()&&this.extended.underlineStyle!==0?1:this.fg&268435456}isBlink(){return this.fg&536870912}isInvisible(){return this.fg&1073741824}isItalic(){return this.bg&67108864}isDim(){return this.bg&134217728}isStrikethrough(){return this.fg&2147483648}isProtected(){return this.bg&536870912}isOverline(){return this.bg&1073741824}getFgColorMode(){return this.fg&50331648}getBgColorMode(){return this.bg&50331648}isFgRGB(){return(this.fg&50331648)==50331648}isBgRGB(){return(this.bg&50331648)==50331648}isFgPalette(){return(this.fg&50331648)==16777216||(this.fg&50331648)==33554432}isBgPalette(){return(this.bg&50331648)==16777216||(this.bg&50331648)==33554432}isFgDefault(){return(this.fg&50331648)==0}isBgDefault(){return(this.bg&50331648)==0}isAttributeDefault(){return this.fg===0&&this.bg===0}getFgColor(){switch(this.fg&50331648){case 16777216:case 33554432:return this.fg&255;case 50331648:return this.fg&16777215;default:return-1}}getBgColor(){switch(this.bg&50331648){case 16777216:case 33554432:return this.bg&255;case 50331648:return this.bg&16777215;default:return-1}}hasExtendedAttrs(){return this.bg&268435456}updateExtended(){this.extended.isEmpty()?this.bg&=-268435457:this.bg|=268435456}getUnderlineColor(){if(this.bg&268435456&&~this.extended.underlineColor)switch(this.extended.underlineColor&50331648){case 16777216:case 33554432:return this.extended.underlineColor&255;case 50331648:return this.extended.underlineColor&16777215;default:return this.getFgColor()}return this.getFgColor()}getUnderlineColorMode(){return this.bg&268435456&&~this.extended.underlineColor?this.extended.underlineColor&50331648:this.getFgColorMode()}isUnderlineColorRGB(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)==50331648:this.isFgRGB()}isUnderlineColorPalette(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)==16777216||(this.extended.underlineColor&50331648)==33554432:this.isFgPalette()}isUnderlineColorDefault(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)==0:this.isFgDefault()}getUnderlineStyle(){return this.fg&268435456?this.bg&268435456?this.extended.underlineStyle:1:0}getUnderlineVariantOffset(){return this.extended.underlineVariantOffset}},Ee=class e{constructor(e=0,t=0){this._ext=0,this._urlId=0,this._ext=e,this._urlId=t}get ext(){return this._urlId?this._ext&-469762049|this.underlineStyle<<26:this._ext}set ext(e){this._ext=e}get underlineStyle(){return this._urlId?5:(this._ext&469762048)>>26}set underlineStyle(e){this._ext&=-469762049,this._ext|=e<<26&469762048}get underlineColor(){return this._ext&67108863}set underlineColor(e){this._ext&=-67108864,this._ext|=e&67108863}get urlId(){return this._urlId}set urlId(e){this._urlId=e}get underlineVariantOffset(){let e=(this._ext&3758096384)>>29;return e<0?e^4294967288:e}set underlineVariantOffset(e){this._ext&=536870911,this._ext|=e<<29&3758096384}clone(){return new e(this._ext,this._urlId)}isEmpty(){return this.underlineStyle===0&&this._urlId===0}},De=class e extends Te{constructor(){super(...arguments),this.content=0,this.fg=0,this.bg=0,this.extended=new Ee,this.combinedData=``}static fromCharData(t){let n=new e;return n.setFromCharData(t),n}isCombined(){return this.content&2097152}getWidth(){return this.content>>22}getChars(){return this.content&2097152?this.combinedData:this.content&2097151?ye(this.content&2097151):``}getCode(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):this.content&2097151}setFromCharData(e){this.fg=e[0],this.bg=0;let t=!1;if(e[1].length>2)t=!0;else if(e[1].length===2){let n=e[1].charCodeAt(0);if(55296<=n&&n<=56319){let r=e[1].charCodeAt(1);56320<=r&&r<=57343?this.content=(n-55296)*1024+r-56320+65536|e[2]<<22:t=!0}else t=!0}else this.content=e[1].charCodeAt(0)|e[2]<<22;t&&(this.combinedData=e[1],this.content=2097152|e[2]<<22)}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}},Oe=`di$target`,ke=`di$dependencies`,Ae=new Map;function je(e){return e[ke]||[]}function E(e){if(Ae.has(e))return Ae.get(e);let t=function(e,n,r){if(arguments.length!==3)throw Error(`@IServiceName-decorator can only be used to decorate a parameter`);Me(t,e,r)};return t._id=e,Ae.set(e,t),t}function Me(e,t,n){t[Oe]===t?t[ke].push({id:e,index:n}):(t[ke]=[{id:e,index:n}],t[Oe]=t)}var D=E(`BufferService`),Ne=E(`CoreMouseService`),Pe=E(`CoreService`),Fe=E(`CharsetService`),Ie=E(`InstantiationService`),Le=E(`LogService`),O=E(`OptionsService`),Re=E(`OscLinkService`),ze=E(`UnicodeService`),Be=E(`DecorationService`),Ve=class{constructor(e,t,n){this._bufferService=e,this._optionsService=t,this._oscLinkService=n}provideLinks(e,t){let n=this._bufferService.buffer.lines.get(e-1);if(!n){t(void 0);return}let r=[],i=this._optionsService.rawOptions.linkHandler,a=new De,o=n.getTrimmedLength(),s=-1,c=-1,l=!1;for(let t=0;ti?i.activate(e,t,a):He(e,t),hover:(e,t)=>i?.hover?.(e,t,a),leave:(e,t)=>i?.leave?.(e,t,a)})}l=!1,a.hasExtendedAttrs()&&a.extended.urlId?(c=t,s=a.extended.urlId):(c=-1,s=-1)}}t(r)}};Ve=C([w(0,D),w(1,O),w(2,Re)],Ve);function He(e,t){if(confirm(`Do you want to navigate to ${t}? - -WARNING: This link could potentially be dangerous`)){let e=window.open();if(e){try{e.opener=null}catch{}e.location.href=t}else console.warn(`Opening link blocked as opener could not be cleared`)}}var Ue=E(`CharSizeService`),We=E(`CoreBrowserService`),Ge=E(`MouseService`),Ke=E(`RenderService`),qe=E(`SelectionService`),Je=E(`CharacterJoinerService`),Ye=E(`ThemeService`),Xe=E(`LinkProviderService`),Ze=new class{constructor(){this.listeners=[],this.unexpectedErrorHandler=function(e){setTimeout(()=>{throw e.stack?rt.isErrorNoTelemetry(e)?new rt(e.message+` - -`+e.stack):Error(e.message+` - -`+e.stack):e},0)}}addListener(e){return this.listeners.push(e),()=>{this._removeListener(e)}}emit(e){this.listeners.forEach(t=>{t(e)})}_removeListener(e){this.listeners.splice(this.listeners.indexOf(e),1)}setUnexpectedErrorHandler(e){this.unexpectedErrorHandler=e}getUnexpectedErrorHandler(){return this.unexpectedErrorHandler}onUnexpectedError(e){this.unexpectedErrorHandler(e),this.emit(e)}onUnexpectedExternalError(e){this.unexpectedErrorHandler(e)}};function Qe(e){et(e)||Ze.onUnexpectedError(e)}var $e=`Canceled`;function et(e){return e instanceof tt||e instanceof Error&&e.name===$e&&e.message===$e}var tt=class extends Error{constructor(){super($e),this.name=this.message}};function nt(e){return Error(e?`Illegal argument: ${e}`:`Illegal argument`)}var rt=class e extends Error{constructor(e){super(e),this.name=`CodeExpectedError`}static fromError(t){if(t instanceof e)return t;let n=new e;return n.message=t.message,n.stack=t.stack,n}static isErrorNoTelemetry(e){return e.name===`CodeExpectedError`}},it=class e extends Error{constructor(t){super(t||`An unexpected bug occurred.`),Object.setPrototypeOf(this,e.prototype)}};function at(e,t,n=0,r=e.length){let i=n,a=r;for(;i{function t(e){return e<0}e.isLessThan=t;function n(e){return e<=0}e.isLessThanOrEqual=n;function r(e){return e>0}e.isGreaterThan=r;function i(e){return e===0}e.isNeitherLessOrGreaterThan=i,e.greaterThan=1,e.lessThan=-1,e.neitherLessOrGreaterThan=0})(ct||={});function lt(e,t){return(n,r)=>t(e(n),e(r))}var ut=(e,t)=>e-t,dt=class e{constructor(e){this.iterate=e}forEach(e){this.iterate(t=>(e(t),!0))}toArray(){let e=[];return this.iterate(t=>(e.push(t),!0)),e}filter(t){return new e(e=>this.iterate(n=>!t(n)||e(n)))}map(t){return new e(e=>this.iterate(n=>e(t(n))))}some(e){let t=!1;return this.iterate(n=>(t=e(n),!t)),t}findFirst(e){let t;return this.iterate(n=>e(n)?(t=n,!1):!0),t}findLast(e){let t;return this.iterate(n=>(e(n)&&(t=n),!0)),t}findLastMaxBy(e){let t,n=!0;return this.iterate(r=>((n||ct.isGreaterThan(e(r,t)))&&(n=!1,t=r),!0)),t}};dt.empty=new dt(e=>{});function ft(e,t){let n=Object.create(null);for(let r of e){let e=t(r),i=n[e];i||=n[e]=[],i.push(r)}return n}var pt=class{constructor(){this.map=new Map}add(e,t){let n=this.map.get(e);n||(n=new Set,this.map.set(e,n)),n.add(t)}delete(e,t){let n=this.map.get(e);n&&(n.delete(t),n.size===0&&this.map.delete(e))}forEach(e,t){let n=this.map.get(e);n&&n.forEach(t)}get(e){return this.map.get(e)||new Set}};function mt(e,t){let n=this,r=!1,i;return function(){if(r)return i;if(r=!0,t)try{i=e.apply(n,arguments)}finally{t()}else i=e.apply(n,arguments);return i}}var ht;(e=>{function t(e){return e&&typeof e==`object`&&typeof e[Symbol.iterator]==`function`}e.is=t;let n=Object.freeze([]);function r(){return n}e.empty=r;function*i(e){yield e}e.single=i;function a(e){return t(e)?e:i(e)}e.wrap=a;function o(e){return e||n}e.from=o;function*s(e){for(let t=e.length-1;t>=0;t--)yield e[t]}e.reverse=s;function c(e){return!e||e[Symbol.iterator]().next().done===!0}e.isEmpty=c;function l(e){return e[Symbol.iterator]().next().value}e.first=l;function u(e,t){let n=0;for(let r of e)if(t(r,n++))return!0;return!1}e.some=u;function d(e,t){for(let n of e)if(t(n))return n}e.find=d;function*f(e,t){for(let n of e)t(n)&&(yield n)}e.filter=f;function*p(e,t){let n=0;for(let r of e)yield t(r,n++)}e.map=p;function*m(e,t){let n=0;for(let r of e)yield*t(r,n++)}e.flatMap=m;function*h(...e){for(let t of e)yield*t}e.concat=h;function g(e,t,n){let r=n;for(let n of e)r=t(r,n);return r}e.reduce=g;function*_(e,t,n=e.length){for(t<0&&(t+=e.length),n<0?n+=e.length:n>e.length&&(n=e.length);tt.source!==null&&!this.getRootParent(t,e).isSingleton).flatMap(([e])=>e)}computeLeakingDisposables(e=10,t){let n;if(t)n=t;else{let e=new Map,t=[...this.livingDisposables.values()].filter(t=>t.source!==null&&!this.getRootParent(t,e).isSingleton);if(t.length===0)return;let r=new Set(t.map(e=>e.value));if(n=t.filter(e=>!(e.parent&&r.has(e.parent))),n.length===0)throw Error(`There are cyclic diposable chains!`)}if(!n)return;function r(e){function t(e,t){for(;e.length>0&&t.some(t=>typeof t==`string`?t===e[0]:e[0].match(t));)e.shift()}let n=e.source.split(` -`).map(e=>e.trim().replace(`at `,``)).filter(e=>e!==``);return t(n,[`Error`,/^trackDisposable \(.*\)$/,/^DisposableTracker.trackDisposable \(.*\)$/]),n.reverse()}let i=new pt;for(let e of n){let t=r(e);for(let n=0;n<=t.length;n++)i.add(t.slice(0,n).join(` -`),e)}n.sort(lt(e=>e.idx,ut));let a=``,o=0;for(let t of n.slice(0,e)){o++;let e=r(t),s=[];for(let t=0;tr(e)[t]),e=>e);delete o[e[t]];for(let[e,t]of Object.entries(o))s.unshift(` - stacktraces of ${t.length} other leaks continue with ${e}`);s.unshift(a)}a+=` - - -==================== Leaking disposable ${o}/${n.length}: ${t.value.constructor.name} ==================== -${s.join(` -`)} -============================================================ - -`}return n.length>e&&(a+=` - - -... and ${n.length-e} more leaking disposables - -`),{leaks:n,details:a}}};_t.idx=0;function vt(e){return gt?.trackDisposable(e),e}function yt(e){gt?.markAsDisposed(e)}function bt(e,t){gt?.setParent(e,t)}function xt(e){return gt?.markAsSingleton(e),e}function St(e){if(ht.is(e)){let t=[];for(let n of e)if(n)try{n.dispose()}catch(e){t.push(e)}if(t.length===1)throw t[0];if(t.length>1)throw AggregateError(t,`Encountered errors while disposing of store`);return Array.isArray(e)?[]:e}else if(e)return e.dispose(),e}function Ct(...e){return k(()=>St(e))}function k(e){let t=vt({dispose:mt(()=>{yt(t),e()})});return t}var wt=class e{constructor(){this._toDispose=new Set,this._isDisposed=!1,vt(this)}dispose(){this._isDisposed||(yt(this),this._isDisposed=!0,this.clear())}get isDisposed(){return this._isDisposed}clear(){if(this._toDispose.size!==0)try{St(this._toDispose)}finally{this._toDispose.clear()}}add(t){if(!t)return t;if(t===this)throw Error(`Cannot register a disposable on itself!`);return bt(t,this),this._isDisposed?e.DISABLE_DISPOSED_WARNING||console.warn(Error(`Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!`).stack):this._toDispose.add(t),t}delete(e){if(e){if(e===this)throw Error(`Cannot dispose a disposable on itself!`);this._toDispose.delete(e),e.dispose()}}deleteAndLeak(e){e&&this._toDispose.has(e)&&(this._toDispose.delete(e),bt(e,null))}};wt.DISABLE_DISPOSED_WARNING=!1;var Tt=wt,A=class{constructor(){this._store=new Tt,vt(this),bt(this._store,this)}dispose(){yt(this),this._store.dispose()}_register(e){if(e===this)throw Error(`Cannot register a disposable on itself!`);return this._store.add(e)}};A.None=Object.freeze({dispose(){}});var Et=class{constructor(){this._isDisposed=!1,vt(this)}get value(){return this._isDisposed?void 0:this._value}set value(e){this._isDisposed||e===this._value||(this._value?.dispose(),e&&bt(e,this),this._value=e)}clear(){this.value=void 0}dispose(){this._isDisposed=!0,yt(this),this._value?.dispose(),this._value=void 0}clearAndLeak(){let e=this._value;return this._value=void 0,e&&bt(e,null),e}},Dt=typeof window==`object`?window:globalThis,Ot=class e{constructor(t){this.element=t,this.next=e.Undefined,this.prev=e.Undefined}};Ot.Undefined=new Ot(void 0);var j=Ot,kt=class{constructor(){this._first=j.Undefined,this._last=j.Undefined,this._size=0}get size(){return this._size}isEmpty(){return this._first===j.Undefined}clear(){let e=this._first;for(;e!==j.Undefined;){let t=e.next;e.prev=j.Undefined,e.next=j.Undefined,e=t}this._first=j.Undefined,this._last=j.Undefined,this._size=0}unshift(e){return this._insert(e,!1)}push(e){return this._insert(e,!0)}_insert(e,t){let n=new j(e);if(this._first===j.Undefined)this._first=n,this._last=n;else if(t){let e=this._last;this._last=n,n.prev=e,e.next=n}else{let e=this._first;this._first=n,n.next=e,e.prev=n}this._size+=1;let r=!1;return()=>{r||(r=!0,this._remove(n))}}shift(){if(this._first!==j.Undefined){let e=this._first.element;return this._remove(this._first),e}}pop(){if(this._last!==j.Undefined){let e=this._last.element;return this._remove(this._last),e}}_remove(e){if(e.prev!==j.Undefined&&e.next!==j.Undefined){let t=e.prev;t.next=e.next,e.next.prev=t}else e.prev===j.Undefined&&e.next===j.Undefined?(this._first=j.Undefined,this._last=j.Undefined):e.next===j.Undefined?(this._last=this._last.prev,this._last.next=j.Undefined):e.prev===j.Undefined&&(this._first=this._first.next,this._first.prev=j.Undefined);--this._size}*[Symbol.iterator](){let e=this._first;for(;e!==j.Undefined;)yield e.element,e=e.next}},At=globalThis.performance&&typeof globalThis.performance.now==`function`,jt=class e{static create(t){return new e(t)}constructor(e){this._now=At&&e===!1?Date.now:globalThis.performance.now.bind(globalThis.performance),this._startTime=this._now(),this._stopTime=-1}stop(){this._stopTime=this._now()}reset(){this._startTime=this._now(),this._stopTime=-1}elapsed(){return this._stopTime===-1?this._now()-this._startTime:this._stopTime-this._startTime}},M;(e=>{e.None=()=>A.None;function t(e,t){return d(e,()=>{},0,void 0,!0,void 0,t)}e.defer=t;function n(e){return(t,n=null,r)=>{let i=!1,a;return a=e(e=>{if(!i)return a?a.dispose():i=!0,t.call(n,e)},null,r),i&&a.dispose(),a}}e.once=n;function r(e,t,n){return l((n,r=null,i)=>e(e=>n.call(r,t(e)),null,i),n)}e.map=r;function i(e,t,n){return l((n,r=null,i)=>e(e=>{t(e),n.call(r,e)},null,i),n)}e.forEach=i;function a(e,t,n){return l((n,r=null,i)=>e(e=>t(e)&&n.call(r,e),null,i),n)}e.filter=a;function o(e){return e}e.signal=o;function s(...e){return(t,n=null,r)=>u(Ct(...e.map(e=>e(e=>t.call(n,e)))),r)}e.any=s;function c(e,t,n,i){let a=n;return r(e,e=>(a=t(a,e),a),i)}e.reduce=c;function l(e,t){let n,r=new N({onWillAddFirstListener(){n=e(r.fire,r)},onDidRemoveLastListener(){n?.dispose()}});return t?.add(r),r.event}function u(e,t){return t instanceof Array?t.push(e):t&&t.add(e),e}function d(e,t,n=100,r=!1,i=!1,a,o){let s,c,l,u=0,d,f=new N({leakWarningThreshold:a,onWillAddFirstListener(){s=e(e=>{u++,c=t(c,e),r&&!l&&(f.fire(c),c=void 0),d=()=>{let e=c;c=void 0,l=void 0,(!r||u>1)&&f.fire(e),u=0},typeof n==`number`?(clearTimeout(l),l=setTimeout(d,n)):l===void 0&&(l=0,queueMicrotask(d))})},onWillRemoveListener(){i&&u>0&&d?.()},onDidRemoveLastListener(){d=void 0,s.dispose()}});return o?.add(f),f.event}e.debounce=d;function f(t,n=0,r){return e.debounce(t,(e,t)=>e?(e.push(t),e):[t],n,void 0,!0,void 0,r)}e.accumulate=f;function p(e,t=(e,t)=>e===t,n){let r=!0,i;return a(e,e=>{let n=r||!t(e,i);return r=!1,i=e,n},n)}e.latch=p;function m(t,n,r){return[e.filter(t,n,r),e.filter(t,e=>!n(e),r)]}e.split=m;function h(e,t=!1,n=[],r){let i=n.slice(),a=e(e=>{i?i.push(e):s.fire(e)});r&&r.add(a);let o=()=>{i?.forEach(e=>s.fire(e)),i=null},s=new N({onWillAddFirstListener(){a||(a=e(e=>s.fire(e)),r&&r.add(a))},onDidAddFirstListener(){i&&(t?setTimeout(o):o())},onDidRemoveLastListener(){a&&a.dispose(),a=null}});return r&&r.add(s),s.event}e.buffer=h;function g(e,t){return(n,r,i)=>{let a=t(new v);return e(function(e){let t=a.evaluate(e);t!==_&&n.call(r,t)},void 0,i)}}e.chain=g;let _=Symbol(`HaltChainable`);class v{constructor(){this.steps=[]}map(e){return this.steps.push(e),this}forEach(e){return this.steps.push(t=>(e(t),t)),this}filter(e){return this.steps.push(t=>e(t)?t:_),this}reduce(e,t){let n=t;return this.steps.push(t=>(n=e(n,t),n)),this}latch(e=(e,t)=>e===t){let t=!0,n;return this.steps.push(r=>{let i=t||!e(r,n);return t=!1,n=r,i?r:_}),this}evaluate(e){for(let t of this.steps)if(e=t(e),e===_)break;return e}}function ee(e,t,n=e=>e){let r=(...e)=>i.fire(n(...e)),i=new N({onWillAddFirstListener:()=>e.on(t,r),onDidRemoveLastListener:()=>e.removeListener(t,r)});return i.event}e.fromNodeEventEmitter=ee;function y(e,t,n=e=>e){let r=(...e)=>i.fire(n(...e)),i=new N({onWillAddFirstListener:()=>e.addEventListener(t,r),onDidRemoveLastListener:()=>e.removeEventListener(t,r)});return i.event}e.fromDOMEventEmitter=y;function te(e){return new Promise(t=>n(e)(t))}e.toPromise=te;function b(e){let t=new N;return e.then(e=>{t.fire(e)},()=>{t.fire(void 0)}).finally(()=>{t.dispose()}),t.event}e.fromPromise=b;function ne(e,t){return e(e=>t.fire(e))}e.forward=ne;function re(e,t,n){return t(n),e(e=>t(e))}e.runAndSubscribe=re;class ie{constructor(e,t){this._observable=e,this._counter=0,this._hasChanged=!1;let n={onWillAddFirstListener:()=>{e.addObserver(this)},onDidRemoveLastListener:()=>{e.removeObserver(this)}};this.emitter=new N(n),t&&t.add(this.emitter)}beginUpdate(e){this._counter++}handlePossibleChange(e){}handleChange(e,t){this._hasChanged=!0}endUpdate(e){this._counter--,this._counter===0&&(this._observable.reportChanges(),this._hasChanged&&(this._hasChanged=!1,this.emitter.fire(this._observable.get())))}}function ae(e,t){return new ie(e,t).emitter.event}e.fromObservable=ae;function x(e){return(t,n,r)=>{let i=0,a=!1,o={beginUpdate(){i++},endUpdate(){i--,i===0&&(e.reportChanges(),a&&(a=!1,t.call(n)))},handlePossibleChange(){},handleChange(){a=!0}};e.addObserver(o),e.reportChanges();let s={dispose(){e.removeObserver(o)}};return r instanceof Tt?r.add(s):Array.isArray(r)&&r.push(s),s}}e.fromObservableLight=x})(M||={});var Mt=class e{constructor(t){this.listenerCount=0,this.invocationCount=0,this.elapsedOverall=0,this.durations=[],this.name=`${t}_${e._idPool++}`,e.all.add(this)}start(e){this._stopWatch=new jt,this.listenerCount=e}stop(){if(this._stopWatch){let e=this._stopWatch.elapsed();this.durations.push(e),this.elapsedOverall+=e,this.invocationCount+=1,this._stopWatch=void 0}}};Mt.all=new Set,Mt._idPool=0;var Nt=Mt,Pt=-1,Ft=class e{constructor(t,n,r=(e._idPool++).toString(16).padStart(3,`0`)){this._errorHandler=t,this.threshold=n,this.name=r,this._warnCountdown=0}dispose(){this._stacks?.clear()}check(e,t){let n=this.threshold;if(n<=0||t{let t=this._stacks.get(e.value)||0;this._stacks.set(e.value,t-1)}}getMostFrequentStack(){if(!this._stacks)return;let e,t=0;for(let[n,r]of this._stacks)(!e||t0||this._options?.leakWarningThreshold?new It(e?.onListenerError??Qe,this._options?.leakWarningThreshold??Pt):void 0,this._perfMon=this._options?._profName?new Nt(this._options._profName):void 0,this._deliveryQueue=this._options?.deliveryQueue}dispose(){this._disposed||(this._disposed=!0,this._deliveryQueue?.current===this&&this._deliveryQueue.reset(),this._listeners&&(this._listeners=void 0,this._size=0),this._options?.onDidRemoveLastListener?.(),this._leakageMon?.dispose())}get event(){return this._event??=(e,t,n)=>{if(this._leakageMon&&this._size>this._leakageMon.threshold**2){let e=`[${this._leakageMon.name}] REFUSES to accept new listeners because it exceeded its threshold by far (${this._size} vs ${this._leakageMon.threshold})`;console.warn(e);let t=this._leakageMon.getMostFrequentStack()??[`UNKNOWN stack`,-1],n=new zt(`${e}. HINT: Stack shows most frequent listener (${t[1]}-times)`,t[0]);return(this._options?.onListenerError||Qe)(n),A.None}if(this._disposed)return A.None;t&&(e=e.bind(t));let r=new Vt(e),i;this._leakageMon&&this._size>=Math.ceil(this._leakageMon.threshold*.2)&&(r.stack=Lt.create(),i=this._leakageMon.check(r.stack,this._size+1)),this._listeners?this._listeners instanceof Vt?(this._deliveryQueue??=new Wt,this._listeners=[this._listeners,r]):this._listeners.push(r):(this._options?.onWillAddFirstListener?.(this),this._listeners=r,this._options?.onDidAddFirstListener?.(this)),this._size++;let a=k(()=>{Ut?.unregister(a),i?.(),this._removeListener(r)});if(n instanceof Tt?n.add(a):Array.isArray(n)&&n.push(a),Ut){let e=Error().stack.split(` -`).slice(2,3).join(` -`).trim(),t=/(file:|vscode-file:\/\/vscode-app)?(\/[^:]*:\d+:\d+)/.exec(e);Ut.register(a,t?.[2]??e,a)}return a},this._event}_removeListener(e){if(this._options?.onWillRemoveListener?.(this),!this._listeners)return;if(this._size===1){this._listeners=void 0,this._options?.onDidRemoveLastListener?.(this),this._size=0;return}let t=this._listeners,n=t.indexOf(e);if(n===-1)throw console.log(`disposed?`,this._disposed),console.log(`size?`,this._size),console.log(`arr?`,JSON.stringify(this._listeners)),Error(`Attempted to dispose unknown listener`);this._size--,t[n]=void 0;let r=this._deliveryQueue.current===this;if(this._size*Ht<=t.length){let e=0;for(let n=0;n0}},Wt=class{constructor(){this.i=-1,this.end=0}enqueue(e,t,n){this.i=0,this.end=n,this.current=e,this.value=t}reset(){this.i=this.end,this.current=void 0,this.value=void 0}},Gt=class{constructor(){this.mapWindowIdToZoomLevel=new Map,this._onDidChangeZoomLevel=new N,this.onDidChangeZoomLevel=this._onDidChangeZoomLevel.event,this.mapWindowIdToZoomFactor=new Map,this._onDidChangeFullscreen=new N,this.onDidChangeFullscreen=this._onDidChangeFullscreen.event,this.mapWindowIdToFullScreen=new Map}getZoomLevel(e){return this.mapWindowIdToZoomLevel.get(this.getWindowId(e))??0}setZoomLevel(e,t){if(this.getZoomLevel(t)===e)return;let n=this.getWindowId(t);this.mapWindowIdToZoomLevel.set(n,e),this._onDidChangeZoomLevel.fire(n)}getZoomFactor(e){return this.mapWindowIdToZoomFactor.get(this.getWindowId(e))??1}setZoomFactor(e,t){this.mapWindowIdToZoomFactor.set(this.getWindowId(t),e)}setFullscreen(e,t){if(this.isFullscreen(t)===e)return;let n=this.getWindowId(t);this.mapWindowIdToFullScreen.set(n,e),this._onDidChangeFullscreen.fire(n)}isFullscreen(e){return!!this.mapWindowIdToFullScreen.get(this.getWindowId(e))}getWindowId(e){return e.vscodeWindowId}};Gt.INSTANCE=new Gt;var Kt=Gt;function qt(e,t,n){typeof t==`string`&&(t=e.matchMedia(t)),t.addEventListener(`change`,n)}Kt.INSTANCE.onDidChangeZoomLevel;function Jt(e){return Kt.INSTANCE.getZoomFactor(e)}Kt.INSTANCE.onDidChangeFullscreen;var Yt=typeof navigator==`object`?navigator.userAgent:``,Xt=Yt.indexOf(`Firefox`)>=0,Zt=Yt.indexOf(`AppleWebKit`)>=0,Qt=Yt.indexOf(`Chrome`)>=0,$t=!Qt&&Yt.indexOf(`Safari`)>=0;Yt.indexOf(`Electron/`),Yt.indexOf(`Android`);var en=!1;if(typeof Dt.matchMedia==`function`){let e=Dt.matchMedia(`(display-mode: standalone) or (display-mode: window-controls-overlay)`),t=Dt.matchMedia(`(display-mode: fullscreen)`);en=e.matches,qt(Dt,e,({matches:e})=>{en&&t.matches||(en=e)})}function tn(){return en}var nn=`en`,rn=!1,an=!1,on=!1,sn=!1,cn=!1,ln=nn,un,dn=globalThis,fn;typeof dn.vscode<`u`&&typeof dn.vscode.process<`u`?fn=dn.vscode.process:typeof process<`u`&&typeof process?.versions?.node==`string`&&(fn=process);var pn=typeof fn?.versions?.electron==`string`&&fn?.type===`renderer`;if(typeof fn==`object`){rn=fn.platform===`win32`,an=fn.platform===`darwin`,on=fn.platform===`linux`,on&&fn.env.SNAP&&fn.env.SNAP_REVISION,fn.env.CI||fn.env.BUILD_ARTIFACTSTAGINGDIRECTORY,ln=nn;let e=fn.env.VSCODE_NLS_CONFIG;if(e)try{let t=JSON.parse(e);t.userLocale,t.osLocale,ln=t.resolvedLanguage||nn,t.languagePack?.translationsConfigFile}catch{}sn=!0}else typeof navigator==`object`&&!pn?(un=navigator.userAgent,rn=un.indexOf(`Windows`)>=0,an=un.indexOf(`Macintosh`)>=0,(un.indexOf(`Macintosh`)>=0||un.indexOf(`iPad`)>=0||un.indexOf(`iPhone`)>=0)&&navigator.maxTouchPoints&&navigator.maxTouchPoints,on=un.indexOf(`Linux`)>=0,un?.indexOf(`Mobi`),cn=!0,ln=globalThis._VSCODE_NLS_LANGUAGE||nn,navigator.language.toLowerCase()):console.error(`Unable to resolve platform.`);var mn=rn,hn=an,gn=on,_n=sn;cn&&typeof dn.importScripts==`function`&&dn.origin;var vn=un,yn=ln,bn;(e=>{function t(){return yn}e.value=t;function n(){return yn.length===2?yn===`en`:yn.length>=3&&yn[0]===`e`&&yn[1]===`n`&&yn[2]===`-`}e.isDefaultVariant=n;function r(){return yn===`en`}e.isDefault=r})(bn||={});var xn=typeof dn.postMessage==`function`&&!dn.importScripts;(()=>{if(xn){let e=[];dn.addEventListener(`message`,t=>{if(t.data&&t.data.vscodeScheduleAsyncWork)for(let n=0,r=e.length;n{let r=++t;e.push({id:r,callback:n}),dn.postMessage({vscodeScheduleAsyncWork:r},`*`)}}return e=>setTimeout(e)})();var Sn=!!(vn&&vn.indexOf(`Chrome`)>=0);vn&&vn.indexOf(`Firefox`),!Sn&&vn&&vn.indexOf(`Safari`),vn&&vn.indexOf(`Edg/`),vn&&vn.indexOf(`Android`);var Cn=typeof navigator==`object`?navigator:{};_n||document.queryCommandSupported&&document.queryCommandSupported(`copy`)||Cn&&Cn.clipboard&&Cn.clipboard.writeText,_n||Cn&&Cn.clipboard&&Cn.clipboard.readText,_n||tn()||Cn.keyboard,`ontouchstart`in Dt||Cn.maxTouchPoints,Dt.PointerEvent&&(`ontouchstart`in Dt||navigator.maxTouchPoints);var wn=class{constructor(){this._keyCodeToStr=[],this._strToKeyCode=Object.create(null)}define(e,t){this._keyCodeToStr[e]=t,this._strToKeyCode[t.toLowerCase()]=e}keyCodeToStr(e){return this._keyCodeToStr[e]}strToKeyCode(e){return this._strToKeyCode[e.toLowerCase()]||0}},Tn=new wn,En=new wn,Dn=new wn,On=Array(230),kn;(e=>{function t(e){return Tn.keyCodeToStr(e)}e.toString=t;function n(e){return Tn.strToKeyCode(e)}e.fromString=n;function r(e){return En.keyCodeToStr(e)}e.toUserSettingsUS=r;function i(e){return Dn.keyCodeToStr(e)}e.toUserSettingsGeneral=i;function a(e){return En.strToKeyCode(e)||Dn.strToKeyCode(e)}e.fromUserSettings=a;function o(e){if(e>=98&&e<=113)return null;switch(e){case 16:return`Up`;case 18:return`Down`;case 15:return`Left`;case 17:return`Right`}return Tn.keyCodeToStr(e)}e.toElectronAccelerator=o})(kn||={});var An=class e{constructor(e,t,n,r,i){this.ctrlKey=e,this.shiftKey=t,this.altKey=n,this.metaKey=r,this.keyCode=i}equals(t){return t instanceof e&&this.ctrlKey===t.ctrlKey&&this.shiftKey===t.shiftKey&&this.altKey===t.altKey&&this.metaKey===t.metaKey&&this.keyCode===t.keyCode}getHashCode(){return`K${this.ctrlKey?`1`:`0`}${this.shiftKey?`1`:`0`}${this.altKey?`1`:`0`}${this.metaKey?`1`:`0`}${this.keyCode}`}isModifierKey(){return this.keyCode===0||this.keyCode===5||this.keyCode===57||this.keyCode===6||this.keyCode===4}toKeybinding(){return new jn([this])}isDuplicateModifierCase(){return this.ctrlKey&&this.keyCode===5||this.shiftKey&&this.keyCode===4||this.altKey&&this.keyCode===6||this.metaKey&&this.keyCode===57}},jn=class{constructor(e){if(e.length===0)throw nt(`chords`);this.chords=e}getHashCode(){let e=``;for(let t=0,n=this.chords.length;t{function t(t){return t===e.None||t===e.Cancelled||t instanceof Gn?!0:!t||typeof t!=`object`?!1:typeof t.isCancellationRequested==`boolean`&&typeof t.onCancellationRequested==`function`}e.isCancellationToken=t,e.None=Object.freeze({isCancellationRequested:!1,onCancellationRequested:M.None}),e.Cancelled=Object.freeze({isCancellationRequested:!0,onCancellationRequested:Un})})(Wn||={});var Gn=class{constructor(){this._isCancelled=!1,this._emitter=null}cancel(){this._isCancelled||(this._isCancelled=!0,this._emitter&&(this._emitter.fire(void 0),this.dispose()))}get isCancellationRequested(){return this._isCancelled}get onCancellationRequested(){return this._isCancelled?Un:(this._emitter||=new N,this._emitter.event)}dispose(){this._emitter&&=(this._emitter.dispose(),null)}},Kn=class{constructor(e,t){this._isDisposed=!1,this._token=-1,typeof e==`function`&&typeof t==`number`&&this.setIfNotSet(e,t)}dispose(){this.cancel(),this._isDisposed=!0}cancel(){this._token!==-1&&(clearTimeout(this._token),this._token=-1)}cancelAndSet(e,t){if(this._isDisposed)throw new it(`Calling 'cancelAndSet' on a disposed TimeoutTimer`);this.cancel(),this._token=setTimeout(()=>{this._token=-1,e()},t)}setIfNotSet(e,t){if(this._isDisposed)throw new it(`Calling 'setIfNotSet' on a disposed TimeoutTimer`);this._token===-1&&(this._token=setTimeout(()=>{this._token=-1,e()},t))}},qn=class{constructor(){this.disposable=void 0,this.isDisposed=!1}cancel(){this.disposable?.dispose(),this.disposable=void 0}cancelAndSet(e,t,n=globalThis){if(this.isDisposed)throw new it(`Calling 'cancelAndSet' on a disposed IntervalTimer`);this.cancel();let r=n.setInterval(()=>{e()},t);this.disposable=k(()=>{n.clearInterval(r),this.disposable=void 0})}dispose(){this.cancel(),this.isDisposed=!0}};(function(){typeof globalThis.requestIdleCallback!=`function`||globalThis.cancelIdleCallback})();var Jn;(e=>{async function t(e){let t,n=await Promise.all(e.map(e=>e.then(e=>e,e=>{t||=e})));if(typeof t<`u`)throw t;return n}e.settled=t;function n(e){return new Promise(async(t,n)=>{try{await e(t,n)}catch(e){n(e)}})}e.withAsyncBody=n})(Jn||={});var Yn=class e{static fromArray(t){return new e(e=>{e.emitMany(t)})}static fromPromise(t){return new e(async e=>{e.emitMany(await t)})}static fromPromises(t){return new e(async e=>{await Promise.all(t.map(async t=>e.emitOne(await t)))})}static merge(t){return new e(async e=>{await Promise.all(t.map(async t=>{for await(let n of t)e.emitOne(n)}))})}constructor(e,t){this._state=0,this._results=[],this._error=null,this._onReturn=t,this._onStateChanged=new N,queueMicrotask(async()=>{let t={emitOne:e=>this.emitOne(e),emitMany:e=>this.emitMany(e),reject:e=>this.reject(e)};try{await Promise.resolve(e(t)),this.resolve()}catch(e){this.reject(e)}finally{t.emitOne=void 0,t.emitMany=void 0,t.reject=void 0}})}[Symbol.asyncIterator](){let e=0;return{next:async()=>{do{if(this._state===2)throw this._error;if(e(this._onReturn?.(),{done:!0,value:void 0})}}static map(t,n){return new e(async e=>{for await(let r of t)e.emitOne(n(r))})}map(t){return e.map(this,t)}static filter(t,n){return new e(async e=>{for await(let r of t)n(r)&&e.emitOne(r)})}filter(t){return e.filter(this,t)}static coalesce(t){return e.filter(t,e=>!!e)}coalesce(){return e.coalesce(this)}static async toPromise(e){let t=[];for await(let n of e)t.push(n);return t}toPromise(){return e.toPromise(this)}emitOne(e){this._state===0&&(this._results.push(e),this._onStateChanged.fire())}emitMany(e){this._state===0&&(this._results=this._results.concat(e),this._onStateChanged.fire())}resolve(){this._state===0&&(this._state=1,this._onStateChanged.fire())}reject(e){this._state===0&&(this._state=2,this._error=e,this._onStateChanged.fire())}};Yn.EMPTY=Yn.fromArray([]);function Xn(e){return 55296<=e&&e<=56319}function Zn(e){return 56320<=e&&e<=57343}function Qn(e,t){return(e-55296<<10)+(t-56320)+65536}function $n(e){return er(e,0)}function er(e,t){switch(typeof e){case`object`:return e===null?tr(349,t):Array.isArray(e)?ir(e,t):ar(e,t);case`string`:return rr(e,t);case`boolean`:return nr(e,t);case`number`:return tr(e,t);case`undefined`:return tr(937,t);default:return tr(617,t)}}function tr(e,t){return(t<<5)-t+e|0}function nr(e,t){return tr(e?433:863,t)}function rr(e,t){t=tr(149417,t);for(let n=0,r=e.length;ner(t,e),t)}function ar(e,t){return t=tr(181387,t),Object.keys(e).sort().reduce((t,n)=>(t=rr(n,t),er(e[n],t)),t)}function or(e,t,n=32){let r=n-t,i=~((1<>>r)>>>0}function sr(e,t=0,n=e.byteLength,r=0){for(let i=0;ie.toString(16).padStart(2,`0`)).join(``):cr((e>>>0).toString(16),t/4)}var ur=class e{constructor(){this._h0=1732584193,this._h1=4023233417,this._h2=2562383102,this._h3=271733878,this._h4=3285377520,this._buff=new Uint8Array(67),this._buffDV=new DataView(this._buff.buffer),this._buffLen=0,this._totalLen=0,this._leftoverHighSurrogate=0,this._finished=!1}update(e){let t=e.length;if(t===0)return;let n=this._buff,r=this._buffLen,i=this._leftoverHighSurrogate,a,o;for(i===0?(a=e.charCodeAt(0),o=0):(a=i,o=-1,i=0);;){let s=a;if(Xn(a))if(o+1>>6,e[t++]=128|(n&63)>>>0):n<65536?(e[t++]=224|(n&61440)>>>12,e[t++]=128|(n&4032)>>>6,e[t++]=128|(n&63)>>>0):(e[t++]=240|(n&1835008)>>>18,e[t++]=128|(n&258048)>>>12,e[t++]=128|(n&4032)>>>6,e[t++]=128|(n&63)>>>0),t>=64&&(this._step(),t-=64,this._totalLen+=64,e[0]=e[64],e[1]=e[65],e[2]=e[66]),t}digest(){return this._finished||(this._finished=!0,this._leftoverHighSurrogate&&(this._leftoverHighSurrogate=0,this._buffLen=this._push(this._buff,this._buffLen,65533)),this._totalLen+=this._buffLen,this._wrapUp()),lr(this._h0)+lr(this._h1)+lr(this._h2)+lr(this._h3)+lr(this._h4)}_wrapUp(){this._buff[this._buffLen++]=128,sr(this._buff,this._buffLen),this._buffLen>56&&(this._step(),sr(this._buff));let e=8*this._totalLen;this._buffDV.setUint32(56,Math.floor(e/4294967296),!1),this._buffDV.setUint32(60,e%4294967296,!1),this._step()}_step(){let t=e._bigBlock32,n=this._buffDV;for(let e=0;e<64;e+=4)t.setUint32(e,n.getUint32(e,!1),!1);for(let e=64;e<320;e+=4)t.setUint32(e,or(t.getUint32(e-12,!1)^t.getUint32(e-32,!1)^t.getUint32(e-56,!1)^t.getUint32(e-64,!1),1),!1);let r=this._h0,i=this._h1,a=this._h2,o=this._h3,s=this._h4,c,l,u;for(let e=0;e<80;e++)e<20?(c=i&a|~i&o,l=1518500249):e<40?(c=i^a^o,l=1859775393):e<60?(c=i&a|i&o|a&o,l=2400959708):(c=i^a^o,l=3395469782),u=or(r,5)+c+s+l+t.getUint32(e*4,!1)&4294967295,s=o,o=a,a=or(i,30),i=r,r=u;this._h0=this._h0+r&4294967295,this._h1=this._h1+i&4294967295,this._h2=this._h2+a&4294967295,this._h3=this._h3+o&4294967295,this._h4=this._h4+s&4294967295}};ur._bigBlock32=new DataView(new ArrayBuffer(320));var{registerWindow:dr,getWindow:fr,getDocument:pr,getWindows:mr,getWindowsCount:hr,getWindowId:gr,getWindowById:_r,hasWindow:vr,onDidRegisterWindow:yr,onWillUnregisterWindow:br,onDidUnregisterWindow:xr}=function(){let e=new Map,t={window:Dt,disposables:new Tt};e.set(Dt.vscodeWindowId,t);let n=new N,r=new N,i=new N;function a(n,r){return(typeof n==`number`?e.get(n):void 0)??(r?t:void 0)}return{onDidRegisterWindow:n.event,onWillUnregisterWindow:i.event,onDidUnregisterWindow:r.event,registerWindow(t){if(e.has(t.vscodeWindowId))return A.None;let a=new Tt,o={window:t,disposables:a.add(new Tt)};return e.set(t.vscodeWindowId,o),a.add(k(()=>{e.delete(t.vscodeWindowId),r.fire(t)})),a.add(P(t,F.BEFORE_UNLOAD,()=>{i.fire(t)})),n.fire(o),a},getWindows(){return e.values()},getWindowsCount(){return e.size},getWindowId(e){return e.vscodeWindowId},hasWindow(t){return e.has(t)},getWindowById:a,getWindow(e){let t=e;if(t?.ownerDocument?.defaultView)return t.ownerDocument.defaultView.window;let n=e;return n?.view?n.view.window:Dt},getDocument(e){return fr(e).document}}}(),Sr=class{constructor(e,t,n,r){this._node=e,this._type=t,this._handler=n,this._options=r||!1,this._node.addEventListener(this._type,this._handler,this._options)}dispose(){this._handler&&=(this._node.removeEventListener(this._type,this._handler,this._options),this._node=null,null)}};function P(e,t,n,r){return new Sr(e,t,n,r)}function Cr(e,t){return function(n){return t(new Vn(e,n))}}function wr(e){return function(t){return e(new Ln(t))}}var Tr=function(e,t,n,r){let i=n;return t===`click`||t===`mousedown`||t===`contextmenu`?i=Cr(fr(e),n):(t===`keydown`||t===`keypress`||t===`keyup`)&&(i=wr(n)),P(e,t,i,r)},Er,Dr=class extends qn{constructor(e){super(),this.defaultTarget=e&&fr(e)}cancelAndSet(e,t,n){return super.cancelAndSet(e,t,n??this.defaultTarget)}},Or=class{constructor(e,t=0){this._runner=e,this.priority=t,this._canceled=!1}dispose(){this._canceled=!0}execute(){if(!this._canceled)try{this._runner()}catch(e){Qe(e)}}static sort(e,t){return t.priority-e.priority}};(function(){let e=new Map,t=new Map,n=new Map,r=new Map,i=i=>{n.set(i,!1);let a=e.get(i)??[];for(t.set(i,a),e.set(i,[]),r.set(i,!0);a.length>0;)a.sort(Or.sort),a.shift().execute();r.set(i,!1)};Er=(t,r,a=0)=>{let o=gr(t),s=new Or(r,a),c=e.get(o);return c||(c=[],e.set(o,c)),c.push(s),n.get(o)||(n.set(o,!0),t.requestAnimationFrame(()=>i(o))),s}})();var kr=class e{constructor(e,t){this.width=e,this.height=t}with(t=this.width,n=this.height){return t!==this.width||n!==this.height?new e(t,n):this}static is(e){return typeof e==`object`&&typeof e.height==`number`&&typeof e.width==`number`}static lift(t){return t instanceof e?t:new e(t.width,t.height)}static equals(e,t){return e===t?!0:!e||!t?!1:e.width===t.width&&e.height===t.height}};kr.None=new kr(0,0);function Ar(e){let t=e.getBoundingClientRect(),n=fr(e);return{left:t.left+n.scrollX,top:t.top+n.scrollY,width:t.width,height:t.height}}new class{constructor(){this.mutationObservers=new Map}observe(e,t,n){let r=this.mutationObservers.get(e);r||(r=new Map,this.mutationObservers.set(e,r));let i=$n(n),a=r.get(i);if(a)a.users+=1;else{let o=new N,s=new MutationObserver(e=>o.fire(e));s.observe(e,n);let c=a={users:1,observer:s,onDidMutate:o.event};t.add(k(()=>{--c.users,c.users===0&&(o.dispose(),s.disconnect(),r?.delete(i),r?.size===0&&this.mutationObservers.delete(e))})),r.set(i,a)}return a.onDidMutate}};var F={CLICK:`click`,AUXCLICK:`auxclick`,DBLCLICK:`dblclick`,MOUSE_UP:`mouseup`,MOUSE_DOWN:`mousedown`,MOUSE_OVER:`mouseover`,MOUSE_MOVE:`mousemove`,MOUSE_OUT:`mouseout`,MOUSE_ENTER:`mouseenter`,MOUSE_LEAVE:`mouseleave`,MOUSE_WHEEL:`wheel`,POINTER_UP:`pointerup`,POINTER_DOWN:`pointerdown`,POINTER_MOVE:`pointermove`,POINTER_LEAVE:`pointerleave`,CONTEXT_MENU:`contextmenu`,WHEEL:`wheel`,KEY_DOWN:`keydown`,KEY_PRESS:`keypress`,KEY_UP:`keyup`,LOAD:`load`,BEFORE_UNLOAD:`beforeunload`,UNLOAD:`unload`,PAGE_SHOW:`pageshow`,PAGE_HIDE:`pagehide`,PASTE:`paste`,ABORT:`abort`,ERROR:`error`,RESIZE:`resize`,SCROLL:`scroll`,FULLSCREEN_CHANGE:`fullscreenchange`,WK_FULLSCREEN_CHANGE:`webkitfullscreenchange`,SELECT:`select`,CHANGE:`change`,SUBMIT:`submit`,RESET:`reset`,FOCUS:`focus`,FOCUS_IN:`focusin`,FOCUS_OUT:`focusout`,BLUR:`blur`,INPUT:`input`,STORAGE:`storage`,DRAG_START:`dragstart`,DRAG:`drag`,DRAG_ENTER:`dragenter`,DRAG_LEAVE:`dragleave`,DRAG_OVER:`dragover`,DROP:`drop`,DRAG_END:`dragend`,ANIMATION_START:Zt?`webkitAnimationStart`:`animationstart`,ANIMATION_END:Zt?`webkitAnimationEnd`:`animationend`,ANIMATION_ITERATION:Zt?`webkitAnimationIteration`:`animationiteration`},jr=/([\w\-]+)?(#([\w\-]+))?((\.([\w\-]+))*)/;function Mr(e,t,n,...r){let i=jr.exec(t);if(!i)throw Error(`Bad use of emmet`);let a=i[1]||`div`,o;return o=e===`http://www.w3.org/1999/xhtml`?document.createElement(a):document.createElementNS(e,a),i[3]&&(o.id=i[3]),i[4]&&(o.className=i[4].replace(/\./g,` `).trim()),n&&Object.entries(n).forEach(([e,t])=>{typeof t>`u`||(/^on\w+$/.test(e)?o[e]=t:e===`selected`?t&&o.setAttribute(e,`true`):o.setAttribute(e,t))}),o.append(...r),o}function Nr(e,t,...n){return Mr(`http://www.w3.org/1999/xhtml`,e,t,...n)}Nr.SVG=function(e,t,...n){return Mr(`http://www.w3.org/2000/svg`,e,t,...n)};var Pr=class{constructor(e){this.domNode=e,this._maxWidth=``,this._width=``,this._height=``,this._top=``,this._left=``,this._bottom=``,this._right=``,this._paddingTop=``,this._paddingLeft=``,this._paddingBottom=``,this._paddingRight=``,this._fontFamily=``,this._fontWeight=``,this._fontSize=``,this._fontStyle=``,this._fontFeatureSettings=``,this._fontVariationSettings=``,this._textDecoration=``,this._lineHeight=``,this._letterSpacing=``,this._className=``,this._display=``,this._position=``,this._visibility=``,this._color=``,this._backgroundColor=``,this._layerHint=!1,this._contain=`none`,this._boxShadow=``}setMaxWidth(e){let t=I(e);this._maxWidth!==t&&(this._maxWidth=t,this.domNode.style.maxWidth=this._maxWidth)}setWidth(e){let t=I(e);this._width!==t&&(this._width=t,this.domNode.style.width=this._width)}setHeight(e){let t=I(e);this._height!==t&&(this._height=t,this.domNode.style.height=this._height)}setTop(e){let t=I(e);this._top!==t&&(this._top=t,this.domNode.style.top=this._top)}setLeft(e){let t=I(e);this._left!==t&&(this._left=t,this.domNode.style.left=this._left)}setBottom(e){let t=I(e);this._bottom!==t&&(this._bottom=t,this.domNode.style.bottom=this._bottom)}setRight(e){let t=I(e);this._right!==t&&(this._right=t,this.domNode.style.right=this._right)}setPaddingTop(e){let t=I(e);this._paddingTop!==t&&(this._paddingTop=t,this.domNode.style.paddingTop=this._paddingTop)}setPaddingLeft(e){let t=I(e);this._paddingLeft!==t&&(this._paddingLeft=t,this.domNode.style.paddingLeft=this._paddingLeft)}setPaddingBottom(e){let t=I(e);this._paddingBottom!==t&&(this._paddingBottom=t,this.domNode.style.paddingBottom=this._paddingBottom)}setPaddingRight(e){let t=I(e);this._paddingRight!==t&&(this._paddingRight=t,this.domNode.style.paddingRight=this._paddingRight)}setFontFamily(e){this._fontFamily!==e&&(this._fontFamily=e,this.domNode.style.fontFamily=this._fontFamily)}setFontWeight(e){this._fontWeight!==e&&(this._fontWeight=e,this.domNode.style.fontWeight=this._fontWeight)}setFontSize(e){let t=I(e);this._fontSize!==t&&(this._fontSize=t,this.domNode.style.fontSize=this._fontSize)}setFontStyle(e){this._fontStyle!==e&&(this._fontStyle=e,this.domNode.style.fontStyle=this._fontStyle)}setFontFeatureSettings(e){this._fontFeatureSettings!==e&&(this._fontFeatureSettings=e,this.domNode.style.fontFeatureSettings=this._fontFeatureSettings)}setFontVariationSettings(e){this._fontVariationSettings!==e&&(this._fontVariationSettings=e,this.domNode.style.fontVariationSettings=this._fontVariationSettings)}setTextDecoration(e){this._textDecoration!==e&&(this._textDecoration=e,this.domNode.style.textDecoration=this._textDecoration)}setLineHeight(e){let t=I(e);this._lineHeight!==t&&(this._lineHeight=t,this.domNode.style.lineHeight=this._lineHeight)}setLetterSpacing(e){let t=I(e);this._letterSpacing!==t&&(this._letterSpacing=t,this.domNode.style.letterSpacing=this._letterSpacing)}setClassName(e){this._className!==e&&(this._className=e,this.domNode.className=this._className)}toggleClassName(e,t){this.domNode.classList.toggle(e,t),this._className=this.domNode.className}setDisplay(e){this._display!==e&&(this._display=e,this.domNode.style.display=this._display)}setPosition(e){this._position!==e&&(this._position=e,this.domNode.style.position=this._position)}setVisibility(e){this._visibility!==e&&(this._visibility=e,this.domNode.style.visibility=this._visibility)}setColor(e){this._color!==e&&(this._color=e,this.domNode.style.color=this._color)}setBackgroundColor(e){this._backgroundColor!==e&&(this._backgroundColor=e,this.domNode.style.backgroundColor=this._backgroundColor)}setLayerHinting(e){this._layerHint!==e&&(this._layerHint=e,this.domNode.style.transform=this._layerHint?`translate3d(0px, 0px, 0px)`:``)}setBoxShadow(e){this._boxShadow!==e&&(this._boxShadow=e,this.domNode.style.boxShadow=e)}setContain(e){this._contain!==e&&(this._contain=e,this.domNode.style.contain=this._contain)}setAttribute(e,t){this.domNode.setAttribute(e,t)}removeAttribute(e){this.domNode.removeAttribute(e)}appendChild(e){this.domNode.appendChild(e.domNode)}removeChild(e){this.domNode.removeChild(e.domNode)}};function I(e){return typeof e==`number`?`${e}px`:e}function Fr(e){return new Pr(e)}var Ir=class{constructor(){this._hooks=new Tt,this._pointerMoveCallback=null,this._onStopCallback=null}dispose(){this.stopMonitoring(!1),this._hooks.dispose()}stopMonitoring(e,t){if(!this.isMonitoring())return;this._hooks.clear(),this._pointerMoveCallback=null;let n=this._onStopCallback;this._onStopCallback=null,e&&n&&n(t)}isMonitoring(){return!!this._pointerMoveCallback}startMonitoring(e,t,n,r,i){this.isMonitoring()&&this.stopMonitoring(!1),this._pointerMoveCallback=r,this._onStopCallback=i;let a=e;try{e.setPointerCapture(t),this._hooks.add(k(()=>{try{e.releasePointerCapture(t)}catch{}}))}catch{a=fr(e)}this._hooks.add(P(a,F.POINTER_MOVE,e=>{if(e.buttons!==n){this.stopMonitoring(!0);return}e.preventDefault(),this._pointerMoveCallback(e)})),this._hooks.add(P(a,F.POINTER_UP,e=>this.stopMonitoring(!0)))}};function Lr(e,t,n){let r=null,i=null;if(typeof n.value==`function`?(r=`value`,i=n.value,i.length!==0&&console.warn(`Memoize should only be used in functions with zero parameters`)):typeof n.get==`function`&&(r=`get`,i=n.get),!i)throw Error(`not supported`);let a=`$memoize$${t}`;n[r]=function(...e){return this.hasOwnProperty(a)||Object.defineProperty(this,a,{configurable:!1,enumerable:!1,writable:!1,value:i.apply(this,e)}),this[a]}}var Rr;(e=>(e.Tap=`-xterm-gesturetap`,e.Change=`-xterm-gesturechange`,e.Start=`-xterm-gesturestart`,e.End=`-xterm-gesturesend`,e.Contextmenu=`-xterm-gesturecontextmenu`))(Rr||={});var zr=class e extends A{constructor(){super(),this.dispatched=!1,this.targets=new kt,this.ignoreTargets=new kt,this.activeTouches={},this.handle=null,this._lastSetTapCountTime=0,this._register(M.runAndSubscribe(yr,({window:e,disposables:t})=>{t.add(P(e.document,`touchstart`,e=>this.onTouchStart(e),{passive:!1})),t.add(P(e.document,`touchend`,t=>this.onTouchEnd(e,t))),t.add(P(e.document,`touchmove`,e=>this.onTouchMove(e),{passive:!1}))},{window:Dt,disposables:this._store}))}static addTarget(t){return e.isTouchDevice()?(e.INSTANCE||=xt(new e),k(e.INSTANCE.targets.push(t))):A.None}static ignoreTarget(t){return e.isTouchDevice()?(e.INSTANCE||=xt(new e),k(e.INSTANCE.ignoreTargets.push(t))):A.None}static isTouchDevice(){return`ontouchstart`in Dt||navigator.maxTouchPoints>0}dispose(){this.handle&&=(this.handle.dispose(),null),super.dispose()}onTouchStart(e){let t=Date.now();this.handle&&=(this.handle.dispose(),null);for(let n=0,r=e.targetTouches.length;n=e.HOLD_DELAY&&Math.abs(s.initialPageX-st(s.rollingPageX))<30&&Math.abs(s.initialPageY-st(s.rollingPageY))<30){let e=this.newGestureEvent(Rr.Contextmenu,s.initialTarget);e.pageX=st(s.rollingPageX),e.pageY=st(s.rollingPageY),this.dispatchEvent(e)}else if(i===1){let e=st(s.rollingPageX),n=st(s.rollingPageY),i=st(s.rollingTimestamps)-s.rollingTimestamps[0],a=e-s.rollingPageX[0],o=n-s.rollingPageY[0],c=[...this.targets].filter(e=>s.initialTarget instanceof Node&&e.contains(s.initialTarget));this.inertia(t,c,r,Math.abs(a)/i,a>0?1:-1,e,Math.abs(o)/i,o>0?1:-1,n)}this.dispatchEvent(this.newGestureEvent(Rr.End,s.initialTarget)),delete this.activeTouches[o.identifier]}this.dispatched&&=(n.preventDefault(),n.stopPropagation(),!1)}newGestureEvent(e,t){let n=document.createEvent(`CustomEvent`);return n.initEvent(e,!1,!0),n.initialTarget=t,n.tapCount=0,n}dispatchEvent(t){if(t.type===Rr.Tap){let n=new Date().getTime(),r=0;r=n-this._lastSetTapCountTime>e.CLEAR_TAP_COUNT_TIME?1:2,this._lastSetTapCountTime=n,t.tapCount=r}else(t.type===Rr.Change||t.type===Rr.Contextmenu)&&(this._lastSetTapCountTime=0);if(t.initialTarget instanceof Node){for(let e of this.ignoreTargets)if(e.contains(t.initialTarget))return;let e=[];for(let n of this.targets)if(n.contains(t.initialTarget)){let r=0,i=t.initialTarget;for(;i&&i!==n;)r++,i=i.parentElement;e.push([r,n])}e.sort((e,t)=>e[0]-t[0]);for(let[n,r]of e)r.dispatchEvent(t),this.dispatched=!0}}inertia(t,n,r,i,a,o,s,c,l){this.handle=Er(t,()=>{let u=Date.now(),d=u-r,f=0,p=0,m=!0;i+=e.SCROLL_FRICTION*d,s+=e.SCROLL_FRICTION*d,i>0&&(m=!1,f=a*i*d),s>0&&(m=!1,p=c*s*d);let h=this.newGestureEvent(Rr.Change);h.translationX=f,h.translationY=p,n.forEach(e=>e.dispatchEvent(h)),m||this.inertia(t,n,u,i,a,o+f,s,c,l+p)})}onTouchMove(e){let t=Date.now();for(let n=0,r=e.changedTouches.length;n3&&(i.rollingPageX.shift(),i.rollingPageY.shift(),i.rollingTimestamps.shift()),i.rollingPageX.push(r.pageX),i.rollingPageY.push(r.pageY),i.rollingTimestamps.push(t)}this.dispatched&&=(e.preventDefault(),e.stopPropagation(),!1)}};zr.SCROLL_FRICTION=-.005,zr.HOLD_DELAY=700,zr.CLEAR_TAP_COUNT_TIME=400,C([Lr],zr,`isTouchDevice`,1);var Br=zr,Vr=class extends A{onclick(e,t){this._register(P(e,F.CLICK,n=>t(new Vn(fr(e),n))))}onmousedown(e,t){this._register(P(e,F.MOUSE_DOWN,n=>t(new Vn(fr(e),n))))}onmouseover(e,t){this._register(P(e,F.MOUSE_OVER,n=>t(new Vn(fr(e),n))))}onmouseleave(e,t){this._register(P(e,F.MOUSE_LEAVE,n=>t(new Vn(fr(e),n))))}onkeydown(e,t){this._register(P(e,F.KEY_DOWN,e=>t(new Ln(e))))}onkeyup(e,t){this._register(P(e,F.KEY_UP,e=>t(new Ln(e))))}oninput(e,t){this._register(P(e,F.INPUT,t))}onblur(e,t){this._register(P(e,F.BLUR,t))}onfocus(e,t){this._register(P(e,F.FOCUS,t))}onchange(e,t){this._register(P(e,F.CHANGE,t))}ignoreGesture(e){return Br.ignoreTarget(e)}},Hr=11,Ur=class extends Vr{constructor(e){super(),this._onActivate=e.onActivate,this.bgDomNode=document.createElement(`div`),this.bgDomNode.className=`arrow-background`,this.bgDomNode.style.position=`absolute`,this.bgDomNode.style.width=e.bgWidth+`px`,this.bgDomNode.style.height=e.bgHeight+`px`,typeof e.top<`u`&&(this.bgDomNode.style.top=`0px`),typeof e.left<`u`&&(this.bgDomNode.style.left=`0px`),typeof e.bottom<`u`&&(this.bgDomNode.style.bottom=`0px`),typeof e.right<`u`&&(this.bgDomNode.style.right=`0px`),this.domNode=document.createElement(`div`),this.domNode.className=e.className,this.domNode.style.position=`absolute`,this.domNode.style.width=Hr+`px`,this.domNode.style.height=Hr+`px`,typeof e.top<`u`&&(this.domNode.style.top=e.top+`px`),typeof e.left<`u`&&(this.domNode.style.left=e.left+`px`),typeof e.bottom<`u`&&(this.domNode.style.bottom=e.bottom+`px`),typeof e.right<`u`&&(this.domNode.style.right=e.right+`px`),this._pointerMoveMonitor=this._register(new Ir),this._register(Tr(this.bgDomNode,F.POINTER_DOWN,e=>this._arrowPointerDown(e))),this._register(Tr(this.domNode,F.POINTER_DOWN,e=>this._arrowPointerDown(e))),this._pointerdownRepeatTimer=this._register(new Dr),this._pointerdownScheduleRepeatTimer=this._register(new Kn)}_arrowPointerDown(e){!e.target||!(e.target instanceof Element)||(this._onActivate(),this._pointerdownRepeatTimer.cancel(),this._pointerdownScheduleRepeatTimer.cancelAndSet(()=>{this._pointerdownRepeatTimer.cancelAndSet(()=>this._onActivate(),1e3/24,fr(e))},200),this._pointerMoveMonitor.startMonitoring(e.target,e.pointerId,e.buttons,e=>{},()=>{this._pointerdownRepeatTimer.cancel(),this._pointerdownScheduleRepeatTimer.cancel()}),e.preventDefault())}},Wr=class e{constructor(e,t,n,r,i,a,o){this._forceIntegerValues=e,this._scrollStateBrand=void 0,this._forceIntegerValues&&(t|=0,n|=0,r|=0,i|=0,a|=0,o|=0),this.rawScrollLeft=r,this.rawScrollTop=o,t<0&&(t=0),r+t>n&&(r=n-t),r<0&&(r=0),i<0&&(i=0),o+i>a&&(o=a-i),o<0&&(o=0),this.width=t,this.scrollWidth=n,this.scrollLeft=r,this.height=i,this.scrollHeight=a,this.scrollTop=o}equals(e){return this.rawScrollLeft===e.rawScrollLeft&&this.rawScrollTop===e.rawScrollTop&&this.width===e.width&&this.scrollWidth===e.scrollWidth&&this.scrollLeft===e.scrollLeft&&this.height===e.height&&this.scrollHeight===e.scrollHeight&&this.scrollTop===e.scrollTop}withScrollDimensions(t,n){return new e(this._forceIntegerValues,typeof t.width<`u`?t.width:this.width,typeof t.scrollWidth<`u`?t.scrollWidth:this.scrollWidth,n?this.rawScrollLeft:this.scrollLeft,typeof t.height<`u`?t.height:this.height,typeof t.scrollHeight<`u`?t.scrollHeight:this.scrollHeight,n?this.rawScrollTop:this.scrollTop)}withScrollPosition(t){return new e(this._forceIntegerValues,this.width,this.scrollWidth,typeof t.scrollLeft<`u`?t.scrollLeft:this.rawScrollLeft,this.height,this.scrollHeight,typeof t.scrollTop<`u`?t.scrollTop:this.rawScrollTop)}createScrollEvent(e,t){let n=this.width!==e.width,r=this.scrollWidth!==e.scrollWidth,i=this.scrollLeft!==e.scrollLeft,a=this.height!==e.height,o=this.scrollHeight!==e.scrollHeight,s=this.scrollTop!==e.scrollTop;return{inSmoothScrolling:t,oldWidth:e.width,oldScrollWidth:e.scrollWidth,oldScrollLeft:e.scrollLeft,width:this.width,scrollWidth:this.scrollWidth,scrollLeft:this.scrollLeft,oldHeight:e.height,oldScrollHeight:e.scrollHeight,oldScrollTop:e.scrollTop,height:this.height,scrollHeight:this.scrollHeight,scrollTop:this.scrollTop,widthChanged:n,scrollWidthChanged:r,scrollLeftChanged:i,heightChanged:a,scrollHeightChanged:o,scrollTopChanged:s}}},Gr=class extends A{constructor(e){super(),this._scrollableBrand=void 0,this._onScroll=this._register(new N),this.onScroll=this._onScroll.event,this._smoothScrollDuration=e.smoothScrollDuration,this._scheduleAtNextAnimationFrame=e.scheduleAtNextAnimationFrame,this._state=new Wr(e.forceIntegerValues,0,0,0,0,0,0),this._smoothScrolling=null}dispose(){this._smoothScrolling&&=(this._smoothScrolling.dispose(),null),super.dispose()}setSmoothScrollDuration(e){this._smoothScrollDuration=e}validateScrollPosition(e){return this._state.withScrollPosition(e)}getScrollDimensions(){return this._state}setScrollDimensions(e,t){let n=this._state.withScrollDimensions(e,t);this._setState(n,!!this._smoothScrolling),this._smoothScrolling?.acceptScrollDimensions(this._state)}getFutureScrollPosition(){return this._smoothScrolling?this._smoothScrolling.to:this._state}getCurrentScrollPosition(){return this._state}setScrollPositionNow(e){let t=this._state.withScrollPosition(e);this._smoothScrolling&&=(this._smoothScrolling.dispose(),null),this._setState(t,!1)}setScrollPositionSmooth(e,t){if(this._smoothScrollDuration===0)return this.setScrollPositionNow(e);if(this._smoothScrolling){e={scrollLeft:typeof e.scrollLeft>`u`?this._smoothScrolling.to.scrollLeft:e.scrollLeft,scrollTop:typeof e.scrollTop>`u`?this._smoothScrolling.to.scrollTop:e.scrollTop};let n=this._state.withScrollPosition(e);if(this._smoothScrolling.to.scrollLeft===n.scrollLeft&&this._smoothScrolling.to.scrollTop===n.scrollTop)return;let r;r=t?new Yr(this._smoothScrolling.from,n,this._smoothScrolling.startTime,this._smoothScrolling.duration):this._smoothScrolling.combine(this._state,n,this._smoothScrollDuration),this._smoothScrolling.dispose(),this._smoothScrolling=r}else{let t=this._state.withScrollPosition(e);this._smoothScrolling=Yr.start(this._state,t,this._smoothScrollDuration)}this._smoothScrolling.animationFrameDisposable=this._scheduleAtNextAnimationFrame(()=>{this._smoothScrolling&&(this._smoothScrolling.animationFrameDisposable=null,this._performSmoothScrolling())})}hasPendingScrollAnimation(){return!!this._smoothScrolling}_performSmoothScrolling(){if(!this._smoothScrolling)return;let e=this._smoothScrolling.tick(),t=this._state.withScrollPosition(e);if(this._setState(t,!0),this._smoothScrolling){if(e.isDone){this._smoothScrolling.dispose(),this._smoothScrolling=null;return}this._smoothScrolling.animationFrameDisposable=this._scheduleAtNextAnimationFrame(()=>{this._smoothScrolling&&(this._smoothScrolling.animationFrameDisposable=null,this._performSmoothScrolling())})}}_setState(e,t){let n=this._state;n.equals(e)||(this._state=e,this._onScroll.fire(this._state.createScrollEvent(n,t)))}},Kr=class{constructor(e,t,n){this.scrollLeft=e,this.scrollTop=t,this.isDone=n}};function qr(e,t){let n=t-e;return function(t){return e+n*Zr(t)}}function Jr(e,t,n){return function(r){return r2.5*n){let r,i;return e{this._domNode?.setClassName(this._visibleClassName)},0))}_hide(e){this._revealTimer.cancel(),this._isVisible&&(this._isVisible=!1,this._domNode?.setClassName(this._invisibleClassName+(e?` fade`:``)))}},$r=140,ei=class extends Vr{constructor(e){super(),this._lazyRender=e.lazyRender,this._host=e.host,this._scrollable=e.scrollable,this._scrollByPage=e.scrollByPage,this._scrollbarState=e.scrollbarState,this._visibilityController=this._register(new Qr(e.visibility,`visible scrollbar `+e.extraScrollbarClassName,`invisible scrollbar `+e.extraScrollbarClassName)),this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._pointerMoveMonitor=this._register(new Ir),this._shouldRender=!0,this.domNode=Fr(document.createElement(`div`)),this.domNode.setAttribute(`role`,`presentation`),this.domNode.setAttribute(`aria-hidden`,`true`),this._visibilityController.setDomNode(this.domNode),this.domNode.setPosition(`absolute`),this._register(P(this.domNode.domNode,F.POINTER_DOWN,e=>this._domNodePointerDown(e)))}_createArrow(e){let t=this._register(new Ur(e));this.domNode.domNode.appendChild(t.bgDomNode),this.domNode.domNode.appendChild(t.domNode)}_createSlider(e,t,n,r){this.slider=Fr(document.createElement(`div`)),this.slider.setClassName(`slider`),this.slider.setPosition(`absolute`),this.slider.setTop(e),this.slider.setLeft(t),typeof n==`number`&&this.slider.setWidth(n),typeof r==`number`&&this.slider.setHeight(r),this.slider.setLayerHinting(!0),this.slider.setContain(`strict`),this.domNode.domNode.appendChild(this.slider.domNode),this._register(P(this.slider.domNode,F.POINTER_DOWN,e=>{e.button===0&&(e.preventDefault(),this._sliderPointerDown(e))})),this.onclick(this.slider.domNode,e=>{e.leftButton&&e.stopPropagation()})}_onElementSize(e){return this._scrollbarState.setVisibleSize(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}_onElementScrollSize(e){return this._scrollbarState.setScrollSize(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}_onElementScrollPosition(e){return this._scrollbarState.setScrollPosition(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}beginReveal(){this._visibilityController.setShouldBeVisible(!0)}beginHide(){this._visibilityController.setShouldBeVisible(!1)}render(){this._shouldRender&&(this._shouldRender=!1,this._renderDomNode(this._scrollbarState.getRectangleLargeSize(),this._scrollbarState.getRectangleSmallSize()),this._updateSlider(this._scrollbarState.getSliderSize(),this._scrollbarState.getArrowSize()+this._scrollbarState.getSliderPosition()))}_domNodePointerDown(e){e.target===this.domNode.domNode&&this._onPointerDown(e)}delegatePointerDown(e){let t=this.domNode.domNode.getClientRects()[0].top,n=t+this._scrollbarState.getSliderPosition(),r=t+this._scrollbarState.getSliderPosition()+this._scrollbarState.getSliderSize(),i=this._sliderPointerPosition(e);n<=i&&i<=r?e.button===0&&(e.preventDefault(),this._sliderPointerDown(e)):this._onPointerDown(e)}_onPointerDown(e){let t,n;if(e.target===this.domNode.domNode&&typeof e.offsetX==`number`&&typeof e.offsetY==`number`)t=e.offsetX,n=e.offsetY;else{let r=Ar(this.domNode.domNode);t=e.pageX-r.left,n=e.pageY-r.top}let r=this._pointerDownRelativePosition(t,n);this._setDesiredScrollPositionNow(this._scrollByPage?this._scrollbarState.getDesiredScrollPositionFromOffsetPaged(r):this._scrollbarState.getDesiredScrollPositionFromOffset(r)),e.button===0&&(e.preventDefault(),this._sliderPointerDown(e))}_sliderPointerDown(e){if(!e.target||!(e.target instanceof Element))return;let t=this._sliderPointerPosition(e),n=this._sliderOrthogonalPointerPosition(e),r=this._scrollbarState.clone();this.slider.toggleClassName(`active`,!0),this._pointerMoveMonitor.startMonitoring(e.target,e.pointerId,e.buttons,e=>{let i=this._sliderOrthogonalPointerPosition(e),a=Math.abs(i-n);if(mn&&a>$r){this._setDesiredScrollPositionNow(r.getScrollPosition());return}let o=this._sliderPointerPosition(e)-t;this._setDesiredScrollPositionNow(r.getDesiredScrollPositionFromDelta(o))},()=>{this.slider.toggleClassName(`active`,!1),this._host.onDragEnd()}),this._host.onDragStart()}_setDesiredScrollPositionNow(e){let t={};this.writeScrollPosition(t,e),this._scrollable.setScrollPositionNow(t)}updateScrollbarSize(e){this._updateScrollbarSize(e),this._scrollbarState.setScrollbarSize(e),this._shouldRender=!0,this._lazyRender||this.render()}isNeeded(){return this._scrollbarState.isNeeded()}},ti=class e{constructor(e,t,n,r,i,a){this._scrollbarSize=Math.round(t),this._oppositeScrollbarSize=Math.round(n),this._arrowSize=Math.round(e),this._visibleSize=r,this._scrollSize=i,this._scrollPosition=a,this._computedAvailableSize=0,this._computedIsNeeded=!1,this._computedSliderSize=0,this._computedSliderRatio=0,this._computedSliderPosition=0,this._refreshComputedValues()}clone(){return new e(this._arrowSize,this._scrollbarSize,this._oppositeScrollbarSize,this._visibleSize,this._scrollSize,this._scrollPosition)}setVisibleSize(e){let t=Math.round(e);return this._visibleSize===t?!1:(this._visibleSize=t,this._refreshComputedValues(),!0)}setScrollSize(e){let t=Math.round(e);return this._scrollSize===t?!1:(this._scrollSize=t,this._refreshComputedValues(),!0)}setScrollPosition(e){let t=Math.round(e);return this._scrollPosition===t?!1:(this._scrollPosition=t,this._refreshComputedValues(),!0)}setScrollbarSize(e){this._scrollbarSize=Math.round(e)}setOppositeScrollbarSize(e){this._oppositeScrollbarSize=Math.round(e)}static _computeValues(e,t,n,r,i){let a=Math.max(0,n-e),o=Math.max(0,a-2*t),s=r>0&&r>n;if(!s)return{computedAvailableSize:Math.round(a),computedIsNeeded:s,computedSliderSize:Math.round(o),computedSliderRatio:0,computedSliderPosition:0};let c=Math.round(Math.max(20,Math.floor(n*o/r))),l=(o-c)/(r-n),u=i*l;return{computedAvailableSize:Math.round(a),computedIsNeeded:s,computedSliderSize:Math.round(c),computedSliderRatio:l,computedSliderPosition:Math.round(u)}}_refreshComputedValues(){let t=e._computeValues(this._oppositeScrollbarSize,this._arrowSize,this._visibleSize,this._scrollSize,this._scrollPosition);this._computedAvailableSize=t.computedAvailableSize,this._computedIsNeeded=t.computedIsNeeded,this._computedSliderSize=t.computedSliderSize,this._computedSliderRatio=t.computedSliderRatio,this._computedSliderPosition=t.computedSliderPosition}getArrowSize(){return this._arrowSize}getScrollPosition(){return this._scrollPosition}getRectangleLargeSize(){return this._computedAvailableSize}getRectangleSmallSize(){return this._scrollbarSize}isNeeded(){return this._computedIsNeeded}getSliderSize(){return this._computedSliderSize}getSliderPosition(){return this._computedSliderPosition}getDesiredScrollPositionFromOffset(e){if(!this._computedIsNeeded)return 0;let t=e-this._arrowSize-this._computedSliderSize/2;return Math.round(t/this._computedSliderRatio)}getDesiredScrollPositionFromOffsetPaged(e){if(!this._computedIsNeeded)return 0;let t=e-this._arrowSize,n=this._scrollPosition;return t0&&Math.abs(e.deltaY)>0)return 1;let n=.5;if((!this._isAlmostInt(e.deltaX)||!this._isAlmostInt(e.deltaY))&&(n+=.25),t){let r=Math.abs(e.deltaX),i=Math.abs(e.deltaY),a=Math.abs(t.deltaX),o=Math.abs(t.deltaY),s=Math.max(Math.min(r,a),1),c=Math.max(Math.min(i,o),1),l=Math.max(r,a),u=Math.max(i,o);l%s===0&&u%c===0&&(n-=.5)}return Math.min(Math.max(n,0),1)}_isAlmostInt(e){return Math.abs(Math.round(e)-e)<.01}};ci.INSTANCE=new ci;var li=ci,ui=class extends Vr{constructor(e,t,n){super(),this._onScroll=this._register(new N),this.onScroll=this._onScroll.event,this._onWillScroll=this._register(new N),this.onWillScroll=this._onWillScroll.event,this._options=fi(t),this._scrollable=n,this._register(this._scrollable.onScroll(e=>{this._onWillScroll.fire(e),this._onDidScroll(e),this._onScroll.fire(e)}));let r={onMouseWheel:e=>this._onMouseWheel(e),onDragStart:()=>this._onDragStart(),onDragEnd:()=>this._onDragEnd()};this._verticalScrollbar=this._register(new ri(this._scrollable,this._options,r)),this._horizontalScrollbar=this._register(new ni(this._scrollable,this._options,r)),this._domNode=document.createElement(`div`),this._domNode.className=`xterm-scrollable-element `+this._options.className,this._domNode.setAttribute(`role`,`presentation`),this._domNode.style.position=`relative`,this._domNode.appendChild(e),this._domNode.appendChild(this._horizontalScrollbar.domNode.domNode),this._domNode.appendChild(this._verticalScrollbar.domNode.domNode),this._options.useShadows?(this._leftShadowDomNode=Fr(document.createElement(`div`)),this._leftShadowDomNode.setClassName(`shadow`),this._domNode.appendChild(this._leftShadowDomNode.domNode),this._topShadowDomNode=Fr(document.createElement(`div`)),this._topShadowDomNode.setClassName(`shadow`),this._domNode.appendChild(this._topShadowDomNode.domNode),this._topLeftShadowDomNode=Fr(document.createElement(`div`)),this._topLeftShadowDomNode.setClassName(`shadow`),this._domNode.appendChild(this._topLeftShadowDomNode.domNode)):(this._leftShadowDomNode=null,this._topShadowDomNode=null,this._topLeftShadowDomNode=null),this._listenOnDomNode=this._options.listenOnDomNode||this._domNode,this._mouseWheelToDispose=[],this._setListeningToMouseWheel(this._options.handleMouseWheel),this.onmouseover(this._listenOnDomNode,e=>this._onMouseOver(e)),this.onmouseleave(this._listenOnDomNode,e=>this._onMouseLeave(e)),this._hideTimeout=this._register(new Kn),this._isDragging=!1,this._mouseIsOver=!1,this._shouldRender=!0,this._revealOnScroll=!0}get options(){return this._options}dispose(){this._mouseWheelToDispose=St(this._mouseWheelToDispose),super.dispose()}getDomNode(){return this._domNode}getOverviewRulerLayoutInfo(){return{parent:this._domNode,insertBefore:this._verticalScrollbar.domNode.domNode}}delegateVerticalScrollbarPointerDown(e){this._verticalScrollbar.delegatePointerDown(e)}getScrollDimensions(){return this._scrollable.getScrollDimensions()}setScrollDimensions(e){this._scrollable.setScrollDimensions(e,!1)}updateClassName(e){this._options.className=e,hn&&(this._options.className+=` mac`),this._domNode.className=`xterm-scrollable-element `+this._options.className}updateOptions(e){typeof e.handleMouseWheel<`u`&&(this._options.handleMouseWheel=e.handleMouseWheel,this._setListeningToMouseWheel(this._options.handleMouseWheel)),typeof e.mouseWheelScrollSensitivity<`u`&&(this._options.mouseWheelScrollSensitivity=e.mouseWheelScrollSensitivity),typeof e.fastScrollSensitivity<`u`&&(this._options.fastScrollSensitivity=e.fastScrollSensitivity),typeof e.scrollPredominantAxis<`u`&&(this._options.scrollPredominantAxis=e.scrollPredominantAxis),typeof e.horizontal<`u`&&(this._options.horizontal=e.horizontal),typeof e.vertical<`u`&&(this._options.vertical=e.vertical),typeof e.horizontalScrollbarSize<`u`&&(this._options.horizontalScrollbarSize=e.horizontalScrollbarSize),typeof e.verticalScrollbarSize<`u`&&(this._options.verticalScrollbarSize=e.verticalScrollbarSize),typeof e.scrollByPage<`u`&&(this._options.scrollByPage=e.scrollByPage),this._horizontalScrollbar.updateOptions(this._options),this._verticalScrollbar.updateOptions(this._options),this._options.lazyRender||this._render()}setRevealOnScroll(e){this._revealOnScroll=e}delegateScrollFromMouseWheelEvent(e){this._onMouseWheel(new Hn(e))}_setListeningToMouseWheel(e){this._mouseWheelToDispose.length>0!==e&&(this._mouseWheelToDispose=St(this._mouseWheelToDispose),e)&&this._mouseWheelToDispose.push(P(this._listenOnDomNode,F.MOUSE_WHEEL,e=>{this._onMouseWheel(new Hn(e))},{passive:!1}))}_onMouseWheel(e){if(e.browserEvent?.defaultPrevented)return;let t=li.INSTANCE;oi&&t.acceptStandardWheelEvent(e);let n=!1;if(e.deltaY||e.deltaX){let r=e.deltaY*this._options.mouseWheelScrollSensitivity,i=e.deltaX*this._options.mouseWheelScrollSensitivity;this._options.scrollPredominantAxis&&(this._options.scrollYToX&&i+r===0?i=r=0:Math.abs(r)>=Math.abs(i)?i=0:r=0),this._options.flipAxes&&([r,i]=[i,r]);let a=!hn&&e.browserEvent&&e.browserEvent.shiftKey;(this._options.scrollYToX||a)&&!i&&(i=r,r=0),e.browserEvent&&e.browserEvent.altKey&&(i*=this._options.fastScrollSensitivity,r*=this._options.fastScrollSensitivity);let o=this._scrollable.getFutureScrollPosition(),s={};if(r){let e=ai*r,t=o.scrollTop-(e<0?Math.floor(e):Math.ceil(e));this._verticalScrollbar.writeScrollPosition(s,t)}if(i){let e=ai*i,t=o.scrollLeft-(e<0?Math.floor(e):Math.ceil(e));this._horizontalScrollbar.writeScrollPosition(s,t)}s=this._scrollable.validateScrollPosition(s),(o.scrollLeft!==s.scrollLeft||o.scrollTop!==s.scrollTop)&&(oi&&this._options.mouseWheelSmoothScroll&&t.isPhysicalMouseWheel()?this._scrollable.setScrollPositionSmooth(s):this._scrollable.setScrollPositionNow(s),n=!0)}let r=n;!r&&this._options.alwaysConsumeMouseWheel&&(r=!0),!r&&this._options.consumeMouseWheelIfScrollbarIsNeeded&&(this._verticalScrollbar.isNeeded()||this._horizontalScrollbar.isNeeded())&&(r=!0),r&&(e.preventDefault(),e.stopPropagation())}_onDidScroll(e){this._shouldRender=this._horizontalScrollbar.onDidScroll(e)||this._shouldRender,this._shouldRender=this._verticalScrollbar.onDidScroll(e)||this._shouldRender,this._options.useShadows&&(this._shouldRender=!0),this._revealOnScroll&&this._reveal(),this._options.lazyRender||this._render()}renderNow(){if(!this._options.lazyRender)throw Error("Please use `lazyRender` together with `renderNow`!");this._render()}_render(){if(this._shouldRender&&(this._shouldRender=!1,this._horizontalScrollbar.render(),this._verticalScrollbar.render(),this._options.useShadows)){let e=this._scrollable.getCurrentScrollPosition(),t=e.scrollTop>0,n=e.scrollLeft>0,r=n?` left`:``,i=t?` top`:``,a=n||t?` top-left-corner`:``;this._leftShadowDomNode.setClassName(`shadow${r}`),this._topShadowDomNode.setClassName(`shadow${i}`),this._topLeftShadowDomNode.setClassName(`shadow${a}${i}${r}`)}}_onDragStart(){this._isDragging=!0,this._reveal()}_onDragEnd(){this._isDragging=!1,this._hide()}_onMouseLeave(e){this._mouseIsOver=!1,this._hide()}_onMouseOver(e){this._mouseIsOver=!0,this._reveal()}_reveal(){this._verticalScrollbar.beginReveal(),this._horizontalScrollbar.beginReveal(),this._scheduleHide()}_hide(){!this._mouseIsOver&&!this._isDragging&&(this._verticalScrollbar.beginHide(),this._horizontalScrollbar.beginHide())}_scheduleHide(){!this._mouseIsOver&&!this._isDragging&&this._hideTimeout.cancelAndSet(()=>this._hide(),ii)}},di=class extends ui{constructor(e,t,n){super(e,t,n)}setScrollPosition(e){e.reuseAnimation?this._scrollable.setScrollPositionSmooth(e,e.reuseAnimation):this._scrollable.setScrollPositionNow(e)}getScrollPosition(){return this._scrollable.getCurrentScrollPosition()}};function fi(e){let t={lazyRender:typeof e.lazyRender<`u`&&e.lazyRender,className:typeof e.className<`u`?e.className:``,useShadows:typeof e.useShadows<`u`?e.useShadows:!0,handleMouseWheel:typeof e.handleMouseWheel<`u`?e.handleMouseWheel:!0,flipAxes:typeof e.flipAxes<`u`&&e.flipAxes,consumeMouseWheelIfScrollbarIsNeeded:typeof e.consumeMouseWheelIfScrollbarIsNeeded<`u`&&e.consumeMouseWheelIfScrollbarIsNeeded,alwaysConsumeMouseWheel:typeof e.alwaysConsumeMouseWheel<`u`&&e.alwaysConsumeMouseWheel,scrollYToX:typeof e.scrollYToX<`u`&&e.scrollYToX,mouseWheelScrollSensitivity:typeof e.mouseWheelScrollSensitivity<`u`?e.mouseWheelScrollSensitivity:1,fastScrollSensitivity:typeof e.fastScrollSensitivity<`u`?e.fastScrollSensitivity:5,scrollPredominantAxis:typeof e.scrollPredominantAxis<`u`?e.scrollPredominantAxis:!0,mouseWheelSmoothScroll:typeof e.mouseWheelSmoothScroll<`u`?e.mouseWheelSmoothScroll:!0,arrowSize:typeof e.arrowSize<`u`?e.arrowSize:11,listenOnDomNode:typeof e.listenOnDomNode<`u`?e.listenOnDomNode:null,horizontal:typeof e.horizontal<`u`?e.horizontal:1,horizontalScrollbarSize:typeof e.horizontalScrollbarSize<`u`?e.horizontalScrollbarSize:10,horizontalSliderSize:typeof e.horizontalSliderSize<`u`?e.horizontalSliderSize:0,horizontalHasArrows:typeof e.horizontalHasArrows<`u`&&e.horizontalHasArrows,vertical:typeof e.vertical<`u`?e.vertical:1,verticalScrollbarSize:typeof e.verticalScrollbarSize<`u`?e.verticalScrollbarSize:10,verticalHasArrows:typeof e.verticalHasArrows<`u`&&e.verticalHasArrows,verticalSliderSize:typeof e.verticalSliderSize<`u`?e.verticalSliderSize:0,scrollByPage:typeof e.scrollByPage<`u`&&e.scrollByPage};return t.horizontalSliderSize=typeof e.horizontalSliderSize<`u`?e.horizontalSliderSize:t.horizontalScrollbarSize,t.verticalSliderSize=typeof e.verticalSliderSize<`u`?e.verticalSliderSize:t.verticalScrollbarSize,hn&&(t.className+=` mac`),t}var pi=class extends A{constructor(e,t,n,r,i,a,o,s){super(),this._bufferService=n,this._optionsService=o,this._renderService=s,this._onRequestScrollLines=this._register(new N),this.onRequestScrollLines=this._onRequestScrollLines.event,this._isSyncing=!1,this._isHandlingScroll=!1,this._suppressOnScrollHandler=!1;let c=this._register(new Gr({forceIntegerValues:!1,smoothScrollDuration:this._optionsService.rawOptions.smoothScrollDuration,scheduleAtNextAnimationFrame:e=>Er(r.window,e)}));this._register(this._optionsService.onSpecificOptionChange(`smoothScrollDuration`,()=>{c.setSmoothScrollDuration(this._optionsService.rawOptions.smoothScrollDuration)})),this._scrollableElement=this._register(new di(t,{vertical:1,horizontal:2,useShadows:!1,mouseWheelSmoothScroll:!0,...this._getChangeOptions()},c)),this._register(this._optionsService.onMultipleOptionChange([`scrollSensitivity`,`fastScrollSensitivity`,`overviewRuler`],()=>this._scrollableElement.updateOptions(this._getChangeOptions()))),this._register(i.onProtocolChange(e=>{this._scrollableElement.updateOptions({handleMouseWheel:!(e&16)})})),this._scrollableElement.setScrollDimensions({height:0,scrollHeight:0}),this._register(M.runAndSubscribe(a.onChangeColors,()=>{this._scrollableElement.getDomNode().style.backgroundColor=a.colors.background.css})),e.appendChild(this._scrollableElement.getDomNode()),this._register(k(()=>this._scrollableElement.getDomNode().remove())),this._styleElement=r.mainDocument.createElement(`style`),t.appendChild(this._styleElement),this._register(k(()=>this._styleElement.remove())),this._register(M.runAndSubscribe(a.onChangeColors,()=>{this._styleElement.textContent=[`.xterm .xterm-scrollable-element > .scrollbar > .slider {`,` background: ${a.colors.scrollbarSliderBackground.css};`,`}`,`.xterm .xterm-scrollable-element > .scrollbar > .slider:hover {`,` background: ${a.colors.scrollbarSliderHoverBackground.css};`,`}`,`.xterm .xterm-scrollable-element > .scrollbar > .slider.active {`,` background: ${a.colors.scrollbarSliderActiveBackground.css};`,`}`].join(` -`)})),this._register(this._bufferService.onResize(()=>this.queueSync())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._latestYDisp=void 0,this.queueSync()})),this._register(this._bufferService.onScroll(()=>this._sync())),this._register(this._scrollableElement.onScroll(e=>this._handleScroll(e)))}scrollLines(e){let t=this._scrollableElement.getScrollPosition();this._scrollableElement.setScrollPosition({reuseAnimation:!0,scrollTop:t.scrollTop+e*this._renderService.dimensions.css.cell.height})}scrollToLine(e,t){t&&(this._latestYDisp=e),this._scrollableElement.setScrollPosition({reuseAnimation:!t,scrollTop:e*this._renderService.dimensions.css.cell.height})}_getChangeOptions(){return{mouseWheelScrollSensitivity:this._optionsService.rawOptions.scrollSensitivity,fastScrollSensitivity:this._optionsService.rawOptions.fastScrollSensitivity,verticalScrollbarSize:this._optionsService.rawOptions.overviewRuler?.width||14}}queueSync(e){e!==void 0&&(this._latestYDisp=e),this._queuedAnimationFrame===void 0&&(this._queuedAnimationFrame=this._renderService.addRefreshCallback(()=>{this._queuedAnimationFrame=void 0,this._sync(this._latestYDisp)}))}_sync(e=this._bufferService.buffer.ydisp){!this._renderService||this._isSyncing||(this._isSyncing=!0,this._suppressOnScrollHandler=!0,this._scrollableElement.setScrollDimensions({height:this._renderService.dimensions.css.canvas.height,scrollHeight:this._renderService.dimensions.css.cell.height*this._bufferService.buffer.lines.length}),this._suppressOnScrollHandler=!1,e!==this._latestYDisp&&this._scrollableElement.setScrollPosition({scrollTop:e*this._renderService.dimensions.css.cell.height}),this._isSyncing=!1)}_handleScroll(e){if(!this._renderService||this._isHandlingScroll||this._suppressOnScrollHandler)return;this._isHandlingScroll=!0;let t=Math.round(e.scrollTop/this._renderService.dimensions.css.cell.height),n=t-this._bufferService.buffer.ydisp;n!==0&&(this._latestYDisp=t,this._onRequestScrollLines.fire(n)),this._isHandlingScroll=!1}};pi=C([w(2,D),w(3,We),w(4,Ne),w(5,Ye),w(6,O),w(7,Ke)],pi);var mi=class extends A{constructor(e,t,n,r,i){super(),this._screenElement=e,this._bufferService=t,this._coreBrowserService=n,this._decorationService=r,this._renderService=i,this._decorationElements=new Map,this._altBufferIsActive=!1,this._dimensionsChanged=!1,this._container=document.createElement(`div`),this._container.classList.add(`xterm-decoration-container`),this._screenElement.appendChild(this._container),this._register(this._renderService.onRenderedViewportChange(()=>this._doRefreshDecorations())),this._register(this._renderService.onDimensionsChange(()=>{this._dimensionsChanged=!0,this._queueRefresh()})),this._register(this._coreBrowserService.onDprChange(()=>this._queueRefresh())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._altBufferIsActive=this._bufferService.buffer===this._bufferService.buffers.alt})),this._register(this._decorationService.onDecorationRegistered(()=>this._queueRefresh())),this._register(this._decorationService.onDecorationRemoved(e=>this._removeDecoration(e))),this._register(k(()=>{this._container.remove(),this._decorationElements.clear()}))}_queueRefresh(){this._animationFrame===void 0&&(this._animationFrame=this._renderService.addRefreshCallback(()=>{this._doRefreshDecorations(),this._animationFrame=void 0}))}_doRefreshDecorations(){for(let e of this._decorationService.decorations)this._renderDecoration(e);this._dimensionsChanged=!1}_renderDecoration(e){this._refreshStyle(e),this._dimensionsChanged&&this._refreshXPosition(e)}_createElement(e){let t=this._coreBrowserService.mainDocument.createElement(`div`);t.classList.add(`xterm-decoration`),t.classList.toggle(`xterm-decoration-top-layer`,e?.options?.layer===`top`),t.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,t.style.height=`${(e.options.height||1)*this._renderService.dimensions.css.cell.height}px`,t.style.top=`${(e.marker.line-this._bufferService.buffers.active.ydisp)*this._renderService.dimensions.css.cell.height}px`,t.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`;let n=e.options.x??0;return n&&n>this._bufferService.cols&&(t.style.display=`none`),this._refreshXPosition(e,t),t}_refreshStyle(e){let t=e.marker.line-this._bufferService.buffers.active.ydisp;if(t<0||t>=this._bufferService.rows)e.element&&(e.element.style.display=`none`,e.onRenderEmitter.fire(e.element));else{let n=this._decorationElements.get(e);n||(n=this._createElement(e),e.element=n,this._decorationElements.set(e,n),this._container.appendChild(n),e.onDispose(()=>{this._decorationElements.delete(e),n.remove()})),n.style.display=this._altBufferIsActive?`none`:`block`,this._altBufferIsActive||(n.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,n.style.height=`${(e.options.height||1)*this._renderService.dimensions.css.cell.height}px`,n.style.top=`${t*this._renderService.dimensions.css.cell.height}px`,n.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`),e.onRenderEmitter.fire(n)}}_refreshXPosition(e,t=e.element){if(!t)return;let n=e.options.x??0;(e.options.anchor||`left`)===`right`?t.style.right=n?`${n*this._renderService.dimensions.css.cell.width}px`:``:t.style.left=n?`${n*this._renderService.dimensions.css.cell.width}px`:``}_removeDecoration(e){this._decorationElements.get(e)?.remove(),this._decorationElements.delete(e),e.dispose()}};mi=C([w(1,D),w(2,We),w(3,Be),w(4,Ke)],mi);var hi=class{constructor(){this._zones=[],this._zonePool=[],this._zonePoolIndex=0,this._linePadding={full:0,left:0,center:0,right:0}}get zones(){return this._zonePool.length=Math.min(this._zonePool.length,this._zones.length),this._zones}clear(){this._zones.length=0,this._zonePoolIndex=0}addDecoration(e){if(e.options.overviewRulerOptions){for(let t of this._zones)if(t.color===e.options.overviewRulerOptions.color&&t.position===e.options.overviewRulerOptions.position){if(this._lineIntersectsZone(t,e.marker.line))return;if(this._lineAdjacentToZone(t,e.marker.line,e.options.overviewRulerOptions.position)){this._addLineToZone(t,e.marker.line);return}}if(this._zonePoolIndex=e.startBufferLine&&t<=e.endBufferLine}_lineAdjacentToZone(e,t,n){return t>=e.startBufferLine-this._linePadding[n||`full`]&&t<=e.endBufferLine+this._linePadding[n||`full`]}_addLineToZone(e,t){e.startBufferLine=Math.min(e.startBufferLine,t),e.endBufferLine=Math.max(e.endBufferLine,t)}},gi={full:0,left:0,center:0,right:0},_i={full:0,left:0,center:0,right:0},vi={full:0,left:0,center:0,right:0},yi=class extends A{constructor(e,t,n,r,i,a,o,s){super(),this._viewportElement=e,this._screenElement=t,this._bufferService=n,this._decorationService=r,this._renderService=i,this._optionsService=a,this._themeService=o,this._coreBrowserService=s,this._colorZoneStore=new hi,this._shouldUpdateDimensions=!0,this._shouldUpdateAnchor=!0,this._lastKnownBufferLength=0,this._canvas=this._coreBrowserService.mainDocument.createElement(`canvas`),this._canvas.classList.add(`xterm-decoration-overview-ruler`),this._refreshCanvasDimensions(),this._viewportElement.parentElement?.insertBefore(this._canvas,this._viewportElement),this._register(k(()=>this._canvas?.remove()));let c=this._canvas.getContext(`2d`);if(c)this._ctx=c;else throw Error(`Ctx cannot be null`);this._register(this._decorationService.onDecorationRegistered(()=>this._queueRefresh(void 0,!0))),this._register(this._decorationService.onDecorationRemoved(()=>this._queueRefresh(void 0,!0))),this._register(this._renderService.onRenderedViewportChange(()=>this._queueRefresh())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._canvas.style.display=this._bufferService.buffer===this._bufferService.buffers.alt?`none`:`block`})),this._register(this._bufferService.onScroll(()=>{this._lastKnownBufferLength!==this._bufferService.buffers.normal.lines.length&&(this._refreshDrawHeightConstants(),this._refreshColorZonePadding())})),this._register(this._renderService.onRender(()=>{(!this._containerHeight||this._containerHeight!==this._screenElement.clientHeight)&&(this._queueRefresh(!0),this._containerHeight=this._screenElement.clientHeight)})),this._register(this._coreBrowserService.onDprChange(()=>this._queueRefresh(!0))),this._register(this._optionsService.onSpecificOptionChange(`overviewRuler`,()=>this._queueRefresh(!0))),this._register(this._themeService.onChangeColors(()=>this._queueRefresh())),this._queueRefresh(!0)}get _width(){return this._optionsService.options.overviewRuler?.width||0}_refreshDrawConstants(){let e=Math.floor((this._canvas.width-1)/3),t=Math.ceil((this._canvas.width-1)/3);_i.full=this._canvas.width,_i.left=e,_i.center=t,_i.right=e,this._refreshDrawHeightConstants(),vi.full=1,vi.left=1,vi.center=1+_i.left,vi.right=1+_i.left+_i.center}_refreshDrawHeightConstants(){gi.full=Math.round(2*this._coreBrowserService.dpr);let e=this._canvas.height/this._bufferService.buffer.lines.length,t=Math.round(Math.max(Math.min(e,12),6)*this._coreBrowserService.dpr);gi.left=t,gi.center=t,gi.right=t}_refreshColorZonePadding(){this._colorZoneStore.setPadding({full:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*gi.full),left:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*gi.left),center:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*gi.center),right:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*gi.right)}),this._lastKnownBufferLength=this._bufferService.buffers.normal.lines.length}_refreshCanvasDimensions(){this._canvas.style.width=`${this._width}px`,this._canvas.width=Math.round(this._width*this._coreBrowserService.dpr),this._canvas.style.height=`${this._screenElement.clientHeight}px`,this._canvas.height=Math.round(this._screenElement.clientHeight*this._coreBrowserService.dpr),this._refreshDrawConstants(),this._refreshColorZonePadding()}_refreshDecorations(){this._shouldUpdateDimensions&&this._refreshCanvasDimensions(),this._ctx.clearRect(0,0,this._canvas.width,this._canvas.height),this._colorZoneStore.clear();for(let e of this._decorationService.decorations)this._colorZoneStore.addDecoration(e);this._ctx.lineWidth=1,this._renderRulerOutline();let e=this._colorZoneStore.zones;for(let t of e)t.position!==`full`&&this._renderColorZone(t);for(let t of e)t.position===`full`&&this._renderColorZone(t);this._shouldUpdateDimensions=!1,this._shouldUpdateAnchor=!1}_renderRulerOutline(){this._ctx.fillStyle=this._themeService.colors.overviewRulerBorder.css,this._ctx.fillRect(0,0,1,this._canvas.height),this._optionsService.rawOptions.overviewRuler.showTopBorder&&this._ctx.fillRect(1,0,this._canvas.width-1,1),this._optionsService.rawOptions.overviewRuler.showBottomBorder&&this._ctx.fillRect(1,this._canvas.height-1,this._canvas.width-1,this._canvas.height)}_renderColorZone(e){this._ctx.fillStyle=e.color,this._ctx.fillRect(vi[e.position||`full`],Math.round((this._canvas.height-1)*(e.startBufferLine/this._bufferService.buffers.active.lines.length)-gi[e.position||`full`]/2),_i[e.position||`full`],Math.round((this._canvas.height-1)*((e.endBufferLine-e.startBufferLine)/this._bufferService.buffers.active.lines.length)+gi[e.position||`full`]))}_queueRefresh(e,t){this._shouldUpdateDimensions=e||this._shouldUpdateDimensions,this._shouldUpdateAnchor=t||this._shouldUpdateAnchor,this._animationFrame===void 0&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{this._refreshDecorations(),this._animationFrame=void 0}))}};yi=C([w(2,D),w(3,Be),w(4,Ke),w(5,O),w(6,Ye),w(7,We)],yi);var L;(e=>(e.NUL=`\0`,e.SOH=``,e.STX=``,e.ETX=``,e.EOT=``,e.ENQ=``,e.ACK=``,e.BEL=`\x07`,e.BS=`\b`,e.HT=` `,e.LF=` -`,e.VT=`\v`,e.FF=`\f`,e.CR=`\r`,e.SO=``,e.SI=``,e.DLE=``,e.DC1=``,e.DC2=``,e.DC3=``,e.DC4=``,e.NAK=``,e.SYN=``,e.ETB=``,e.CAN=``,e.EM=``,e.SUB=``,e.ESC=`\x1B`,e.FS=``,e.GS=``,e.RS=``,e.US=``,e.SP=` `,e.DEL=``))(L||={});var bi;(e=>(e.PAD=`€`,e.HOP=``,e.BPH=`‚`,e.NBH=`ƒ`,e.IND=`„`,e.NEL=`…`,e.SSA=`†`,e.ESA=`‡`,e.HTS=`ˆ`,e.HTJ=`‰`,e.VTS=`Š`,e.PLD=`‹`,e.PLU=`Œ`,e.RI=``,e.SS2=`Ž`,e.SS3=``,e.DCS=``,e.PU1=`‘`,e.PU2=`’`,e.STS=`“`,e.CCH=`”`,e.MW=`•`,e.SPA=`–`,e.EPA=`—`,e.SOS=`˜`,e.SGCI=`™`,e.SCI=`š`,e.CSI=`›`,e.ST=`œ`,e.OSC=``,e.PM=`ž`,e.APC=`Ÿ`))(bi||={});var xi;(e=>e.ST=`${L.ESC}\\`)(xi||={});var Si=class{constructor(e,t,n,r,i,a){this._textarea=e,this._compositionView=t,this._bufferService=n,this._optionsService=r,this._coreService=i,this._renderService=a,this._isComposing=!1,this._isSendingComposition=!1,this._compositionPosition={start:0,end:0},this._dataAlreadySent=``}get isComposing(){return this._isComposing}compositionstart(){this._isComposing=!0,this._compositionPosition.start=this._textarea.value.length,this._compositionView.textContent=``,this._dataAlreadySent=``,this._compositionView.classList.add(`active`)}compositionupdate(e){this._compositionView.textContent=e.data,this.updateCompositionElements(),setTimeout(()=>{this._compositionPosition.end=this._textarea.value.length},0)}compositionend(){this._finalizeComposition(!0)}keydown(e){if(this._isComposing||this._isSendingComposition){if(e.keyCode===20||e.keyCode===229||e.keyCode===16||e.keyCode===17||e.keyCode===18)return!1;this._finalizeComposition(!1)}return e.keyCode===229?(this._handleAnyTextareaChanges(),!1):!0}_finalizeComposition(e){if(this._compositionView.classList.remove(`active`),this._isComposing=!1,e){let e={start:this._compositionPosition.start,end:this._compositionPosition.end};this._isSendingComposition=!0,setTimeout(()=>{if(this._isSendingComposition){this._isSendingComposition=!1;let t;e.start+=this._dataAlreadySent.length,t=this._isComposing?this._textarea.value.substring(e.start,this._compositionPosition.start):this._textarea.value.substring(e.start),t.length>0&&this._coreService.triggerDataEvent(t,!0)}},0)}else{this._isSendingComposition=!1;let e=this._textarea.value.substring(this._compositionPosition.start,this._compositionPosition.end);this._coreService.triggerDataEvent(e,!0)}}_handleAnyTextareaChanges(){let e=this._textarea.value;setTimeout(()=>{if(!this._isComposing){let t=this._textarea.value,n=t.replace(e,``);this._dataAlreadySent=n,t.length>e.length?this._coreService.triggerDataEvent(n,!0):t.lengththis.updateCompositionElements(!0),0)}}};Si=C([w(2,D),w(3,O),w(4,Pe),w(5,Ke)],Si);var R=0,z=0,B=0,V=0,Ci={css:`#00000000`,rgba:0},H;(e=>{function t(e,t,n,r){return r===void 0?`#${Ti(e)}${Ti(t)}${Ti(n)}`:`#${Ti(e)}${Ti(t)}${Ti(n)}${Ti(r)}`}e.toCss=t;function n(e,t,n,r=255){return(e<<24|t<<16|n<<8|r)>>>0}e.toRgba=n;function r(t,n,r,i){return{css:e.toCss(t,n,r,i),rgba:e.toRgba(t,n,r,i)}}e.toColor=r})(H||={});var U;(e=>{function t(e,t){if(V=(t.rgba&255)/255,V===1)return{css:t.css,rgba:t.rgba};let n=t.rgba>>24&255,r=t.rgba>>16&255,i=t.rgba>>8&255,a=e.rgba>>24&255,o=e.rgba>>16&255,s=e.rgba>>8&255;return R=a+Math.round((n-a)*V),z=o+Math.round((r-o)*V),B=s+Math.round((i-s)*V),{css:H.toCss(R,z,B),rgba:H.toRgba(R,z,B)}}e.blend=t;function n(e){return(e.rgba&255)==255}e.isOpaque=n;function r(e,t,n){let r=wi.ensureContrastRatio(e.rgba,t.rgba,n);if(r)return H.toColor(r>>24&255,r>>16&255,r>>8&255)}e.ensureContrastRatio=r;function i(e){let t=(e.rgba|255)>>>0;return[R,z,B]=wi.toChannels(t),{css:H.toCss(R,z,B),rgba:t}}e.opaque=i;function a(e,t){return V=Math.round(t*255),[R,z,B]=wi.toChannels(e.rgba),{css:H.toCss(R,z,B,V),rgba:H.toRgba(R,z,B,V)}}e.opacity=a;function o(e,t){return V=e.rgba&255,a(e,V*t/255)}e.multiplyOpacity=o;function s(e){return[e.rgba>>24&255,e.rgba>>16&255,e.rgba>>8&255]}e.toColorRGB=s})(U||={});var W;(e=>{let t,n;try{let e=document.createElement(`canvas`);e.width=1,e.height=1;let r=e.getContext(`2d`,{willReadFrequently:!0});r&&(t=r,t.globalCompositeOperation=`copy`,n=t.createLinearGradient(0,0,1,1))}catch{}function r(e){if(e.match(/#[\da-f]{3,8}/i))switch(e.length){case 4:return R=parseInt(e.slice(1,2).repeat(2),16),z=parseInt(e.slice(2,3).repeat(2),16),B=parseInt(e.slice(3,4).repeat(2),16),H.toColor(R,z,B);case 5:return R=parseInt(e.slice(1,2).repeat(2),16),z=parseInt(e.slice(2,3).repeat(2),16),B=parseInt(e.slice(3,4).repeat(2),16),V=parseInt(e.slice(4,5).repeat(2),16),H.toColor(R,z,B,V);case 7:return{css:e,rgba:(parseInt(e.slice(1),16)<<8|255)>>>0};case 9:return{css:e,rgba:parseInt(e.slice(1),16)>>>0}}let r=e.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);if(r)return R=parseInt(r[1]),z=parseInt(r[2]),B=parseInt(r[3]),V=Math.round((r[5]===void 0?1:parseFloat(r[5]))*255),H.toColor(R,z,B,V);if(!t||!n||(t.fillStyle=n,t.fillStyle=e,typeof t.fillStyle!=`string`)||(t.fillRect(0,0,1,1),[R,z,B,V]=t.getImageData(0,0,1,1).data,V!==255))throw Error(`css.toColor: Unsupported css format`);return{rgba:H.toRgba(R,z,B,V),css:e}}e.toColor=r})(W||={});var G;(e=>{function t(e){return n(e>>16&255,e>>8&255,e&255)}e.relativeLuminance=t;function n(e,t,n){let r=e/255,i=t/255,a=n/255,o=r<=.03928?r/12.92:((r+.055)/1.055)**2.4,s=i<=.03928?i/12.92:((i+.055)/1.055)**2.4,c=a<=.03928?a/12.92:((a+.055)/1.055)**2.4;return o*.2126+s*.7152+c*.0722}e.relativeLuminance2=n})(G||={});var wi;(e=>{function t(e,t){if(V=(t&255)/255,V===1)return t;let n=t>>24&255,r=t>>16&255,i=t>>8&255,a=e>>24&255,o=e>>16&255,s=e>>8&255;return R=a+Math.round((n-a)*V),z=o+Math.round((r-o)*V),B=s+Math.round((i-s)*V),H.toRgba(R,z,B)}e.blend=t;function n(e,t,n){let a=G.relativeLuminance(e>>8),o=G.relativeLuminance(t>>8);if(Ei(a,o)>8));if(sEi(a,G.relativeLuminance(r>>8))?o:r}return o}let s=i(e,t,n),c=Ei(a,G.relativeLuminance(s>>8));if(cEi(a,G.relativeLuminance(i>>8))?s:i}return s}}e.ensureContrastRatio=n;function r(e,t,n){let r=e>>24&255,i=e>>16&255,a=e>>8&255,o=t>>24&255,s=t>>16&255,c=t>>8&255,l=Ei(G.relativeLuminance2(o,s,c),G.relativeLuminance2(r,i,a));for(;l0||s>0||c>0);)o-=Math.max(0,Math.ceil(o*.1)),s-=Math.max(0,Math.ceil(s*.1)),c-=Math.max(0,Math.ceil(c*.1)),l=Ei(G.relativeLuminance2(o,s,c),G.relativeLuminance2(r,i,a));return(o<<24|s<<16|c<<8|255)>>>0}e.reduceLuminance=r;function i(e,t,n){let r=e>>24&255,i=e>>16&255,a=e>>8&255,o=t>>24&255,s=t>>16&255,c=t>>8&255,l=Ei(G.relativeLuminance2(o,s,c),G.relativeLuminance2(r,i,a));for(;l>>0}e.increaseLuminance=i;function a(e){return[e>>24&255,e>>16&255,e>>8&255,e&255]}e.toChannels=a})(wi||={});function Ti(e){let t=e.toString(16);return t.length<2?`0`+t:t}function Ei(e,t){return e1){let e=this._getJoinedRanges(r,o,a,t,i);for(let t=0;t1){let e=this._getJoinedRanges(r,o,a,t,i);for(let t=0;t=ae,C=S,w=this._workCell;if(f.length>0&&S===f[0][0]&&ce){let r=f.shift(),i=this._isCellInSelection(r[0],t);for(v=r[0]+1;v=r[1],ce?(se=!0,w=new Di(this._workCell,e.translateToString(!0,r[0],r[1]),r[1]-r[0]),C=r[1]-1,m=w.getWidth()):ae=r[1]}let le=this._isCellInSelection(S,t),ue=n&&S===a,de=oe&&S>=l&&S<=u,fe=!1;this._decorationService.forEachDecorationAtCell(S,t,void 0,e=>{fe=!0});let T=w.getChars()||we;if(T===` `&&(w.isUnderline()||w.isOverline())&&(T=`\xA0`),ie=m*s-c.get(T,w.isBold(),w.isItalic()),!h)h=this._document.createElement(`span`);else if(g&&(le&&re||!le&&!re&&w.bg===ee)&&(le&&re&&p.selectionForeground||w.fg===y)&&w.extended.ext===te&&de===b&&ie===ne&&!ue&&!se&&!fe&&ce){w.isInvisible()?_+=we:_+=T,g++;continue}else g&&(h.textContent=_),h=this._document.createElement(`span`),g=0,_=``;if(ee=w.bg,y=w.fg,te=w.extended.ext,b=de,ne=ie,re=le,se&&a>=S&&a<=C&&(a=S),!this._coreService.isCursorHidden&&ue&&this._coreService.isCursorInitialized){if(x.push(`xterm-cursor`),this._coreBrowserService.isFocused)o&&x.push(`xterm-cursor-blink`),x.push(r===`bar`?`xterm-cursor-bar`:r===`underline`?`xterm-cursor-underline`:`xterm-cursor-block`);else if(i)switch(i){case`outline`:x.push(`xterm-cursor-outline`);break;case`block`:x.push(`xterm-cursor-block`);break;case`bar`:x.push(`xterm-cursor-bar`);break;case`underline`:x.push(`xterm-cursor-underline`);break;default:break}}if(w.isBold()&&x.push(`xterm-bold`),w.isItalic()&&x.push(`xterm-italic`),w.isDim()&&x.push(`xterm-dim`),_=w.isInvisible()?we:w.getChars()||we,w.isUnderline()&&(x.push(`xterm-underline-${w.extended.underlineStyle}`),_===` `&&(_=`\xA0`),!w.isUnderlineColorDefault()))if(w.isUnderlineColorRGB())h.style.textDecorationColor=`rgb(${Te.toColorRGB(w.getUnderlineColor()).join(`,`)})`;else{let e=w.getUnderlineColor();this._optionsService.rawOptions.drawBoldTextInBrightColors&&w.isBold()&&e<8&&(e+=8),h.style.textDecorationColor=p.ansi[e].css}w.isOverline()&&(x.push(`xterm-overline`),_===` `&&(_=`\xA0`)),w.isStrikethrough()&&x.push(`xterm-strikethrough`),de&&(h.style.textDecoration=`underline`);let pe=w.getFgColor(),me=w.getFgColorMode(),he=w.getBgColor(),ge=w.getBgColorMode(),_e=!!w.isInverse();if(_e){let e=pe;pe=he,he=e;let t=me;me=ge,ge=t}let ve,ye,be=!1;this._decorationService.forEachDecorationAtCell(S,t,void 0,e=>{e.options.layer!==`top`&&be||(e.backgroundColorRGB&&(ge=50331648,he=e.backgroundColorRGB.rgba>>8&16777215,ve=e.backgroundColorRGB),e.foregroundColorRGB&&(me=50331648,pe=e.foregroundColorRGB.rgba>>8&16777215,ye=e.foregroundColorRGB),be=e.options.layer===`top`)}),!be&&le&&(ve=this._coreBrowserService.isFocused?p.selectionBackgroundOpaque:p.selectionInactiveBackgroundOpaque,he=ve.rgba>>8&16777215,ge=50331648,be=!0,p.selectionForeground&&(me=50331648,pe=p.selectionForeground.rgba>>8&16777215,ye=p.selectionForeground)),be&&x.push(`xterm-decoration-top`);let xe;switch(ge){case 16777216:case 33554432:xe=p.ansi[he],x.push(`xterm-bg-${he}`);break;case 50331648:xe=H.toColor(he>>16,he>>8&255,he&255),this._addStyle(h,`background-color:#${Fi((he>>>0).toString(16),`0`,6)}`);break;default:_e?(xe=p.foreground,x.push(`xterm-bg-257`)):xe=p.background}switch(ve||w.isDim()&&(ve=U.multiplyOpacity(xe,.5)),me){case 16777216:case 33554432:w.isBold()&&pe<8&&this._optionsService.rawOptions.drawBoldTextInBrightColors&&(pe+=8),this._applyMinimumContrast(h,xe,p.ansi[pe],w,ve,void 0)||x.push(`xterm-fg-${pe}`);break;case 50331648:let e=H.toColor(pe>>16&255,pe>>8&255,pe&255);this._applyMinimumContrast(h,xe,e,w,ve,ye)||this._addStyle(h,`color:#${Fi(pe.toString(16),`0`,6)}`);break;default:this._applyMinimumContrast(h,xe,p.foreground,w,ve,ye)||_e&&x.push(`xterm-fg-257`)}x.length&&=(h.className=x.join(` `),0),!ue&&!se&&!fe&&ce?g++:h.textContent=_,ie!==this.defaultSpacing&&(h.style.letterSpacing=`${ie}px`),d.push(h),S=C}return h&&g&&(h.textContent=_),d}_applyMinimumContrast(e,t,n,r,i,a){if(this._optionsService.rawOptions.minimumContrastRatio===1||ji(r.getCode()))return!1;let o=this._getContrastCache(r),s;if(!i&&!a&&(s=o.getColor(t.rgba,n.rgba)),s===void 0){let e=this._optionsService.rawOptions.minimumContrastRatio/(r.isDim()?2:1);s=U.ensureContrastRatio(i||t,a||n,e),o.setColor((i||t).rgba,(a||n).rgba,s??null)}return s?(this._addStyle(e,`color:${s.css}`),!0):!1}_getContrastCache(e){return e.isDim()?this._themeService.colors.halfContrastCache:this._themeService.colors.contrastCache}_addStyle(e,t){e.setAttribute(`style`,`${e.getAttribute(`style`)||``}${t};`)}_isCellInSelection(e,t){let n=this._selectionStart,r=this._selectionEnd;return!n||!r?!1:this._columnSelectMode?n[0]<=r[0]?e>=n[0]&&t>=n[1]&&e=n[1]&&e>=r[0]&&t<=r[1]:t>n[1]&&t=n[0]&&e=n[0]}};Pi=C([w(1,Je),w(2,O),w(3,We),w(4,Pe),w(5,Be),w(6,Ye)],Pi);function Fi(e,t,n){for(;e.length0&&(this._flat[r]=t),t}let i=e;t&&(i+=`B`),n&&(i+=`I`);let a=this._holey.get(i);if(a===void 0){let r=0;t&&(r|=1),n&&(r|=2),a=this._measure(e,r),a>0&&this._holey.set(i,a)}return a}_measure(e,t){let n=this._measureElements[t];return n.textContent=e.repeat(32),n.offsetWidth/32}},Li=class{constructor(){this.clear()}clear(){this.hasSelection=!1,this.columnSelectMode=!1,this.viewportStartRow=0,this.viewportEndRow=0,this.viewportCappedStartRow=0,this.viewportCappedEndRow=0,this.startCol=0,this.endCol=0,this.selectionStart=void 0,this.selectionEnd=void 0}update(e,t,n,r=!1){if(this.selectionStart=t,this.selectionEnd=n,!t||!n||t[0]===n[0]&&t[1]===n[1]){this.clear();return}let i=e.buffers.active.ydisp,a=t[1]-i,o=n[1]-i,s=Math.max(a,0),c=Math.min(o,e.rows-1);if(s>=e.rows||c<0){this.clear();return}this.hasSelection=!0,this.columnSelectMode=r,this.viewportStartRow=a,this.viewportEndRow=o,this.viewportCappedStartRow=s,this.viewportCappedEndRow=c,this.startCol=t[0],this.endCol=n[0]}isCellSelected(e,t,n){return this.hasSelection?(n-=e.buffer.active.viewportY,this.columnSelectMode?this.startCol<=this.endCol?t>=this.startCol&&n>=this.viewportCappedStartRow&&t=this.viewportCappedStartRow&&t>=this.endCol&&n<=this.viewportCappedEndRow:n>this.viewportStartRow&&n=this.startCol&&t=this.startCol):!1}};function Ri(){return new Li}var zi=`xterm-dom-renderer-owner-`,Bi=`xterm-rows`,Vi=`xterm-fg-`,Hi=`xterm-bg-`,Ui=`xterm-focus`,Wi=`xterm-selection`,Gi=1,Ki=class extends A{constructor(e,t,n,r,i,a,o,s,c,l,u,d,f,p){super(),this._terminal=e,this._document=t,this._element=n,this._screenElement=r,this._viewportElement=i,this._helperContainer=a,this._linkifier2=o,this._charSizeService=c,this._optionsService=l,this._bufferService=u,this._coreService=d,this._coreBrowserService=f,this._themeService=p,this._terminalClass=Gi++,this._rowElements=[],this._selectionRenderModel=Ri(),this.onRequestRedraw=this._register(new N).event,this._rowContainer=this._document.createElement(`div`),this._rowContainer.classList.add(Bi),this._rowContainer.style.lineHeight=`normal`,this._rowContainer.setAttribute(`aria-hidden`,`true`),this._refreshRowElements(this._bufferService.cols,this._bufferService.rows),this._selectionContainer=this._document.createElement(`div`),this._selectionContainer.classList.add(Wi),this._selectionContainer.setAttribute(`aria-hidden`,`true`),this.dimensions=Mi(),this._updateDimensions(),this._register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this._register(this._themeService.onChangeColors(e=>this._injectCss(e))),this._injectCss(this._themeService.colors),this._rowFactory=s.createInstance(Pi,document),this._element.classList.add(zi+this._terminalClass),this._screenElement.appendChild(this._rowContainer),this._screenElement.appendChild(this._selectionContainer),this._register(this._linkifier2.onShowLinkUnderline(e=>this._handleLinkHover(e))),this._register(this._linkifier2.onHideLinkUnderline(e=>this._handleLinkLeave(e))),this._register(k(()=>{this._element.classList.remove(zi+this._terminalClass),this._rowContainer.remove(),this._selectionContainer.remove(),this._widthCache.dispose(),this._themeStyleElement.remove(),this._dimensionsStyleElement.remove()})),this._widthCache=new Ii(this._document,this._helperContainer),this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}_updateDimensions(){let e=this._coreBrowserService.dpr;this.dimensions.device.char.width=this._charSizeService.width*e,this.dimensions.device.char.height=Math.ceil(this._charSizeService.height*e),this.dimensions.device.cell.width=this.dimensions.device.char.width+Math.round(this._optionsService.rawOptions.letterSpacing),this.dimensions.device.cell.height=Math.floor(this.dimensions.device.char.height*this._optionsService.rawOptions.lineHeight),this.dimensions.device.char.left=0,this.dimensions.device.char.top=0,this.dimensions.device.canvas.width=this.dimensions.device.cell.width*this._bufferService.cols,this.dimensions.device.canvas.height=this.dimensions.device.cell.height*this._bufferService.rows,this.dimensions.css.canvas.width=Math.round(this.dimensions.device.canvas.width/e),this.dimensions.css.canvas.height=Math.round(this.dimensions.device.canvas.height/e),this.dimensions.css.cell.width=this.dimensions.css.canvas.width/this._bufferService.cols,this.dimensions.css.cell.height=this.dimensions.css.canvas.height/this._bufferService.rows;for(let e of this._rowElements)e.style.width=`${this.dimensions.css.canvas.width}px`,e.style.height=`${this.dimensions.css.cell.height}px`,e.style.lineHeight=`${this.dimensions.css.cell.height}px`,e.style.overflow=`hidden`;this._dimensionsStyleElement||(this._dimensionsStyleElement=this._document.createElement(`style`),this._screenElement.appendChild(this._dimensionsStyleElement));let t=`${this._terminalSelector} .${Bi} span { display: inline-block; height: 100%; vertical-align: top;}`;this._dimensionsStyleElement.textContent=t,this._selectionContainer.style.height=this._viewportElement.style.height,this._screenElement.style.width=`${this.dimensions.css.canvas.width}px`,this._screenElement.style.height=`${this.dimensions.css.canvas.height}px`}_injectCss(e){this._themeStyleElement||(this._themeStyleElement=this._document.createElement(`style`),this._screenElement.appendChild(this._themeStyleElement));let t=`${this._terminalSelector} .${Bi} { pointer-events: none; color: ${e.foreground.css}; font-family: ${this._optionsService.rawOptions.fontFamily}; font-size: ${this._optionsService.rawOptions.fontSize}px; font-kerning: none; white-space: pre}`;t+=`${this._terminalSelector} .${Bi} .xterm-dim { color: ${U.multiplyOpacity(e.foreground,.5).css};}`,t+=`${this._terminalSelector} span:not(.xterm-bold) { font-weight: ${this._optionsService.rawOptions.fontWeight};}${this._terminalSelector} span.xterm-bold { font-weight: ${this._optionsService.rawOptions.fontWeightBold};}${this._terminalSelector} span.xterm-italic { font-style: italic;}`;let n=`blink_underline_${this._terminalClass}`,r=`blink_bar_${this._terminalClass}`,i=`blink_block_${this._terminalClass}`;t+=`@keyframes ${n} { 50% { border-bottom-style: hidden; }}`,t+=`@keyframes ${r} { 50% { box-shadow: none; }}`,t+=`@keyframes ${i} { 0% { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css}; } 50% { background-color: inherit; color: ${e.cursor.css}; }}`,t+=`${this._terminalSelector} .${Bi}.${Ui} .xterm-cursor.xterm-cursor-blink.xterm-cursor-underline { animation: ${n} 1s step-end infinite;}${this._terminalSelector} .${Bi}.${Ui} .xterm-cursor.xterm-cursor-blink.xterm-cursor-bar { animation: ${r} 1s step-end infinite;}${this._terminalSelector} .${Bi}.${Ui} .xterm-cursor.xterm-cursor-blink.xterm-cursor-block { animation: ${i} 1s step-end infinite;}${this._terminalSelector} .${Bi} .xterm-cursor.xterm-cursor-block { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css};}${this._terminalSelector} .${Bi} .xterm-cursor.xterm-cursor-block:not(.xterm-cursor-blink) { background-color: ${e.cursor.css} !important; color: ${e.cursorAccent.css} !important;}${this._terminalSelector} .${Bi} .xterm-cursor.xterm-cursor-outline { outline: 1px solid ${e.cursor.css}; outline-offset: -1px;}${this._terminalSelector} .${Bi} .xterm-cursor.xterm-cursor-bar { box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${e.cursor.css} inset;}${this._terminalSelector} .${Bi} .xterm-cursor.xterm-cursor-underline { border-bottom: 1px ${e.cursor.css}; border-bottom-style: solid; height: calc(100% - 1px);}`,t+=`${this._terminalSelector} .${Wi} { position: absolute; top: 0; left: 0; z-index: 1; pointer-events: none;}${this._terminalSelector}.focus .${Wi} div { position: absolute; background-color: ${e.selectionBackgroundOpaque.css};}${this._terminalSelector} .${Wi} div { position: absolute; background-color: ${e.selectionInactiveBackgroundOpaque.css};}`;for(let[n,r]of e.ansi.entries())t+=`${this._terminalSelector} .${Vi}${n} { color: ${r.css}; }${this._terminalSelector} .${Vi}${n}.xterm-dim { color: ${U.multiplyOpacity(r,.5).css}; }${this._terminalSelector} .${Hi}${n} { background-color: ${r.css}; }`;t+=`${this._terminalSelector} .${Vi}257 { color: ${U.opaque(e.background).css}; }${this._terminalSelector} .${Vi}257.xterm-dim { color: ${U.multiplyOpacity(U.opaque(e.background),.5).css}; }${this._terminalSelector} .${Hi}257 { background-color: ${e.foreground.css}; }`,this._themeStyleElement.textContent=t}_setDefaultSpacing(){let e=this.dimensions.css.cell.width-this._widthCache.get(`W`,!1,!1);this._rowContainer.style.letterSpacing=`${e}px`,this._rowFactory.defaultSpacing=e}handleDevicePixelRatioChange(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}_refreshRowElements(e,t){for(let e=this._rowElements.length;e<=t;e++){let e=this._document.createElement(`div`);this._rowContainer.appendChild(e),this._rowElements.push(e)}for(;this._rowElements.length>t;)this._rowContainer.removeChild(this._rowElements.pop())}handleResize(e,t){this._refreshRowElements(e,t),this._updateDimensions(),this.handleSelectionChanged(this._selectionRenderModel.selectionStart,this._selectionRenderModel.selectionEnd,this._selectionRenderModel.columnSelectMode)}handleCharSizeChanged(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}handleBlur(){this._rowContainer.classList.remove(Ui),this.renderRows(0,this._bufferService.rows-1)}handleFocus(){this._rowContainer.classList.add(Ui),this.renderRows(this._bufferService.buffer.y,this._bufferService.buffer.y)}handleSelectionChanged(e,t,n){if(this._selectionContainer.replaceChildren(),this._rowFactory.handleSelectionChanged(e,t,n),this.renderRows(0,this._bufferService.rows-1),!e||!t||(this._selectionRenderModel.update(this._terminal,e,t,n),!this._selectionRenderModel.hasSelection))return;let r=this._selectionRenderModel.viewportStartRow,i=this._selectionRenderModel.viewportEndRow,a=this._selectionRenderModel.viewportCappedStartRow,o=this._selectionRenderModel.viewportCappedEndRow,s=this._document.createDocumentFragment();if(n){let n=e[0]>t[0];s.appendChild(this._createSelectionElement(a,n?t[0]:e[0],n?e[0]:t[0],o-a+1))}else{let n=r===a?e[0]:0,c=a===i?t[0]:this._bufferService.cols;s.appendChild(this._createSelectionElement(a,n,c));let l=o-a-1;if(s.appendChild(this._createSelectionElement(a+1,0,this._bufferService.cols,l)),a!==o){let e=i===o?t[0]:this._bufferService.cols;s.appendChild(this._createSelectionElement(o,0,e))}}this._selectionContainer.appendChild(s)}_createSelectionElement(e,t,n,r=1){let i=this._document.createElement(`div`),a=t*this.dimensions.css.cell.width,o=this.dimensions.css.cell.width*(n-t);return a+o>this.dimensions.css.canvas.width&&(o=this.dimensions.css.canvas.width-a),i.style.height=`${r*this.dimensions.css.cell.height}px`,i.style.top=`${e*this.dimensions.css.cell.height}px`,i.style.left=`${a}px`,i.style.width=`${o}px`,i}handleCursorMove(){}_handleOptionsChanged(){this._updateDimensions(),this._injectCss(this._themeService.colors),this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}clear(){for(let e of this._rowElements)e.replaceChildren()}renderRows(e,t){let n=this._bufferService.buffer,r=n.ybase+n.y,i=Math.min(n.x,this._bufferService.cols-1),a=this._coreService.decPrivateModes.cursorBlink??this._optionsService.rawOptions.cursorBlink,o=this._coreService.decPrivateModes.cursorStyle??this._optionsService.rawOptions.cursorStyle,s=this._optionsService.rawOptions.cursorInactiveStyle;for(let c=e;c<=t;c++){let e=c+n.ydisp,t=this._rowElements[c],l=n.lines.get(e);if(!t||!l)break;t.replaceChildren(...this._rowFactory.createRow(l,e,e===r,o,s,i,a,this.dimensions.css.cell.width,this._widthCache,-1,-1))}}get _terminalSelector(){return`.${zi}${this._terminalClass}`}_handleLinkHover(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!0)}_handleLinkLeave(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!1)}_setCellUnderline(e,t,n,r,i,a){n<0&&(e=0),r<0&&(t=0);let o=this._bufferService.rows-1;n=Math.max(Math.min(n,o),0),r=Math.max(Math.min(r,o),0),i=Math.min(i,this._bufferService.cols);let s=this._bufferService.buffer,c=s.ybase+s.y,l=Math.min(s.x,i-1),u=this._optionsService.rawOptions.cursorBlink,d=this._optionsService.rawOptions.cursorStyle,f=this._optionsService.rawOptions.cursorInactiveStyle;for(let o=n;o<=r;++o){let p=o+s.ydisp,m=this._rowElements[o],h=s.lines.get(p);if(!m||!h)break;m.replaceChildren(...this._rowFactory.createRow(h,p,p===c,d,f,l,u,this.dimensions.css.cell.width,this._widthCache,a?o===n?e:0:-1,a?(o===r?t:i)-1:-1))}}};Ki=C([w(7,Ie),w(8,Ue),w(9,O),w(10,D),w(11,Pe),w(12,We),w(13,Ye)],Ki);var qi=class extends A{constructor(e,t,n){super(),this._optionsService=n,this.width=0,this.height=0,this._onCharSizeChange=this._register(new N),this.onCharSizeChange=this._onCharSizeChange.event;try{this._measureStrategy=this._register(new Xi(this._optionsService))}catch{this._measureStrategy=this._register(new Yi(e,t,this._optionsService))}this._register(this._optionsService.onMultipleOptionChange([`fontFamily`,`fontSize`],()=>this.measure()))}get hasValidSize(){return this.width>0&&this.height>0}measure(){let e=this._measureStrategy.measure();(e.width!==this.width||e.height!==this.height)&&(this.width=e.width,this.height=e.height,this._onCharSizeChange.fire())}};qi=C([w(2,O)],qi);var Ji=class extends A{constructor(){super(...arguments),this._result={width:0,height:0}}_validateAndSet(e,t){e!==void 0&&e>0&&t!==void 0&&t>0&&(this._result.width=e,this._result.height=t)}},Yi=class extends Ji{constructor(e,t,n){super(),this._document=e,this._parentElement=t,this._optionsService=n,this._measureElement=this._document.createElement(`span`),this._measureElement.classList.add(`xterm-char-measure-element`),this._measureElement.textContent=`W`.repeat(32),this._measureElement.setAttribute(`aria-hidden`,`true`),this._measureElement.style.whiteSpace=`pre`,this._measureElement.style.fontKerning=`none`,this._parentElement.appendChild(this._measureElement)}measure(){return this._measureElement.style.fontFamily=this._optionsService.rawOptions.fontFamily,this._measureElement.style.fontSize=`${this._optionsService.rawOptions.fontSize}px`,this._validateAndSet(Number(this._measureElement.offsetWidth)/32,Number(this._measureElement.offsetHeight)),this._result}},Xi=class extends Ji{constructor(e){super(),this._optionsService=e,this._canvas=new OffscreenCanvas(100,100),this._ctx=this._canvas.getContext(`2d`);let t=this._ctx.measureText(`W`);if(!(`width`in t&&`fontBoundingBoxAscent`in t&&`fontBoundingBoxDescent`in t))throw Error(`Required font metrics not supported`)}measure(){this._ctx.font=`${this._optionsService.rawOptions.fontSize}px ${this._optionsService.rawOptions.fontFamily}`;let e=this._ctx.measureText(`W`);return this._validateAndSet(e.width,e.fontBoundingBoxAscent+e.fontBoundingBoxDescent),this._result}},Zi=class extends A{constructor(e,t,n){super(),this._textarea=e,this._window=t,this.mainDocument=n,this._isFocused=!1,this._cachedIsFocused=void 0,this._screenDprMonitor=this._register(new Qi(this._window)),this._onDprChange=this._register(new N),this.onDprChange=this._onDprChange.event,this._onWindowChange=this._register(new N),this.onWindowChange=this._onWindowChange.event,this._register(this.onWindowChange(e=>this._screenDprMonitor.setWindow(e))),this._register(M.forward(this._screenDprMonitor.onDprChange,this._onDprChange)),this._register(P(this._textarea,`focus`,()=>this._isFocused=!0)),this._register(P(this._textarea,`blur`,()=>this._isFocused=!1))}get window(){return this._window}set window(e){this._window!==e&&(this._window=e,this._onWindowChange.fire(this._window))}get dpr(){return this.window.devicePixelRatio}get isFocused(){return this._cachedIsFocused===void 0&&(this._cachedIsFocused=this._isFocused&&this._textarea.ownerDocument.hasFocus(),queueMicrotask(()=>this._cachedIsFocused=void 0)),this._cachedIsFocused}},Qi=class extends A{constructor(e){super(),this._parentWindow=e,this._windowResizeListener=this._register(new Et),this._onDprChange=this._register(new N),this.onDprChange=this._onDprChange.event,this._outerListener=()=>this._setDprAndFireIfDiffers(),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._updateDpr(),this._setWindowResizeListener(),this._register(k(()=>this.clearListener()))}setWindow(e){this._parentWindow=e,this._setWindowResizeListener(),this._setDprAndFireIfDiffers()}_setWindowResizeListener(){this._windowResizeListener.value=P(this._parentWindow,`resize`,()=>this._setDprAndFireIfDiffers())}_setDprAndFireIfDiffers(){this._parentWindow.devicePixelRatio!==this._currentDevicePixelRatio&&this._onDprChange.fire(this._parentWindow.devicePixelRatio),this._updateDpr()}_updateDpr(){this._outerListener&&(this._resolutionMediaMatchList?.removeListener(this._outerListener),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._resolutionMediaMatchList=this._parentWindow.matchMedia(`screen and (resolution: ${this._parentWindow.devicePixelRatio}dppx)`),this._resolutionMediaMatchList.addListener(this._outerListener))}clearListener(){!this._resolutionMediaMatchList||!this._outerListener||(this._resolutionMediaMatchList.removeListener(this._outerListener),this._resolutionMediaMatchList=void 0,this._outerListener=void 0)}},$i=class extends A{constructor(){super(),this.linkProviders=[],this._register(k(()=>this.linkProviders.length=0))}registerLinkProvider(e){return this.linkProviders.push(e),{dispose:()=>{let t=this.linkProviders.indexOf(e);t!==-1&&this.linkProviders.splice(t,1)}}}};function ea(e,t,n){let r=n.getBoundingClientRect(),i=e.getComputedStyle(n),a=parseInt(i.getPropertyValue(`padding-left`)),o=parseInt(i.getPropertyValue(`padding-top`));return[t.clientX-r.left-a,t.clientY-r.top-o]}function ta(e,t,n,r,i,a,o,s,c){if(!a)return;let l=ea(e,t,n);if(l)return l[0]=Math.ceil((l[0]+(c?o/2:0))/o),l[1]=Math.ceil(l[1]/s),l[0]=Math.min(Math.max(l[0],1),r+ +!!c),l[1]=Math.min(Math.max(l[1],1),i),l}var na=class{constructor(e,t){this._renderService=e,this._charSizeService=t}getCoords(e,t,n,r,i){return ta(window,e,t,n,r,this._charSizeService.hasValidSize,this._renderService.dimensions.css.cell.width,this._renderService.dimensions.css.cell.height,i)}getMouseReportCoords(e,t){let n=ea(window,e,t);if(this._charSizeService.hasValidSize)return n[0]=Math.min(Math.max(n[0],0),this._renderService.dimensions.css.canvas.width-1),n[1]=Math.min(Math.max(n[1],0),this._renderService.dimensions.css.canvas.height-1),{col:Math.floor(n[0]/this._renderService.dimensions.css.cell.width),row:Math.floor(n[1]/this._renderService.dimensions.css.cell.height),x:Math.floor(n[0]),y:Math.floor(n[1])}}};na=C([w(0,Ke),w(1,Ue)],na);var ra=class{constructor(e,t){this._renderCallback=e,this._coreBrowserService=t,this._refreshCallbacks=[]}dispose(){this._animationFrame&&=(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),void 0)}addRefreshCallback(e){return this._refreshCallbacks.push(e),this._animationFrame||=this._coreBrowserService.window.requestAnimationFrame(()=>this._innerRefresh()),this._animationFrame}refresh(e,t,n){this._rowCount=n,e=e===void 0?0:e,t=t===void 0?this._rowCount-1:t,this._rowStart=this._rowStart===void 0?e:Math.min(this._rowStart,e),this._rowEnd=this._rowEnd===void 0?t:Math.max(this._rowEnd,t),!this._animationFrame&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._innerRefresh()))}_innerRefresh(){if(this._animationFrame=void 0,this._rowStart===void 0||this._rowEnd===void 0||this._rowCount===void 0){this._runRefreshCallbacks();return}let e=Math.max(this._rowStart,0),t=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(e,t),this._runRefreshCallbacks()}_runRefreshCallbacks(){for(let e of this._refreshCallbacks)e(0);this._refreshCallbacks=[]}},ia={};ce(ia,{getSafariVersion:()=>da,isChromeOS:()=>_a,isFirefox:()=>ca,isIpad:()=>pa,isIphone:()=>ma,isLegacyEdge:()=>la,isLinux:()=>ga,isMac:()=>fa,isNode:()=>aa,isSafari:()=>ua,isWindows:()=>ha});var aa=typeof process<`u`&&`title`in process,oa=aa?`node`:navigator.userAgent,sa=aa?`node`:navigator.platform,ca=oa.includes(`Firefox`),la=oa.includes(`Edge`),ua=/^((?!chrome|android).)*safari/i.test(oa);function da(){if(!ua)return 0;let e=oa.match(/Version\/(\d+)/);return e===null||e.length<2?0:parseInt(e[1])}var fa=[`Macintosh`,`MacIntel`,`MacPPC`,`Mac68K`].includes(sa),pa=sa===`iPad`,ma=sa===`iPhone`,ha=[`Windows`,`Win16`,`Win32`,`WinCE`].includes(sa),ga=sa.indexOf(`Linux`)>=0,_a=/\bCrOS\b/.test(oa),va=class{constructor(){this._tasks=[],this._i=0}enqueue(e){this._tasks.push(e),this._start()}flush(){for(;this._ii){r-t<-20&&console.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(r-t))}ms`),this._start();return}r=i}this.clear()}},ya=class extends va{_requestCallback(e){return setTimeout(()=>e(this._createDeadline(16)))}_cancelCallback(e){clearTimeout(e)}_createDeadline(e){let t=performance.now()+e;return{timeRemaining:()=>Math.max(0,t-performance.now())}}},ba=class extends va{_requestCallback(e){return requestIdleCallback(e)}_cancelCallback(e){cancelIdleCallback(e)}},xa=!aa&&`requestIdleCallback`in window?ba:ya,Sa=class{constructor(){this._queue=new xa}set(e){this._queue.clear(),this._queue.enqueue(e)}flush(){this._queue.flush()}},Ca=class extends A{constructor(e,t,n,r,i,a,o,s,c){super(),this._rowCount=e,this._optionsService=n,this._charSizeService=r,this._coreService=i,this._coreBrowserService=s,this._renderer=this._register(new Et),this._pausedResizeTask=new Sa,this._observerDisposable=this._register(new Et),this._isPaused=!1,this._needsFullRefresh=!1,this._isNextRenderRedrawOnly=!0,this._needsSelectionRefresh=!1,this._canvasWidth=0,this._canvasHeight=0,this._selectionState={start:void 0,end:void 0,columnSelectMode:!1},this._onDimensionsChange=this._register(new N),this.onDimensionsChange=this._onDimensionsChange.event,this._onRenderedViewportChange=this._register(new N),this.onRenderedViewportChange=this._onRenderedViewportChange.event,this._onRender=this._register(new N),this.onRender=this._onRender.event,this._onRefreshRequest=this._register(new N),this.onRefreshRequest=this._onRefreshRequest.event,this._renderDebouncer=new ra((e,t)=>this._renderRows(e,t),this._coreBrowserService),this._register(this._renderDebouncer),this._syncOutputHandler=new wa(this._coreBrowserService,this._coreService,()=>this._fullRefresh()),this._register(k(()=>this._syncOutputHandler.dispose())),this._register(this._coreBrowserService.onDprChange(()=>this.handleDevicePixelRatioChange())),this._register(o.onResize(()=>this._fullRefresh())),this._register(o.buffers.onBufferActivate(()=>this._renderer.value?.clear())),this._register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this._register(this._charSizeService.onCharSizeChange(()=>this.handleCharSizeChanged())),this._register(a.onDecorationRegistered(()=>this._fullRefresh())),this._register(a.onDecorationRemoved(()=>this._fullRefresh())),this._register(this._optionsService.onMultipleOptionChange([`customGlyphs`,`drawBoldTextInBrightColors`,`letterSpacing`,`lineHeight`,`fontFamily`,`fontSize`,`fontWeight`,`fontWeightBold`,`minimumContrastRatio`,`rescaleOverlappingGlyphs`],()=>{this.clear(),this.handleResize(o.cols,o.rows),this._fullRefresh()})),this._register(this._optionsService.onMultipleOptionChange([`cursorBlink`,`cursorStyle`],()=>this.refreshRows(o.buffer.y,o.buffer.y,!0))),this._register(c.onChangeColors(()=>this._fullRefresh())),this._registerIntersectionObserver(this._coreBrowserService.window,t),this._register(this._coreBrowserService.onWindowChange(e=>this._registerIntersectionObserver(e,t)))}get dimensions(){return this._renderer.value.dimensions}_registerIntersectionObserver(e,t){if(`IntersectionObserver`in e){let n=new e.IntersectionObserver(e=>this._handleIntersectionChange(e[e.length-1]),{threshold:0});n.observe(t),this._observerDisposable.value=k(()=>n.disconnect())}}_handleIntersectionChange(e){this._isPaused=e.isIntersecting===void 0?e.intersectionRatio===0:!e.isIntersecting,!this._isPaused&&!this._charSizeService.hasValidSize&&this._charSizeService.measure(),!this._isPaused&&this._needsFullRefresh&&(this._pausedResizeTask.flush(),this.refreshRows(0,this._rowCount-1),this._needsFullRefresh=!1)}refreshRows(e,t,n=!1){if(this._isPaused){this._needsFullRefresh=!0;return}if(this._coreService.decPrivateModes.synchronizedOutput){this._syncOutputHandler.bufferRows(e,t);return}let r=this._syncOutputHandler.flush();r&&(e=Math.min(e,r.start),t=Math.max(t,r.end)),n||(this._isNextRenderRedrawOnly=!1),this._renderDebouncer.refresh(e,t,this._rowCount)}_renderRows(e,t){if(this._renderer.value){if(this._coreService.decPrivateModes.synchronizedOutput){this._syncOutputHandler.bufferRows(e,t);return}e=Math.min(e,this._rowCount-1),t=Math.min(t,this._rowCount-1),this._renderer.value.renderRows(e,t),this._needsSelectionRefresh&&=(this._renderer.value.handleSelectionChanged(this._selectionState.start,this._selectionState.end,this._selectionState.columnSelectMode),!1),this._isNextRenderRedrawOnly||this._onRenderedViewportChange.fire({start:e,end:t}),this._onRender.fire({start:e,end:t}),this._isNextRenderRedrawOnly=!0}}resize(e,t){this._rowCount=t,this._fireOnCanvasResize()}_handleOptionsChanged(){this._renderer.value&&(this.refreshRows(0,this._rowCount-1),this._fireOnCanvasResize())}_fireOnCanvasResize(){this._renderer.value&&(this._renderer.value.dimensions.css.canvas.width===this._canvasWidth&&this._renderer.value.dimensions.css.canvas.height===this._canvasHeight||this._onDimensionsChange.fire(this._renderer.value.dimensions))}hasRenderer(){return!!this._renderer.value}setRenderer(e){this._renderer.value=e,this._renderer.value&&(this._renderer.value.onRequestRedraw(e=>this.refreshRows(e.start,e.end,!0)),this._needsSelectionRefresh=!0,this._fullRefresh())}addRefreshCallback(e){return this._renderDebouncer.addRefreshCallback(e)}_fullRefresh(){this._isPaused?this._needsFullRefresh=!0:this.refreshRows(0,this._rowCount-1)}clearTextureAtlas(){this._renderer.value&&(this._renderer.value.clearTextureAtlas?.(),this._fullRefresh())}handleDevicePixelRatioChange(){this._charSizeService.measure(),this._renderer.value&&(this._renderer.value.handleDevicePixelRatioChange(),this.refreshRows(0,this._rowCount-1))}handleResize(e,t){this._renderer.value&&(this._isPaused?this._pausedResizeTask.set(()=>this._renderer.value?.handleResize(e,t)):this._renderer.value.handleResize(e,t),this._fullRefresh())}handleCharSizeChanged(){this._renderer.value?.handleCharSizeChanged()}handleBlur(){this._renderer.value?.handleBlur()}handleFocus(){this._renderer.value?.handleFocus()}handleSelectionChanged(e,t,n){this._selectionState.start=e,this._selectionState.end=t,this._selectionState.columnSelectMode=n,this._renderer.value?.handleSelectionChanged(e,t,n)}handleCursorMove(){this._renderer.value?.handleCursorMove()}clear(){this._renderer.value?.clear()}};Ca=C([w(2,O),w(3,Ue),w(4,Pe),w(5,Be),w(6,D),w(7,We),w(8,Ye)],Ca);var wa=class{constructor(e,t,n){this._coreBrowserService=e,this._coreService=t,this._onTimeout=n,this._start=0,this._end=0,this._isBuffering=!1}bufferRows(e,t){this._isBuffering?(this._start=Math.min(this._start,e),this._end=Math.max(this._end,t)):(this._start=e,this._end=t,this._isBuffering=!0),this._timeout===void 0&&(this._timeout=this._coreBrowserService.window.setTimeout(()=>{this._timeout=void 0,this._coreService.decPrivateModes.synchronizedOutput=!1,this._onTimeout()},1e3))}flush(){if(this._timeout!==void 0&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0),!this._isBuffering)return;let e={start:this._start,end:this._end};return this._isBuffering=!1,e}dispose(){this._timeout!==void 0&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0)}};function Ta(e,t,n,r){let i=n.buffer.x,a=n.buffer.y;if(!n.buffer.hasScrollback)return Oa(i,a,e,t,n,r)+ka(a,t,n,r)+Aa(i,a,e,t,n,r);let o;if(a===t)return o=i>e?`D`:`C`,La(Math.abs(i-e),Ia(o,r));o=a>t?`D`:`C`;let s=Math.abs(a-t);return La(Da(a>t?e:i,n)+(s-1)*n.cols+1+Ea(a>t?i:e,n),Ia(o,r))}function Ea(e,t){return e-1}function Da(e,t){return t.cols-e}function Oa(e,t,n,r,i,a){return ka(t,r,i,a).length===0?``:La(Fa(e,t,e,t-Ma(t,i),!1,i).length,Ia(`D`,a))}function ka(e,t,n,r){let i=e-Ma(e,n),a=t-Ma(t,n);return La(Math.abs(i-a)-ja(e,t,n),Ia(Pa(e,t),r))}function Aa(e,t,n,r,i,a){let o;o=ka(t,r,i,a).length>0?r-Ma(r,i):t;let s=r,c=Na(e,t,n,r,i,a);return La(Fa(e,o,n,s,c===`C`,i).length,Ia(c,a))}function ja(e,t,n){let r=0,i=e-Ma(e,n),a=t-Ma(t,n);for(let o=0;o=0&&e0?r-Ma(r,i):t,e=n&&ot?`A`:`B`}function Fa(e,t,n,r,i,a){let o=e,s=t,c=``;for(;(o!==n||s!==r)&&s>=0&&sa.cols-1?(c+=a.buffer.translateBufferLineToString(s,!1,e,o),o=0,e=0,s++):!i&&o<0&&(c+=a.buffer.translateBufferLineToString(s,!1,0,e+1),o=a.cols-1,e=o,s--);return c+a.buffer.translateBufferLineToString(s,!1,e,o)}function Ia(e,t){let n=t?`O`:`[`;return L.ESC+n+e}function La(e,t){e=Math.floor(e);let n=``;for(let r=0;rthis._bufferService.cols?e%this._bufferService.cols===0?[this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)-1]:[e%this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)]:[e,this.selectionStart[1]]}if(this.selectionStartLength&&this.selectionEnd[1]===this.selectionStart[1]){let e=this.selectionStart[0]+this.selectionStartLength;return e>this._bufferService.cols?[e%this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)]:[Math.max(e,this.selectionEnd[0]),this.selectionEnd[1]]}return this.selectionEnd}}areSelectionValuesReversed(){let e=this.selectionStart,t=this.selectionEnd;return!e||!t?!1:e[1]>t[1]||e[1]===t[1]&&e[0]>t[0]}handleTrim(e){return this.selectionStart&&(this.selectionStart[1]-=e),this.selectionEnd&&(this.selectionEnd[1]-=e),this.selectionEnd&&this.selectionEnd[1]<0?(this.clearSelection(),!0):(this.selectionStart&&this.selectionStart[1]<0&&(this.selectionStart[1]=0),!1)}};function za(e,t){if(e.start.y>e.end.y)throw Error(`Buffer range end (${e.end.x}, ${e.end.y}) cannot be before start (${e.start.x}, ${e.start.y})`);return t*(e.end.y-e.start.y)+(e.end.x-e.start.x+1)}var Ba=50,Va=15,Ha=50,Ua=500,Wa=RegExp(`\xA0`,`g`),Ga=class extends A{constructor(e,t,n,r,i,a,o,s,c){super(),this._element=e,this._screenElement=t,this._linkifier=n,this._bufferService=r,this._coreService=i,this._mouseService=a,this._optionsService=o,this._renderService=s,this._coreBrowserService=c,this._dragScrollAmount=0,this._enabled=!0,this._workCell=new De,this._mouseDownTimeStamp=0,this._oldHasSelection=!1,this._oldSelectionStart=void 0,this._oldSelectionEnd=void 0,this._onLinuxMouseSelection=this._register(new N),this.onLinuxMouseSelection=this._onLinuxMouseSelection.event,this._onRedrawRequest=this._register(new N),this.onRequestRedraw=this._onRedrawRequest.event,this._onSelectionChange=this._register(new N),this.onSelectionChange=this._onSelectionChange.event,this._onRequestScrollLines=this._register(new N),this.onRequestScrollLines=this._onRequestScrollLines.event,this._mouseMoveListener=e=>this._handleMouseMove(e),this._mouseUpListener=e=>this._handleMouseUp(e),this._coreService.onUserInput(()=>{this.hasSelection&&this.clearSelection()}),this._trimListener=this._bufferService.buffer.lines.onTrim(e=>this._handleTrim(e)),this._register(this._bufferService.buffers.onBufferActivate(e=>this._handleBufferActivate(e))),this.enable(),this._model=new Ra(this._bufferService),this._activeSelectionMode=0,this._register(k(()=>{this._removeMouseDownListeners()})),this._register(this._bufferService.onResize(e=>{e.rowsChanged&&this.clearSelection()}))}reset(){this.clearSelection()}disable(){this.clearSelection(),this._enabled=!1}enable(){this._enabled=!0}get selectionStart(){return this._model.finalSelectionStart}get selectionEnd(){return this._model.finalSelectionEnd}get hasSelection(){let e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;return!e||!t?!1:e[0]!==t[0]||e[1]!==t[1]}get selectionText(){let e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;if(!e||!t)return``;let n=this._bufferService.buffer,r=[];if(this._activeSelectionMode===3){if(e[0]===t[0])return``;let i=e[0]e.replace(Wa,` `)).join(ha?`\r -`:` -`)}clearSelection(){this._model.clearSelection(),this._removeMouseDownListeners(),this.refresh(),this._onSelectionChange.fire()}refresh(e){this._refreshAnimationFrame||=this._coreBrowserService.window.requestAnimationFrame(()=>this._refresh()),ga&&e&&this.selectionText.length&&this._onLinuxMouseSelection.fire(this.selectionText)}_refresh(){this._refreshAnimationFrame=void 0,this._onRedrawRequest.fire({start:this._model.finalSelectionStart,end:this._model.finalSelectionEnd,columnSelectMode:this._activeSelectionMode===3})}_isClickInSelection(e){let t=this._getMouseBufferCoords(e),n=this._model.finalSelectionStart,r=this._model.finalSelectionEnd;return!n||!r||!t?!1:this._areCoordsInSelection(t,n,r)}isCellInSelection(e,t){let n=this._model.finalSelectionStart,r=this._model.finalSelectionEnd;return!n||!r?!1:this._areCoordsInSelection([e,t],n,r)}_areCoordsInSelection(e,t,n){return e[1]>t[1]&&e[1]=t[0]&&e[0]=t[0]}_selectWordAtCursor(e,t){let n=this._linkifier.currentLink?.link?.range;if(n)return this._model.selectionStart=[n.start.x-1,n.start.y-1],this._model.selectionStartLength=za(n,this._bufferService.cols),this._model.selectionEnd=void 0,!0;let r=this._getMouseBufferCoords(e);return r?(this._selectWordAt(r,t),this._model.selectionEnd=void 0,!0):!1}selectAll(){this._model.isSelectAllActive=!0,this.refresh(),this._onSelectionChange.fire()}selectLines(e,t){this._model.clearSelection(),e=Math.max(e,0),t=Math.min(t,this._bufferService.buffer.lines.length-1),this._model.selectionStart=[0,e],this._model.selectionEnd=[this._bufferService.cols,t],this.refresh(),this._onSelectionChange.fire()}_handleTrim(e){this._model.handleTrim(e)&&this.refresh()}_getMouseBufferCoords(e){let t=this._mouseService.getCoords(e,this._screenElement,this._bufferService.cols,this._bufferService.rows,!0);if(t)return t[0]--,t[1]--,t[1]+=this._bufferService.buffer.ydisp,t}_getMouseEventScrollAmount(e){let t=ea(this._coreBrowserService.window,e,this._screenElement)[1],n=this._renderService.dimensions.css.canvas.height;return t>=0&&t<=n?0:(t>n&&(t-=n),t=Math.min(Math.max(t,-Ba),Ba),t/=Ba,t/Math.abs(t)+Math.round(t*(Va-1)))}shouldForceSelection(e){return fa?e.altKey&&this._optionsService.rawOptions.macOptionClickForcesSelection:e.shiftKey}handleMouseDown(e){if(this._mouseDownTimeStamp=e.timeStamp,!(e.button===2&&this.hasSelection)&&e.button===0){if(!this._enabled){if(!this.shouldForceSelection(e))return;e.stopPropagation()}e.preventDefault(),this._dragScrollAmount=0,this._enabled&&e.shiftKey?this._handleIncrementalClick(e):e.detail===1?this._handleSingleClick(e):e.detail===2?this._handleDoubleClick(e):e.detail===3&&this._handleTripleClick(e),this._addMouseDownListeners(),this.refresh(!0)}}_addMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.addEventListener(`mousemove`,this._mouseMoveListener),this._screenElement.ownerDocument.addEventListener(`mouseup`,this._mouseUpListener)),this._dragScrollIntervalTimer=this._coreBrowserService.window.setInterval(()=>this._dragScroll(),Ha)}_removeMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.removeEventListener(`mousemove`,this._mouseMoveListener),this._screenElement.ownerDocument.removeEventListener(`mouseup`,this._mouseUpListener)),this._coreBrowserService.window.clearInterval(this._dragScrollIntervalTimer),this._dragScrollIntervalTimer=void 0}_handleIncrementalClick(e){this._model.selectionStart&&(this._model.selectionEnd=this._getMouseBufferCoords(e))}_handleSingleClick(e){if(this._model.selectionStartLength=0,this._model.isSelectAllActive=!1,this._activeSelectionMode=this.shouldColumnSelect(e)?3:0,this._model.selectionStart=this._getMouseBufferCoords(e),!this._model.selectionStart)return;this._model.selectionEnd=void 0;let t=this._bufferService.buffer.lines.get(this._model.selectionStart[1]);t&&t.length!==this._model.selectionStart[0]&&t.hasWidth(this._model.selectionStart[0])===0&&this._model.selectionStart[0]++}_handleDoubleClick(e){this._selectWordAtCursor(e,!0)&&(this._activeSelectionMode=1)}_handleTripleClick(e){let t=this._getMouseBufferCoords(e);t&&(this._activeSelectionMode=2,this._selectLineAt(t[1]))}shouldColumnSelect(e){return e.altKey&&!(fa&&this._optionsService.rawOptions.macOptionClickForcesSelection)}_handleMouseMove(e){if(e.stopImmediatePropagation(),!this._model.selectionStart)return;let t=this._model.selectionEnd?[this._model.selectionEnd[0],this._model.selectionEnd[1]]:null;if(this._model.selectionEnd=this._getMouseBufferCoords(e),!this._model.selectionEnd){this.refresh(!0);return}this._activeSelectionMode===2?this._model.selectionEnd[1]0?this._model.selectionEnd[0]=this._bufferService.cols:this._dragScrollAmount<0&&(this._model.selectionEnd[0]=0));let n=this._bufferService.buffer;if(this._model.selectionEnd[1]0?(this._activeSelectionMode!==3&&(this._model.selectionEnd[0]=this._bufferService.cols),this._model.selectionEnd[1]=Math.min(e.ydisp+this._bufferService.rows,e.lines.length-1)):(this._activeSelectionMode!==3&&(this._model.selectionEnd[0]=0),this._model.selectionEnd[1]=e.ydisp),this.refresh()}}_handleMouseUp(e){let t=e.timeStamp-this._mouseDownTimeStamp;if(this._removeMouseDownListeners(),this.selectionText.length<=1&&tthis._handleTrim(e))}_convertViewportColToCharacterIndex(e,t){let n=t;for(let r=0;t>=r;r++){let i=e.loadCell(r,this._workCell).getChars().length;this._workCell.getWidth()===0?n--:i>1&&t!==r&&(n+=i-1)}return n}setSelection(e,t,n){this._model.clearSelection(),this._removeMouseDownListeners(),this._model.selectionStart=[e,t],this._model.selectionStartLength=n,this.refresh(),this._fireEventIfSelectionChanged()}rightClickSelect(e){this._isClickInSelection(e)||(this._selectWordAtCursor(e,!1)&&this.refresh(!0),this._fireEventIfSelectionChanged())}_getWordAt(e,t,n=!0,r=!0){if(e[0]>=this._bufferService.cols)return;let i=this._bufferService.buffer,a=i.lines.get(e[1]);if(!a)return;let o=i.translateBufferLineToString(e[1],!1),s=this._convertViewportColToCharacterIndex(a,e[0]),c=s,l=e[0]-s,u=0,d=0,f=0,p=0;if(o.charAt(s)===` `){for(;s>0&&o.charAt(s-1)===` `;)s--;for(;c1&&(p+=r-1,c+=r-1);t>0&&s>0&&!this._isCharWordSeparator(a.loadCell(t-1,this._workCell));){a.loadCell(t-1,this._workCell);let e=this._workCell.getChars().length;this._workCell.getWidth()===0?(u++,t--):e>1&&(f+=e-1,s-=e-1),s--,t--}for(;n1&&(p+=e-1,c+=e-1),c++,n++}}c++;let m=s+l-u+f,h=Math.min(this._bufferService.cols,c-s+u+d-f-p);if(!(!t&&o.slice(s,c).trim()===``)){if(n&&m===0&&a.getCodePoint(0)!==32){let t=i.lines.get(e[1]-1);if(t&&a.isWrapped&&t.getCodePoint(this._bufferService.cols-1)!==32){let t=this._getWordAt([this._bufferService.cols-1,e[1]-1],!1,!0,!1);if(t){let e=this._bufferService.cols-t.start;m-=e,h+=e}}}if(r&&m+h===this._bufferService.cols&&a.getCodePoint(this._bufferService.cols-1)!==32){let t=i.lines.get(e[1]+1);if(t?.isWrapped&&t.getCodePoint(0)!==32){let t=this._getWordAt([0,e[1]+1],!1,!1,!0);t&&(h+=t.length)}}return{start:m,length:h}}}_selectWordAt(e,t){let n=this._getWordAt(e,t);if(n){for(;n.start<0;)n.start+=this._bufferService.cols,e[1]--;this._model.selectionStart=[n.start,e[1]],this._model.selectionStartLength=n.length}}_selectToWordAt(e){let t=this._getWordAt(e,!0);if(t){let n=e[1];for(;t.start<0;)t.start+=this._bufferService.cols,n--;if(!this._model.areSelectionValuesReversed())for(;t.start+t.length>this._bufferService.cols;)t.length-=this._bufferService.cols,n++;this._model.selectionEnd=[this._model.areSelectionValuesReversed()?t.start:t.start+t.length,n]}}_isCharWordSeparator(e){return e.getWidth()!==0&&this._optionsService.rawOptions.wordSeparator.indexOf(e.getChars())>=0}_selectLineAt(e){let t=this._bufferService.buffer.getWrappedRangeForLine(e),n={start:{x:0,y:t.first},end:{x:this._bufferService.cols-1,y:t.last}};this._model.selectionStart=[0,t.first],this._model.selectionEnd=void 0,this._model.selectionStartLength=za(n,this._bufferService.cols)}};Ga=C([w(3,D),w(4,Pe),w(5,Ge),w(6,O),w(7,Ke),w(8,We)],Ga);var Ka=class{constructor(){this._data={}}set(e,t,n){this._data[e]||(this._data[e]={}),this._data[e][t]=n}get(e,t){return this._data[e]?this._data[e][t]:void 0}clear(){this._data={}}},qa=class{constructor(){this._color=new Ka,this._css=new Ka}setCss(e,t,n){this._css.set(e,t,n)}getCss(e,t){return this._css.get(e,t)}setColor(e,t,n){this._color.set(e,t,n)}getColor(e,t){return this._color.get(e,t)}clear(){this._color.clear(),this._css.clear()}},K=Object.freeze((()=>{let e=[W.toColor(`#2e3436`),W.toColor(`#cc0000`),W.toColor(`#4e9a06`),W.toColor(`#c4a000`),W.toColor(`#3465a4`),W.toColor(`#75507b`),W.toColor(`#06989a`),W.toColor(`#d3d7cf`),W.toColor(`#555753`),W.toColor(`#ef2929`),W.toColor(`#8ae234`),W.toColor(`#fce94f`),W.toColor(`#729fcf`),W.toColor(`#ad7fa8`),W.toColor(`#34e2e2`),W.toColor(`#eeeeec`)],t=[0,95,135,175,215,255];for(let n=0;n<216;n++){let r=t[n/36%6|0],i=t[n/6%6|0],a=t[n%6];e.push({css:H.toCss(r,i,a),rgba:H.toRgba(r,i,a)})}for(let t=0;t<24;t++){let n=8+t*10;e.push({css:H.toCss(n,n,n),rgba:H.toRgba(n,n,n)})}return e})()),Ja=W.toColor(`#ffffff`),Ya=W.toColor(`#000000`),Xa=W.toColor(`#ffffff`),Za=Ya,Qa={css:`rgba(255, 255, 255, 0.3)`,rgba:4294967117},$a=Ja,eo=class extends A{constructor(e){super(),this._optionsService=e,this._contrastCache=new qa,this._halfContrastCache=new qa,this._onChangeColors=this._register(new N),this.onChangeColors=this._onChangeColors.event,this._colors={foreground:Ja,background:Ya,cursor:Xa,cursorAccent:Za,selectionForeground:void 0,selectionBackgroundTransparent:Qa,selectionBackgroundOpaque:U.blend(Ya,Qa),selectionInactiveBackgroundTransparent:Qa,selectionInactiveBackgroundOpaque:U.blend(Ya,Qa),scrollbarSliderBackground:U.opacity(Ja,.2),scrollbarSliderHoverBackground:U.opacity(Ja,.4),scrollbarSliderActiveBackground:U.opacity(Ja,.5),overviewRulerBorder:Ja,ansi:K.slice(),contrastCache:this._contrastCache,halfContrastCache:this._halfContrastCache},this._updateRestoreColors(),this._setTheme(this._optionsService.rawOptions.theme),this._register(this._optionsService.onSpecificOptionChange(`minimumContrastRatio`,()=>this._contrastCache.clear())),this._register(this._optionsService.onSpecificOptionChange(`theme`,()=>this._setTheme(this._optionsService.rawOptions.theme)))}get colors(){return this._colors}_setTheme(e={}){let t=this._colors;if(t.foreground=q(e.foreground,Ja),t.background=q(e.background,Ya),t.cursor=U.blend(t.background,q(e.cursor,Xa)),t.cursorAccent=U.blend(t.background,q(e.cursorAccent,Za)),t.selectionBackgroundTransparent=q(e.selectionBackground,Qa),t.selectionBackgroundOpaque=U.blend(t.background,t.selectionBackgroundTransparent),t.selectionInactiveBackgroundTransparent=q(e.selectionInactiveBackground,t.selectionBackgroundTransparent),t.selectionInactiveBackgroundOpaque=U.blend(t.background,t.selectionInactiveBackgroundTransparent),t.selectionForeground=e.selectionForeground?q(e.selectionForeground,Ci):void 0,t.selectionForeground===Ci&&(t.selectionForeground=void 0),U.isOpaque(t.selectionBackgroundTransparent)&&(t.selectionBackgroundTransparent=U.opacity(t.selectionBackgroundTransparent,.3)),U.isOpaque(t.selectionInactiveBackgroundTransparent)&&(t.selectionInactiveBackgroundTransparent=U.opacity(t.selectionInactiveBackgroundTransparent,.3)),t.scrollbarSliderBackground=q(e.scrollbarSliderBackground,U.opacity(t.foreground,.2)),t.scrollbarSliderHoverBackground=q(e.scrollbarSliderHoverBackground,U.opacity(t.foreground,.4)),t.scrollbarSliderActiveBackground=q(e.scrollbarSliderActiveBackground,U.opacity(t.foreground,.5)),t.overviewRulerBorder=q(e.overviewRulerBorder,$a),t.ansi=K.slice(),t.ansi[0]=q(e.black,K[0]),t.ansi[1]=q(e.red,K[1]),t.ansi[2]=q(e.green,K[2]),t.ansi[3]=q(e.yellow,K[3]),t.ansi[4]=q(e.blue,K[4]),t.ansi[5]=q(e.magenta,K[5]),t.ansi[6]=q(e.cyan,K[6]),t.ansi[7]=q(e.white,K[7]),t.ansi[8]=q(e.brightBlack,K[8]),t.ansi[9]=q(e.brightRed,K[9]),t.ansi[10]=q(e.brightGreen,K[10]),t.ansi[11]=q(e.brightYellow,K[11]),t.ansi[12]=q(e.brightBlue,K[12]),t.ansi[13]=q(e.brightMagenta,K[13]),t.ansi[14]=q(e.brightCyan,K[14]),t.ansi[15]=q(e.brightWhite,K[15]),e.extendedAnsi){let n=Math.min(t.ansi.length-16,e.extendedAnsi.length);for(let r=0;re.index-t.index),r=[];for(let t of n){let n=this._services.get(t.id);if(!n)throw Error(`[createInstance] ${e.name} depends on UNKNOWN service ${t.id._id}.`);r.push(n)}let i=n.length>0?n[0].index:t.length;if(t.length!==i)throw Error(`[createInstance] First service dependency of ${e.name} at position ${i+1} conflicts with ${t.length} static arguments`);return new e(...t,...r)}},ro={trace:0,debug:1,info:2,warn:3,error:4,off:5},io=`xterm.js: `,ao=class extends A{constructor(e){super(),this._optionsService=e,this._logLevel=5,this._updateLogLevel(),this._register(this._optionsService.onSpecificOptionChange(`logLevel`,()=>this._updateLogLevel())),oo=this}get logLevel(){return this._logLevel}_updateLogLevel(){this._logLevel=ro[this._optionsService.rawOptions.logLevel]}_evalLazyOptionalParams(e){for(let t=0;tthis._length)for(let t=this._length;t=e;t--)this._array[this._getCyclicIndex(t+n.length)]=this._array[this._getCyclicIndex(t)];for(let t=0;tthis._maxLength){let e=this._length+n.length-this._maxLength;this._startIndex+=e,this._length=this._maxLength,this.onTrimEmitter.fire(e)}else this._length+=n.length}trimStart(e){e>this._length&&(e=this._length),this._startIndex+=e,this._length-=e,this.onTrimEmitter.fire(e)}shiftElements(e,t,n){if(!(t<=0)){if(e<0||e>=this._length)throw Error(`start argument out of range`);if(e+n<0)throw Error(`Cannot shift elements in list beyond index 0`);if(n>0){for(let r=t-1;r>=0;r--)this.set(e+r+n,this.get(e+r));let r=e+t+n-this._length;if(r>0)for(this._length+=r;this._length>this._maxLength;)this._length--,this._startIndex++,this.onTrimEmitter.fire(1)}else for(let r=0;r>22,t&2097152?this._combined[e].charCodeAt(this._combined[e].length-1):n]}set(e,t){this._data[e*J+1]=t[0],t[1].length>1?(this._combined[e]=t[1],this._data[e*J+0]=e|2097152|t[2]<<22):this._data[e*J+0]=t[1].charCodeAt(0)|t[2]<<22}getWidth(e){return this._data[e*J+0]>>22}hasWidth(e){return this._data[e*J+0]&12582912}getFg(e){return this._data[e*J+1]}getBg(e){return this._data[e*J+2]}hasContent(e){return this._data[e*J+0]&4194303}getCodePoint(e){let t=this._data[e*J+0];return t&2097152?this._combined[e].charCodeAt(this._combined[e].length-1):t&2097151}isCombined(e){return this._data[e*J+0]&2097152}getString(e){let t=this._data[e*J+0];return t&2097152?this._combined[e]:t&2097151?ye(t&2097151):``}isProtected(e){return this._data[e*J+2]&536870912}loadCell(e,t){return co=e*J,t.content=this._data[co+0],t.fg=this._data[co+1],t.bg=this._data[co+2],t.content&2097152&&(t.combinedData=this._combined[e]),t.bg&268435456&&(t.extended=this._extendedAttrs[e]),t}setCell(e,t){t.content&2097152&&(this._combined[e]=t.combinedData),t.bg&268435456&&(this._extendedAttrs[e]=t.extended),this._data[e*J+0]=t.content,this._data[e*J+1]=t.fg,this._data[e*J+2]=t.bg}setCellFromCodepoint(e,t,n,r){r.bg&268435456&&(this._extendedAttrs[e]=r.extended),this._data[e*J+0]=t|n<<22,this._data[e*J+1]=r.fg,this._data[e*J+2]=r.bg}addCodepointToCell(e,t,n){let r=this._data[e*J+0];r&2097152?this._combined[e]+=ye(t):r&2097151?(this._combined[e]=ye(r&2097151)+ye(t),r&=-2097152,r|=2097152):r=t|1<<22,n&&(r&=-12582913,r|=n<<22),this._data[e*J+0]=r}insertCells(e,t,n){if(e%=this.length,e&&this.getWidth(e-1)===2&&this.setCellFromCodepoint(e-1,0,1,n),t=0;--n)this.setCell(e+t+n,this.loadCell(e+n,r));for(let r=0;rthis.length){if(this._data.buffer.byteLength>=n*4)this._data=new Uint32Array(this._data.buffer,0,n);else{let e=new Uint32Array(n);e.set(this._data),this._data=e}for(let n=this.length;n=e&&delete this._combined[r]}let r=Object.keys(this._extendedAttrs);for(let t=0;t=e&&delete this._extendedAttrs[n]}}return this.length=e,n*4*lo=0;--e)if(this._data[e*J+0]&4194303)return e+(this._data[e*J+0]>>22);return 0}getNoBgTrimmedLength(){for(let e=this.length-1;e>=0;--e)if(this._data[e*J+0]&4194303||this._data[e*J+2]&50331648)return e+(this._data[e*J+0]>>22);return 0}copyCellsFrom(e,t,n,r,i){let a=e._data;if(i)for(let i=r-1;i>=0;i--){for(let e=0;e=t&&(this._combined[i-t+n]=e._combined[i])}}translateToString(e,t,n,r){t??=0,n??=this.length,e&&(n=Math.min(n,this.getTrimmedLength())),r&&(r.length=0);let i=``;for(;t>22||1}return r&&r.push(t),i}};function fo(e,t,n,r,i,a){let o=[];for(let s=0;s=s&&r0&&(e>d||u[e].getTrimmedLength()===0);e--)h++;h>0&&(o.push(s+u.length-h),o.push(h)),s+=u.length-1}return o}function po(e,t){let n=[],r=0,i=t[r],a=0;for(let o=0;ogo(e,r,t)).reduce((e,t)=>e+t),a=0,o=0,s=0;for(;sc&&(a-=c,o++);let l=e[o].getWidth(a-1)===2;l&&a--;let u=l?n-1:n;r.push(u),s+=u}return r}function go(e,t,n){if(t===e.length-1)return e[t].getTrimmedLength();let r=!e[t].hasContent(n-1)&&e[t].getWidth(n-1)===1,i=e[t+1].getWidth(0)===2;return r&&i?n-1:n}var _o=class e{constructor(t){this.line=t,this.isDisposed=!1,this._disposables=[],this._id=e._nextId++,this._onDispose=this.register(new N),this.onDispose=this._onDispose.event}get id(){return this._id}dispose(){this.isDisposed||(this.isDisposed=!0,this.line=-1,this._onDispose.fire(),St(this._disposables),this._disposables.length=0)}register(e){return this._disposables.push(e),e}};_o._nextId=1;var vo=_o,X={},yo=X.B;X[0]={"`":`◆`,a:`▒`,b:`␉`,c:`␌`,d:`␍`,e:`␊`,f:`°`,g:`±`,h:`␤`,i:`␋`,j:`┘`,k:`┐`,l:`┌`,m:`└`,n:`┼`,o:`⎺`,p:`⎻`,q:`─`,r:`⎼`,s:`⎽`,t:`├`,u:`┤`,v:`┴`,w:`┬`,x:`│`,y:`≤`,z:`≥`,"{":`π`,"|":`≠`,"}":`£`,"~":`·`},X.A={"#":`£`},X.B=void 0,X[4]={"#":`£`,"@":`¾`,"[":`ij`,"\\":`½`,"]":`|`,"{":`¨`,"|":`f`,"}":`¼`,"~":`´`},X.C=X[5]={"[":`Ä`,"\\":`Ö`,"]":`Å`,"^":`Ü`,"`":`é`,"{":`ä`,"|":`ö`,"}":`å`,"~":`ü`},X.R={"#":`£`,"@":`à`,"[":`°`,"\\":`ç`,"]":`§`,"{":`é`,"|":`ù`,"}":`è`,"~":`¨`},X.Q={"@":`à`,"[":`â`,"\\":`ç`,"]":`ê`,"^":`î`,"`":`ô`,"{":`é`,"|":`ù`,"}":`è`,"~":`û`},X.K={"@":`§`,"[":`Ä`,"\\":`Ö`,"]":`Ü`,"{":`ä`,"|":`ö`,"}":`ü`,"~":`ß`},X.Y={"#":`£`,"@":`§`,"[":`°`,"\\":`ç`,"]":`é`,"`":`ù`,"{":`à`,"|":`ò`,"}":`è`,"~":`ì`},X.E=X[6]={"@":`Ä`,"[":`Æ`,"\\":`Ø`,"]":`Å`,"^":`Ü`,"`":`ä`,"{":`æ`,"|":`ø`,"}":`å`,"~":`ü`},X.Z={"#":`£`,"@":`§`,"[":`¡`,"\\":`Ñ`,"]":`¿`,"{":`°`,"|":`ñ`,"}":`ç`},X.H=X[7]={"@":`É`,"[":`Ä`,"\\":`Ö`,"]":`Å`,"^":`Ü`,"`":`é`,"{":`ä`,"|":`ö`,"}":`å`,"~":`ü`},X[`=`]={"#":`ù`,"@":`à`,"[":`é`,"\\":`ç`,"]":`ê`,"^":`î`,_:`è`,"`":`ô`,"{":`ä`,"|":`ö`,"}":`ü`,"~":`û`};var bo=4294967295,xo=class{constructor(e,t,n){this._hasScrollback=e,this._optionsService=t,this._bufferService=n,this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.tabs={},this.savedY=0,this.savedX=0,this.savedCurAttrData=Y.clone(),this.savedCharset=yo,this.markers=[],this._nullCell=De.fromCharData([0,Ce,1,0]),this._whitespaceCell=De.fromCharData([0,we,1,32]),this._isClearing=!1,this._memoryCleanupQueue=new xa,this._memoryCleanupPosition=0,this._cols=this._bufferService.cols,this._rows=this._bufferService.rows,this.lines=new so(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}getNullCell(e){return e?(this._nullCell.fg=e.fg,this._nullCell.bg=e.bg,this._nullCell.extended=e.extended):(this._nullCell.fg=0,this._nullCell.bg=0,this._nullCell.extended=new Ee),this._nullCell}getWhitespaceCell(e){return e?(this._whitespaceCell.fg=e.fg,this._whitespaceCell.bg=e.bg,this._whitespaceCell.extended=e.extended):(this._whitespaceCell.fg=0,this._whitespaceCell.bg=0,this._whitespaceCell.extended=new Ee),this._whitespaceCell}getBlankLine(e,t){return new uo(this._bufferService.cols,this.getNullCell(e),t)}get hasScrollback(){return this._hasScrollback&&this.lines.maxLength>this._rows}get isCursorInViewport(){let e=this.ybase+this.y-this.ydisp;return e>=0&&ebo?bo:t}fillViewportRows(e){if(this.lines.length===0){e===void 0&&(e=Y);let t=this._rows;for(;t--;)this.lines.push(this.getBlankLine(e))}}clear(){this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.lines=new so(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}resize(e,t){let n=this.getNullCell(Y),r=0,i=this._getCorrectBufferLength(t);if(i>this.lines.maxLength&&(this.lines.maxLength=i),this.lines.length>0){if(this._cols0&&this.lines.length<=this.ybase+this.y+a+1?(this.ybase--,a++,this.ydisp>0&&this.ydisp--):this.lines.push(new uo(e,n)));else for(let e=this._rows;e>t;e--)this.lines.length>t+this.ybase&&(this.lines.length>this.ybase+this.y+1?this.lines.pop():(this.ybase++,this.ydisp++));if(i0&&(this.lines.trimStart(e),this.ybase=Math.max(this.ybase-e,0),this.ydisp=Math.max(this.ydisp-e,0),this.savedY=Math.max(this.savedY-e,0)),this.lines.maxLength=i}this.x=Math.min(this.x,e-1),this.y=Math.min(this.y,t-1),a&&(this.y+=a),this.savedX=Math.min(this.savedX,e-1),this.scrollTop=0}if(this.scrollBottom=t-1,this._isReflowEnabled&&(this._reflow(e,t),this._cols>e))for(let t=0;t.1*this.lines.length&&(this._memoryCleanupPosition=0,this._memoryCleanupQueue.enqueue(()=>this._batchedMemoryCleanup()))}_batchedMemoryCleanup(){let e=!0;this._memoryCleanupPosition>=this.lines.length&&(this._memoryCleanupPosition=0,e=!1);let t=0;for(;this._memoryCleanupPosition100)return!0;return e}get _isReflowEnabled(){let e=this._optionsService.rawOptions.windowsPty;return e&&e.buildNumber?this._hasScrollback&&e.backend===`conpty`&&e.buildNumber>=21376:this._hasScrollback&&!this._optionsService.rawOptions.windowsMode}_reflow(e,t){this._cols!==e&&(e>this._cols?this._reflowLarger(e,t):this._reflowSmaller(e,t))}_reflowLarger(e,t){let n=this._optionsService.rawOptions.reflowCursorLine,r=fo(this.lines,this._cols,e,this.ybase+this.y,this.getNullCell(Y),n);if(r.length>0){let n=po(this.lines,r);mo(this.lines,n.layout),this._reflowLargerAdjustViewport(e,t,n.countRemoved)}}_reflowLargerAdjustViewport(e,t,n){let r=this.getNullCell(Y),i=n;for(;i-->0;)this.ybase===0?(this.y>0&&this.y--,this.lines.length=0;o--){let s=this.lines.get(o);if(!s||!s.isWrapped&&s.getTrimmedLength()<=e)continue;let c=[s];for(;s.isWrapped&&o>0;)s=this.lines.get(--o),c.unshift(s);if(!n){let e=this.ybase+this.y;if(e>=o&&e0&&(i.push({start:o+c.length+a,newLines:p}),a+=p.length),c.push(...p);let m=u.length-1,h=u[m];h===0&&(m--,h=u[m]);let g=c.length-d-1,_=l;for(;g>=0;){let e=Math.min(_,h);if(c[m]===void 0)break;c[m].copyCellsFrom(c[g],_-e,h-e,e,!0),h-=e,h===0&&(m--,h=u[m]),_-=e,_===0&&(g--,_=go(c,Math.max(g,0),this._cols))}for(let t=0;t0;)this.ybase===0?this.y0){let e=[],t=[];for(let e=0;e=0;l--)if(s&&s.start>r+c){for(let e=s.newLines.length-1;e>=0;e--)this.lines.set(l--,s.newLines[e]);l++,e.push({index:r+1,amount:s.newLines.length}),c+=s.newLines.length,s=i[++o]}else this.lines.set(l,t[r--]);let l=0;for(let t=e.length-1;t>=0;t--)e[t].index+=l,this.lines.onInsertEmitter.fire(e[t]),l+=e[t].amount;let u=Math.max(0,n+a-this.lines.maxLength);u>0&&this.lines.onTrimEmitter.fire(u)}}translateBufferLineToString(e,t,n=0,r){let i=this.lines.get(e);return i?i.translateToString(t,n,r):``}getWrappedRangeForLine(e){let t=e,n=e;for(;t>0&&this.lines.get(t).isWrapped;)t--;for(;n+10;);return e>=this._cols?this._cols-1:e<0?0:e}nextStop(e){for(e??=this.x;!this.tabs[++e]&&e=this._cols?this._cols-1:e<0?0:e}clearMarkers(e){this._isClearing=!0;for(let t=0;t{t.line-=e,t.line<0&&t.dispose()})),t.register(this.lines.onInsert(e=>{t.line>=e.index&&(t.line+=e.amount)})),t.register(this.lines.onDelete(e=>{t.line>=e.index&&t.linee.index&&(t.line-=e.amount)})),t.register(t.onDispose(()=>this._removeMarker(t))),t}_removeMarker(e){this._isClearing||this.markers.splice(this.markers.indexOf(e),1)}},So=class extends A{constructor(e,t){super(),this._optionsService=e,this._bufferService=t,this._onBufferActivate=this._register(new N),this.onBufferActivate=this._onBufferActivate.event,this.reset(),this._register(this._optionsService.onSpecificOptionChange(`scrollback`,()=>this.resize(this._bufferService.cols,this._bufferService.rows))),this._register(this._optionsService.onSpecificOptionChange(`tabStopWidth`,()=>this.setupTabStops()))}reset(){this._normal=new xo(!0,this._optionsService,this._bufferService),this._normal.fillViewportRows(),this._alt=new xo(!1,this._optionsService,this._bufferService),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}),this.setupTabStops()}get alt(){return this._alt}get active(){return this._activeBuffer}get normal(){return this._normal}activateNormalBuffer(){this._activeBuffer!==this._normal&&(this._normal.x=this._alt.x,this._normal.y=this._alt.y,this._alt.clearAllMarkers(),this._alt.clear(),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}))}activateAltBuffer(e){this._activeBuffer!==this._alt&&(this._alt.fillViewportRows(e),this._alt.x=this._normal.x,this._alt.y=this._normal.y,this._activeBuffer=this._alt,this._onBufferActivate.fire({activeBuffer:this._alt,inactiveBuffer:this._normal}))}resize(e,t){this._normal.resize(e,t),this._alt.resize(e,t),this.setupTabStops(e)}setupTabStops(e){this._normal.setupTabStops(e),this._alt.setupTabStops(e)}},Co=2,wo=1,To=class extends A{constructor(e){super(),this.isUserScrolling=!1,this._onResize=this._register(new N),this.onResize=this._onResize.event,this._onScroll=this._register(new N),this.onScroll=this._onScroll.event,this.cols=Math.max(e.rawOptions.cols||0,Co),this.rows=Math.max(e.rawOptions.rows||0,wo),this.buffers=this._register(new So(e,this)),this._register(this.buffers.onBufferActivate(e=>{this._onScroll.fire(e.activeBuffer.ydisp)}))}get buffer(){return this.buffers.active}resize(e,t){let n=this.cols!==e,r=this.rows!==t;this.cols=e,this.rows=t,this.buffers.resize(e,t),this._onResize.fire({cols:e,rows:t,colsChanged:n,rowsChanged:r})}reset(){this.buffers.reset(),this.isUserScrolling=!1}scroll(e,t=!1){let n=this.buffer,r;r=this._cachedBlankLine,(!r||r.length!==this.cols||r.getFg(0)!==e.fg||r.getBg(0)!==e.bg)&&(r=n.getBlankLine(e,t),this._cachedBlankLine=r),r.isWrapped=t;let i=n.ybase+n.scrollTop,a=n.ybase+n.scrollBottom;if(n.scrollTop===0){let e=n.lines.isFull;a===n.lines.length-1?e?n.lines.recycle().copyFrom(r):n.lines.push(r.clone()):n.lines.splice(a+1,0,r.clone()),e?this.isUserScrolling&&(n.ydisp=Math.max(n.ydisp-1,0)):(n.ybase++,this.isUserScrolling||n.ydisp++)}else{let e=a-i+1;n.lines.shiftElements(i+1,e-1,-1),n.lines.set(a,r.clone())}this.isUserScrolling||(n.ydisp=n.ybase),this._onScroll.fire(n.ydisp)}scrollLines(e,t){let n=this.buffer;if(e<0){if(n.ydisp===0)return;this.isUserScrolling=!0}else e+n.ydisp>=n.ybase&&(this.isUserScrolling=!1);let r=n.ydisp;n.ydisp=Math.max(Math.min(n.ydisp+e,n.ybase),0),r!==n.ydisp&&(t||this._onScroll.fire(n.ydisp))}};To=C([w(0,O)],To);var Eo={cols:80,rows:24,cursorBlink:!1,cursorStyle:`block`,cursorWidth:1,cursorInactiveStyle:`outline`,customGlyphs:!0,drawBoldTextInBrightColors:!0,documentOverride:null,fastScrollModifier:`alt`,fastScrollSensitivity:5,fontFamily:`monospace`,fontSize:15,fontWeight:`normal`,fontWeightBold:`bold`,ignoreBracketedPasteMode:!1,lineHeight:1,letterSpacing:0,linkHandler:null,logLevel:`info`,logger:null,scrollback:1e3,scrollOnEraseInDisplay:!1,scrollOnUserInput:!0,scrollSensitivity:1,screenReaderMode:!1,smoothScrollDuration:0,macOptionIsMeta:!1,macOptionClickForcesSelection:!1,minimumContrastRatio:1,disableStdin:!1,allowProposedApi:!1,allowTransparency:!1,tabStopWidth:8,theme:{},reflowCursorLine:!1,rescaleOverlappingGlyphs:!1,rightClickSelectsWord:fa,windowOptions:{},windowsMode:!1,windowsPty:{},wordSeparator:` ()[]{}',"\``,altClickMovesCursor:!0,convertEol:!1,termName:`xterm`,cancelEvents:!1,overviewRuler:{}},Do=[`normal`,`bold`,`100`,`200`,`300`,`400`,`500`,`600`,`700`,`800`,`900`],Oo=class extends A{constructor(e){super(),this._onOptionChange=this._register(new N),this.onOptionChange=this._onOptionChange.event;let t={...Eo};for(let n in e)if(n in t)try{let r=e[n];t[n]=this._sanitizeAndValidateOption(n,r)}catch(e){console.error(e)}this.rawOptions=t,this.options={...t},this._setupOptions(),this._register(k(()=>{this.rawOptions.linkHandler=null,this.rawOptions.documentOverride=null}))}onSpecificOptionChange(e,t){return this.onOptionChange(n=>{n===e&&t(this.rawOptions[e])})}onMultipleOptionChange(e,t){return this.onOptionChange(n=>{e.indexOf(n)!==-1&&t()})}_setupOptions(){let e=e=>{if(!(e in Eo))throw Error(`No option with key "${e}"`);return this.rawOptions[e]},t=(e,t)=>{if(!(e in Eo))throw Error(`No option with key "${e}"`);t=this._sanitizeAndValidateOption(e,t),this.rawOptions[e]!==t&&(this.rawOptions[e]=t,this._onOptionChange.fire(e))};for(let n in this.rawOptions){let r={get:e.bind(this,n),set:t.bind(this,n)};Object.defineProperty(this.options,n,r)}}_sanitizeAndValidateOption(e,t){switch(e){case`cursorStyle`:if(t||=Eo[e],!ko(t))throw Error(`"${t}" is not a valid value for ${e}`);break;case`wordSeparator`:t||=Eo[e];break;case`fontWeight`:case`fontWeightBold`:if(typeof t==`number`&&1<=t&&t<=1e3)break;t=Do.includes(t)?t:Eo[e];break;case`cursorWidth`:t=Math.floor(t);case`lineHeight`:case`tabStopWidth`:if(t<1)throw Error(`${e} cannot be less than 1, value: ${t}`);break;case`minimumContrastRatio`:t=Math.max(1,Math.min(21,Math.round(t*10)/10));break;case`scrollback`:if(t=Math.min(t,4294967295),t<0)throw Error(`${e} cannot be less than 0, value: ${t}`);break;case`fastScrollSensitivity`:case`scrollSensitivity`:if(t<=0)throw Error(`${e} cannot be less than or equal to 0, value: ${t}`);break;case`rows`:case`cols`:if(!t&&t!==0)throw Error(`${e} must be numeric, value: ${t}`);break;case`windowsPty`:t??={};break}return t}};function ko(e){return e===`block`||e===`underline`||e===`bar`}function Ao(e,t=5){if(typeof e!=`object`)return e;let n=Array.isArray(e)?[]:{};for(let r in e)n[r]=t<=1?e[r]:e[r]&&Ao(e[r],t-1);return n}var jo=Object.freeze({insertMode:!1}),Mo=Object.freeze({applicationCursorKeys:!1,applicationKeypad:!1,bracketedPasteMode:!1,cursorBlink:void 0,cursorStyle:void 0,origin:!1,reverseWraparound:!1,sendFocus:!1,synchronizedOutput:!1,wraparound:!0}),No=class extends A{constructor(e,t,n){super(),this._bufferService=e,this._logService=t,this._optionsService=n,this.isCursorInitialized=!1,this.isCursorHidden=!1,this._onData=this._register(new N),this.onData=this._onData.event,this._onUserInput=this._register(new N),this.onUserInput=this._onUserInput.event,this._onBinary=this._register(new N),this.onBinary=this._onBinary.event,this._onRequestScrollToBottom=this._register(new N),this.onRequestScrollToBottom=this._onRequestScrollToBottom.event,this.modes=Ao(jo),this.decPrivateModes=Ao(Mo)}reset(){this.modes=Ao(jo),this.decPrivateModes=Ao(Mo)}triggerDataEvent(e,t=!1){if(this._optionsService.rawOptions.disableStdin)return;let n=this._bufferService.buffer;t&&this._optionsService.rawOptions.scrollOnUserInput&&n.ybase!==n.ydisp&&this._onRequestScrollToBottom.fire(),t&&this._onUserInput.fire(),this._logService.debug(`sending data "${e}"`),this._logService.trace(`sending data (codes)`,()=>e.split(``).map(e=>e.charCodeAt(0))),this._onData.fire(e)}triggerBinaryEvent(e){this._optionsService.rawOptions.disableStdin||(this._logService.debug(`sending binary "${e}"`),this._logService.trace(`sending binary (codes)`,()=>e.split(``).map(e=>e.charCodeAt(0))),this._onBinary.fire(e))}};No=C([w(0,D),w(1,Le),w(2,O)],No);var Po={NONE:{events:0,restrict:()=>!1},X10:{events:1,restrict:e=>e.button===4||e.action!==1?!1:(e.ctrl=!1,e.alt=!1,e.shift=!1,!0)},VT200:{events:19,restrict:e=>e.action!==32},DRAG:{events:23,restrict:e=>!(e.action===32&&e.button===3)},ANY:{events:31,restrict:e=>!0}};function Fo(e,t){let n=(e.ctrl?16:0)|(e.shift?4:0)|(e.alt?8:0);return e.button===4?(n|=64,n|=e.action):(n|=e.button&3,e.button&4&&(n|=64),e.button&8&&(n|=128),e.action===32?n|=32:e.action===0&&!t&&(n|=3)),n}var Io=String.fromCharCode,Lo={DEFAULT:e=>{let t=[Fo(e,!1)+32,e.col+32,e.row+32];return t[0]>255||t[1]>255||t[2]>255?``:`\x1B[M${Io(t[0])}${Io(t[1])}${Io(t[2])}`},SGR:e=>{let t=e.action===0&&e.button!==4?`m`:`M`;return`\x1B[<${Fo(e,!0)};${e.col};${e.row}${t}`},SGR_PIXELS:e=>{let t=e.action===0&&e.button!==4?`m`:`M`;return`\x1B[<${Fo(e,!0)};${e.x};${e.y}${t}`}},Ro=class extends A{constructor(e,t,n){super(),this._bufferService=e,this._coreService=t,this._optionsService=n,this._protocols={},this._encodings={},this._activeProtocol=``,this._activeEncoding=``,this._lastEvent=null,this._wheelPartialScroll=0,this._onProtocolChange=this._register(new N),this.onProtocolChange=this._onProtocolChange.event;for(let e of Object.keys(Po))this.addProtocol(e,Po[e]);for(let e of Object.keys(Lo))this.addEncoding(e,Lo[e]);this.reset()}addProtocol(e,t){this._protocols[e]=t}addEncoding(e,t){this._encodings[e]=t}get activeProtocol(){return this._activeProtocol}get areMouseEventsActive(){return this._protocols[this._activeProtocol].events!==0}set activeProtocol(e){if(!this._protocols[e])throw Error(`unknown protocol "${e}"`);this._activeProtocol=e,this._onProtocolChange.fire(this._protocols[e].events)}get activeEncoding(){return this._activeEncoding}set activeEncoding(e){if(!this._encodings[e])throw Error(`unknown encoding "${e}"`);this._activeEncoding=e}reset(){this.activeProtocol=`NONE`,this.activeEncoding=`DEFAULT`,this._lastEvent=null,this._wheelPartialScroll=0}consumeWheelEvent(e,t,n){if(e.deltaY===0||e.shiftKey||t===void 0||n===void 0)return 0;let r=t/n,i=this._applyScrollModifier(e.deltaY,e);return e.deltaMode===WheelEvent.DOM_DELTA_PIXEL?(i/=r+0,Math.abs(e.deltaY)<50&&(i*=.3),this._wheelPartialScroll+=i,i=Math.floor(Math.abs(this._wheelPartialScroll))*(this._wheelPartialScroll>0?1:-1),this._wheelPartialScroll%=1):e.deltaMode===WheelEvent.DOM_DELTA_PAGE&&(i*=this._bufferService.rows),i}_applyScrollModifier(e,t){return t.altKey||t.ctrlKey||t.shiftKey?e*this._optionsService.rawOptions.fastScrollSensitivity*this._optionsService.rawOptions.scrollSensitivity:e*this._optionsService.rawOptions.scrollSensitivity}triggerMouseEvent(e){if(e.col<0||e.col>=this._bufferService.cols||e.row<0||e.row>=this._bufferService.rows||e.button===4&&e.action===32||e.button===3&&e.action!==32||e.button!==4&&(e.action===2||e.action===3)||(e.col++,e.row++,e.action===32&&this._lastEvent&&this._equalEvents(this._lastEvent,e,this._activeEncoding===`SGR_PIXELS`))||!this._protocols[this._activeProtocol].restrict(e))return!1;let t=this._encodings[this._activeEncoding](e);return t&&(this._activeEncoding===`DEFAULT`?this._coreService.triggerBinaryEvent(t):this._coreService.triggerDataEvent(t,!0)),this._lastEvent=e,!0}explainEvents(e){return{down:!!(e&1),up:!!(e&2),drag:!!(e&4),move:!!(e&8),wheel:!!(e&16)}}_equalEvents(e,t,n){if(n){if(e.x!==t.x||e.y!==t.y)return!1}else if(e.col!==t.col||e.row!==t.row)return!1;return!(e.button!==t.button||e.action!==t.action||e.ctrl!==t.ctrl||e.alt!==t.alt||e.shift!==t.shift)}};Ro=C([w(0,D),w(1,Pe),w(2,O)],Ro);var zo=[[768,879],[1155,1158],[1160,1161],[1425,1469],[1471,1471],[1473,1474],[1476,1477],[1479,1479],[1536,1539],[1552,1557],[1611,1630],[1648,1648],[1750,1764],[1767,1768],[1770,1773],[1807,1807],[1809,1809],[1840,1866],[1958,1968],[2027,2035],[2305,2306],[2364,2364],[2369,2376],[2381,2381],[2385,2388],[2402,2403],[2433,2433],[2492,2492],[2497,2500],[2509,2509],[2530,2531],[2561,2562],[2620,2620],[2625,2626],[2631,2632],[2635,2637],[2672,2673],[2689,2690],[2748,2748],[2753,2757],[2759,2760],[2765,2765],[2786,2787],[2817,2817],[2876,2876],[2879,2879],[2881,2883],[2893,2893],[2902,2902],[2946,2946],[3008,3008],[3021,3021],[3134,3136],[3142,3144],[3146,3149],[3157,3158],[3260,3260],[3263,3263],[3270,3270],[3276,3277],[3298,3299],[3393,3395],[3405,3405],[3530,3530],[3538,3540],[3542,3542],[3633,3633],[3636,3642],[3655,3662],[3761,3761],[3764,3769],[3771,3772],[3784,3789],[3864,3865],[3893,3893],[3895,3895],[3897,3897],[3953,3966],[3968,3972],[3974,3975],[3984,3991],[3993,4028],[4038,4038],[4141,4144],[4146,4146],[4150,4151],[4153,4153],[4184,4185],[4448,4607],[4959,4959],[5906,5908],[5938,5940],[5970,5971],[6002,6003],[6068,6069],[6071,6077],[6086,6086],[6089,6099],[6109,6109],[6155,6157],[6313,6313],[6432,6434],[6439,6440],[6450,6450],[6457,6459],[6679,6680],[6912,6915],[6964,6964],[6966,6970],[6972,6972],[6978,6978],[7019,7027],[7616,7626],[7678,7679],[8203,8207],[8234,8238],[8288,8291],[8298,8303],[8400,8431],[12330,12335],[12441,12442],[43014,43014],[43019,43019],[43045,43046],[64286,64286],[65024,65039],[65056,65059],[65279,65279],[65529,65531]],Bo=[[68097,68099],[68101,68102],[68108,68111],[68152,68154],[68159,68159],[119143,119145],[119155,119170],[119173,119179],[119210,119213],[119362,119364],[917505,917505],[917536,917631],[917760,917999]],Z;function Vo(e,t){let n=0,r=t.length-1,i;if(et[r][1])return!1;for(;r>=n;)if(i=n+r>>1,e>t[i][1])n=i+1;else if(e=131072&&e<=196605||e>=196608&&e<=262141?2:1}charProperties(e,t){let n=this.wcwidth(e),r=n===0&&t!==0;if(r){let e=Uo.extractWidth(t);e===0?r=!1:e>n&&(n=e)}return Uo.createPropertyValue(0,n,r)}},Uo=class e{constructor(){this._providers=Object.create(null),this._active=``,this._onChange=new N,this.onChange=this._onChange.event;let e=new Ho;this.register(e),this._active=e.version,this._activeProvider=e}static extractShouldJoin(e){return(e&1)!=0}static extractWidth(e){return e>>1&3}static extractCharKind(e){return e>>3}static createPropertyValue(e,t,n=!1){return(e&16777215)<<3|(t&3)<<1|!!n}dispose(){this._onChange.dispose()}get versions(){return Object.keys(this._providers)}get activeVersion(){return this._active}set activeVersion(e){if(!this._providers[e])throw Error(`unknown Unicode version "${e}"`);this._active=e,this._activeProvider=this._providers[e],this._onChange.fire(e)}register(e){this._providers[e.version]=e}wcwidth(e){return this._activeProvider.wcwidth(e)}getStringCellWidth(t){let n=0,r=0,i=t.length;for(let a=0;a=i)return n+this.wcwidth(o);let e=t.charCodeAt(a);56320<=e&&e<=57343?o=(o-55296)*1024+e-56320+65536:n+=this.wcwidth(e)}let s=this.charProperties(o,r),c=e.extractWidth(s);e.extractShouldJoin(s)&&(c-=e.extractWidth(r)),n+=c,r=s}return n}charProperties(e,t){return this._activeProvider.charProperties(e,t)}},Wo=class{constructor(){this.glevel=0,this._charsets=[]}reset(){this.charset=void 0,this._charsets=[],this.glevel=0}setgLevel(e){this.glevel=e,this.charset=this._charsets[e]}setgCharset(e,t){this._charsets[e]=t,this.glevel===e&&(this.charset=t)}};function Go(e){let t=e.buffer.lines.get(e.buffer.ybase+e.buffer.y-1)?.get(e.cols-1),n=e.buffer.lines.get(e.buffer.ybase+e.buffer.y);n&&t&&(n.isWrapped=t[3]!==0&&t[3]!==32)}var Ko=2147483647,qo=256,Jo=class e{constructor(e=32,t=32){if(this.maxLength=e,this.maxSubParamsLength=t,t>qo)throw Error(`maxSubParamsLength must not be greater than 256`);this.params=new Int32Array(e),this.length=0,this._subParams=new Int32Array(t),this._subParamsLength=0,this._subParamsIdx=new Uint16Array(e),this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}static fromArray(t){let n=new e;if(!t.length)return n;for(let e=+!!Array.isArray(t[0]);e>8,r=this._subParamsIdx[t]&255;r-n>0&&e.push(Array.prototype.slice.call(this._subParams,n,r))}return e}reset(){this.length=0,this._subParamsLength=0,this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}addParam(e){if(this._digitIsSub=!1,this.length>=this.maxLength){this._rejectDigits=!0;return}if(e<-1)throw Error(`values lesser than -1 are not allowed`);this._subParamsIdx[this.length]=this._subParamsLength<<8|this._subParamsLength,this.params[this.length++]=e>Ko?Ko:e}addSubParam(e){if(this._digitIsSub=!0,this.length){if(this._rejectDigits||this._subParamsLength>=this.maxSubParamsLength){this._rejectSubDigits=!0;return}if(e<-1)throw Error(`values lesser than -1 are not allowed`);this._subParams[this._subParamsLength++]=e>Ko?Ko:e,this._subParamsIdx[this.length-1]++}}hasSubParams(e){return(this._subParamsIdx[e]&255)-(this._subParamsIdx[e]>>8)>0}getSubParams(e){let t=this._subParamsIdx[e]>>8,n=this._subParamsIdx[e]&255;return n-t>0?this._subParams.subarray(t,n):null}getSubParamsAll(){let e={};for(let t=0;t>8,r=this._subParamsIdx[t]&255;r-n>0&&(e[t]=this._subParams.slice(n,r))}return e}addDigit(e){let t;if(this._rejectDigits||!(t=this._digitIsSub?this._subParamsLength:this.length)||this._digitIsSub&&this._rejectSubDigits)return;let n=this._digitIsSub?this._subParams:this.params,r=n[t-1];n[t-1]=~r?Math.min(r*10+e,Ko):e}},Yo=[],Xo=class{constructor(){this._state=0,this._active=Yo,this._id=-1,this._handlers=Object.create(null),this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}registerHandler(e,t){this._handlers[e]===void 0&&(this._handlers[e]=[]);let n=this._handlers[e];return n.push(t),{dispose:()=>{let e=n.indexOf(t);e!==-1&&n.splice(e,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=Yo}reset(){if(this._state===2)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].end(!1);this._stack.paused=!1,this._active=Yo,this._id=-1,this._state=0}_start(){if(this._active=this._handlers[this._id]||Yo,!this._active.length)this._handlerFb(this._id,`START`);else for(let e=this._active.length-1;e>=0;e--)this._active[e].start()}_put(e,t,n){if(!this._active.length)this._handlerFb(this._id,`PUT`,be(e,t,n));else for(let r=this._active.length-1;r>=0;r--)this._active[r].put(e,t,n)}start(){this.reset(),this._state=1}put(e,t,n){if(this._state!==3){if(this._state===1)for(;t0&&this._put(e,t,n)}}end(e,t=!0){if(this._state!==0){if(this._state!==3)if(this._state===1&&this._start(),!this._active.length)this._handlerFb(this._id,`END`,e);else{let n=!1,r=this._active.length-1,i=!1;if(this._stack.paused&&(r=this._stack.loopPosition-1,n=t,i=this._stack.fallThrough,this._stack.paused=!1),!i&&n===!1){for(;r>=0&&(n=this._active[r].end(e),n!==!0);r--)if(n instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=r,this._stack.fallThrough=!1,n;r--}for(;r>=0;r--)if(n=this._active[r].end(!1),n instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=r,this._stack.fallThrough=!0,n}this._active=Yo,this._id=-1,this._state=0}}},Zo=class{constructor(e){this._handler=e,this._data=``,this._hitLimit=!1}start(){this._data=``,this._hitLimit=!1}put(e,t,n){this._hitLimit||(this._data+=be(e,t,n),this._data.length>1e7&&(this._data=``,this._hitLimit=!0))}end(e){let t=!1;if(this._hitLimit)t=!1;else if(e&&(t=this._handler(this._data),t instanceof Promise))return t.then(e=>(this._data=``,this._hitLimit=!1,e));return this._data=``,this._hitLimit=!1,t}},Qo=[],$o=class{constructor(){this._handlers=Object.create(null),this._active=Qo,this._ident=0,this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=Qo}registerHandler(e,t){this._handlers[e]===void 0&&(this._handlers[e]=[]);let n=this._handlers[e];return n.push(t),{dispose:()=>{let e=n.indexOf(t);e!==-1&&n.splice(e,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}reset(){if(this._active.length)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].unhook(!1);this._stack.paused=!1,this._active=Qo,this._ident=0}hook(e,t){if(this.reset(),this._ident=e,this._active=this._handlers[e]||Qo,!this._active.length)this._handlerFb(this._ident,`HOOK`,t);else for(let e=this._active.length-1;e>=0;e--)this._active[e].hook(t)}put(e,t,n){if(!this._active.length)this._handlerFb(this._ident,`PUT`,be(e,t,n));else for(let r=this._active.length-1;r>=0;r--)this._active[r].put(e,t,n)}unhook(e,t=!0){if(!this._active.length)this._handlerFb(this._ident,`UNHOOK`,e);else{let n=!1,r=this._active.length-1,i=!1;if(this._stack.paused&&(r=this._stack.loopPosition-1,n=t,i=this._stack.fallThrough,this._stack.paused=!1),!i&&n===!1){for(;r>=0&&(n=this._active[r].unhook(e),n!==!0);r--)if(n instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=r,this._stack.fallThrough=!1,n;r--}for(;r>=0;r--)if(n=this._active[r].unhook(!1),n instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=r,this._stack.fallThrough=!0,n}this._active=Qo,this._ident=0}},es=new Jo;es.addParam(0);var ts=class{constructor(e){this._handler=e,this._data=``,this._params=es,this._hitLimit=!1}hook(e){this._params=e.length>1||e.params[0]?e.clone():es,this._data=``,this._hitLimit=!1}put(e,t,n){this._hitLimit||(this._data+=be(e,t,n),this._data.length>1e7&&(this._data=``,this._hitLimit=!0))}unhook(e){let t=!1;if(this._hitLimit)t=!1;else if(e&&(t=this._handler(this._data,this._params),t instanceof Promise))return t.then(e=>(this._params=es,this._data=``,this._hitLimit=!1,e));return this._params=es,this._data=``,this._hitLimit=!1,t}},ns=class{constructor(e){this.table=new Uint8Array(e)}setDefault(e,t){this.table.fill(e<<4|t)}add(e,t,n,r){this.table[t<<8|e]=n<<4|r}addMany(e,t,n,r){for(let i=0;it),n=(e,n)=>t.slice(e,n),r=n(32,127),i=n(0,24);i.push(25),i.push.apply(i,n(28,32));let a=n(0,14),o;for(o in e.setDefault(1,0),e.addMany(r,0,2,0),a)e.addMany([24,26,153,154],o,3,0),e.addMany(n(128,144),o,3,0),e.addMany(n(144,152),o,3,0),e.add(156,o,0,0),e.add(27,o,11,1),e.add(157,o,4,8),e.addMany([152,158,159],o,0,7),e.add(155,o,11,3),e.add(144,o,11,9);return e.addMany(i,0,3,0),e.addMany(i,1,3,1),e.add(127,1,0,1),e.addMany(i,8,0,8),e.addMany(i,3,3,3),e.add(127,3,0,3),e.addMany(i,4,3,4),e.add(127,4,0,4),e.addMany(i,6,3,6),e.addMany(i,5,3,5),e.add(127,5,0,5),e.addMany(i,2,3,2),e.add(127,2,0,2),e.add(93,1,4,8),e.addMany(r,8,5,8),e.add(127,8,5,8),e.addMany([156,27,24,26,7],8,6,0),e.addMany(n(28,32),8,0,8),e.addMany([88,94,95],1,0,7),e.addMany(r,7,0,7),e.addMany(i,7,0,7),e.add(156,7,0,0),e.add(127,7,0,7),e.add(91,1,11,3),e.addMany(n(64,127),3,7,0),e.addMany(n(48,60),3,8,4),e.addMany([60,61,62,63],3,9,4),e.addMany(n(48,60),4,8,4),e.addMany(n(64,127),4,7,0),e.addMany([60,61,62,63],4,0,6),e.addMany(n(32,64),6,0,6),e.add(127,6,0,6),e.addMany(n(64,127),6,0,0),e.addMany(n(32,48),3,9,5),e.addMany(n(32,48),5,9,5),e.addMany(n(48,64),5,0,6),e.addMany(n(64,127),5,7,0),e.addMany(n(32,48),4,9,5),e.addMany(n(32,48),1,9,2),e.addMany(n(32,48),2,9,2),e.addMany(n(48,127),2,10,0),e.addMany(n(48,80),1,10,0),e.addMany(n(81,88),1,10,0),e.addMany([89,90,92],1,10,0),e.addMany(n(96,127),1,10,0),e.add(80,1,11,9),e.addMany(i,9,0,9),e.add(127,9,0,9),e.addMany(n(28,32),9,0,9),e.addMany(n(32,48),9,9,12),e.addMany(n(48,60),9,8,10),e.addMany([60,61,62,63],9,9,10),e.addMany(i,11,0,11),e.addMany(n(32,128),11,0,11),e.addMany(n(28,32),11,0,11),e.addMany(i,10,0,10),e.add(127,10,0,10),e.addMany(n(28,32),10,0,10),e.addMany(n(48,60),10,8,10),e.addMany([60,61,62,63],10,0,11),e.addMany(n(32,48),10,9,12),e.addMany(i,12,0,12),e.add(127,12,0,12),e.addMany(n(28,32),12,0,12),e.addMany(n(32,48),12,9,12),e.addMany(n(48,64),12,0,11),e.addMany(n(64,127),12,12,13),e.addMany(n(64,127),10,12,13),e.addMany(n(64,127),9,12,13),e.addMany(i,13,13,13),e.addMany(r,13,13,13),e.add(127,13,0,13),e.addMany([27,156,24,26],13,14,0),e.add(rs,0,2,0),e.add(rs,8,5,8),e.add(rs,6,0,6),e.add(rs,11,0,11),e.add(rs,13,13,13),e}(),as=class extends A{constructor(e=is){super(),this._transitions=e,this._parseStack={state:0,handlers:[],handlerPos:0,transition:0,chunkPos:0},this.initialState=0,this.currentState=this.initialState,this._params=new Jo,this._params.addParam(0),this._collect=0,this.precedingJoinState=0,this._printHandlerFb=(e,t,n)=>{},this._executeHandlerFb=e=>{},this._csiHandlerFb=(e,t)=>{},this._escHandlerFb=e=>{},this._errorHandlerFb=e=>e,this._printHandler=this._printHandlerFb,this._executeHandlers=Object.create(null),this._csiHandlers=Object.create(null),this._escHandlers=Object.create(null),this._register(k(()=>{this._csiHandlers=Object.create(null),this._executeHandlers=Object.create(null),this._escHandlers=Object.create(null)})),this._oscParser=this._register(new Xo),this._dcsParser=this._register(new $o),this._errorHandler=this._errorHandlerFb,this.registerEscHandler({final:`\\`},()=>!0)}_identifier(e,t=[64,126]){let n=0;if(e.prefix){if(e.prefix.length>1)throw Error(`only one byte as prefix supported`);if(n=e.prefix.charCodeAt(0),n&&60>n||n>63)throw Error(`prefix must be in range 0x3c .. 0x3f`)}if(e.intermediates){if(e.intermediates.length>2)throw Error(`only two bytes as intermediates are supported`);for(let t=0;tr||r>47)throw Error(`intermediate must be in range 0x20 .. 0x2f`);n<<=8,n|=r}}if(e.final.length!==1)throw Error(`final must be a single byte`);let r=e.final.charCodeAt(0);if(t[0]>r||r>t[1])throw Error(`final must be in range ${t[0]} .. ${t[1]}`);return n<<=8,n|=r,n}identToString(e){let t=[];for(;e;)t.push(String.fromCharCode(e&255)),e>>=8;return t.reverse().join(``)}setPrintHandler(e){this._printHandler=e}clearPrintHandler(){this._printHandler=this._printHandlerFb}registerEscHandler(e,t){let n=this._identifier(e,[48,126]);this._escHandlers[n]===void 0&&(this._escHandlers[n]=[]);let r=this._escHandlers[n];return r.push(t),{dispose:()=>{let e=r.indexOf(t);e!==-1&&r.splice(e,1)}}}clearEscHandler(e){this._escHandlers[this._identifier(e,[48,126])]&&delete this._escHandlers[this._identifier(e,[48,126])]}setEscHandlerFallback(e){this._escHandlerFb=e}setExecuteHandler(e,t){this._executeHandlers[e.charCodeAt(0)]=t}clearExecuteHandler(e){this._executeHandlers[e.charCodeAt(0)]&&delete this._executeHandlers[e.charCodeAt(0)]}setExecuteHandlerFallback(e){this._executeHandlerFb=e}registerCsiHandler(e,t){let n=this._identifier(e);this._csiHandlers[n]===void 0&&(this._csiHandlers[n]=[]);let r=this._csiHandlers[n];return r.push(t),{dispose:()=>{let e=r.indexOf(t);e!==-1&&r.splice(e,1)}}}clearCsiHandler(e){this._csiHandlers[this._identifier(e)]&&delete this._csiHandlers[this._identifier(e)]}setCsiHandlerFallback(e){this._csiHandlerFb=e}registerDcsHandler(e,t){return this._dcsParser.registerHandler(this._identifier(e),t)}clearDcsHandler(e){this._dcsParser.clearHandler(this._identifier(e))}setDcsHandlerFallback(e){this._dcsParser.setHandlerFallback(e)}registerOscHandler(e,t){return this._oscParser.registerHandler(e,t)}clearOscHandler(e){this._oscParser.clearHandler(e)}setOscHandlerFallback(e){this._oscParser.setHandlerFallback(e)}setErrorHandler(e){this._errorHandler=e}clearErrorHandler(){this._errorHandler=this._errorHandlerFb}reset(){this.currentState=this.initialState,this._oscParser.reset(),this._dcsParser.reset(),this._params.reset(),this._params.addParam(0),this._collect=0,this.precedingJoinState=0,this._parseStack.state!==0&&(this._parseStack.state=2,this._parseStack.handlers=[])}_preserveStack(e,t,n,r,i){this._parseStack.state=e,this._parseStack.handlers=t,this._parseStack.handlerPos=n,this._parseStack.transition=r,this._parseStack.chunkPos=i}parse(e,t,n){let r=0,i=0,a=0,o;if(this._parseStack.state)if(this._parseStack.state===2)this._parseStack.state=0,a=this._parseStack.chunkPos+1;else{if(n===void 0||this._parseStack.state===1)throw this._parseStack.state=1,Error(`improper continuation due to previous async handler, giving up parsing`);let t=this._parseStack.handlers,i=this._parseStack.handlerPos-1;switch(this._parseStack.state){case 3:if(n===!1&&i>-1){for(;i>=0&&(o=t[i](this._params),o!==!0);i--)if(o instanceof Promise)return this._parseStack.handlerPos=i,o}this._parseStack.handlers=[];break;case 4:if(n===!1&&i>-1){for(;i>=0&&(o=t[i](),o!==!0);i--)if(o instanceof Promise)return this._parseStack.handlerPos=i,o}this._parseStack.handlers=[];break;case 6:if(r=e[this._parseStack.chunkPos],o=this._dcsParser.unhook(r!==24&&r!==26,n),o)return o;r===27&&(this._parseStack.transition|=1),this._params.reset(),this._params.addParam(0),this._collect=0;break;case 5:if(r=e[this._parseStack.chunkPos],o=this._oscParser.end(r!==24&&r!==26,n),o)return o;r===27&&(this._parseStack.transition|=1),this._params.reset(),this._params.addParam(0),this._collect=0;break}this._parseStack.state=0,a=this._parseStack.chunkPos+1,this.precedingJoinState=0,this.currentState=this._parseStack.transition&15}for(let n=a;n>4){case 2:for(let i=n+1;;++i){if(i>=t||(r=e[i])<32||r>126&&r=t||(r=e[i])<32||r>126&&r=t||(r=e[i])<32||r>126&&r=t||(r=e[i])<32||r>126&&r=0&&(o=a[s](this._params),o!==!0);s--)if(o instanceof Promise)return this._preserveStack(3,a,s,i,n),o;s<0&&this._csiHandlerFb(this._collect<<8|r,this._params),this.precedingJoinState=0;break;case 8:do switch(r){case 59:this._params.addParam(0);break;case 58:this._params.addSubParam(-1);break;default:this._params.addDigit(r-48)}while(++n47&&r<60);n--;break;case 9:this._collect<<=8,this._collect|=r;break;case 10:let c=this._escHandlers[this._collect<<8|r],l=c?c.length-1:-1;for(;l>=0&&(o=c[l](),o!==!0);l--)if(o instanceof Promise)return this._preserveStack(4,c,l,i,n),o;l<0&&this._escHandlerFb(this._collect<<8|r),this.precedingJoinState=0;break;case 11:this._params.reset(),this._params.addParam(0),this._collect=0;break;case 12:this._dcsParser.hook(this._collect<<8|r,this._params);break;case 13:for(let i=n+1;;++i)if(i>=t||(r=e[i])===24||r===26||r===27||r>127&&r=t||(r=e[i])<32||r>127&&r>4:i>>8}return n}}function ls(e,t){let n=e.toString(16),r=n.length<2?`0`+n:n;switch(t){case 4:return n[0];case 8:return r;case 12:return(r+r).slice(0,3);default:return r+r}}function us(e,t=16){let[n,r,i]=e;return`rgb:${ls(n,t)}/${ls(r,t)}/${ls(i,t)}`}var ds={"(":0,")":1,"*":2,"+":3,"-":1,".":2},fs=131072,ps=10;function ms(e,t){if(e>24)return t.setWinLines||!1;switch(e){case 1:return!!t.restoreWin;case 2:return!!t.minimizeWin;case 3:return!!t.setWinPosition;case 4:return!!t.setWinSizePixels;case 5:return!!t.raiseWin;case 6:return!!t.lowerWin;case 7:return!!t.refreshWin;case 8:return!!t.setWinSizeChars;case 9:return!!t.maximizeWin;case 10:return!!t.fullscreenWin;case 11:return!!t.getWinState;case 13:return!!t.getWinPosition;case 14:return!!t.getWinSizePixels;case 15:return!!t.getScreenSizePixels;case 16:return!!t.getCellSizePixels;case 18:return!!t.getWinSizeChars;case 19:return!!t.getScreenSizeChars;case 20:return!!t.getIconTitle;case 21:return!!t.getWinTitle;case 22:return!!t.pushTitle;case 23:return!!t.popTitle;case 24:return!!t.setWinLines}return!1}var hs=5e3,gs=0,_s=class extends A{constructor(e,t,n,r,i,a,o,s,c=new as){super(),this._bufferService=e,this._charsetService=t,this._coreService=n,this._logService=r,this._optionsService=i,this._oscLinkService=a,this._coreMouseService=o,this._unicodeService=s,this._parser=c,this._parseBuffer=new Uint32Array(4096),this._stringDecoder=new xe,this._utf8Decoder=new Se,this._windowTitle=``,this._iconName=``,this._windowTitleStack=[],this._iconNameStack=[],this._curAttrData=Y.clone(),this._eraseAttrDataInternal=Y.clone(),this._onRequestBell=this._register(new N),this.onRequestBell=this._onRequestBell.event,this._onRequestRefreshRows=this._register(new N),this.onRequestRefreshRows=this._onRequestRefreshRows.event,this._onRequestReset=this._register(new N),this.onRequestReset=this._onRequestReset.event,this._onRequestSendFocus=this._register(new N),this.onRequestSendFocus=this._onRequestSendFocus.event,this._onRequestSyncScrollBar=this._register(new N),this.onRequestSyncScrollBar=this._onRequestSyncScrollBar.event,this._onRequestWindowsOptionsReport=this._register(new N),this.onRequestWindowsOptionsReport=this._onRequestWindowsOptionsReport.event,this._onA11yChar=this._register(new N),this.onA11yChar=this._onA11yChar.event,this._onA11yTab=this._register(new N),this.onA11yTab=this._onA11yTab.event,this._onCursorMove=this._register(new N),this.onCursorMove=this._onCursorMove.event,this._onLineFeed=this._register(new N),this.onLineFeed=this._onLineFeed.event,this._onScroll=this._register(new N),this.onScroll=this._onScroll.event,this._onTitleChange=this._register(new N),this.onTitleChange=this._onTitleChange.event,this._onColor=this._register(new N),this.onColor=this._onColor.event,this._parseStack={paused:!1,cursorStartX:0,cursorStartY:0,decodedLength:0,position:0},this._specialColors=[256,257,258],this._register(this._parser),this._dirtyRowTracker=new vs(this._bufferService),this._activeBuffer=this._bufferService.buffer,this._register(this._bufferService.buffers.onBufferActivate(e=>this._activeBuffer=e.activeBuffer)),this._parser.setCsiHandlerFallback((e,t)=>{this._logService.debug(`Unknown CSI code: `,{identifier:this._parser.identToString(e),params:t.toArray()})}),this._parser.setEscHandlerFallback(e=>{this._logService.debug(`Unknown ESC code: `,{identifier:this._parser.identToString(e)})}),this._parser.setExecuteHandlerFallback(e=>{this._logService.debug(`Unknown EXECUTE code: `,{code:e})}),this._parser.setOscHandlerFallback((e,t,n)=>{this._logService.debug(`Unknown OSC code: `,{identifier:e,action:t,data:n})}),this._parser.setDcsHandlerFallback((e,t,n)=>{t===`HOOK`&&(n=n.toArray()),this._logService.debug(`Unknown DCS code: `,{identifier:this._parser.identToString(e),action:t,payload:n})}),this._parser.setPrintHandler((e,t,n)=>this.print(e,t,n)),this._parser.registerCsiHandler({final:`@`},e=>this.insertChars(e)),this._parser.registerCsiHandler({intermediates:` `,final:`@`},e=>this.scrollLeft(e)),this._parser.registerCsiHandler({final:`A`},e=>this.cursorUp(e)),this._parser.registerCsiHandler({intermediates:` `,final:`A`},e=>this.scrollRight(e)),this._parser.registerCsiHandler({final:`B`},e=>this.cursorDown(e)),this._parser.registerCsiHandler({final:`C`},e=>this.cursorForward(e)),this._parser.registerCsiHandler({final:`D`},e=>this.cursorBackward(e)),this._parser.registerCsiHandler({final:`E`},e=>this.cursorNextLine(e)),this._parser.registerCsiHandler({final:`F`},e=>this.cursorPrecedingLine(e)),this._parser.registerCsiHandler({final:`G`},e=>this.cursorCharAbsolute(e)),this._parser.registerCsiHandler({final:`H`},e=>this.cursorPosition(e)),this._parser.registerCsiHandler({final:`I`},e=>this.cursorForwardTab(e)),this._parser.registerCsiHandler({final:`J`},e=>this.eraseInDisplay(e,!1)),this._parser.registerCsiHandler({prefix:`?`,final:`J`},e=>this.eraseInDisplay(e,!0)),this._parser.registerCsiHandler({final:`K`},e=>this.eraseInLine(e,!1)),this._parser.registerCsiHandler({prefix:`?`,final:`K`},e=>this.eraseInLine(e,!0)),this._parser.registerCsiHandler({final:`L`},e=>this.insertLines(e)),this._parser.registerCsiHandler({final:`M`},e=>this.deleteLines(e)),this._parser.registerCsiHandler({final:`P`},e=>this.deleteChars(e)),this._parser.registerCsiHandler({final:`S`},e=>this.scrollUp(e)),this._parser.registerCsiHandler({final:`T`},e=>this.scrollDown(e)),this._parser.registerCsiHandler({final:`X`},e=>this.eraseChars(e)),this._parser.registerCsiHandler({final:`Z`},e=>this.cursorBackwardTab(e)),this._parser.registerCsiHandler({final:"`"},e=>this.charPosAbsolute(e)),this._parser.registerCsiHandler({final:`a`},e=>this.hPositionRelative(e)),this._parser.registerCsiHandler({final:`b`},e=>this.repeatPrecedingCharacter(e)),this._parser.registerCsiHandler({final:`c`},e=>this.sendDeviceAttributesPrimary(e)),this._parser.registerCsiHandler({prefix:`>`,final:`c`},e=>this.sendDeviceAttributesSecondary(e)),this._parser.registerCsiHandler({final:`d`},e=>this.linePosAbsolute(e)),this._parser.registerCsiHandler({final:`e`},e=>this.vPositionRelative(e)),this._parser.registerCsiHandler({final:`f`},e=>this.hVPosition(e)),this._parser.registerCsiHandler({final:`g`},e=>this.tabClear(e)),this._parser.registerCsiHandler({final:`h`},e=>this.setMode(e)),this._parser.registerCsiHandler({prefix:`?`,final:`h`},e=>this.setModePrivate(e)),this._parser.registerCsiHandler({final:`l`},e=>this.resetMode(e)),this._parser.registerCsiHandler({prefix:`?`,final:`l`},e=>this.resetModePrivate(e)),this._parser.registerCsiHandler({final:`m`},e=>this.charAttributes(e)),this._parser.registerCsiHandler({final:`n`},e=>this.deviceStatus(e)),this._parser.registerCsiHandler({prefix:`?`,final:`n`},e=>this.deviceStatusPrivate(e)),this._parser.registerCsiHandler({intermediates:`!`,final:`p`},e=>this.softReset(e)),this._parser.registerCsiHandler({intermediates:` `,final:`q`},e=>this.setCursorStyle(e)),this._parser.registerCsiHandler({final:`r`},e=>this.setScrollRegion(e)),this._parser.registerCsiHandler({final:`s`},e=>this.saveCursor(e)),this._parser.registerCsiHandler({final:`t`},e=>this.windowOptions(e)),this._parser.registerCsiHandler({final:`u`},e=>this.restoreCursor(e)),this._parser.registerCsiHandler({intermediates:`'`,final:`}`},e=>this.insertColumns(e)),this._parser.registerCsiHandler({intermediates:`'`,final:`~`},e=>this.deleteColumns(e)),this._parser.registerCsiHandler({intermediates:`"`,final:`q`},e=>this.selectProtected(e)),this._parser.registerCsiHandler({intermediates:`$`,final:`p`},e=>this.requestMode(e,!0)),this._parser.registerCsiHandler({prefix:`?`,intermediates:`$`,final:`p`},e=>this.requestMode(e,!1)),this._parser.setExecuteHandler(L.BEL,()=>this.bell()),this._parser.setExecuteHandler(L.LF,()=>this.lineFeed()),this._parser.setExecuteHandler(L.VT,()=>this.lineFeed()),this._parser.setExecuteHandler(L.FF,()=>this.lineFeed()),this._parser.setExecuteHandler(L.CR,()=>this.carriageReturn()),this._parser.setExecuteHandler(L.BS,()=>this.backspace()),this._parser.setExecuteHandler(L.HT,()=>this.tab()),this._parser.setExecuteHandler(L.SO,()=>this.shiftOut()),this._parser.setExecuteHandler(L.SI,()=>this.shiftIn()),this._parser.setExecuteHandler(bi.IND,()=>this.index()),this._parser.setExecuteHandler(bi.NEL,()=>this.nextLine()),this._parser.setExecuteHandler(bi.HTS,()=>this.tabSet()),this._parser.registerOscHandler(0,new Zo(e=>(this.setTitle(e),this.setIconName(e),!0))),this._parser.registerOscHandler(1,new Zo(e=>this.setIconName(e))),this._parser.registerOscHandler(2,new Zo(e=>this.setTitle(e))),this._parser.registerOscHandler(4,new Zo(e=>this.setOrReportIndexedColor(e))),this._parser.registerOscHandler(8,new Zo(e=>this.setHyperlink(e))),this._parser.registerOscHandler(10,new Zo(e=>this.setOrReportFgColor(e))),this._parser.registerOscHandler(11,new Zo(e=>this.setOrReportBgColor(e))),this._parser.registerOscHandler(12,new Zo(e=>this.setOrReportCursorColor(e))),this._parser.registerOscHandler(104,new Zo(e=>this.restoreIndexedColor(e))),this._parser.registerOscHandler(110,new Zo(e=>this.restoreFgColor(e))),this._parser.registerOscHandler(111,new Zo(e=>this.restoreBgColor(e))),this._parser.registerOscHandler(112,new Zo(e=>this.restoreCursorColor(e))),this._parser.registerEscHandler({final:`7`},()=>this.saveCursor()),this._parser.registerEscHandler({final:`8`},()=>this.restoreCursor()),this._parser.registerEscHandler({final:`D`},()=>this.index()),this._parser.registerEscHandler({final:`E`},()=>this.nextLine()),this._parser.registerEscHandler({final:`H`},()=>this.tabSet()),this._parser.registerEscHandler({final:`M`},()=>this.reverseIndex()),this._parser.registerEscHandler({final:`=`},()=>this.keypadApplicationMode()),this._parser.registerEscHandler({final:`>`},()=>this.keypadNumericMode()),this._parser.registerEscHandler({final:`c`},()=>this.fullReset()),this._parser.registerEscHandler({final:`n`},()=>this.setgLevel(2)),this._parser.registerEscHandler({final:`o`},()=>this.setgLevel(3)),this._parser.registerEscHandler({final:`|`},()=>this.setgLevel(3)),this._parser.registerEscHandler({final:`}`},()=>this.setgLevel(2)),this._parser.registerEscHandler({final:`~`},()=>this.setgLevel(1)),this._parser.registerEscHandler({intermediates:`%`,final:`@`},()=>this.selectDefaultCharset()),this._parser.registerEscHandler({intermediates:`%`,final:`G`},()=>this.selectDefaultCharset());for(let e in X)this._parser.registerEscHandler({intermediates:`(`,final:e},()=>this.selectCharset(`(`+e)),this._parser.registerEscHandler({intermediates:`)`,final:e},()=>this.selectCharset(`)`+e)),this._parser.registerEscHandler({intermediates:`*`,final:e},()=>this.selectCharset(`*`+e)),this._parser.registerEscHandler({intermediates:`+`,final:e},()=>this.selectCharset(`+`+e)),this._parser.registerEscHandler({intermediates:`-`,final:e},()=>this.selectCharset(`-`+e)),this._parser.registerEscHandler({intermediates:`.`,final:e},()=>this.selectCharset(`.`+e)),this._parser.registerEscHandler({intermediates:`/`,final:e},()=>this.selectCharset(`/`+e));this._parser.registerEscHandler({intermediates:`#`,final:`8`},()=>this.screenAlignmentPattern()),this._parser.setErrorHandler(e=>(this._logService.error(`Parsing error: `,e),e)),this._parser.registerDcsHandler({intermediates:`$`,final:`q`},new ts((e,t)=>this.requestStatusString(e,t)))}getAttrData(){return this._curAttrData}_preserveStack(e,t,n,r){this._parseStack.paused=!0,this._parseStack.cursorStartX=e,this._parseStack.cursorStartY=t,this._parseStack.decodedLength=n,this._parseStack.position=r}_logSlowResolvingAsync(e){this._logService.logLevel<=3&&Promise.race([e,new Promise((e,t)=>setTimeout(()=>t(`#SLOW_TIMEOUT`),hs))]).catch(e=>{if(e!==`#SLOW_TIMEOUT`)throw e;console.warn(`async parser handler taking longer than ${hs} ms`)})}_getCurrentLinkId(){return this._curAttrData.extended.urlId}parse(e,t){let n,r=this._activeBuffer.x,i=this._activeBuffer.y,a=0,o=this._parseStack.paused;if(o){if(n=this._parser.parse(this._parseBuffer,this._parseStack.decodedLength,t))return this._logSlowResolvingAsync(n),n;r=this._parseStack.cursorStartX,i=this._parseStack.cursorStartY,this._parseStack.paused=!1,e.length>fs&&(a=this._parseStack.position+fs)}if(this._logService.logLevel<=1&&this._logService.debug(`parsing data ${typeof e==`string`?` "${e}"`:` "${Array.prototype.map.call(e,e=>String.fromCharCode(e)).join(``)}"`}`),this._logService.logLevel===0&&this._logService.trace(`parsing data (codes)`,typeof e==`string`?e.split(``).map(e=>e.charCodeAt(0)):e),this._parseBuffer.lengthfs)for(let t=a;t0&&d.getWidth(this._activeBuffer.x-1)===2&&d.setCellFromCodepoint(this._activeBuffer.x-1,0,1,u);let f=this._parser.precedingJoinState;for(let p=t;ps){if(c){let e=d,t=this._activeBuffer.x-m;for(this._activeBuffer.x=m,this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData(),!0)):(this._activeBuffer.y>=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!0),d=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y),m>0&&d instanceof uo&&d.copyCellsFrom(e,t,0,m,!1);t=0;)d.setCellFromCodepoint(this._activeBuffer.x++,0,0,u);continue}if(l&&(d.insertCells(this._activeBuffer.x,i-m,this._activeBuffer.getNullCell(u)),d.getWidth(s-1)===2&&d.setCellFromCodepoint(s-1,0,1,u)),d.setCellFromCodepoint(this._activeBuffer.x++,r,i,u),i>0)for(;--i;)d.setCellFromCodepoint(this._activeBuffer.x++,0,0,u)}this._parser.precedingJoinState=f,this._activeBuffer.x0&&d.getWidth(this._activeBuffer.x)===0&&!d.hasContent(this._activeBuffer.x)&&d.setCellFromCodepoint(this._activeBuffer.x,0,1,u),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}registerCsiHandler(e,t){return e.final===`t`&&!e.prefix&&!e.intermediates?this._parser.registerCsiHandler(e,e=>!ms(e.params[0],this._optionsService.rawOptions.windowOptions)||t(e)):this._parser.registerCsiHandler(e,t)}registerDcsHandler(e,t){return this._parser.registerDcsHandler(e,new ts(t))}registerEscHandler(e,t){return this._parser.registerEscHandler(e,t)}registerOscHandler(e,t){return this._parser.registerOscHandler(e,new Zo(t))}bell(){return this._onRequestBell.fire(),!0}lineFeed(){return this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._optionsService.rawOptions.convertEol&&(this._activeBuffer.x=0),this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData())):this._activeBuffer.y>=this._bufferService.rows?this._activeBuffer.y=this._bufferService.rows-1:this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.x>=this._bufferService.cols&&this._activeBuffer.x--,this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._onLineFeed.fire(),!0}carriageReturn(){return this._activeBuffer.x=0,!0}backspace(){if(!this._coreService.decPrivateModes.reverseWraparound)return this._restrictCursor(),this._activeBuffer.x>0&&this._activeBuffer.x--,!0;if(this._restrictCursor(this._bufferService.cols),this._activeBuffer.x>0)this._activeBuffer.x--;else if(this._activeBuffer.x===0&&this._activeBuffer.y>this._activeBuffer.scrollTop&&this._activeBuffer.y<=this._activeBuffer.scrollBottom&&this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y)?.isWrapped){this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.y--,this._activeBuffer.x=this._bufferService.cols-1;let e=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y);e.hasWidth(this._activeBuffer.x)&&!e.hasContent(this._activeBuffer.x)&&this._activeBuffer.x--}return this._restrictCursor(),!0}tab(){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let e=this._activeBuffer.x;return this._activeBuffer.x=this._activeBuffer.nextStop(),this._optionsService.rawOptions.screenReaderMode&&this._onA11yTab.fire(this._activeBuffer.x-e),!0}shiftOut(){return this._charsetService.setgLevel(1),!0}shiftIn(){return this._charsetService.setgLevel(0),!0}_restrictCursor(e=this._bufferService.cols-1){this._activeBuffer.x=Math.min(e,Math.max(0,this._activeBuffer.x)),this._activeBuffer.y=this._coreService.decPrivateModes.origin?Math.min(this._activeBuffer.scrollBottom,Math.max(this._activeBuffer.scrollTop,this._activeBuffer.y)):Math.min(this._bufferService.rows-1,Math.max(0,this._activeBuffer.y)),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_setCursor(e,t){this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._coreService.decPrivateModes.origin?(this._activeBuffer.x=e,this._activeBuffer.y=this._activeBuffer.scrollTop+t):(this._activeBuffer.x=e,this._activeBuffer.y=t),this._restrictCursor(),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_moveCursor(e,t){this._restrictCursor(),this._setCursor(this._activeBuffer.x+e,this._activeBuffer.y+t)}cursorUp(e){let t=this._activeBuffer.y-this._activeBuffer.scrollTop;return t>=0?this._moveCursor(0,-Math.min(t,e.params[0]||1)):this._moveCursor(0,-(e.params[0]||1)),!0}cursorDown(e){let t=this._activeBuffer.scrollBottom-this._activeBuffer.y;return t>=0?this._moveCursor(0,Math.min(t,e.params[0]||1)):this._moveCursor(0,e.params[0]||1),!0}cursorForward(e){return this._moveCursor(e.params[0]||1,0),!0}cursorBackward(e){return this._moveCursor(-(e.params[0]||1),0),!0}cursorNextLine(e){return this.cursorDown(e),this._activeBuffer.x=0,!0}cursorPrecedingLine(e){return this.cursorUp(e),this._activeBuffer.x=0,!0}cursorCharAbsolute(e){return this._setCursor((e.params[0]||1)-1,this._activeBuffer.y),!0}cursorPosition(e){return this._setCursor(e.length>=2?(e.params[1]||1)-1:0,(e.params[0]||1)-1),!0}charPosAbsolute(e){return this._setCursor((e.params[0]||1)-1,this._activeBuffer.y),!0}hPositionRelative(e){return this._moveCursor(e.params[0]||1,0),!0}linePosAbsolute(e){return this._setCursor(this._activeBuffer.x,(e.params[0]||1)-1),!0}vPositionRelative(e){return this._moveCursor(0,e.params[0]||1),!0}hVPosition(e){return this.cursorPosition(e),!0}tabClear(e){let t=e.params[0];return t===0?delete this._activeBuffer.tabs[this._activeBuffer.x]:t===3&&(this._activeBuffer.tabs={}),!0}cursorForwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let t=e.params[0]||1;for(;t--;)this._activeBuffer.x=this._activeBuffer.nextStop();return!0}cursorBackwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let t=e.params[0]||1;for(;t--;)this._activeBuffer.x=this._activeBuffer.prevStop();return!0}selectProtected(e){let t=e.params[0];return t===1&&(this._curAttrData.bg|=536870912),(t===2||t===0)&&(this._curAttrData.bg&=-536870913),!0}_eraseInBufferLine(e,t,n,r=!1,i=!1){let a=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);a.replaceCells(t,n,this._activeBuffer.getNullCell(this._eraseAttrData()),i),r&&(a.isWrapped=!1)}_resetBufferLine(e,t=!1){let n=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);n&&(n.fill(this._activeBuffer.getNullCell(this._eraseAttrData()),t),this._bufferService.buffer.clearMarkers(this._activeBuffer.ybase+e),n.isWrapped=!1)}eraseInDisplay(e,t=!1){this._restrictCursor(this._bufferService.cols);let n;switch(e.params[0]){case 0:for(n=this._activeBuffer.y,this._dirtyRowTracker.markDirty(n),this._eraseInBufferLine(n++,this._activeBuffer.x,this._bufferService.cols,this._activeBuffer.x===0,t);n=this._bufferService.cols&&(this._activeBuffer.lines.get(n+1).isWrapped=!1);n--;)this._resetBufferLine(n,t);this._dirtyRowTracker.markDirty(0);break;case 2:if(this._optionsService.rawOptions.scrollOnEraseInDisplay){for(n=this._bufferService.rows,this._dirtyRowTracker.markRangeDirty(0,n-1);n--&&!this._activeBuffer.lines.get(this._activeBuffer.ybase+n)?.getTrimmedLength(););for(;n>=0;n--)this._bufferService.scroll(this._eraseAttrData())}else{for(n=this._bufferService.rows,this._dirtyRowTracker.markDirty(n-1);n--;)this._resetBufferLine(n,t);this._dirtyRowTracker.markDirty(0)}break;case 3:let e=this._activeBuffer.lines.length-this._bufferService.rows;e>0&&(this._activeBuffer.lines.trimStart(e),this._activeBuffer.ybase=Math.max(this._activeBuffer.ybase-e,0),this._activeBuffer.ydisp=Math.max(this._activeBuffer.ydisp-e,0),this._onScroll.fire(0));break}return!0}eraseInLine(e,t=!1){switch(this._restrictCursor(this._bufferService.cols),e.params[0]){case 0:this._eraseInBufferLine(this._activeBuffer.y,this._activeBuffer.x,this._bufferService.cols,this._activeBuffer.x===0,t);break;case 1:this._eraseInBufferLine(this._activeBuffer.y,0,this._activeBuffer.x+1,!1,t);break;case 2:this._eraseInBufferLine(this._activeBuffer.y,0,this._bufferService.cols,!0,t);break}return this._dirtyRowTracker.markDirty(this._activeBuffer.y),!0}insertLines(e){this._restrictCursor();let t=e.params[0]||1;if(this._activeBuffer.y>this._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.y65535?2:1}let c=s;for(let e=1;e0||(this._is(`xterm`)||this._is(`rxvt-unicode`)||this._is(`screen`)?this._coreService.triggerDataEvent(L.ESC+`[?1;2c`):this._is(`linux`)&&this._coreService.triggerDataEvent(L.ESC+`[?6c`)),!0}sendDeviceAttributesSecondary(e){return e.params[0]>0||(this._is(`xterm`)?this._coreService.triggerDataEvent(L.ESC+`[>0;276;0c`):this._is(`rxvt-unicode`)?this._coreService.triggerDataEvent(L.ESC+`[>85;95;0c`):this._is(`linux`)?this._coreService.triggerDataEvent(e.params[0]+`c`):this._is(`screen`)&&this._coreService.triggerDataEvent(L.ESC+`[>83;40003;0c`)),!0}_is(e){return(this._optionsService.rawOptions.termName+``).indexOf(e)===0}setMode(e){for(let t=0;t(e[e.NOT_RECOGNIZED=0]=`NOT_RECOGNIZED`,e[e.SET=1]=`SET`,e[e.RESET=2]=`RESET`,e[e.PERMANENTLY_SET=3]=`PERMANENTLY_SET`,e[e.PERMANENTLY_RESET=4]=`PERMANENTLY_RESET`))(n||={});let r=this._coreService.decPrivateModes,{activeProtocol:i,activeEncoding:a}=this._coreMouseService,o=this._coreService,{buffers:s,cols:c}=this._bufferService,{active:l,alt:u}=s,d=this._optionsService.rawOptions,f=(e,n)=>(o.triggerDataEvent(`${L.ESC}[${t?``:`?`}${e};${n}$y`),!0),p=e=>e?1:2,m=e.params[0];return t?m===2?f(m,4):m===4?f(m,p(o.modes.insertMode)):m===12?f(m,3):m===20?f(m,p(d.convertEol)):f(m,0):m===1?f(m,p(r.applicationCursorKeys)):m===3?f(m,d.windowOptions.setWinLines?c===80?2:+(c===132):0):m===6?f(m,p(r.origin)):m===7?f(m,p(r.wraparound)):m===8?f(m,3):m===9?f(m,p(i===`X10`)):m===12?f(m,p(d.cursorBlink)):m===25?f(m,p(!o.isCursorHidden)):m===45?f(m,p(r.reverseWraparound)):m===66?f(m,p(r.applicationKeypad)):m===67?f(m,4):m===1e3?f(m,p(i===`VT200`)):m===1002?f(m,p(i===`DRAG`)):m===1003?f(m,p(i===`ANY`)):m===1004?f(m,p(r.sendFocus)):m===1005?f(m,4):m===1006?f(m,p(a===`SGR`)):m===1015?f(m,4):m===1016?f(m,p(a===`SGR_PIXELS`)):m===1048?f(m,1):m===47||m===1047||m===1049?f(m,p(l===u)):m===2004?f(m,p(r.bracketedPasteMode)):m===2026?f(m,p(r.synchronizedOutput)):f(m,0)}_updateAttrColor(e,t,n,r,i){return t===2?(e|=50331648,e&=-16777216,e|=Te.fromColorRGB([n,r,i])):t===5&&(e&=-50331904,e|=33554432|n&255),e}_extractColor(e,t,n){let r=[0,0,-1,0,0,0],i=0,a=0;do{if(r[a+i]=e.params[t+a],e.hasSubParams(t+a)){let n=e.getSubParams(t+a),o=0;do r[1]===5&&(i=1),r[a+o+1+i]=n[o];while(++o=2||r[1]===2&&a+i>=5)break;r[1]&&(i=1)}while(++a+t5)&&(e=1),t.extended.underlineStyle=e,t.fg|=268435456,e===0&&(t.fg&=-268435457),t.updateExtended()}_processSGR0(e){e.fg=Y.fg,e.bg=Y.bg,e.extended=e.extended.clone(),e.extended.underlineStyle=0,e.extended.underlineColor&=-67108864,e.updateExtended()}charAttributes(e){if(e.length===1&&e.params[0]===0)return this._processSGR0(this._curAttrData),!0;let t=e.length,n,r=this._curAttrData;for(let i=0;i=30&&n<=37?(r.fg&=-50331904,r.fg|=16777216|n-30):n>=40&&n<=47?(r.bg&=-50331904,r.bg|=16777216|n-40):n>=90&&n<=97?(r.fg&=-50331904,r.fg|=n-90|16777224):n>=100&&n<=107?(r.bg&=-50331904,r.bg|=n-100|16777224):n===0?this._processSGR0(r):n===1?r.fg|=134217728:n===3?r.bg|=67108864:n===4?(r.fg|=268435456,this._processUnderline(e.hasSubParams(i)?e.getSubParams(i)[0]:1,r)):n===5?r.fg|=536870912:n===7?r.fg|=67108864:n===8?r.fg|=1073741824:n===9?r.fg|=2147483648:n===2?r.bg|=134217728:n===21?this._processUnderline(2,r):n===22?(r.fg&=-134217729,r.bg&=-134217729):n===23?r.bg&=-67108865:n===24?(r.fg&=-268435457,this._processUnderline(0,r)):n===25?r.fg&=-536870913:n===27?r.fg&=-67108865:n===28?r.fg&=-1073741825:n===29?r.fg&=2147483647:n===39?(r.fg&=-67108864,r.fg|=Y.fg&16777215):n===49?(r.bg&=-67108864,r.bg|=Y.bg&16777215):n===38||n===48||n===58?i+=this._extractColor(e,i,r):n===53?r.bg|=1073741824:n===55?r.bg&=-1073741825:n===59?(r.extended=r.extended.clone(),r.extended.underlineColor=-1,r.updateExtended()):n===100?(r.fg&=-67108864,r.fg|=Y.fg&16777215,r.bg&=-67108864,r.bg|=Y.bg&16777215):this._logService.debug(`Unknown SGR attribute: %d.`,n);return!0}deviceStatus(e){switch(e.params[0]){case 5:this._coreService.triggerDataEvent(`${L.ESC}[0n`);break;case 6:let e=this._activeBuffer.y+1,t=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`${L.ESC}[${e};${t}R`);break}return!0}deviceStatusPrivate(e){switch(e.params[0]){case 6:let e=this._activeBuffer.y+1,t=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`${L.ESC}[?${e};${t}R`);break;case 15:break;case 25:break;case 26:break;case 53:break}return!0}softReset(e){return this._coreService.isCursorHidden=!1,this._onRequestSyncScrollBar.fire(),this._activeBuffer.scrollTop=0,this._activeBuffer.scrollBottom=this._bufferService.rows-1,this._curAttrData=Y.clone(),this._coreService.reset(),this._charsetService.reset(),this._activeBuffer.savedX=0,this._activeBuffer.savedY=this._activeBuffer.ybase,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,this._coreService.decPrivateModes.origin=!1,!0}setCursorStyle(e){let t=e.length===0?1:e.params[0];if(t===0)this._coreService.decPrivateModes.cursorStyle=void 0,this._coreService.decPrivateModes.cursorBlink=void 0;else{switch(t){case 1:case 2:this._coreService.decPrivateModes.cursorStyle=`block`;break;case 3:case 4:this._coreService.decPrivateModes.cursorStyle=`underline`;break;case 5:case 6:this._coreService.decPrivateModes.cursorStyle=`bar`;break}let e=t%2==1;this._coreService.decPrivateModes.cursorBlink=e}return!0}setScrollRegion(e){let t=e.params[0]||1,n;return(e.length<2||(n=e.params[1])>this._bufferService.rows||n===0)&&(n=this._bufferService.rows),n>t&&(this._activeBuffer.scrollTop=t-1,this._activeBuffer.scrollBottom=n-1,this._setCursor(0,0)),!0}windowOptions(e){if(!ms(e.params[0],this._optionsService.rawOptions.windowOptions))return!0;let t=e.length>1?e.params[1]:0;switch(e.params[0]){case 14:t!==2&&this._onRequestWindowsOptionsReport.fire(0);break;case 16:this._onRequestWindowsOptionsReport.fire(1);break;case 18:this._bufferService&&this._coreService.triggerDataEvent(`${L.ESC}[8;${this._bufferService.rows};${this._bufferService.cols}t`);break;case 22:(t===0||t===2)&&(this._windowTitleStack.push(this._windowTitle),this._windowTitleStack.length>ps&&this._windowTitleStack.shift()),(t===0||t===1)&&(this._iconNameStack.push(this._iconName),this._iconNameStack.length>ps&&this._iconNameStack.shift());break;case 23:(t===0||t===2)&&this._windowTitleStack.length&&this.setTitle(this._windowTitleStack.pop()),(t===0||t===1)&&this._iconNameStack.length&&this.setIconName(this._iconNameStack.pop());break}return!0}saveCursor(e){return this._activeBuffer.savedX=this._activeBuffer.x,this._activeBuffer.savedY=this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,!0}restoreCursor(e){return this._activeBuffer.x=this._activeBuffer.savedX||0,this._activeBuffer.y=Math.max(this._activeBuffer.savedY-this._activeBuffer.ybase,0),this._curAttrData.fg=this._activeBuffer.savedCurAttrData.fg,this._curAttrData.bg=this._activeBuffer.savedCurAttrData.bg,this._charsetService.charset=this._savedCharset,this._activeBuffer.savedCharset&&(this._charsetService.charset=this._activeBuffer.savedCharset),this._restrictCursor(),!0}setTitle(e){return this._windowTitle=e,this._onTitleChange.fire(e),!0}setIconName(e){return this._iconName=e,!0}setOrReportIndexedColor(e){let t=[],n=e.split(`;`);for(;n.length>1;){let e=n.shift(),r=n.shift();if(/^\d+$/.exec(e)){let n=parseInt(e);if(ys(n))if(r===`?`)t.push({type:0,index:n});else{let e=cs(r);e&&t.push({type:1,index:n,color:e})}}}return t.length&&this._onColor.fire(t),!0}setHyperlink(e){let t=e.indexOf(`;`);if(t===-1)return!0;let n=e.slice(0,t).trim(),r=e.slice(t+1);return r?this._createHyperlink(n,r):!n.trim()&&this._finishHyperlink()}_createHyperlink(e,t){this._getCurrentLinkId()&&this._finishHyperlink();let n=e.split(`:`),r,i=n.findIndex(e=>e.startsWith(`id=`));return i!==-1&&(r=n[i].slice(3)||void 0),this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=this._oscLinkService.registerLink({id:r,uri:t}),this._curAttrData.updateExtended(),!0}_finishHyperlink(){return this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=0,this._curAttrData.updateExtended(),!0}_setOrReportSpecialColor(e,t){let n=e.split(`;`);for(let e=0;e=this._specialColors.length);++e,++t)if(n[e]===`?`)this._onColor.fire([{type:0,index:this._specialColors[t]}]);else{let r=cs(n[e]);r&&this._onColor.fire([{type:1,index:this._specialColors[t],color:r}])}return!0}setOrReportFgColor(e){return this._setOrReportSpecialColor(e,0)}setOrReportBgColor(e){return this._setOrReportSpecialColor(e,1)}setOrReportCursorColor(e){return this._setOrReportSpecialColor(e,2)}restoreIndexedColor(e){if(!e)return this._onColor.fire([{type:2}]),!0;let t=[],n=e.split(`;`);for(let e=0;e=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._restrictCursor(),!0}tabSet(){return this._activeBuffer.tabs[this._activeBuffer.x]=!0,!0}reverseIndex(){if(this._restrictCursor(),this._activeBuffer.y===this._activeBuffer.scrollTop){let e=this._activeBuffer.scrollBottom-this._activeBuffer.scrollTop;this._activeBuffer.lines.shiftElements(this._activeBuffer.ybase+this._activeBuffer.y,e,1),this._activeBuffer.lines.set(this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.getBlankLine(this._eraseAttrData())),this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop,this._activeBuffer.scrollBottom)}else this._activeBuffer.y--,this._restrictCursor();return!0}fullReset(){return this._parser.reset(),this._onRequestReset.fire(),!0}reset(){this._curAttrData=Y.clone(),this._eraseAttrDataInternal=Y.clone()}_eraseAttrData(){return this._eraseAttrDataInternal.bg&=-67108864,this._eraseAttrDataInternal.bg|=this._curAttrData.bg&67108863,this._eraseAttrDataInternal}setgLevel(e){return this._charsetService.setgLevel(e),!0}screenAlignmentPattern(){let e=new De;e.content=4194373,e.fg=this._curAttrData.fg,e.bg=this._curAttrData.bg,this._setCursor(0,0);for(let t=0;t(this._coreService.triggerDataEvent(`${L.ESC}${e}${L.ESC}\\`),!0),r=this._bufferService.buffer,i=this._optionsService.rawOptions;return n(e===`"q`?`P1$r${+!!this._curAttrData.isProtected()}"q`:e===`"p`?`P1$r61;1"p`:e===`r`?`P1$r${r.scrollTop+1};${r.scrollBottom+1}r`:e===`m`?`P1$r0m`:e===` q`?`P1$r${{block:2,underline:4,bar:6}[i.cursorStyle]-+!!i.cursorBlink} q`:`P0$r`)}markRangeDirty(e,t){this._dirtyRowTracker.markRangeDirty(e,t)}},vs=class{constructor(e){this._bufferService=e,this.clearRange()}clearRange(){this.start=this._bufferService.buffer.y,this.end=this._bufferService.buffer.y}markDirty(e){ethis.end&&(this.end=e)}markRangeDirty(e,t){e>t&&(gs=e,e=t,t=gs),ethis.end&&(this.end=t)}markAllDirty(){this.markRangeDirty(0,this._bufferService.rows-1)}};vs=C([w(0,D)],vs);function ys(e){return 0<=e&&e<256}var bs=5e7,xs=12,Ss=50,Cs=class extends A{constructor(e){super(),this._action=e,this._writeBuffer=[],this._callbacks=[],this._pendingData=0,this._bufferOffset=0,this._isSyncWriting=!1,this._syncCalls=0,this._didUserInput=!1,this._onWriteParsed=this._register(new N),this.onWriteParsed=this._onWriteParsed.event}handleUserInput(){this._didUserInput=!0}writeSync(e,t){if(t!==void 0&&this._syncCalls>t){this._syncCalls=0;return}if(this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(void 0),this._syncCalls++,this._isSyncWriting)return;this._isSyncWriting=!0;let n;for(;n=this._writeBuffer.shift();){this._action(n);let e=this._callbacks.shift();e&&e()}this._pendingData=0,this._bufferOffset=2147483647,this._isSyncWriting=!1,this._syncCalls=0}write(e,t){if(this._pendingData>bs)throw Error(`write data discarded, use flow control to avoid losing data`);if(!this._writeBuffer.length){if(this._bufferOffset=0,this._didUserInput){this._didUserInput=!1,this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t),this._innerWrite();return}setTimeout(()=>this._innerWrite())}this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t)}_innerWrite(e=0,t=!0){let n=e||performance.now();for(;this._writeBuffer.length>this._bufferOffset;){let e=this._writeBuffer[this._bufferOffset],r=this._action(e,t);if(r){r.catch(e=>(queueMicrotask(()=>{throw e}),Promise.resolve(!1))).then(e=>performance.now()-n>=xs?setTimeout(()=>this._innerWrite(0,e)):this._innerWrite(n,e));return}let i=this._callbacks[this._bufferOffset];if(i&&i(),this._bufferOffset++,this._pendingData-=e.length,performance.now()-n>=xs)break}this._writeBuffer.length>this._bufferOffset?(this._bufferOffset>Ss&&(this._writeBuffer=this._writeBuffer.slice(this._bufferOffset),this._callbacks=this._callbacks.slice(this._bufferOffset),this._bufferOffset=0),setTimeout(()=>this._innerWrite())):(this._writeBuffer.length=0,this._callbacks.length=0,this._pendingData=0,this._bufferOffset=0),this._onWriteParsed.fire()}},ws=class{constructor(e){this._bufferService=e,this._nextId=1,this._entriesWithId=new Map,this._dataByLinkId=new Map}registerLink(e){let t=this._bufferService.buffer;if(e.id===void 0){let n=t.addMarker(t.ybase+t.y),r={data:e,id:this._nextId++,lines:[n]};return n.onDispose(()=>this._removeMarkerFromLink(r,n)),this._dataByLinkId.set(r.id,r),r.id}let n=e,r=this._getEntryIdKey(n),i=this._entriesWithId.get(r);if(i)return this.addLineToLink(i.id,t.ybase+t.y),i.id;let a=t.addMarker(t.ybase+t.y),o={id:this._nextId++,key:this._getEntryIdKey(n),data:n,lines:[a]};return a.onDispose(()=>this._removeMarkerFromLink(o,a)),this._entriesWithId.set(o.key,o),this._dataByLinkId.set(o.id,o),o.id}addLineToLink(e,t){let n=this._dataByLinkId.get(e);if(n&&n.lines.every(e=>e.line!==t)){let e=this._bufferService.buffer.addMarker(t);n.lines.push(e),e.onDispose(()=>this._removeMarkerFromLink(n,e))}}getLinkData(e){return this._dataByLinkId.get(e)?.data}_getEntryIdKey(e){return`${e.id};;${e.uri}`}_removeMarkerFromLink(e,t){let n=e.lines.indexOf(t);n!==-1&&(e.lines.splice(n,1),e.lines.length===0&&(e.data.id!==void 0&&this._entriesWithId.delete(e.key),this._dataByLinkId.delete(e.id)))}};ws=C([w(0,D)],ws);var Ts=!1,Es=class extends A{constructor(e){super(),this._windowsWrappingHeuristics=this._register(new Et),this._onBinary=this._register(new N),this.onBinary=this._onBinary.event,this._onData=this._register(new N),this.onData=this._onData.event,this._onLineFeed=this._register(new N),this.onLineFeed=this._onLineFeed.event,this._onResize=this._register(new N),this.onResize=this._onResize.event,this._onWriteParsed=this._register(new N),this.onWriteParsed=this._onWriteParsed.event,this._onScroll=this._register(new N),this._instantiationService=new no,this.optionsService=this._register(new Oo(e)),this._instantiationService.setService(O,this.optionsService),this._bufferService=this._register(this._instantiationService.createInstance(To)),this._instantiationService.setService(D,this._bufferService),this._logService=this._register(this._instantiationService.createInstance(ao)),this._instantiationService.setService(Le,this._logService),this.coreService=this._register(this._instantiationService.createInstance(No)),this._instantiationService.setService(Pe,this.coreService),this.coreMouseService=this._register(this._instantiationService.createInstance(Ro)),this._instantiationService.setService(Ne,this.coreMouseService),this.unicodeService=this._register(this._instantiationService.createInstance(Uo)),this._instantiationService.setService(ze,this.unicodeService),this._charsetService=this._instantiationService.createInstance(Wo),this._instantiationService.setService(Fe,this._charsetService),this._oscLinkService=this._instantiationService.createInstance(ws),this._instantiationService.setService(Re,this._oscLinkService),this._inputHandler=this._register(new _s(this._bufferService,this._charsetService,this.coreService,this._logService,this.optionsService,this._oscLinkService,this.coreMouseService,this.unicodeService)),this._register(M.forward(this._inputHandler.onLineFeed,this._onLineFeed)),this._register(this._inputHandler),this._register(M.forward(this._bufferService.onResize,this._onResize)),this._register(M.forward(this.coreService.onData,this._onData)),this._register(M.forward(this.coreService.onBinary,this._onBinary)),this._register(this.coreService.onRequestScrollToBottom(()=>this.scrollToBottom(!0))),this._register(this.coreService.onUserInput(()=>this._writeBuffer.handleUserInput())),this._register(this.optionsService.onMultipleOptionChange([`windowsMode`,`windowsPty`],()=>this._handleWindowsPtyOptionChange())),this._register(this._bufferService.onScroll(()=>{this._onScroll.fire({position:this._bufferService.buffer.ydisp}),this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop,this._bufferService.buffer.scrollBottom)})),this._writeBuffer=this._register(new Cs((e,t)=>this._inputHandler.parse(e,t))),this._register(M.forward(this._writeBuffer.onWriteParsed,this._onWriteParsed))}get onScroll(){return this._onScrollApi||(this._onScrollApi=this._register(new N),this._onScroll.event(e=>{this._onScrollApi?.fire(e.position)})),this._onScrollApi.event}get cols(){return this._bufferService.cols}get rows(){return this._bufferService.rows}get buffers(){return this._bufferService.buffers}get options(){return this.optionsService.options}set options(e){for(let t in e)this.optionsService.options[t]=e[t]}write(e,t){this._writeBuffer.write(e,t)}writeSync(e,t){this._logService.logLevel<=3&&!Ts&&(this._logService.warn(`writeSync is unreliable and will be removed soon.`),Ts=!0),this._writeBuffer.writeSync(e,t)}input(e,t=!0){this.coreService.triggerDataEvent(e,t)}resize(e,t){isNaN(e)||isNaN(t)||(e=Math.max(e,Co),t=Math.max(t,wo),this._bufferService.resize(e,t))}scroll(e,t=!1){this._bufferService.scroll(e,t)}scrollLines(e,t){this._bufferService.scrollLines(e,t)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(e){this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){let t=e-this._bufferService.buffer.ydisp;t!==0&&this.scrollLines(t)}registerEscHandler(e,t){return this._inputHandler.registerEscHandler(e,t)}registerDcsHandler(e,t){return this._inputHandler.registerDcsHandler(e,t)}registerCsiHandler(e,t){return this._inputHandler.registerCsiHandler(e,t)}registerOscHandler(e,t){return this._inputHandler.registerOscHandler(e,t)}_setup(){this._handleWindowsPtyOptionChange()}reset(){this._inputHandler.reset(),this._bufferService.reset(),this._charsetService.reset(),this.coreService.reset(),this.coreMouseService.reset()}_handleWindowsPtyOptionChange(){let e=!1,t=this.optionsService.rawOptions.windowsPty;t&&t.buildNumber!==void 0&&t.buildNumber!==void 0?e=t.backend===`conpty`&&t.buildNumber<21376:this.optionsService.rawOptions.windowsMode&&(e=!0),e?this._enableWindowsWrappingHeuristics():this._windowsWrappingHeuristics.clear()}_enableWindowsWrappingHeuristics(){if(!this._windowsWrappingHeuristics.value){let e=[];e.push(this.onLineFeed(Go.bind(null,this._bufferService))),e.push(this.registerCsiHandler({final:`H`},()=>(Go(this._bufferService),!1))),this._windowsWrappingHeuristics.value=k(()=>{for(let t of e)t.dispose()})}}},Ds={48:[`0`,`)`],49:[`1`,`!`],50:[`2`,`@`],51:[`3`,`#`],52:[`4`,`$`],53:[`5`,`%`],54:[`6`,`^`],55:[`7`,`&`],56:[`8`,`*`],57:[`9`,`(`],186:[`;`,`:`],187:[`=`,`+`],188:[`,`,`<`],189:[`-`,`_`],190:[`.`,`>`],191:[`/`,`?`],192:["`",`~`],219:[`[`,`{`],220:[`\\`,`|`],221:[`]`,`}`],222:[`'`,`"`]};function Os(e,t,n,r){let i={type:0,cancel:!1,key:void 0},a=!!e.shiftKey|(e.altKey?2:0)|(e.ctrlKey?4:0)|(e.metaKey?8:0);switch(e.keyCode){case 0:e.key===`UIKeyInputUpArrow`?t?i.key=L.ESC+`OA`:i.key=L.ESC+`[A`:e.key===`UIKeyInputLeftArrow`?t?i.key=L.ESC+`OD`:i.key=L.ESC+`[D`:e.key===`UIKeyInputRightArrow`?t?i.key=L.ESC+`OC`:i.key=L.ESC+`[C`:e.key===`UIKeyInputDownArrow`&&(t?i.key=L.ESC+`OB`:i.key=L.ESC+`[B`);break;case 8:i.key=e.ctrlKey?`\b`:L.DEL,e.altKey&&(i.key=L.ESC+i.key);break;case 9:if(e.shiftKey){i.key=L.ESC+`[Z`;break}i.key=L.HT,i.cancel=!0;break;case 13:i.key=e.altKey?L.ESC+L.CR:L.CR,i.cancel=!0;break;case 27:i.key=L.ESC,e.altKey&&(i.key=L.ESC+L.ESC),i.cancel=!0;break;case 37:if(e.metaKey)break;a?i.key=L.ESC+`[1;`+(a+1)+`D`:t?i.key=L.ESC+`OD`:i.key=L.ESC+`[D`;break;case 39:if(e.metaKey)break;a?i.key=L.ESC+`[1;`+(a+1)+`C`:t?i.key=L.ESC+`OC`:i.key=L.ESC+`[C`;break;case 38:if(e.metaKey)break;a?i.key=L.ESC+`[1;`+(a+1)+`A`:t?i.key=L.ESC+`OA`:i.key=L.ESC+`[A`;break;case 40:if(e.metaKey)break;a?i.key=L.ESC+`[1;`+(a+1)+`B`:t?i.key=L.ESC+`OB`:i.key=L.ESC+`[B`;break;case 45:!e.shiftKey&&!e.ctrlKey&&(i.key=L.ESC+`[2~`);break;case 46:a?i.key=L.ESC+`[3;`+(a+1)+`~`:i.key=L.ESC+`[3~`;break;case 36:a?i.key=L.ESC+`[1;`+(a+1)+`H`:t?i.key=L.ESC+`OH`:i.key=L.ESC+`[H`;break;case 35:a?i.key=L.ESC+`[1;`+(a+1)+`F`:t?i.key=L.ESC+`OF`:i.key=L.ESC+`[F`;break;case 33:e.shiftKey?i.type=2:e.ctrlKey?i.key=L.ESC+`[5;`+(a+1)+`~`:i.key=L.ESC+`[5~`;break;case 34:e.shiftKey?i.type=3:e.ctrlKey?i.key=L.ESC+`[6;`+(a+1)+`~`:i.key=L.ESC+`[6~`;break;case 112:a?i.key=L.ESC+`[1;`+(a+1)+`P`:i.key=L.ESC+`OP`;break;case 113:a?i.key=L.ESC+`[1;`+(a+1)+`Q`:i.key=L.ESC+`OQ`;break;case 114:a?i.key=L.ESC+`[1;`+(a+1)+`R`:i.key=L.ESC+`OR`;break;case 115:a?i.key=L.ESC+`[1;`+(a+1)+`S`:i.key=L.ESC+`OS`;break;case 116:a?i.key=L.ESC+`[15;`+(a+1)+`~`:i.key=L.ESC+`[15~`;break;case 117:a?i.key=L.ESC+`[17;`+(a+1)+`~`:i.key=L.ESC+`[17~`;break;case 118:a?i.key=L.ESC+`[18;`+(a+1)+`~`:i.key=L.ESC+`[18~`;break;case 119:a?i.key=L.ESC+`[19;`+(a+1)+`~`:i.key=L.ESC+`[19~`;break;case 120:a?i.key=L.ESC+`[20;`+(a+1)+`~`:i.key=L.ESC+`[20~`;break;case 121:a?i.key=L.ESC+`[21;`+(a+1)+`~`:i.key=L.ESC+`[21~`;break;case 122:a?i.key=L.ESC+`[23;`+(a+1)+`~`:i.key=L.ESC+`[23~`;break;case 123:a?i.key=L.ESC+`[24;`+(a+1)+`~`:i.key=L.ESC+`[24~`;break;default:if(e.ctrlKey&&!e.shiftKey&&!e.altKey&&!e.metaKey)e.keyCode>=65&&e.keyCode<=90?i.key=String.fromCharCode(e.keyCode-64):e.keyCode===32?i.key=L.NUL:e.keyCode>=51&&e.keyCode<=55?i.key=String.fromCharCode(e.keyCode-51+27):e.keyCode===56?i.key=L.DEL:e.keyCode===219?i.key=L.ESC:e.keyCode===220?i.key=L.FS:e.keyCode===221&&(i.key=L.GS);else if((!n||r)&&e.altKey&&!e.metaKey){let t=Ds[e.keyCode]?.[+!!e.shiftKey];if(t)i.key=L.ESC+t;else if(e.keyCode>=65&&e.keyCode<=90){let t=e.ctrlKey?e.keyCode-64:e.keyCode+32,n=String.fromCharCode(t);e.shiftKey&&(n=n.toUpperCase()),i.key=L.ESC+n}else if(e.keyCode===32)i.key=L.ESC+(e.ctrlKey?L.NUL:` `);else if(e.key===`Dead`&&e.code.startsWith(`Key`)){let t=e.code.slice(3,4);e.shiftKey||(t=t.toLowerCase()),i.key=L.ESC+t,i.cancel=!0}}else n&&!e.altKey&&!e.ctrlKey&&!e.shiftKey&&e.metaKey?e.keyCode===65&&(i.type=1):e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.keyCode>=48&&e.key.length===1?i.key=e.key:e.key&&e.ctrlKey&&(e.key===`_`&&(i.key=L.US),e.key===`@`&&(i.key=L.NUL));break}return i}var Q=0,ks=class{constructor(e){this._getKey=e,this._array=[],this._insertedValues=[],this._flushInsertedTask=new xa,this._isFlushingInserted=!1,this._deletedIndices=[],this._flushDeletedTask=new xa,this._isFlushingDeleted=!1}clear(){this._array.length=0,this._insertedValues.length=0,this._flushInsertedTask.clear(),this._isFlushingInserted=!1,this._deletedIndices.length=0,this._flushDeletedTask.clear(),this._isFlushingDeleted=!1}insert(e){this._flushCleanupDeleted(),this._insertedValues.length===0&&this._flushInsertedTask.enqueue(()=>this._flushInserted()),this._insertedValues.push(e)}_flushInserted(){let e=this._insertedValues.sort((e,t)=>this._getKey(e)-this._getKey(t)),t=0,n=0,r=Array(this._array.length+this._insertedValues.length);for(let i=0;i=this._array.length||this._getKey(e[t])<=this._getKey(this._array[n])?(r[i]=e[t],t++):r[i]=this._array[n++];this._array=r,this._insertedValues.length=0}_flushCleanupInserted(){!this._isFlushingInserted&&this._insertedValues.length>0&&this._flushInsertedTask.flush()}delete(e){if(this._flushCleanupInserted(),this._array.length===0)return!1;let t=this._getKey(e);if(t===void 0||(Q=this._search(t),Q===-1)||this._getKey(this._array[Q])!==t)return!1;do if(this._array[Q]===e)return this._deletedIndices.length===0&&this._flushDeletedTask.enqueue(()=>this._flushDeleted()),this._deletedIndices.push(Q),!0;while(++Qe-t),t=0,n=Array(this._array.length-e.length),r=0;for(let i=0;i0&&this._flushDeletedTask.flush()}*getKeyIterator(e){if(this._flushCleanupInserted(),this._flushCleanupDeleted(),this._array.length!==0&&(Q=this._search(e),!(Q<0||Q>=this._array.length)&&this._getKey(this._array[Q])===e))do yield this._array[Q];while(++Q=this._array.length)&&this._getKey(this._array[Q])===e))do t(this._array[Q]);while(++Q=t;){let r=t+n>>1,i=this._getKey(this._array[r]);if(i>e)n=r-1;else if(i0&&this._getKey(this._array[r-1])===e;)r--;return r}}return t}},As=0,js=0,Ms=class extends A{constructor(){super(),this._decorations=new ks(e=>e?.marker.line),this._onDecorationRegistered=this._register(new N),this.onDecorationRegistered=this._onDecorationRegistered.event,this._onDecorationRemoved=this._register(new N),this.onDecorationRemoved=this._onDecorationRemoved.event,this._register(k(()=>this.reset()))}get decorations(){return this._decorations.values()}registerDecoration(e){if(e.marker.isDisposed)return;let t=new Ns(e);if(t){let e=t.marker.onDispose(()=>t.dispose()),n=t.onDispose(()=>{n.dispose(),t&&(this._decorations.delete(t)&&this._onDecorationRemoved.fire(t),e.dispose())});this._decorations.insert(t),this._onDecorationRegistered.fire(t)}return t}reset(){for(let e of this._decorations.values())e.dispose();this._decorations.clear()}*getDecorationsAtCell(e,t,n){let r=0,i=0;for(let a of this._decorations.getKeyIterator(t))r=a.options.x??0,i=r+(a.options.width??1),e>=r&&e{As=t.options.x??0,js=As+(t.options.width??1),e>=As&&e=this._debounceThresholdMS)this._lastRefreshMs=r,this._innerRefresh();else if(!this._additionalRefreshRequested){let e=r-this._lastRefreshMs,t=this._debounceThresholdMS-e;this._additionalRefreshRequested=!0,this._refreshTimeoutID=window.setTimeout(()=>{this._lastRefreshMs=performance.now(),this._innerRefresh(),this._additionalRefreshRequested=!1,this._refreshTimeoutID=void 0},t)}}_innerRefresh(){if(this._rowStart===void 0||this._rowEnd===void 0||this._rowCount===void 0)return;let e=Math.max(this._rowStart,0),t=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(e,t)}},Is=20,Ls=class extends A{constructor(e,t,n,r){super(),this._terminal=e,this._coreBrowserService=n,this._renderService=r,this._rowColumns=new WeakMap,this._liveRegionLineCount=0,this._charsToConsume=[],this._charsToAnnounce=``;let i=this._coreBrowserService.mainDocument;this._accessibilityContainer=i.createElement(`div`),this._accessibilityContainer.classList.add(`xterm-accessibility`),this._rowContainer=i.createElement(`div`),this._rowContainer.setAttribute(`role`,`list`),this._rowContainer.classList.add(`xterm-accessibility-tree`),this._rowElements=[];for(let e=0;ethis._handleBoundaryFocus(e,0),this._bottomBoundaryFocusListener=e=>this._handleBoundaryFocus(e,1),this._rowElements[0].addEventListener(`focus`,this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener(`focus`,this._bottomBoundaryFocusListener),this._accessibilityContainer.appendChild(this._rowContainer),this._liveRegion=i.createElement(`div`),this._liveRegion.classList.add(`live-region`),this._liveRegion.setAttribute(`aria-live`,`assertive`),this._accessibilityContainer.appendChild(this._liveRegion),this._liveRegionDebouncer=this._register(new Fs(this._renderRows.bind(this))),!this._terminal.element)throw Error(`Cannot enable accessibility before Terminal.open`);this._terminal.element.insertAdjacentElement(`afterbegin`,this._accessibilityContainer),this._register(this._terminal.onResize(e=>this._handleResize(e.rows))),this._register(this._terminal.onRender(e=>this._refreshRows(e.start,e.end))),this._register(this._terminal.onScroll(()=>this._refreshRows())),this._register(this._terminal.onA11yChar(e=>this._handleChar(e))),this._register(this._terminal.onLineFeed(()=>this._handleChar(` -`))),this._register(this._terminal.onA11yTab(e=>this._handleTab(e))),this._register(this._terminal.onKey(e=>this._handleKey(e.key))),this._register(this._terminal.onBlur(()=>this._clearLiveRegion())),this._register(this._renderService.onDimensionsChange(()=>this._refreshRowsDimensions())),this._register(P(i,`selectionchange`,()=>this._handleSelectionChange())),this._register(this._coreBrowserService.onDprChange(()=>this._refreshRowsDimensions())),this._refreshRowsDimensions(),this._refreshRows(),this._register(k(()=>{this._accessibilityContainer.remove(),this._rowElements.length=0}))}_handleTab(e){for(let t=0;t0?this._charsToConsume.shift()!==e&&(this._charsToAnnounce+=e):this._charsToAnnounce+=e,e===` -`&&(this._liveRegionLineCount++,this._liveRegionLineCount===Is+1&&(this._liveRegion.textContent+=fe.get())))}_clearLiveRegion(){this._liveRegion.textContent=``,this._liveRegionLineCount=0}_handleKey(e){this._clearLiveRegion(),/\p{Control}/u.test(e)||this._charsToConsume.push(e)}_refreshRows(e,t){this._liveRegionDebouncer.refresh(e,t,this._terminal.rows)}_renderRows(e,t){let n=this._terminal.buffer,r=n.lines.length.toString();for(let i=e;i<=t;i++){let e=n.lines.get(n.ydisp+i),t=[],a=e?.translateToString(!0,void 0,void 0,t)||``,o=(n.ydisp+i+1).toString(),s=this._rowElements[i];s&&(a.length===0?(s.textContent=`\xA0`,this._rowColumns.set(s,[0,1])):(s.textContent=a,this._rowColumns.set(s,t)),s.setAttribute(`aria-posinset`,o),s.setAttribute(`aria-setsize`,r),this._alignRowWidth(s))}this._announceCharacters()}_announceCharacters(){this._charsToAnnounce.length!==0&&(this._liveRegion.textContent+=this._charsToAnnounce,this._charsToAnnounce=``)}_handleBoundaryFocus(e,t){let n=e.target,r=this._rowElements[t===0?1:this._rowElements.length-2];if(n.getAttribute(`aria-posinset`)===(t===0?`1`:`${this._terminal.buffer.lines.length}`)||e.relatedTarget!==r)return;let i,a;if(t===0?(i=n,a=this._rowElements.pop(),this._rowContainer.removeChild(a)):(i=this._rowElements.shift(),a=n,this._rowContainer.removeChild(i)),i.removeEventListener(`focus`,this._topBoundaryFocusListener),a.removeEventListener(`focus`,this._bottomBoundaryFocusListener),t===0){let e=this._createAccessibilityTreeNode();this._rowElements.unshift(e),this._rowContainer.insertAdjacentElement(`afterbegin`,e)}else{let e=this._createAccessibilityTreeNode();this._rowElements.push(e),this._rowContainer.appendChild(e)}this._rowElements[0].addEventListener(`focus`,this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener(`focus`,this._bottomBoundaryFocusListener),this._terminal.scrollLines(t===0?-1:1),this._rowElements[t===0?1:this._rowElements.length-2].focus(),e.preventDefault(),e.stopImmediatePropagation()}_handleSelectionChange(){if(this._rowElements.length===0)return;let e=this._coreBrowserService.mainDocument.getSelection();if(!e)return;if(e.isCollapsed){this._rowContainer.contains(e.anchorNode)&&this._terminal.clearSelection();return}if(!e.anchorNode||!e.focusNode){console.error(`anchorNode and/or focusNode are null`);return}let t={node:e.anchorNode,offset:e.anchorOffset},n={node:e.focusNode,offset:e.focusOffset};if((t.node.compareDocumentPosition(n.node)&Node.DOCUMENT_POSITION_PRECEDING||t.node===n.node&&t.offset>n.offset)&&([t,n]=[n,t]),t.node.compareDocumentPosition(this._rowElements[0])&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_FOLLOWING)&&(t={node:this._rowElements[0].childNodes[0],offset:0}),!this._rowContainer.contains(t.node))return;let r=this._rowElements.slice(-1)[0];if(n.node.compareDocumentPosition(r)&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_PRECEDING)&&(n={node:r,offset:r.textContent?.length??0}),!this._rowContainer.contains(n.node))return;let i=({node:e,offset:t})=>{let n=e instanceof Text?e.parentNode:e,r=parseInt(n?.getAttribute(`aria-posinset`),10)-1;if(isNaN(r))return console.warn(`row is invalid. Race condition?`),null;let i=this._rowColumns.get(n);if(!i)return console.warn(`columns is null. Race condition?`),null;let a=t=this._terminal.cols&&(++r,a=0),{row:r,column:a}},a=i(t),o=i(n);if(!(!a||!o)){if(a.row>o.row||a.row===o.row&&a.column>=o.column)throw Error(`invalid range`);this._terminal.select(a.column,a.row,(o.row-a.row)*this._terminal.cols-a.column+o.column)}}_handleResize(e){this._rowElements[this._rowElements.length-1].removeEventListener(`focus`,this._bottomBoundaryFocusListener);for(let e=this._rowContainer.children.length;ee;)this._rowContainer.removeChild(this._rowElements.pop());this._rowElements[this._rowElements.length-1].addEventListener(`focus`,this._bottomBoundaryFocusListener),this._refreshRowsDimensions()}_createAccessibilityTreeNode(){let e=this._coreBrowserService.mainDocument.createElement(`div`);return e.setAttribute(`role`,`listitem`),e.tabIndex=-1,this._refreshRowDimensions(e),e}_refreshRowsDimensions(){if(this._renderService.dimensions.css.cell.height){Object.assign(this._accessibilityContainer.style,{width:`${this._renderService.dimensions.css.canvas.width}px`,fontSize:`${this._terminal.options.fontSize}px`}),this._rowElements.length!==this._terminal.rows&&this._handleResize(this._terminal.rows);for(let e=0;e{St(this._linkCacheDisposables),this._linkCacheDisposables.length=0,this._lastMouseEvent=void 0,this._activeProviderReplies?.clear()})),this._register(this._bufferService.onResize(()=>{this._clearCurrentLink(),this._wasResized=!0})),this._register(P(this._element,`mouseleave`,()=>{this._isMouseOut=!0,this._clearCurrentLink()})),this._register(P(this._element,`mousemove`,this._handleMouseMove.bind(this))),this._register(P(this._element,`mousedown`,this._handleMouseDown.bind(this))),this._register(P(this._element,`mouseup`,this._handleMouseUp.bind(this)))}get currentLink(){return this._currentLink}_handleMouseMove(e){this._lastMouseEvent=e;let t=this._positionFromMouseEvent(e,this._element,this._mouseService);if(!t)return;this._isMouseOut=!1;let n=e.composedPath();for(let e=0;e{e?.forEach(e=>{e.link.dispose&&e.link.dispose()})}),this._activeProviderReplies=new Map,this._activeLine=e.y);let n=!1;for(let[r,i]of this._linkProviderService.linkProviders.entries())t?this._activeProviderReplies?.get(r)&&(n=this._checkLinkProviderResult(r,e,n)):i.provideLinks(e.y,t=>{if(this._isMouseOut)return;let i=t?.map(e=>({link:e}));this._activeProviderReplies?.set(r,i),n=this._checkLinkProviderResult(r,e,n),this._activeProviderReplies?.size===this._linkProviderService.linkProviders.length&&this._removeIntersectingLinks(e.y,this._activeProviderReplies)})}_removeIntersectingLinks(e,t){let n=new Set;for(let r=0;re?this._bufferService.cols:r.link.range.end.x;for(let e=a;e<=o;e++){if(n.has(e)){i.splice(t--,1);break}n.add(e)}}}}_checkLinkProviderResult(e,t,n){if(!this._activeProviderReplies)return n;let r=this._activeProviderReplies.get(e),i=!1;for(let t=0;tthis._linkAtPosition(e.link,t));e&&(n=!0,this._handleNewLink(e))}if(this._activeProviderReplies.size===this._linkProviderService.linkProviders.length&&!n)for(let e=0;ethis._linkAtPosition(e.link,t));if(r){n=!0,this._handleNewLink(r);break}}return n}_handleMouseDown(){this._mouseDownLink=this._currentLink}_handleMouseUp(e){if(!this._currentLink)return;let t=this._positionFromMouseEvent(e,this._element,this._mouseService);t&&this._mouseDownLink&&zs(this._mouseDownLink.link,this._currentLink.link)&&this._linkAtPosition(this._currentLink.link,t)&&this._currentLink.link.activate(e,this._currentLink.link.text)}_clearCurrentLink(e,t){!this._currentLink||!this._lastMouseEvent||(!e||!t||this._currentLink.link.range.start.y>=e&&this._currentLink.link.range.end.y<=t)&&(this._linkLeave(this._element,this._currentLink.link,this._lastMouseEvent),this._currentLink=void 0,St(this._linkCacheDisposables),this._linkCacheDisposables.length=0)}_handleNewLink(e){if(!this._lastMouseEvent)return;let t=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);t&&this._linkAtPosition(e.link,t)&&(this._currentLink=e,this._currentLink.state={decorations:{underline:e.link.decorations===void 0||e.link.decorations.underline,pointerCursor:e.link.decorations===void 0||e.link.decorations.pointerCursor},isHovered:!0},this._linkHover(this._element,e.link,this._lastMouseEvent),e.link.decorations={},Object.defineProperties(e.link.decorations,{pointerCursor:{get:()=>this._currentLink?.state?.decorations.pointerCursor,set:e=>{this._currentLink?.state&&this._currentLink.state.decorations.pointerCursor!==e&&(this._currentLink.state.decorations.pointerCursor=e,this._currentLink.state.isHovered&&this._element.classList.toggle(`xterm-cursor-pointer`,e))}},underline:{get:()=>this._currentLink?.state?.decorations.underline,set:t=>{this._currentLink?.state&&this._currentLink?.state?.decorations.underline!==t&&(this._currentLink.state.decorations.underline=t,this._currentLink.state.isHovered&&this._fireUnderlineEvent(e.link,t))}}}),this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange(e=>{if(!this._currentLink)return;let t=e.start===0?0:e.start+1+this._bufferService.buffer.ydisp,n=this._bufferService.buffer.ydisp+1+e.end;if(this._currentLink.link.range.start.y>=t&&this._currentLink.link.range.end.y<=n&&(this._clearCurrentLink(t,n),this._lastMouseEvent)){let e=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);e&&this._askForLink(e,!1)}})))}_linkHover(e,t,n){this._currentLink?.state&&(this._currentLink.state.isHovered=!0,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!0),this._currentLink.state.decorations.pointerCursor&&e.classList.add(`xterm-cursor-pointer`)),t.hover&&t.hover(n,t.text)}_fireUnderlineEvent(e,t){let n=e.range,r=this._bufferService.buffer.ydisp,i=this._createLinkUnderlineEvent(n.start.x-1,n.start.y-r-1,n.end.x,n.end.y-r-1,void 0);(t?this._onShowLinkUnderline:this._onHideLinkUnderline).fire(i)}_linkLeave(e,t,n){this._currentLink?.state&&(this._currentLink.state.isHovered=!1,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!1),this._currentLink.state.decorations.pointerCursor&&e.classList.remove(`xterm-cursor-pointer`)),t.leave&&t.leave(n,t.text)}_linkAtPosition(e,t){let n=e.range.start.y*this._bufferService.cols+e.range.start.x,r=e.range.end.y*this._bufferService.cols+e.range.end.x,i=t.y*this._bufferService.cols+t.x;return n<=i&&i<=r}_positionFromMouseEvent(e,t,n){let r=n.getCoords(e,t,this._bufferService.cols,this._bufferService.rows);if(r)return{x:r[0],y:r[1]+this._bufferService.buffer.ydisp}}_createLinkUnderlineEvent(e,t,n,r,i){return{x1:e,y1:t,x2:n,y2:r,cols:this._bufferService.cols,fg:i}}};Rs=C([w(1,Ge),w(2,Ke),w(3,D),w(4,Xe)],Rs);function zs(e,t){return e.text===t.text&&e.range.start.x===t.range.start.x&&e.range.start.y===t.range.start.y&&e.range.end.x===t.range.end.x&&e.range.end.y===t.range.end.y}var Bs=class extends Es{constructor(e={}){super(e),this._linkifier=this._register(new Et),this.browser=ia,this._keyDownHandled=!1,this._keyDownSeen=!1,this._keyPressHandled=!1,this._unprocessedDeadKey=!1,this._accessibilityManager=this._register(new Et),this._onCursorMove=this._register(new N),this.onCursorMove=this._onCursorMove.event,this._onKey=this._register(new N),this.onKey=this._onKey.event,this._onRender=this._register(new N),this.onRender=this._onRender.event,this._onSelectionChange=this._register(new N),this.onSelectionChange=this._onSelectionChange.event,this._onTitleChange=this._register(new N),this.onTitleChange=this._onTitleChange.event,this._onBell=this._register(new N),this.onBell=this._onBell.event,this._onFocus=this._register(new N),this._onBlur=this._register(new N),this._onA11yCharEmitter=this._register(new N),this._onA11yTabEmitter=this._register(new N),this._onWillOpen=this._register(new N),this._setup(),this._decorationService=this._instantiationService.createInstance(Ms),this._instantiationService.setService(Be,this._decorationService),this._linkProviderService=this._instantiationService.createInstance($i),this._instantiationService.setService(Xe,this._linkProviderService),this._linkProviderService.registerLinkProvider(this._instantiationService.createInstance(Ve)),this._register(this._inputHandler.onRequestBell(()=>this._onBell.fire())),this._register(this._inputHandler.onRequestRefreshRows(e=>this.refresh(e?.start??0,e?.end??this.rows-1))),this._register(this._inputHandler.onRequestSendFocus(()=>this._reportFocus())),this._register(this._inputHandler.onRequestReset(()=>this.reset())),this._register(this._inputHandler.onRequestWindowsOptionsReport(e=>this._reportWindowsOptions(e))),this._register(this._inputHandler.onColor(e=>this._handleColorEvent(e))),this._register(M.forward(this._inputHandler.onCursorMove,this._onCursorMove)),this._register(M.forward(this._inputHandler.onTitleChange,this._onTitleChange)),this._register(M.forward(this._inputHandler.onA11yChar,this._onA11yCharEmitter)),this._register(M.forward(this._inputHandler.onA11yTab,this._onA11yTabEmitter)),this._register(this._bufferService.onResize(e=>this._afterResize(e.cols,e.rows))),this._register(k(()=>{this._customKeyEventHandler=void 0,this.element?.parentNode?.removeChild(this.element)}))}get linkifier(){return this._linkifier.value}get onFocus(){return this._onFocus.event}get onBlur(){return this._onBlur.event}get onA11yChar(){return this._onA11yCharEmitter.event}get onA11yTab(){return this._onA11yTabEmitter.event}get onWillOpen(){return this._onWillOpen.event}_handleColorEvent(e){if(this._themeService)for(let t of e){let e,n=``;switch(t.index){case 256:e=`foreground`,n=`10`;break;case 257:e=`background`,n=`11`;break;case 258:e=`cursor`,n=`12`;break;default:e=`ansi`,n=`4;`+t.index}switch(t.type){case 0:let r=U.toColorRGB(e===`ansi`?this._themeService.colors.ansi[t.index]:this._themeService.colors[e]);this.coreService.triggerDataEvent(`${L.ESC}]${n};${us(r)}${xi.ST}`);break;case 1:if(e===`ansi`)this._themeService.modifyColors(e=>e.ansi[t.index]=H.toColor(...t.color));else{let n=e;this._themeService.modifyColors(e=>e[n]=H.toColor(...t.color))}break;case 2:this._themeService.restoreColor(t.index);break}}}_setup(){super._setup(),this._customKeyEventHandler=void 0}get buffer(){return this.buffers.active}focus(){this.textarea&&this.textarea.focus({preventScroll:!0})}_handleScreenReaderModeOptionChange(e){e?!this._accessibilityManager.value&&this._renderService&&(this._accessibilityManager.value=this._instantiationService.createInstance(Ls,this)):this._accessibilityManager.clear()}_handleTextAreaFocus(e){this.coreService.decPrivateModes.sendFocus&&this.coreService.triggerDataEvent(L.ESC+`[I`),this.element.classList.add(`focus`),this._showCursor(),this._onFocus.fire()}blur(){return this.textarea?.blur()}_handleTextAreaBlur(){this.textarea.value=``,this.refresh(this.buffer.y,this.buffer.y),this.coreService.decPrivateModes.sendFocus&&this.coreService.triggerDataEvent(L.ESC+`[O`),this.element.classList.remove(`focus`),this._onBlur.fire()}_syncTextArea(){if(!this.textarea||!this.buffer.isCursorInViewport||this._compositionHelper.isComposing||!this._renderService)return;let e=this.buffer.ybase+this.buffer.y,t=this.buffer.lines.get(e);if(!t)return;let n=Math.min(this.buffer.x,this.cols-1),r=this._renderService.dimensions.css.cell.height,i=t.getWidth(n),a=this._renderService.dimensions.css.cell.width*i,o=this.buffer.y*this._renderService.dimensions.css.cell.height,s=n*this._renderService.dimensions.css.cell.width;this.textarea.style.left=s+`px`,this.textarea.style.top=o+`px`,this.textarea.style.width=a+`px`,this.textarea.style.height=r+`px`,this.textarea.style.lineHeight=r+`px`,this.textarea.style.zIndex=`-5`}_initGlobal(){this._bindKeys(),this._register(P(this.element,`copy`,e=>{this.hasSelection()&&me(e,this._selectionService)}));let e=e=>he(e,this.textarea,this.coreService,this.optionsService);this._register(P(this.textarea,`paste`,e)),this._register(P(this.element,`paste`,e)),ca?this._register(P(this.element,`mousedown`,e=>{e.button===2&&ve(e,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})):this._register(P(this.element,`contextmenu`,e=>{ve(e,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})),ga&&this._register(P(this.element,`auxclick`,e=>{e.button===1&&_e(e,this.textarea,this.screenElement)}))}_bindKeys(){this._register(P(this.textarea,`keyup`,e=>this._keyUp(e),!0)),this._register(P(this.textarea,`keydown`,e=>this._keyDown(e),!0)),this._register(P(this.textarea,`keypress`,e=>this._keyPress(e),!0)),this._register(P(this.textarea,`compositionstart`,()=>this._compositionHelper.compositionstart())),this._register(P(this.textarea,`compositionupdate`,e=>this._compositionHelper.compositionupdate(e))),this._register(P(this.textarea,`compositionend`,()=>this._compositionHelper.compositionend())),this._register(P(this.textarea,`input`,e=>this._inputEvent(e),!0)),this._register(this.onRender(()=>this._compositionHelper.updateCompositionElements()))}open(e){if(!e)throw Error(`Terminal requires a parent element.`);if(e.isConnected||this._logService.debug(`Terminal.open was called on an element that was not attached to the DOM`),this.element?.ownerDocument.defaultView&&this._coreBrowserService){this.element.ownerDocument.defaultView!==this._coreBrowserService.window&&(this._coreBrowserService.window=this.element.ownerDocument.defaultView);return}this._document=e.ownerDocument,this.options.documentOverride&&this.options.documentOverride instanceof Document&&(this._document=this.optionsService.rawOptions.documentOverride),this.element=this._document.createElement(`div`),this.element.dir=`ltr`,this.element.classList.add(`terminal`),this.element.classList.add(`xterm`),e.appendChild(this.element);let t=this._document.createDocumentFragment();this._viewportElement=this._document.createElement(`div`),this._viewportElement.classList.add(`xterm-viewport`),t.appendChild(this._viewportElement),this.screenElement=this._document.createElement(`div`),this.screenElement.classList.add(`xterm-screen`),this._register(P(this.screenElement,`mousemove`,e=>this.updateCursorStyle(e))),this._helperContainer=this._document.createElement(`div`),this._helperContainer.classList.add(`xterm-helpers`),this.screenElement.appendChild(this._helperContainer),t.appendChild(this.screenElement);let n=this.textarea=this._document.createElement(`textarea`);this.textarea.classList.add(`xterm-helper-textarea`),this.textarea.setAttribute(`aria-label`,ue.get()),_a||this.textarea.setAttribute(`aria-multiline`,`false`),this.textarea.setAttribute(`autocorrect`,`off`),this.textarea.setAttribute(`autocapitalize`,`off`),this.textarea.setAttribute(`spellcheck`,`false`),this.textarea.tabIndex=0,this._register(this.optionsService.onSpecificOptionChange(`disableStdin`,()=>n.readOnly=this.optionsService.rawOptions.disableStdin)),this.textarea.readOnly=this.optionsService.rawOptions.disableStdin,this._coreBrowserService=this._register(this._instantiationService.createInstance(Zi,this.textarea,e.ownerDocument.defaultView??window,this._document??typeof window<`u`?window.document:null)),this._instantiationService.setService(We,this._coreBrowserService),this._register(P(this.textarea,`focus`,e=>this._handleTextAreaFocus(e))),this._register(P(this.textarea,`blur`,()=>this._handleTextAreaBlur())),this._helperContainer.appendChild(this.textarea),this._charSizeService=this._instantiationService.createInstance(qi,this._document,this._helperContainer),this._instantiationService.setService(Ue,this._charSizeService),this._themeService=this._instantiationService.createInstance(eo),this._instantiationService.setService(Ye,this._themeService),this._characterJoinerService=this._instantiationService.createInstance(Oi),this._instantiationService.setService(Je,this._characterJoinerService),this._renderService=this._register(this._instantiationService.createInstance(Ca,this.rows,this.screenElement)),this._instantiationService.setService(Ke,this._renderService),this._register(this._renderService.onRenderedViewportChange(e=>this._onRender.fire(e))),this.onResize(e=>this._renderService.resize(e.cols,e.rows)),this._compositionView=this._document.createElement(`div`),this._compositionView.classList.add(`composition-view`),this._compositionHelper=this._instantiationService.createInstance(Si,this.textarea,this._compositionView),this._helperContainer.appendChild(this._compositionView),this._mouseService=this._instantiationService.createInstance(na),this._instantiationService.setService(Ge,this._mouseService);let r=this._linkifier.value=this._register(this._instantiationService.createInstance(Rs,this.screenElement));this.element.appendChild(t);try{this._onWillOpen.fire(this.element)}catch{}this._renderService.hasRenderer()||this._renderService.setRenderer(this._createRenderer()),this._register(this.onCursorMove(()=>{this._renderService.handleCursorMove(),this._syncTextArea()})),this._register(this.onResize(()=>this._renderService.handleResize(this.cols,this.rows))),this._register(this.onBlur(()=>this._renderService.handleBlur())),this._register(this.onFocus(()=>this._renderService.handleFocus())),this._viewport=this._register(this._instantiationService.createInstance(pi,this.element,this.screenElement)),this._register(this._viewport.onRequestScrollLines(e=>{super.scrollLines(e,!1),this.refresh(0,this.rows-1)})),this._selectionService=this._register(this._instantiationService.createInstance(Ga,this.element,this.screenElement,r)),this._instantiationService.setService(qe,this._selectionService),this._register(this._selectionService.onRequestScrollLines(e=>this.scrollLines(e.amount,e.suppressScrollEvent))),this._register(this._selectionService.onSelectionChange(()=>this._onSelectionChange.fire())),this._register(this._selectionService.onRequestRedraw(e=>this._renderService.handleSelectionChanged(e.start,e.end,e.columnSelectMode))),this._register(this._selectionService.onLinuxMouseSelection(e=>{this.textarea.value=e,this.textarea.focus(),this.textarea.select()})),this._register(M.any(this._onScroll.event,this._inputHandler.onScroll)(()=>{this._selectionService.refresh(),this._viewport?.queueSync()})),this._register(this._instantiationService.createInstance(mi,this.screenElement)),this._register(P(this.element,`mousedown`,e=>this._selectionService.handleMouseDown(e))),this.coreMouseService.areMouseEventsActive?(this._selectionService.disable(),this.element.classList.add(`enable-mouse-events`)):this._selectionService.enable(),this.options.screenReaderMode&&(this._accessibilityManager.value=this._instantiationService.createInstance(Ls,this)),this._register(this.optionsService.onSpecificOptionChange(`screenReaderMode`,e=>this._handleScreenReaderModeOptionChange(e))),this.options.overviewRuler.width&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(yi,this._viewportElement,this.screenElement))),this.optionsService.onSpecificOptionChange(`overviewRuler`,e=>{!this._overviewRulerRenderer&&e&&this._viewportElement&&this.screenElement&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(yi,this._viewportElement,this.screenElement)))}),this._charSizeService.measure(),this.refresh(0,this.rows-1),this._initGlobal(),this.bindMouse()}_createRenderer(){return this._instantiationService.createInstance(Ki,this,this._document,this.element,this.screenElement,this._viewportElement,this._helperContainer,this.linkifier)}bindMouse(){let e=this,t=this.element;function n(t){let n=e._mouseService.getMouseReportCoords(t,e.screenElement);if(!n)return!1;let r,i;switch(t.overrideType||t.type){case`mousemove`:i=32,t.buttons===void 0?(r=3,t.button!==void 0&&(r=t.button<3?t.button:3)):r=t.buttons&1?0:t.buttons&4?1:t.buttons&2?2:3;break;case`mouseup`:i=0,r=t.button<3?t.button:3;break;case`mousedown`:i=1,r=t.button<3?t.button:3;break;case`wheel`:if(e._customWheelEventHandler&&e._customWheelEventHandler(t)===!1)return!1;let n=t.deltaY;if(n===0||e.coreMouseService.consumeWheelEvent(t,e._renderService?.dimensions?.device?.cell?.height,e._coreBrowserService?.dpr)===0)return!1;i=n<0?0:1,r=4;break;default:return!1}return i===void 0||r===void 0||r>4?!1:e.coreMouseService.triggerMouseEvent({col:n.col,row:n.row,x:n.x,y:n.y,button:r,action:i,ctrl:t.ctrlKey,alt:t.altKey,shift:t.shiftKey})}let r={mouseup:null,wheel:null,mousedrag:null,mousemove:null},i={mouseup:e=>(n(e),e.buttons||(this._document.removeEventListener(`mouseup`,r.mouseup),r.mousedrag&&this._document.removeEventListener(`mousemove`,r.mousedrag)),this.cancel(e)),wheel:e=>(n(e),this.cancel(e,!0)),mousedrag:e=>{e.buttons&&n(e)},mousemove:e=>{e.buttons||n(e)}};this._register(this.coreMouseService.onProtocolChange(e=>{e?(this.optionsService.rawOptions.logLevel===`debug`&&this._logService.debug(`Binding to mouse events:`,this.coreMouseService.explainEvents(e)),this.element.classList.add(`enable-mouse-events`),this._selectionService.disable()):(this._logService.debug(`Unbinding from mouse events.`),this.element.classList.remove(`enable-mouse-events`),this._selectionService.enable()),e&8?r.mousemove||=(t.addEventListener(`mousemove`,i.mousemove),i.mousemove):(t.removeEventListener(`mousemove`,r.mousemove),r.mousemove=null),e&16?r.wheel||=(t.addEventListener(`wheel`,i.wheel,{passive:!1}),i.wheel):(t.removeEventListener(`wheel`,r.wheel),r.wheel=null),e&2?r.mouseup||=i.mouseup:(this._document.removeEventListener(`mouseup`,r.mouseup),r.mouseup=null),e&4?r.mousedrag||=i.mousedrag:(this._document.removeEventListener(`mousemove`,r.mousedrag),r.mousedrag=null)})),this.coreMouseService.activeProtocol=this.coreMouseService.activeProtocol,this._register(P(t,`mousedown`,e=>{if(e.preventDefault(),this.focus(),!(!this.coreMouseService.areMouseEventsActive||this._selectionService.shouldForceSelection(e)))return n(e),r.mouseup&&this._document.addEventListener(`mouseup`,r.mouseup),r.mousedrag&&this._document.addEventListener(`mousemove`,r.mousedrag),this.cancel(e)})),this._register(P(t,`wheel`,t=>{if(!r.wheel){if(this._customWheelEventHandler&&this._customWheelEventHandler(t)===!1)return!1;if(!this.buffer.hasScrollback){if(t.deltaY===0)return!1;if(e.coreMouseService.consumeWheelEvent(t,e._renderService?.dimensions?.device?.cell?.height,e._coreBrowserService?.dpr)===0)return this.cancel(t,!0);let n=L.ESC+(this.coreService.decPrivateModes.applicationCursorKeys?`O`:`[`)+(t.deltaY<0?`A`:`B`);return this.coreService.triggerDataEvent(n,!0),this.cancel(t,!0)}}},{passive:!1}))}refresh(e,t){this._renderService?.refreshRows(e,t)}updateCursorStyle(e){this._selectionService?.shouldColumnSelect(e)?this.element.classList.add(`column-select`):this.element.classList.remove(`column-select`)}_showCursor(){this.coreService.isCursorInitialized||(this.coreService.isCursorInitialized=!0,this.refresh(this.buffer.y,this.buffer.y))}scrollLines(e,t){this._viewport?this._viewport.scrollLines(e):super.scrollLines(e,t),this.refresh(0,this.rows-1)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(e){e&&this._viewport?this._viewport.scrollToLine(this.buffer.ybase,!0):this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){let t=e-this._bufferService.buffer.ydisp;t!==0&&this.scrollLines(t)}paste(e){ge(e,this.textarea,this.coreService,this.optionsService)}attachCustomKeyEventHandler(e){this._customKeyEventHandler=e}attachCustomWheelEventHandler(e){this._customWheelEventHandler=e}registerLinkProvider(e){return this._linkProviderService.registerLinkProvider(e)}registerCharacterJoiner(e){if(!this._characterJoinerService)throw Error(`Terminal must be opened first`);let t=this._characterJoinerService.register(e);return this.refresh(0,this.rows-1),t}deregisterCharacterJoiner(e){if(!this._characterJoinerService)throw Error(`Terminal must be opened first`);this._characterJoinerService.deregister(e)&&this.refresh(0,this.rows-1)}get markers(){return this.buffer.markers}registerMarker(e){return this.buffer.addMarker(this.buffer.ybase+this.buffer.y+e)}registerDecoration(e){return this._decorationService.registerDecoration(e)}hasSelection(){return this._selectionService?this._selectionService.hasSelection:!1}select(e,t,n){this._selectionService.setSelection(e,t,n)}getSelection(){return this._selectionService?this._selectionService.selectionText:``}getSelectionPosition(){if(!(!this._selectionService||!this._selectionService.hasSelection))return{start:{x:this._selectionService.selectionStart[0],y:this._selectionService.selectionStart[1]},end:{x:this._selectionService.selectionEnd[0],y:this._selectionService.selectionEnd[1]}}}clearSelection(){this._selectionService?.clearSelection()}selectAll(){this._selectionService?.selectAll()}selectLines(e,t){this._selectionService?.selectLines(e,t)}_keyDown(e){if(this._keyDownHandled=!1,this._keyDownSeen=!0,this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)return!1;let t=this.browser.isMac&&this.options.macOptionIsMeta&&e.altKey;if(!t&&!this._compositionHelper.keydown(e))return this.options.scrollOnUserInput&&this.buffer.ybase!==this.buffer.ydisp&&this.scrollToBottom(!0),!1;!t&&(e.key===`Dead`||e.key===`AltGraph`)&&(this._unprocessedDeadKey=!0);let n=Os(e,this.coreService.decPrivateModes.applicationCursorKeys,this.browser.isMac,this.options.macOptionIsMeta);if(this.updateCursorStyle(e),n.type===3||n.type===2){let t=this.rows-1;return this.scrollLines(n.type===2?-t:t),this.cancel(e,!0)}if(n.type===1&&this.selectAll(),this._isThirdLevelShift(this.browser,e)||(n.cancel&&this.cancel(e,!0),!n.key)||e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.key.length===1&&e.key.charCodeAt(0)>=65&&e.key.charCodeAt(0)<=90)return!0;if(this._unprocessedDeadKey)return this._unprocessedDeadKey=!1,!0;if((n.key===L.ETX||n.key===L.CR)&&(this.textarea.value=``),this._onKey.fire({key:n.key,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(n.key,!0),!this.optionsService.rawOptions.screenReaderMode||e.altKey||e.ctrlKey)return this.cancel(e,!0);this._keyDownHandled=!0}_isThirdLevelShift(e,t){let n=e.isMac&&!this.options.macOptionIsMeta&&t.altKey&&!t.ctrlKey&&!t.metaKey||e.isWindows&&t.altKey&&t.ctrlKey&&!t.metaKey||e.isWindows&&t.getModifierState(`AltGraph`);return t.type===`keypress`?n:n&&(!t.keyCode||t.keyCode>47)}_keyUp(e){this._keyDownSeen=!1,!(this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)&&(Vs(e)||this.focus(),this.updateCursorStyle(e),this._keyPressHandled=!1)}_keyPress(e){let t;if(this._keyPressHandled=!1,this._keyDownHandled||this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)return!1;if(this.cancel(e),e.charCode)t=e.charCode;else if(e.which===null||e.which===void 0)t=e.keyCode;else if(e.which!==0&&e.charCode!==0)t=e.which;else return!1;return!t||(e.altKey||e.ctrlKey||e.metaKey)&&!this._isThirdLevelShift(this.browser,e)?!1:(t=String.fromCharCode(t),this._onKey.fire({key:t,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(t,!0),this._keyPressHandled=!0,this._unprocessedDeadKey=!1,!0)}_inputEvent(e){if(e.data&&e.inputType===`insertText`&&(!e.composed||!this._keyDownSeen)&&!this.optionsService.rawOptions.screenReaderMode){if(this._keyPressHandled)return!1;this._unprocessedDeadKey=!1;let t=e.data;return this.coreService.triggerDataEvent(t,!0),this.cancel(e),!0}return!1}resize(e,t){if(e===this.cols&&t===this.rows){this._charSizeService&&!this._charSizeService.hasValidSize&&this._charSizeService.measure();return}super.resize(e,t)}_afterResize(e,t){this._charSizeService?.measure()}clear(){if(!(this.buffer.ybase===0&&this.buffer.y===0)){this.buffer.clearAllMarkers(),this.buffer.lines.set(0,this.buffer.lines.get(this.buffer.ybase+this.buffer.y)),this.buffer.lines.length=1,this.buffer.ydisp=0,this.buffer.ybase=0,this.buffer.y=0;for(let e=1;e=0;e--)this._addons[e].instance.dispose()}loadAddon(e,t){let n={instance:t,dispose:t.dispose,isDisposed:!1};this._addons.push(n),t.dispose=()=>this._wrappedAddonDispose(n),t.activate(e)}_wrappedAddonDispose(e){if(e.isDisposed)return;let t=-1;for(let n=0;n=this._line.length))return t?(this._line.loadCell(e,t),t):this._line.loadCell(e,new De)}translateToString(e,t,n){return this._line.translateToString(e,t,n)}},Ws=class{constructor(e,t){this._buffer=e,this.type=t}init(e){return this._buffer=e,this}get cursorY(){return this._buffer.y}get cursorX(){return this._buffer.x}get viewportY(){return this._buffer.ydisp}get baseY(){return this._buffer.ybase}get length(){return this._buffer.lines.length}getLine(e){let t=this._buffer.lines.get(e);if(t)return new Us(t)}getNullCell(){return new De}},Gs=class extends A{constructor(e){super(),this._core=e,this._onBufferChange=this._register(new N),this.onBufferChange=this._onBufferChange.event,this._normal=new Ws(this._core.buffers.normal,`normal`),this._alternate=new Ws(this._core.buffers.alt,`alternate`),this._core.buffers.onBufferActivate(()=>this._onBufferChange.fire(this.active))}get active(){if(this._core.buffers.active===this._core.buffers.normal)return this.normal;if(this._core.buffers.active===this._core.buffers.alt)return this.alternate;throw Error(`Active buffer is neither normal nor alternate`)}get normal(){return this._normal.init(this._core.buffers.normal)}get alternate(){return this._alternate.init(this._core.buffers.alt)}},Ks=class{constructor(e){this._core=e}registerCsiHandler(e,t){return this._core.registerCsiHandler(e,e=>t(e.toArray()))}addCsiHandler(e,t){return this.registerCsiHandler(e,t)}registerDcsHandler(e,t){return this._core.registerDcsHandler(e,(e,n)=>t(e,n.toArray()))}addDcsHandler(e,t){return this.registerDcsHandler(e,t)}registerEscHandler(e,t){return this._core.registerEscHandler(e,t)}addEscHandler(e,t){return this.registerEscHandler(e,t)}registerOscHandler(e,t){return this._core.registerOscHandler(e,t)}addOscHandler(e,t){return this.registerOscHandler(e,t)}},qs=class{constructor(e){this._core=e}register(e){this._core.unicodeService.register(e)}get versions(){return this._core.unicodeService.versions}get activeVersion(){return this._core.unicodeService.activeVersion}set activeVersion(e){this._core.unicodeService.activeVersion=e}},Js=[`cols`,`rows`],Ys=0,Xs=class extends A{constructor(e){super(),this._core=this._register(new Bs(e)),this._addonManager=this._register(new Hs),this._publicOptions={...this._core.options};let t=e=>this._core.options[e],n=(e,t)=>{this._checkReadonlyOptions(e),this._core.options[e]=t};for(let e in this._core.options){let r={get:t.bind(this,e),set:n.bind(this,e)};Object.defineProperty(this._publicOptions,e,r)}}_checkReadonlyOptions(e){if(Js.includes(e))throw Error(`Option "${e}" can only be set in the constructor`)}_checkProposedApi(){if(!this._core.optionsService.rawOptions.allowProposedApi)throw Error(`You must set the allowProposedApi option to true to use proposed API`)}get onBell(){return this._core.onBell}get onBinary(){return this._core.onBinary}get onCursorMove(){return this._core.onCursorMove}get onData(){return this._core.onData}get onKey(){return this._core.onKey}get onLineFeed(){return this._core.onLineFeed}get onRender(){return this._core.onRender}get onResize(){return this._core.onResize}get onScroll(){return this._core.onScroll}get onSelectionChange(){return this._core.onSelectionChange}get onTitleChange(){return this._core.onTitleChange}get onWriteParsed(){return this._core.onWriteParsed}get element(){return this._core.element}get parser(){return this._parser||=new Ks(this._core),this._parser}get unicode(){return this._checkProposedApi(),new qs(this._core)}get textarea(){return this._core.textarea}get rows(){return this._core.rows}get cols(){return this._core.cols}get buffer(){return this._buffer||=this._register(new Gs(this._core)),this._buffer}get markers(){return this._checkProposedApi(),this._core.markers}get modes(){let e=this._core.coreService.decPrivateModes,t=`none`;switch(this._core.coreMouseService.activeProtocol){case`X10`:t=`x10`;break;case`VT200`:t=`vt200`;break;case`DRAG`:t=`drag`;break;case`ANY`:t=`any`;break}return{applicationCursorKeysMode:e.applicationCursorKeys,applicationKeypadMode:e.applicationKeypad,bracketedPasteMode:e.bracketedPasteMode,insertMode:this._core.coreService.modes.insertMode,mouseTrackingMode:t,originMode:e.origin,reverseWraparoundMode:e.reverseWraparound,sendFocusMode:e.sendFocus,synchronizedOutputMode:e.synchronizedOutput,wraparoundMode:e.wraparound}}get options(){return this._publicOptions}set options(e){for(let t in e)this._publicOptions[t]=e[t]}blur(){this._core.blur()}focus(){this._core.focus()}input(e,t=!0){this._core.input(e,t)}resize(e,t){this._verifyIntegers(e,t),this._core.resize(e,t)}open(e){this._core.open(e)}attachCustomKeyEventHandler(e){this._core.attachCustomKeyEventHandler(e)}attachCustomWheelEventHandler(e){this._core.attachCustomWheelEventHandler(e)}registerLinkProvider(e){return this._core.registerLinkProvider(e)}registerCharacterJoiner(e){return this._checkProposedApi(),this._core.registerCharacterJoiner(e)}deregisterCharacterJoiner(e){this._checkProposedApi(),this._core.deregisterCharacterJoiner(e)}registerMarker(e=0){return this._verifyIntegers(e),this._core.registerMarker(e)}registerDecoration(e){return this._checkProposedApi(),this._verifyPositiveIntegers(e.x??0,e.width??0,e.height??0),this._core.registerDecoration(e)}hasSelection(){return this._core.hasSelection()}select(e,t,n){this._verifyIntegers(e,t,n),this._core.select(e,t,n)}getSelection(){return this._core.getSelection()}getSelectionPosition(){return this._core.getSelectionPosition()}clearSelection(){this._core.clearSelection()}selectAll(){this._core.selectAll()}selectLines(e,t){this._verifyIntegers(e,t),this._core.selectLines(e,t)}dispose(){super.dispose()}scrollLines(e){this._verifyIntegers(e),this._core.scrollLines(e)}scrollPages(e){this._verifyIntegers(e),this._core.scrollPages(e)}scrollToTop(){this._core.scrollToTop()}scrollToBottom(){this._core.scrollToBottom()}scrollToLine(e){this._verifyIntegers(e),this._core.scrollToLine(e)}clear(){this._core.clear()}write(e,t){this._core.write(e,t)}writeln(e,t){this._core.write(e),this._core.write(`\r -`,t)}paste(e){this._core.paste(e)}refresh(e,t){this._verifyIntegers(e,t),this._core.refresh(e,t)}reset(){this._core.reset()}clearTextureAtlas(){this._core.clearTextureAtlas()}loadAddon(e){this._addonManager.loadAddon(this,e)}static get strings(){return{get promptLabel(){return ue.get()},set promptLabel(e){ue.set(e)},get tooMuchOutput(){return fe.get()},set tooMuchOutput(e){fe.set(e)}}}_verifyIntegers(...e){for(Ys of e)if(Ys===1/0||isNaN(Ys)||Ys%1!=0)throw Error(`This API only accepts integers`)}_verifyPositiveIntegers(...e){for(Ys of e)if(Ys&&(Ys===1/0||isNaN(Ys)||Ys%1!=0||Ys<0))throw Error(`This API only accepts positive integers`)}},Zs=2,Qs=1,$s=class{activate(e){this._terminal=e}dispose(){}fit(){let e=this.proposeDimensions();if(!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows))return;let t=this._terminal._core;(this._terminal.rows!==e.rows||this._terminal.cols!==e.cols)&&(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}proposeDimensions(){if(!this._terminal||!this._terminal.element||!this._terminal.element.parentElement)return;let e=this._terminal._core._renderService.dimensions;if(e.css.cell.width===0||e.css.cell.height===0)return;let t=this._terminal.options.scrollback===0?0:this._terminal.options.overviewRuler?.width||14,n=window.getComputedStyle(this._terminal.element.parentElement),r=parseInt(n.getPropertyValue(`height`)),i=Math.max(0,parseInt(n.getPropertyValue(`width`))),a=window.getComputedStyle(this._terminal.element),o={top:parseInt(a.getPropertyValue(`padding-top`)),bottom:parseInt(a.getPropertyValue(`padding-bottom`)),right:parseInt(a.getPropertyValue(`padding-right`)),left:parseInt(a.getPropertyValue(`padding-left`))},s=o.top+o.bottom,c=o.right+o.left,l=r-s,u=i-c-t;return{cols:Math.max(Zs,Math.floor(u/e.css.cell.width)),rows:Math.max(Qs,Math.floor(l/e.css.cell.height))}}};function ec(){return{fontSize:typeof window<`u`&&typeof window.matchMedia==`function`&&window.matchMedia(`(pointer: coarse)`).matches?13:12,fontFamily:getComputedStyle(document.body).fontFamily}}var tc=250;function nc(e){return e.type===`keydown`&&e.ctrlKey&&!e.altKey&&!e.metaKey&&(e.code===`KeyL`||e.key===`l`||e.key===`L`)}var rc=class{seen=null;noteKey(e,t){nc(e)&&(this.seen={at:t,trusted:e.isTrusted,repeat:e.repeat,code:e.code})}report(e,t){if(!e.includes(`\f`))return null;let n=this.seen;return this.seen=null,!n||t-n.at>tc||t{for(let t of e){if(i.current.has(t))continue;let e=a.current.get(t);if(!e||e.clientHeight===0||e.clientWidth===0)continue;let n=new Xs({...ec(),theme:{background:`#0b0b0d`,foreground:`#e6e6ec`},cursorBlink:!0}),c=new $s;n.loadAddon(c);let l=new rc;n.attachCustomKeyEventHandler(e=>(l.noteKey(e,performance.now()),!0)),n.onData(e=>{let n=r.current;if(!n)return;n.send(JSON.stringify({type:`input`,pane:t,data:e}));let i=l.report(e,performance.now());i&&n.send(JSON.stringify({type:`clear_key_report`,pane:t,...i}))}),n.onTitleChange(e=>{let n=e.replace(/\s+/g,` `).trim();n&&s(e=>({...e,[t]:n}))}),n.open(e),i.current.set(t,{term:n,fit:c});let u=o.current.get(t);if(u){for(let e of u)n.write(e);o.current.delete(t),n.write(``,()=>n.scrollToBottom())}}for(let[t,n]of i.current)e.includes(t)||(n.term.dispose(),i.current.delete(t))},[e,t,n])}var ac=60;function oc({panes:e,size:t,zoomed:n,socketRef:r,viewsRef:i,bodyRefs:a,sentSizesRef:o,ownsSize:s,layoutPending:c}){let u=(0,l.useRef)(null),d=(0,l.useCallback)(()=>{for(let[e,t]of i.current){let n=a.current.get(e);if(!n||n.clientHeight===0||n.clientWidth===0)continue;let{rows:i,cols:s}=t.term,c=o.current.get(e);c&&c.rows===i&&c.cols===s||(o.current.set(e,{rows:i,cols:s}),r.current?.send(JSON.stringify({type:`resize`,pane:e,rows:i,cols:s})))}},[r,i,a,o]);(0,l.useEffect)(()=>{for(let[e,t]of i.current){let n=a.current.get(e);if(!(!n||n.clientHeight===0||n.clientWidth===0)){if(!s||c){let n=o.current.get(e);n&&t.term.resize(n.cols,n.rows);continue}t.fit.fit()}}if(!(!s||c))return u.current&&clearTimeout(u.current),u.current=setTimeout(d,ac),()=>{u.current&&clearTimeout(u.current)}},[e,n,t,d,i,a,o,s,c])}function sc(e){let[t,n]=(0,l.useState)({});return{recovery:t,setRecovery:n,cancelRecovery:(0,l.useCallback)(t=>b(e.current,t),[e])}}var cc={esc:`\x1B`,tab:` `,"shift-tab":`\x1B[Z`,"ctrl-c":``,"ctrl-d":``,"ctrl-z":``,"ctrl-l":`\f`,"ctrl-r":``,up:`\x1B[A`,down:`\x1B[B`,right:`\x1B[C`,left:`\x1B[D`},lc={up:`\x1BOA`,down:`\x1BOB`,right:`\x1BOC`,left:`\x1BOD`};function uc(e,t=!1){if(t){let t=lc[e];if(t)return t}return cc[e]}var dc=[{key:`esc`,label:`Esc`,aria:`Escape`},{key:`tab`,label:`Tab`,aria:`Tab`},{key:`shift-tab`,label:`⇧Tab`,aria:`Shift Tab`},{key:`ctrl-c`,label:`^C`,aria:`Control C`},{key:`ctrl-d`,label:`^D`,aria:`Control D`},{key:`ctrl-z`,label:`^Z`,aria:`Control Z`},{key:`ctrl-l`,label:`^L`,aria:`Control L`},{key:`ctrl-r`,label:`^R`,aria:`Control R`},{key:`left`,label:`←`,aria:`Left arrow`},{key:`down`,label:`↓`,aria:`Down arrow`},{key:`up`,label:`↑`,aria:`Up arrow`},{key:`right`,label:`→`,aria:`Right arrow`}];function fc(e,t){return e!==null&&t.includes(e)?e:null}function pc(e,t){return e!==null&&fc(e,t)===null}function mc(e,t){return e===t?null:t}function hc({socketRef:e,viewsRef:t,zoomed:n,zoomAskedRef:r,active:i}){let a=t=>e.current?.send(JSON.stringify(t));return{create:()=>a({type:`create`,rows:24,cols:80}),toggleZoom:e=>{let t=mc(r.current===void 0?n:r.current,e);r.current=t,a({type:`zoom`,pane:t})},claimSize:()=>a({type:`claim_size`}),closePane:e=>a({type:`close`,pane:e}),reorder:e=>a({type:`reorder`,order:e}),sendKey:e=>{if(i===null)return;let n=t.current.get(i)?.term.modes.applicationCursorKeysMode??!1;a({type:`input`,pane:i,data:uc(e,n)})}}}function gc({repo:e,panes:t,active:n,setActive:r,zoomed:i,zoom:a,viewsRef:o,lastActiveByRepoRef:s}){(0,l.useEffect)(()=>{if(!pc(i,t)&&n===null&&t.length>0){let n=s.current.get(e);r(n!==void 0&&t.includes(n)?n:t[t.length-1])}},[n,t,e,i,r,s]),(0,l.useEffect)(()=>{a!==null&&a!==n&&(r(a),s.current.set(e,a))},[a,n,e,r,s]),(0,l.useEffect)(()=>{n!==null&&o.current.get(n)?.term.focus()},[n,o])}function _c({pending:e,size:t,socketRef:n,slotRefs:r,panesExist:i,onAnswered:a}){(0,l.useEffect)(()=>{if(e===null)return;let t=n.current;if(!t||t.readyState!==WebSocket.OPEN)return;let o=[];for(let n=0;n{let t=new Xs(ec()),n=new $s;t.loadAddon(n),t.open(e);let r=n.proposeDimensions();if(t.dispose(),!r)throw Error(`could not measure the cell`);return{rows:r.rows,cols:r.cols}})}catch{s=[]}t.send(JSON.stringify({type:`start`,sizes:s})),a()},[e,t,n,r,i,a])}var $=e();function vc({report:e,pane:t,onCancel:r}){let i=t===void 0?y(e):`pane ${t} · ${y(e)}`;return(0,$.jsxs)(`span`,{className:`flex min-w-0 shrink items-center gap-1 rounded-sm bg-ink-800 px-1 text-accent`,title:e.detail??i,children:[(0,$.jsx)(`span`,{className:`truncate`,children:i}),e.detail&&(0,$.jsx)(`span`,{className:`hidden min-w-0 truncate text-ink-400 md:inline`,children:e.detail}),(0,$.jsx)(`button`,{onMouseDown:e=>e.stopPropagation(),onClick:r,title:`Stop waiting and release this pane's slot`,"aria-label":`cancel recovery${t===void 0?``:` for pane ${t}`}`,className:`flex h-5 w-5 shrink-0 items-center justify-center rounded-sm text-ink-400 hover:text-removed`,children:(0,$.jsx)(n,{className:`h-3 w-3`})})]})}function yc({pane:e,index:t,label:r,cellStyle:a,isActive:o,isZoomed:s,showZoom:c,isDragged:l,isDropTarget:u,reorderable:d,recovery:f,onCancelRecovery:p,onFocus:h,onToggleZoom:g,onClose:_,onPaneDragStart:v,onPaneDragMove:ee,onPaneDragEnd:y,onPaneDragCancel:te,bodyRef:b}){return(0,$.jsxs)(`div`,{"data-pane-id":e,onMouseDown:h,style:a,className:`min-h-0 min-w-0 flex-col overflow-hidden rounded-sm border ${u?`border-accent ring-1 ring-accent`:o?`border-accent`:`border-ink-700`} ${l?`opacity-60`:``}`,children:[(0,$.jsxs)(`div`,{onPointerDown:v,onPointerMove:ee,onPointerUp:y,onPointerCancel:te,className:`flex shrink-0 items-center gap-1 select-none bg-ink-900 px-2 py-0.5 text-xs ${d?l?`cursor-grabbing touch-none`:`cursor-grab touch-none`:``}`,children:[(0,$.jsx)(`span`,{title:r,className:`min-w-0 flex-1 truncate ${o?`text-ink-50`:`text-ink-400`}`,children:m(r,20)}),f&&(0,$.jsx)(vc,{report:f,onCancel:p}),c&&(0,$.jsx)(`button`,{onMouseDown:e=>e.stopPropagation(),onClick:g,"aria-pressed":s,title:s?`Restore the grid`:`Zoom this terminal`,"aria-label":s?`Restore the grid`:`Zoom this terminal`,className:`flex h-8 w-8 shrink-0 items-center justify-center rounded-sm text-ink-400 hover:text-accent md:h-6 md:w-6`,children:(0,$.jsx)(i,{maximized:s})}),(0,$.jsx)(`button`,{onMouseDown:e=>e.stopPropagation(),onClick:_,title:`Close terminal`,"aria-label":`close terminal ${t+1}`,className:`flex h-8 w-8 shrink-0 items-center justify-center rounded-sm text-ink-400 hover:text-removed md:h-6 md:w-6`,children:(0,$.jsx)(n,{})})]}),(0,$.jsx)(`div`,{ref:b,className:`min-h-0 flex-1`})]})}function bc({count:e,cells:t,slotRefs:n}){return Array.from({length:e},(e,r)=>{let i=t[r];return(0,$.jsx)(yc,{pane:-1-r,index:r,label:`starting…`,cellStyle:{display:`flex`,gridColumn:`${i.colStart} / span ${i.colSpan}`,gridRow:`${i.row}`},isActive:!1,isZoomed:!1,showZoom:!1,isDragged:!1,isDropTarget:!1,reorderable:!1,onCancelRecovery:()=>{},onFocus:()=>{},onToggleZoom:()=>{},onClose:()=>{},onPaneDragStart:()=>{},onPaneDragMove:()=>{},onPaneDragEnd:()=>{},onPaneDragCancel:()=>{},bodyRef:e=>{e?n.current.set(r,e):n.current.delete(r)}},`slot-${r}`)})}function xc({onKey:e}){return(0,$.jsx)(`div`,{className:`flex shrink-0 items-stretch gap-1 overflow-x-auto border-t border-ink-700 bg-ink-900 px-1 py-1 md:hidden`,children:dc.map(({key:t,label:n,aria:r})=>(0,$.jsx)(`button`,{onPointerDown:e=>e.preventDefault(),onClick:()=>e(t),"aria-label":r,className:`flex min-h-9 min-w-9 shrink-0 items-center justify-center rounded-sm border border-ink-700 bg-ink-850 px-2 text-xs text-ink-200 active:bg-ink-700 active:text-accent`,children:n},t))})}function Sc({showDivider:e,draggingUpper:t,onUpperDragStart:n,onUpperDragMove:r,onUpperDragEnd:i,onUpperDragCancel:a}){return e?(0,$.jsx)(`div`,{role:`separator`,"aria-orientation":`horizontal`,"aria-label":`Resize the terminal panel (double-click to reset)`,title:`Drag to resize · double-click to reset`,onPointerDown:n,onPointerMove:r,onPointerUp:i,onPointerCancel:a,onLostPointerCapture:i,className:`absolute -top-px left-0 z-10 hidden h-1.5 w-full cursor-row-resize touch-none md:block ${t?`bg-accent`:`hover:bg-accent`}`}):null}function Cc({ownsSize:e,maximized:t,recovery:n,panes:r,onCancelRecovery:a,onClaimSize:s,onCreate:l,onToggleMaximized:u}){let d=`flex shrink-0 items-center rounded-sm px-1.5 py-0.5 text-ink-400 hover:text-accent`;return(0,$.jsxs)(`div`,{className:`flex shrink-0 items-center gap-2 bg-ink-900 px-2 py-1 text-xs`,children:[v(n,r).map(e=>(0,$.jsx)(vc,{pane:e,report:n[e],onCancel:()=>a(e)},e)),!e&&(0,$.jsx)(`button`,{onClick:s,title:`These panes are sized for another client. Resize them to fit this screen.`,"aria-label":`Fit the panes to this screen`,className:`ml-auto ${d}`,children:(0,$.jsx)(c,{})}),(0,$.jsx)(`button`,{onClick:l,title:`New terminal`,"aria-label":`New terminal`,className:`${d} ${e?`ml-auto`:``}`,children:(0,$.jsx)(o,{})}),(0,$.jsx)(`button`,{onClick:u,"aria-pressed":t,title:t?`Restore panel height`:`Maximize the panel`,"aria-label":t?`Restore panel height`:`Maximize the panel`,className:`hidden md:flex ${d}`,children:(0,$.jsx)(i,{maximized:t})})]})}function wc({repo:e,maximized:t,onToggleMaximized:n,className:r=``,sectionRef:i,...a}){let o=(0,l.useRef)(null),s=(0,l.useRef)(null),c=(0,l.useRef)(new Map),u=(0,l.useRef)(new Map),d=(0,l.useRef)(new Map),p=(0,l.useRef)(new Map),m=(0,l.useRef)(new Map),g=(0,l.useRef)(void 0),_=(0,l.useRef)(new Map),[v,ee]=(0,l.useState)(null),[y,te]=(0,l.useState)(0),[b,ne]=(0,l.useState)([]),[re,ie]=(0,l.useState)(null),[ae,x]=(0,l.useState)(null),[S,se]=(0,l.useState)({w:0,h:0}),[ce,C]=(0,l.useState)({}),[w,le]=(0,l.useState)(!0),{recovery:ue,setRecovery:de,cancelRecovery:fe}=sc(s),T=fc(ae,b);oe({repo:e,socketRef:s,viewsRef:c,pendingRef:p,sentSizesRef:d,lastActiveByRepoRef:m,zoomAskedRef:g,setPending:ee,setReplayLeft:te,setPanes:ne,setActive:ie,setZoomed:x,setTitles:C,setOwnsSize:le,setRecovery:de}),ic({panes:b,size:S,zoomed:T,socketRef:s,viewsRef:c,bodyRefs:u,pendingRef:p,setTitles:C});let pe=(0,l.useCallback)(()=>ee(null),[]);_c({pending:v,size:S,socketRef:s,slotRefs:_,panesExist:b.length>0,onAnswered:pe}),oc({panes:b,size:S,zoomed:T,socketRef:s,viewsRef:c,bodyRefs:u,sentSizesRef:d,ownsSize:w,layoutPending:pc(ae,b)}),(0,l.useEffect)(()=>{let e=o.current;if(!e)return;let t=new ResizeObserver(()=>{let t=e.clientWidth,n=e.clientHeight;se(e=>e.w===t&&e.h===n?e:{w:t,h:n})});return t.observe(e),()=>t.disconnect()},[]),gc({repo:e,panes:b,active:re,setActive:ie,zoomed:ae,zoom:T,viewsRef:c,lastActiveByRepoRef:m});let me=t=>{ie(t),m.current.set(e,t)},{create:he,toggleZoom:ge,claimSize:_e,closePane:ve,reorder:ye,sendKey:be}=hc({socketRef:s,viewsRef:c,zoomed:T,zoomAskedRef:g,active:re}),{draggingPane:xe,dragOverPane:Se,reorderable:Ce,endPaneDrag:we,onPaneDragStart:Te,onPaneDragMove:Ee,onPaneDragEnd:De}=h({panes:b,zoomed:T,onFocus:me,onReorder:ye}),Oe=f(b.length+y>0?b.length+y:v??0,S.w>=S.h);return(0,$.jsxs)(`section`,{ref:i,className:`relative flex min-h-0 min-w-0 flex-col border-t border-ink-700 ${r}`,children:[(0,$.jsx)(Sc,{...a}),(0,$.jsx)(Cc,{ownsSize:w,maximized:t,recovery:ue,panes:b,onCancelRecovery:fe,onClaimSize:_e,onCreate:he,onToggleMaximized:n}),(0,$.jsxs)(`div`,{className:`relative min-h-0 flex-1 overflow-hidden bg-ink-950 p-1`,children:[b.length===0&&v===null&&(0,$.jsxs)(`p`,{className:`p-3 text-ink-400`,children:[`No terminal open. Press `,(0,$.jsx)(`span`,{className:`text-accent`,children:`+`}),` above to start one.`]}),(0,$.jsxs)(`div`,{ref:o,className:`grid h-full gap-1`,style:T===null?{gridTemplateColumns:`repeat(${Oe.cols}, minmax(0, 1fr))`,gridTemplateRows:`repeat(${Oe.rows}, minmax(0, 1fr))`}:{gridTemplateColumns:`1fr`,gridTemplateRows:`1fr`},children:[b.length===0&&v!==null&&(0,$.jsx)(bc,{count:v,cells:Oe.cells,slotRefs:_}),b.map((e,t)=>{let n=ce[e]??`term ${t+1}`,r=Oe.cells[t];return(0,$.jsx)(yc,{pane:e,index:t,label:n,cellStyle:T===null?{display:`flex`,gridColumn:`${r.colStart} / span ${r.colSpan}`,gridRow:`${r.row}`}:{display:e===T?`flex`:`none`},isActive:e===re,isZoomed:T===e,showZoom:b.length>1,isDragged:xe===e,isDropTarget:Se===e,reorderable:Ce,recovery:ue[e],onCancelRecovery:()=>fe(e),onFocus:()=>me(e),onToggleZoom:()=>ge(e),onClose:()=>ve(e),onPaneDragStart:t=>Te(t,e),onPaneDragMove:Ee,onPaneDragEnd:De,onPaneDragCancel:we,bodyRef:t=>{t?u.current.set(e,t):u.current.delete(e)}},e)})]})]}),b.length>0&&(0,$.jsx)(xc,{onKey:be})]})}export{wc as TerminalPanel}; \ No newline at end of file diff --git a/viewer-ui/dist/assets/Terminal-mF7xsueX.js b/viewer-ui/dist/assets/Terminal-mF7xsueX.js new file mode 100644 index 00000000..5369ea34 --- /dev/null +++ b/viewer-ui/dist/assets/Terminal-mF7xsueX.js @@ -0,0 +1,35 @@ +import{a as e,c as t,i as n,l as r,n as i,o as a,r as o,s,t as c}from"./index-C2FlDlUR.js";var l=r();function u(e,t){for(;t;)[e,t]=[t,e%t];return e}function d(e,t){switch(e){case 1:return[1];case 2:return t?[2]:[1,1];case 3:return[2,1];case 4:return[2,2];case 5:return[3,2];case 6:return[3,3];case 7:return[4,3];default:return[4,4]}}function f(e,t){let n=d(e,t),r=n.reduce((e,t)=>e*t/u(e,t),1),i=[];return n.forEach((e,t)=>{let n=r/e;for(let r=0;r=4352&&e<=4447||e>=11904&&e<=12350||e>=12353&&e<=13311||e>=13312&&e<=19903||e>=19968&&e<=40959||e>=40960&&e<=42191||e>=44032&&e<=55203||e>=63744&&e<=64255||e>=65072&&e<=65103||e>=65280&&e<=65376||e>=65504&&e<=65510||e>=127744&&e<=129791||e>=131072&&e<=262141}function m(e,t){let n=0;for(let t of e)n+=p(t.codePointAt(0)??0)?2:1;if(n<=t)return e;let r=0,i=``;for(let n of e){let e=p(n.codePointAt(0)??0)?2:1;if(r+e>t-1)break;i+=n,r+=e}return`${i}…`}function h({panes:e,zoomed:t,onFocus:n,onReorder:r}){let a=(0,l.useRef)(null),o=(0,l.useRef)(null),s=(0,l.useRef)(null),c=(0,l.useRef)(!1),[u,d]=(0,l.useState)(null),[f,p]=(0,l.useState)(null),m=t===null&&e.length>1,h=()=>{a.current=null,o.current=null,s.current=null,c.current=!1,d(null),p(null)};return{draggingPane:u,dragOverPane:f,reorderable:m,endPaneDrag:h,onPaneDragStart:(e,t)=>{e.target.closest(`button`)||(n(t),!(e.button!==0||!m)&&(a.current=t,o.current={x:e.clientX,y:e.clientY},c.current=!1,e.currentTarget.setPointerCapture(e.pointerId)))},onPaneDragMove:e=>{let t=a.current,n=o.current;if(t===null||n===null||!c.current&&Math.hypot(e.clientX-n.x,e.clientY-n.y)<4)return;c.current=!0,d(t);let r=document.elementFromPoint(e.clientX,e.clientY)?.closest(`[data-pane-id]`),i=r?Number(r.getAttribute(`data-pane-id`)):null,l=i!==null&&i!==t?i:null;s.current=l,p(l)},onPaneDragEnd:()=>{let t=a.current,n=s.current;t!==null&&c.current&&n!==null&&r(i(e,t,n)),h()}}}var g=`nightcrow.viewer`;function _(){return globalThis.crypto?.randomUUID?.()||`tab-${Math.floor(Math.random()*2**48).toString(36)}`}function ee(){try{let e=sessionStorage.getItem(g);if(e)return e;let t=_();return sessionStorage.setItem(g,t),t}catch{return _()}}var te=!1;function ne(){return te?!1:(te=!0,!0)}function re(e){return typeof e==`number`&&Number.isSafeInteger(e)}function v(e){return re(e)&&e>=0}function ie(e){let t;try{t=JSON.parse(e)}catch{return null}if(!t||typeof t!=`object`||Array.isArray(t))return null;let n=t,r;switch(n.type){case`created`:r=v(n.pane)&&v(n.rows)&&v(n.cols)&&(n.client===void 0||v(n.client))&&(n.title===void 0||typeof n.title==`string`);break;case`exited`:r=v(n.pane);break;case`resized`:r=v(n.pane)&&v(n.rows)&&v(n.cols);break;case`hello`:r=v(n.client)&&v(n.panes);break;case`size_owner`:r=typeof n.owned==`boolean`;break;case`error`:r=typeof n.message==`string`;break;case`reordered`:r=Array.isArray(n.order)&&n.order.every(v);break;case`zoomed`:r=n.pane===null||v(n.pane);break;case`pending`:r=v(n.count);break;case`recovery`:r=v(n.pane)&&typeof n.state==`string`&&(n.detail===void 0||typeof n.detail==`string`)&&(n.deadline_epoch===void 0||re(n.deadline_epoch))&&v(n.attempt);break;default:return null}return r?n:null}function y(e,t){return!e||e.readyState!==WebSocket.OPEN?!1:(e.send(JSON.stringify(t)),!0)}function ae(e){return e.byteLength<4?null:{pane:new DataView(e).getUint32(0,!0),data:new Uint8Array(e,4)}}function oe(e,t){return t.state===`cancelled`?b(e,t.pane):{...e,[t.pane]:{state:t.state,detail:t.detail,deadlineEpoch:t.deadline_epoch,attempt:t.attempt}}}function b(e,t){if(!(t in e))return e;let n={...e};return delete n[t],n}function se(e,t){return Object.keys(e).map(Number).filter(e=>!t.includes(e)).sort((e,t)=>e-t)}function x(e){if(e===void 0||!Number.isFinite(e))return;let t=new Date(e*1e3);if(!Number.isNaN(t.getTime()))return`${String(t.getHours()).padStart(2,`0`)}:${String(t.getMinutes()).padStart(2,`0`)}`}function ce(e){let t=x(e.deadlineEpoch),n=[e.state];return t&&n.push(`until ${t}`),e.attempt>0&&n.push(`attempt ${e.attempt}`),n.join(` · `)}function le(e,t){if(typeof e==`string`){let n=ie(e);n&&ue(n,t);return}if(!(e instanceof ArrayBuffer))return;let n=ae(e);if(!n)return;let r=t.viewsRef.current.get(n.pane);if(r){r.term.write(n.data);return}let i=t.pendingRef.current.get(n.pane)??[];i.push(n.data),t.pendingRef.current.set(n.pane,i)}function ue(e,n){switch(e.type){case`hello`:n.clientIdRef.current=e.client,n.setReplayLeft(e.panes);return;case`pending`:n.setPending(e.count);return;case`created`:{let t=e.pane;n.sentSizesRef.current.set(t,{rows:e.rows,cols:e.cols});let r=e.title;r&&n.setTitles(e=>({...e,[t]:r})),n.setPanes(e=>[...e,t]),n.setReplayLeft(e=>e>0?e-1:0),e.client!=null&&e.client===n.clientIdRef.current?(n.setActive(t),n.lastActiveByRepoRef.current.set(n.repo,t)):n.lastActiveByRepoRef.current.get(n.repo)===t&&n.setActive(t);return}case`exited`:n.setPanes(t=>t.filter(t=>t!==e.pane)),n.setActive(t=>t===e.pane?null:t),n.pendingRef.current.delete(e.pane),n.sentSizesRef.current.delete(e.pane),n.setTitles(t=>{if(!(e.pane in t))return t;let n={...t};return delete n[e.pane],n});return;case`resized`:n.sentSizesRef.current.set(e.pane,{rows:e.rows,cols:e.cols}),n.viewsRef.current.get(e.pane)?.term.resize(e.cols,e.rows);return;case`recovery`:n.setRecovery(t=>oe(t,e));return;case`size_owner`:n.setOwnsSize(e.owned);return;case`reordered`:n.setPanes(t=>c(t,e.order));return;case`zoomed`:n.zoomAskedRef.current=void 0,n.setZoomed(e.pane??null);return;case`error`:t.error(e.message);return}return e}function S({repo:e,socketRef:t,viewsRef:n,pendingRef:r,sentSizesRef:i,lastActiveByRepoRef:a,zoomAskedRef:o,setPending:s,setReplayLeft:c,setPanes:u,setActive:d,setZoomed:f,setTitles:p,setOwnsSize:m,setRecovery:h}){let g=(0,l.useRef)(null);(0,l.useLayoutEffect)(()=>{let l=!1,_,te={repo:e,clientIdRef:g,viewsRef:n,pendingRef:r,sentSizesRef:i,lastActiveByRepoRef:a,zoomAskedRef:o,setPending:s,setReplayLeft:c,setPanes:u,setActive:d,setZoomed:f,setTitles:p,setOwnsSize:m,setRecovery:h},re=()=>{n.current.forEach(e=>e.term.dispose()),n.current.clear(),r.current.clear(),i.current.clear()},v=()=>{g.current=null,c(0),o.current=void 0,s(null),u([]),d(null),f(null),p({});let n=ne();n&&m(!0),h({}),re();let r=location.protocol===`https:`?`wss:`:`ws:`,i=new URLSearchParams({repo:e,viewer:ee()});n&&i.set(`claim`,`1`);let a=new WebSocket(`${r}//${location.host}/ws/term?${i}`);a.binaryType=`arraybuffer`,t.current=a,a.onmessage=e=>{t.current===a&&le(e.data,te)},a.onclose=()=>{l||(_=setTimeout(v,1e3))}};return v(),()=>{l=!0,_&&clearTimeout(_),t.current?.close(),re()}},[e])}var de=Object.defineProperty,fe=Object.getOwnPropertyDescriptor,pe=(e,t)=>{for(var n in t)de(e,n,{get:t[n],enumerable:!0})},C=(e,t,n,r)=>{for(var i=r>1?void 0:r?fe(t,n):t,a=e.length-1,o;a>=0;a--)(o=e[a])&&(i=(r?o(t,n,i):o(i))||i);return r&&i&&de(t,n,i),i},w=(e,t)=>(n,r)=>t(n,r,e),T=`Terminal input`,me={get:()=>T,set:e=>T=e},he=`Too much output to announce, navigate to rows manually to read`,ge={get:()=>he,set:e=>he=e};function _e(e){return e.replace(/\r?\n/g,`\r`)}function ve(e,t){return t?`\x1B[200~`+e+`\x1B[201~`:e}function ye(e,t){e.clipboardData&&e.clipboardData.setData(`text/plain`,t.selectionText),e.preventDefault()}function be(e,t,n,r){e.stopPropagation(),e.clipboardData&&xe(e.clipboardData.getData(`text/plain`),t,n,r)}function xe(e,t,n,r){e=_e(e),e=ve(e,n.decPrivateModes.bracketedPasteMode&&r.rawOptions.ignoreBracketedPasteMode!==!0),n.triggerDataEvent(e,!0),t.value=``}function Se(e,t,n){let r=n.getBoundingClientRect(),i=e.clientX-r.left-10,a=e.clientY-r.top-10;t.style.width=`20px`,t.style.height=`20px`,t.style.left=`${i}px`,t.style.top=`${a}px`,t.style.zIndex=`1000`,t.focus()}function Ce(e,t,n,r,i){Se(e,t,n),i&&r.rightClickSelect(e),t.value=r.selectionText,t.select()}function we(e){return e>65535?(e-=65536,String.fromCharCode((e>>10)+55296)+String.fromCharCode(e%1024+56320)):String.fromCharCode(e)}function Te(e,t=0,n=e.length){let r=``;for(let i=t;i65535?(t-=65536,r+=String.fromCharCode((t>>10)+55296)+String.fromCharCode(t%1024+56320)):r+=String.fromCharCode(t)}return r}var Ee=class{constructor(){this._interim=0}clear(){this._interim=0}decode(e,t){let n=e.length;if(!n)return 0;let r=0,i=0;if(this._interim){let n=e.charCodeAt(i++);56320<=n&&n<=57343?t[r++]=(this._interim-55296)*1024+n-56320+65536:(t[r++]=this._interim,t[r++]=n),this._interim=0}for(let a=i;a=n)return this._interim=i,r;let o=e.charCodeAt(a);56320<=o&&o<=57343?t[r++]=(i-55296)*1024+o-56320+65536:(t[r++]=i,t[r++]=o);continue}i!==65279&&(t[r++]=i)}return r}},De=class{constructor(){this.interim=new Uint8Array(3)}clear(){this.interim.fill(0)}decode(e,t){let n=e.length;if(!n)return 0;let r=0,i,a,o,s,c=0,l=0;if(this.interim[0]){let i=!1,a=this.interim[0];a&=(a&224)==192?31:(a&240)==224?15:7;let o=0,s;for(;(s=this.interim[++o]&63)&&o<4;)a<<=6,a|=s;let c=(this.interim[0]&224)==192?2:(this.interim[0]&240)==224?3:4,u=c-o;for(;l=n)return 0;if(s=e[l++],(s&192)!=128){l--,i=!0;break}else this.interim[o++]=s,a<<=6,a|=s&63}i||(c===2?a<128?l--:t[r++]=a:c===3?a<2048||a>=55296&&a<=57343||a===65279||(t[r++]=a):a<65536||a>1114111||(t[r++]=a)),this.interim.fill(0)}let u=n-4,d=l;for(;d=n)return this.interim[0]=i,r;if(a=e[d++],(a&192)!=128){d--;continue}if(c=(i&31)<<6|a&63,c<128){d--;continue}t[r++]=c}else if((i&240)==224){if(d>=n)return this.interim[0]=i,r;if(a=e[d++],(a&192)!=128){d--;continue}if(d>=n)return this.interim[0]=i,this.interim[1]=a,r;if(o=e[d++],(o&192)!=128){d--;continue}if(c=(i&15)<<12|(a&63)<<6|o&63,c<2048||c>=55296&&c<=57343||c===65279)continue;t[r++]=c}else if((i&248)==240){if(d>=n)return this.interim[0]=i,r;if(a=e[d++],(a&192)!=128){d--;continue}if(d>=n)return this.interim[0]=i,this.interim[1]=a,r;if(o=e[d++],(o&192)!=128){d--;continue}if(d>=n)return this.interim[0]=i,this.interim[1]=a,this.interim[2]=o,r;if(s=e[d++],(s&192)!=128){d--;continue}if(c=(i&7)<<18|(a&63)<<12|(o&63)<<6|s&63,c<65536||c>1114111)continue;t[r++]=c}}return r}},Oe=``,ke=` `,Ae=class e{constructor(){this.fg=0,this.bg=0,this.extended=new je}static toColorRGB(e){return[e>>>16&255,e>>>8&255,e&255]}static fromColorRGB(e){return(e[0]&255)<<16|(e[1]&255)<<8|e[2]&255}clone(){let t=new e;return t.fg=this.fg,t.bg=this.bg,t.extended=this.extended.clone(),t}isInverse(){return this.fg&67108864}isBold(){return this.fg&134217728}isUnderline(){return this.hasExtendedAttrs()&&this.extended.underlineStyle!==0?1:this.fg&268435456}isBlink(){return this.fg&536870912}isInvisible(){return this.fg&1073741824}isItalic(){return this.bg&67108864}isDim(){return this.bg&134217728}isStrikethrough(){return this.fg&2147483648}isProtected(){return this.bg&536870912}isOverline(){return this.bg&1073741824}getFgColorMode(){return this.fg&50331648}getBgColorMode(){return this.bg&50331648}isFgRGB(){return(this.fg&50331648)==50331648}isBgRGB(){return(this.bg&50331648)==50331648}isFgPalette(){return(this.fg&50331648)==16777216||(this.fg&50331648)==33554432}isBgPalette(){return(this.bg&50331648)==16777216||(this.bg&50331648)==33554432}isFgDefault(){return(this.fg&50331648)==0}isBgDefault(){return(this.bg&50331648)==0}isAttributeDefault(){return this.fg===0&&this.bg===0}getFgColor(){switch(this.fg&50331648){case 16777216:case 33554432:return this.fg&255;case 50331648:return this.fg&16777215;default:return-1}}getBgColor(){switch(this.bg&50331648){case 16777216:case 33554432:return this.bg&255;case 50331648:return this.bg&16777215;default:return-1}}hasExtendedAttrs(){return this.bg&268435456}updateExtended(){this.extended.isEmpty()?this.bg&=-268435457:this.bg|=268435456}getUnderlineColor(){if(this.bg&268435456&&~this.extended.underlineColor)switch(this.extended.underlineColor&50331648){case 16777216:case 33554432:return this.extended.underlineColor&255;case 50331648:return this.extended.underlineColor&16777215;default:return this.getFgColor()}return this.getFgColor()}getUnderlineColorMode(){return this.bg&268435456&&~this.extended.underlineColor?this.extended.underlineColor&50331648:this.getFgColorMode()}isUnderlineColorRGB(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)==50331648:this.isFgRGB()}isUnderlineColorPalette(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)==16777216||(this.extended.underlineColor&50331648)==33554432:this.isFgPalette()}isUnderlineColorDefault(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)==0:this.isFgDefault()}getUnderlineStyle(){return this.fg&268435456?this.bg&268435456?this.extended.underlineStyle:1:0}getUnderlineVariantOffset(){return this.extended.underlineVariantOffset}},je=class e{constructor(e=0,t=0){this._ext=0,this._urlId=0,this._ext=e,this._urlId=t}get ext(){return this._urlId?this._ext&-469762049|this.underlineStyle<<26:this._ext}set ext(e){this._ext=e}get underlineStyle(){return this._urlId?5:(this._ext&469762048)>>26}set underlineStyle(e){this._ext&=-469762049,this._ext|=e<<26&469762048}get underlineColor(){return this._ext&67108863}set underlineColor(e){this._ext&=-67108864,this._ext|=e&67108863}get urlId(){return this._urlId}set urlId(e){this._urlId=e}get underlineVariantOffset(){let e=(this._ext&3758096384)>>29;return e<0?e^4294967288:e}set underlineVariantOffset(e){this._ext&=536870911,this._ext|=e<<29&3758096384}clone(){return new e(this._ext,this._urlId)}isEmpty(){return this.underlineStyle===0&&this._urlId===0}},Me=class e extends Ae{constructor(){super(...arguments),this.content=0,this.fg=0,this.bg=0,this.extended=new je,this.combinedData=``}static fromCharData(t){let n=new e;return n.setFromCharData(t),n}isCombined(){return this.content&2097152}getWidth(){return this.content>>22}getChars(){return this.content&2097152?this.combinedData:this.content&2097151?we(this.content&2097151):``}getCode(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):this.content&2097151}setFromCharData(e){this.fg=e[0],this.bg=0;let t=!1;if(e[1].length>2)t=!0;else if(e[1].length===2){let n=e[1].charCodeAt(0);if(55296<=n&&n<=56319){let r=e[1].charCodeAt(1);56320<=r&&r<=57343?this.content=(n-55296)*1024+r-56320+65536|e[2]<<22:t=!0}else t=!0}else this.content=e[1].charCodeAt(0)|e[2]<<22;t&&(this.combinedData=e[1],this.content=2097152|e[2]<<22)}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}},Ne=`di$target`,Pe=`di$dependencies`,Fe=new Map;function Ie(e){return e[Pe]||[]}function E(e){if(Fe.has(e))return Fe.get(e);let t=function(e,n,r){if(arguments.length!==3)throw Error(`@IServiceName-decorator can only be used to decorate a parameter`);Le(t,e,r)};return t._id=e,Fe.set(e,t),t}function Le(e,t,n){t[Ne]===t?t[Pe].push({id:e,index:n}):(t[Pe]=[{id:e,index:n}],t[Ne]=t)}var D=E(`BufferService`),Re=E(`CoreMouseService`),ze=E(`CoreService`),Be=E(`CharsetService`),Ve=E(`InstantiationService`),He=E(`LogService`),O=E(`OptionsService`),Ue=E(`OscLinkService`),We=E(`UnicodeService`),Ge=E(`DecorationService`),Ke=class{constructor(e,t,n){this._bufferService=e,this._optionsService=t,this._oscLinkService=n}provideLinks(e,t){let n=this._bufferService.buffer.lines.get(e-1);if(!n){t(void 0);return}let r=[],i=this._optionsService.rawOptions.linkHandler,a=new Me,o=n.getTrimmedLength(),s=-1,c=-1,l=!1;for(let t=0;ti?i.activate(e,t,a):qe(e,t),hover:(e,t)=>i?.hover?.(e,t,a),leave:(e,t)=>i?.leave?.(e,t,a)})}l=!1,a.hasExtendedAttrs()&&a.extended.urlId?(c=t,s=a.extended.urlId):(c=-1,s=-1)}}t(r)}};Ke=C([w(0,D),w(1,O),w(2,Ue)],Ke);function qe(e,t){if(confirm(`Do you want to navigate to ${t}? + +WARNING: This link could potentially be dangerous`)){let e=window.open();if(e){try{e.opener=null}catch{}e.location.href=t}else console.warn(`Opening link blocked as opener could not be cleared`)}}var Je=E(`CharSizeService`),Ye=E(`CoreBrowserService`),Xe=E(`MouseService`),Ze=E(`RenderService`),Qe=E(`SelectionService`),$e=E(`CharacterJoinerService`),et=E(`ThemeService`),tt=E(`LinkProviderService`),nt=new class{constructor(){this.listeners=[],this.unexpectedErrorHandler=function(e){setTimeout(()=>{throw e.stack?ct.isErrorNoTelemetry(e)?new ct(e.message+` + +`+e.stack):Error(e.message+` + +`+e.stack):e},0)}}addListener(e){return this.listeners.push(e),()=>{this._removeListener(e)}}emit(e){this.listeners.forEach(t=>{t(e)})}_removeListener(e){this.listeners.splice(this.listeners.indexOf(e),1)}setUnexpectedErrorHandler(e){this.unexpectedErrorHandler=e}getUnexpectedErrorHandler(){return this.unexpectedErrorHandler}onUnexpectedError(e){this.unexpectedErrorHandler(e),this.emit(e)}onUnexpectedExternalError(e){this.unexpectedErrorHandler(e)}};function rt(e){at(e)||nt.onUnexpectedError(e)}var it=`Canceled`;function at(e){return e instanceof ot||e instanceof Error&&e.name===it&&e.message===it}var ot=class extends Error{constructor(){super(it),this.name=this.message}};function st(e){return Error(e?`Illegal argument: ${e}`:`Illegal argument`)}var ct=class e extends Error{constructor(e){super(e),this.name=`CodeExpectedError`}static fromError(t){if(t instanceof e)return t;let n=new e;return n.message=t.message,n.stack=t.stack,n}static isErrorNoTelemetry(e){return e.name===`CodeExpectedError`}},lt=class e extends Error{constructor(t){super(t||`An unexpected bug occurred.`),Object.setPrototypeOf(this,e.prototype)}};function ut(e,t,n=0,r=e.length){let i=n,a=r;for(;i{function t(e){return e<0}e.isLessThan=t;function n(e){return e<=0}e.isLessThanOrEqual=n;function r(e){return e>0}e.isGreaterThan=r;function i(e){return e===0}e.isNeitherLessOrGreaterThan=i,e.greaterThan=1,e.lessThan=-1,e.neitherLessOrGreaterThan=0})(pt||={});function mt(e,t){return(n,r)=>t(e(n),e(r))}var ht=(e,t)=>e-t,gt=class e{constructor(e){this.iterate=e}forEach(e){this.iterate(t=>(e(t),!0))}toArray(){let e=[];return this.iterate(t=>(e.push(t),!0)),e}filter(t){return new e(e=>this.iterate(n=>!t(n)||e(n)))}map(t){return new e(e=>this.iterate(n=>e(t(n))))}some(e){let t=!1;return this.iterate(n=>(t=e(n),!t)),t}findFirst(e){let t;return this.iterate(n=>e(n)?(t=n,!1):!0),t}findLast(e){let t;return this.iterate(n=>(e(n)&&(t=n),!0)),t}findLastMaxBy(e){let t,n=!0;return this.iterate(r=>((n||pt.isGreaterThan(e(r,t)))&&(n=!1,t=r),!0)),t}};gt.empty=new gt(e=>{});function _t(e,t){let n=Object.create(null);for(let r of e){let e=t(r),i=n[e];i||=n[e]=[],i.push(r)}return n}var vt=class{constructor(){this.map=new Map}add(e,t){let n=this.map.get(e);n||(n=new Set,this.map.set(e,n)),n.add(t)}delete(e,t){let n=this.map.get(e);n&&(n.delete(t),n.size===0&&this.map.delete(e))}forEach(e,t){let n=this.map.get(e);n&&n.forEach(t)}get(e){return this.map.get(e)||new Set}};function yt(e,t){let n=this,r=!1,i;return function(){if(r)return i;if(r=!0,t)try{i=e.apply(n,arguments)}finally{t()}else i=e.apply(n,arguments);return i}}var bt;(e=>{function t(e){return e&&typeof e==`object`&&typeof e[Symbol.iterator]==`function`}e.is=t;let n=Object.freeze([]);function r(){return n}e.empty=r;function*i(e){yield e}e.single=i;function a(e){return t(e)?e:i(e)}e.wrap=a;function o(e){return e||n}e.from=o;function*s(e){for(let t=e.length-1;t>=0;t--)yield e[t]}e.reverse=s;function c(e){return!e||e[Symbol.iterator]().next().done===!0}e.isEmpty=c;function l(e){return e[Symbol.iterator]().next().value}e.first=l;function u(e,t){let n=0;for(let r of e)if(t(r,n++))return!0;return!1}e.some=u;function d(e,t){for(let n of e)if(t(n))return n}e.find=d;function*f(e,t){for(let n of e)t(n)&&(yield n)}e.filter=f;function*p(e,t){let n=0;for(let r of e)yield t(r,n++)}e.map=p;function*m(e,t){let n=0;for(let r of e)yield*t(r,n++)}e.flatMap=m;function*h(...e){for(let t of e)yield*t}e.concat=h;function g(e,t,n){let r=n;for(let n of e)r=t(r,n);return r}e.reduce=g;function*_(e,t,n=e.length){for(t<0&&(t+=e.length),n<0?n+=e.length:n>e.length&&(n=e.length);tt.source!==null&&!this.getRootParent(t,e).isSingleton).flatMap(([e])=>e)}computeLeakingDisposables(e=10,t){let n;if(t)n=t;else{let e=new Map,t=[...this.livingDisposables.values()].filter(t=>t.source!==null&&!this.getRootParent(t,e).isSingleton);if(t.length===0)return;let r=new Set(t.map(e=>e.value));if(n=t.filter(e=>!(e.parent&&r.has(e.parent))),n.length===0)throw Error(`There are cyclic diposable chains!`)}if(!n)return;function r(e){function t(e,t){for(;e.length>0&&t.some(t=>typeof t==`string`?t===e[0]:e[0].match(t));)e.shift()}let n=e.source.split(` +`).map(e=>e.trim().replace(`at `,``)).filter(e=>e!==``);return t(n,[`Error`,/^trackDisposable \(.*\)$/,/^DisposableTracker.trackDisposable \(.*\)$/]),n.reverse()}let i=new vt;for(let e of n){let t=r(e);for(let n=0;n<=t.length;n++)i.add(t.slice(0,n).join(` +`),e)}n.sort(mt(e=>e.idx,ht));let a=``,o=0;for(let t of n.slice(0,e)){o++;let e=r(t),s=[];for(let t=0;tr(e)[t]),e=>e);delete o[e[t]];for(let[e,t]of Object.entries(o))s.unshift(` - stacktraces of ${t.length} other leaks continue with ${e}`);s.unshift(a)}a+=` + + +==================== Leaking disposable ${o}/${n.length}: ${t.value.constructor.name} ==================== +${s.join(` +`)} +============================================================ + +`}return n.length>e&&(a+=` + + +... and ${n.length-e} more leaking disposables + +`),{leaks:n,details:a}}};St.idx=0;function Ct(e){return xt?.trackDisposable(e),e}function wt(e){xt?.markAsDisposed(e)}function Tt(e,t){xt?.setParent(e,t)}function Et(e){return xt?.markAsSingleton(e),e}function Dt(e){if(bt.is(e)){let t=[];for(let n of e)if(n)try{n.dispose()}catch(e){t.push(e)}if(t.length===1)throw t[0];if(t.length>1)throw AggregateError(t,`Encountered errors while disposing of store`);return Array.isArray(e)?[]:e}else if(e)return e.dispose(),e}function Ot(...e){return k(()=>Dt(e))}function k(e){let t=Ct({dispose:yt(()=>{wt(t),e()})});return t}var kt=class e{constructor(){this._toDispose=new Set,this._isDisposed=!1,Ct(this)}dispose(){this._isDisposed||(wt(this),this._isDisposed=!0,this.clear())}get isDisposed(){return this._isDisposed}clear(){if(this._toDispose.size!==0)try{Dt(this._toDispose)}finally{this._toDispose.clear()}}add(t){if(!t)return t;if(t===this)throw Error(`Cannot register a disposable on itself!`);return Tt(t,this),this._isDisposed?e.DISABLE_DISPOSED_WARNING||console.warn(Error(`Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!`).stack):this._toDispose.add(t),t}delete(e){if(e){if(e===this)throw Error(`Cannot dispose a disposable on itself!`);this._toDispose.delete(e),e.dispose()}}deleteAndLeak(e){e&&this._toDispose.has(e)&&(this._toDispose.delete(e),Tt(e,null))}};kt.DISABLE_DISPOSED_WARNING=!1;var At=kt,A=class{constructor(){this._store=new At,Ct(this),Tt(this._store,this)}dispose(){wt(this),this._store.dispose()}_register(e){if(e===this)throw Error(`Cannot register a disposable on itself!`);return this._store.add(e)}};A.None=Object.freeze({dispose(){}});var jt=class{constructor(){this._isDisposed=!1,Ct(this)}get value(){return this._isDisposed?void 0:this._value}set value(e){this._isDisposed||e===this._value||(this._value?.dispose(),e&&Tt(e,this),this._value=e)}clear(){this.value=void 0}dispose(){this._isDisposed=!0,wt(this),this._value?.dispose(),this._value=void 0}clearAndLeak(){let e=this._value;return this._value=void 0,e&&Tt(e,null),e}},Mt=typeof window==`object`?window:globalThis,Nt=class e{constructor(t){this.element=t,this.next=e.Undefined,this.prev=e.Undefined}};Nt.Undefined=new Nt(void 0);var j=Nt,Pt=class{constructor(){this._first=j.Undefined,this._last=j.Undefined,this._size=0}get size(){return this._size}isEmpty(){return this._first===j.Undefined}clear(){let e=this._first;for(;e!==j.Undefined;){let t=e.next;e.prev=j.Undefined,e.next=j.Undefined,e=t}this._first=j.Undefined,this._last=j.Undefined,this._size=0}unshift(e){return this._insert(e,!1)}push(e){return this._insert(e,!0)}_insert(e,t){let n=new j(e);if(this._first===j.Undefined)this._first=n,this._last=n;else if(t){let e=this._last;this._last=n,n.prev=e,e.next=n}else{let e=this._first;this._first=n,n.next=e,e.prev=n}this._size+=1;let r=!1;return()=>{r||(r=!0,this._remove(n))}}shift(){if(this._first!==j.Undefined){let e=this._first.element;return this._remove(this._first),e}}pop(){if(this._last!==j.Undefined){let e=this._last.element;return this._remove(this._last),e}}_remove(e){if(e.prev!==j.Undefined&&e.next!==j.Undefined){let t=e.prev;t.next=e.next,e.next.prev=t}else e.prev===j.Undefined&&e.next===j.Undefined?(this._first=j.Undefined,this._last=j.Undefined):e.next===j.Undefined?(this._last=this._last.prev,this._last.next=j.Undefined):e.prev===j.Undefined&&(this._first=this._first.next,this._first.prev=j.Undefined);--this._size}*[Symbol.iterator](){let e=this._first;for(;e!==j.Undefined;)yield e.element,e=e.next}},Ft=globalThis.performance&&typeof globalThis.performance.now==`function`,It=class e{static create(t){return new e(t)}constructor(e){this._now=Ft&&e===!1?Date.now:globalThis.performance.now.bind(globalThis.performance),this._startTime=this._now(),this._stopTime=-1}stop(){this._stopTime=this._now()}reset(){this._startTime=this._now(),this._stopTime=-1}elapsed(){return this._stopTime===-1?this._now()-this._startTime:this._stopTime-this._startTime}},M;(e=>{e.None=()=>A.None;function t(e,t){return d(e,()=>{},0,void 0,!0,void 0,t)}e.defer=t;function n(e){return(t,n=null,r)=>{let i=!1,a;return a=e(e=>{if(!i)return a?a.dispose():i=!0,t.call(n,e)},null,r),i&&a.dispose(),a}}e.once=n;function r(e,t,n){return l((n,r=null,i)=>e(e=>n.call(r,t(e)),null,i),n)}e.map=r;function i(e,t,n){return l((n,r=null,i)=>e(e=>{t(e),n.call(r,e)},null,i),n)}e.forEach=i;function a(e,t,n){return l((n,r=null,i)=>e(e=>t(e)&&n.call(r,e),null,i),n)}e.filter=a;function o(e){return e}e.signal=o;function s(...e){return(t,n=null,r)=>u(Ot(...e.map(e=>e(e=>t.call(n,e)))),r)}e.any=s;function c(e,t,n,i){let a=n;return r(e,e=>(a=t(a,e),a),i)}e.reduce=c;function l(e,t){let n,r=new N({onWillAddFirstListener(){n=e(r.fire,r)},onDidRemoveLastListener(){n?.dispose()}});return t?.add(r),r.event}function u(e,t){return t instanceof Array?t.push(e):t&&t.add(e),e}function d(e,t,n=100,r=!1,i=!1,a,o){let s,c,l,u=0,d,f=new N({leakWarningThreshold:a,onWillAddFirstListener(){s=e(e=>{u++,c=t(c,e),r&&!l&&(f.fire(c),c=void 0),d=()=>{let e=c;c=void 0,l=void 0,(!r||u>1)&&f.fire(e),u=0},typeof n==`number`?(clearTimeout(l),l=setTimeout(d,n)):l===void 0&&(l=0,queueMicrotask(d))})},onWillRemoveListener(){i&&u>0&&d?.()},onDidRemoveLastListener(){d=void 0,s.dispose()}});return o?.add(f),f.event}e.debounce=d;function f(t,n=0,r){return e.debounce(t,(e,t)=>e?(e.push(t),e):[t],n,void 0,!0,void 0,r)}e.accumulate=f;function p(e,t=(e,t)=>e===t,n){let r=!0,i;return a(e,e=>{let n=r||!t(e,i);return r=!1,i=e,n},n)}e.latch=p;function m(t,n,r){return[e.filter(t,n,r),e.filter(t,e=>!n(e),r)]}e.split=m;function h(e,t=!1,n=[],r){let i=n.slice(),a=e(e=>{i?i.push(e):s.fire(e)});r&&r.add(a);let o=()=>{i?.forEach(e=>s.fire(e)),i=null},s=new N({onWillAddFirstListener(){a||(a=e(e=>s.fire(e)),r&&r.add(a))},onDidAddFirstListener(){i&&(t?setTimeout(o):o())},onDidRemoveLastListener(){a&&a.dispose(),a=null}});return r&&r.add(s),s.event}e.buffer=h;function g(e,t){return(n,r,i)=>{let a=t(new ee);return e(function(e){let t=a.evaluate(e);t!==_&&n.call(r,t)},void 0,i)}}e.chain=g;let _=Symbol(`HaltChainable`);class ee{constructor(){this.steps=[]}map(e){return this.steps.push(e),this}forEach(e){return this.steps.push(t=>(e(t),t)),this}filter(e){return this.steps.push(t=>e(t)?t:_),this}reduce(e,t){let n=t;return this.steps.push(t=>(n=e(n,t),n)),this}latch(e=(e,t)=>e===t){let t=!0,n;return this.steps.push(r=>{let i=t||!e(r,n);return t=!1,n=r,i?r:_}),this}evaluate(e){for(let t of this.steps)if(e=t(e),e===_)break;return e}}function te(e,t,n=e=>e){let r=(...e)=>i.fire(n(...e)),i=new N({onWillAddFirstListener:()=>e.on(t,r),onDidRemoveLastListener:()=>e.removeListener(t,r)});return i.event}e.fromNodeEventEmitter=te;function ne(e,t,n=e=>e){let r=(...e)=>i.fire(n(...e)),i=new N({onWillAddFirstListener:()=>e.addEventListener(t,r),onDidRemoveLastListener:()=>e.removeEventListener(t,r)});return i.event}e.fromDOMEventEmitter=ne;function re(e){return new Promise(t=>n(e)(t))}e.toPromise=re;function v(e){let t=new N;return e.then(e=>{t.fire(e)},()=>{t.fire(void 0)}).finally(()=>{t.dispose()}),t.event}e.fromPromise=v;function ie(e,t){return e(e=>t.fire(e))}e.forward=ie;function y(e,t,n){return t(n),e(e=>t(e))}e.runAndSubscribe=y;class ae{constructor(e,t){this._observable=e,this._counter=0,this._hasChanged=!1;let n={onWillAddFirstListener:()=>{e.addObserver(this)},onDidRemoveLastListener:()=>{e.removeObserver(this)}};this.emitter=new N(n),t&&t.add(this.emitter)}beginUpdate(e){this._counter++}handlePossibleChange(e){}handleChange(e,t){this._hasChanged=!0}endUpdate(e){this._counter--,this._counter===0&&(this._observable.reportChanges(),this._hasChanged&&(this._hasChanged=!1,this.emitter.fire(this._observable.get())))}}function oe(e,t){return new ae(e,t).emitter.event}e.fromObservable=oe;function b(e){return(t,n,r)=>{let i=0,a=!1,o={beginUpdate(){i++},endUpdate(){i--,i===0&&(e.reportChanges(),a&&(a=!1,t.call(n)))},handlePossibleChange(){},handleChange(){a=!0}};e.addObserver(o),e.reportChanges();let s={dispose(){e.removeObserver(o)}};return r instanceof At?r.add(s):Array.isArray(r)&&r.push(s),s}}e.fromObservableLight=b})(M||={});var Lt=class e{constructor(t){this.listenerCount=0,this.invocationCount=0,this.elapsedOverall=0,this.durations=[],this.name=`${t}_${e._idPool++}`,e.all.add(this)}start(e){this._stopWatch=new It,this.listenerCount=e}stop(){if(this._stopWatch){let e=this._stopWatch.elapsed();this.durations.push(e),this.elapsedOverall+=e,this.invocationCount+=1,this._stopWatch=void 0}}};Lt.all=new Set,Lt._idPool=0;var Rt=Lt,zt=-1,Bt=class e{constructor(t,n,r=(e._idPool++).toString(16).padStart(3,`0`)){this._errorHandler=t,this.threshold=n,this.name=r,this._warnCountdown=0}dispose(){this._stacks?.clear()}check(e,t){let n=this.threshold;if(n<=0||t{let t=this._stacks.get(e.value)||0;this._stacks.set(e.value,t-1)}}getMostFrequentStack(){if(!this._stacks)return;let e,t=0;for(let[n,r]of this._stacks)(!e||t0||this._options?.leakWarningThreshold?new Vt(e?.onListenerError??rt,this._options?.leakWarningThreshold??zt):void 0,this._perfMon=this._options?._profName?new Rt(this._options._profName):void 0,this._deliveryQueue=this._options?.deliveryQueue}dispose(){this._disposed||(this._disposed=!0,this._deliveryQueue?.current===this&&this._deliveryQueue.reset(),this._listeners&&(this._listeners=void 0,this._size=0),this._options?.onDidRemoveLastListener?.(),this._leakageMon?.dispose())}get event(){return this._event??=(e,t,n)=>{if(this._leakageMon&&this._size>this._leakageMon.threshold**2){let e=`[${this._leakageMon.name}] REFUSES to accept new listeners because it exceeded its threshold by far (${this._size} vs ${this._leakageMon.threshold})`;console.warn(e);let t=this._leakageMon.getMostFrequentStack()??[`UNKNOWN stack`,-1],n=new Wt(`${e}. HINT: Stack shows most frequent listener (${t[1]}-times)`,t[0]);return(this._options?.onListenerError||rt)(n),A.None}if(this._disposed)return A.None;t&&(e=e.bind(t));let r=new Kt(e),i;this._leakageMon&&this._size>=Math.ceil(this._leakageMon.threshold*.2)&&(r.stack=Ht.create(),i=this._leakageMon.check(r.stack,this._size+1)),this._listeners?this._listeners instanceof Kt?(this._deliveryQueue??=new Yt,this._listeners=[this._listeners,r]):this._listeners.push(r):(this._options?.onWillAddFirstListener?.(this),this._listeners=r,this._options?.onDidAddFirstListener?.(this)),this._size++;let a=k(()=>{Jt?.unregister(a),i?.(),this._removeListener(r)});if(n instanceof At?n.add(a):Array.isArray(n)&&n.push(a),Jt){let e=Error().stack.split(` +`).slice(2,3).join(` +`).trim(),t=/(file:|vscode-file:\/\/vscode-app)?(\/[^:]*:\d+:\d+)/.exec(e);Jt.register(a,t?.[2]??e,a)}return a},this._event}_removeListener(e){if(this._options?.onWillRemoveListener?.(this),!this._listeners)return;if(this._size===1){this._listeners=void 0,this._options?.onDidRemoveLastListener?.(this),this._size=0;return}let t=this._listeners,n=t.indexOf(e);if(n===-1)throw console.log(`disposed?`,this._disposed),console.log(`size?`,this._size),console.log(`arr?`,JSON.stringify(this._listeners)),Error(`Attempted to dispose unknown listener`);this._size--,t[n]=void 0;let r=this._deliveryQueue.current===this;if(this._size*qt<=t.length){let e=0;for(let n=0;n0}},Yt=class{constructor(){this.i=-1,this.end=0}enqueue(e,t,n){this.i=0,this.end=n,this.current=e,this.value=t}reset(){this.i=this.end,this.current=void 0,this.value=void 0}},Xt=class{constructor(){this.mapWindowIdToZoomLevel=new Map,this._onDidChangeZoomLevel=new N,this.onDidChangeZoomLevel=this._onDidChangeZoomLevel.event,this.mapWindowIdToZoomFactor=new Map,this._onDidChangeFullscreen=new N,this.onDidChangeFullscreen=this._onDidChangeFullscreen.event,this.mapWindowIdToFullScreen=new Map}getZoomLevel(e){return this.mapWindowIdToZoomLevel.get(this.getWindowId(e))??0}setZoomLevel(e,t){if(this.getZoomLevel(t)===e)return;let n=this.getWindowId(t);this.mapWindowIdToZoomLevel.set(n,e),this._onDidChangeZoomLevel.fire(n)}getZoomFactor(e){return this.mapWindowIdToZoomFactor.get(this.getWindowId(e))??1}setZoomFactor(e,t){this.mapWindowIdToZoomFactor.set(this.getWindowId(t),e)}setFullscreen(e,t){if(this.isFullscreen(t)===e)return;let n=this.getWindowId(t);this.mapWindowIdToFullScreen.set(n,e),this._onDidChangeFullscreen.fire(n)}isFullscreen(e){return!!this.mapWindowIdToFullScreen.get(this.getWindowId(e))}getWindowId(e){return e.vscodeWindowId}};Xt.INSTANCE=new Xt;var Zt=Xt;function Qt(e,t,n){typeof t==`string`&&(t=e.matchMedia(t)),t.addEventListener(`change`,n)}Zt.INSTANCE.onDidChangeZoomLevel;function $t(e){return Zt.INSTANCE.getZoomFactor(e)}Zt.INSTANCE.onDidChangeFullscreen;var en=typeof navigator==`object`?navigator.userAgent:``,tn=en.indexOf(`Firefox`)>=0,nn=en.indexOf(`AppleWebKit`)>=0,rn=en.indexOf(`Chrome`)>=0,an=!rn&&en.indexOf(`Safari`)>=0;en.indexOf(`Electron/`),en.indexOf(`Android`);var on=!1;if(typeof Mt.matchMedia==`function`){let e=Mt.matchMedia(`(display-mode: standalone) or (display-mode: window-controls-overlay)`),t=Mt.matchMedia(`(display-mode: fullscreen)`);on=e.matches,Qt(Mt,e,({matches:e})=>{on&&t.matches||(on=e)})}function sn(){return on}var cn=`en`,ln=!1,un=!1,dn=!1,fn=!1,pn=!1,mn=cn,hn,gn=globalThis,_n;typeof gn.vscode<`u`&&typeof gn.vscode.process<`u`?_n=gn.vscode.process:typeof process<`u`&&typeof process?.versions?.node==`string`&&(_n=process);var vn=typeof _n?.versions?.electron==`string`&&_n?.type===`renderer`;if(typeof _n==`object`){ln=_n.platform===`win32`,un=_n.platform===`darwin`,dn=_n.platform===`linux`,dn&&_n.env.SNAP&&_n.env.SNAP_REVISION,_n.env.CI||_n.env.BUILD_ARTIFACTSTAGINGDIRECTORY,mn=cn;let e=_n.env.VSCODE_NLS_CONFIG;if(e)try{let t=JSON.parse(e);t.userLocale,t.osLocale,mn=t.resolvedLanguage||cn,t.languagePack?.translationsConfigFile}catch{}fn=!0}else typeof navigator==`object`&&!vn?(hn=navigator.userAgent,ln=hn.indexOf(`Windows`)>=0,un=hn.indexOf(`Macintosh`)>=0,(hn.indexOf(`Macintosh`)>=0||hn.indexOf(`iPad`)>=0||hn.indexOf(`iPhone`)>=0)&&navigator.maxTouchPoints&&navigator.maxTouchPoints,dn=hn.indexOf(`Linux`)>=0,hn?.indexOf(`Mobi`),pn=!0,mn=globalThis._VSCODE_NLS_LANGUAGE||cn,navigator.language.toLowerCase()):console.error(`Unable to resolve platform.`);var yn=ln,bn=un,xn=dn,Sn=fn;pn&&typeof gn.importScripts==`function`&&gn.origin;var Cn=hn,wn=mn,Tn;(e=>{function t(){return wn}e.value=t;function n(){return wn.length===2?wn===`en`:wn.length>=3&&wn[0]===`e`&&wn[1]===`n`&&wn[2]===`-`}e.isDefaultVariant=n;function r(){return wn===`en`}e.isDefault=r})(Tn||={});var En=typeof gn.postMessage==`function`&&!gn.importScripts;(()=>{if(En){let e=[];gn.addEventListener(`message`,t=>{if(t.data&&t.data.vscodeScheduleAsyncWork)for(let n=0,r=e.length;n{let r=++t;e.push({id:r,callback:n}),gn.postMessage({vscodeScheduleAsyncWork:r},`*`)}}return e=>setTimeout(e)})();var Dn=!!(Cn&&Cn.indexOf(`Chrome`)>=0);Cn&&Cn.indexOf(`Firefox`),!Dn&&Cn&&Cn.indexOf(`Safari`),Cn&&Cn.indexOf(`Edg/`),Cn&&Cn.indexOf(`Android`);var On=typeof navigator==`object`?navigator:{};Sn||document.queryCommandSupported&&document.queryCommandSupported(`copy`)||On&&On.clipboard&&On.clipboard.writeText,Sn||On&&On.clipboard&&On.clipboard.readText,Sn||sn()||On.keyboard,`ontouchstart`in Mt||On.maxTouchPoints,Mt.PointerEvent&&(`ontouchstart`in Mt||navigator.maxTouchPoints);var kn=class{constructor(){this._keyCodeToStr=[],this._strToKeyCode=Object.create(null)}define(e,t){this._keyCodeToStr[e]=t,this._strToKeyCode[t.toLowerCase()]=e}keyCodeToStr(e){return this._keyCodeToStr[e]}strToKeyCode(e){return this._strToKeyCode[e.toLowerCase()]||0}},An=new kn,jn=new kn,Mn=new kn,Nn=Array(230),Pn;(e=>{function t(e){return An.keyCodeToStr(e)}e.toString=t;function n(e){return An.strToKeyCode(e)}e.fromString=n;function r(e){return jn.keyCodeToStr(e)}e.toUserSettingsUS=r;function i(e){return Mn.keyCodeToStr(e)}e.toUserSettingsGeneral=i;function a(e){return jn.strToKeyCode(e)||Mn.strToKeyCode(e)}e.fromUserSettings=a;function o(e){if(e>=98&&e<=113)return null;switch(e){case 16:return`Up`;case 18:return`Down`;case 15:return`Left`;case 17:return`Right`}return An.keyCodeToStr(e)}e.toElectronAccelerator=o})(Pn||={});var Fn=class e{constructor(e,t,n,r,i){this.ctrlKey=e,this.shiftKey=t,this.altKey=n,this.metaKey=r,this.keyCode=i}equals(t){return t instanceof e&&this.ctrlKey===t.ctrlKey&&this.shiftKey===t.shiftKey&&this.altKey===t.altKey&&this.metaKey===t.metaKey&&this.keyCode===t.keyCode}getHashCode(){return`K${this.ctrlKey?`1`:`0`}${this.shiftKey?`1`:`0`}${this.altKey?`1`:`0`}${this.metaKey?`1`:`0`}${this.keyCode}`}isModifierKey(){return this.keyCode===0||this.keyCode===5||this.keyCode===57||this.keyCode===6||this.keyCode===4}toKeybinding(){return new In([this])}isDuplicateModifierCase(){return this.ctrlKey&&this.keyCode===5||this.shiftKey&&this.keyCode===4||this.altKey&&this.keyCode===6||this.metaKey&&this.keyCode===57}},In=class{constructor(e){if(e.length===0)throw st(`chords`);this.chords=e}getHashCode(){let e=``;for(let t=0,n=this.chords.length;t{function t(t){return t===e.None||t===e.Cancelled||t instanceof Xn?!0:!t||typeof t!=`object`?!1:typeof t.isCancellationRequested==`boolean`&&typeof t.onCancellationRequested==`function`}e.isCancellationToken=t,e.None=Object.freeze({isCancellationRequested:!1,onCancellationRequested:M.None}),e.Cancelled=Object.freeze({isCancellationRequested:!0,onCancellationRequested:Jn})})(Yn||={});var Xn=class{constructor(){this._isCancelled=!1,this._emitter=null}cancel(){this._isCancelled||(this._isCancelled=!0,this._emitter&&(this._emitter.fire(void 0),this.dispose()))}get isCancellationRequested(){return this._isCancelled}get onCancellationRequested(){return this._isCancelled?Jn:(this._emitter||=new N,this._emitter.event)}dispose(){this._emitter&&=(this._emitter.dispose(),null)}},Zn=class{constructor(e,t){this._isDisposed=!1,this._token=-1,typeof e==`function`&&typeof t==`number`&&this.setIfNotSet(e,t)}dispose(){this.cancel(),this._isDisposed=!0}cancel(){this._token!==-1&&(clearTimeout(this._token),this._token=-1)}cancelAndSet(e,t){if(this._isDisposed)throw new lt(`Calling 'cancelAndSet' on a disposed TimeoutTimer`);this.cancel(),this._token=setTimeout(()=>{this._token=-1,e()},t)}setIfNotSet(e,t){if(this._isDisposed)throw new lt(`Calling 'setIfNotSet' on a disposed TimeoutTimer`);this._token===-1&&(this._token=setTimeout(()=>{this._token=-1,e()},t))}},Qn=class{constructor(){this.disposable=void 0,this.isDisposed=!1}cancel(){this.disposable?.dispose(),this.disposable=void 0}cancelAndSet(e,t,n=globalThis){if(this.isDisposed)throw new lt(`Calling 'cancelAndSet' on a disposed IntervalTimer`);this.cancel();let r=n.setInterval(()=>{e()},t);this.disposable=k(()=>{n.clearInterval(r),this.disposable=void 0})}dispose(){this.cancel(),this.isDisposed=!0}};(function(){typeof globalThis.requestIdleCallback!=`function`||globalThis.cancelIdleCallback})();var $n;(e=>{async function t(e){let t,n=await Promise.all(e.map(e=>e.then(e=>e,e=>{t||=e})));if(typeof t<`u`)throw t;return n}e.settled=t;function n(e){return new Promise(async(t,n)=>{try{await e(t,n)}catch(e){n(e)}})}e.withAsyncBody=n})($n||={});var er=class e{static fromArray(t){return new e(e=>{e.emitMany(t)})}static fromPromise(t){return new e(async e=>{e.emitMany(await t)})}static fromPromises(t){return new e(async e=>{await Promise.all(t.map(async t=>e.emitOne(await t)))})}static merge(t){return new e(async e=>{await Promise.all(t.map(async t=>{for await(let n of t)e.emitOne(n)}))})}constructor(e,t){this._state=0,this._results=[],this._error=null,this._onReturn=t,this._onStateChanged=new N,queueMicrotask(async()=>{let t={emitOne:e=>this.emitOne(e),emitMany:e=>this.emitMany(e),reject:e=>this.reject(e)};try{await Promise.resolve(e(t)),this.resolve()}catch(e){this.reject(e)}finally{t.emitOne=void 0,t.emitMany=void 0,t.reject=void 0}})}[Symbol.asyncIterator](){let e=0;return{next:async()=>{do{if(this._state===2)throw this._error;if(e(this._onReturn?.(),{done:!0,value:void 0})}}static map(t,n){return new e(async e=>{for await(let r of t)e.emitOne(n(r))})}map(t){return e.map(this,t)}static filter(t,n){return new e(async e=>{for await(let r of t)n(r)&&e.emitOne(r)})}filter(t){return e.filter(this,t)}static coalesce(t){return e.filter(t,e=>!!e)}coalesce(){return e.coalesce(this)}static async toPromise(e){let t=[];for await(let n of e)t.push(n);return t}toPromise(){return e.toPromise(this)}emitOne(e){this._state===0&&(this._results.push(e),this._onStateChanged.fire())}emitMany(e){this._state===0&&(this._results=this._results.concat(e),this._onStateChanged.fire())}resolve(){this._state===0&&(this._state=1,this._onStateChanged.fire())}reject(e){this._state===0&&(this._state=2,this._error=e,this._onStateChanged.fire())}};er.EMPTY=er.fromArray([]);function tr(e){return 55296<=e&&e<=56319}function nr(e){return 56320<=e&&e<=57343}function rr(e,t){return(e-55296<<10)+(t-56320)+65536}function ir(e){return ar(e,0)}function ar(e,t){switch(typeof e){case`object`:return e===null?or(349,t):Array.isArray(e)?lr(e,t):ur(e,t);case`string`:return cr(e,t);case`boolean`:return sr(e,t);case`number`:return or(e,t);case`undefined`:return or(937,t);default:return or(617,t)}}function or(e,t){return(t<<5)-t+e|0}function sr(e,t){return or(e?433:863,t)}function cr(e,t){t=or(149417,t);for(let n=0,r=e.length;nar(t,e),t)}function ur(e,t){return t=or(181387,t),Object.keys(e).sort().reduce((t,n)=>(t=cr(n,t),ar(e[n],t)),t)}function dr(e,t,n=32){let r=n-t,i=~((1<>>r)>>>0}function fr(e,t=0,n=e.byteLength,r=0){for(let i=0;ie.toString(16).padStart(2,`0`)).join(``):pr((e>>>0).toString(16),t/4)}var hr=class e{constructor(){this._h0=1732584193,this._h1=4023233417,this._h2=2562383102,this._h3=271733878,this._h4=3285377520,this._buff=new Uint8Array(67),this._buffDV=new DataView(this._buff.buffer),this._buffLen=0,this._totalLen=0,this._leftoverHighSurrogate=0,this._finished=!1}update(e){let t=e.length;if(t===0)return;let n=this._buff,r=this._buffLen,i=this._leftoverHighSurrogate,a,o;for(i===0?(a=e.charCodeAt(0),o=0):(a=i,o=-1,i=0);;){let s=a;if(tr(a))if(o+1>>6,e[t++]=128|(n&63)>>>0):n<65536?(e[t++]=224|(n&61440)>>>12,e[t++]=128|(n&4032)>>>6,e[t++]=128|(n&63)>>>0):(e[t++]=240|(n&1835008)>>>18,e[t++]=128|(n&258048)>>>12,e[t++]=128|(n&4032)>>>6,e[t++]=128|(n&63)>>>0),t>=64&&(this._step(),t-=64,this._totalLen+=64,e[0]=e[64],e[1]=e[65],e[2]=e[66]),t}digest(){return this._finished||(this._finished=!0,this._leftoverHighSurrogate&&(this._leftoverHighSurrogate=0,this._buffLen=this._push(this._buff,this._buffLen,65533)),this._totalLen+=this._buffLen,this._wrapUp()),mr(this._h0)+mr(this._h1)+mr(this._h2)+mr(this._h3)+mr(this._h4)}_wrapUp(){this._buff[this._buffLen++]=128,fr(this._buff,this._buffLen),this._buffLen>56&&(this._step(),fr(this._buff));let e=8*this._totalLen;this._buffDV.setUint32(56,Math.floor(e/4294967296),!1),this._buffDV.setUint32(60,e%4294967296,!1),this._step()}_step(){let t=e._bigBlock32,n=this._buffDV;for(let e=0;e<64;e+=4)t.setUint32(e,n.getUint32(e,!1),!1);for(let e=64;e<320;e+=4)t.setUint32(e,dr(t.getUint32(e-12,!1)^t.getUint32(e-32,!1)^t.getUint32(e-56,!1)^t.getUint32(e-64,!1),1),!1);let r=this._h0,i=this._h1,a=this._h2,o=this._h3,s=this._h4,c,l,u;for(let e=0;e<80;e++)e<20?(c=i&a|~i&o,l=1518500249):e<40?(c=i^a^o,l=1859775393):e<60?(c=i&a|i&o|a&o,l=2400959708):(c=i^a^o,l=3395469782),u=dr(r,5)+c+s+l+t.getUint32(e*4,!1)&4294967295,s=o,o=a,a=dr(i,30),i=r,r=u;this._h0=this._h0+r&4294967295,this._h1=this._h1+i&4294967295,this._h2=this._h2+a&4294967295,this._h3=this._h3+o&4294967295,this._h4=this._h4+s&4294967295}};hr._bigBlock32=new DataView(new ArrayBuffer(320));var{registerWindow:gr,getWindow:_r,getDocument:vr,getWindows:yr,getWindowsCount:br,getWindowId:xr,getWindowById:Sr,hasWindow:Cr,onDidRegisterWindow:wr,onWillUnregisterWindow:Tr,onDidUnregisterWindow:Er}=function(){let e=new Map,t={window:Mt,disposables:new At};e.set(Mt.vscodeWindowId,t);let n=new N,r=new N,i=new N;function a(n,r){return(typeof n==`number`?e.get(n):void 0)??(r?t:void 0)}return{onDidRegisterWindow:n.event,onWillUnregisterWindow:i.event,onDidUnregisterWindow:r.event,registerWindow(t){if(e.has(t.vscodeWindowId))return A.None;let a=new At,o={window:t,disposables:a.add(new At)};return e.set(t.vscodeWindowId,o),a.add(k(()=>{e.delete(t.vscodeWindowId),r.fire(t)})),a.add(P(t,F.BEFORE_UNLOAD,()=>{i.fire(t)})),n.fire(o),a},getWindows(){return e.values()},getWindowsCount(){return e.size},getWindowId(e){return e.vscodeWindowId},hasWindow(t){return e.has(t)},getWindowById:a,getWindow(e){let t=e;if(t?.ownerDocument?.defaultView)return t.ownerDocument.defaultView.window;let n=e;return n?.view?n.view.window:Mt},getDocument(e){return _r(e).document}}}(),Dr=class{constructor(e,t,n,r){this._node=e,this._type=t,this._handler=n,this._options=r||!1,this._node.addEventListener(this._type,this._handler,this._options)}dispose(){this._handler&&=(this._node.removeEventListener(this._type,this._handler,this._options),this._node=null,null)}};function P(e,t,n,r){return new Dr(e,t,n,r)}function Or(e,t){return function(n){return t(new Kn(e,n))}}function kr(e){return function(t){return e(new Hn(t))}}var Ar=function(e,t,n,r){let i=n;return t===`click`||t===`mousedown`||t===`contextmenu`?i=Or(_r(e),n):(t===`keydown`||t===`keypress`||t===`keyup`)&&(i=kr(n)),P(e,t,i,r)},jr,Mr=class extends Qn{constructor(e){super(),this.defaultTarget=e&&_r(e)}cancelAndSet(e,t,n){return super.cancelAndSet(e,t,n??this.defaultTarget)}},Nr=class{constructor(e,t=0){this._runner=e,this.priority=t,this._canceled=!1}dispose(){this._canceled=!0}execute(){if(!this._canceled)try{this._runner()}catch(e){rt(e)}}static sort(e,t){return t.priority-e.priority}};(function(){let e=new Map,t=new Map,n=new Map,r=new Map,i=i=>{n.set(i,!1);let a=e.get(i)??[];for(t.set(i,a),e.set(i,[]),r.set(i,!0);a.length>0;)a.sort(Nr.sort),a.shift().execute();r.set(i,!1)};jr=(t,r,a=0)=>{let o=xr(t),s=new Nr(r,a),c=e.get(o);return c||(c=[],e.set(o,c)),c.push(s),n.get(o)||(n.set(o,!0),t.requestAnimationFrame(()=>i(o))),s}})();var Pr=class e{constructor(e,t){this.width=e,this.height=t}with(t=this.width,n=this.height){return t!==this.width||n!==this.height?new e(t,n):this}static is(e){return typeof e==`object`&&typeof e.height==`number`&&typeof e.width==`number`}static lift(t){return t instanceof e?t:new e(t.width,t.height)}static equals(e,t){return e===t?!0:!e||!t?!1:e.width===t.width&&e.height===t.height}};Pr.None=new Pr(0,0);function Fr(e){let t=e.getBoundingClientRect(),n=_r(e);return{left:t.left+n.scrollX,top:t.top+n.scrollY,width:t.width,height:t.height}}new class{constructor(){this.mutationObservers=new Map}observe(e,t,n){let r=this.mutationObservers.get(e);r||(r=new Map,this.mutationObservers.set(e,r));let i=ir(n),a=r.get(i);if(a)a.users+=1;else{let o=new N,s=new MutationObserver(e=>o.fire(e));s.observe(e,n);let c=a={users:1,observer:s,onDidMutate:o.event};t.add(k(()=>{--c.users,c.users===0&&(o.dispose(),s.disconnect(),r?.delete(i),r?.size===0&&this.mutationObservers.delete(e))})),r.set(i,a)}return a.onDidMutate}};var F={CLICK:`click`,AUXCLICK:`auxclick`,DBLCLICK:`dblclick`,MOUSE_UP:`mouseup`,MOUSE_DOWN:`mousedown`,MOUSE_OVER:`mouseover`,MOUSE_MOVE:`mousemove`,MOUSE_OUT:`mouseout`,MOUSE_ENTER:`mouseenter`,MOUSE_LEAVE:`mouseleave`,MOUSE_WHEEL:`wheel`,POINTER_UP:`pointerup`,POINTER_DOWN:`pointerdown`,POINTER_MOVE:`pointermove`,POINTER_LEAVE:`pointerleave`,CONTEXT_MENU:`contextmenu`,WHEEL:`wheel`,KEY_DOWN:`keydown`,KEY_PRESS:`keypress`,KEY_UP:`keyup`,LOAD:`load`,BEFORE_UNLOAD:`beforeunload`,UNLOAD:`unload`,PAGE_SHOW:`pageshow`,PAGE_HIDE:`pagehide`,PASTE:`paste`,ABORT:`abort`,ERROR:`error`,RESIZE:`resize`,SCROLL:`scroll`,FULLSCREEN_CHANGE:`fullscreenchange`,WK_FULLSCREEN_CHANGE:`webkitfullscreenchange`,SELECT:`select`,CHANGE:`change`,SUBMIT:`submit`,RESET:`reset`,FOCUS:`focus`,FOCUS_IN:`focusin`,FOCUS_OUT:`focusout`,BLUR:`blur`,INPUT:`input`,STORAGE:`storage`,DRAG_START:`dragstart`,DRAG:`drag`,DRAG_ENTER:`dragenter`,DRAG_LEAVE:`dragleave`,DRAG_OVER:`dragover`,DROP:`drop`,DRAG_END:`dragend`,ANIMATION_START:nn?`webkitAnimationStart`:`animationstart`,ANIMATION_END:nn?`webkitAnimationEnd`:`animationend`,ANIMATION_ITERATION:nn?`webkitAnimationIteration`:`animationiteration`},Ir=/([\w\-]+)?(#([\w\-]+))?((\.([\w\-]+))*)/;function Lr(e,t,n,...r){let i=Ir.exec(t);if(!i)throw Error(`Bad use of emmet`);let a=i[1]||`div`,o;return o=e===`http://www.w3.org/1999/xhtml`?document.createElement(a):document.createElementNS(e,a),i[3]&&(o.id=i[3]),i[4]&&(o.className=i[4].replace(/\./g,` `).trim()),n&&Object.entries(n).forEach(([e,t])=>{typeof t>`u`||(/^on\w+$/.test(e)?o[e]=t:e===`selected`?t&&o.setAttribute(e,`true`):o.setAttribute(e,t))}),o.append(...r),o}function Rr(e,t,...n){return Lr(`http://www.w3.org/1999/xhtml`,e,t,...n)}Rr.SVG=function(e,t,...n){return Lr(`http://www.w3.org/2000/svg`,e,t,...n)};var zr=class{constructor(e){this.domNode=e,this._maxWidth=``,this._width=``,this._height=``,this._top=``,this._left=``,this._bottom=``,this._right=``,this._paddingTop=``,this._paddingLeft=``,this._paddingBottom=``,this._paddingRight=``,this._fontFamily=``,this._fontWeight=``,this._fontSize=``,this._fontStyle=``,this._fontFeatureSettings=``,this._fontVariationSettings=``,this._textDecoration=``,this._lineHeight=``,this._letterSpacing=``,this._className=``,this._display=``,this._position=``,this._visibility=``,this._color=``,this._backgroundColor=``,this._layerHint=!1,this._contain=`none`,this._boxShadow=``}setMaxWidth(e){let t=I(e);this._maxWidth!==t&&(this._maxWidth=t,this.domNode.style.maxWidth=this._maxWidth)}setWidth(e){let t=I(e);this._width!==t&&(this._width=t,this.domNode.style.width=this._width)}setHeight(e){let t=I(e);this._height!==t&&(this._height=t,this.domNode.style.height=this._height)}setTop(e){let t=I(e);this._top!==t&&(this._top=t,this.domNode.style.top=this._top)}setLeft(e){let t=I(e);this._left!==t&&(this._left=t,this.domNode.style.left=this._left)}setBottom(e){let t=I(e);this._bottom!==t&&(this._bottom=t,this.domNode.style.bottom=this._bottom)}setRight(e){let t=I(e);this._right!==t&&(this._right=t,this.domNode.style.right=this._right)}setPaddingTop(e){let t=I(e);this._paddingTop!==t&&(this._paddingTop=t,this.domNode.style.paddingTop=this._paddingTop)}setPaddingLeft(e){let t=I(e);this._paddingLeft!==t&&(this._paddingLeft=t,this.domNode.style.paddingLeft=this._paddingLeft)}setPaddingBottom(e){let t=I(e);this._paddingBottom!==t&&(this._paddingBottom=t,this.domNode.style.paddingBottom=this._paddingBottom)}setPaddingRight(e){let t=I(e);this._paddingRight!==t&&(this._paddingRight=t,this.domNode.style.paddingRight=this._paddingRight)}setFontFamily(e){this._fontFamily!==e&&(this._fontFamily=e,this.domNode.style.fontFamily=this._fontFamily)}setFontWeight(e){this._fontWeight!==e&&(this._fontWeight=e,this.domNode.style.fontWeight=this._fontWeight)}setFontSize(e){let t=I(e);this._fontSize!==t&&(this._fontSize=t,this.domNode.style.fontSize=this._fontSize)}setFontStyle(e){this._fontStyle!==e&&(this._fontStyle=e,this.domNode.style.fontStyle=this._fontStyle)}setFontFeatureSettings(e){this._fontFeatureSettings!==e&&(this._fontFeatureSettings=e,this.domNode.style.fontFeatureSettings=this._fontFeatureSettings)}setFontVariationSettings(e){this._fontVariationSettings!==e&&(this._fontVariationSettings=e,this.domNode.style.fontVariationSettings=this._fontVariationSettings)}setTextDecoration(e){this._textDecoration!==e&&(this._textDecoration=e,this.domNode.style.textDecoration=this._textDecoration)}setLineHeight(e){let t=I(e);this._lineHeight!==t&&(this._lineHeight=t,this.domNode.style.lineHeight=this._lineHeight)}setLetterSpacing(e){let t=I(e);this._letterSpacing!==t&&(this._letterSpacing=t,this.domNode.style.letterSpacing=this._letterSpacing)}setClassName(e){this._className!==e&&(this._className=e,this.domNode.className=this._className)}toggleClassName(e,t){this.domNode.classList.toggle(e,t),this._className=this.domNode.className}setDisplay(e){this._display!==e&&(this._display=e,this.domNode.style.display=this._display)}setPosition(e){this._position!==e&&(this._position=e,this.domNode.style.position=this._position)}setVisibility(e){this._visibility!==e&&(this._visibility=e,this.domNode.style.visibility=this._visibility)}setColor(e){this._color!==e&&(this._color=e,this.domNode.style.color=this._color)}setBackgroundColor(e){this._backgroundColor!==e&&(this._backgroundColor=e,this.domNode.style.backgroundColor=this._backgroundColor)}setLayerHinting(e){this._layerHint!==e&&(this._layerHint=e,this.domNode.style.transform=this._layerHint?`translate3d(0px, 0px, 0px)`:``)}setBoxShadow(e){this._boxShadow!==e&&(this._boxShadow=e,this.domNode.style.boxShadow=e)}setContain(e){this._contain!==e&&(this._contain=e,this.domNode.style.contain=this._contain)}setAttribute(e,t){this.domNode.setAttribute(e,t)}removeAttribute(e){this.domNode.removeAttribute(e)}appendChild(e){this.domNode.appendChild(e.domNode)}removeChild(e){this.domNode.removeChild(e.domNode)}};function I(e){return typeof e==`number`?`${e}px`:e}function Br(e){return new zr(e)}var Vr=class{constructor(){this._hooks=new At,this._pointerMoveCallback=null,this._onStopCallback=null}dispose(){this.stopMonitoring(!1),this._hooks.dispose()}stopMonitoring(e,t){if(!this.isMonitoring())return;this._hooks.clear(),this._pointerMoveCallback=null;let n=this._onStopCallback;this._onStopCallback=null,e&&n&&n(t)}isMonitoring(){return!!this._pointerMoveCallback}startMonitoring(e,t,n,r,i){this.isMonitoring()&&this.stopMonitoring(!1),this._pointerMoveCallback=r,this._onStopCallback=i;let a=e;try{e.setPointerCapture(t),this._hooks.add(k(()=>{try{e.releasePointerCapture(t)}catch{}}))}catch{a=_r(e)}this._hooks.add(P(a,F.POINTER_MOVE,e=>{if(e.buttons!==n){this.stopMonitoring(!0);return}e.preventDefault(),this._pointerMoveCallback(e)})),this._hooks.add(P(a,F.POINTER_UP,e=>this.stopMonitoring(!0)))}};function Hr(e,t,n){let r=null,i=null;if(typeof n.value==`function`?(r=`value`,i=n.value,i.length!==0&&console.warn(`Memoize should only be used in functions with zero parameters`)):typeof n.get==`function`&&(r=`get`,i=n.get),!i)throw Error(`not supported`);let a=`$memoize$${t}`;n[r]=function(...e){return this.hasOwnProperty(a)||Object.defineProperty(this,a,{configurable:!1,enumerable:!1,writable:!1,value:i.apply(this,e)}),this[a]}}var Ur;(e=>(e.Tap=`-xterm-gesturetap`,e.Change=`-xterm-gesturechange`,e.Start=`-xterm-gesturestart`,e.End=`-xterm-gesturesend`,e.Contextmenu=`-xterm-gesturecontextmenu`))(Ur||={});var Wr=class e extends A{constructor(){super(),this.dispatched=!1,this.targets=new Pt,this.ignoreTargets=new Pt,this.activeTouches={},this.handle=null,this._lastSetTapCountTime=0,this._register(M.runAndSubscribe(wr,({window:e,disposables:t})=>{t.add(P(e.document,`touchstart`,e=>this.onTouchStart(e),{passive:!1})),t.add(P(e.document,`touchend`,t=>this.onTouchEnd(e,t))),t.add(P(e.document,`touchmove`,e=>this.onTouchMove(e),{passive:!1}))},{window:Mt,disposables:this._store}))}static addTarget(t){return e.isTouchDevice()?(e.INSTANCE||=Et(new e),k(e.INSTANCE.targets.push(t))):A.None}static ignoreTarget(t){return e.isTouchDevice()?(e.INSTANCE||=Et(new e),k(e.INSTANCE.ignoreTargets.push(t))):A.None}static isTouchDevice(){return`ontouchstart`in Mt||navigator.maxTouchPoints>0}dispose(){this.handle&&=(this.handle.dispose(),null),super.dispose()}onTouchStart(e){let t=Date.now();this.handle&&=(this.handle.dispose(),null);for(let n=0,r=e.targetTouches.length;n=e.HOLD_DELAY&&Math.abs(s.initialPageX-ft(s.rollingPageX))<30&&Math.abs(s.initialPageY-ft(s.rollingPageY))<30){let e=this.newGestureEvent(Ur.Contextmenu,s.initialTarget);e.pageX=ft(s.rollingPageX),e.pageY=ft(s.rollingPageY),this.dispatchEvent(e)}else if(i===1){let e=ft(s.rollingPageX),n=ft(s.rollingPageY),i=ft(s.rollingTimestamps)-s.rollingTimestamps[0],a=e-s.rollingPageX[0],o=n-s.rollingPageY[0],c=[...this.targets].filter(e=>s.initialTarget instanceof Node&&e.contains(s.initialTarget));this.inertia(t,c,r,Math.abs(a)/i,a>0?1:-1,e,Math.abs(o)/i,o>0?1:-1,n)}this.dispatchEvent(this.newGestureEvent(Ur.End,s.initialTarget)),delete this.activeTouches[o.identifier]}this.dispatched&&=(n.preventDefault(),n.stopPropagation(),!1)}newGestureEvent(e,t){let n=document.createEvent(`CustomEvent`);return n.initEvent(e,!1,!0),n.initialTarget=t,n.tapCount=0,n}dispatchEvent(t){if(t.type===Ur.Tap){let n=new Date().getTime(),r=0;r=n-this._lastSetTapCountTime>e.CLEAR_TAP_COUNT_TIME?1:2,this._lastSetTapCountTime=n,t.tapCount=r}else(t.type===Ur.Change||t.type===Ur.Contextmenu)&&(this._lastSetTapCountTime=0);if(t.initialTarget instanceof Node){for(let e of this.ignoreTargets)if(e.contains(t.initialTarget))return;let e=[];for(let n of this.targets)if(n.contains(t.initialTarget)){let r=0,i=t.initialTarget;for(;i&&i!==n;)r++,i=i.parentElement;e.push([r,n])}e.sort((e,t)=>e[0]-t[0]);for(let[n,r]of e)r.dispatchEvent(t),this.dispatched=!0}}inertia(t,n,r,i,a,o,s,c,l){this.handle=jr(t,()=>{let u=Date.now(),d=u-r,f=0,p=0,m=!0;i+=e.SCROLL_FRICTION*d,s+=e.SCROLL_FRICTION*d,i>0&&(m=!1,f=a*i*d),s>0&&(m=!1,p=c*s*d);let h=this.newGestureEvent(Ur.Change);h.translationX=f,h.translationY=p,n.forEach(e=>e.dispatchEvent(h)),m||this.inertia(t,n,u,i,a,o+f,s,c,l+p)})}onTouchMove(e){let t=Date.now();for(let n=0,r=e.changedTouches.length;n3&&(i.rollingPageX.shift(),i.rollingPageY.shift(),i.rollingTimestamps.shift()),i.rollingPageX.push(r.pageX),i.rollingPageY.push(r.pageY),i.rollingTimestamps.push(t)}this.dispatched&&=(e.preventDefault(),e.stopPropagation(),!1)}};Wr.SCROLL_FRICTION=-.005,Wr.HOLD_DELAY=700,Wr.CLEAR_TAP_COUNT_TIME=400,C([Hr],Wr,`isTouchDevice`,1);var Gr=Wr,Kr=class extends A{onclick(e,t){this._register(P(e,F.CLICK,n=>t(new Kn(_r(e),n))))}onmousedown(e,t){this._register(P(e,F.MOUSE_DOWN,n=>t(new Kn(_r(e),n))))}onmouseover(e,t){this._register(P(e,F.MOUSE_OVER,n=>t(new Kn(_r(e),n))))}onmouseleave(e,t){this._register(P(e,F.MOUSE_LEAVE,n=>t(new Kn(_r(e),n))))}onkeydown(e,t){this._register(P(e,F.KEY_DOWN,e=>t(new Hn(e))))}onkeyup(e,t){this._register(P(e,F.KEY_UP,e=>t(new Hn(e))))}oninput(e,t){this._register(P(e,F.INPUT,t))}onblur(e,t){this._register(P(e,F.BLUR,t))}onfocus(e,t){this._register(P(e,F.FOCUS,t))}onchange(e,t){this._register(P(e,F.CHANGE,t))}ignoreGesture(e){return Gr.ignoreTarget(e)}},qr=11,Jr=class extends Kr{constructor(e){super(),this._onActivate=e.onActivate,this.bgDomNode=document.createElement(`div`),this.bgDomNode.className=`arrow-background`,this.bgDomNode.style.position=`absolute`,this.bgDomNode.style.width=e.bgWidth+`px`,this.bgDomNode.style.height=e.bgHeight+`px`,typeof e.top<`u`&&(this.bgDomNode.style.top=`0px`),typeof e.left<`u`&&(this.bgDomNode.style.left=`0px`),typeof e.bottom<`u`&&(this.bgDomNode.style.bottom=`0px`),typeof e.right<`u`&&(this.bgDomNode.style.right=`0px`),this.domNode=document.createElement(`div`),this.domNode.className=e.className,this.domNode.style.position=`absolute`,this.domNode.style.width=qr+`px`,this.domNode.style.height=qr+`px`,typeof e.top<`u`&&(this.domNode.style.top=e.top+`px`),typeof e.left<`u`&&(this.domNode.style.left=e.left+`px`),typeof e.bottom<`u`&&(this.domNode.style.bottom=e.bottom+`px`),typeof e.right<`u`&&(this.domNode.style.right=e.right+`px`),this._pointerMoveMonitor=this._register(new Vr),this._register(Ar(this.bgDomNode,F.POINTER_DOWN,e=>this._arrowPointerDown(e))),this._register(Ar(this.domNode,F.POINTER_DOWN,e=>this._arrowPointerDown(e))),this._pointerdownRepeatTimer=this._register(new Mr),this._pointerdownScheduleRepeatTimer=this._register(new Zn)}_arrowPointerDown(e){!e.target||!(e.target instanceof Element)||(this._onActivate(),this._pointerdownRepeatTimer.cancel(),this._pointerdownScheduleRepeatTimer.cancelAndSet(()=>{this._pointerdownRepeatTimer.cancelAndSet(()=>this._onActivate(),1e3/24,_r(e))},200),this._pointerMoveMonitor.startMonitoring(e.target,e.pointerId,e.buttons,e=>{},()=>{this._pointerdownRepeatTimer.cancel(),this._pointerdownScheduleRepeatTimer.cancel()}),e.preventDefault())}},Yr=class e{constructor(e,t,n,r,i,a,o){this._forceIntegerValues=e,this._scrollStateBrand=void 0,this._forceIntegerValues&&(t|=0,n|=0,r|=0,i|=0,a|=0,o|=0),this.rawScrollLeft=r,this.rawScrollTop=o,t<0&&(t=0),r+t>n&&(r=n-t),r<0&&(r=0),i<0&&(i=0),o+i>a&&(o=a-i),o<0&&(o=0),this.width=t,this.scrollWidth=n,this.scrollLeft=r,this.height=i,this.scrollHeight=a,this.scrollTop=o}equals(e){return this.rawScrollLeft===e.rawScrollLeft&&this.rawScrollTop===e.rawScrollTop&&this.width===e.width&&this.scrollWidth===e.scrollWidth&&this.scrollLeft===e.scrollLeft&&this.height===e.height&&this.scrollHeight===e.scrollHeight&&this.scrollTop===e.scrollTop}withScrollDimensions(t,n){return new e(this._forceIntegerValues,typeof t.width<`u`?t.width:this.width,typeof t.scrollWidth<`u`?t.scrollWidth:this.scrollWidth,n?this.rawScrollLeft:this.scrollLeft,typeof t.height<`u`?t.height:this.height,typeof t.scrollHeight<`u`?t.scrollHeight:this.scrollHeight,n?this.rawScrollTop:this.scrollTop)}withScrollPosition(t){return new e(this._forceIntegerValues,this.width,this.scrollWidth,typeof t.scrollLeft<`u`?t.scrollLeft:this.rawScrollLeft,this.height,this.scrollHeight,typeof t.scrollTop<`u`?t.scrollTop:this.rawScrollTop)}createScrollEvent(e,t){let n=this.width!==e.width,r=this.scrollWidth!==e.scrollWidth,i=this.scrollLeft!==e.scrollLeft,a=this.height!==e.height,o=this.scrollHeight!==e.scrollHeight,s=this.scrollTop!==e.scrollTop;return{inSmoothScrolling:t,oldWidth:e.width,oldScrollWidth:e.scrollWidth,oldScrollLeft:e.scrollLeft,width:this.width,scrollWidth:this.scrollWidth,scrollLeft:this.scrollLeft,oldHeight:e.height,oldScrollHeight:e.scrollHeight,oldScrollTop:e.scrollTop,height:this.height,scrollHeight:this.scrollHeight,scrollTop:this.scrollTop,widthChanged:n,scrollWidthChanged:r,scrollLeftChanged:i,heightChanged:a,scrollHeightChanged:o,scrollTopChanged:s}}},Xr=class extends A{constructor(e){super(),this._scrollableBrand=void 0,this._onScroll=this._register(new N),this.onScroll=this._onScroll.event,this._smoothScrollDuration=e.smoothScrollDuration,this._scheduleAtNextAnimationFrame=e.scheduleAtNextAnimationFrame,this._state=new Yr(e.forceIntegerValues,0,0,0,0,0,0),this._smoothScrolling=null}dispose(){this._smoothScrolling&&=(this._smoothScrolling.dispose(),null),super.dispose()}setSmoothScrollDuration(e){this._smoothScrollDuration=e}validateScrollPosition(e){return this._state.withScrollPosition(e)}getScrollDimensions(){return this._state}setScrollDimensions(e,t){let n=this._state.withScrollDimensions(e,t);this._setState(n,!!this._smoothScrolling),this._smoothScrolling?.acceptScrollDimensions(this._state)}getFutureScrollPosition(){return this._smoothScrolling?this._smoothScrolling.to:this._state}getCurrentScrollPosition(){return this._state}setScrollPositionNow(e){let t=this._state.withScrollPosition(e);this._smoothScrolling&&=(this._smoothScrolling.dispose(),null),this._setState(t,!1)}setScrollPositionSmooth(e,t){if(this._smoothScrollDuration===0)return this.setScrollPositionNow(e);if(this._smoothScrolling){e={scrollLeft:typeof e.scrollLeft>`u`?this._smoothScrolling.to.scrollLeft:e.scrollLeft,scrollTop:typeof e.scrollTop>`u`?this._smoothScrolling.to.scrollTop:e.scrollTop};let n=this._state.withScrollPosition(e);if(this._smoothScrolling.to.scrollLeft===n.scrollLeft&&this._smoothScrolling.to.scrollTop===n.scrollTop)return;let r;r=t?new ei(this._smoothScrolling.from,n,this._smoothScrolling.startTime,this._smoothScrolling.duration):this._smoothScrolling.combine(this._state,n,this._smoothScrollDuration),this._smoothScrolling.dispose(),this._smoothScrolling=r}else{let t=this._state.withScrollPosition(e);this._smoothScrolling=ei.start(this._state,t,this._smoothScrollDuration)}this._smoothScrolling.animationFrameDisposable=this._scheduleAtNextAnimationFrame(()=>{this._smoothScrolling&&(this._smoothScrolling.animationFrameDisposable=null,this._performSmoothScrolling())})}hasPendingScrollAnimation(){return!!this._smoothScrolling}_performSmoothScrolling(){if(!this._smoothScrolling)return;let e=this._smoothScrolling.tick(),t=this._state.withScrollPosition(e);if(this._setState(t,!0),this._smoothScrolling){if(e.isDone){this._smoothScrolling.dispose(),this._smoothScrolling=null;return}this._smoothScrolling.animationFrameDisposable=this._scheduleAtNextAnimationFrame(()=>{this._smoothScrolling&&(this._smoothScrolling.animationFrameDisposable=null,this._performSmoothScrolling())})}}_setState(e,t){let n=this._state;n.equals(e)||(this._state=e,this._onScroll.fire(this._state.createScrollEvent(n,t)))}},Zr=class{constructor(e,t,n){this.scrollLeft=e,this.scrollTop=t,this.isDone=n}};function Qr(e,t){let n=t-e;return function(t){return e+n*ni(t)}}function $r(e,t,n){return function(r){return r2.5*n){let r,i;return e{this._domNode?.setClassName(this._visibleClassName)},0))}_hide(e){this._revealTimer.cancel(),this._isVisible&&(this._isVisible=!1,this._domNode?.setClassName(this._invisibleClassName+(e?` fade`:``)))}},ii=140,ai=class extends Kr{constructor(e){super(),this._lazyRender=e.lazyRender,this._host=e.host,this._scrollable=e.scrollable,this._scrollByPage=e.scrollByPage,this._scrollbarState=e.scrollbarState,this._visibilityController=this._register(new ri(e.visibility,`visible scrollbar `+e.extraScrollbarClassName,`invisible scrollbar `+e.extraScrollbarClassName)),this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._pointerMoveMonitor=this._register(new Vr),this._shouldRender=!0,this.domNode=Br(document.createElement(`div`)),this.domNode.setAttribute(`role`,`presentation`),this.domNode.setAttribute(`aria-hidden`,`true`),this._visibilityController.setDomNode(this.domNode),this.domNode.setPosition(`absolute`),this._register(P(this.domNode.domNode,F.POINTER_DOWN,e=>this._domNodePointerDown(e)))}_createArrow(e){let t=this._register(new Jr(e));this.domNode.domNode.appendChild(t.bgDomNode),this.domNode.domNode.appendChild(t.domNode)}_createSlider(e,t,n,r){this.slider=Br(document.createElement(`div`)),this.slider.setClassName(`slider`),this.slider.setPosition(`absolute`),this.slider.setTop(e),this.slider.setLeft(t),typeof n==`number`&&this.slider.setWidth(n),typeof r==`number`&&this.slider.setHeight(r),this.slider.setLayerHinting(!0),this.slider.setContain(`strict`),this.domNode.domNode.appendChild(this.slider.domNode),this._register(P(this.slider.domNode,F.POINTER_DOWN,e=>{e.button===0&&(e.preventDefault(),this._sliderPointerDown(e))})),this.onclick(this.slider.domNode,e=>{e.leftButton&&e.stopPropagation()})}_onElementSize(e){return this._scrollbarState.setVisibleSize(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}_onElementScrollSize(e){return this._scrollbarState.setScrollSize(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}_onElementScrollPosition(e){return this._scrollbarState.setScrollPosition(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}beginReveal(){this._visibilityController.setShouldBeVisible(!0)}beginHide(){this._visibilityController.setShouldBeVisible(!1)}render(){this._shouldRender&&(this._shouldRender=!1,this._renderDomNode(this._scrollbarState.getRectangleLargeSize(),this._scrollbarState.getRectangleSmallSize()),this._updateSlider(this._scrollbarState.getSliderSize(),this._scrollbarState.getArrowSize()+this._scrollbarState.getSliderPosition()))}_domNodePointerDown(e){e.target===this.domNode.domNode&&this._onPointerDown(e)}delegatePointerDown(e){let t=this.domNode.domNode.getClientRects()[0].top,n=t+this._scrollbarState.getSliderPosition(),r=t+this._scrollbarState.getSliderPosition()+this._scrollbarState.getSliderSize(),i=this._sliderPointerPosition(e);n<=i&&i<=r?e.button===0&&(e.preventDefault(),this._sliderPointerDown(e)):this._onPointerDown(e)}_onPointerDown(e){let t,n;if(e.target===this.domNode.domNode&&typeof e.offsetX==`number`&&typeof e.offsetY==`number`)t=e.offsetX,n=e.offsetY;else{let r=Fr(this.domNode.domNode);t=e.pageX-r.left,n=e.pageY-r.top}let r=this._pointerDownRelativePosition(t,n);this._setDesiredScrollPositionNow(this._scrollByPage?this._scrollbarState.getDesiredScrollPositionFromOffsetPaged(r):this._scrollbarState.getDesiredScrollPositionFromOffset(r)),e.button===0&&(e.preventDefault(),this._sliderPointerDown(e))}_sliderPointerDown(e){if(!e.target||!(e.target instanceof Element))return;let t=this._sliderPointerPosition(e),n=this._sliderOrthogonalPointerPosition(e),r=this._scrollbarState.clone();this.slider.toggleClassName(`active`,!0),this._pointerMoveMonitor.startMonitoring(e.target,e.pointerId,e.buttons,e=>{let i=this._sliderOrthogonalPointerPosition(e),a=Math.abs(i-n);if(yn&&a>ii){this._setDesiredScrollPositionNow(r.getScrollPosition());return}let o=this._sliderPointerPosition(e)-t;this._setDesiredScrollPositionNow(r.getDesiredScrollPositionFromDelta(o))},()=>{this.slider.toggleClassName(`active`,!1),this._host.onDragEnd()}),this._host.onDragStart()}_setDesiredScrollPositionNow(e){let t={};this.writeScrollPosition(t,e),this._scrollable.setScrollPositionNow(t)}updateScrollbarSize(e){this._updateScrollbarSize(e),this._scrollbarState.setScrollbarSize(e),this._shouldRender=!0,this._lazyRender||this.render()}isNeeded(){return this._scrollbarState.isNeeded()}},oi=class e{constructor(e,t,n,r,i,a){this._scrollbarSize=Math.round(t),this._oppositeScrollbarSize=Math.round(n),this._arrowSize=Math.round(e),this._visibleSize=r,this._scrollSize=i,this._scrollPosition=a,this._computedAvailableSize=0,this._computedIsNeeded=!1,this._computedSliderSize=0,this._computedSliderRatio=0,this._computedSliderPosition=0,this._refreshComputedValues()}clone(){return new e(this._arrowSize,this._scrollbarSize,this._oppositeScrollbarSize,this._visibleSize,this._scrollSize,this._scrollPosition)}setVisibleSize(e){let t=Math.round(e);return this._visibleSize===t?!1:(this._visibleSize=t,this._refreshComputedValues(),!0)}setScrollSize(e){let t=Math.round(e);return this._scrollSize===t?!1:(this._scrollSize=t,this._refreshComputedValues(),!0)}setScrollPosition(e){let t=Math.round(e);return this._scrollPosition===t?!1:(this._scrollPosition=t,this._refreshComputedValues(),!0)}setScrollbarSize(e){this._scrollbarSize=Math.round(e)}setOppositeScrollbarSize(e){this._oppositeScrollbarSize=Math.round(e)}static _computeValues(e,t,n,r,i){let a=Math.max(0,n-e),o=Math.max(0,a-2*t),s=r>0&&r>n;if(!s)return{computedAvailableSize:Math.round(a),computedIsNeeded:s,computedSliderSize:Math.round(o),computedSliderRatio:0,computedSliderPosition:0};let c=Math.round(Math.max(20,Math.floor(n*o/r))),l=(o-c)/(r-n),u=i*l;return{computedAvailableSize:Math.round(a),computedIsNeeded:s,computedSliderSize:Math.round(c),computedSliderRatio:l,computedSliderPosition:Math.round(u)}}_refreshComputedValues(){let t=e._computeValues(this._oppositeScrollbarSize,this._arrowSize,this._visibleSize,this._scrollSize,this._scrollPosition);this._computedAvailableSize=t.computedAvailableSize,this._computedIsNeeded=t.computedIsNeeded,this._computedSliderSize=t.computedSliderSize,this._computedSliderRatio=t.computedSliderRatio,this._computedSliderPosition=t.computedSliderPosition}getArrowSize(){return this._arrowSize}getScrollPosition(){return this._scrollPosition}getRectangleLargeSize(){return this._computedAvailableSize}getRectangleSmallSize(){return this._scrollbarSize}isNeeded(){return this._computedIsNeeded}getSliderSize(){return this._computedSliderSize}getSliderPosition(){return this._computedSliderPosition}getDesiredScrollPositionFromOffset(e){if(!this._computedIsNeeded)return 0;let t=e-this._arrowSize-this._computedSliderSize/2;return Math.round(t/this._computedSliderRatio)}getDesiredScrollPositionFromOffsetPaged(e){if(!this._computedIsNeeded)return 0;let t=e-this._arrowSize,n=this._scrollPosition;return t0&&Math.abs(e.deltaY)>0)return 1;let n=.5;if((!this._isAlmostInt(e.deltaX)||!this._isAlmostInt(e.deltaY))&&(n+=.25),t){let r=Math.abs(e.deltaX),i=Math.abs(e.deltaY),a=Math.abs(t.deltaX),o=Math.abs(t.deltaY),s=Math.max(Math.min(r,a),1),c=Math.max(Math.min(i,o),1),l=Math.max(r,a),u=Math.max(i,o);l%s===0&&u%c===0&&(n-=.5)}return Math.min(Math.max(n,0),1)}_isAlmostInt(e){return Math.abs(Math.round(e)-e)<.01}};pi.INSTANCE=new pi;var mi=pi,hi=class extends Kr{constructor(e,t,n){super(),this._onScroll=this._register(new N),this.onScroll=this._onScroll.event,this._onWillScroll=this._register(new N),this.onWillScroll=this._onWillScroll.event,this._options=_i(t),this._scrollable=n,this._register(this._scrollable.onScroll(e=>{this._onWillScroll.fire(e),this._onDidScroll(e),this._onScroll.fire(e)}));let r={onMouseWheel:e=>this._onMouseWheel(e),onDragStart:()=>this._onDragStart(),onDragEnd:()=>this._onDragEnd()};this._verticalScrollbar=this._register(new ci(this._scrollable,this._options,r)),this._horizontalScrollbar=this._register(new si(this._scrollable,this._options,r)),this._domNode=document.createElement(`div`),this._domNode.className=`xterm-scrollable-element `+this._options.className,this._domNode.setAttribute(`role`,`presentation`),this._domNode.style.position=`relative`,this._domNode.appendChild(e),this._domNode.appendChild(this._horizontalScrollbar.domNode.domNode),this._domNode.appendChild(this._verticalScrollbar.domNode.domNode),this._options.useShadows?(this._leftShadowDomNode=Br(document.createElement(`div`)),this._leftShadowDomNode.setClassName(`shadow`),this._domNode.appendChild(this._leftShadowDomNode.domNode),this._topShadowDomNode=Br(document.createElement(`div`)),this._topShadowDomNode.setClassName(`shadow`),this._domNode.appendChild(this._topShadowDomNode.domNode),this._topLeftShadowDomNode=Br(document.createElement(`div`)),this._topLeftShadowDomNode.setClassName(`shadow`),this._domNode.appendChild(this._topLeftShadowDomNode.domNode)):(this._leftShadowDomNode=null,this._topShadowDomNode=null,this._topLeftShadowDomNode=null),this._listenOnDomNode=this._options.listenOnDomNode||this._domNode,this._mouseWheelToDispose=[],this._setListeningToMouseWheel(this._options.handleMouseWheel),this.onmouseover(this._listenOnDomNode,e=>this._onMouseOver(e)),this.onmouseleave(this._listenOnDomNode,e=>this._onMouseLeave(e)),this._hideTimeout=this._register(new Zn),this._isDragging=!1,this._mouseIsOver=!1,this._shouldRender=!0,this._revealOnScroll=!0}get options(){return this._options}dispose(){this._mouseWheelToDispose=Dt(this._mouseWheelToDispose),super.dispose()}getDomNode(){return this._domNode}getOverviewRulerLayoutInfo(){return{parent:this._domNode,insertBefore:this._verticalScrollbar.domNode.domNode}}delegateVerticalScrollbarPointerDown(e){this._verticalScrollbar.delegatePointerDown(e)}getScrollDimensions(){return this._scrollable.getScrollDimensions()}setScrollDimensions(e){this._scrollable.setScrollDimensions(e,!1)}updateClassName(e){this._options.className=e,bn&&(this._options.className+=` mac`),this._domNode.className=`xterm-scrollable-element `+this._options.className}updateOptions(e){typeof e.handleMouseWheel<`u`&&(this._options.handleMouseWheel=e.handleMouseWheel,this._setListeningToMouseWheel(this._options.handleMouseWheel)),typeof e.mouseWheelScrollSensitivity<`u`&&(this._options.mouseWheelScrollSensitivity=e.mouseWheelScrollSensitivity),typeof e.fastScrollSensitivity<`u`&&(this._options.fastScrollSensitivity=e.fastScrollSensitivity),typeof e.scrollPredominantAxis<`u`&&(this._options.scrollPredominantAxis=e.scrollPredominantAxis),typeof e.horizontal<`u`&&(this._options.horizontal=e.horizontal),typeof e.vertical<`u`&&(this._options.vertical=e.vertical),typeof e.horizontalScrollbarSize<`u`&&(this._options.horizontalScrollbarSize=e.horizontalScrollbarSize),typeof e.verticalScrollbarSize<`u`&&(this._options.verticalScrollbarSize=e.verticalScrollbarSize),typeof e.scrollByPage<`u`&&(this._options.scrollByPage=e.scrollByPage),this._horizontalScrollbar.updateOptions(this._options),this._verticalScrollbar.updateOptions(this._options),this._options.lazyRender||this._render()}setRevealOnScroll(e){this._revealOnScroll=e}delegateScrollFromMouseWheelEvent(e){this._onMouseWheel(new qn(e))}_setListeningToMouseWheel(e){this._mouseWheelToDispose.length>0!==e&&(this._mouseWheelToDispose=Dt(this._mouseWheelToDispose),e)&&this._mouseWheelToDispose.push(P(this._listenOnDomNode,F.MOUSE_WHEEL,e=>{this._onMouseWheel(new qn(e))},{passive:!1}))}_onMouseWheel(e){if(e.browserEvent?.defaultPrevented)return;let t=mi.INSTANCE;di&&t.acceptStandardWheelEvent(e);let n=!1;if(e.deltaY||e.deltaX){let r=e.deltaY*this._options.mouseWheelScrollSensitivity,i=e.deltaX*this._options.mouseWheelScrollSensitivity;this._options.scrollPredominantAxis&&(this._options.scrollYToX&&i+r===0?i=r=0:Math.abs(r)>=Math.abs(i)?i=0:r=0),this._options.flipAxes&&([r,i]=[i,r]);let a=!bn&&e.browserEvent&&e.browserEvent.shiftKey;(this._options.scrollYToX||a)&&!i&&(i=r,r=0),e.browserEvent&&e.browserEvent.altKey&&(i*=this._options.fastScrollSensitivity,r*=this._options.fastScrollSensitivity);let o=this._scrollable.getFutureScrollPosition(),s={};if(r){let e=ui*r,t=o.scrollTop-(e<0?Math.floor(e):Math.ceil(e));this._verticalScrollbar.writeScrollPosition(s,t)}if(i){let e=ui*i,t=o.scrollLeft-(e<0?Math.floor(e):Math.ceil(e));this._horizontalScrollbar.writeScrollPosition(s,t)}s=this._scrollable.validateScrollPosition(s),(o.scrollLeft!==s.scrollLeft||o.scrollTop!==s.scrollTop)&&(di&&this._options.mouseWheelSmoothScroll&&t.isPhysicalMouseWheel()?this._scrollable.setScrollPositionSmooth(s):this._scrollable.setScrollPositionNow(s),n=!0)}let r=n;!r&&this._options.alwaysConsumeMouseWheel&&(r=!0),!r&&this._options.consumeMouseWheelIfScrollbarIsNeeded&&(this._verticalScrollbar.isNeeded()||this._horizontalScrollbar.isNeeded())&&(r=!0),r&&(e.preventDefault(),e.stopPropagation())}_onDidScroll(e){this._shouldRender=this._horizontalScrollbar.onDidScroll(e)||this._shouldRender,this._shouldRender=this._verticalScrollbar.onDidScroll(e)||this._shouldRender,this._options.useShadows&&(this._shouldRender=!0),this._revealOnScroll&&this._reveal(),this._options.lazyRender||this._render()}renderNow(){if(!this._options.lazyRender)throw Error("Please use `lazyRender` together with `renderNow`!");this._render()}_render(){if(this._shouldRender&&(this._shouldRender=!1,this._horizontalScrollbar.render(),this._verticalScrollbar.render(),this._options.useShadows)){let e=this._scrollable.getCurrentScrollPosition(),t=e.scrollTop>0,n=e.scrollLeft>0,r=n?` left`:``,i=t?` top`:``,a=n||t?` top-left-corner`:``;this._leftShadowDomNode.setClassName(`shadow${r}`),this._topShadowDomNode.setClassName(`shadow${i}`),this._topLeftShadowDomNode.setClassName(`shadow${a}${i}${r}`)}}_onDragStart(){this._isDragging=!0,this._reveal()}_onDragEnd(){this._isDragging=!1,this._hide()}_onMouseLeave(e){this._mouseIsOver=!1,this._hide()}_onMouseOver(e){this._mouseIsOver=!0,this._reveal()}_reveal(){this._verticalScrollbar.beginReveal(),this._horizontalScrollbar.beginReveal(),this._scheduleHide()}_hide(){!this._mouseIsOver&&!this._isDragging&&(this._verticalScrollbar.beginHide(),this._horizontalScrollbar.beginHide())}_scheduleHide(){!this._mouseIsOver&&!this._isDragging&&this._hideTimeout.cancelAndSet(()=>this._hide(),li)}},gi=class extends hi{constructor(e,t,n){super(e,t,n)}setScrollPosition(e){e.reuseAnimation?this._scrollable.setScrollPositionSmooth(e,e.reuseAnimation):this._scrollable.setScrollPositionNow(e)}getScrollPosition(){return this._scrollable.getCurrentScrollPosition()}};function _i(e){let t={lazyRender:typeof e.lazyRender<`u`&&e.lazyRender,className:typeof e.className<`u`?e.className:``,useShadows:typeof e.useShadows<`u`?e.useShadows:!0,handleMouseWheel:typeof e.handleMouseWheel<`u`?e.handleMouseWheel:!0,flipAxes:typeof e.flipAxes<`u`&&e.flipAxes,consumeMouseWheelIfScrollbarIsNeeded:typeof e.consumeMouseWheelIfScrollbarIsNeeded<`u`&&e.consumeMouseWheelIfScrollbarIsNeeded,alwaysConsumeMouseWheel:typeof e.alwaysConsumeMouseWheel<`u`&&e.alwaysConsumeMouseWheel,scrollYToX:typeof e.scrollYToX<`u`&&e.scrollYToX,mouseWheelScrollSensitivity:typeof e.mouseWheelScrollSensitivity<`u`?e.mouseWheelScrollSensitivity:1,fastScrollSensitivity:typeof e.fastScrollSensitivity<`u`?e.fastScrollSensitivity:5,scrollPredominantAxis:typeof e.scrollPredominantAxis<`u`?e.scrollPredominantAxis:!0,mouseWheelSmoothScroll:typeof e.mouseWheelSmoothScroll<`u`?e.mouseWheelSmoothScroll:!0,arrowSize:typeof e.arrowSize<`u`?e.arrowSize:11,listenOnDomNode:typeof e.listenOnDomNode<`u`?e.listenOnDomNode:null,horizontal:typeof e.horizontal<`u`?e.horizontal:1,horizontalScrollbarSize:typeof e.horizontalScrollbarSize<`u`?e.horizontalScrollbarSize:10,horizontalSliderSize:typeof e.horizontalSliderSize<`u`?e.horizontalSliderSize:0,horizontalHasArrows:typeof e.horizontalHasArrows<`u`&&e.horizontalHasArrows,vertical:typeof e.vertical<`u`?e.vertical:1,verticalScrollbarSize:typeof e.verticalScrollbarSize<`u`?e.verticalScrollbarSize:10,verticalHasArrows:typeof e.verticalHasArrows<`u`&&e.verticalHasArrows,verticalSliderSize:typeof e.verticalSliderSize<`u`?e.verticalSliderSize:0,scrollByPage:typeof e.scrollByPage<`u`&&e.scrollByPage};return t.horizontalSliderSize=typeof e.horizontalSliderSize<`u`?e.horizontalSliderSize:t.horizontalScrollbarSize,t.verticalSliderSize=typeof e.verticalSliderSize<`u`?e.verticalSliderSize:t.verticalScrollbarSize,bn&&(t.className+=` mac`),t}var vi=class extends A{constructor(e,t,n,r,i,a,o,s){super(),this._bufferService=n,this._optionsService=o,this._renderService=s,this._onRequestScrollLines=this._register(new N),this.onRequestScrollLines=this._onRequestScrollLines.event,this._isSyncing=!1,this._isHandlingScroll=!1,this._suppressOnScrollHandler=!1;let c=this._register(new Xr({forceIntegerValues:!1,smoothScrollDuration:this._optionsService.rawOptions.smoothScrollDuration,scheduleAtNextAnimationFrame:e=>jr(r.window,e)}));this._register(this._optionsService.onSpecificOptionChange(`smoothScrollDuration`,()=>{c.setSmoothScrollDuration(this._optionsService.rawOptions.smoothScrollDuration)})),this._scrollableElement=this._register(new gi(t,{vertical:1,horizontal:2,useShadows:!1,mouseWheelSmoothScroll:!0,...this._getChangeOptions()},c)),this._register(this._optionsService.onMultipleOptionChange([`scrollSensitivity`,`fastScrollSensitivity`,`overviewRuler`],()=>this._scrollableElement.updateOptions(this._getChangeOptions()))),this._register(i.onProtocolChange(e=>{this._scrollableElement.updateOptions({handleMouseWheel:!(e&16)})})),this._scrollableElement.setScrollDimensions({height:0,scrollHeight:0}),this._register(M.runAndSubscribe(a.onChangeColors,()=>{this._scrollableElement.getDomNode().style.backgroundColor=a.colors.background.css})),e.appendChild(this._scrollableElement.getDomNode()),this._register(k(()=>this._scrollableElement.getDomNode().remove())),this._styleElement=r.mainDocument.createElement(`style`),t.appendChild(this._styleElement),this._register(k(()=>this._styleElement.remove())),this._register(M.runAndSubscribe(a.onChangeColors,()=>{this._styleElement.textContent=[`.xterm .xterm-scrollable-element > .scrollbar > .slider {`,` background: ${a.colors.scrollbarSliderBackground.css};`,`}`,`.xterm .xterm-scrollable-element > .scrollbar > .slider:hover {`,` background: ${a.colors.scrollbarSliderHoverBackground.css};`,`}`,`.xterm .xterm-scrollable-element > .scrollbar > .slider.active {`,` background: ${a.colors.scrollbarSliderActiveBackground.css};`,`}`].join(` +`)})),this._register(this._bufferService.onResize(()=>this.queueSync())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._latestYDisp=void 0,this.queueSync()})),this._register(this._bufferService.onScroll(()=>this._sync())),this._register(this._scrollableElement.onScroll(e=>this._handleScroll(e)))}scrollLines(e){let t=this._scrollableElement.getScrollPosition();this._scrollableElement.setScrollPosition({reuseAnimation:!0,scrollTop:t.scrollTop+e*this._renderService.dimensions.css.cell.height})}scrollToLine(e,t){t&&(this._latestYDisp=e),this._scrollableElement.setScrollPosition({reuseAnimation:!t,scrollTop:e*this._renderService.dimensions.css.cell.height})}_getChangeOptions(){return{mouseWheelScrollSensitivity:this._optionsService.rawOptions.scrollSensitivity,fastScrollSensitivity:this._optionsService.rawOptions.fastScrollSensitivity,verticalScrollbarSize:this._optionsService.rawOptions.overviewRuler?.width||14}}queueSync(e){e!==void 0&&(this._latestYDisp=e),this._queuedAnimationFrame===void 0&&(this._queuedAnimationFrame=this._renderService.addRefreshCallback(()=>{this._queuedAnimationFrame=void 0,this._sync(this._latestYDisp)}))}_sync(e=this._bufferService.buffer.ydisp){!this._renderService||this._isSyncing||(this._isSyncing=!0,this._suppressOnScrollHandler=!0,this._scrollableElement.setScrollDimensions({height:this._renderService.dimensions.css.canvas.height,scrollHeight:this._renderService.dimensions.css.cell.height*this._bufferService.buffer.lines.length}),this._suppressOnScrollHandler=!1,e!==this._latestYDisp&&this._scrollableElement.setScrollPosition({scrollTop:e*this._renderService.dimensions.css.cell.height}),this._isSyncing=!1)}_handleScroll(e){if(!this._renderService||this._isHandlingScroll||this._suppressOnScrollHandler)return;this._isHandlingScroll=!0;let t=Math.round(e.scrollTop/this._renderService.dimensions.css.cell.height),n=t-this._bufferService.buffer.ydisp;n!==0&&(this._latestYDisp=t,this._onRequestScrollLines.fire(n)),this._isHandlingScroll=!1}};vi=C([w(2,D),w(3,Ye),w(4,Re),w(5,et),w(6,O),w(7,Ze)],vi);var yi=class extends A{constructor(e,t,n,r,i){super(),this._screenElement=e,this._bufferService=t,this._coreBrowserService=n,this._decorationService=r,this._renderService=i,this._decorationElements=new Map,this._altBufferIsActive=!1,this._dimensionsChanged=!1,this._container=document.createElement(`div`),this._container.classList.add(`xterm-decoration-container`),this._screenElement.appendChild(this._container),this._register(this._renderService.onRenderedViewportChange(()=>this._doRefreshDecorations())),this._register(this._renderService.onDimensionsChange(()=>{this._dimensionsChanged=!0,this._queueRefresh()})),this._register(this._coreBrowserService.onDprChange(()=>this._queueRefresh())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._altBufferIsActive=this._bufferService.buffer===this._bufferService.buffers.alt})),this._register(this._decorationService.onDecorationRegistered(()=>this._queueRefresh())),this._register(this._decorationService.onDecorationRemoved(e=>this._removeDecoration(e))),this._register(k(()=>{this._container.remove(),this._decorationElements.clear()}))}_queueRefresh(){this._animationFrame===void 0&&(this._animationFrame=this._renderService.addRefreshCallback(()=>{this._doRefreshDecorations(),this._animationFrame=void 0}))}_doRefreshDecorations(){for(let e of this._decorationService.decorations)this._renderDecoration(e);this._dimensionsChanged=!1}_renderDecoration(e){this._refreshStyle(e),this._dimensionsChanged&&this._refreshXPosition(e)}_createElement(e){let t=this._coreBrowserService.mainDocument.createElement(`div`);t.classList.add(`xterm-decoration`),t.classList.toggle(`xterm-decoration-top-layer`,e?.options?.layer===`top`),t.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,t.style.height=`${(e.options.height||1)*this._renderService.dimensions.css.cell.height}px`,t.style.top=`${(e.marker.line-this._bufferService.buffers.active.ydisp)*this._renderService.dimensions.css.cell.height}px`,t.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`;let n=e.options.x??0;return n&&n>this._bufferService.cols&&(t.style.display=`none`),this._refreshXPosition(e,t),t}_refreshStyle(e){let t=e.marker.line-this._bufferService.buffers.active.ydisp;if(t<0||t>=this._bufferService.rows)e.element&&(e.element.style.display=`none`,e.onRenderEmitter.fire(e.element));else{let n=this._decorationElements.get(e);n||(n=this._createElement(e),e.element=n,this._decorationElements.set(e,n),this._container.appendChild(n),e.onDispose(()=>{this._decorationElements.delete(e),n.remove()})),n.style.display=this._altBufferIsActive?`none`:`block`,this._altBufferIsActive||(n.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,n.style.height=`${(e.options.height||1)*this._renderService.dimensions.css.cell.height}px`,n.style.top=`${t*this._renderService.dimensions.css.cell.height}px`,n.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`),e.onRenderEmitter.fire(n)}}_refreshXPosition(e,t=e.element){if(!t)return;let n=e.options.x??0;(e.options.anchor||`left`)===`right`?t.style.right=n?`${n*this._renderService.dimensions.css.cell.width}px`:``:t.style.left=n?`${n*this._renderService.dimensions.css.cell.width}px`:``}_removeDecoration(e){this._decorationElements.get(e)?.remove(),this._decorationElements.delete(e),e.dispose()}};yi=C([w(1,D),w(2,Ye),w(3,Ge),w(4,Ze)],yi);var bi=class{constructor(){this._zones=[],this._zonePool=[],this._zonePoolIndex=0,this._linePadding={full:0,left:0,center:0,right:0}}get zones(){return this._zonePool.length=Math.min(this._zonePool.length,this._zones.length),this._zones}clear(){this._zones.length=0,this._zonePoolIndex=0}addDecoration(e){if(e.options.overviewRulerOptions){for(let t of this._zones)if(t.color===e.options.overviewRulerOptions.color&&t.position===e.options.overviewRulerOptions.position){if(this._lineIntersectsZone(t,e.marker.line))return;if(this._lineAdjacentToZone(t,e.marker.line,e.options.overviewRulerOptions.position)){this._addLineToZone(t,e.marker.line);return}}if(this._zonePoolIndex=e.startBufferLine&&t<=e.endBufferLine}_lineAdjacentToZone(e,t,n){return t>=e.startBufferLine-this._linePadding[n||`full`]&&t<=e.endBufferLine+this._linePadding[n||`full`]}_addLineToZone(e,t){e.startBufferLine=Math.min(e.startBufferLine,t),e.endBufferLine=Math.max(e.endBufferLine,t)}},xi={full:0,left:0,center:0,right:0},Si={full:0,left:0,center:0,right:0},Ci={full:0,left:0,center:0,right:0},wi=class extends A{constructor(e,t,n,r,i,a,o,s){super(),this._viewportElement=e,this._screenElement=t,this._bufferService=n,this._decorationService=r,this._renderService=i,this._optionsService=a,this._themeService=o,this._coreBrowserService=s,this._colorZoneStore=new bi,this._shouldUpdateDimensions=!0,this._shouldUpdateAnchor=!0,this._lastKnownBufferLength=0,this._canvas=this._coreBrowserService.mainDocument.createElement(`canvas`),this._canvas.classList.add(`xterm-decoration-overview-ruler`),this._refreshCanvasDimensions(),this._viewportElement.parentElement?.insertBefore(this._canvas,this._viewportElement),this._register(k(()=>this._canvas?.remove()));let c=this._canvas.getContext(`2d`);if(c)this._ctx=c;else throw Error(`Ctx cannot be null`);this._register(this._decorationService.onDecorationRegistered(()=>this._queueRefresh(void 0,!0))),this._register(this._decorationService.onDecorationRemoved(()=>this._queueRefresh(void 0,!0))),this._register(this._renderService.onRenderedViewportChange(()=>this._queueRefresh())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._canvas.style.display=this._bufferService.buffer===this._bufferService.buffers.alt?`none`:`block`})),this._register(this._bufferService.onScroll(()=>{this._lastKnownBufferLength!==this._bufferService.buffers.normal.lines.length&&(this._refreshDrawHeightConstants(),this._refreshColorZonePadding())})),this._register(this._renderService.onRender(()=>{(!this._containerHeight||this._containerHeight!==this._screenElement.clientHeight)&&(this._queueRefresh(!0),this._containerHeight=this._screenElement.clientHeight)})),this._register(this._coreBrowserService.onDprChange(()=>this._queueRefresh(!0))),this._register(this._optionsService.onSpecificOptionChange(`overviewRuler`,()=>this._queueRefresh(!0))),this._register(this._themeService.onChangeColors(()=>this._queueRefresh())),this._queueRefresh(!0)}get _width(){return this._optionsService.options.overviewRuler?.width||0}_refreshDrawConstants(){let e=Math.floor((this._canvas.width-1)/3),t=Math.ceil((this._canvas.width-1)/3);Si.full=this._canvas.width,Si.left=e,Si.center=t,Si.right=e,this._refreshDrawHeightConstants(),Ci.full=1,Ci.left=1,Ci.center=1+Si.left,Ci.right=1+Si.left+Si.center}_refreshDrawHeightConstants(){xi.full=Math.round(2*this._coreBrowserService.dpr);let e=this._canvas.height/this._bufferService.buffer.lines.length,t=Math.round(Math.max(Math.min(e,12),6)*this._coreBrowserService.dpr);xi.left=t,xi.center=t,xi.right=t}_refreshColorZonePadding(){this._colorZoneStore.setPadding({full:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*xi.full),left:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*xi.left),center:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*xi.center),right:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*xi.right)}),this._lastKnownBufferLength=this._bufferService.buffers.normal.lines.length}_refreshCanvasDimensions(){this._canvas.style.width=`${this._width}px`,this._canvas.width=Math.round(this._width*this._coreBrowserService.dpr),this._canvas.style.height=`${this._screenElement.clientHeight}px`,this._canvas.height=Math.round(this._screenElement.clientHeight*this._coreBrowserService.dpr),this._refreshDrawConstants(),this._refreshColorZonePadding()}_refreshDecorations(){this._shouldUpdateDimensions&&this._refreshCanvasDimensions(),this._ctx.clearRect(0,0,this._canvas.width,this._canvas.height),this._colorZoneStore.clear();for(let e of this._decorationService.decorations)this._colorZoneStore.addDecoration(e);this._ctx.lineWidth=1,this._renderRulerOutline();let e=this._colorZoneStore.zones;for(let t of e)t.position!==`full`&&this._renderColorZone(t);for(let t of e)t.position===`full`&&this._renderColorZone(t);this._shouldUpdateDimensions=!1,this._shouldUpdateAnchor=!1}_renderRulerOutline(){this._ctx.fillStyle=this._themeService.colors.overviewRulerBorder.css,this._ctx.fillRect(0,0,1,this._canvas.height),this._optionsService.rawOptions.overviewRuler.showTopBorder&&this._ctx.fillRect(1,0,this._canvas.width-1,1),this._optionsService.rawOptions.overviewRuler.showBottomBorder&&this._ctx.fillRect(1,this._canvas.height-1,this._canvas.width-1,this._canvas.height)}_renderColorZone(e){this._ctx.fillStyle=e.color,this._ctx.fillRect(Ci[e.position||`full`],Math.round((this._canvas.height-1)*(e.startBufferLine/this._bufferService.buffers.active.lines.length)-xi[e.position||`full`]/2),Si[e.position||`full`],Math.round((this._canvas.height-1)*((e.endBufferLine-e.startBufferLine)/this._bufferService.buffers.active.lines.length)+xi[e.position||`full`]))}_queueRefresh(e,t){this._shouldUpdateDimensions=e||this._shouldUpdateDimensions,this._shouldUpdateAnchor=t||this._shouldUpdateAnchor,this._animationFrame===void 0&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{this._refreshDecorations(),this._animationFrame=void 0}))}};wi=C([w(2,D),w(3,Ge),w(4,Ze),w(5,O),w(6,et),w(7,Ye)],wi);var L;(e=>(e.NUL=`\0`,e.SOH=``,e.STX=``,e.ETX=``,e.EOT=``,e.ENQ=``,e.ACK=``,e.BEL=`\x07`,e.BS=`\b`,e.HT=` `,e.LF=` +`,e.VT=`\v`,e.FF=`\f`,e.CR=`\r`,e.SO=``,e.SI=``,e.DLE=``,e.DC1=``,e.DC2=``,e.DC3=``,e.DC4=``,e.NAK=``,e.SYN=``,e.ETB=``,e.CAN=``,e.EM=``,e.SUB=``,e.ESC=`\x1B`,e.FS=``,e.GS=``,e.RS=``,e.US=``,e.SP=` `,e.DEL=``))(L||={});var Ti;(e=>(e.PAD=`€`,e.HOP=``,e.BPH=`‚`,e.NBH=`ƒ`,e.IND=`„`,e.NEL=`…`,e.SSA=`†`,e.ESA=`‡`,e.HTS=`ˆ`,e.HTJ=`‰`,e.VTS=`Š`,e.PLD=`‹`,e.PLU=`Œ`,e.RI=``,e.SS2=`Ž`,e.SS3=``,e.DCS=``,e.PU1=`‘`,e.PU2=`’`,e.STS=`“`,e.CCH=`”`,e.MW=`•`,e.SPA=`–`,e.EPA=`—`,e.SOS=`˜`,e.SGCI=`™`,e.SCI=`š`,e.CSI=`›`,e.ST=`œ`,e.OSC=``,e.PM=`ž`,e.APC=`Ÿ`))(Ti||={});var Ei;(e=>e.ST=`${L.ESC}\\`)(Ei||={});var Di=class{constructor(e,t,n,r,i,a){this._textarea=e,this._compositionView=t,this._bufferService=n,this._optionsService=r,this._coreService=i,this._renderService=a,this._isComposing=!1,this._isSendingComposition=!1,this._compositionPosition={start:0,end:0},this._dataAlreadySent=``}get isComposing(){return this._isComposing}compositionstart(){this._isComposing=!0,this._compositionPosition.start=this._textarea.value.length,this._compositionView.textContent=``,this._dataAlreadySent=``,this._compositionView.classList.add(`active`)}compositionupdate(e){this._compositionView.textContent=e.data,this.updateCompositionElements(),setTimeout(()=>{this._compositionPosition.end=this._textarea.value.length},0)}compositionend(){this._finalizeComposition(!0)}keydown(e){if(this._isComposing||this._isSendingComposition){if(e.keyCode===20||e.keyCode===229||e.keyCode===16||e.keyCode===17||e.keyCode===18)return!1;this._finalizeComposition(!1)}return e.keyCode===229?(this._handleAnyTextareaChanges(),!1):!0}_finalizeComposition(e){if(this._compositionView.classList.remove(`active`),this._isComposing=!1,e){let e={start:this._compositionPosition.start,end:this._compositionPosition.end};this._isSendingComposition=!0,setTimeout(()=>{if(this._isSendingComposition){this._isSendingComposition=!1;let t;e.start+=this._dataAlreadySent.length,t=this._isComposing?this._textarea.value.substring(e.start,this._compositionPosition.start):this._textarea.value.substring(e.start),t.length>0&&this._coreService.triggerDataEvent(t,!0)}},0)}else{this._isSendingComposition=!1;let e=this._textarea.value.substring(this._compositionPosition.start,this._compositionPosition.end);this._coreService.triggerDataEvent(e,!0)}}_handleAnyTextareaChanges(){let e=this._textarea.value;setTimeout(()=>{if(!this._isComposing){let t=this._textarea.value,n=t.replace(e,``);this._dataAlreadySent=n,t.length>e.length?this._coreService.triggerDataEvent(n,!0):t.lengththis.updateCompositionElements(!0),0)}}};Di=C([w(2,D),w(3,O),w(4,ze),w(5,Ze)],Di);var R=0,z=0,B=0,V=0,Oi={css:`#00000000`,rgba:0},H;(e=>{function t(e,t,n,r){return r===void 0?`#${Ai(e)}${Ai(t)}${Ai(n)}`:`#${Ai(e)}${Ai(t)}${Ai(n)}${Ai(r)}`}e.toCss=t;function n(e,t,n,r=255){return(e<<24|t<<16|n<<8|r)>>>0}e.toRgba=n;function r(t,n,r,i){return{css:e.toCss(t,n,r,i),rgba:e.toRgba(t,n,r,i)}}e.toColor=r})(H||={});var U;(e=>{function t(e,t){if(V=(t.rgba&255)/255,V===1)return{css:t.css,rgba:t.rgba};let n=t.rgba>>24&255,r=t.rgba>>16&255,i=t.rgba>>8&255,a=e.rgba>>24&255,o=e.rgba>>16&255,s=e.rgba>>8&255;return R=a+Math.round((n-a)*V),z=o+Math.round((r-o)*V),B=s+Math.round((i-s)*V),{css:H.toCss(R,z,B),rgba:H.toRgba(R,z,B)}}e.blend=t;function n(e){return(e.rgba&255)==255}e.isOpaque=n;function r(e,t,n){let r=ki.ensureContrastRatio(e.rgba,t.rgba,n);if(r)return H.toColor(r>>24&255,r>>16&255,r>>8&255)}e.ensureContrastRatio=r;function i(e){let t=(e.rgba|255)>>>0;return[R,z,B]=ki.toChannels(t),{css:H.toCss(R,z,B),rgba:t}}e.opaque=i;function a(e,t){return V=Math.round(t*255),[R,z,B]=ki.toChannels(e.rgba),{css:H.toCss(R,z,B,V),rgba:H.toRgba(R,z,B,V)}}e.opacity=a;function o(e,t){return V=e.rgba&255,a(e,V*t/255)}e.multiplyOpacity=o;function s(e){return[e.rgba>>24&255,e.rgba>>16&255,e.rgba>>8&255]}e.toColorRGB=s})(U||={});var W;(e=>{let t,n;try{let e=document.createElement(`canvas`);e.width=1,e.height=1;let r=e.getContext(`2d`,{willReadFrequently:!0});r&&(t=r,t.globalCompositeOperation=`copy`,n=t.createLinearGradient(0,0,1,1))}catch{}function r(e){if(e.match(/#[\da-f]{3,8}/i))switch(e.length){case 4:return R=parseInt(e.slice(1,2).repeat(2),16),z=parseInt(e.slice(2,3).repeat(2),16),B=parseInt(e.slice(3,4).repeat(2),16),H.toColor(R,z,B);case 5:return R=parseInt(e.slice(1,2).repeat(2),16),z=parseInt(e.slice(2,3).repeat(2),16),B=parseInt(e.slice(3,4).repeat(2),16),V=parseInt(e.slice(4,5).repeat(2),16),H.toColor(R,z,B,V);case 7:return{css:e,rgba:(parseInt(e.slice(1),16)<<8|255)>>>0};case 9:return{css:e,rgba:parseInt(e.slice(1),16)>>>0}}let r=e.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);if(r)return R=parseInt(r[1]),z=parseInt(r[2]),B=parseInt(r[3]),V=Math.round((r[5]===void 0?1:parseFloat(r[5]))*255),H.toColor(R,z,B,V);if(!t||!n||(t.fillStyle=n,t.fillStyle=e,typeof t.fillStyle!=`string`)||(t.fillRect(0,0,1,1),[R,z,B,V]=t.getImageData(0,0,1,1).data,V!==255))throw Error(`css.toColor: Unsupported css format`);return{rgba:H.toRgba(R,z,B,V),css:e}}e.toColor=r})(W||={});var G;(e=>{function t(e){return n(e>>16&255,e>>8&255,e&255)}e.relativeLuminance=t;function n(e,t,n){let r=e/255,i=t/255,a=n/255,o=r<=.03928?r/12.92:((r+.055)/1.055)**2.4,s=i<=.03928?i/12.92:((i+.055)/1.055)**2.4,c=a<=.03928?a/12.92:((a+.055)/1.055)**2.4;return o*.2126+s*.7152+c*.0722}e.relativeLuminance2=n})(G||={});var ki;(e=>{function t(e,t){if(V=(t&255)/255,V===1)return t;let n=t>>24&255,r=t>>16&255,i=t>>8&255,a=e>>24&255,o=e>>16&255,s=e>>8&255;return R=a+Math.round((n-a)*V),z=o+Math.round((r-o)*V),B=s+Math.round((i-s)*V),H.toRgba(R,z,B)}e.blend=t;function n(e,t,n){let a=G.relativeLuminance(e>>8),o=G.relativeLuminance(t>>8);if(ji(a,o)>8));if(sji(a,G.relativeLuminance(r>>8))?o:r}return o}let s=i(e,t,n),c=ji(a,G.relativeLuminance(s>>8));if(cji(a,G.relativeLuminance(i>>8))?s:i}return s}}e.ensureContrastRatio=n;function r(e,t,n){let r=e>>24&255,i=e>>16&255,a=e>>8&255,o=t>>24&255,s=t>>16&255,c=t>>8&255,l=ji(G.relativeLuminance2(o,s,c),G.relativeLuminance2(r,i,a));for(;l0||s>0||c>0);)o-=Math.max(0,Math.ceil(o*.1)),s-=Math.max(0,Math.ceil(s*.1)),c-=Math.max(0,Math.ceil(c*.1)),l=ji(G.relativeLuminance2(o,s,c),G.relativeLuminance2(r,i,a));return(o<<24|s<<16|c<<8|255)>>>0}e.reduceLuminance=r;function i(e,t,n){let r=e>>24&255,i=e>>16&255,a=e>>8&255,o=t>>24&255,s=t>>16&255,c=t>>8&255,l=ji(G.relativeLuminance2(o,s,c),G.relativeLuminance2(r,i,a));for(;l>>0}e.increaseLuminance=i;function a(e){return[e>>24&255,e>>16&255,e>>8&255,e&255]}e.toChannels=a})(ki||={});function Ai(e){let t=e.toString(16);return t.length<2?`0`+t:t}function ji(e,t){return e1){let e=this._getJoinedRanges(r,o,a,t,i);for(let t=0;t1){let e=this._getJoinedRanges(r,o,a,t,i);for(let t=0;t=oe,ue=x,S=this._workCell;if(f.length>0&&x===f[0][0]&&le){let r=f.shift(),i=this._isCellInSelection(r[0],t);for(ee=r[0]+1;ee=r[1],le?(ce=!0,S=new Mi(this._workCell,e.translateToString(!0,r[0],r[1]),r[1]-r[0]),ue=r[1]-1,m=S.getWidth()):oe=r[1]}let de=this._isCellInSelection(x,t),fe=n&&x===a,pe=se&&x>=l&&x<=u,C=!1;this._decorationService.forEachDecorationAtCell(x,t,void 0,e=>{C=!0});let w=S.getChars()||ke;if(w===` `&&(S.isUnderline()||S.isOverline())&&(w=`\xA0`),ae=m*s-c.get(w,S.isBold(),S.isItalic()),!h)h=this._document.createElement(`span`);else if(g&&(de&&y||!de&&!y&&S.bg===te)&&(de&&y&&p.selectionForeground||S.fg===ne)&&S.extended.ext===re&&pe===v&&ae===ie&&!fe&&!ce&&!C&&le){S.isInvisible()?_+=ke:_+=w,g++;continue}else g&&(h.textContent=_),h=this._document.createElement(`span`),g=0,_=``;if(te=S.bg,ne=S.fg,re=S.extended.ext,v=pe,ie=ae,y=de,ce&&a>=x&&a<=ue&&(a=x),!this._coreService.isCursorHidden&&fe&&this._coreService.isCursorInitialized){if(b.push(`xterm-cursor`),this._coreBrowserService.isFocused)o&&b.push(`xterm-cursor-blink`),b.push(r===`bar`?`xterm-cursor-bar`:r===`underline`?`xterm-cursor-underline`:`xterm-cursor-block`);else if(i)switch(i){case`outline`:b.push(`xterm-cursor-outline`);break;case`block`:b.push(`xterm-cursor-block`);break;case`bar`:b.push(`xterm-cursor-bar`);break;case`underline`:b.push(`xterm-cursor-underline`);break;default:break}}if(S.isBold()&&b.push(`xterm-bold`),S.isItalic()&&b.push(`xterm-italic`),S.isDim()&&b.push(`xterm-dim`),_=S.isInvisible()?ke:S.getChars()||ke,S.isUnderline()&&(b.push(`xterm-underline-${S.extended.underlineStyle}`),_===` `&&(_=`\xA0`),!S.isUnderlineColorDefault()))if(S.isUnderlineColorRGB())h.style.textDecorationColor=`rgb(${Ae.toColorRGB(S.getUnderlineColor()).join(`,`)})`;else{let e=S.getUnderlineColor();this._optionsService.rawOptions.drawBoldTextInBrightColors&&S.isBold()&&e<8&&(e+=8),h.style.textDecorationColor=p.ansi[e].css}S.isOverline()&&(b.push(`xterm-overline`),_===` `&&(_=`\xA0`)),S.isStrikethrough()&&b.push(`xterm-strikethrough`),pe&&(h.style.textDecoration=`underline`);let T=S.getFgColor(),me=S.getFgColorMode(),he=S.getBgColor(),ge=S.getBgColorMode(),_e=!!S.isInverse();if(_e){let e=T;T=he,he=e;let t=me;me=ge,ge=t}let ve,ye,be=!1;this._decorationService.forEachDecorationAtCell(x,t,void 0,e=>{e.options.layer!==`top`&&be||(e.backgroundColorRGB&&(ge=50331648,he=e.backgroundColorRGB.rgba>>8&16777215,ve=e.backgroundColorRGB),e.foregroundColorRGB&&(me=50331648,T=e.foregroundColorRGB.rgba>>8&16777215,ye=e.foregroundColorRGB),be=e.options.layer===`top`)}),!be&&de&&(ve=this._coreBrowserService.isFocused?p.selectionBackgroundOpaque:p.selectionInactiveBackgroundOpaque,he=ve.rgba>>8&16777215,ge=50331648,be=!0,p.selectionForeground&&(me=50331648,T=p.selectionForeground.rgba>>8&16777215,ye=p.selectionForeground)),be&&b.push(`xterm-decoration-top`);let xe;switch(ge){case 16777216:case 33554432:xe=p.ansi[he],b.push(`xterm-bg-${he}`);break;case 50331648:xe=H.toColor(he>>16,he>>8&255,he&255),this._addStyle(h,`background-color:#${Bi((he>>>0).toString(16),`0`,6)}`);break;default:_e?(xe=p.foreground,b.push(`xterm-bg-257`)):xe=p.background}switch(ve||S.isDim()&&(ve=U.multiplyOpacity(xe,.5)),me){case 16777216:case 33554432:S.isBold()&&T<8&&this._optionsService.rawOptions.drawBoldTextInBrightColors&&(T+=8),this._applyMinimumContrast(h,xe,p.ansi[T],S,ve,void 0)||b.push(`xterm-fg-${T}`);break;case 50331648:let e=H.toColor(T>>16&255,T>>8&255,T&255);this._applyMinimumContrast(h,xe,e,S,ve,ye)||this._addStyle(h,`color:#${Bi(T.toString(16),`0`,6)}`);break;default:this._applyMinimumContrast(h,xe,p.foreground,S,ve,ye)||_e&&b.push(`xterm-fg-257`)}b.length&&=(h.className=b.join(` `),0),!fe&&!ce&&!C&&le?g++:h.textContent=_,ae!==this.defaultSpacing&&(h.style.letterSpacing=`${ae}px`),d.push(h),x=ue}return h&&g&&(h.textContent=_),d}_applyMinimumContrast(e,t,n,r,i,a){if(this._optionsService.rawOptions.minimumContrastRatio===1||Ii(r.getCode()))return!1;let o=this._getContrastCache(r),s;if(!i&&!a&&(s=o.getColor(t.rgba,n.rgba)),s===void 0){let e=this._optionsService.rawOptions.minimumContrastRatio/(r.isDim()?2:1);s=U.ensureContrastRatio(i||t,a||n,e),o.setColor((i||t).rgba,(a||n).rgba,s??null)}return s?(this._addStyle(e,`color:${s.css}`),!0):!1}_getContrastCache(e){return e.isDim()?this._themeService.colors.halfContrastCache:this._themeService.colors.contrastCache}_addStyle(e,t){e.setAttribute(`style`,`${e.getAttribute(`style`)||``}${t};`)}_isCellInSelection(e,t){let n=this._selectionStart,r=this._selectionEnd;return!n||!r?!1:this._columnSelectMode?n[0]<=r[0]?e>=n[0]&&t>=n[1]&&e=n[1]&&e>=r[0]&&t<=r[1]:t>n[1]&&t=n[0]&&e=n[0]}};zi=C([w(1,$e),w(2,O),w(3,Ye),w(4,ze),w(5,Ge),w(6,et)],zi);function Bi(e,t,n){for(;e.length0&&(this._flat[r]=t),t}let i=e;t&&(i+=`B`),n&&(i+=`I`);let a=this._holey.get(i);if(a===void 0){let r=0;t&&(r|=1),n&&(r|=2),a=this._measure(e,r),a>0&&this._holey.set(i,a)}return a}_measure(e,t){let n=this._measureElements[t];return n.textContent=e.repeat(32),n.offsetWidth/32}},Hi=class{constructor(){this.clear()}clear(){this.hasSelection=!1,this.columnSelectMode=!1,this.viewportStartRow=0,this.viewportEndRow=0,this.viewportCappedStartRow=0,this.viewportCappedEndRow=0,this.startCol=0,this.endCol=0,this.selectionStart=void 0,this.selectionEnd=void 0}update(e,t,n,r=!1){if(this.selectionStart=t,this.selectionEnd=n,!t||!n||t[0]===n[0]&&t[1]===n[1]){this.clear();return}let i=e.buffers.active.ydisp,a=t[1]-i,o=n[1]-i,s=Math.max(a,0),c=Math.min(o,e.rows-1);if(s>=e.rows||c<0){this.clear();return}this.hasSelection=!0,this.columnSelectMode=r,this.viewportStartRow=a,this.viewportEndRow=o,this.viewportCappedStartRow=s,this.viewportCappedEndRow=c,this.startCol=t[0],this.endCol=n[0]}isCellSelected(e,t,n){return this.hasSelection?(n-=e.buffer.active.viewportY,this.columnSelectMode?this.startCol<=this.endCol?t>=this.startCol&&n>=this.viewportCappedStartRow&&t=this.viewportCappedStartRow&&t>=this.endCol&&n<=this.viewportCappedEndRow:n>this.viewportStartRow&&n=this.startCol&&t=this.startCol):!1}};function Ui(){return new Hi}var Wi=`xterm-dom-renderer-owner-`,Gi=`xterm-rows`,Ki=`xterm-fg-`,qi=`xterm-bg-`,Ji=`xterm-focus`,Yi=`xterm-selection`,Xi=1,Zi=class extends A{constructor(e,t,n,r,i,a,o,s,c,l,u,d,f,p){super(),this._terminal=e,this._document=t,this._element=n,this._screenElement=r,this._viewportElement=i,this._helperContainer=a,this._linkifier2=o,this._charSizeService=c,this._optionsService=l,this._bufferService=u,this._coreService=d,this._coreBrowserService=f,this._themeService=p,this._terminalClass=Xi++,this._rowElements=[],this._selectionRenderModel=Ui(),this.onRequestRedraw=this._register(new N).event,this._rowContainer=this._document.createElement(`div`),this._rowContainer.classList.add(Gi),this._rowContainer.style.lineHeight=`normal`,this._rowContainer.setAttribute(`aria-hidden`,`true`),this._refreshRowElements(this._bufferService.cols,this._bufferService.rows),this._selectionContainer=this._document.createElement(`div`),this._selectionContainer.classList.add(Yi),this._selectionContainer.setAttribute(`aria-hidden`,`true`),this.dimensions=Li(),this._updateDimensions(),this._register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this._register(this._themeService.onChangeColors(e=>this._injectCss(e))),this._injectCss(this._themeService.colors),this._rowFactory=s.createInstance(zi,document),this._element.classList.add(Wi+this._terminalClass),this._screenElement.appendChild(this._rowContainer),this._screenElement.appendChild(this._selectionContainer),this._register(this._linkifier2.onShowLinkUnderline(e=>this._handleLinkHover(e))),this._register(this._linkifier2.onHideLinkUnderline(e=>this._handleLinkLeave(e))),this._register(k(()=>{this._element.classList.remove(Wi+this._terminalClass),this._rowContainer.remove(),this._selectionContainer.remove(),this._widthCache.dispose(),this._themeStyleElement.remove(),this._dimensionsStyleElement.remove()})),this._widthCache=new Vi(this._document,this._helperContainer),this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}_updateDimensions(){let e=this._coreBrowserService.dpr;this.dimensions.device.char.width=this._charSizeService.width*e,this.dimensions.device.char.height=Math.ceil(this._charSizeService.height*e),this.dimensions.device.cell.width=this.dimensions.device.char.width+Math.round(this._optionsService.rawOptions.letterSpacing),this.dimensions.device.cell.height=Math.floor(this.dimensions.device.char.height*this._optionsService.rawOptions.lineHeight),this.dimensions.device.char.left=0,this.dimensions.device.char.top=0,this.dimensions.device.canvas.width=this.dimensions.device.cell.width*this._bufferService.cols,this.dimensions.device.canvas.height=this.dimensions.device.cell.height*this._bufferService.rows,this.dimensions.css.canvas.width=Math.round(this.dimensions.device.canvas.width/e),this.dimensions.css.canvas.height=Math.round(this.dimensions.device.canvas.height/e),this.dimensions.css.cell.width=this.dimensions.css.canvas.width/this._bufferService.cols,this.dimensions.css.cell.height=this.dimensions.css.canvas.height/this._bufferService.rows;for(let e of this._rowElements)e.style.width=`${this.dimensions.css.canvas.width}px`,e.style.height=`${this.dimensions.css.cell.height}px`,e.style.lineHeight=`${this.dimensions.css.cell.height}px`,e.style.overflow=`hidden`;this._dimensionsStyleElement||(this._dimensionsStyleElement=this._document.createElement(`style`),this._screenElement.appendChild(this._dimensionsStyleElement));let t=`${this._terminalSelector} .${Gi} span { display: inline-block; height: 100%; vertical-align: top;}`;this._dimensionsStyleElement.textContent=t,this._selectionContainer.style.height=this._viewportElement.style.height,this._screenElement.style.width=`${this.dimensions.css.canvas.width}px`,this._screenElement.style.height=`${this.dimensions.css.canvas.height}px`}_injectCss(e){this._themeStyleElement||(this._themeStyleElement=this._document.createElement(`style`),this._screenElement.appendChild(this._themeStyleElement));let t=`${this._terminalSelector} .${Gi} { pointer-events: none; color: ${e.foreground.css}; font-family: ${this._optionsService.rawOptions.fontFamily}; font-size: ${this._optionsService.rawOptions.fontSize}px; font-kerning: none; white-space: pre}`;t+=`${this._terminalSelector} .${Gi} .xterm-dim { color: ${U.multiplyOpacity(e.foreground,.5).css};}`,t+=`${this._terminalSelector} span:not(.xterm-bold) { font-weight: ${this._optionsService.rawOptions.fontWeight};}${this._terminalSelector} span.xterm-bold { font-weight: ${this._optionsService.rawOptions.fontWeightBold};}${this._terminalSelector} span.xterm-italic { font-style: italic;}`;let n=`blink_underline_${this._terminalClass}`,r=`blink_bar_${this._terminalClass}`,i=`blink_block_${this._terminalClass}`;t+=`@keyframes ${n} { 50% { border-bottom-style: hidden; }}`,t+=`@keyframes ${r} { 50% { box-shadow: none; }}`,t+=`@keyframes ${i} { 0% { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css}; } 50% { background-color: inherit; color: ${e.cursor.css}; }}`,t+=`${this._terminalSelector} .${Gi}.${Ji} .xterm-cursor.xterm-cursor-blink.xterm-cursor-underline { animation: ${n} 1s step-end infinite;}${this._terminalSelector} .${Gi}.${Ji} .xterm-cursor.xterm-cursor-blink.xterm-cursor-bar { animation: ${r} 1s step-end infinite;}${this._terminalSelector} .${Gi}.${Ji} .xterm-cursor.xterm-cursor-blink.xterm-cursor-block { animation: ${i} 1s step-end infinite;}${this._terminalSelector} .${Gi} .xterm-cursor.xterm-cursor-block { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css};}${this._terminalSelector} .${Gi} .xterm-cursor.xterm-cursor-block:not(.xterm-cursor-blink) { background-color: ${e.cursor.css} !important; color: ${e.cursorAccent.css} !important;}${this._terminalSelector} .${Gi} .xterm-cursor.xterm-cursor-outline { outline: 1px solid ${e.cursor.css}; outline-offset: -1px;}${this._terminalSelector} .${Gi} .xterm-cursor.xterm-cursor-bar { box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${e.cursor.css} inset;}${this._terminalSelector} .${Gi} .xterm-cursor.xterm-cursor-underline { border-bottom: 1px ${e.cursor.css}; border-bottom-style: solid; height: calc(100% - 1px);}`,t+=`${this._terminalSelector} .${Yi} { position: absolute; top: 0; left: 0; z-index: 1; pointer-events: none;}${this._terminalSelector}.focus .${Yi} div { position: absolute; background-color: ${e.selectionBackgroundOpaque.css};}${this._terminalSelector} .${Yi} div { position: absolute; background-color: ${e.selectionInactiveBackgroundOpaque.css};}`;for(let[n,r]of e.ansi.entries())t+=`${this._terminalSelector} .${Ki}${n} { color: ${r.css}; }${this._terminalSelector} .${Ki}${n}.xterm-dim { color: ${U.multiplyOpacity(r,.5).css}; }${this._terminalSelector} .${qi}${n} { background-color: ${r.css}; }`;t+=`${this._terminalSelector} .${Ki}257 { color: ${U.opaque(e.background).css}; }${this._terminalSelector} .${Ki}257.xterm-dim { color: ${U.multiplyOpacity(U.opaque(e.background),.5).css}; }${this._terminalSelector} .${qi}257 { background-color: ${e.foreground.css}; }`,this._themeStyleElement.textContent=t}_setDefaultSpacing(){let e=this.dimensions.css.cell.width-this._widthCache.get(`W`,!1,!1);this._rowContainer.style.letterSpacing=`${e}px`,this._rowFactory.defaultSpacing=e}handleDevicePixelRatioChange(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}_refreshRowElements(e,t){for(let e=this._rowElements.length;e<=t;e++){let e=this._document.createElement(`div`);this._rowContainer.appendChild(e),this._rowElements.push(e)}for(;this._rowElements.length>t;)this._rowContainer.removeChild(this._rowElements.pop())}handleResize(e,t){this._refreshRowElements(e,t),this._updateDimensions(),this.handleSelectionChanged(this._selectionRenderModel.selectionStart,this._selectionRenderModel.selectionEnd,this._selectionRenderModel.columnSelectMode)}handleCharSizeChanged(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}handleBlur(){this._rowContainer.classList.remove(Ji),this.renderRows(0,this._bufferService.rows-1)}handleFocus(){this._rowContainer.classList.add(Ji),this.renderRows(this._bufferService.buffer.y,this._bufferService.buffer.y)}handleSelectionChanged(e,t,n){if(this._selectionContainer.replaceChildren(),this._rowFactory.handleSelectionChanged(e,t,n),this.renderRows(0,this._bufferService.rows-1),!e||!t||(this._selectionRenderModel.update(this._terminal,e,t,n),!this._selectionRenderModel.hasSelection))return;let r=this._selectionRenderModel.viewportStartRow,i=this._selectionRenderModel.viewportEndRow,a=this._selectionRenderModel.viewportCappedStartRow,o=this._selectionRenderModel.viewportCappedEndRow,s=this._document.createDocumentFragment();if(n){let n=e[0]>t[0];s.appendChild(this._createSelectionElement(a,n?t[0]:e[0],n?e[0]:t[0],o-a+1))}else{let n=r===a?e[0]:0,c=a===i?t[0]:this._bufferService.cols;s.appendChild(this._createSelectionElement(a,n,c));let l=o-a-1;if(s.appendChild(this._createSelectionElement(a+1,0,this._bufferService.cols,l)),a!==o){let e=i===o?t[0]:this._bufferService.cols;s.appendChild(this._createSelectionElement(o,0,e))}}this._selectionContainer.appendChild(s)}_createSelectionElement(e,t,n,r=1){let i=this._document.createElement(`div`),a=t*this.dimensions.css.cell.width,o=this.dimensions.css.cell.width*(n-t);return a+o>this.dimensions.css.canvas.width&&(o=this.dimensions.css.canvas.width-a),i.style.height=`${r*this.dimensions.css.cell.height}px`,i.style.top=`${e*this.dimensions.css.cell.height}px`,i.style.left=`${a}px`,i.style.width=`${o}px`,i}handleCursorMove(){}_handleOptionsChanged(){this._updateDimensions(),this._injectCss(this._themeService.colors),this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}clear(){for(let e of this._rowElements)e.replaceChildren()}renderRows(e,t){let n=this._bufferService.buffer,r=n.ybase+n.y,i=Math.min(n.x,this._bufferService.cols-1),a=this._coreService.decPrivateModes.cursorBlink??this._optionsService.rawOptions.cursorBlink,o=this._coreService.decPrivateModes.cursorStyle??this._optionsService.rawOptions.cursorStyle,s=this._optionsService.rawOptions.cursorInactiveStyle;for(let c=e;c<=t;c++){let e=c+n.ydisp,t=this._rowElements[c],l=n.lines.get(e);if(!t||!l)break;t.replaceChildren(...this._rowFactory.createRow(l,e,e===r,o,s,i,a,this.dimensions.css.cell.width,this._widthCache,-1,-1))}}get _terminalSelector(){return`.${Wi}${this._terminalClass}`}_handleLinkHover(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!0)}_handleLinkLeave(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!1)}_setCellUnderline(e,t,n,r,i,a){n<0&&(e=0),r<0&&(t=0);let o=this._bufferService.rows-1;n=Math.max(Math.min(n,o),0),r=Math.max(Math.min(r,o),0),i=Math.min(i,this._bufferService.cols);let s=this._bufferService.buffer,c=s.ybase+s.y,l=Math.min(s.x,i-1),u=this._optionsService.rawOptions.cursorBlink,d=this._optionsService.rawOptions.cursorStyle,f=this._optionsService.rawOptions.cursorInactiveStyle;for(let o=n;o<=r;++o){let p=o+s.ydisp,m=this._rowElements[o],h=s.lines.get(p);if(!m||!h)break;m.replaceChildren(...this._rowFactory.createRow(h,p,p===c,d,f,l,u,this.dimensions.css.cell.width,this._widthCache,a?o===n?e:0:-1,a?(o===r?t:i)-1:-1))}}};Zi=C([w(7,Ve),w(8,Je),w(9,O),w(10,D),w(11,ze),w(12,Ye),w(13,et)],Zi);var Qi=class extends A{constructor(e,t,n){super(),this._optionsService=n,this.width=0,this.height=0,this._onCharSizeChange=this._register(new N),this.onCharSizeChange=this._onCharSizeChange.event;try{this._measureStrategy=this._register(new ta(this._optionsService))}catch{this._measureStrategy=this._register(new ea(e,t,this._optionsService))}this._register(this._optionsService.onMultipleOptionChange([`fontFamily`,`fontSize`],()=>this.measure()))}get hasValidSize(){return this.width>0&&this.height>0}measure(){let e=this._measureStrategy.measure();(e.width!==this.width||e.height!==this.height)&&(this.width=e.width,this.height=e.height,this._onCharSizeChange.fire())}};Qi=C([w(2,O)],Qi);var $i=class extends A{constructor(){super(...arguments),this._result={width:0,height:0}}_validateAndSet(e,t){e!==void 0&&e>0&&t!==void 0&&t>0&&(this._result.width=e,this._result.height=t)}},ea=class extends $i{constructor(e,t,n){super(),this._document=e,this._parentElement=t,this._optionsService=n,this._measureElement=this._document.createElement(`span`),this._measureElement.classList.add(`xterm-char-measure-element`),this._measureElement.textContent=`W`.repeat(32),this._measureElement.setAttribute(`aria-hidden`,`true`),this._measureElement.style.whiteSpace=`pre`,this._measureElement.style.fontKerning=`none`,this._parentElement.appendChild(this._measureElement)}measure(){return this._measureElement.style.fontFamily=this._optionsService.rawOptions.fontFamily,this._measureElement.style.fontSize=`${this._optionsService.rawOptions.fontSize}px`,this._validateAndSet(Number(this._measureElement.offsetWidth)/32,Number(this._measureElement.offsetHeight)),this._result}},ta=class extends $i{constructor(e){super(),this._optionsService=e,this._canvas=new OffscreenCanvas(100,100),this._ctx=this._canvas.getContext(`2d`);let t=this._ctx.measureText(`W`);if(!(`width`in t&&`fontBoundingBoxAscent`in t&&`fontBoundingBoxDescent`in t))throw Error(`Required font metrics not supported`)}measure(){this._ctx.font=`${this._optionsService.rawOptions.fontSize}px ${this._optionsService.rawOptions.fontFamily}`;let e=this._ctx.measureText(`W`);return this._validateAndSet(e.width,e.fontBoundingBoxAscent+e.fontBoundingBoxDescent),this._result}},na=class extends A{constructor(e,t,n){super(),this._textarea=e,this._window=t,this.mainDocument=n,this._isFocused=!1,this._cachedIsFocused=void 0,this._screenDprMonitor=this._register(new ra(this._window)),this._onDprChange=this._register(new N),this.onDprChange=this._onDprChange.event,this._onWindowChange=this._register(new N),this.onWindowChange=this._onWindowChange.event,this._register(this.onWindowChange(e=>this._screenDprMonitor.setWindow(e))),this._register(M.forward(this._screenDprMonitor.onDprChange,this._onDprChange)),this._register(P(this._textarea,`focus`,()=>this._isFocused=!0)),this._register(P(this._textarea,`blur`,()=>this._isFocused=!1))}get window(){return this._window}set window(e){this._window!==e&&(this._window=e,this._onWindowChange.fire(this._window))}get dpr(){return this.window.devicePixelRatio}get isFocused(){return this._cachedIsFocused===void 0&&(this._cachedIsFocused=this._isFocused&&this._textarea.ownerDocument.hasFocus(),queueMicrotask(()=>this._cachedIsFocused=void 0)),this._cachedIsFocused}},ra=class extends A{constructor(e){super(),this._parentWindow=e,this._windowResizeListener=this._register(new jt),this._onDprChange=this._register(new N),this.onDprChange=this._onDprChange.event,this._outerListener=()=>this._setDprAndFireIfDiffers(),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._updateDpr(),this._setWindowResizeListener(),this._register(k(()=>this.clearListener()))}setWindow(e){this._parentWindow=e,this._setWindowResizeListener(),this._setDprAndFireIfDiffers()}_setWindowResizeListener(){this._windowResizeListener.value=P(this._parentWindow,`resize`,()=>this._setDprAndFireIfDiffers())}_setDprAndFireIfDiffers(){this._parentWindow.devicePixelRatio!==this._currentDevicePixelRatio&&this._onDprChange.fire(this._parentWindow.devicePixelRatio),this._updateDpr()}_updateDpr(){this._outerListener&&(this._resolutionMediaMatchList?.removeListener(this._outerListener),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._resolutionMediaMatchList=this._parentWindow.matchMedia(`screen and (resolution: ${this._parentWindow.devicePixelRatio}dppx)`),this._resolutionMediaMatchList.addListener(this._outerListener))}clearListener(){!this._resolutionMediaMatchList||!this._outerListener||(this._resolutionMediaMatchList.removeListener(this._outerListener),this._resolutionMediaMatchList=void 0,this._outerListener=void 0)}},ia=class extends A{constructor(){super(),this.linkProviders=[],this._register(k(()=>this.linkProviders.length=0))}registerLinkProvider(e){return this.linkProviders.push(e),{dispose:()=>{let t=this.linkProviders.indexOf(e);t!==-1&&this.linkProviders.splice(t,1)}}}};function aa(e,t,n){let r=n.getBoundingClientRect(),i=e.getComputedStyle(n),a=parseInt(i.getPropertyValue(`padding-left`)),o=parseInt(i.getPropertyValue(`padding-top`));return[t.clientX-r.left-a,t.clientY-r.top-o]}function oa(e,t,n,r,i,a,o,s,c){if(!a)return;let l=aa(e,t,n);if(l)return l[0]=Math.ceil((l[0]+(c?o/2:0))/o),l[1]=Math.ceil(l[1]/s),l[0]=Math.min(Math.max(l[0],1),r+ +!!c),l[1]=Math.min(Math.max(l[1],1),i),l}var sa=class{constructor(e,t){this._renderService=e,this._charSizeService=t}getCoords(e,t,n,r,i){return oa(window,e,t,n,r,this._charSizeService.hasValidSize,this._renderService.dimensions.css.cell.width,this._renderService.dimensions.css.cell.height,i)}getMouseReportCoords(e,t){let n=aa(window,e,t);if(this._charSizeService.hasValidSize)return n[0]=Math.min(Math.max(n[0],0),this._renderService.dimensions.css.canvas.width-1),n[1]=Math.min(Math.max(n[1],0),this._renderService.dimensions.css.canvas.height-1),{col:Math.floor(n[0]/this._renderService.dimensions.css.cell.width),row:Math.floor(n[1]/this._renderService.dimensions.css.cell.height),x:Math.floor(n[0]),y:Math.floor(n[1])}}};sa=C([w(0,Ze),w(1,Je)],sa);var ca=class{constructor(e,t){this._renderCallback=e,this._coreBrowserService=t,this._refreshCallbacks=[]}dispose(){this._animationFrame&&=(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),void 0)}addRefreshCallback(e){return this._refreshCallbacks.push(e),this._animationFrame||=this._coreBrowserService.window.requestAnimationFrame(()=>this._innerRefresh()),this._animationFrame}refresh(e,t,n){this._rowCount=n,e=e===void 0?0:e,t=t===void 0?this._rowCount-1:t,this._rowStart=this._rowStart===void 0?e:Math.min(this._rowStart,e),this._rowEnd=this._rowEnd===void 0?t:Math.max(this._rowEnd,t),!this._animationFrame&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._innerRefresh()))}_innerRefresh(){if(this._animationFrame=void 0,this._rowStart===void 0||this._rowEnd===void 0||this._rowCount===void 0){this._runRefreshCallbacks();return}let e=Math.max(this._rowStart,0),t=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(e,t),this._runRefreshCallbacks()}_runRefreshCallbacks(){for(let e of this._refreshCallbacks)e(0);this._refreshCallbacks=[]}},la={};pe(la,{getSafariVersion:()=>ga,isChromeOS:()=>Sa,isFirefox:()=>pa,isIpad:()=>va,isIphone:()=>ya,isLegacyEdge:()=>ma,isLinux:()=>xa,isMac:()=>_a,isNode:()=>ua,isSafari:()=>ha,isWindows:()=>ba});var ua=typeof process<`u`&&`title`in process,da=ua?`node`:navigator.userAgent,fa=ua?`node`:navigator.platform,pa=da.includes(`Firefox`),ma=da.includes(`Edge`),ha=/^((?!chrome|android).)*safari/i.test(da);function ga(){if(!ha)return 0;let e=da.match(/Version\/(\d+)/);return e===null||e.length<2?0:parseInt(e[1])}var _a=[`Macintosh`,`MacIntel`,`MacPPC`,`Mac68K`].includes(fa),va=fa===`iPad`,ya=fa===`iPhone`,ba=[`Windows`,`Win16`,`Win32`,`WinCE`].includes(fa),xa=fa.indexOf(`Linux`)>=0,Sa=/\bCrOS\b/.test(da),Ca=class{constructor(){this._tasks=[],this._i=0}enqueue(e){this._tasks.push(e),this._start()}flush(){for(;this._ii){r-t<-20&&console.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(r-t))}ms`),this._start();return}r=i}this.clear()}},wa=class extends Ca{_requestCallback(e){return setTimeout(()=>e(this._createDeadline(16)))}_cancelCallback(e){clearTimeout(e)}_createDeadline(e){let t=performance.now()+e;return{timeRemaining:()=>Math.max(0,t-performance.now())}}},Ta=class extends Ca{_requestCallback(e){return requestIdleCallback(e)}_cancelCallback(e){cancelIdleCallback(e)}},Ea=!ua&&`requestIdleCallback`in window?Ta:wa,Da=class{constructor(){this._queue=new Ea}set(e){this._queue.clear(),this._queue.enqueue(e)}flush(){this._queue.flush()}},Oa=class extends A{constructor(e,t,n,r,i,a,o,s,c){super(),this._rowCount=e,this._optionsService=n,this._charSizeService=r,this._coreService=i,this._coreBrowserService=s,this._renderer=this._register(new jt),this._pausedResizeTask=new Da,this._observerDisposable=this._register(new jt),this._isPaused=!1,this._needsFullRefresh=!1,this._isNextRenderRedrawOnly=!0,this._needsSelectionRefresh=!1,this._canvasWidth=0,this._canvasHeight=0,this._selectionState={start:void 0,end:void 0,columnSelectMode:!1},this._onDimensionsChange=this._register(new N),this.onDimensionsChange=this._onDimensionsChange.event,this._onRenderedViewportChange=this._register(new N),this.onRenderedViewportChange=this._onRenderedViewportChange.event,this._onRender=this._register(new N),this.onRender=this._onRender.event,this._onRefreshRequest=this._register(new N),this.onRefreshRequest=this._onRefreshRequest.event,this._renderDebouncer=new ca((e,t)=>this._renderRows(e,t),this._coreBrowserService),this._register(this._renderDebouncer),this._syncOutputHandler=new ka(this._coreBrowserService,this._coreService,()=>this._fullRefresh()),this._register(k(()=>this._syncOutputHandler.dispose())),this._register(this._coreBrowserService.onDprChange(()=>this.handleDevicePixelRatioChange())),this._register(o.onResize(()=>this._fullRefresh())),this._register(o.buffers.onBufferActivate(()=>this._renderer.value?.clear())),this._register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this._register(this._charSizeService.onCharSizeChange(()=>this.handleCharSizeChanged())),this._register(a.onDecorationRegistered(()=>this._fullRefresh())),this._register(a.onDecorationRemoved(()=>this._fullRefresh())),this._register(this._optionsService.onMultipleOptionChange([`customGlyphs`,`drawBoldTextInBrightColors`,`letterSpacing`,`lineHeight`,`fontFamily`,`fontSize`,`fontWeight`,`fontWeightBold`,`minimumContrastRatio`,`rescaleOverlappingGlyphs`],()=>{this.clear(),this.handleResize(o.cols,o.rows),this._fullRefresh()})),this._register(this._optionsService.onMultipleOptionChange([`cursorBlink`,`cursorStyle`],()=>this.refreshRows(o.buffer.y,o.buffer.y,!0))),this._register(c.onChangeColors(()=>this._fullRefresh())),this._registerIntersectionObserver(this._coreBrowserService.window,t),this._register(this._coreBrowserService.onWindowChange(e=>this._registerIntersectionObserver(e,t)))}get dimensions(){return this._renderer.value.dimensions}_registerIntersectionObserver(e,t){if(`IntersectionObserver`in e){let n=new e.IntersectionObserver(e=>this._handleIntersectionChange(e[e.length-1]),{threshold:0});n.observe(t),this._observerDisposable.value=k(()=>n.disconnect())}}_handleIntersectionChange(e){this._isPaused=e.isIntersecting===void 0?e.intersectionRatio===0:!e.isIntersecting,!this._isPaused&&!this._charSizeService.hasValidSize&&this._charSizeService.measure(),!this._isPaused&&this._needsFullRefresh&&(this._pausedResizeTask.flush(),this.refreshRows(0,this._rowCount-1),this._needsFullRefresh=!1)}refreshRows(e,t,n=!1){if(this._isPaused){this._needsFullRefresh=!0;return}if(this._coreService.decPrivateModes.synchronizedOutput){this._syncOutputHandler.bufferRows(e,t);return}let r=this._syncOutputHandler.flush();r&&(e=Math.min(e,r.start),t=Math.max(t,r.end)),n||(this._isNextRenderRedrawOnly=!1),this._renderDebouncer.refresh(e,t,this._rowCount)}_renderRows(e,t){if(this._renderer.value){if(this._coreService.decPrivateModes.synchronizedOutput){this._syncOutputHandler.bufferRows(e,t);return}e=Math.min(e,this._rowCount-1),t=Math.min(t,this._rowCount-1),this._renderer.value.renderRows(e,t),this._needsSelectionRefresh&&=(this._renderer.value.handleSelectionChanged(this._selectionState.start,this._selectionState.end,this._selectionState.columnSelectMode),!1),this._isNextRenderRedrawOnly||this._onRenderedViewportChange.fire({start:e,end:t}),this._onRender.fire({start:e,end:t}),this._isNextRenderRedrawOnly=!0}}resize(e,t){this._rowCount=t,this._fireOnCanvasResize()}_handleOptionsChanged(){this._renderer.value&&(this.refreshRows(0,this._rowCount-1),this._fireOnCanvasResize())}_fireOnCanvasResize(){this._renderer.value&&(this._renderer.value.dimensions.css.canvas.width===this._canvasWidth&&this._renderer.value.dimensions.css.canvas.height===this._canvasHeight||this._onDimensionsChange.fire(this._renderer.value.dimensions))}hasRenderer(){return!!this._renderer.value}setRenderer(e){this._renderer.value=e,this._renderer.value&&(this._renderer.value.onRequestRedraw(e=>this.refreshRows(e.start,e.end,!0)),this._needsSelectionRefresh=!0,this._fullRefresh())}addRefreshCallback(e){return this._renderDebouncer.addRefreshCallback(e)}_fullRefresh(){this._isPaused?this._needsFullRefresh=!0:this.refreshRows(0,this._rowCount-1)}clearTextureAtlas(){this._renderer.value&&(this._renderer.value.clearTextureAtlas?.(),this._fullRefresh())}handleDevicePixelRatioChange(){this._charSizeService.measure(),this._renderer.value&&(this._renderer.value.handleDevicePixelRatioChange(),this.refreshRows(0,this._rowCount-1))}handleResize(e,t){this._renderer.value&&(this._isPaused?this._pausedResizeTask.set(()=>this._renderer.value?.handleResize(e,t)):this._renderer.value.handleResize(e,t),this._fullRefresh())}handleCharSizeChanged(){this._renderer.value?.handleCharSizeChanged()}handleBlur(){this._renderer.value?.handleBlur()}handleFocus(){this._renderer.value?.handleFocus()}handleSelectionChanged(e,t,n){this._selectionState.start=e,this._selectionState.end=t,this._selectionState.columnSelectMode=n,this._renderer.value?.handleSelectionChanged(e,t,n)}handleCursorMove(){this._renderer.value?.handleCursorMove()}clear(){this._renderer.value?.clear()}};Oa=C([w(2,O),w(3,Je),w(4,ze),w(5,Ge),w(6,D),w(7,Ye),w(8,et)],Oa);var ka=class{constructor(e,t,n){this._coreBrowserService=e,this._coreService=t,this._onTimeout=n,this._start=0,this._end=0,this._isBuffering=!1}bufferRows(e,t){this._isBuffering?(this._start=Math.min(this._start,e),this._end=Math.max(this._end,t)):(this._start=e,this._end=t,this._isBuffering=!0),this._timeout===void 0&&(this._timeout=this._coreBrowserService.window.setTimeout(()=>{this._timeout=void 0,this._coreService.decPrivateModes.synchronizedOutput=!1,this._onTimeout()},1e3))}flush(){if(this._timeout!==void 0&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0),!this._isBuffering)return;let e={start:this._start,end:this._end};return this._isBuffering=!1,e}dispose(){this._timeout!==void 0&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0)}};function Aa(e,t,n,r){let i=n.buffer.x,a=n.buffer.y;if(!n.buffer.hasScrollback)return Na(i,a,e,t,n,r)+Pa(a,t,n,r)+Fa(i,a,e,t,n,r);let o;if(a===t)return o=i>e?`D`:`C`,Ha(Math.abs(i-e),Va(o,r));o=a>t?`D`:`C`;let s=Math.abs(a-t);return Ha(Ma(a>t?e:i,n)+(s-1)*n.cols+1+ja(a>t?i:e,n),Va(o,r))}function ja(e,t){return e-1}function Ma(e,t){return t.cols-e}function Na(e,t,n,r,i,a){return Pa(t,r,i,a).length===0?``:Ha(Ba(e,t,e,t-La(t,i),!1,i).length,Va(`D`,a))}function Pa(e,t,n,r){let i=e-La(e,n),a=t-La(t,n);return Ha(Math.abs(i-a)-Ia(e,t,n),Va(za(e,t),r))}function Fa(e,t,n,r,i,a){let o;o=Pa(t,r,i,a).length>0?r-La(r,i):t;let s=r,c=Ra(e,t,n,r,i,a);return Ha(Ba(e,o,n,s,c===`C`,i).length,Va(c,a))}function Ia(e,t,n){let r=0,i=e-La(e,n),a=t-La(t,n);for(let o=0;o=0&&e0?r-La(r,i):t,e=n&&ot?`A`:`B`}function Ba(e,t,n,r,i,a){let o=e,s=t,c=``;for(;(o!==n||s!==r)&&s>=0&&sa.cols-1?(c+=a.buffer.translateBufferLineToString(s,!1,e,o),o=0,e=0,s++):!i&&o<0&&(c+=a.buffer.translateBufferLineToString(s,!1,0,e+1),o=a.cols-1,e=o,s--);return c+a.buffer.translateBufferLineToString(s,!1,e,o)}function Va(e,t){let n=t?`O`:`[`;return L.ESC+n+e}function Ha(e,t){e=Math.floor(e);let n=``;for(let r=0;rthis._bufferService.cols?e%this._bufferService.cols===0?[this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)-1]:[e%this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)]:[e,this.selectionStart[1]]}if(this.selectionStartLength&&this.selectionEnd[1]===this.selectionStart[1]){let e=this.selectionStart[0]+this.selectionStartLength;return e>this._bufferService.cols?[e%this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)]:[Math.max(e,this.selectionEnd[0]),this.selectionEnd[1]]}return this.selectionEnd}}areSelectionValuesReversed(){let e=this.selectionStart,t=this.selectionEnd;return!e||!t?!1:e[1]>t[1]||e[1]===t[1]&&e[0]>t[0]}handleTrim(e){return this.selectionStart&&(this.selectionStart[1]-=e),this.selectionEnd&&(this.selectionEnd[1]-=e),this.selectionEnd&&this.selectionEnd[1]<0?(this.clearSelection(),!0):(this.selectionStart&&this.selectionStart[1]<0&&(this.selectionStart[1]=0),!1)}};function Wa(e,t){if(e.start.y>e.end.y)throw Error(`Buffer range end (${e.end.x}, ${e.end.y}) cannot be before start (${e.start.x}, ${e.start.y})`);return t*(e.end.y-e.start.y)+(e.end.x-e.start.x+1)}var Ga=50,Ka=15,qa=50,Ja=500,Ya=RegExp(`\xA0`,`g`),Xa=class extends A{constructor(e,t,n,r,i,a,o,s,c){super(),this._element=e,this._screenElement=t,this._linkifier=n,this._bufferService=r,this._coreService=i,this._mouseService=a,this._optionsService=o,this._renderService=s,this._coreBrowserService=c,this._dragScrollAmount=0,this._enabled=!0,this._workCell=new Me,this._mouseDownTimeStamp=0,this._oldHasSelection=!1,this._oldSelectionStart=void 0,this._oldSelectionEnd=void 0,this._onLinuxMouseSelection=this._register(new N),this.onLinuxMouseSelection=this._onLinuxMouseSelection.event,this._onRedrawRequest=this._register(new N),this.onRequestRedraw=this._onRedrawRequest.event,this._onSelectionChange=this._register(new N),this.onSelectionChange=this._onSelectionChange.event,this._onRequestScrollLines=this._register(new N),this.onRequestScrollLines=this._onRequestScrollLines.event,this._mouseMoveListener=e=>this._handleMouseMove(e),this._mouseUpListener=e=>this._handleMouseUp(e),this._coreService.onUserInput(()=>{this.hasSelection&&this.clearSelection()}),this._trimListener=this._bufferService.buffer.lines.onTrim(e=>this._handleTrim(e)),this._register(this._bufferService.buffers.onBufferActivate(e=>this._handleBufferActivate(e))),this.enable(),this._model=new Ua(this._bufferService),this._activeSelectionMode=0,this._register(k(()=>{this._removeMouseDownListeners()})),this._register(this._bufferService.onResize(e=>{e.rowsChanged&&this.clearSelection()}))}reset(){this.clearSelection()}disable(){this.clearSelection(),this._enabled=!1}enable(){this._enabled=!0}get selectionStart(){return this._model.finalSelectionStart}get selectionEnd(){return this._model.finalSelectionEnd}get hasSelection(){let e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;return!e||!t?!1:e[0]!==t[0]||e[1]!==t[1]}get selectionText(){let e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;if(!e||!t)return``;let n=this._bufferService.buffer,r=[];if(this._activeSelectionMode===3){if(e[0]===t[0])return``;let i=e[0]e.replace(Ya,` `)).join(ba?`\r +`:` +`)}clearSelection(){this._model.clearSelection(),this._removeMouseDownListeners(),this.refresh(),this._onSelectionChange.fire()}refresh(e){this._refreshAnimationFrame||=this._coreBrowserService.window.requestAnimationFrame(()=>this._refresh()),xa&&e&&this.selectionText.length&&this._onLinuxMouseSelection.fire(this.selectionText)}_refresh(){this._refreshAnimationFrame=void 0,this._onRedrawRequest.fire({start:this._model.finalSelectionStart,end:this._model.finalSelectionEnd,columnSelectMode:this._activeSelectionMode===3})}_isClickInSelection(e){let t=this._getMouseBufferCoords(e),n=this._model.finalSelectionStart,r=this._model.finalSelectionEnd;return!n||!r||!t?!1:this._areCoordsInSelection(t,n,r)}isCellInSelection(e,t){let n=this._model.finalSelectionStart,r=this._model.finalSelectionEnd;return!n||!r?!1:this._areCoordsInSelection([e,t],n,r)}_areCoordsInSelection(e,t,n){return e[1]>t[1]&&e[1]=t[0]&&e[0]=t[0]}_selectWordAtCursor(e,t){let n=this._linkifier.currentLink?.link?.range;if(n)return this._model.selectionStart=[n.start.x-1,n.start.y-1],this._model.selectionStartLength=Wa(n,this._bufferService.cols),this._model.selectionEnd=void 0,!0;let r=this._getMouseBufferCoords(e);return r?(this._selectWordAt(r,t),this._model.selectionEnd=void 0,!0):!1}selectAll(){this._model.isSelectAllActive=!0,this.refresh(),this._onSelectionChange.fire()}selectLines(e,t){this._model.clearSelection(),e=Math.max(e,0),t=Math.min(t,this._bufferService.buffer.lines.length-1),this._model.selectionStart=[0,e],this._model.selectionEnd=[this._bufferService.cols,t],this.refresh(),this._onSelectionChange.fire()}_handleTrim(e){this._model.handleTrim(e)&&this.refresh()}_getMouseBufferCoords(e){let t=this._mouseService.getCoords(e,this._screenElement,this._bufferService.cols,this._bufferService.rows,!0);if(t)return t[0]--,t[1]--,t[1]+=this._bufferService.buffer.ydisp,t}_getMouseEventScrollAmount(e){let t=aa(this._coreBrowserService.window,e,this._screenElement)[1],n=this._renderService.dimensions.css.canvas.height;return t>=0&&t<=n?0:(t>n&&(t-=n),t=Math.min(Math.max(t,-Ga),Ga),t/=Ga,t/Math.abs(t)+Math.round(t*(Ka-1)))}shouldForceSelection(e){return _a?e.altKey&&this._optionsService.rawOptions.macOptionClickForcesSelection:e.shiftKey}handleMouseDown(e){if(this._mouseDownTimeStamp=e.timeStamp,!(e.button===2&&this.hasSelection)&&e.button===0){if(!this._enabled){if(!this.shouldForceSelection(e))return;e.stopPropagation()}e.preventDefault(),this._dragScrollAmount=0,this._enabled&&e.shiftKey?this._handleIncrementalClick(e):e.detail===1?this._handleSingleClick(e):e.detail===2?this._handleDoubleClick(e):e.detail===3&&this._handleTripleClick(e),this._addMouseDownListeners(),this.refresh(!0)}}_addMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.addEventListener(`mousemove`,this._mouseMoveListener),this._screenElement.ownerDocument.addEventListener(`mouseup`,this._mouseUpListener)),this._dragScrollIntervalTimer=this._coreBrowserService.window.setInterval(()=>this._dragScroll(),qa)}_removeMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.removeEventListener(`mousemove`,this._mouseMoveListener),this._screenElement.ownerDocument.removeEventListener(`mouseup`,this._mouseUpListener)),this._coreBrowserService.window.clearInterval(this._dragScrollIntervalTimer),this._dragScrollIntervalTimer=void 0}_handleIncrementalClick(e){this._model.selectionStart&&(this._model.selectionEnd=this._getMouseBufferCoords(e))}_handleSingleClick(e){if(this._model.selectionStartLength=0,this._model.isSelectAllActive=!1,this._activeSelectionMode=this.shouldColumnSelect(e)?3:0,this._model.selectionStart=this._getMouseBufferCoords(e),!this._model.selectionStart)return;this._model.selectionEnd=void 0;let t=this._bufferService.buffer.lines.get(this._model.selectionStart[1]);t&&t.length!==this._model.selectionStart[0]&&t.hasWidth(this._model.selectionStart[0])===0&&this._model.selectionStart[0]++}_handleDoubleClick(e){this._selectWordAtCursor(e,!0)&&(this._activeSelectionMode=1)}_handleTripleClick(e){let t=this._getMouseBufferCoords(e);t&&(this._activeSelectionMode=2,this._selectLineAt(t[1]))}shouldColumnSelect(e){return e.altKey&&!(_a&&this._optionsService.rawOptions.macOptionClickForcesSelection)}_handleMouseMove(e){if(e.stopImmediatePropagation(),!this._model.selectionStart)return;let t=this._model.selectionEnd?[this._model.selectionEnd[0],this._model.selectionEnd[1]]:null;if(this._model.selectionEnd=this._getMouseBufferCoords(e),!this._model.selectionEnd){this.refresh(!0);return}this._activeSelectionMode===2?this._model.selectionEnd[1]0?this._model.selectionEnd[0]=this._bufferService.cols:this._dragScrollAmount<0&&(this._model.selectionEnd[0]=0));let n=this._bufferService.buffer;if(this._model.selectionEnd[1]0?(this._activeSelectionMode!==3&&(this._model.selectionEnd[0]=this._bufferService.cols),this._model.selectionEnd[1]=Math.min(e.ydisp+this._bufferService.rows,e.lines.length-1)):(this._activeSelectionMode!==3&&(this._model.selectionEnd[0]=0),this._model.selectionEnd[1]=e.ydisp),this.refresh()}}_handleMouseUp(e){let t=e.timeStamp-this._mouseDownTimeStamp;if(this._removeMouseDownListeners(),this.selectionText.length<=1&&tthis._handleTrim(e))}_convertViewportColToCharacterIndex(e,t){let n=t;for(let r=0;t>=r;r++){let i=e.loadCell(r,this._workCell).getChars().length;this._workCell.getWidth()===0?n--:i>1&&t!==r&&(n+=i-1)}return n}setSelection(e,t,n){this._model.clearSelection(),this._removeMouseDownListeners(),this._model.selectionStart=[e,t],this._model.selectionStartLength=n,this.refresh(),this._fireEventIfSelectionChanged()}rightClickSelect(e){this._isClickInSelection(e)||(this._selectWordAtCursor(e,!1)&&this.refresh(!0),this._fireEventIfSelectionChanged())}_getWordAt(e,t,n=!0,r=!0){if(e[0]>=this._bufferService.cols)return;let i=this._bufferService.buffer,a=i.lines.get(e[1]);if(!a)return;let o=i.translateBufferLineToString(e[1],!1),s=this._convertViewportColToCharacterIndex(a,e[0]),c=s,l=e[0]-s,u=0,d=0,f=0,p=0;if(o.charAt(s)===` `){for(;s>0&&o.charAt(s-1)===` `;)s--;for(;c1&&(p+=r-1,c+=r-1);t>0&&s>0&&!this._isCharWordSeparator(a.loadCell(t-1,this._workCell));){a.loadCell(t-1,this._workCell);let e=this._workCell.getChars().length;this._workCell.getWidth()===0?(u++,t--):e>1&&(f+=e-1,s-=e-1),s--,t--}for(;n1&&(p+=e-1,c+=e-1),c++,n++}}c++;let m=s+l-u+f,h=Math.min(this._bufferService.cols,c-s+u+d-f-p);if(!(!t&&o.slice(s,c).trim()===``)){if(n&&m===0&&a.getCodePoint(0)!==32){let t=i.lines.get(e[1]-1);if(t&&a.isWrapped&&t.getCodePoint(this._bufferService.cols-1)!==32){let t=this._getWordAt([this._bufferService.cols-1,e[1]-1],!1,!0,!1);if(t){let e=this._bufferService.cols-t.start;m-=e,h+=e}}}if(r&&m+h===this._bufferService.cols&&a.getCodePoint(this._bufferService.cols-1)!==32){let t=i.lines.get(e[1]+1);if(t?.isWrapped&&t.getCodePoint(0)!==32){let t=this._getWordAt([0,e[1]+1],!1,!1,!0);t&&(h+=t.length)}}return{start:m,length:h}}}_selectWordAt(e,t){let n=this._getWordAt(e,t);if(n){for(;n.start<0;)n.start+=this._bufferService.cols,e[1]--;this._model.selectionStart=[n.start,e[1]],this._model.selectionStartLength=n.length}}_selectToWordAt(e){let t=this._getWordAt(e,!0);if(t){let n=e[1];for(;t.start<0;)t.start+=this._bufferService.cols,n--;if(!this._model.areSelectionValuesReversed())for(;t.start+t.length>this._bufferService.cols;)t.length-=this._bufferService.cols,n++;this._model.selectionEnd=[this._model.areSelectionValuesReversed()?t.start:t.start+t.length,n]}}_isCharWordSeparator(e){return e.getWidth()!==0&&this._optionsService.rawOptions.wordSeparator.indexOf(e.getChars())>=0}_selectLineAt(e){let t=this._bufferService.buffer.getWrappedRangeForLine(e),n={start:{x:0,y:t.first},end:{x:this._bufferService.cols-1,y:t.last}};this._model.selectionStart=[0,t.first],this._model.selectionEnd=void 0,this._model.selectionStartLength=Wa(n,this._bufferService.cols)}};Xa=C([w(3,D),w(4,ze),w(5,Xe),w(6,O),w(7,Ze),w(8,Ye)],Xa);var Za=class{constructor(){this._data={}}set(e,t,n){this._data[e]||(this._data[e]={}),this._data[e][t]=n}get(e,t){return this._data[e]?this._data[e][t]:void 0}clear(){this._data={}}},Qa=class{constructor(){this._color=new Za,this._css=new Za}setCss(e,t,n){this._css.set(e,t,n)}getCss(e,t){return this._css.get(e,t)}setColor(e,t,n){this._color.set(e,t,n)}getColor(e,t){return this._color.get(e,t)}clear(){this._color.clear(),this._css.clear()}},K=Object.freeze((()=>{let e=[W.toColor(`#2e3436`),W.toColor(`#cc0000`),W.toColor(`#4e9a06`),W.toColor(`#c4a000`),W.toColor(`#3465a4`),W.toColor(`#75507b`),W.toColor(`#06989a`),W.toColor(`#d3d7cf`),W.toColor(`#555753`),W.toColor(`#ef2929`),W.toColor(`#8ae234`),W.toColor(`#fce94f`),W.toColor(`#729fcf`),W.toColor(`#ad7fa8`),W.toColor(`#34e2e2`),W.toColor(`#eeeeec`)],t=[0,95,135,175,215,255];for(let n=0;n<216;n++){let r=t[n/36%6|0],i=t[n/6%6|0],a=t[n%6];e.push({css:H.toCss(r,i,a),rgba:H.toRgba(r,i,a)})}for(let t=0;t<24;t++){let n=8+t*10;e.push({css:H.toCss(n,n,n),rgba:H.toRgba(n,n,n)})}return e})()),$a=W.toColor(`#ffffff`),eo=W.toColor(`#000000`),to=W.toColor(`#ffffff`),no=eo,ro={css:`rgba(255, 255, 255, 0.3)`,rgba:4294967117},io=$a,ao=class extends A{constructor(e){super(),this._optionsService=e,this._contrastCache=new Qa,this._halfContrastCache=new Qa,this._onChangeColors=this._register(new N),this.onChangeColors=this._onChangeColors.event,this._colors={foreground:$a,background:eo,cursor:to,cursorAccent:no,selectionForeground:void 0,selectionBackgroundTransparent:ro,selectionBackgroundOpaque:U.blend(eo,ro),selectionInactiveBackgroundTransparent:ro,selectionInactiveBackgroundOpaque:U.blend(eo,ro),scrollbarSliderBackground:U.opacity($a,.2),scrollbarSliderHoverBackground:U.opacity($a,.4),scrollbarSliderActiveBackground:U.opacity($a,.5),overviewRulerBorder:$a,ansi:K.slice(),contrastCache:this._contrastCache,halfContrastCache:this._halfContrastCache},this._updateRestoreColors(),this._setTheme(this._optionsService.rawOptions.theme),this._register(this._optionsService.onSpecificOptionChange(`minimumContrastRatio`,()=>this._contrastCache.clear())),this._register(this._optionsService.onSpecificOptionChange(`theme`,()=>this._setTheme(this._optionsService.rawOptions.theme)))}get colors(){return this._colors}_setTheme(e={}){let t=this._colors;if(t.foreground=q(e.foreground,$a),t.background=q(e.background,eo),t.cursor=U.blend(t.background,q(e.cursor,to)),t.cursorAccent=U.blend(t.background,q(e.cursorAccent,no)),t.selectionBackgroundTransparent=q(e.selectionBackground,ro),t.selectionBackgroundOpaque=U.blend(t.background,t.selectionBackgroundTransparent),t.selectionInactiveBackgroundTransparent=q(e.selectionInactiveBackground,t.selectionBackgroundTransparent),t.selectionInactiveBackgroundOpaque=U.blend(t.background,t.selectionInactiveBackgroundTransparent),t.selectionForeground=e.selectionForeground?q(e.selectionForeground,Oi):void 0,t.selectionForeground===Oi&&(t.selectionForeground=void 0),U.isOpaque(t.selectionBackgroundTransparent)&&(t.selectionBackgroundTransparent=U.opacity(t.selectionBackgroundTransparent,.3)),U.isOpaque(t.selectionInactiveBackgroundTransparent)&&(t.selectionInactiveBackgroundTransparent=U.opacity(t.selectionInactiveBackgroundTransparent,.3)),t.scrollbarSliderBackground=q(e.scrollbarSliderBackground,U.opacity(t.foreground,.2)),t.scrollbarSliderHoverBackground=q(e.scrollbarSliderHoverBackground,U.opacity(t.foreground,.4)),t.scrollbarSliderActiveBackground=q(e.scrollbarSliderActiveBackground,U.opacity(t.foreground,.5)),t.overviewRulerBorder=q(e.overviewRulerBorder,io),t.ansi=K.slice(),t.ansi[0]=q(e.black,K[0]),t.ansi[1]=q(e.red,K[1]),t.ansi[2]=q(e.green,K[2]),t.ansi[3]=q(e.yellow,K[3]),t.ansi[4]=q(e.blue,K[4]),t.ansi[5]=q(e.magenta,K[5]),t.ansi[6]=q(e.cyan,K[6]),t.ansi[7]=q(e.white,K[7]),t.ansi[8]=q(e.brightBlack,K[8]),t.ansi[9]=q(e.brightRed,K[9]),t.ansi[10]=q(e.brightGreen,K[10]),t.ansi[11]=q(e.brightYellow,K[11]),t.ansi[12]=q(e.brightBlue,K[12]),t.ansi[13]=q(e.brightMagenta,K[13]),t.ansi[14]=q(e.brightCyan,K[14]),t.ansi[15]=q(e.brightWhite,K[15]),e.extendedAnsi){let n=Math.min(t.ansi.length-16,e.extendedAnsi.length);for(let r=0;re.index-t.index),r=[];for(let t of n){let n=this._services.get(t.id);if(!n)throw Error(`[createInstance] ${e.name} depends on UNKNOWN service ${t.id._id}.`);r.push(n)}let i=n.length>0?n[0].index:t.length;if(t.length!==i)throw Error(`[createInstance] First service dependency of ${e.name} at position ${i+1} conflicts with ${t.length} static arguments`);return new e(...t,...r)}},co={trace:0,debug:1,info:2,warn:3,error:4,off:5},lo=`xterm.js: `,uo=class extends A{constructor(e){super(),this._optionsService=e,this._logLevel=5,this._updateLogLevel(),this._register(this._optionsService.onSpecificOptionChange(`logLevel`,()=>this._updateLogLevel())),fo=this}get logLevel(){return this._logLevel}_updateLogLevel(){this._logLevel=co[this._optionsService.rawOptions.logLevel]}_evalLazyOptionalParams(e){for(let t=0;tthis._length)for(let t=this._length;t=e;t--)this._array[this._getCyclicIndex(t+n.length)]=this._array[this._getCyclicIndex(t)];for(let t=0;tthis._maxLength){let e=this._length+n.length-this._maxLength;this._startIndex+=e,this._length=this._maxLength,this.onTrimEmitter.fire(e)}else this._length+=n.length}trimStart(e){e>this._length&&(e=this._length),this._startIndex+=e,this._length-=e,this.onTrimEmitter.fire(e)}shiftElements(e,t,n){if(!(t<=0)){if(e<0||e>=this._length)throw Error(`start argument out of range`);if(e+n<0)throw Error(`Cannot shift elements in list beyond index 0`);if(n>0){for(let r=t-1;r>=0;r--)this.set(e+r+n,this.get(e+r));let r=e+t+n-this._length;if(r>0)for(this._length+=r;this._length>this._maxLength;)this._length--,this._startIndex++,this.onTrimEmitter.fire(1)}else for(let r=0;r>22,t&2097152?this._combined[e].charCodeAt(this._combined[e].length-1):n]}set(e,t){this._data[e*J+1]=t[0],t[1].length>1?(this._combined[e]=t[1],this._data[e*J+0]=e|2097152|t[2]<<22):this._data[e*J+0]=t[1].charCodeAt(0)|t[2]<<22}getWidth(e){return this._data[e*J+0]>>22}hasWidth(e){return this._data[e*J+0]&12582912}getFg(e){return this._data[e*J+1]}getBg(e){return this._data[e*J+2]}hasContent(e){return this._data[e*J+0]&4194303}getCodePoint(e){let t=this._data[e*J+0];return t&2097152?this._combined[e].charCodeAt(this._combined[e].length-1):t&2097151}isCombined(e){return this._data[e*J+0]&2097152}getString(e){let t=this._data[e*J+0];return t&2097152?this._combined[e]:t&2097151?we(t&2097151):``}isProtected(e){return this._data[e*J+2]&536870912}loadCell(e,t){return mo=e*J,t.content=this._data[mo+0],t.fg=this._data[mo+1],t.bg=this._data[mo+2],t.content&2097152&&(t.combinedData=this._combined[e]),t.bg&268435456&&(t.extended=this._extendedAttrs[e]),t}setCell(e,t){t.content&2097152&&(this._combined[e]=t.combinedData),t.bg&268435456&&(this._extendedAttrs[e]=t.extended),this._data[e*J+0]=t.content,this._data[e*J+1]=t.fg,this._data[e*J+2]=t.bg}setCellFromCodepoint(e,t,n,r){r.bg&268435456&&(this._extendedAttrs[e]=r.extended),this._data[e*J+0]=t|n<<22,this._data[e*J+1]=r.fg,this._data[e*J+2]=r.bg}addCodepointToCell(e,t,n){let r=this._data[e*J+0];r&2097152?this._combined[e]+=we(t):r&2097151?(this._combined[e]=we(r&2097151)+we(t),r&=-2097152,r|=2097152):r=t|1<<22,n&&(r&=-12582913,r|=n<<22),this._data[e*J+0]=r}insertCells(e,t,n){if(e%=this.length,e&&this.getWidth(e-1)===2&&this.setCellFromCodepoint(e-1,0,1,n),t=0;--n)this.setCell(e+t+n,this.loadCell(e+n,r));for(let r=0;rthis.length){if(this._data.buffer.byteLength>=n*4)this._data=new Uint32Array(this._data.buffer,0,n);else{let e=new Uint32Array(n);e.set(this._data),this._data=e}for(let n=this.length;n=e&&delete this._combined[r]}let r=Object.keys(this._extendedAttrs);for(let t=0;t=e&&delete this._extendedAttrs[n]}}return this.length=e,n*4*ho=0;--e)if(this._data[e*J+0]&4194303)return e+(this._data[e*J+0]>>22);return 0}getNoBgTrimmedLength(){for(let e=this.length-1;e>=0;--e)if(this._data[e*J+0]&4194303||this._data[e*J+2]&50331648)return e+(this._data[e*J+0]>>22);return 0}copyCellsFrom(e,t,n,r,i){let a=e._data;if(i)for(let i=r-1;i>=0;i--){for(let e=0;e=t&&(this._combined[i-t+n]=e._combined[i])}}translateToString(e,t,n,r){t??=0,n??=this.length,e&&(n=Math.min(n,this.getTrimmedLength())),r&&(r.length=0);let i=``;for(;t>22||1}return r&&r.push(t),i}};function _o(e,t,n,r,i,a){let o=[];for(let s=0;s=s&&r0&&(e>d||u[e].getTrimmedLength()===0);e--)h++;h>0&&(o.push(s+u.length-h),o.push(h)),s+=u.length-1}return o}function vo(e,t){let n=[],r=0,i=t[r],a=0;for(let o=0;oxo(e,r,t)).reduce((e,t)=>e+t),a=0,o=0,s=0;for(;sc&&(a-=c,o++);let l=e[o].getWidth(a-1)===2;l&&a--;let u=l?n-1:n;r.push(u),s+=u}return r}function xo(e,t,n){if(t===e.length-1)return e[t].getTrimmedLength();let r=!e[t].hasContent(n-1)&&e[t].getWidth(n-1)===1,i=e[t+1].getWidth(0)===2;return r&&i?n-1:n}var So=class e{constructor(t){this.line=t,this.isDisposed=!1,this._disposables=[],this._id=e._nextId++,this._onDispose=this.register(new N),this.onDispose=this._onDispose.event}get id(){return this._id}dispose(){this.isDisposed||(this.isDisposed=!0,this.line=-1,this._onDispose.fire(),Dt(this._disposables),this._disposables.length=0)}register(e){return this._disposables.push(e),e}};So._nextId=1;var Co=So,X={},wo=X.B;X[0]={"`":`◆`,a:`▒`,b:`␉`,c:`␌`,d:`␍`,e:`␊`,f:`°`,g:`±`,h:`␤`,i:`␋`,j:`┘`,k:`┐`,l:`┌`,m:`└`,n:`┼`,o:`⎺`,p:`⎻`,q:`─`,r:`⎼`,s:`⎽`,t:`├`,u:`┤`,v:`┴`,w:`┬`,x:`│`,y:`≤`,z:`≥`,"{":`π`,"|":`≠`,"}":`£`,"~":`·`},X.A={"#":`£`},X.B=void 0,X[4]={"#":`£`,"@":`¾`,"[":`ij`,"\\":`½`,"]":`|`,"{":`¨`,"|":`f`,"}":`¼`,"~":`´`},X.C=X[5]={"[":`Ä`,"\\":`Ö`,"]":`Å`,"^":`Ü`,"`":`é`,"{":`ä`,"|":`ö`,"}":`å`,"~":`ü`},X.R={"#":`£`,"@":`à`,"[":`°`,"\\":`ç`,"]":`§`,"{":`é`,"|":`ù`,"}":`è`,"~":`¨`},X.Q={"@":`à`,"[":`â`,"\\":`ç`,"]":`ê`,"^":`î`,"`":`ô`,"{":`é`,"|":`ù`,"}":`è`,"~":`û`},X.K={"@":`§`,"[":`Ä`,"\\":`Ö`,"]":`Ü`,"{":`ä`,"|":`ö`,"}":`ü`,"~":`ß`},X.Y={"#":`£`,"@":`§`,"[":`°`,"\\":`ç`,"]":`é`,"`":`ù`,"{":`à`,"|":`ò`,"}":`è`,"~":`ì`},X.E=X[6]={"@":`Ä`,"[":`Æ`,"\\":`Ø`,"]":`Å`,"^":`Ü`,"`":`ä`,"{":`æ`,"|":`ø`,"}":`å`,"~":`ü`},X.Z={"#":`£`,"@":`§`,"[":`¡`,"\\":`Ñ`,"]":`¿`,"{":`°`,"|":`ñ`,"}":`ç`},X.H=X[7]={"@":`É`,"[":`Ä`,"\\":`Ö`,"]":`Å`,"^":`Ü`,"`":`é`,"{":`ä`,"|":`ö`,"}":`å`,"~":`ü`},X[`=`]={"#":`ù`,"@":`à`,"[":`é`,"\\":`ç`,"]":`ê`,"^":`î`,_:`è`,"`":`ô`,"{":`ä`,"|":`ö`,"}":`ü`,"~":`û`};var To=4294967295,Eo=class{constructor(e,t,n){this._hasScrollback=e,this._optionsService=t,this._bufferService=n,this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.tabs={},this.savedY=0,this.savedX=0,this.savedCurAttrData=Y.clone(),this.savedCharset=wo,this.markers=[],this._nullCell=Me.fromCharData([0,Oe,1,0]),this._whitespaceCell=Me.fromCharData([0,ke,1,32]),this._isClearing=!1,this._memoryCleanupQueue=new Ea,this._memoryCleanupPosition=0,this._cols=this._bufferService.cols,this._rows=this._bufferService.rows,this.lines=new po(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}getNullCell(e){return e?(this._nullCell.fg=e.fg,this._nullCell.bg=e.bg,this._nullCell.extended=e.extended):(this._nullCell.fg=0,this._nullCell.bg=0,this._nullCell.extended=new je),this._nullCell}getWhitespaceCell(e){return e?(this._whitespaceCell.fg=e.fg,this._whitespaceCell.bg=e.bg,this._whitespaceCell.extended=e.extended):(this._whitespaceCell.fg=0,this._whitespaceCell.bg=0,this._whitespaceCell.extended=new je),this._whitespaceCell}getBlankLine(e,t){return new go(this._bufferService.cols,this.getNullCell(e),t)}get hasScrollback(){return this._hasScrollback&&this.lines.maxLength>this._rows}get isCursorInViewport(){let e=this.ybase+this.y-this.ydisp;return e>=0&&eTo?To:t}fillViewportRows(e){if(this.lines.length===0){e===void 0&&(e=Y);let t=this._rows;for(;t--;)this.lines.push(this.getBlankLine(e))}}clear(){this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.lines=new po(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}resize(e,t){let n=this.getNullCell(Y),r=0,i=this._getCorrectBufferLength(t);if(i>this.lines.maxLength&&(this.lines.maxLength=i),this.lines.length>0){if(this._cols0&&this.lines.length<=this.ybase+this.y+a+1?(this.ybase--,a++,this.ydisp>0&&this.ydisp--):this.lines.push(new go(e,n)));else for(let e=this._rows;e>t;e--)this.lines.length>t+this.ybase&&(this.lines.length>this.ybase+this.y+1?this.lines.pop():(this.ybase++,this.ydisp++));if(i0&&(this.lines.trimStart(e),this.ybase=Math.max(this.ybase-e,0),this.ydisp=Math.max(this.ydisp-e,0),this.savedY=Math.max(this.savedY-e,0)),this.lines.maxLength=i}this.x=Math.min(this.x,e-1),this.y=Math.min(this.y,t-1),a&&(this.y+=a),this.savedX=Math.min(this.savedX,e-1),this.scrollTop=0}if(this.scrollBottom=t-1,this._isReflowEnabled&&(this._reflow(e,t),this._cols>e))for(let t=0;t.1*this.lines.length&&(this._memoryCleanupPosition=0,this._memoryCleanupQueue.enqueue(()=>this._batchedMemoryCleanup()))}_batchedMemoryCleanup(){let e=!0;this._memoryCleanupPosition>=this.lines.length&&(this._memoryCleanupPosition=0,e=!1);let t=0;for(;this._memoryCleanupPosition100)return!0;return e}get _isReflowEnabled(){let e=this._optionsService.rawOptions.windowsPty;return e&&e.buildNumber?this._hasScrollback&&e.backend===`conpty`&&e.buildNumber>=21376:this._hasScrollback&&!this._optionsService.rawOptions.windowsMode}_reflow(e,t){this._cols!==e&&(e>this._cols?this._reflowLarger(e,t):this._reflowSmaller(e,t))}_reflowLarger(e,t){let n=this._optionsService.rawOptions.reflowCursorLine,r=_o(this.lines,this._cols,e,this.ybase+this.y,this.getNullCell(Y),n);if(r.length>0){let n=vo(this.lines,r);yo(this.lines,n.layout),this._reflowLargerAdjustViewport(e,t,n.countRemoved)}}_reflowLargerAdjustViewport(e,t,n){let r=this.getNullCell(Y),i=n;for(;i-->0;)this.ybase===0?(this.y>0&&this.y--,this.lines.length=0;o--){let s=this.lines.get(o);if(!s||!s.isWrapped&&s.getTrimmedLength()<=e)continue;let c=[s];for(;s.isWrapped&&o>0;)s=this.lines.get(--o),c.unshift(s);if(!n){let e=this.ybase+this.y;if(e>=o&&e0&&(i.push({start:o+c.length+a,newLines:p}),a+=p.length),c.push(...p);let m=u.length-1,h=u[m];h===0&&(m--,h=u[m]);let g=c.length-d-1,_=l;for(;g>=0;){let e=Math.min(_,h);if(c[m]===void 0)break;c[m].copyCellsFrom(c[g],_-e,h-e,e,!0),h-=e,h===0&&(m--,h=u[m]),_-=e,_===0&&(g--,_=xo(c,Math.max(g,0),this._cols))}for(let t=0;t0;)this.ybase===0?this.y0){let e=[],t=[];for(let e=0;e=0;l--)if(s&&s.start>r+c){for(let e=s.newLines.length-1;e>=0;e--)this.lines.set(l--,s.newLines[e]);l++,e.push({index:r+1,amount:s.newLines.length}),c+=s.newLines.length,s=i[++o]}else this.lines.set(l,t[r--]);let l=0;for(let t=e.length-1;t>=0;t--)e[t].index+=l,this.lines.onInsertEmitter.fire(e[t]),l+=e[t].amount;let u=Math.max(0,n+a-this.lines.maxLength);u>0&&this.lines.onTrimEmitter.fire(u)}}translateBufferLineToString(e,t,n=0,r){let i=this.lines.get(e);return i?i.translateToString(t,n,r):``}getWrappedRangeForLine(e){let t=e,n=e;for(;t>0&&this.lines.get(t).isWrapped;)t--;for(;n+10;);return e>=this._cols?this._cols-1:e<0?0:e}nextStop(e){for(e??=this.x;!this.tabs[++e]&&e=this._cols?this._cols-1:e<0?0:e}clearMarkers(e){this._isClearing=!0;for(let t=0;t{t.line-=e,t.line<0&&t.dispose()})),t.register(this.lines.onInsert(e=>{t.line>=e.index&&(t.line+=e.amount)})),t.register(this.lines.onDelete(e=>{t.line>=e.index&&t.linee.index&&(t.line-=e.amount)})),t.register(t.onDispose(()=>this._removeMarker(t))),t}_removeMarker(e){this._isClearing||this.markers.splice(this.markers.indexOf(e),1)}},Do=class extends A{constructor(e,t){super(),this._optionsService=e,this._bufferService=t,this._onBufferActivate=this._register(new N),this.onBufferActivate=this._onBufferActivate.event,this.reset(),this._register(this._optionsService.onSpecificOptionChange(`scrollback`,()=>this.resize(this._bufferService.cols,this._bufferService.rows))),this._register(this._optionsService.onSpecificOptionChange(`tabStopWidth`,()=>this.setupTabStops()))}reset(){this._normal=new Eo(!0,this._optionsService,this._bufferService),this._normal.fillViewportRows(),this._alt=new Eo(!1,this._optionsService,this._bufferService),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}),this.setupTabStops()}get alt(){return this._alt}get active(){return this._activeBuffer}get normal(){return this._normal}activateNormalBuffer(){this._activeBuffer!==this._normal&&(this._normal.x=this._alt.x,this._normal.y=this._alt.y,this._alt.clearAllMarkers(),this._alt.clear(),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}))}activateAltBuffer(e){this._activeBuffer!==this._alt&&(this._alt.fillViewportRows(e),this._alt.x=this._normal.x,this._alt.y=this._normal.y,this._activeBuffer=this._alt,this._onBufferActivate.fire({activeBuffer:this._alt,inactiveBuffer:this._normal}))}resize(e,t){this._normal.resize(e,t),this._alt.resize(e,t),this.setupTabStops(e)}setupTabStops(e){this._normal.setupTabStops(e),this._alt.setupTabStops(e)}},Oo=2,ko=1,Ao=class extends A{constructor(e){super(),this.isUserScrolling=!1,this._onResize=this._register(new N),this.onResize=this._onResize.event,this._onScroll=this._register(new N),this.onScroll=this._onScroll.event,this.cols=Math.max(e.rawOptions.cols||0,Oo),this.rows=Math.max(e.rawOptions.rows||0,ko),this.buffers=this._register(new Do(e,this)),this._register(this.buffers.onBufferActivate(e=>{this._onScroll.fire(e.activeBuffer.ydisp)}))}get buffer(){return this.buffers.active}resize(e,t){let n=this.cols!==e,r=this.rows!==t;this.cols=e,this.rows=t,this.buffers.resize(e,t),this._onResize.fire({cols:e,rows:t,colsChanged:n,rowsChanged:r})}reset(){this.buffers.reset(),this.isUserScrolling=!1}scroll(e,t=!1){let n=this.buffer,r;r=this._cachedBlankLine,(!r||r.length!==this.cols||r.getFg(0)!==e.fg||r.getBg(0)!==e.bg)&&(r=n.getBlankLine(e,t),this._cachedBlankLine=r),r.isWrapped=t;let i=n.ybase+n.scrollTop,a=n.ybase+n.scrollBottom;if(n.scrollTop===0){let e=n.lines.isFull;a===n.lines.length-1?e?n.lines.recycle().copyFrom(r):n.lines.push(r.clone()):n.lines.splice(a+1,0,r.clone()),e?this.isUserScrolling&&(n.ydisp=Math.max(n.ydisp-1,0)):(n.ybase++,this.isUserScrolling||n.ydisp++)}else{let e=a-i+1;n.lines.shiftElements(i+1,e-1,-1),n.lines.set(a,r.clone())}this.isUserScrolling||(n.ydisp=n.ybase),this._onScroll.fire(n.ydisp)}scrollLines(e,t){let n=this.buffer;if(e<0){if(n.ydisp===0)return;this.isUserScrolling=!0}else e+n.ydisp>=n.ybase&&(this.isUserScrolling=!1);let r=n.ydisp;n.ydisp=Math.max(Math.min(n.ydisp+e,n.ybase),0),r!==n.ydisp&&(t||this._onScroll.fire(n.ydisp))}};Ao=C([w(0,O)],Ao);var jo={cols:80,rows:24,cursorBlink:!1,cursorStyle:`block`,cursorWidth:1,cursorInactiveStyle:`outline`,customGlyphs:!0,drawBoldTextInBrightColors:!0,documentOverride:null,fastScrollModifier:`alt`,fastScrollSensitivity:5,fontFamily:`monospace`,fontSize:15,fontWeight:`normal`,fontWeightBold:`bold`,ignoreBracketedPasteMode:!1,lineHeight:1,letterSpacing:0,linkHandler:null,logLevel:`info`,logger:null,scrollback:1e3,scrollOnEraseInDisplay:!1,scrollOnUserInput:!0,scrollSensitivity:1,screenReaderMode:!1,smoothScrollDuration:0,macOptionIsMeta:!1,macOptionClickForcesSelection:!1,minimumContrastRatio:1,disableStdin:!1,allowProposedApi:!1,allowTransparency:!1,tabStopWidth:8,theme:{},reflowCursorLine:!1,rescaleOverlappingGlyphs:!1,rightClickSelectsWord:_a,windowOptions:{},windowsMode:!1,windowsPty:{},wordSeparator:` ()[]{}',"\``,altClickMovesCursor:!0,convertEol:!1,termName:`xterm`,cancelEvents:!1,overviewRuler:{}},Mo=[`normal`,`bold`,`100`,`200`,`300`,`400`,`500`,`600`,`700`,`800`,`900`],No=class extends A{constructor(e){super(),this._onOptionChange=this._register(new N),this.onOptionChange=this._onOptionChange.event;let t={...jo};for(let n in e)if(n in t)try{let r=e[n];t[n]=this._sanitizeAndValidateOption(n,r)}catch(e){console.error(e)}this.rawOptions=t,this.options={...t},this._setupOptions(),this._register(k(()=>{this.rawOptions.linkHandler=null,this.rawOptions.documentOverride=null}))}onSpecificOptionChange(e,t){return this.onOptionChange(n=>{n===e&&t(this.rawOptions[e])})}onMultipleOptionChange(e,t){return this.onOptionChange(n=>{e.indexOf(n)!==-1&&t()})}_setupOptions(){let e=e=>{if(!(e in jo))throw Error(`No option with key "${e}"`);return this.rawOptions[e]},t=(e,t)=>{if(!(e in jo))throw Error(`No option with key "${e}"`);t=this._sanitizeAndValidateOption(e,t),this.rawOptions[e]!==t&&(this.rawOptions[e]=t,this._onOptionChange.fire(e))};for(let n in this.rawOptions){let r={get:e.bind(this,n),set:t.bind(this,n)};Object.defineProperty(this.options,n,r)}}_sanitizeAndValidateOption(e,t){switch(e){case`cursorStyle`:if(t||=jo[e],!Po(t))throw Error(`"${t}" is not a valid value for ${e}`);break;case`wordSeparator`:t||=jo[e];break;case`fontWeight`:case`fontWeightBold`:if(typeof t==`number`&&1<=t&&t<=1e3)break;t=Mo.includes(t)?t:jo[e];break;case`cursorWidth`:t=Math.floor(t);case`lineHeight`:case`tabStopWidth`:if(t<1)throw Error(`${e} cannot be less than 1, value: ${t}`);break;case`minimumContrastRatio`:t=Math.max(1,Math.min(21,Math.round(t*10)/10));break;case`scrollback`:if(t=Math.min(t,4294967295),t<0)throw Error(`${e} cannot be less than 0, value: ${t}`);break;case`fastScrollSensitivity`:case`scrollSensitivity`:if(t<=0)throw Error(`${e} cannot be less than or equal to 0, value: ${t}`);break;case`rows`:case`cols`:if(!t&&t!==0)throw Error(`${e} must be numeric, value: ${t}`);break;case`windowsPty`:t??={};break}return t}};function Po(e){return e===`block`||e===`underline`||e===`bar`}function Fo(e,t=5){if(typeof e!=`object`)return e;let n=Array.isArray(e)?[]:{};for(let r in e)n[r]=t<=1?e[r]:e[r]&&Fo(e[r],t-1);return n}var Io=Object.freeze({insertMode:!1}),Lo=Object.freeze({applicationCursorKeys:!1,applicationKeypad:!1,bracketedPasteMode:!1,cursorBlink:void 0,cursorStyle:void 0,origin:!1,reverseWraparound:!1,sendFocus:!1,synchronizedOutput:!1,wraparound:!0}),Ro=class extends A{constructor(e,t,n){super(),this._bufferService=e,this._logService=t,this._optionsService=n,this.isCursorInitialized=!1,this.isCursorHidden=!1,this._onData=this._register(new N),this.onData=this._onData.event,this._onUserInput=this._register(new N),this.onUserInput=this._onUserInput.event,this._onBinary=this._register(new N),this.onBinary=this._onBinary.event,this._onRequestScrollToBottom=this._register(new N),this.onRequestScrollToBottom=this._onRequestScrollToBottom.event,this.modes=Fo(Io),this.decPrivateModes=Fo(Lo)}reset(){this.modes=Fo(Io),this.decPrivateModes=Fo(Lo)}triggerDataEvent(e,t=!1){if(this._optionsService.rawOptions.disableStdin)return;let n=this._bufferService.buffer;t&&this._optionsService.rawOptions.scrollOnUserInput&&n.ybase!==n.ydisp&&this._onRequestScrollToBottom.fire(),t&&this._onUserInput.fire(),this._logService.debug(`sending data "${e}"`),this._logService.trace(`sending data (codes)`,()=>e.split(``).map(e=>e.charCodeAt(0))),this._onData.fire(e)}triggerBinaryEvent(e){this._optionsService.rawOptions.disableStdin||(this._logService.debug(`sending binary "${e}"`),this._logService.trace(`sending binary (codes)`,()=>e.split(``).map(e=>e.charCodeAt(0))),this._onBinary.fire(e))}};Ro=C([w(0,D),w(1,He),w(2,O)],Ro);var zo={NONE:{events:0,restrict:()=>!1},X10:{events:1,restrict:e=>e.button===4||e.action!==1?!1:(e.ctrl=!1,e.alt=!1,e.shift=!1,!0)},VT200:{events:19,restrict:e=>e.action!==32},DRAG:{events:23,restrict:e=>!(e.action===32&&e.button===3)},ANY:{events:31,restrict:e=>!0}};function Bo(e,t){let n=(e.ctrl?16:0)|(e.shift?4:0)|(e.alt?8:0);return e.button===4?(n|=64,n|=e.action):(n|=e.button&3,e.button&4&&(n|=64),e.button&8&&(n|=128),e.action===32?n|=32:e.action===0&&!t&&(n|=3)),n}var Vo=String.fromCharCode,Ho={DEFAULT:e=>{let t=[Bo(e,!1)+32,e.col+32,e.row+32];return t[0]>255||t[1]>255||t[2]>255?``:`\x1B[M${Vo(t[0])}${Vo(t[1])}${Vo(t[2])}`},SGR:e=>{let t=e.action===0&&e.button!==4?`m`:`M`;return`\x1B[<${Bo(e,!0)};${e.col};${e.row}${t}`},SGR_PIXELS:e=>{let t=e.action===0&&e.button!==4?`m`:`M`;return`\x1B[<${Bo(e,!0)};${e.x};${e.y}${t}`}},Uo=class extends A{constructor(e,t,n){super(),this._bufferService=e,this._coreService=t,this._optionsService=n,this._protocols={},this._encodings={},this._activeProtocol=``,this._activeEncoding=``,this._lastEvent=null,this._wheelPartialScroll=0,this._onProtocolChange=this._register(new N),this.onProtocolChange=this._onProtocolChange.event;for(let e of Object.keys(zo))this.addProtocol(e,zo[e]);for(let e of Object.keys(Ho))this.addEncoding(e,Ho[e]);this.reset()}addProtocol(e,t){this._protocols[e]=t}addEncoding(e,t){this._encodings[e]=t}get activeProtocol(){return this._activeProtocol}get areMouseEventsActive(){return this._protocols[this._activeProtocol].events!==0}set activeProtocol(e){if(!this._protocols[e])throw Error(`unknown protocol "${e}"`);this._activeProtocol=e,this._onProtocolChange.fire(this._protocols[e].events)}get activeEncoding(){return this._activeEncoding}set activeEncoding(e){if(!this._encodings[e])throw Error(`unknown encoding "${e}"`);this._activeEncoding=e}reset(){this.activeProtocol=`NONE`,this.activeEncoding=`DEFAULT`,this._lastEvent=null,this._wheelPartialScroll=0}consumeWheelEvent(e,t,n){if(e.deltaY===0||e.shiftKey||t===void 0||n===void 0)return 0;let r=t/n,i=this._applyScrollModifier(e.deltaY,e);return e.deltaMode===WheelEvent.DOM_DELTA_PIXEL?(i/=r+0,Math.abs(e.deltaY)<50&&(i*=.3),this._wheelPartialScroll+=i,i=Math.floor(Math.abs(this._wheelPartialScroll))*(this._wheelPartialScroll>0?1:-1),this._wheelPartialScroll%=1):e.deltaMode===WheelEvent.DOM_DELTA_PAGE&&(i*=this._bufferService.rows),i}_applyScrollModifier(e,t){return t.altKey||t.ctrlKey||t.shiftKey?e*this._optionsService.rawOptions.fastScrollSensitivity*this._optionsService.rawOptions.scrollSensitivity:e*this._optionsService.rawOptions.scrollSensitivity}triggerMouseEvent(e){if(e.col<0||e.col>=this._bufferService.cols||e.row<0||e.row>=this._bufferService.rows||e.button===4&&e.action===32||e.button===3&&e.action!==32||e.button!==4&&(e.action===2||e.action===3)||(e.col++,e.row++,e.action===32&&this._lastEvent&&this._equalEvents(this._lastEvent,e,this._activeEncoding===`SGR_PIXELS`))||!this._protocols[this._activeProtocol].restrict(e))return!1;let t=this._encodings[this._activeEncoding](e);return t&&(this._activeEncoding===`DEFAULT`?this._coreService.triggerBinaryEvent(t):this._coreService.triggerDataEvent(t,!0)),this._lastEvent=e,!0}explainEvents(e){return{down:!!(e&1),up:!!(e&2),drag:!!(e&4),move:!!(e&8),wheel:!!(e&16)}}_equalEvents(e,t,n){if(n){if(e.x!==t.x||e.y!==t.y)return!1}else if(e.col!==t.col||e.row!==t.row)return!1;return!(e.button!==t.button||e.action!==t.action||e.ctrl!==t.ctrl||e.alt!==t.alt||e.shift!==t.shift)}};Uo=C([w(0,D),w(1,ze),w(2,O)],Uo);var Wo=[[768,879],[1155,1158],[1160,1161],[1425,1469],[1471,1471],[1473,1474],[1476,1477],[1479,1479],[1536,1539],[1552,1557],[1611,1630],[1648,1648],[1750,1764],[1767,1768],[1770,1773],[1807,1807],[1809,1809],[1840,1866],[1958,1968],[2027,2035],[2305,2306],[2364,2364],[2369,2376],[2381,2381],[2385,2388],[2402,2403],[2433,2433],[2492,2492],[2497,2500],[2509,2509],[2530,2531],[2561,2562],[2620,2620],[2625,2626],[2631,2632],[2635,2637],[2672,2673],[2689,2690],[2748,2748],[2753,2757],[2759,2760],[2765,2765],[2786,2787],[2817,2817],[2876,2876],[2879,2879],[2881,2883],[2893,2893],[2902,2902],[2946,2946],[3008,3008],[3021,3021],[3134,3136],[3142,3144],[3146,3149],[3157,3158],[3260,3260],[3263,3263],[3270,3270],[3276,3277],[3298,3299],[3393,3395],[3405,3405],[3530,3530],[3538,3540],[3542,3542],[3633,3633],[3636,3642],[3655,3662],[3761,3761],[3764,3769],[3771,3772],[3784,3789],[3864,3865],[3893,3893],[3895,3895],[3897,3897],[3953,3966],[3968,3972],[3974,3975],[3984,3991],[3993,4028],[4038,4038],[4141,4144],[4146,4146],[4150,4151],[4153,4153],[4184,4185],[4448,4607],[4959,4959],[5906,5908],[5938,5940],[5970,5971],[6002,6003],[6068,6069],[6071,6077],[6086,6086],[6089,6099],[6109,6109],[6155,6157],[6313,6313],[6432,6434],[6439,6440],[6450,6450],[6457,6459],[6679,6680],[6912,6915],[6964,6964],[6966,6970],[6972,6972],[6978,6978],[7019,7027],[7616,7626],[7678,7679],[8203,8207],[8234,8238],[8288,8291],[8298,8303],[8400,8431],[12330,12335],[12441,12442],[43014,43014],[43019,43019],[43045,43046],[64286,64286],[65024,65039],[65056,65059],[65279,65279],[65529,65531]],Go=[[68097,68099],[68101,68102],[68108,68111],[68152,68154],[68159,68159],[119143,119145],[119155,119170],[119173,119179],[119210,119213],[119362,119364],[917505,917505],[917536,917631],[917760,917999]],Z;function Ko(e,t){let n=0,r=t.length-1,i;if(et[r][1])return!1;for(;r>=n;)if(i=n+r>>1,e>t[i][1])n=i+1;else if(e=131072&&e<=196605||e>=196608&&e<=262141?2:1}charProperties(e,t){let n=this.wcwidth(e),r=n===0&&t!==0;if(r){let e=Jo.extractWidth(t);e===0?r=!1:e>n&&(n=e)}return Jo.createPropertyValue(0,n,r)}},Jo=class e{constructor(){this._providers=Object.create(null),this._active=``,this._onChange=new N,this.onChange=this._onChange.event;let e=new qo;this.register(e),this._active=e.version,this._activeProvider=e}static extractShouldJoin(e){return(e&1)!=0}static extractWidth(e){return e>>1&3}static extractCharKind(e){return e>>3}static createPropertyValue(e,t,n=!1){return(e&16777215)<<3|(t&3)<<1|!!n}dispose(){this._onChange.dispose()}get versions(){return Object.keys(this._providers)}get activeVersion(){return this._active}set activeVersion(e){if(!this._providers[e])throw Error(`unknown Unicode version "${e}"`);this._active=e,this._activeProvider=this._providers[e],this._onChange.fire(e)}register(e){this._providers[e.version]=e}wcwidth(e){return this._activeProvider.wcwidth(e)}getStringCellWidth(t){let n=0,r=0,i=t.length;for(let a=0;a=i)return n+this.wcwidth(o);let e=t.charCodeAt(a);56320<=e&&e<=57343?o=(o-55296)*1024+e-56320+65536:n+=this.wcwidth(e)}let s=this.charProperties(o,r),c=e.extractWidth(s);e.extractShouldJoin(s)&&(c-=e.extractWidth(r)),n+=c,r=s}return n}charProperties(e,t){return this._activeProvider.charProperties(e,t)}},Yo=class{constructor(){this.glevel=0,this._charsets=[]}reset(){this.charset=void 0,this._charsets=[],this.glevel=0}setgLevel(e){this.glevel=e,this.charset=this._charsets[e]}setgCharset(e,t){this._charsets[e]=t,this.glevel===e&&(this.charset=t)}};function Xo(e){let t=e.buffer.lines.get(e.buffer.ybase+e.buffer.y-1)?.get(e.cols-1),n=e.buffer.lines.get(e.buffer.ybase+e.buffer.y);n&&t&&(n.isWrapped=t[3]!==0&&t[3]!==32)}var Zo=2147483647,Qo=256,$o=class e{constructor(e=32,t=32){if(this.maxLength=e,this.maxSubParamsLength=t,t>Qo)throw Error(`maxSubParamsLength must not be greater than 256`);this.params=new Int32Array(e),this.length=0,this._subParams=new Int32Array(t),this._subParamsLength=0,this._subParamsIdx=new Uint16Array(e),this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}static fromArray(t){let n=new e;if(!t.length)return n;for(let e=+!!Array.isArray(t[0]);e>8,r=this._subParamsIdx[t]&255;r-n>0&&e.push(Array.prototype.slice.call(this._subParams,n,r))}return e}reset(){this.length=0,this._subParamsLength=0,this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}addParam(e){if(this._digitIsSub=!1,this.length>=this.maxLength){this._rejectDigits=!0;return}if(e<-1)throw Error(`values lesser than -1 are not allowed`);this._subParamsIdx[this.length]=this._subParamsLength<<8|this._subParamsLength,this.params[this.length++]=e>Zo?Zo:e}addSubParam(e){if(this._digitIsSub=!0,this.length){if(this._rejectDigits||this._subParamsLength>=this.maxSubParamsLength){this._rejectSubDigits=!0;return}if(e<-1)throw Error(`values lesser than -1 are not allowed`);this._subParams[this._subParamsLength++]=e>Zo?Zo:e,this._subParamsIdx[this.length-1]++}}hasSubParams(e){return(this._subParamsIdx[e]&255)-(this._subParamsIdx[e]>>8)>0}getSubParams(e){let t=this._subParamsIdx[e]>>8,n=this._subParamsIdx[e]&255;return n-t>0?this._subParams.subarray(t,n):null}getSubParamsAll(){let e={};for(let t=0;t>8,r=this._subParamsIdx[t]&255;r-n>0&&(e[t]=this._subParams.slice(n,r))}return e}addDigit(e){let t;if(this._rejectDigits||!(t=this._digitIsSub?this._subParamsLength:this.length)||this._digitIsSub&&this._rejectSubDigits)return;let n=this._digitIsSub?this._subParams:this.params,r=n[t-1];n[t-1]=~r?Math.min(r*10+e,Zo):e}},es=[],ts=class{constructor(){this._state=0,this._active=es,this._id=-1,this._handlers=Object.create(null),this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}registerHandler(e,t){this._handlers[e]===void 0&&(this._handlers[e]=[]);let n=this._handlers[e];return n.push(t),{dispose:()=>{let e=n.indexOf(t);e!==-1&&n.splice(e,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=es}reset(){if(this._state===2)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].end(!1);this._stack.paused=!1,this._active=es,this._id=-1,this._state=0}_start(){if(this._active=this._handlers[this._id]||es,!this._active.length)this._handlerFb(this._id,`START`);else for(let e=this._active.length-1;e>=0;e--)this._active[e].start()}_put(e,t,n){if(!this._active.length)this._handlerFb(this._id,`PUT`,Te(e,t,n));else for(let r=this._active.length-1;r>=0;r--)this._active[r].put(e,t,n)}start(){this.reset(),this._state=1}put(e,t,n){if(this._state!==3){if(this._state===1)for(;t0&&this._put(e,t,n)}}end(e,t=!0){if(this._state!==0){if(this._state!==3)if(this._state===1&&this._start(),!this._active.length)this._handlerFb(this._id,`END`,e);else{let n=!1,r=this._active.length-1,i=!1;if(this._stack.paused&&(r=this._stack.loopPosition-1,n=t,i=this._stack.fallThrough,this._stack.paused=!1),!i&&n===!1){for(;r>=0&&(n=this._active[r].end(e),n!==!0);r--)if(n instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=r,this._stack.fallThrough=!1,n;r--}for(;r>=0;r--)if(n=this._active[r].end(!1),n instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=r,this._stack.fallThrough=!0,n}this._active=es,this._id=-1,this._state=0}}},ns=class{constructor(e){this._handler=e,this._data=``,this._hitLimit=!1}start(){this._data=``,this._hitLimit=!1}put(e,t,n){this._hitLimit||(this._data+=Te(e,t,n),this._data.length>1e7&&(this._data=``,this._hitLimit=!0))}end(e){let t=!1;if(this._hitLimit)t=!1;else if(e&&(t=this._handler(this._data),t instanceof Promise))return t.then(e=>(this._data=``,this._hitLimit=!1,e));return this._data=``,this._hitLimit=!1,t}},rs=[],is=class{constructor(){this._handlers=Object.create(null),this._active=rs,this._ident=0,this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=rs}registerHandler(e,t){this._handlers[e]===void 0&&(this._handlers[e]=[]);let n=this._handlers[e];return n.push(t),{dispose:()=>{let e=n.indexOf(t);e!==-1&&n.splice(e,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}reset(){if(this._active.length)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].unhook(!1);this._stack.paused=!1,this._active=rs,this._ident=0}hook(e,t){if(this.reset(),this._ident=e,this._active=this._handlers[e]||rs,!this._active.length)this._handlerFb(this._ident,`HOOK`,t);else for(let e=this._active.length-1;e>=0;e--)this._active[e].hook(t)}put(e,t,n){if(!this._active.length)this._handlerFb(this._ident,`PUT`,Te(e,t,n));else for(let r=this._active.length-1;r>=0;r--)this._active[r].put(e,t,n)}unhook(e,t=!0){if(!this._active.length)this._handlerFb(this._ident,`UNHOOK`,e);else{let n=!1,r=this._active.length-1,i=!1;if(this._stack.paused&&(r=this._stack.loopPosition-1,n=t,i=this._stack.fallThrough,this._stack.paused=!1),!i&&n===!1){for(;r>=0&&(n=this._active[r].unhook(e),n!==!0);r--)if(n instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=r,this._stack.fallThrough=!1,n;r--}for(;r>=0;r--)if(n=this._active[r].unhook(!1),n instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=r,this._stack.fallThrough=!0,n}this._active=rs,this._ident=0}},as=new $o;as.addParam(0);var os=class{constructor(e){this._handler=e,this._data=``,this._params=as,this._hitLimit=!1}hook(e){this._params=e.length>1||e.params[0]?e.clone():as,this._data=``,this._hitLimit=!1}put(e,t,n){this._hitLimit||(this._data+=Te(e,t,n),this._data.length>1e7&&(this._data=``,this._hitLimit=!0))}unhook(e){let t=!1;if(this._hitLimit)t=!1;else if(e&&(t=this._handler(this._data,this._params),t instanceof Promise))return t.then(e=>(this._params=as,this._data=``,this._hitLimit=!1,e));return this._params=as,this._data=``,this._hitLimit=!1,t}},ss=class{constructor(e){this.table=new Uint8Array(e)}setDefault(e,t){this.table.fill(e<<4|t)}add(e,t,n,r){this.table[t<<8|e]=n<<4|r}addMany(e,t,n,r){for(let i=0;it),n=(e,n)=>t.slice(e,n),r=n(32,127),i=n(0,24);i.push(25),i.push.apply(i,n(28,32));let a=n(0,14),o;for(o in e.setDefault(1,0),e.addMany(r,0,2,0),a)e.addMany([24,26,153,154],o,3,0),e.addMany(n(128,144),o,3,0),e.addMany(n(144,152),o,3,0),e.add(156,o,0,0),e.add(27,o,11,1),e.add(157,o,4,8),e.addMany([152,158,159],o,0,7),e.add(155,o,11,3),e.add(144,o,11,9);return e.addMany(i,0,3,0),e.addMany(i,1,3,1),e.add(127,1,0,1),e.addMany(i,8,0,8),e.addMany(i,3,3,3),e.add(127,3,0,3),e.addMany(i,4,3,4),e.add(127,4,0,4),e.addMany(i,6,3,6),e.addMany(i,5,3,5),e.add(127,5,0,5),e.addMany(i,2,3,2),e.add(127,2,0,2),e.add(93,1,4,8),e.addMany(r,8,5,8),e.add(127,8,5,8),e.addMany([156,27,24,26,7],8,6,0),e.addMany(n(28,32),8,0,8),e.addMany([88,94,95],1,0,7),e.addMany(r,7,0,7),e.addMany(i,7,0,7),e.add(156,7,0,0),e.add(127,7,0,7),e.add(91,1,11,3),e.addMany(n(64,127),3,7,0),e.addMany(n(48,60),3,8,4),e.addMany([60,61,62,63],3,9,4),e.addMany(n(48,60),4,8,4),e.addMany(n(64,127),4,7,0),e.addMany([60,61,62,63],4,0,6),e.addMany(n(32,64),6,0,6),e.add(127,6,0,6),e.addMany(n(64,127),6,0,0),e.addMany(n(32,48),3,9,5),e.addMany(n(32,48),5,9,5),e.addMany(n(48,64),5,0,6),e.addMany(n(64,127),5,7,0),e.addMany(n(32,48),4,9,5),e.addMany(n(32,48),1,9,2),e.addMany(n(32,48),2,9,2),e.addMany(n(48,127),2,10,0),e.addMany(n(48,80),1,10,0),e.addMany(n(81,88),1,10,0),e.addMany([89,90,92],1,10,0),e.addMany(n(96,127),1,10,0),e.add(80,1,11,9),e.addMany(i,9,0,9),e.add(127,9,0,9),e.addMany(n(28,32),9,0,9),e.addMany(n(32,48),9,9,12),e.addMany(n(48,60),9,8,10),e.addMany([60,61,62,63],9,9,10),e.addMany(i,11,0,11),e.addMany(n(32,128),11,0,11),e.addMany(n(28,32),11,0,11),e.addMany(i,10,0,10),e.add(127,10,0,10),e.addMany(n(28,32),10,0,10),e.addMany(n(48,60),10,8,10),e.addMany([60,61,62,63],10,0,11),e.addMany(n(32,48),10,9,12),e.addMany(i,12,0,12),e.add(127,12,0,12),e.addMany(n(28,32),12,0,12),e.addMany(n(32,48),12,9,12),e.addMany(n(48,64),12,0,11),e.addMany(n(64,127),12,12,13),e.addMany(n(64,127),10,12,13),e.addMany(n(64,127),9,12,13),e.addMany(i,13,13,13),e.addMany(r,13,13,13),e.add(127,13,0,13),e.addMany([27,156,24,26],13,14,0),e.add(cs,0,2,0),e.add(cs,8,5,8),e.add(cs,6,0,6),e.add(cs,11,0,11),e.add(cs,13,13,13),e}(),us=class extends A{constructor(e=ls){super(),this._transitions=e,this._parseStack={state:0,handlers:[],handlerPos:0,transition:0,chunkPos:0},this.initialState=0,this.currentState=this.initialState,this._params=new $o,this._params.addParam(0),this._collect=0,this.precedingJoinState=0,this._printHandlerFb=(e,t,n)=>{},this._executeHandlerFb=e=>{},this._csiHandlerFb=(e,t)=>{},this._escHandlerFb=e=>{},this._errorHandlerFb=e=>e,this._printHandler=this._printHandlerFb,this._executeHandlers=Object.create(null),this._csiHandlers=Object.create(null),this._escHandlers=Object.create(null),this._register(k(()=>{this._csiHandlers=Object.create(null),this._executeHandlers=Object.create(null),this._escHandlers=Object.create(null)})),this._oscParser=this._register(new ts),this._dcsParser=this._register(new is),this._errorHandler=this._errorHandlerFb,this.registerEscHandler({final:`\\`},()=>!0)}_identifier(e,t=[64,126]){let n=0;if(e.prefix){if(e.prefix.length>1)throw Error(`only one byte as prefix supported`);if(n=e.prefix.charCodeAt(0),n&&60>n||n>63)throw Error(`prefix must be in range 0x3c .. 0x3f`)}if(e.intermediates){if(e.intermediates.length>2)throw Error(`only two bytes as intermediates are supported`);for(let t=0;tr||r>47)throw Error(`intermediate must be in range 0x20 .. 0x2f`);n<<=8,n|=r}}if(e.final.length!==1)throw Error(`final must be a single byte`);let r=e.final.charCodeAt(0);if(t[0]>r||r>t[1])throw Error(`final must be in range ${t[0]} .. ${t[1]}`);return n<<=8,n|=r,n}identToString(e){let t=[];for(;e;)t.push(String.fromCharCode(e&255)),e>>=8;return t.reverse().join(``)}setPrintHandler(e){this._printHandler=e}clearPrintHandler(){this._printHandler=this._printHandlerFb}registerEscHandler(e,t){let n=this._identifier(e,[48,126]);this._escHandlers[n]===void 0&&(this._escHandlers[n]=[]);let r=this._escHandlers[n];return r.push(t),{dispose:()=>{let e=r.indexOf(t);e!==-1&&r.splice(e,1)}}}clearEscHandler(e){this._escHandlers[this._identifier(e,[48,126])]&&delete this._escHandlers[this._identifier(e,[48,126])]}setEscHandlerFallback(e){this._escHandlerFb=e}setExecuteHandler(e,t){this._executeHandlers[e.charCodeAt(0)]=t}clearExecuteHandler(e){this._executeHandlers[e.charCodeAt(0)]&&delete this._executeHandlers[e.charCodeAt(0)]}setExecuteHandlerFallback(e){this._executeHandlerFb=e}registerCsiHandler(e,t){let n=this._identifier(e);this._csiHandlers[n]===void 0&&(this._csiHandlers[n]=[]);let r=this._csiHandlers[n];return r.push(t),{dispose:()=>{let e=r.indexOf(t);e!==-1&&r.splice(e,1)}}}clearCsiHandler(e){this._csiHandlers[this._identifier(e)]&&delete this._csiHandlers[this._identifier(e)]}setCsiHandlerFallback(e){this._csiHandlerFb=e}registerDcsHandler(e,t){return this._dcsParser.registerHandler(this._identifier(e),t)}clearDcsHandler(e){this._dcsParser.clearHandler(this._identifier(e))}setDcsHandlerFallback(e){this._dcsParser.setHandlerFallback(e)}registerOscHandler(e,t){return this._oscParser.registerHandler(e,t)}clearOscHandler(e){this._oscParser.clearHandler(e)}setOscHandlerFallback(e){this._oscParser.setHandlerFallback(e)}setErrorHandler(e){this._errorHandler=e}clearErrorHandler(){this._errorHandler=this._errorHandlerFb}reset(){this.currentState=this.initialState,this._oscParser.reset(),this._dcsParser.reset(),this._params.reset(),this._params.addParam(0),this._collect=0,this.precedingJoinState=0,this._parseStack.state!==0&&(this._parseStack.state=2,this._parseStack.handlers=[])}_preserveStack(e,t,n,r,i){this._parseStack.state=e,this._parseStack.handlers=t,this._parseStack.handlerPos=n,this._parseStack.transition=r,this._parseStack.chunkPos=i}parse(e,t,n){let r=0,i=0,a=0,o;if(this._parseStack.state)if(this._parseStack.state===2)this._parseStack.state=0,a=this._parseStack.chunkPos+1;else{if(n===void 0||this._parseStack.state===1)throw this._parseStack.state=1,Error(`improper continuation due to previous async handler, giving up parsing`);let t=this._parseStack.handlers,i=this._parseStack.handlerPos-1;switch(this._parseStack.state){case 3:if(n===!1&&i>-1){for(;i>=0&&(o=t[i](this._params),o!==!0);i--)if(o instanceof Promise)return this._parseStack.handlerPos=i,o}this._parseStack.handlers=[];break;case 4:if(n===!1&&i>-1){for(;i>=0&&(o=t[i](),o!==!0);i--)if(o instanceof Promise)return this._parseStack.handlerPos=i,o}this._parseStack.handlers=[];break;case 6:if(r=e[this._parseStack.chunkPos],o=this._dcsParser.unhook(r!==24&&r!==26,n),o)return o;r===27&&(this._parseStack.transition|=1),this._params.reset(),this._params.addParam(0),this._collect=0;break;case 5:if(r=e[this._parseStack.chunkPos],o=this._oscParser.end(r!==24&&r!==26,n),o)return o;r===27&&(this._parseStack.transition|=1),this._params.reset(),this._params.addParam(0),this._collect=0;break}this._parseStack.state=0,a=this._parseStack.chunkPos+1,this.precedingJoinState=0,this.currentState=this._parseStack.transition&15}for(let n=a;n>4){case 2:for(let i=n+1;;++i){if(i>=t||(r=e[i])<32||r>126&&r=t||(r=e[i])<32||r>126&&r=t||(r=e[i])<32||r>126&&r=t||(r=e[i])<32||r>126&&r=0&&(o=a[s](this._params),o!==!0);s--)if(o instanceof Promise)return this._preserveStack(3,a,s,i,n),o;s<0&&this._csiHandlerFb(this._collect<<8|r,this._params),this.precedingJoinState=0;break;case 8:do switch(r){case 59:this._params.addParam(0);break;case 58:this._params.addSubParam(-1);break;default:this._params.addDigit(r-48)}while(++n47&&r<60);n--;break;case 9:this._collect<<=8,this._collect|=r;break;case 10:let c=this._escHandlers[this._collect<<8|r],l=c?c.length-1:-1;for(;l>=0&&(o=c[l](),o!==!0);l--)if(o instanceof Promise)return this._preserveStack(4,c,l,i,n),o;l<0&&this._escHandlerFb(this._collect<<8|r),this.precedingJoinState=0;break;case 11:this._params.reset(),this._params.addParam(0),this._collect=0;break;case 12:this._dcsParser.hook(this._collect<<8|r,this._params);break;case 13:for(let i=n+1;;++i)if(i>=t||(r=e[i])===24||r===26||r===27||r>127&&r=t||(r=e[i])<32||r>127&&r>4:i>>8}return n}}function ms(e,t){let n=e.toString(16),r=n.length<2?`0`+n:n;switch(t){case 4:return n[0];case 8:return r;case 12:return(r+r).slice(0,3);default:return r+r}}function hs(e,t=16){let[n,r,i]=e;return`rgb:${ms(n,t)}/${ms(r,t)}/${ms(i,t)}`}var gs={"(":0,")":1,"*":2,"+":3,"-":1,".":2},_s=131072,vs=10;function ys(e,t){if(e>24)return t.setWinLines||!1;switch(e){case 1:return!!t.restoreWin;case 2:return!!t.minimizeWin;case 3:return!!t.setWinPosition;case 4:return!!t.setWinSizePixels;case 5:return!!t.raiseWin;case 6:return!!t.lowerWin;case 7:return!!t.refreshWin;case 8:return!!t.setWinSizeChars;case 9:return!!t.maximizeWin;case 10:return!!t.fullscreenWin;case 11:return!!t.getWinState;case 13:return!!t.getWinPosition;case 14:return!!t.getWinSizePixels;case 15:return!!t.getScreenSizePixels;case 16:return!!t.getCellSizePixels;case 18:return!!t.getWinSizeChars;case 19:return!!t.getScreenSizeChars;case 20:return!!t.getIconTitle;case 21:return!!t.getWinTitle;case 22:return!!t.pushTitle;case 23:return!!t.popTitle;case 24:return!!t.setWinLines}return!1}var bs=5e3,xs=0,Ss=class extends A{constructor(e,t,n,r,i,a,o,s,c=new us){super(),this._bufferService=e,this._charsetService=t,this._coreService=n,this._logService=r,this._optionsService=i,this._oscLinkService=a,this._coreMouseService=o,this._unicodeService=s,this._parser=c,this._parseBuffer=new Uint32Array(4096),this._stringDecoder=new Ee,this._utf8Decoder=new De,this._windowTitle=``,this._iconName=``,this._windowTitleStack=[],this._iconNameStack=[],this._curAttrData=Y.clone(),this._eraseAttrDataInternal=Y.clone(),this._onRequestBell=this._register(new N),this.onRequestBell=this._onRequestBell.event,this._onRequestRefreshRows=this._register(new N),this.onRequestRefreshRows=this._onRequestRefreshRows.event,this._onRequestReset=this._register(new N),this.onRequestReset=this._onRequestReset.event,this._onRequestSendFocus=this._register(new N),this.onRequestSendFocus=this._onRequestSendFocus.event,this._onRequestSyncScrollBar=this._register(new N),this.onRequestSyncScrollBar=this._onRequestSyncScrollBar.event,this._onRequestWindowsOptionsReport=this._register(new N),this.onRequestWindowsOptionsReport=this._onRequestWindowsOptionsReport.event,this._onA11yChar=this._register(new N),this.onA11yChar=this._onA11yChar.event,this._onA11yTab=this._register(new N),this.onA11yTab=this._onA11yTab.event,this._onCursorMove=this._register(new N),this.onCursorMove=this._onCursorMove.event,this._onLineFeed=this._register(new N),this.onLineFeed=this._onLineFeed.event,this._onScroll=this._register(new N),this.onScroll=this._onScroll.event,this._onTitleChange=this._register(new N),this.onTitleChange=this._onTitleChange.event,this._onColor=this._register(new N),this.onColor=this._onColor.event,this._parseStack={paused:!1,cursorStartX:0,cursorStartY:0,decodedLength:0,position:0},this._specialColors=[256,257,258],this._register(this._parser),this._dirtyRowTracker=new Cs(this._bufferService),this._activeBuffer=this._bufferService.buffer,this._register(this._bufferService.buffers.onBufferActivate(e=>this._activeBuffer=e.activeBuffer)),this._parser.setCsiHandlerFallback((e,t)=>{this._logService.debug(`Unknown CSI code: `,{identifier:this._parser.identToString(e),params:t.toArray()})}),this._parser.setEscHandlerFallback(e=>{this._logService.debug(`Unknown ESC code: `,{identifier:this._parser.identToString(e)})}),this._parser.setExecuteHandlerFallback(e=>{this._logService.debug(`Unknown EXECUTE code: `,{code:e})}),this._parser.setOscHandlerFallback((e,t,n)=>{this._logService.debug(`Unknown OSC code: `,{identifier:e,action:t,data:n})}),this._parser.setDcsHandlerFallback((e,t,n)=>{t===`HOOK`&&(n=n.toArray()),this._logService.debug(`Unknown DCS code: `,{identifier:this._parser.identToString(e),action:t,payload:n})}),this._parser.setPrintHandler((e,t,n)=>this.print(e,t,n)),this._parser.registerCsiHandler({final:`@`},e=>this.insertChars(e)),this._parser.registerCsiHandler({intermediates:` `,final:`@`},e=>this.scrollLeft(e)),this._parser.registerCsiHandler({final:`A`},e=>this.cursorUp(e)),this._parser.registerCsiHandler({intermediates:` `,final:`A`},e=>this.scrollRight(e)),this._parser.registerCsiHandler({final:`B`},e=>this.cursorDown(e)),this._parser.registerCsiHandler({final:`C`},e=>this.cursorForward(e)),this._parser.registerCsiHandler({final:`D`},e=>this.cursorBackward(e)),this._parser.registerCsiHandler({final:`E`},e=>this.cursorNextLine(e)),this._parser.registerCsiHandler({final:`F`},e=>this.cursorPrecedingLine(e)),this._parser.registerCsiHandler({final:`G`},e=>this.cursorCharAbsolute(e)),this._parser.registerCsiHandler({final:`H`},e=>this.cursorPosition(e)),this._parser.registerCsiHandler({final:`I`},e=>this.cursorForwardTab(e)),this._parser.registerCsiHandler({final:`J`},e=>this.eraseInDisplay(e,!1)),this._parser.registerCsiHandler({prefix:`?`,final:`J`},e=>this.eraseInDisplay(e,!0)),this._parser.registerCsiHandler({final:`K`},e=>this.eraseInLine(e,!1)),this._parser.registerCsiHandler({prefix:`?`,final:`K`},e=>this.eraseInLine(e,!0)),this._parser.registerCsiHandler({final:`L`},e=>this.insertLines(e)),this._parser.registerCsiHandler({final:`M`},e=>this.deleteLines(e)),this._parser.registerCsiHandler({final:`P`},e=>this.deleteChars(e)),this._parser.registerCsiHandler({final:`S`},e=>this.scrollUp(e)),this._parser.registerCsiHandler({final:`T`},e=>this.scrollDown(e)),this._parser.registerCsiHandler({final:`X`},e=>this.eraseChars(e)),this._parser.registerCsiHandler({final:`Z`},e=>this.cursorBackwardTab(e)),this._parser.registerCsiHandler({final:"`"},e=>this.charPosAbsolute(e)),this._parser.registerCsiHandler({final:`a`},e=>this.hPositionRelative(e)),this._parser.registerCsiHandler({final:`b`},e=>this.repeatPrecedingCharacter(e)),this._parser.registerCsiHandler({final:`c`},e=>this.sendDeviceAttributesPrimary(e)),this._parser.registerCsiHandler({prefix:`>`,final:`c`},e=>this.sendDeviceAttributesSecondary(e)),this._parser.registerCsiHandler({final:`d`},e=>this.linePosAbsolute(e)),this._parser.registerCsiHandler({final:`e`},e=>this.vPositionRelative(e)),this._parser.registerCsiHandler({final:`f`},e=>this.hVPosition(e)),this._parser.registerCsiHandler({final:`g`},e=>this.tabClear(e)),this._parser.registerCsiHandler({final:`h`},e=>this.setMode(e)),this._parser.registerCsiHandler({prefix:`?`,final:`h`},e=>this.setModePrivate(e)),this._parser.registerCsiHandler({final:`l`},e=>this.resetMode(e)),this._parser.registerCsiHandler({prefix:`?`,final:`l`},e=>this.resetModePrivate(e)),this._parser.registerCsiHandler({final:`m`},e=>this.charAttributes(e)),this._parser.registerCsiHandler({final:`n`},e=>this.deviceStatus(e)),this._parser.registerCsiHandler({prefix:`?`,final:`n`},e=>this.deviceStatusPrivate(e)),this._parser.registerCsiHandler({intermediates:`!`,final:`p`},e=>this.softReset(e)),this._parser.registerCsiHandler({intermediates:` `,final:`q`},e=>this.setCursorStyle(e)),this._parser.registerCsiHandler({final:`r`},e=>this.setScrollRegion(e)),this._parser.registerCsiHandler({final:`s`},e=>this.saveCursor(e)),this._parser.registerCsiHandler({final:`t`},e=>this.windowOptions(e)),this._parser.registerCsiHandler({final:`u`},e=>this.restoreCursor(e)),this._parser.registerCsiHandler({intermediates:`'`,final:`}`},e=>this.insertColumns(e)),this._parser.registerCsiHandler({intermediates:`'`,final:`~`},e=>this.deleteColumns(e)),this._parser.registerCsiHandler({intermediates:`"`,final:`q`},e=>this.selectProtected(e)),this._parser.registerCsiHandler({intermediates:`$`,final:`p`},e=>this.requestMode(e,!0)),this._parser.registerCsiHandler({prefix:`?`,intermediates:`$`,final:`p`},e=>this.requestMode(e,!1)),this._parser.setExecuteHandler(L.BEL,()=>this.bell()),this._parser.setExecuteHandler(L.LF,()=>this.lineFeed()),this._parser.setExecuteHandler(L.VT,()=>this.lineFeed()),this._parser.setExecuteHandler(L.FF,()=>this.lineFeed()),this._parser.setExecuteHandler(L.CR,()=>this.carriageReturn()),this._parser.setExecuteHandler(L.BS,()=>this.backspace()),this._parser.setExecuteHandler(L.HT,()=>this.tab()),this._parser.setExecuteHandler(L.SO,()=>this.shiftOut()),this._parser.setExecuteHandler(L.SI,()=>this.shiftIn()),this._parser.setExecuteHandler(Ti.IND,()=>this.index()),this._parser.setExecuteHandler(Ti.NEL,()=>this.nextLine()),this._parser.setExecuteHandler(Ti.HTS,()=>this.tabSet()),this._parser.registerOscHandler(0,new ns(e=>(this.setTitle(e),this.setIconName(e),!0))),this._parser.registerOscHandler(1,new ns(e=>this.setIconName(e))),this._parser.registerOscHandler(2,new ns(e=>this.setTitle(e))),this._parser.registerOscHandler(4,new ns(e=>this.setOrReportIndexedColor(e))),this._parser.registerOscHandler(8,new ns(e=>this.setHyperlink(e))),this._parser.registerOscHandler(10,new ns(e=>this.setOrReportFgColor(e))),this._parser.registerOscHandler(11,new ns(e=>this.setOrReportBgColor(e))),this._parser.registerOscHandler(12,new ns(e=>this.setOrReportCursorColor(e))),this._parser.registerOscHandler(104,new ns(e=>this.restoreIndexedColor(e))),this._parser.registerOscHandler(110,new ns(e=>this.restoreFgColor(e))),this._parser.registerOscHandler(111,new ns(e=>this.restoreBgColor(e))),this._parser.registerOscHandler(112,new ns(e=>this.restoreCursorColor(e))),this._parser.registerEscHandler({final:`7`},()=>this.saveCursor()),this._parser.registerEscHandler({final:`8`},()=>this.restoreCursor()),this._parser.registerEscHandler({final:`D`},()=>this.index()),this._parser.registerEscHandler({final:`E`},()=>this.nextLine()),this._parser.registerEscHandler({final:`H`},()=>this.tabSet()),this._parser.registerEscHandler({final:`M`},()=>this.reverseIndex()),this._parser.registerEscHandler({final:`=`},()=>this.keypadApplicationMode()),this._parser.registerEscHandler({final:`>`},()=>this.keypadNumericMode()),this._parser.registerEscHandler({final:`c`},()=>this.fullReset()),this._parser.registerEscHandler({final:`n`},()=>this.setgLevel(2)),this._parser.registerEscHandler({final:`o`},()=>this.setgLevel(3)),this._parser.registerEscHandler({final:`|`},()=>this.setgLevel(3)),this._parser.registerEscHandler({final:`}`},()=>this.setgLevel(2)),this._parser.registerEscHandler({final:`~`},()=>this.setgLevel(1)),this._parser.registerEscHandler({intermediates:`%`,final:`@`},()=>this.selectDefaultCharset()),this._parser.registerEscHandler({intermediates:`%`,final:`G`},()=>this.selectDefaultCharset());for(let e in X)this._parser.registerEscHandler({intermediates:`(`,final:e},()=>this.selectCharset(`(`+e)),this._parser.registerEscHandler({intermediates:`)`,final:e},()=>this.selectCharset(`)`+e)),this._parser.registerEscHandler({intermediates:`*`,final:e},()=>this.selectCharset(`*`+e)),this._parser.registerEscHandler({intermediates:`+`,final:e},()=>this.selectCharset(`+`+e)),this._parser.registerEscHandler({intermediates:`-`,final:e},()=>this.selectCharset(`-`+e)),this._parser.registerEscHandler({intermediates:`.`,final:e},()=>this.selectCharset(`.`+e)),this._parser.registerEscHandler({intermediates:`/`,final:e},()=>this.selectCharset(`/`+e));this._parser.registerEscHandler({intermediates:`#`,final:`8`},()=>this.screenAlignmentPattern()),this._parser.setErrorHandler(e=>(this._logService.error(`Parsing error: `,e),e)),this._parser.registerDcsHandler({intermediates:`$`,final:`q`},new os((e,t)=>this.requestStatusString(e,t)))}getAttrData(){return this._curAttrData}_preserveStack(e,t,n,r){this._parseStack.paused=!0,this._parseStack.cursorStartX=e,this._parseStack.cursorStartY=t,this._parseStack.decodedLength=n,this._parseStack.position=r}_logSlowResolvingAsync(e){this._logService.logLevel<=3&&Promise.race([e,new Promise((e,t)=>setTimeout(()=>t(`#SLOW_TIMEOUT`),bs))]).catch(e=>{if(e!==`#SLOW_TIMEOUT`)throw e;console.warn(`async parser handler taking longer than ${bs} ms`)})}_getCurrentLinkId(){return this._curAttrData.extended.urlId}parse(e,t){let n,r=this._activeBuffer.x,i=this._activeBuffer.y,a=0,o=this._parseStack.paused;if(o){if(n=this._parser.parse(this._parseBuffer,this._parseStack.decodedLength,t))return this._logSlowResolvingAsync(n),n;r=this._parseStack.cursorStartX,i=this._parseStack.cursorStartY,this._parseStack.paused=!1,e.length>_s&&(a=this._parseStack.position+_s)}if(this._logService.logLevel<=1&&this._logService.debug(`parsing data ${typeof e==`string`?` "${e}"`:` "${Array.prototype.map.call(e,e=>String.fromCharCode(e)).join(``)}"`}`),this._logService.logLevel===0&&this._logService.trace(`parsing data (codes)`,typeof e==`string`?e.split(``).map(e=>e.charCodeAt(0)):e),this._parseBuffer.length_s)for(let t=a;t0&&d.getWidth(this._activeBuffer.x-1)===2&&d.setCellFromCodepoint(this._activeBuffer.x-1,0,1,u);let f=this._parser.precedingJoinState;for(let p=t;ps){if(c){let e=d,t=this._activeBuffer.x-m;for(this._activeBuffer.x=m,this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData(),!0)):(this._activeBuffer.y>=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!0),d=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y),m>0&&d instanceof go&&d.copyCellsFrom(e,t,0,m,!1);t=0;)d.setCellFromCodepoint(this._activeBuffer.x++,0,0,u);continue}if(l&&(d.insertCells(this._activeBuffer.x,i-m,this._activeBuffer.getNullCell(u)),d.getWidth(s-1)===2&&d.setCellFromCodepoint(s-1,0,1,u)),d.setCellFromCodepoint(this._activeBuffer.x++,r,i,u),i>0)for(;--i;)d.setCellFromCodepoint(this._activeBuffer.x++,0,0,u)}this._parser.precedingJoinState=f,this._activeBuffer.x0&&d.getWidth(this._activeBuffer.x)===0&&!d.hasContent(this._activeBuffer.x)&&d.setCellFromCodepoint(this._activeBuffer.x,0,1,u),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}registerCsiHandler(e,t){return e.final===`t`&&!e.prefix&&!e.intermediates?this._parser.registerCsiHandler(e,e=>!ys(e.params[0],this._optionsService.rawOptions.windowOptions)||t(e)):this._parser.registerCsiHandler(e,t)}registerDcsHandler(e,t){return this._parser.registerDcsHandler(e,new os(t))}registerEscHandler(e,t){return this._parser.registerEscHandler(e,t)}registerOscHandler(e,t){return this._parser.registerOscHandler(e,new ns(t))}bell(){return this._onRequestBell.fire(),!0}lineFeed(){return this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._optionsService.rawOptions.convertEol&&(this._activeBuffer.x=0),this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData())):this._activeBuffer.y>=this._bufferService.rows?this._activeBuffer.y=this._bufferService.rows-1:this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.x>=this._bufferService.cols&&this._activeBuffer.x--,this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._onLineFeed.fire(),!0}carriageReturn(){return this._activeBuffer.x=0,!0}backspace(){if(!this._coreService.decPrivateModes.reverseWraparound)return this._restrictCursor(),this._activeBuffer.x>0&&this._activeBuffer.x--,!0;if(this._restrictCursor(this._bufferService.cols),this._activeBuffer.x>0)this._activeBuffer.x--;else if(this._activeBuffer.x===0&&this._activeBuffer.y>this._activeBuffer.scrollTop&&this._activeBuffer.y<=this._activeBuffer.scrollBottom&&this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y)?.isWrapped){this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.y--,this._activeBuffer.x=this._bufferService.cols-1;let e=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y);e.hasWidth(this._activeBuffer.x)&&!e.hasContent(this._activeBuffer.x)&&this._activeBuffer.x--}return this._restrictCursor(),!0}tab(){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let e=this._activeBuffer.x;return this._activeBuffer.x=this._activeBuffer.nextStop(),this._optionsService.rawOptions.screenReaderMode&&this._onA11yTab.fire(this._activeBuffer.x-e),!0}shiftOut(){return this._charsetService.setgLevel(1),!0}shiftIn(){return this._charsetService.setgLevel(0),!0}_restrictCursor(e=this._bufferService.cols-1){this._activeBuffer.x=Math.min(e,Math.max(0,this._activeBuffer.x)),this._activeBuffer.y=this._coreService.decPrivateModes.origin?Math.min(this._activeBuffer.scrollBottom,Math.max(this._activeBuffer.scrollTop,this._activeBuffer.y)):Math.min(this._bufferService.rows-1,Math.max(0,this._activeBuffer.y)),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_setCursor(e,t){this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._coreService.decPrivateModes.origin?(this._activeBuffer.x=e,this._activeBuffer.y=this._activeBuffer.scrollTop+t):(this._activeBuffer.x=e,this._activeBuffer.y=t),this._restrictCursor(),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_moveCursor(e,t){this._restrictCursor(),this._setCursor(this._activeBuffer.x+e,this._activeBuffer.y+t)}cursorUp(e){let t=this._activeBuffer.y-this._activeBuffer.scrollTop;return t>=0?this._moveCursor(0,-Math.min(t,e.params[0]||1)):this._moveCursor(0,-(e.params[0]||1)),!0}cursorDown(e){let t=this._activeBuffer.scrollBottom-this._activeBuffer.y;return t>=0?this._moveCursor(0,Math.min(t,e.params[0]||1)):this._moveCursor(0,e.params[0]||1),!0}cursorForward(e){return this._moveCursor(e.params[0]||1,0),!0}cursorBackward(e){return this._moveCursor(-(e.params[0]||1),0),!0}cursorNextLine(e){return this.cursorDown(e),this._activeBuffer.x=0,!0}cursorPrecedingLine(e){return this.cursorUp(e),this._activeBuffer.x=0,!0}cursorCharAbsolute(e){return this._setCursor((e.params[0]||1)-1,this._activeBuffer.y),!0}cursorPosition(e){return this._setCursor(e.length>=2?(e.params[1]||1)-1:0,(e.params[0]||1)-1),!0}charPosAbsolute(e){return this._setCursor((e.params[0]||1)-1,this._activeBuffer.y),!0}hPositionRelative(e){return this._moveCursor(e.params[0]||1,0),!0}linePosAbsolute(e){return this._setCursor(this._activeBuffer.x,(e.params[0]||1)-1),!0}vPositionRelative(e){return this._moveCursor(0,e.params[0]||1),!0}hVPosition(e){return this.cursorPosition(e),!0}tabClear(e){let t=e.params[0];return t===0?delete this._activeBuffer.tabs[this._activeBuffer.x]:t===3&&(this._activeBuffer.tabs={}),!0}cursorForwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let t=e.params[0]||1;for(;t--;)this._activeBuffer.x=this._activeBuffer.nextStop();return!0}cursorBackwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let t=e.params[0]||1;for(;t--;)this._activeBuffer.x=this._activeBuffer.prevStop();return!0}selectProtected(e){let t=e.params[0];return t===1&&(this._curAttrData.bg|=536870912),(t===2||t===0)&&(this._curAttrData.bg&=-536870913),!0}_eraseInBufferLine(e,t,n,r=!1,i=!1){let a=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);a.replaceCells(t,n,this._activeBuffer.getNullCell(this._eraseAttrData()),i),r&&(a.isWrapped=!1)}_resetBufferLine(e,t=!1){let n=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);n&&(n.fill(this._activeBuffer.getNullCell(this._eraseAttrData()),t),this._bufferService.buffer.clearMarkers(this._activeBuffer.ybase+e),n.isWrapped=!1)}eraseInDisplay(e,t=!1){this._restrictCursor(this._bufferService.cols);let n;switch(e.params[0]){case 0:for(n=this._activeBuffer.y,this._dirtyRowTracker.markDirty(n),this._eraseInBufferLine(n++,this._activeBuffer.x,this._bufferService.cols,this._activeBuffer.x===0,t);n=this._bufferService.cols&&(this._activeBuffer.lines.get(n+1).isWrapped=!1);n--;)this._resetBufferLine(n,t);this._dirtyRowTracker.markDirty(0);break;case 2:if(this._optionsService.rawOptions.scrollOnEraseInDisplay){for(n=this._bufferService.rows,this._dirtyRowTracker.markRangeDirty(0,n-1);n--&&!this._activeBuffer.lines.get(this._activeBuffer.ybase+n)?.getTrimmedLength(););for(;n>=0;n--)this._bufferService.scroll(this._eraseAttrData())}else{for(n=this._bufferService.rows,this._dirtyRowTracker.markDirty(n-1);n--;)this._resetBufferLine(n,t);this._dirtyRowTracker.markDirty(0)}break;case 3:let e=this._activeBuffer.lines.length-this._bufferService.rows;e>0&&(this._activeBuffer.lines.trimStart(e),this._activeBuffer.ybase=Math.max(this._activeBuffer.ybase-e,0),this._activeBuffer.ydisp=Math.max(this._activeBuffer.ydisp-e,0),this._onScroll.fire(0));break}return!0}eraseInLine(e,t=!1){switch(this._restrictCursor(this._bufferService.cols),e.params[0]){case 0:this._eraseInBufferLine(this._activeBuffer.y,this._activeBuffer.x,this._bufferService.cols,this._activeBuffer.x===0,t);break;case 1:this._eraseInBufferLine(this._activeBuffer.y,0,this._activeBuffer.x+1,!1,t);break;case 2:this._eraseInBufferLine(this._activeBuffer.y,0,this._bufferService.cols,!0,t);break}return this._dirtyRowTracker.markDirty(this._activeBuffer.y),!0}insertLines(e){this._restrictCursor();let t=e.params[0]||1;if(this._activeBuffer.y>this._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.y65535?2:1}let c=s;for(let e=1;e0||(this._is(`xterm`)||this._is(`rxvt-unicode`)||this._is(`screen`)?this._coreService.triggerDataEvent(L.ESC+`[?1;2c`):this._is(`linux`)&&this._coreService.triggerDataEvent(L.ESC+`[?6c`)),!0}sendDeviceAttributesSecondary(e){return e.params[0]>0||(this._is(`xterm`)?this._coreService.triggerDataEvent(L.ESC+`[>0;276;0c`):this._is(`rxvt-unicode`)?this._coreService.triggerDataEvent(L.ESC+`[>85;95;0c`):this._is(`linux`)?this._coreService.triggerDataEvent(e.params[0]+`c`):this._is(`screen`)&&this._coreService.triggerDataEvent(L.ESC+`[>83;40003;0c`)),!0}_is(e){return(this._optionsService.rawOptions.termName+``).indexOf(e)===0}setMode(e){for(let t=0;t(e[e.NOT_RECOGNIZED=0]=`NOT_RECOGNIZED`,e[e.SET=1]=`SET`,e[e.RESET=2]=`RESET`,e[e.PERMANENTLY_SET=3]=`PERMANENTLY_SET`,e[e.PERMANENTLY_RESET=4]=`PERMANENTLY_RESET`))(n||={});let r=this._coreService.decPrivateModes,{activeProtocol:i,activeEncoding:a}=this._coreMouseService,o=this._coreService,{buffers:s,cols:c}=this._bufferService,{active:l,alt:u}=s,d=this._optionsService.rawOptions,f=(e,n)=>(o.triggerDataEvent(`${L.ESC}[${t?``:`?`}${e};${n}$y`),!0),p=e=>e?1:2,m=e.params[0];return t?m===2?f(m,4):m===4?f(m,p(o.modes.insertMode)):m===12?f(m,3):m===20?f(m,p(d.convertEol)):f(m,0):m===1?f(m,p(r.applicationCursorKeys)):m===3?f(m,d.windowOptions.setWinLines?c===80?2:+(c===132):0):m===6?f(m,p(r.origin)):m===7?f(m,p(r.wraparound)):m===8?f(m,3):m===9?f(m,p(i===`X10`)):m===12?f(m,p(d.cursorBlink)):m===25?f(m,p(!o.isCursorHidden)):m===45?f(m,p(r.reverseWraparound)):m===66?f(m,p(r.applicationKeypad)):m===67?f(m,4):m===1e3?f(m,p(i===`VT200`)):m===1002?f(m,p(i===`DRAG`)):m===1003?f(m,p(i===`ANY`)):m===1004?f(m,p(r.sendFocus)):m===1005?f(m,4):m===1006?f(m,p(a===`SGR`)):m===1015?f(m,4):m===1016?f(m,p(a===`SGR_PIXELS`)):m===1048?f(m,1):m===47||m===1047||m===1049?f(m,p(l===u)):m===2004?f(m,p(r.bracketedPasteMode)):m===2026?f(m,p(r.synchronizedOutput)):f(m,0)}_updateAttrColor(e,t,n,r,i){return t===2?(e|=50331648,e&=-16777216,e|=Ae.fromColorRGB([n,r,i])):t===5&&(e&=-50331904,e|=33554432|n&255),e}_extractColor(e,t,n){let r=[0,0,-1,0,0,0],i=0,a=0;do{if(r[a+i]=e.params[t+a],e.hasSubParams(t+a)){let n=e.getSubParams(t+a),o=0;do r[1]===5&&(i=1),r[a+o+1+i]=n[o];while(++o=2||r[1]===2&&a+i>=5)break;r[1]&&(i=1)}while(++a+t5)&&(e=1),t.extended.underlineStyle=e,t.fg|=268435456,e===0&&(t.fg&=-268435457),t.updateExtended()}_processSGR0(e){e.fg=Y.fg,e.bg=Y.bg,e.extended=e.extended.clone(),e.extended.underlineStyle=0,e.extended.underlineColor&=-67108864,e.updateExtended()}charAttributes(e){if(e.length===1&&e.params[0]===0)return this._processSGR0(this._curAttrData),!0;let t=e.length,n,r=this._curAttrData;for(let i=0;i=30&&n<=37?(r.fg&=-50331904,r.fg|=16777216|n-30):n>=40&&n<=47?(r.bg&=-50331904,r.bg|=16777216|n-40):n>=90&&n<=97?(r.fg&=-50331904,r.fg|=n-90|16777224):n>=100&&n<=107?(r.bg&=-50331904,r.bg|=n-100|16777224):n===0?this._processSGR0(r):n===1?r.fg|=134217728:n===3?r.bg|=67108864:n===4?(r.fg|=268435456,this._processUnderline(e.hasSubParams(i)?e.getSubParams(i)[0]:1,r)):n===5?r.fg|=536870912:n===7?r.fg|=67108864:n===8?r.fg|=1073741824:n===9?r.fg|=2147483648:n===2?r.bg|=134217728:n===21?this._processUnderline(2,r):n===22?(r.fg&=-134217729,r.bg&=-134217729):n===23?r.bg&=-67108865:n===24?(r.fg&=-268435457,this._processUnderline(0,r)):n===25?r.fg&=-536870913:n===27?r.fg&=-67108865:n===28?r.fg&=-1073741825:n===29?r.fg&=2147483647:n===39?(r.fg&=-67108864,r.fg|=Y.fg&16777215):n===49?(r.bg&=-67108864,r.bg|=Y.bg&16777215):n===38||n===48||n===58?i+=this._extractColor(e,i,r):n===53?r.bg|=1073741824:n===55?r.bg&=-1073741825:n===59?(r.extended=r.extended.clone(),r.extended.underlineColor=-1,r.updateExtended()):n===100?(r.fg&=-67108864,r.fg|=Y.fg&16777215,r.bg&=-67108864,r.bg|=Y.bg&16777215):this._logService.debug(`Unknown SGR attribute: %d.`,n);return!0}deviceStatus(e){switch(e.params[0]){case 5:this._coreService.triggerDataEvent(`${L.ESC}[0n`);break;case 6:let e=this._activeBuffer.y+1,t=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`${L.ESC}[${e};${t}R`);break}return!0}deviceStatusPrivate(e){switch(e.params[0]){case 6:let e=this._activeBuffer.y+1,t=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`${L.ESC}[?${e};${t}R`);break;case 15:break;case 25:break;case 26:break;case 53:break}return!0}softReset(e){return this._coreService.isCursorHidden=!1,this._onRequestSyncScrollBar.fire(),this._activeBuffer.scrollTop=0,this._activeBuffer.scrollBottom=this._bufferService.rows-1,this._curAttrData=Y.clone(),this._coreService.reset(),this._charsetService.reset(),this._activeBuffer.savedX=0,this._activeBuffer.savedY=this._activeBuffer.ybase,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,this._coreService.decPrivateModes.origin=!1,!0}setCursorStyle(e){let t=e.length===0?1:e.params[0];if(t===0)this._coreService.decPrivateModes.cursorStyle=void 0,this._coreService.decPrivateModes.cursorBlink=void 0;else{switch(t){case 1:case 2:this._coreService.decPrivateModes.cursorStyle=`block`;break;case 3:case 4:this._coreService.decPrivateModes.cursorStyle=`underline`;break;case 5:case 6:this._coreService.decPrivateModes.cursorStyle=`bar`;break}let e=t%2==1;this._coreService.decPrivateModes.cursorBlink=e}return!0}setScrollRegion(e){let t=e.params[0]||1,n;return(e.length<2||(n=e.params[1])>this._bufferService.rows||n===0)&&(n=this._bufferService.rows),n>t&&(this._activeBuffer.scrollTop=t-1,this._activeBuffer.scrollBottom=n-1,this._setCursor(0,0)),!0}windowOptions(e){if(!ys(e.params[0],this._optionsService.rawOptions.windowOptions))return!0;let t=e.length>1?e.params[1]:0;switch(e.params[0]){case 14:t!==2&&this._onRequestWindowsOptionsReport.fire(0);break;case 16:this._onRequestWindowsOptionsReport.fire(1);break;case 18:this._bufferService&&this._coreService.triggerDataEvent(`${L.ESC}[8;${this._bufferService.rows};${this._bufferService.cols}t`);break;case 22:(t===0||t===2)&&(this._windowTitleStack.push(this._windowTitle),this._windowTitleStack.length>vs&&this._windowTitleStack.shift()),(t===0||t===1)&&(this._iconNameStack.push(this._iconName),this._iconNameStack.length>vs&&this._iconNameStack.shift());break;case 23:(t===0||t===2)&&this._windowTitleStack.length&&this.setTitle(this._windowTitleStack.pop()),(t===0||t===1)&&this._iconNameStack.length&&this.setIconName(this._iconNameStack.pop());break}return!0}saveCursor(e){return this._activeBuffer.savedX=this._activeBuffer.x,this._activeBuffer.savedY=this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,!0}restoreCursor(e){return this._activeBuffer.x=this._activeBuffer.savedX||0,this._activeBuffer.y=Math.max(this._activeBuffer.savedY-this._activeBuffer.ybase,0),this._curAttrData.fg=this._activeBuffer.savedCurAttrData.fg,this._curAttrData.bg=this._activeBuffer.savedCurAttrData.bg,this._charsetService.charset=this._savedCharset,this._activeBuffer.savedCharset&&(this._charsetService.charset=this._activeBuffer.savedCharset),this._restrictCursor(),!0}setTitle(e){return this._windowTitle=e,this._onTitleChange.fire(e),!0}setIconName(e){return this._iconName=e,!0}setOrReportIndexedColor(e){let t=[],n=e.split(`;`);for(;n.length>1;){let e=n.shift(),r=n.shift();if(/^\d+$/.exec(e)){let n=parseInt(e);if(ws(n))if(r===`?`)t.push({type:0,index:n});else{let e=ps(r);e&&t.push({type:1,index:n,color:e})}}}return t.length&&this._onColor.fire(t),!0}setHyperlink(e){let t=e.indexOf(`;`);if(t===-1)return!0;let n=e.slice(0,t).trim(),r=e.slice(t+1);return r?this._createHyperlink(n,r):!n.trim()&&this._finishHyperlink()}_createHyperlink(e,t){this._getCurrentLinkId()&&this._finishHyperlink();let n=e.split(`:`),r,i=n.findIndex(e=>e.startsWith(`id=`));return i!==-1&&(r=n[i].slice(3)||void 0),this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=this._oscLinkService.registerLink({id:r,uri:t}),this._curAttrData.updateExtended(),!0}_finishHyperlink(){return this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=0,this._curAttrData.updateExtended(),!0}_setOrReportSpecialColor(e,t){let n=e.split(`;`);for(let e=0;e=this._specialColors.length);++e,++t)if(n[e]===`?`)this._onColor.fire([{type:0,index:this._specialColors[t]}]);else{let r=ps(n[e]);r&&this._onColor.fire([{type:1,index:this._specialColors[t],color:r}])}return!0}setOrReportFgColor(e){return this._setOrReportSpecialColor(e,0)}setOrReportBgColor(e){return this._setOrReportSpecialColor(e,1)}setOrReportCursorColor(e){return this._setOrReportSpecialColor(e,2)}restoreIndexedColor(e){if(!e)return this._onColor.fire([{type:2}]),!0;let t=[],n=e.split(`;`);for(let e=0;e=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._restrictCursor(),!0}tabSet(){return this._activeBuffer.tabs[this._activeBuffer.x]=!0,!0}reverseIndex(){if(this._restrictCursor(),this._activeBuffer.y===this._activeBuffer.scrollTop){let e=this._activeBuffer.scrollBottom-this._activeBuffer.scrollTop;this._activeBuffer.lines.shiftElements(this._activeBuffer.ybase+this._activeBuffer.y,e,1),this._activeBuffer.lines.set(this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.getBlankLine(this._eraseAttrData())),this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop,this._activeBuffer.scrollBottom)}else this._activeBuffer.y--,this._restrictCursor();return!0}fullReset(){return this._parser.reset(),this._onRequestReset.fire(),!0}reset(){this._curAttrData=Y.clone(),this._eraseAttrDataInternal=Y.clone()}_eraseAttrData(){return this._eraseAttrDataInternal.bg&=-67108864,this._eraseAttrDataInternal.bg|=this._curAttrData.bg&67108863,this._eraseAttrDataInternal}setgLevel(e){return this._charsetService.setgLevel(e),!0}screenAlignmentPattern(){let e=new Me;e.content=4194373,e.fg=this._curAttrData.fg,e.bg=this._curAttrData.bg,this._setCursor(0,0);for(let t=0;t(this._coreService.triggerDataEvent(`${L.ESC}${e}${L.ESC}\\`),!0),r=this._bufferService.buffer,i=this._optionsService.rawOptions;return n(e===`"q`?`P1$r${+!!this._curAttrData.isProtected()}"q`:e===`"p`?`P1$r61;1"p`:e===`r`?`P1$r${r.scrollTop+1};${r.scrollBottom+1}r`:e===`m`?`P1$r0m`:e===` q`?`P1$r${{block:2,underline:4,bar:6}[i.cursorStyle]-+!!i.cursorBlink} q`:`P0$r`)}markRangeDirty(e,t){this._dirtyRowTracker.markRangeDirty(e,t)}},Cs=class{constructor(e){this._bufferService=e,this.clearRange()}clearRange(){this.start=this._bufferService.buffer.y,this.end=this._bufferService.buffer.y}markDirty(e){ethis.end&&(this.end=e)}markRangeDirty(e,t){e>t&&(xs=e,e=t,t=xs),ethis.end&&(this.end=t)}markAllDirty(){this.markRangeDirty(0,this._bufferService.rows-1)}};Cs=C([w(0,D)],Cs);function ws(e){return 0<=e&&e<256}var Ts=5e7,Es=12,Ds=50,Os=class extends A{constructor(e){super(),this._action=e,this._writeBuffer=[],this._callbacks=[],this._pendingData=0,this._bufferOffset=0,this._isSyncWriting=!1,this._syncCalls=0,this._didUserInput=!1,this._onWriteParsed=this._register(new N),this.onWriteParsed=this._onWriteParsed.event}handleUserInput(){this._didUserInput=!0}writeSync(e,t){if(t!==void 0&&this._syncCalls>t){this._syncCalls=0;return}if(this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(void 0),this._syncCalls++,this._isSyncWriting)return;this._isSyncWriting=!0;let n;for(;n=this._writeBuffer.shift();){this._action(n);let e=this._callbacks.shift();e&&e()}this._pendingData=0,this._bufferOffset=2147483647,this._isSyncWriting=!1,this._syncCalls=0}write(e,t){if(this._pendingData>Ts)throw Error(`write data discarded, use flow control to avoid losing data`);if(!this._writeBuffer.length){if(this._bufferOffset=0,this._didUserInput){this._didUserInput=!1,this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t),this._innerWrite();return}setTimeout(()=>this._innerWrite())}this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t)}_innerWrite(e=0,t=!0){let n=e||performance.now();for(;this._writeBuffer.length>this._bufferOffset;){let e=this._writeBuffer[this._bufferOffset],r=this._action(e,t);if(r){r.catch(e=>(queueMicrotask(()=>{throw e}),Promise.resolve(!1))).then(e=>performance.now()-n>=Es?setTimeout(()=>this._innerWrite(0,e)):this._innerWrite(n,e));return}let i=this._callbacks[this._bufferOffset];if(i&&i(),this._bufferOffset++,this._pendingData-=e.length,performance.now()-n>=Es)break}this._writeBuffer.length>this._bufferOffset?(this._bufferOffset>Ds&&(this._writeBuffer=this._writeBuffer.slice(this._bufferOffset),this._callbacks=this._callbacks.slice(this._bufferOffset),this._bufferOffset=0),setTimeout(()=>this._innerWrite())):(this._writeBuffer.length=0,this._callbacks.length=0,this._pendingData=0,this._bufferOffset=0),this._onWriteParsed.fire()}},ks=class{constructor(e){this._bufferService=e,this._nextId=1,this._entriesWithId=new Map,this._dataByLinkId=new Map}registerLink(e){let t=this._bufferService.buffer;if(e.id===void 0){let n=t.addMarker(t.ybase+t.y),r={data:e,id:this._nextId++,lines:[n]};return n.onDispose(()=>this._removeMarkerFromLink(r,n)),this._dataByLinkId.set(r.id,r),r.id}let n=e,r=this._getEntryIdKey(n),i=this._entriesWithId.get(r);if(i)return this.addLineToLink(i.id,t.ybase+t.y),i.id;let a=t.addMarker(t.ybase+t.y),o={id:this._nextId++,key:this._getEntryIdKey(n),data:n,lines:[a]};return a.onDispose(()=>this._removeMarkerFromLink(o,a)),this._entriesWithId.set(o.key,o),this._dataByLinkId.set(o.id,o),o.id}addLineToLink(e,t){let n=this._dataByLinkId.get(e);if(n&&n.lines.every(e=>e.line!==t)){let e=this._bufferService.buffer.addMarker(t);n.lines.push(e),e.onDispose(()=>this._removeMarkerFromLink(n,e))}}getLinkData(e){return this._dataByLinkId.get(e)?.data}_getEntryIdKey(e){return`${e.id};;${e.uri}`}_removeMarkerFromLink(e,t){let n=e.lines.indexOf(t);n!==-1&&(e.lines.splice(n,1),e.lines.length===0&&(e.data.id!==void 0&&this._entriesWithId.delete(e.key),this._dataByLinkId.delete(e.id)))}};ks=C([w(0,D)],ks);var As=!1,js=class extends A{constructor(e){super(),this._windowsWrappingHeuristics=this._register(new jt),this._onBinary=this._register(new N),this.onBinary=this._onBinary.event,this._onData=this._register(new N),this.onData=this._onData.event,this._onLineFeed=this._register(new N),this.onLineFeed=this._onLineFeed.event,this._onResize=this._register(new N),this.onResize=this._onResize.event,this._onWriteParsed=this._register(new N),this.onWriteParsed=this._onWriteParsed.event,this._onScroll=this._register(new N),this._instantiationService=new so,this.optionsService=this._register(new No(e)),this._instantiationService.setService(O,this.optionsService),this._bufferService=this._register(this._instantiationService.createInstance(Ao)),this._instantiationService.setService(D,this._bufferService),this._logService=this._register(this._instantiationService.createInstance(uo)),this._instantiationService.setService(He,this._logService),this.coreService=this._register(this._instantiationService.createInstance(Ro)),this._instantiationService.setService(ze,this.coreService),this.coreMouseService=this._register(this._instantiationService.createInstance(Uo)),this._instantiationService.setService(Re,this.coreMouseService),this.unicodeService=this._register(this._instantiationService.createInstance(Jo)),this._instantiationService.setService(We,this.unicodeService),this._charsetService=this._instantiationService.createInstance(Yo),this._instantiationService.setService(Be,this._charsetService),this._oscLinkService=this._instantiationService.createInstance(ks),this._instantiationService.setService(Ue,this._oscLinkService),this._inputHandler=this._register(new Ss(this._bufferService,this._charsetService,this.coreService,this._logService,this.optionsService,this._oscLinkService,this.coreMouseService,this.unicodeService)),this._register(M.forward(this._inputHandler.onLineFeed,this._onLineFeed)),this._register(this._inputHandler),this._register(M.forward(this._bufferService.onResize,this._onResize)),this._register(M.forward(this.coreService.onData,this._onData)),this._register(M.forward(this.coreService.onBinary,this._onBinary)),this._register(this.coreService.onRequestScrollToBottom(()=>this.scrollToBottom(!0))),this._register(this.coreService.onUserInput(()=>this._writeBuffer.handleUserInput())),this._register(this.optionsService.onMultipleOptionChange([`windowsMode`,`windowsPty`],()=>this._handleWindowsPtyOptionChange())),this._register(this._bufferService.onScroll(()=>{this._onScroll.fire({position:this._bufferService.buffer.ydisp}),this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop,this._bufferService.buffer.scrollBottom)})),this._writeBuffer=this._register(new Os((e,t)=>this._inputHandler.parse(e,t))),this._register(M.forward(this._writeBuffer.onWriteParsed,this._onWriteParsed))}get onScroll(){return this._onScrollApi||(this._onScrollApi=this._register(new N),this._onScroll.event(e=>{this._onScrollApi?.fire(e.position)})),this._onScrollApi.event}get cols(){return this._bufferService.cols}get rows(){return this._bufferService.rows}get buffers(){return this._bufferService.buffers}get options(){return this.optionsService.options}set options(e){for(let t in e)this.optionsService.options[t]=e[t]}write(e,t){this._writeBuffer.write(e,t)}writeSync(e,t){this._logService.logLevel<=3&&!As&&(this._logService.warn(`writeSync is unreliable and will be removed soon.`),As=!0),this._writeBuffer.writeSync(e,t)}input(e,t=!0){this.coreService.triggerDataEvent(e,t)}resize(e,t){isNaN(e)||isNaN(t)||(e=Math.max(e,Oo),t=Math.max(t,ko),this._bufferService.resize(e,t))}scroll(e,t=!1){this._bufferService.scroll(e,t)}scrollLines(e,t){this._bufferService.scrollLines(e,t)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(e){this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){let t=e-this._bufferService.buffer.ydisp;t!==0&&this.scrollLines(t)}registerEscHandler(e,t){return this._inputHandler.registerEscHandler(e,t)}registerDcsHandler(e,t){return this._inputHandler.registerDcsHandler(e,t)}registerCsiHandler(e,t){return this._inputHandler.registerCsiHandler(e,t)}registerOscHandler(e,t){return this._inputHandler.registerOscHandler(e,t)}_setup(){this._handleWindowsPtyOptionChange()}reset(){this._inputHandler.reset(),this._bufferService.reset(),this._charsetService.reset(),this.coreService.reset(),this.coreMouseService.reset()}_handleWindowsPtyOptionChange(){let e=!1,t=this.optionsService.rawOptions.windowsPty;t&&t.buildNumber!==void 0&&t.buildNumber!==void 0?e=t.backend===`conpty`&&t.buildNumber<21376:this.optionsService.rawOptions.windowsMode&&(e=!0),e?this._enableWindowsWrappingHeuristics():this._windowsWrappingHeuristics.clear()}_enableWindowsWrappingHeuristics(){if(!this._windowsWrappingHeuristics.value){let e=[];e.push(this.onLineFeed(Xo.bind(null,this._bufferService))),e.push(this.registerCsiHandler({final:`H`},()=>(Xo(this._bufferService),!1))),this._windowsWrappingHeuristics.value=k(()=>{for(let t of e)t.dispose()})}}},Ms={48:[`0`,`)`],49:[`1`,`!`],50:[`2`,`@`],51:[`3`,`#`],52:[`4`,`$`],53:[`5`,`%`],54:[`6`,`^`],55:[`7`,`&`],56:[`8`,`*`],57:[`9`,`(`],186:[`;`,`:`],187:[`=`,`+`],188:[`,`,`<`],189:[`-`,`_`],190:[`.`,`>`],191:[`/`,`?`],192:["`",`~`],219:[`[`,`{`],220:[`\\`,`|`],221:[`]`,`}`],222:[`'`,`"`]};function Ns(e,t,n,r){let i={type:0,cancel:!1,key:void 0},a=!!e.shiftKey|(e.altKey?2:0)|(e.ctrlKey?4:0)|(e.metaKey?8:0);switch(e.keyCode){case 0:e.key===`UIKeyInputUpArrow`?t?i.key=L.ESC+`OA`:i.key=L.ESC+`[A`:e.key===`UIKeyInputLeftArrow`?t?i.key=L.ESC+`OD`:i.key=L.ESC+`[D`:e.key===`UIKeyInputRightArrow`?t?i.key=L.ESC+`OC`:i.key=L.ESC+`[C`:e.key===`UIKeyInputDownArrow`&&(t?i.key=L.ESC+`OB`:i.key=L.ESC+`[B`);break;case 8:i.key=e.ctrlKey?`\b`:L.DEL,e.altKey&&(i.key=L.ESC+i.key);break;case 9:if(e.shiftKey){i.key=L.ESC+`[Z`;break}i.key=L.HT,i.cancel=!0;break;case 13:i.key=e.altKey?L.ESC+L.CR:L.CR,i.cancel=!0;break;case 27:i.key=L.ESC,e.altKey&&(i.key=L.ESC+L.ESC),i.cancel=!0;break;case 37:if(e.metaKey)break;a?i.key=L.ESC+`[1;`+(a+1)+`D`:t?i.key=L.ESC+`OD`:i.key=L.ESC+`[D`;break;case 39:if(e.metaKey)break;a?i.key=L.ESC+`[1;`+(a+1)+`C`:t?i.key=L.ESC+`OC`:i.key=L.ESC+`[C`;break;case 38:if(e.metaKey)break;a?i.key=L.ESC+`[1;`+(a+1)+`A`:t?i.key=L.ESC+`OA`:i.key=L.ESC+`[A`;break;case 40:if(e.metaKey)break;a?i.key=L.ESC+`[1;`+(a+1)+`B`:t?i.key=L.ESC+`OB`:i.key=L.ESC+`[B`;break;case 45:!e.shiftKey&&!e.ctrlKey&&(i.key=L.ESC+`[2~`);break;case 46:a?i.key=L.ESC+`[3;`+(a+1)+`~`:i.key=L.ESC+`[3~`;break;case 36:a?i.key=L.ESC+`[1;`+(a+1)+`H`:t?i.key=L.ESC+`OH`:i.key=L.ESC+`[H`;break;case 35:a?i.key=L.ESC+`[1;`+(a+1)+`F`:t?i.key=L.ESC+`OF`:i.key=L.ESC+`[F`;break;case 33:e.shiftKey?i.type=2:e.ctrlKey?i.key=L.ESC+`[5;`+(a+1)+`~`:i.key=L.ESC+`[5~`;break;case 34:e.shiftKey?i.type=3:e.ctrlKey?i.key=L.ESC+`[6;`+(a+1)+`~`:i.key=L.ESC+`[6~`;break;case 112:a?i.key=L.ESC+`[1;`+(a+1)+`P`:i.key=L.ESC+`OP`;break;case 113:a?i.key=L.ESC+`[1;`+(a+1)+`Q`:i.key=L.ESC+`OQ`;break;case 114:a?i.key=L.ESC+`[1;`+(a+1)+`R`:i.key=L.ESC+`OR`;break;case 115:a?i.key=L.ESC+`[1;`+(a+1)+`S`:i.key=L.ESC+`OS`;break;case 116:a?i.key=L.ESC+`[15;`+(a+1)+`~`:i.key=L.ESC+`[15~`;break;case 117:a?i.key=L.ESC+`[17;`+(a+1)+`~`:i.key=L.ESC+`[17~`;break;case 118:a?i.key=L.ESC+`[18;`+(a+1)+`~`:i.key=L.ESC+`[18~`;break;case 119:a?i.key=L.ESC+`[19;`+(a+1)+`~`:i.key=L.ESC+`[19~`;break;case 120:a?i.key=L.ESC+`[20;`+(a+1)+`~`:i.key=L.ESC+`[20~`;break;case 121:a?i.key=L.ESC+`[21;`+(a+1)+`~`:i.key=L.ESC+`[21~`;break;case 122:a?i.key=L.ESC+`[23;`+(a+1)+`~`:i.key=L.ESC+`[23~`;break;case 123:a?i.key=L.ESC+`[24;`+(a+1)+`~`:i.key=L.ESC+`[24~`;break;default:if(e.ctrlKey&&!e.shiftKey&&!e.altKey&&!e.metaKey)e.keyCode>=65&&e.keyCode<=90?i.key=String.fromCharCode(e.keyCode-64):e.keyCode===32?i.key=L.NUL:e.keyCode>=51&&e.keyCode<=55?i.key=String.fromCharCode(e.keyCode-51+27):e.keyCode===56?i.key=L.DEL:e.keyCode===219?i.key=L.ESC:e.keyCode===220?i.key=L.FS:e.keyCode===221&&(i.key=L.GS);else if((!n||r)&&e.altKey&&!e.metaKey){let t=Ms[e.keyCode]?.[+!!e.shiftKey];if(t)i.key=L.ESC+t;else if(e.keyCode>=65&&e.keyCode<=90){let t=e.ctrlKey?e.keyCode-64:e.keyCode+32,n=String.fromCharCode(t);e.shiftKey&&(n=n.toUpperCase()),i.key=L.ESC+n}else if(e.keyCode===32)i.key=L.ESC+(e.ctrlKey?L.NUL:` `);else if(e.key===`Dead`&&e.code.startsWith(`Key`)){let t=e.code.slice(3,4);e.shiftKey||(t=t.toLowerCase()),i.key=L.ESC+t,i.cancel=!0}}else n&&!e.altKey&&!e.ctrlKey&&!e.shiftKey&&e.metaKey?e.keyCode===65&&(i.type=1):e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.keyCode>=48&&e.key.length===1?i.key=e.key:e.key&&e.ctrlKey&&(e.key===`_`&&(i.key=L.US),e.key===`@`&&(i.key=L.NUL));break}return i}var Q=0,Ps=class{constructor(e){this._getKey=e,this._array=[],this._insertedValues=[],this._flushInsertedTask=new Ea,this._isFlushingInserted=!1,this._deletedIndices=[],this._flushDeletedTask=new Ea,this._isFlushingDeleted=!1}clear(){this._array.length=0,this._insertedValues.length=0,this._flushInsertedTask.clear(),this._isFlushingInserted=!1,this._deletedIndices.length=0,this._flushDeletedTask.clear(),this._isFlushingDeleted=!1}insert(e){this._flushCleanupDeleted(),this._insertedValues.length===0&&this._flushInsertedTask.enqueue(()=>this._flushInserted()),this._insertedValues.push(e)}_flushInserted(){let e=this._insertedValues.sort((e,t)=>this._getKey(e)-this._getKey(t)),t=0,n=0,r=Array(this._array.length+this._insertedValues.length);for(let i=0;i=this._array.length||this._getKey(e[t])<=this._getKey(this._array[n])?(r[i]=e[t],t++):r[i]=this._array[n++];this._array=r,this._insertedValues.length=0}_flushCleanupInserted(){!this._isFlushingInserted&&this._insertedValues.length>0&&this._flushInsertedTask.flush()}delete(e){if(this._flushCleanupInserted(),this._array.length===0)return!1;let t=this._getKey(e);if(t===void 0||(Q=this._search(t),Q===-1)||this._getKey(this._array[Q])!==t)return!1;do if(this._array[Q]===e)return this._deletedIndices.length===0&&this._flushDeletedTask.enqueue(()=>this._flushDeleted()),this._deletedIndices.push(Q),!0;while(++Qe-t),t=0,n=Array(this._array.length-e.length),r=0;for(let i=0;i0&&this._flushDeletedTask.flush()}*getKeyIterator(e){if(this._flushCleanupInserted(),this._flushCleanupDeleted(),this._array.length!==0&&(Q=this._search(e),!(Q<0||Q>=this._array.length)&&this._getKey(this._array[Q])===e))do yield this._array[Q];while(++Q=this._array.length)&&this._getKey(this._array[Q])===e))do t(this._array[Q]);while(++Q=t;){let r=t+n>>1,i=this._getKey(this._array[r]);if(i>e)n=r-1;else if(i0&&this._getKey(this._array[r-1])===e;)r--;return r}}return t}},Fs=0,Is=0,Ls=class extends A{constructor(){super(),this._decorations=new Ps(e=>e?.marker.line),this._onDecorationRegistered=this._register(new N),this.onDecorationRegistered=this._onDecorationRegistered.event,this._onDecorationRemoved=this._register(new N),this.onDecorationRemoved=this._onDecorationRemoved.event,this._register(k(()=>this.reset()))}get decorations(){return this._decorations.values()}registerDecoration(e){if(e.marker.isDisposed)return;let t=new Rs(e);if(t){let e=t.marker.onDispose(()=>t.dispose()),n=t.onDispose(()=>{n.dispose(),t&&(this._decorations.delete(t)&&this._onDecorationRemoved.fire(t),e.dispose())});this._decorations.insert(t),this._onDecorationRegistered.fire(t)}return t}reset(){for(let e of this._decorations.values())e.dispose();this._decorations.clear()}*getDecorationsAtCell(e,t,n){let r=0,i=0;for(let a of this._decorations.getKeyIterator(t))r=a.options.x??0,i=r+(a.options.width??1),e>=r&&e{Fs=t.options.x??0,Is=Fs+(t.options.width??1),e>=Fs&&e=this._debounceThresholdMS)this._lastRefreshMs=r,this._innerRefresh();else if(!this._additionalRefreshRequested){let e=r-this._lastRefreshMs,t=this._debounceThresholdMS-e;this._additionalRefreshRequested=!0,this._refreshTimeoutID=window.setTimeout(()=>{this._lastRefreshMs=performance.now(),this._innerRefresh(),this._additionalRefreshRequested=!1,this._refreshTimeoutID=void 0},t)}}_innerRefresh(){if(this._rowStart===void 0||this._rowEnd===void 0||this._rowCount===void 0)return;let e=Math.max(this._rowStart,0),t=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(e,t)}},Vs=20,Hs=class extends A{constructor(e,t,n,r){super(),this._terminal=e,this._coreBrowserService=n,this._renderService=r,this._rowColumns=new WeakMap,this._liveRegionLineCount=0,this._charsToConsume=[],this._charsToAnnounce=``;let i=this._coreBrowserService.mainDocument;this._accessibilityContainer=i.createElement(`div`),this._accessibilityContainer.classList.add(`xterm-accessibility`),this._rowContainer=i.createElement(`div`),this._rowContainer.setAttribute(`role`,`list`),this._rowContainer.classList.add(`xterm-accessibility-tree`),this._rowElements=[];for(let e=0;ethis._handleBoundaryFocus(e,0),this._bottomBoundaryFocusListener=e=>this._handleBoundaryFocus(e,1),this._rowElements[0].addEventListener(`focus`,this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener(`focus`,this._bottomBoundaryFocusListener),this._accessibilityContainer.appendChild(this._rowContainer),this._liveRegion=i.createElement(`div`),this._liveRegion.classList.add(`live-region`),this._liveRegion.setAttribute(`aria-live`,`assertive`),this._accessibilityContainer.appendChild(this._liveRegion),this._liveRegionDebouncer=this._register(new Bs(this._renderRows.bind(this))),!this._terminal.element)throw Error(`Cannot enable accessibility before Terminal.open`);this._terminal.element.insertAdjacentElement(`afterbegin`,this._accessibilityContainer),this._register(this._terminal.onResize(e=>this._handleResize(e.rows))),this._register(this._terminal.onRender(e=>this._refreshRows(e.start,e.end))),this._register(this._terminal.onScroll(()=>this._refreshRows())),this._register(this._terminal.onA11yChar(e=>this._handleChar(e))),this._register(this._terminal.onLineFeed(()=>this._handleChar(` +`))),this._register(this._terminal.onA11yTab(e=>this._handleTab(e))),this._register(this._terminal.onKey(e=>this._handleKey(e.key))),this._register(this._terminal.onBlur(()=>this._clearLiveRegion())),this._register(this._renderService.onDimensionsChange(()=>this._refreshRowsDimensions())),this._register(P(i,`selectionchange`,()=>this._handleSelectionChange())),this._register(this._coreBrowserService.onDprChange(()=>this._refreshRowsDimensions())),this._refreshRowsDimensions(),this._refreshRows(),this._register(k(()=>{this._accessibilityContainer.remove(),this._rowElements.length=0}))}_handleTab(e){for(let t=0;t0?this._charsToConsume.shift()!==e&&(this._charsToAnnounce+=e):this._charsToAnnounce+=e,e===` +`&&(this._liveRegionLineCount++,this._liveRegionLineCount===Vs+1&&(this._liveRegion.textContent+=ge.get())))}_clearLiveRegion(){this._liveRegion.textContent=``,this._liveRegionLineCount=0}_handleKey(e){this._clearLiveRegion(),/\p{Control}/u.test(e)||this._charsToConsume.push(e)}_refreshRows(e,t){this._liveRegionDebouncer.refresh(e,t,this._terminal.rows)}_renderRows(e,t){let n=this._terminal.buffer,r=n.lines.length.toString();for(let i=e;i<=t;i++){let e=n.lines.get(n.ydisp+i),t=[],a=e?.translateToString(!0,void 0,void 0,t)||``,o=(n.ydisp+i+1).toString(),s=this._rowElements[i];s&&(a.length===0?(s.textContent=`\xA0`,this._rowColumns.set(s,[0,1])):(s.textContent=a,this._rowColumns.set(s,t)),s.setAttribute(`aria-posinset`,o),s.setAttribute(`aria-setsize`,r),this._alignRowWidth(s))}this._announceCharacters()}_announceCharacters(){this._charsToAnnounce.length!==0&&(this._liveRegion.textContent+=this._charsToAnnounce,this._charsToAnnounce=``)}_handleBoundaryFocus(e,t){let n=e.target,r=this._rowElements[t===0?1:this._rowElements.length-2];if(n.getAttribute(`aria-posinset`)===(t===0?`1`:`${this._terminal.buffer.lines.length}`)||e.relatedTarget!==r)return;let i,a;if(t===0?(i=n,a=this._rowElements.pop(),this._rowContainer.removeChild(a)):(i=this._rowElements.shift(),a=n,this._rowContainer.removeChild(i)),i.removeEventListener(`focus`,this._topBoundaryFocusListener),a.removeEventListener(`focus`,this._bottomBoundaryFocusListener),t===0){let e=this._createAccessibilityTreeNode();this._rowElements.unshift(e),this._rowContainer.insertAdjacentElement(`afterbegin`,e)}else{let e=this._createAccessibilityTreeNode();this._rowElements.push(e),this._rowContainer.appendChild(e)}this._rowElements[0].addEventListener(`focus`,this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener(`focus`,this._bottomBoundaryFocusListener),this._terminal.scrollLines(t===0?-1:1),this._rowElements[t===0?1:this._rowElements.length-2].focus(),e.preventDefault(),e.stopImmediatePropagation()}_handleSelectionChange(){if(this._rowElements.length===0)return;let e=this._coreBrowserService.mainDocument.getSelection();if(!e)return;if(e.isCollapsed){this._rowContainer.contains(e.anchorNode)&&this._terminal.clearSelection();return}if(!e.anchorNode||!e.focusNode){console.error(`anchorNode and/or focusNode are null`);return}let t={node:e.anchorNode,offset:e.anchorOffset},n={node:e.focusNode,offset:e.focusOffset};if((t.node.compareDocumentPosition(n.node)&Node.DOCUMENT_POSITION_PRECEDING||t.node===n.node&&t.offset>n.offset)&&([t,n]=[n,t]),t.node.compareDocumentPosition(this._rowElements[0])&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_FOLLOWING)&&(t={node:this._rowElements[0].childNodes[0],offset:0}),!this._rowContainer.contains(t.node))return;let r=this._rowElements.slice(-1)[0];if(n.node.compareDocumentPosition(r)&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_PRECEDING)&&(n={node:r,offset:r.textContent?.length??0}),!this._rowContainer.contains(n.node))return;let i=({node:e,offset:t})=>{let n=e instanceof Text?e.parentNode:e,r=parseInt(n?.getAttribute(`aria-posinset`),10)-1;if(isNaN(r))return console.warn(`row is invalid. Race condition?`),null;let i=this._rowColumns.get(n);if(!i)return console.warn(`columns is null. Race condition?`),null;let a=t=this._terminal.cols&&(++r,a=0),{row:r,column:a}},a=i(t),o=i(n);if(!(!a||!o)){if(a.row>o.row||a.row===o.row&&a.column>=o.column)throw Error(`invalid range`);this._terminal.select(a.column,a.row,(o.row-a.row)*this._terminal.cols-a.column+o.column)}}_handleResize(e){this._rowElements[this._rowElements.length-1].removeEventListener(`focus`,this._bottomBoundaryFocusListener);for(let e=this._rowContainer.children.length;ee;)this._rowContainer.removeChild(this._rowElements.pop());this._rowElements[this._rowElements.length-1].addEventListener(`focus`,this._bottomBoundaryFocusListener),this._refreshRowsDimensions()}_createAccessibilityTreeNode(){let e=this._coreBrowserService.mainDocument.createElement(`div`);return e.setAttribute(`role`,`listitem`),e.tabIndex=-1,this._refreshRowDimensions(e),e}_refreshRowsDimensions(){if(this._renderService.dimensions.css.cell.height){Object.assign(this._accessibilityContainer.style,{width:`${this._renderService.dimensions.css.canvas.width}px`,fontSize:`${this._terminal.options.fontSize}px`}),this._rowElements.length!==this._terminal.rows&&this._handleResize(this._terminal.rows);for(let e=0;e{Dt(this._linkCacheDisposables),this._linkCacheDisposables.length=0,this._lastMouseEvent=void 0,this._activeProviderReplies?.clear()})),this._register(this._bufferService.onResize(()=>{this._clearCurrentLink(),this._wasResized=!0})),this._register(P(this._element,`mouseleave`,()=>{this._isMouseOut=!0,this._clearCurrentLink()})),this._register(P(this._element,`mousemove`,this._handleMouseMove.bind(this))),this._register(P(this._element,`mousedown`,this._handleMouseDown.bind(this))),this._register(P(this._element,`mouseup`,this._handleMouseUp.bind(this)))}get currentLink(){return this._currentLink}_handleMouseMove(e){this._lastMouseEvent=e;let t=this._positionFromMouseEvent(e,this._element,this._mouseService);if(!t)return;this._isMouseOut=!1;let n=e.composedPath();for(let e=0;e{e?.forEach(e=>{e.link.dispose&&e.link.dispose()})}),this._activeProviderReplies=new Map,this._activeLine=e.y);let n=!1;for(let[r,i]of this._linkProviderService.linkProviders.entries())t?this._activeProviderReplies?.get(r)&&(n=this._checkLinkProviderResult(r,e,n)):i.provideLinks(e.y,t=>{if(this._isMouseOut)return;let i=t?.map(e=>({link:e}));this._activeProviderReplies?.set(r,i),n=this._checkLinkProviderResult(r,e,n),this._activeProviderReplies?.size===this._linkProviderService.linkProviders.length&&this._removeIntersectingLinks(e.y,this._activeProviderReplies)})}_removeIntersectingLinks(e,t){let n=new Set;for(let r=0;re?this._bufferService.cols:r.link.range.end.x;for(let e=a;e<=o;e++){if(n.has(e)){i.splice(t--,1);break}n.add(e)}}}}_checkLinkProviderResult(e,t,n){if(!this._activeProviderReplies)return n;let r=this._activeProviderReplies.get(e),i=!1;for(let t=0;tthis._linkAtPosition(e.link,t));e&&(n=!0,this._handleNewLink(e))}if(this._activeProviderReplies.size===this._linkProviderService.linkProviders.length&&!n)for(let e=0;ethis._linkAtPosition(e.link,t));if(r){n=!0,this._handleNewLink(r);break}}return n}_handleMouseDown(){this._mouseDownLink=this._currentLink}_handleMouseUp(e){if(!this._currentLink)return;let t=this._positionFromMouseEvent(e,this._element,this._mouseService);t&&this._mouseDownLink&&Ws(this._mouseDownLink.link,this._currentLink.link)&&this._linkAtPosition(this._currentLink.link,t)&&this._currentLink.link.activate(e,this._currentLink.link.text)}_clearCurrentLink(e,t){!this._currentLink||!this._lastMouseEvent||(!e||!t||this._currentLink.link.range.start.y>=e&&this._currentLink.link.range.end.y<=t)&&(this._linkLeave(this._element,this._currentLink.link,this._lastMouseEvent),this._currentLink=void 0,Dt(this._linkCacheDisposables),this._linkCacheDisposables.length=0)}_handleNewLink(e){if(!this._lastMouseEvent)return;let t=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);t&&this._linkAtPosition(e.link,t)&&(this._currentLink=e,this._currentLink.state={decorations:{underline:e.link.decorations===void 0||e.link.decorations.underline,pointerCursor:e.link.decorations===void 0||e.link.decorations.pointerCursor},isHovered:!0},this._linkHover(this._element,e.link,this._lastMouseEvent),e.link.decorations={},Object.defineProperties(e.link.decorations,{pointerCursor:{get:()=>this._currentLink?.state?.decorations.pointerCursor,set:e=>{this._currentLink?.state&&this._currentLink.state.decorations.pointerCursor!==e&&(this._currentLink.state.decorations.pointerCursor=e,this._currentLink.state.isHovered&&this._element.classList.toggle(`xterm-cursor-pointer`,e))}},underline:{get:()=>this._currentLink?.state?.decorations.underline,set:t=>{this._currentLink?.state&&this._currentLink?.state?.decorations.underline!==t&&(this._currentLink.state.decorations.underline=t,this._currentLink.state.isHovered&&this._fireUnderlineEvent(e.link,t))}}}),this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange(e=>{if(!this._currentLink)return;let t=e.start===0?0:e.start+1+this._bufferService.buffer.ydisp,n=this._bufferService.buffer.ydisp+1+e.end;if(this._currentLink.link.range.start.y>=t&&this._currentLink.link.range.end.y<=n&&(this._clearCurrentLink(t,n),this._lastMouseEvent)){let e=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);e&&this._askForLink(e,!1)}})))}_linkHover(e,t,n){this._currentLink?.state&&(this._currentLink.state.isHovered=!0,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!0),this._currentLink.state.decorations.pointerCursor&&e.classList.add(`xterm-cursor-pointer`)),t.hover&&t.hover(n,t.text)}_fireUnderlineEvent(e,t){let n=e.range,r=this._bufferService.buffer.ydisp,i=this._createLinkUnderlineEvent(n.start.x-1,n.start.y-r-1,n.end.x,n.end.y-r-1,void 0);(t?this._onShowLinkUnderline:this._onHideLinkUnderline).fire(i)}_linkLeave(e,t,n){this._currentLink?.state&&(this._currentLink.state.isHovered=!1,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!1),this._currentLink.state.decorations.pointerCursor&&e.classList.remove(`xterm-cursor-pointer`)),t.leave&&t.leave(n,t.text)}_linkAtPosition(e,t){let n=e.range.start.y*this._bufferService.cols+e.range.start.x,r=e.range.end.y*this._bufferService.cols+e.range.end.x,i=t.y*this._bufferService.cols+t.x;return n<=i&&i<=r}_positionFromMouseEvent(e,t,n){let r=n.getCoords(e,t,this._bufferService.cols,this._bufferService.rows);if(r)return{x:r[0],y:r[1]+this._bufferService.buffer.ydisp}}_createLinkUnderlineEvent(e,t,n,r,i){return{x1:e,y1:t,x2:n,y2:r,cols:this._bufferService.cols,fg:i}}};Us=C([w(1,Xe),w(2,Ze),w(3,D),w(4,tt)],Us);function Ws(e,t){return e.text===t.text&&e.range.start.x===t.range.start.x&&e.range.start.y===t.range.start.y&&e.range.end.x===t.range.end.x&&e.range.end.y===t.range.end.y}var Gs=class extends js{constructor(e={}){super(e),this._linkifier=this._register(new jt),this.browser=la,this._keyDownHandled=!1,this._keyDownSeen=!1,this._keyPressHandled=!1,this._unprocessedDeadKey=!1,this._accessibilityManager=this._register(new jt),this._onCursorMove=this._register(new N),this.onCursorMove=this._onCursorMove.event,this._onKey=this._register(new N),this.onKey=this._onKey.event,this._onRender=this._register(new N),this.onRender=this._onRender.event,this._onSelectionChange=this._register(new N),this.onSelectionChange=this._onSelectionChange.event,this._onTitleChange=this._register(new N),this.onTitleChange=this._onTitleChange.event,this._onBell=this._register(new N),this.onBell=this._onBell.event,this._onFocus=this._register(new N),this._onBlur=this._register(new N),this._onA11yCharEmitter=this._register(new N),this._onA11yTabEmitter=this._register(new N),this._onWillOpen=this._register(new N),this._setup(),this._decorationService=this._instantiationService.createInstance(Ls),this._instantiationService.setService(Ge,this._decorationService),this._linkProviderService=this._instantiationService.createInstance(ia),this._instantiationService.setService(tt,this._linkProviderService),this._linkProviderService.registerLinkProvider(this._instantiationService.createInstance(Ke)),this._register(this._inputHandler.onRequestBell(()=>this._onBell.fire())),this._register(this._inputHandler.onRequestRefreshRows(e=>this.refresh(e?.start??0,e?.end??this.rows-1))),this._register(this._inputHandler.onRequestSendFocus(()=>this._reportFocus())),this._register(this._inputHandler.onRequestReset(()=>this.reset())),this._register(this._inputHandler.onRequestWindowsOptionsReport(e=>this._reportWindowsOptions(e))),this._register(this._inputHandler.onColor(e=>this._handleColorEvent(e))),this._register(M.forward(this._inputHandler.onCursorMove,this._onCursorMove)),this._register(M.forward(this._inputHandler.onTitleChange,this._onTitleChange)),this._register(M.forward(this._inputHandler.onA11yChar,this._onA11yCharEmitter)),this._register(M.forward(this._inputHandler.onA11yTab,this._onA11yTabEmitter)),this._register(this._bufferService.onResize(e=>this._afterResize(e.cols,e.rows))),this._register(k(()=>{this._customKeyEventHandler=void 0,this.element?.parentNode?.removeChild(this.element)}))}get linkifier(){return this._linkifier.value}get onFocus(){return this._onFocus.event}get onBlur(){return this._onBlur.event}get onA11yChar(){return this._onA11yCharEmitter.event}get onA11yTab(){return this._onA11yTabEmitter.event}get onWillOpen(){return this._onWillOpen.event}_handleColorEvent(e){if(this._themeService)for(let t of e){let e,n=``;switch(t.index){case 256:e=`foreground`,n=`10`;break;case 257:e=`background`,n=`11`;break;case 258:e=`cursor`,n=`12`;break;default:e=`ansi`,n=`4;`+t.index}switch(t.type){case 0:let r=U.toColorRGB(e===`ansi`?this._themeService.colors.ansi[t.index]:this._themeService.colors[e]);this.coreService.triggerDataEvent(`${L.ESC}]${n};${hs(r)}${Ei.ST}`);break;case 1:if(e===`ansi`)this._themeService.modifyColors(e=>e.ansi[t.index]=H.toColor(...t.color));else{let n=e;this._themeService.modifyColors(e=>e[n]=H.toColor(...t.color))}break;case 2:this._themeService.restoreColor(t.index);break}}}_setup(){super._setup(),this._customKeyEventHandler=void 0}get buffer(){return this.buffers.active}focus(){this.textarea&&this.textarea.focus({preventScroll:!0})}_handleScreenReaderModeOptionChange(e){e?!this._accessibilityManager.value&&this._renderService&&(this._accessibilityManager.value=this._instantiationService.createInstance(Hs,this)):this._accessibilityManager.clear()}_handleTextAreaFocus(e){this.coreService.decPrivateModes.sendFocus&&this.coreService.triggerDataEvent(L.ESC+`[I`),this.element.classList.add(`focus`),this._showCursor(),this._onFocus.fire()}blur(){return this.textarea?.blur()}_handleTextAreaBlur(){this.textarea.value=``,this.refresh(this.buffer.y,this.buffer.y),this.coreService.decPrivateModes.sendFocus&&this.coreService.triggerDataEvent(L.ESC+`[O`),this.element.classList.remove(`focus`),this._onBlur.fire()}_syncTextArea(){if(!this.textarea||!this.buffer.isCursorInViewport||this._compositionHelper.isComposing||!this._renderService)return;let e=this.buffer.ybase+this.buffer.y,t=this.buffer.lines.get(e);if(!t)return;let n=Math.min(this.buffer.x,this.cols-1),r=this._renderService.dimensions.css.cell.height,i=t.getWidth(n),a=this._renderService.dimensions.css.cell.width*i,o=this.buffer.y*this._renderService.dimensions.css.cell.height,s=n*this._renderService.dimensions.css.cell.width;this.textarea.style.left=s+`px`,this.textarea.style.top=o+`px`,this.textarea.style.width=a+`px`,this.textarea.style.height=r+`px`,this.textarea.style.lineHeight=r+`px`,this.textarea.style.zIndex=`-5`}_initGlobal(){this._bindKeys(),this._register(P(this.element,`copy`,e=>{this.hasSelection()&&ye(e,this._selectionService)}));let e=e=>be(e,this.textarea,this.coreService,this.optionsService);this._register(P(this.textarea,`paste`,e)),this._register(P(this.element,`paste`,e)),pa?this._register(P(this.element,`mousedown`,e=>{e.button===2&&Ce(e,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})):this._register(P(this.element,`contextmenu`,e=>{Ce(e,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})),xa&&this._register(P(this.element,`auxclick`,e=>{e.button===1&&Se(e,this.textarea,this.screenElement)}))}_bindKeys(){this._register(P(this.textarea,`keyup`,e=>this._keyUp(e),!0)),this._register(P(this.textarea,`keydown`,e=>this._keyDown(e),!0)),this._register(P(this.textarea,`keypress`,e=>this._keyPress(e),!0)),this._register(P(this.textarea,`compositionstart`,()=>this._compositionHelper.compositionstart())),this._register(P(this.textarea,`compositionupdate`,e=>this._compositionHelper.compositionupdate(e))),this._register(P(this.textarea,`compositionend`,()=>this._compositionHelper.compositionend())),this._register(P(this.textarea,`input`,e=>this._inputEvent(e),!0)),this._register(this.onRender(()=>this._compositionHelper.updateCompositionElements()))}open(e){if(!e)throw Error(`Terminal requires a parent element.`);if(e.isConnected||this._logService.debug(`Terminal.open was called on an element that was not attached to the DOM`),this.element?.ownerDocument.defaultView&&this._coreBrowserService){this.element.ownerDocument.defaultView!==this._coreBrowserService.window&&(this._coreBrowserService.window=this.element.ownerDocument.defaultView);return}this._document=e.ownerDocument,this.options.documentOverride&&this.options.documentOverride instanceof Document&&(this._document=this.optionsService.rawOptions.documentOverride),this.element=this._document.createElement(`div`),this.element.dir=`ltr`,this.element.classList.add(`terminal`),this.element.classList.add(`xterm`),e.appendChild(this.element);let t=this._document.createDocumentFragment();this._viewportElement=this._document.createElement(`div`),this._viewportElement.classList.add(`xterm-viewport`),t.appendChild(this._viewportElement),this.screenElement=this._document.createElement(`div`),this.screenElement.classList.add(`xterm-screen`),this._register(P(this.screenElement,`mousemove`,e=>this.updateCursorStyle(e))),this._helperContainer=this._document.createElement(`div`),this._helperContainer.classList.add(`xterm-helpers`),this.screenElement.appendChild(this._helperContainer),t.appendChild(this.screenElement);let n=this.textarea=this._document.createElement(`textarea`);this.textarea.classList.add(`xterm-helper-textarea`),this.textarea.setAttribute(`aria-label`,me.get()),Sa||this.textarea.setAttribute(`aria-multiline`,`false`),this.textarea.setAttribute(`autocorrect`,`off`),this.textarea.setAttribute(`autocapitalize`,`off`),this.textarea.setAttribute(`spellcheck`,`false`),this.textarea.tabIndex=0,this._register(this.optionsService.onSpecificOptionChange(`disableStdin`,()=>n.readOnly=this.optionsService.rawOptions.disableStdin)),this.textarea.readOnly=this.optionsService.rawOptions.disableStdin,this._coreBrowserService=this._register(this._instantiationService.createInstance(na,this.textarea,e.ownerDocument.defaultView??window,this._document??typeof window<`u`?window.document:null)),this._instantiationService.setService(Ye,this._coreBrowserService),this._register(P(this.textarea,`focus`,e=>this._handleTextAreaFocus(e))),this._register(P(this.textarea,`blur`,()=>this._handleTextAreaBlur())),this._helperContainer.appendChild(this.textarea),this._charSizeService=this._instantiationService.createInstance(Qi,this._document,this._helperContainer),this._instantiationService.setService(Je,this._charSizeService),this._themeService=this._instantiationService.createInstance(ao),this._instantiationService.setService(et,this._themeService),this._characterJoinerService=this._instantiationService.createInstance(Ni),this._instantiationService.setService($e,this._characterJoinerService),this._renderService=this._register(this._instantiationService.createInstance(Oa,this.rows,this.screenElement)),this._instantiationService.setService(Ze,this._renderService),this._register(this._renderService.onRenderedViewportChange(e=>this._onRender.fire(e))),this.onResize(e=>this._renderService.resize(e.cols,e.rows)),this._compositionView=this._document.createElement(`div`),this._compositionView.classList.add(`composition-view`),this._compositionHelper=this._instantiationService.createInstance(Di,this.textarea,this._compositionView),this._helperContainer.appendChild(this._compositionView),this._mouseService=this._instantiationService.createInstance(sa),this._instantiationService.setService(Xe,this._mouseService);let r=this._linkifier.value=this._register(this._instantiationService.createInstance(Us,this.screenElement));this.element.appendChild(t);try{this._onWillOpen.fire(this.element)}catch{}this._renderService.hasRenderer()||this._renderService.setRenderer(this._createRenderer()),this._register(this.onCursorMove(()=>{this._renderService.handleCursorMove(),this._syncTextArea()})),this._register(this.onResize(()=>this._renderService.handleResize(this.cols,this.rows))),this._register(this.onBlur(()=>this._renderService.handleBlur())),this._register(this.onFocus(()=>this._renderService.handleFocus())),this._viewport=this._register(this._instantiationService.createInstance(vi,this.element,this.screenElement)),this._register(this._viewport.onRequestScrollLines(e=>{super.scrollLines(e,!1),this.refresh(0,this.rows-1)})),this._selectionService=this._register(this._instantiationService.createInstance(Xa,this.element,this.screenElement,r)),this._instantiationService.setService(Qe,this._selectionService),this._register(this._selectionService.onRequestScrollLines(e=>this.scrollLines(e.amount,e.suppressScrollEvent))),this._register(this._selectionService.onSelectionChange(()=>this._onSelectionChange.fire())),this._register(this._selectionService.onRequestRedraw(e=>this._renderService.handleSelectionChanged(e.start,e.end,e.columnSelectMode))),this._register(this._selectionService.onLinuxMouseSelection(e=>{this.textarea.value=e,this.textarea.focus(),this.textarea.select()})),this._register(M.any(this._onScroll.event,this._inputHandler.onScroll)(()=>{this._selectionService.refresh(),this._viewport?.queueSync()})),this._register(this._instantiationService.createInstance(yi,this.screenElement)),this._register(P(this.element,`mousedown`,e=>this._selectionService.handleMouseDown(e))),this.coreMouseService.areMouseEventsActive?(this._selectionService.disable(),this.element.classList.add(`enable-mouse-events`)):this._selectionService.enable(),this.options.screenReaderMode&&(this._accessibilityManager.value=this._instantiationService.createInstance(Hs,this)),this._register(this.optionsService.onSpecificOptionChange(`screenReaderMode`,e=>this._handleScreenReaderModeOptionChange(e))),this.options.overviewRuler.width&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(wi,this._viewportElement,this.screenElement))),this.optionsService.onSpecificOptionChange(`overviewRuler`,e=>{!this._overviewRulerRenderer&&e&&this._viewportElement&&this.screenElement&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(wi,this._viewportElement,this.screenElement)))}),this._charSizeService.measure(),this.refresh(0,this.rows-1),this._initGlobal(),this.bindMouse()}_createRenderer(){return this._instantiationService.createInstance(Zi,this,this._document,this.element,this.screenElement,this._viewportElement,this._helperContainer,this.linkifier)}bindMouse(){let e=this,t=this.element;function n(t){let n=e._mouseService.getMouseReportCoords(t,e.screenElement);if(!n)return!1;let r,i;switch(t.overrideType||t.type){case`mousemove`:i=32,t.buttons===void 0?(r=3,t.button!==void 0&&(r=t.button<3?t.button:3)):r=t.buttons&1?0:t.buttons&4?1:t.buttons&2?2:3;break;case`mouseup`:i=0,r=t.button<3?t.button:3;break;case`mousedown`:i=1,r=t.button<3?t.button:3;break;case`wheel`:if(e._customWheelEventHandler&&e._customWheelEventHandler(t)===!1)return!1;let n=t.deltaY;if(n===0||e.coreMouseService.consumeWheelEvent(t,e._renderService?.dimensions?.device?.cell?.height,e._coreBrowserService?.dpr)===0)return!1;i=n<0?0:1,r=4;break;default:return!1}return i===void 0||r===void 0||r>4?!1:e.coreMouseService.triggerMouseEvent({col:n.col,row:n.row,x:n.x,y:n.y,button:r,action:i,ctrl:t.ctrlKey,alt:t.altKey,shift:t.shiftKey})}let r={mouseup:null,wheel:null,mousedrag:null,mousemove:null},i={mouseup:e=>(n(e),e.buttons||(this._document.removeEventListener(`mouseup`,r.mouseup),r.mousedrag&&this._document.removeEventListener(`mousemove`,r.mousedrag)),this.cancel(e)),wheel:e=>(n(e),this.cancel(e,!0)),mousedrag:e=>{e.buttons&&n(e)},mousemove:e=>{e.buttons||n(e)}};this._register(this.coreMouseService.onProtocolChange(e=>{e?(this.optionsService.rawOptions.logLevel===`debug`&&this._logService.debug(`Binding to mouse events:`,this.coreMouseService.explainEvents(e)),this.element.classList.add(`enable-mouse-events`),this._selectionService.disable()):(this._logService.debug(`Unbinding from mouse events.`),this.element.classList.remove(`enable-mouse-events`),this._selectionService.enable()),e&8?r.mousemove||=(t.addEventListener(`mousemove`,i.mousemove),i.mousemove):(t.removeEventListener(`mousemove`,r.mousemove),r.mousemove=null),e&16?r.wheel||=(t.addEventListener(`wheel`,i.wheel,{passive:!1}),i.wheel):(t.removeEventListener(`wheel`,r.wheel),r.wheel=null),e&2?r.mouseup||=i.mouseup:(this._document.removeEventListener(`mouseup`,r.mouseup),r.mouseup=null),e&4?r.mousedrag||=i.mousedrag:(this._document.removeEventListener(`mousemove`,r.mousedrag),r.mousedrag=null)})),this.coreMouseService.activeProtocol=this.coreMouseService.activeProtocol,this._register(P(t,`mousedown`,e=>{if(e.preventDefault(),this.focus(),!(!this.coreMouseService.areMouseEventsActive||this._selectionService.shouldForceSelection(e)))return n(e),r.mouseup&&this._document.addEventListener(`mouseup`,r.mouseup),r.mousedrag&&this._document.addEventListener(`mousemove`,r.mousedrag),this.cancel(e)})),this._register(P(t,`wheel`,t=>{if(!r.wheel){if(this._customWheelEventHandler&&this._customWheelEventHandler(t)===!1)return!1;if(!this.buffer.hasScrollback){if(t.deltaY===0)return!1;if(e.coreMouseService.consumeWheelEvent(t,e._renderService?.dimensions?.device?.cell?.height,e._coreBrowserService?.dpr)===0)return this.cancel(t,!0);let n=L.ESC+(this.coreService.decPrivateModes.applicationCursorKeys?`O`:`[`)+(t.deltaY<0?`A`:`B`);return this.coreService.triggerDataEvent(n,!0),this.cancel(t,!0)}}},{passive:!1}))}refresh(e,t){this._renderService?.refreshRows(e,t)}updateCursorStyle(e){this._selectionService?.shouldColumnSelect(e)?this.element.classList.add(`column-select`):this.element.classList.remove(`column-select`)}_showCursor(){this.coreService.isCursorInitialized||(this.coreService.isCursorInitialized=!0,this.refresh(this.buffer.y,this.buffer.y))}scrollLines(e,t){this._viewport?this._viewport.scrollLines(e):super.scrollLines(e,t),this.refresh(0,this.rows-1)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(e){e&&this._viewport?this._viewport.scrollToLine(this.buffer.ybase,!0):this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){let t=e-this._bufferService.buffer.ydisp;t!==0&&this.scrollLines(t)}paste(e){xe(e,this.textarea,this.coreService,this.optionsService)}attachCustomKeyEventHandler(e){this._customKeyEventHandler=e}attachCustomWheelEventHandler(e){this._customWheelEventHandler=e}registerLinkProvider(e){return this._linkProviderService.registerLinkProvider(e)}registerCharacterJoiner(e){if(!this._characterJoinerService)throw Error(`Terminal must be opened first`);let t=this._characterJoinerService.register(e);return this.refresh(0,this.rows-1),t}deregisterCharacterJoiner(e){if(!this._characterJoinerService)throw Error(`Terminal must be opened first`);this._characterJoinerService.deregister(e)&&this.refresh(0,this.rows-1)}get markers(){return this.buffer.markers}registerMarker(e){return this.buffer.addMarker(this.buffer.ybase+this.buffer.y+e)}registerDecoration(e){return this._decorationService.registerDecoration(e)}hasSelection(){return this._selectionService?this._selectionService.hasSelection:!1}select(e,t,n){this._selectionService.setSelection(e,t,n)}getSelection(){return this._selectionService?this._selectionService.selectionText:``}getSelectionPosition(){if(!(!this._selectionService||!this._selectionService.hasSelection))return{start:{x:this._selectionService.selectionStart[0],y:this._selectionService.selectionStart[1]},end:{x:this._selectionService.selectionEnd[0],y:this._selectionService.selectionEnd[1]}}}clearSelection(){this._selectionService?.clearSelection()}selectAll(){this._selectionService?.selectAll()}selectLines(e,t){this._selectionService?.selectLines(e,t)}_keyDown(e){if(this._keyDownHandled=!1,this._keyDownSeen=!0,this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)return!1;let t=this.browser.isMac&&this.options.macOptionIsMeta&&e.altKey;if(!t&&!this._compositionHelper.keydown(e))return this.options.scrollOnUserInput&&this.buffer.ybase!==this.buffer.ydisp&&this.scrollToBottom(!0),!1;!t&&(e.key===`Dead`||e.key===`AltGraph`)&&(this._unprocessedDeadKey=!0);let n=Ns(e,this.coreService.decPrivateModes.applicationCursorKeys,this.browser.isMac,this.options.macOptionIsMeta);if(this.updateCursorStyle(e),n.type===3||n.type===2){let t=this.rows-1;return this.scrollLines(n.type===2?-t:t),this.cancel(e,!0)}if(n.type===1&&this.selectAll(),this._isThirdLevelShift(this.browser,e)||(n.cancel&&this.cancel(e,!0),!n.key)||e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.key.length===1&&e.key.charCodeAt(0)>=65&&e.key.charCodeAt(0)<=90)return!0;if(this._unprocessedDeadKey)return this._unprocessedDeadKey=!1,!0;if((n.key===L.ETX||n.key===L.CR)&&(this.textarea.value=``),this._onKey.fire({key:n.key,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(n.key,!0),!this.optionsService.rawOptions.screenReaderMode||e.altKey||e.ctrlKey)return this.cancel(e,!0);this._keyDownHandled=!0}_isThirdLevelShift(e,t){let n=e.isMac&&!this.options.macOptionIsMeta&&t.altKey&&!t.ctrlKey&&!t.metaKey||e.isWindows&&t.altKey&&t.ctrlKey&&!t.metaKey||e.isWindows&&t.getModifierState(`AltGraph`);return t.type===`keypress`?n:n&&(!t.keyCode||t.keyCode>47)}_keyUp(e){this._keyDownSeen=!1,!(this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)&&(Ks(e)||this.focus(),this.updateCursorStyle(e),this._keyPressHandled=!1)}_keyPress(e){let t;if(this._keyPressHandled=!1,this._keyDownHandled||this._customKeyEventHandler&&this._customKeyEventHandler(e)===!1)return!1;if(this.cancel(e),e.charCode)t=e.charCode;else if(e.which===null||e.which===void 0)t=e.keyCode;else if(e.which!==0&&e.charCode!==0)t=e.which;else return!1;return!t||(e.altKey||e.ctrlKey||e.metaKey)&&!this._isThirdLevelShift(this.browser,e)?!1:(t=String.fromCharCode(t),this._onKey.fire({key:t,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(t,!0),this._keyPressHandled=!0,this._unprocessedDeadKey=!1,!0)}_inputEvent(e){if(e.data&&e.inputType===`insertText`&&(!e.composed||!this._keyDownSeen)&&!this.optionsService.rawOptions.screenReaderMode){if(this._keyPressHandled)return!1;this._unprocessedDeadKey=!1;let t=e.data;return this.coreService.triggerDataEvent(t,!0),this.cancel(e),!0}return!1}resize(e,t){if(e===this.cols&&t===this.rows){this._charSizeService&&!this._charSizeService.hasValidSize&&this._charSizeService.measure();return}super.resize(e,t)}_afterResize(e,t){this._charSizeService?.measure()}clear(){if(!(this.buffer.ybase===0&&this.buffer.y===0)){this.buffer.clearAllMarkers(),this.buffer.lines.set(0,this.buffer.lines.get(this.buffer.ybase+this.buffer.y)),this.buffer.lines.length=1,this.buffer.ydisp=0,this.buffer.ybase=0,this.buffer.y=0;for(let e=1;e=0;e--)this._addons[e].instance.dispose()}loadAddon(e,t){let n={instance:t,dispose:t.dispose,isDisposed:!1};this._addons.push(n),t.dispose=()=>this._wrappedAddonDispose(n),t.activate(e)}_wrappedAddonDispose(e){if(e.isDisposed)return;let t=-1;for(let n=0;n=this._line.length))return t?(this._line.loadCell(e,t),t):this._line.loadCell(e,new Me)}translateToString(e,t,n){return this._line.translateToString(e,t,n)}},Ys=class{constructor(e,t){this._buffer=e,this.type=t}init(e){return this._buffer=e,this}get cursorY(){return this._buffer.y}get cursorX(){return this._buffer.x}get viewportY(){return this._buffer.ydisp}get baseY(){return this._buffer.ybase}get length(){return this._buffer.lines.length}getLine(e){let t=this._buffer.lines.get(e);if(t)return new Js(t)}getNullCell(){return new Me}},Xs=class extends A{constructor(e){super(),this._core=e,this._onBufferChange=this._register(new N),this.onBufferChange=this._onBufferChange.event,this._normal=new Ys(this._core.buffers.normal,`normal`),this._alternate=new Ys(this._core.buffers.alt,`alternate`),this._core.buffers.onBufferActivate(()=>this._onBufferChange.fire(this.active))}get active(){if(this._core.buffers.active===this._core.buffers.normal)return this.normal;if(this._core.buffers.active===this._core.buffers.alt)return this.alternate;throw Error(`Active buffer is neither normal nor alternate`)}get normal(){return this._normal.init(this._core.buffers.normal)}get alternate(){return this._alternate.init(this._core.buffers.alt)}},Zs=class{constructor(e){this._core=e}registerCsiHandler(e,t){return this._core.registerCsiHandler(e,e=>t(e.toArray()))}addCsiHandler(e,t){return this.registerCsiHandler(e,t)}registerDcsHandler(e,t){return this._core.registerDcsHandler(e,(e,n)=>t(e,n.toArray()))}addDcsHandler(e,t){return this.registerDcsHandler(e,t)}registerEscHandler(e,t){return this._core.registerEscHandler(e,t)}addEscHandler(e,t){return this.registerEscHandler(e,t)}registerOscHandler(e,t){return this._core.registerOscHandler(e,t)}addOscHandler(e,t){return this.registerOscHandler(e,t)}},Qs=class{constructor(e){this._core=e}register(e){this._core.unicodeService.register(e)}get versions(){return this._core.unicodeService.versions}get activeVersion(){return this._core.unicodeService.activeVersion}set activeVersion(e){this._core.unicodeService.activeVersion=e}},$s=[`cols`,`rows`],ec=0,tc=class extends A{constructor(e){super(),this._core=this._register(new Gs(e)),this._addonManager=this._register(new qs),this._publicOptions={...this._core.options};let t=e=>this._core.options[e],n=(e,t)=>{this._checkReadonlyOptions(e),this._core.options[e]=t};for(let e in this._core.options){let r={get:t.bind(this,e),set:n.bind(this,e)};Object.defineProperty(this._publicOptions,e,r)}}_checkReadonlyOptions(e){if($s.includes(e))throw Error(`Option "${e}" can only be set in the constructor`)}_checkProposedApi(){if(!this._core.optionsService.rawOptions.allowProposedApi)throw Error(`You must set the allowProposedApi option to true to use proposed API`)}get onBell(){return this._core.onBell}get onBinary(){return this._core.onBinary}get onCursorMove(){return this._core.onCursorMove}get onData(){return this._core.onData}get onKey(){return this._core.onKey}get onLineFeed(){return this._core.onLineFeed}get onRender(){return this._core.onRender}get onResize(){return this._core.onResize}get onScroll(){return this._core.onScroll}get onSelectionChange(){return this._core.onSelectionChange}get onTitleChange(){return this._core.onTitleChange}get onWriteParsed(){return this._core.onWriteParsed}get element(){return this._core.element}get parser(){return this._parser||=new Zs(this._core),this._parser}get unicode(){return this._checkProposedApi(),new Qs(this._core)}get textarea(){return this._core.textarea}get rows(){return this._core.rows}get cols(){return this._core.cols}get buffer(){return this._buffer||=this._register(new Xs(this._core)),this._buffer}get markers(){return this._checkProposedApi(),this._core.markers}get modes(){let e=this._core.coreService.decPrivateModes,t=`none`;switch(this._core.coreMouseService.activeProtocol){case`X10`:t=`x10`;break;case`VT200`:t=`vt200`;break;case`DRAG`:t=`drag`;break;case`ANY`:t=`any`;break}return{applicationCursorKeysMode:e.applicationCursorKeys,applicationKeypadMode:e.applicationKeypad,bracketedPasteMode:e.bracketedPasteMode,insertMode:this._core.coreService.modes.insertMode,mouseTrackingMode:t,originMode:e.origin,reverseWraparoundMode:e.reverseWraparound,sendFocusMode:e.sendFocus,synchronizedOutputMode:e.synchronizedOutput,wraparoundMode:e.wraparound}}get options(){return this._publicOptions}set options(e){for(let t in e)this._publicOptions[t]=e[t]}blur(){this._core.blur()}focus(){this._core.focus()}input(e,t=!0){this._core.input(e,t)}resize(e,t){this._verifyIntegers(e,t),this._core.resize(e,t)}open(e){this._core.open(e)}attachCustomKeyEventHandler(e){this._core.attachCustomKeyEventHandler(e)}attachCustomWheelEventHandler(e){this._core.attachCustomWheelEventHandler(e)}registerLinkProvider(e){return this._core.registerLinkProvider(e)}registerCharacterJoiner(e){return this._checkProposedApi(),this._core.registerCharacterJoiner(e)}deregisterCharacterJoiner(e){this._checkProposedApi(),this._core.deregisterCharacterJoiner(e)}registerMarker(e=0){return this._verifyIntegers(e),this._core.registerMarker(e)}registerDecoration(e){return this._checkProposedApi(),this._verifyPositiveIntegers(e.x??0,e.width??0,e.height??0),this._core.registerDecoration(e)}hasSelection(){return this._core.hasSelection()}select(e,t,n){this._verifyIntegers(e,t,n),this._core.select(e,t,n)}getSelection(){return this._core.getSelection()}getSelectionPosition(){return this._core.getSelectionPosition()}clearSelection(){this._core.clearSelection()}selectAll(){this._core.selectAll()}selectLines(e,t){this._verifyIntegers(e,t),this._core.selectLines(e,t)}dispose(){super.dispose()}scrollLines(e){this._verifyIntegers(e),this._core.scrollLines(e)}scrollPages(e){this._verifyIntegers(e),this._core.scrollPages(e)}scrollToTop(){this._core.scrollToTop()}scrollToBottom(){this._core.scrollToBottom()}scrollToLine(e){this._verifyIntegers(e),this._core.scrollToLine(e)}clear(){this._core.clear()}write(e,t){this._core.write(e,t)}writeln(e,t){this._core.write(e),this._core.write(`\r +`,t)}paste(e){this._core.paste(e)}refresh(e,t){this._verifyIntegers(e,t),this._core.refresh(e,t)}reset(){this._core.reset()}clearTextureAtlas(){this._core.clearTextureAtlas()}loadAddon(e){this._addonManager.loadAddon(this,e)}static get strings(){return{get promptLabel(){return me.get()},set promptLabel(e){me.set(e)},get tooMuchOutput(){return ge.get()},set tooMuchOutput(e){ge.set(e)}}}_verifyIntegers(...e){for(ec of e)if(ec===1/0||isNaN(ec)||ec%1!=0)throw Error(`This API only accepts integers`)}_verifyPositiveIntegers(...e){for(ec of e)if(ec&&(ec===1/0||isNaN(ec)||ec%1!=0||ec<0))throw Error(`This API only accepts positive integers`)}},nc=2,rc=1,ic=class{activate(e){this._terminal=e}dispose(){}fit(){let e=this.proposeDimensions();if(!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows))return;let t=this._terminal._core;(this._terminal.rows!==e.rows||this._terminal.cols!==e.cols)&&(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}proposeDimensions(){if(!this._terminal||!this._terminal.element||!this._terminal.element.parentElement)return;let e=this._terminal._core._renderService.dimensions;if(e.css.cell.width===0||e.css.cell.height===0)return;let t=this._terminal.options.scrollback===0?0:this._terminal.options.overviewRuler?.width||14,n=window.getComputedStyle(this._terminal.element.parentElement),r=parseInt(n.getPropertyValue(`height`)),i=Math.max(0,parseInt(n.getPropertyValue(`width`))),a=window.getComputedStyle(this._terminal.element),o={top:parseInt(a.getPropertyValue(`padding-top`)),bottom:parseInt(a.getPropertyValue(`padding-bottom`)),right:parseInt(a.getPropertyValue(`padding-right`)),left:parseInt(a.getPropertyValue(`padding-left`))},s=o.top+o.bottom,c=o.right+o.left,l=r-s,u=i-c-t;return{cols:Math.max(nc,Math.floor(u/e.css.cell.width)),rows:Math.max(rc,Math.floor(l/e.css.cell.height))}}};function ac(){return{fontSize:typeof window<`u`&&typeof window.matchMedia==`function`&&window.matchMedia(`(pointer: coarse)`).matches?13:12,fontFamily:getComputedStyle(document.body).fontFamily}}var oc=250;function sc(e){return e.type===`keydown`&&e.ctrlKey&&!e.altKey&&!e.metaKey&&(e.code===`KeyL`||e.key===`l`||e.key===`L`)}var cc=class{seen=null;noteKey(e,t){sc(e)&&(this.seen={at:t,trusted:e.isTrusted,repeat:e.repeat,code:e.code})}report(e,t){if(!e.includes(`\f`))return null;let n=this.seen;return this.seen=null,!n||t-n.at>oc||t{for(let t of e){if(i.current.has(t))continue;let e=a.current.get(t);if(!e||e.clientHeight===0||e.clientWidth===0)continue;let n=new tc({...ac(),theme:{background:`#0b0b0d`,foreground:`#e6e6ec`},cursorBlink:!0}),c=new ic;n.loadAddon(c);let l=new cc;n.attachCustomKeyEventHandler(e=>(l.noteKey(e,performance.now()),!0)),n.onData(e=>{let n=l.report(e,performance.now());y(r.current,{type:`input`,pane:t,data:e})&&n&&y(r.current,{type:`clear_key_report`,pane:t,...n})}),n.onTitleChange(e=>{let n=e.replace(/\s+/g,` `).trim();n&&s(e=>({...e,[t]:n}))}),n.open(e),i.current.set(t,{term:n,fit:c});let u=o.current.get(t);if(u){for(let e of u)n.write(e);o.current.delete(t),n.write(``,()=>n.scrollToBottom())}}for(let[t,n]of i.current)e.includes(t)||(n.term.dispose(),i.current.delete(t))},[e,t,n])}var uc=60;function dc({panes:e,size:t,zoomed:n,socketRef:r,viewsRef:i,bodyRefs:a,sentSizesRef:o,ownsSize:s,layoutPending:c}){let u=(0,l.useRef)(null),d=(0,l.useCallback)(()=>{for(let[e,t]of i.current){let n=a.current.get(e);if(!n||n.clientHeight===0||n.clientWidth===0)continue;let{rows:i,cols:s}=t.term,c=o.current.get(e);c&&c.rows===i&&c.cols===s||y(r.current,{type:`resize`,pane:e,rows:i,cols:s})&&o.current.set(e,{rows:i,cols:s})}},[r,i,a,o]);(0,l.useEffect)(()=>{for(let[e,t]of i.current){let n=a.current.get(e);if(!(!n||n.clientHeight===0||n.clientWidth===0)){if(!s||c){let n=o.current.get(e);n&&t.term.resize(n.cols,n.rows);continue}t.fit.fit()}}if(!(!s||c))return u.current&&clearTimeout(u.current),u.current=setTimeout(d,uc),()=>{u.current&&clearTimeout(u.current)}},[e,n,t,d,i,a,o,s,c])}function fc(e){let[t,n]=(0,l.useState)({});return{recovery:t,setRecovery:n,cancelRecovery:(0,l.useCallback)(t=>{y(e.current,{type:`cancel_recovery`,pane:t})},[e])}}var pc={esc:`\x1B`,tab:` `,"shift-tab":`\x1B[Z`,"ctrl-c":``,"ctrl-d":``,"ctrl-z":``,"ctrl-l":`\f`,"ctrl-r":``,up:`\x1B[A`,down:`\x1B[B`,right:`\x1B[C`,left:`\x1B[D`},mc={up:`\x1BOA`,down:`\x1BOB`,right:`\x1BOC`,left:`\x1BOD`};function hc(e,t=!1){if(t){let t=mc[e];if(t)return t}return pc[e]}var gc=[{key:`esc`,label:`Esc`,aria:`Escape`},{key:`tab`,label:`Tab`,aria:`Tab`},{key:`shift-tab`,label:`⇧Tab`,aria:`Shift Tab`},{key:`ctrl-c`,label:`^C`,aria:`Control C`},{key:`ctrl-d`,label:`^D`,aria:`Control D`},{key:`ctrl-z`,label:`^Z`,aria:`Control Z`},{key:`ctrl-l`,label:`^L`,aria:`Control L`},{key:`ctrl-r`,label:`^R`,aria:`Control R`},{key:`left`,label:`←`,aria:`Left arrow`},{key:`down`,label:`↓`,aria:`Down arrow`},{key:`up`,label:`↑`,aria:`Up arrow`},{key:`right`,label:`→`,aria:`Right arrow`}];function _c(e,t){return e!==null&&t.includes(e)?e:null}function vc(e,t){return e!==null&&_c(e,t)===null}function yc(e,t){return e===t?null:t}function bc({socketRef:e,viewsRef:t,zoomed:n,zoomAskedRef:r,active:i}){let a=t=>y(e.current,t);return{create:()=>a({type:`create`,rows:24,cols:80}),toggleZoom:e=>{let t=yc(r.current===void 0?n:r.current,e);a({type:`zoom`,pane:t})&&(r.current=t)},claimSize:()=>a({type:`claim_size`}),closePane:e=>a({type:`close`,pane:e}),reorder:e=>a({type:`reorder`,order:e}),sendKey:e=>{if(i===null)return;let n=t.current.get(i)?.term.modes.applicationCursorKeysMode??!1;a({type:`input`,pane:i,data:hc(e,n)})}}}function xc({repo:e,panes:t,active:n,setActive:r,zoomed:i,zoom:a,viewsRef:o,lastActiveByRepoRef:s}){(0,l.useEffect)(()=>{if(!vc(i,t)&&n===null&&t.length>0){let n=s.current.get(e);r(n!==void 0&&t.includes(n)?n:t[t.length-1])}},[n,t,e,i,r,s]),(0,l.useEffect)(()=>{a!==null&&a!==n&&(r(a),s.current.set(e,a))},[a,n,e,r,s]),(0,l.useEffect)(()=>{n!==null&&o.current.get(n)?.term.focus()},[n,o])}function Sc({pending:e,size:t,socketRef:n,slotRefs:r,panesExist:i,onAnswered:a}){(0,l.useEffect)(()=>{if(e===null)return;let t=n.current;if(!t||t.readyState!==WebSocket.OPEN)return;let o=[];for(let n=0;n{let t=new tc(ac()),n=new ic;t.loadAddon(n),t.open(e);let r=n.proposeDimensions();if(t.dispose(),!r)throw Error(`could not measure the cell`);return{rows:r.rows,cols:r.cols}})}catch{s=[]}y(t,{type:`start`,sizes:s})&&a()},[e,t,n,r,i,a])}var $=s();function Cc({report:e,pane:t,onCancel:n}){let r=t===void 0?ce(e):`pane ${t} · ${ce(e)}`;return(0,$.jsxs)(`span`,{className:`flex min-w-0 shrink items-center gap-1 rounded-sm bg-ink-800 px-1 text-accent`,title:e.detail??r,children:[(0,$.jsx)(`span`,{className:`truncate`,children:r}),e.detail&&(0,$.jsx)(`span`,{className:`hidden min-w-0 truncate text-ink-400 md:inline`,children:e.detail}),(0,$.jsx)(`button`,{onMouseDown:e=>e.stopPropagation(),onClick:n,title:`Stop waiting and release this pane's slot`,"aria-label":`cancel recovery${t===void 0?``:` for pane ${t}`}`,className:`flex h-5 w-5 shrink-0 items-center justify-center rounded-sm text-ink-400 hover:text-removed`,children:(0,$.jsx)(a,{className:`h-3 w-3`})})]})}function wc({pane:e,index:t,label:r,cellStyle:i,isActive:o,isZoomed:s,showZoom:c,isDragged:l,isDropTarget:u,reorderable:d,recovery:f,onCancelRecovery:p,onFocus:h,onToggleZoom:g,onClose:_,onPaneDragStart:ee,onPaneDragMove:te,onPaneDragEnd:ne,onPaneDragCancel:re,bodyRef:v}){return(0,$.jsxs)(`div`,{"data-pane-id":e,onMouseDown:h,style:i,className:`min-h-0 min-w-0 flex-col overflow-hidden rounded-sm border ${u?`border-accent ring-1 ring-accent`:o?`border-accent`:`border-ink-700`} ${l?`opacity-60`:``}`,children:[(0,$.jsxs)(`div`,{onPointerDown:ee,onPointerMove:te,onPointerUp:ne,onPointerCancel:re,className:`flex shrink-0 items-center gap-1 select-none bg-ink-900 px-2 py-0.5 text-xs ${d?l?`cursor-grabbing touch-none`:`cursor-grab touch-none`:``}`,children:[(0,$.jsx)(`span`,{title:r,className:`min-w-0 flex-1 truncate ${o?`text-ink-50`:`text-ink-400`}`,children:m(r,20)}),f&&(0,$.jsx)(Cc,{report:f,onCancel:p}),c&&(0,$.jsx)(`button`,{onMouseDown:e=>e.stopPropagation(),onClick:g,"aria-pressed":s,title:s?`Restore the grid`:`Zoom this terminal`,"aria-label":s?`Restore the grid`:`Zoom this terminal`,className:`flex h-8 w-8 shrink-0 items-center justify-center rounded-sm text-ink-400 hover:text-accent md:h-6 md:w-6`,children:(0,$.jsx)(n,{maximized:s})}),(0,$.jsx)(`button`,{onMouseDown:e=>e.stopPropagation(),onClick:_,title:`Close terminal`,"aria-label":`close terminal ${t+1}`,className:`flex h-8 w-8 shrink-0 items-center justify-center rounded-sm text-ink-400 hover:text-removed md:h-6 md:w-6`,children:(0,$.jsx)(a,{})})]}),(0,$.jsx)(`div`,{ref:v,className:`min-h-0 flex-1`})]})}function Tc({count:e,cells:t,slotRefs:n}){return Array.from({length:e},(e,r)=>{let i=t[r];return(0,$.jsx)(wc,{pane:-1-r,index:r,label:`starting…`,cellStyle:{display:`flex`,gridColumn:`${i.colStart} / span ${i.colSpan}`,gridRow:`${i.row}`},isActive:!1,isZoomed:!1,showZoom:!1,isDragged:!1,isDropTarget:!1,reorderable:!1,onCancelRecovery:()=>{},onFocus:()=>{},onToggleZoom:()=>{},onClose:()=>{},onPaneDragStart:()=>{},onPaneDragMove:()=>{},onPaneDragEnd:()=>{},onPaneDragCancel:()=>{},bodyRef:e=>{e?n.current.set(r,e):n.current.delete(r)}},`slot-${r}`)})}function Ec({onKey:e}){return(0,$.jsx)(`div`,{className:`flex shrink-0 items-stretch gap-1 overflow-x-auto border-t border-ink-700 bg-ink-900 px-1 py-1 md:hidden`,children:gc.map(({key:t,label:n,aria:r})=>(0,$.jsx)(`button`,{onPointerDown:e=>e.preventDefault(),onClick:()=>e(t),"aria-label":r,className:`flex min-h-9 min-w-9 shrink-0 items-center justify-center rounded-sm border border-ink-700 bg-ink-850 px-2 text-xs text-ink-200 active:bg-ink-700 active:text-accent`,children:n},t))})}function Dc({showDivider:e,draggingUpper:t,onUpperDragStart:n,onUpperDragMove:r,onUpperDragEnd:i,onUpperDragCancel:a}){return e?(0,$.jsx)(`div`,{role:`separator`,"aria-orientation":`horizontal`,"aria-label":`Resize the terminal panel (double-click to reset)`,title:`Drag to resize · double-click to reset`,onPointerDown:n,onPointerMove:r,onPointerUp:i,onPointerCancel:a,onLostPointerCapture:i,className:`absolute -top-px left-0 z-10 hidden h-1.5 w-full cursor-row-resize touch-none md:block ${t?`bg-accent`:`hover:bg-accent`}`}):null}function Oc({ownsSize:t,maximized:r,recovery:i,panes:a,onCancelRecovery:s,onClaimSize:c,onCreate:l,onToggleMaximized:u}){let d=`flex shrink-0 items-center rounded-sm px-1.5 py-0.5 text-ink-400 hover:text-accent`;return(0,$.jsxs)(`div`,{className:`flex shrink-0 items-center gap-2 bg-ink-900 px-2 py-1 text-xs`,children:[se(i,a).map(e=>(0,$.jsx)(Cc,{pane:e,report:i[e],onCancel:()=>s(e)},e)),!t&&(0,$.jsx)(`button`,{onClick:c,title:`These panes are sized for another client. Resize them to fit this screen.`,"aria-label":`Fit the panes to this screen`,className:`ml-auto ${d}`,children:(0,$.jsx)(o,{})}),(0,$.jsx)(`button`,{onClick:l,title:`New terminal`,"aria-label":`New terminal`,className:`${d} ${t?`ml-auto`:``}`,children:(0,$.jsx)(e,{})}),(0,$.jsx)(`button`,{onClick:u,"aria-pressed":r,title:r?`Restore panel height`:`Maximize the panel`,"aria-label":r?`Restore panel height`:`Maximize the panel`,className:`hidden md:flex ${d}`,children:(0,$.jsx)(n,{maximized:r})})]})}function kc({repo:e,maximized:t,onToggleMaximized:n,className:r=``,sectionRef:i,...a}){let o=(0,l.useRef)(null),s=(0,l.useRef)(null),c=(0,l.useRef)(new Map),u=(0,l.useRef)(new Map),d=(0,l.useRef)(new Map),p=(0,l.useRef)(new Map),m=(0,l.useRef)(new Map),g=(0,l.useRef)(void 0),_=(0,l.useRef)(new Map),[ee,te]=(0,l.useState)(null),[ne,re]=(0,l.useState)(0),[v,ie]=(0,l.useState)([]),[y,ae]=(0,l.useState)(null),[oe,b]=(0,l.useState)(null),[se,x]=(0,l.useState)({w:0,h:0}),[ce,le]=(0,l.useState)({}),[ue,de]=(0,l.useState)(!0),{recovery:fe,setRecovery:pe,cancelRecovery:C}=fc(s),w=_c(oe,v);S({repo:e,socketRef:s,viewsRef:c,pendingRef:p,sentSizesRef:d,lastActiveByRepoRef:m,zoomAskedRef:g,setPending:te,setReplayLeft:re,setPanes:ie,setActive:ae,setZoomed:b,setTitles:le,setOwnsSize:de,setRecovery:pe}),lc({panes:v,size:se,zoomed:w,socketRef:s,viewsRef:c,bodyRefs:u,pendingRef:p,setTitles:le});let T=(0,l.useCallback)(()=>te(null),[]);Sc({pending:ee,size:se,socketRef:s,slotRefs:_,panesExist:v.length>0,onAnswered:T}),dc({panes:v,size:se,zoomed:w,socketRef:s,viewsRef:c,bodyRefs:u,sentSizesRef:d,ownsSize:ue,layoutPending:vc(oe,v)}),(0,l.useEffect)(()=>{let e=o.current;if(!e)return;let t=new ResizeObserver(()=>{let t=e.clientWidth,n=e.clientHeight;x(e=>e.w===t&&e.h===n?e:{w:t,h:n})});return t.observe(e),()=>t.disconnect()},[]),xc({repo:e,panes:v,active:y,setActive:ae,zoomed:oe,zoom:w,viewsRef:c,lastActiveByRepoRef:m});let me=t=>{ae(t),m.current.set(e,t)},{create:he,toggleZoom:ge,claimSize:_e,closePane:ve,reorder:ye,sendKey:be}=bc({socketRef:s,viewsRef:c,zoomed:w,zoomAskedRef:g,active:y}),{draggingPane:xe,dragOverPane:Se,reorderable:Ce,endPaneDrag:we,onPaneDragStart:Te,onPaneDragMove:Ee,onPaneDragEnd:De}=h({panes:v,zoomed:w,onFocus:me,onReorder:ye}),Oe=f(v.length+ne>0?v.length+ne:ee??0,se.w>=se.h);return(0,$.jsxs)(`section`,{ref:i,className:`relative flex min-h-0 min-w-0 flex-col border-t border-ink-700 ${r}`,children:[(0,$.jsx)(Dc,{...a}),(0,$.jsx)(Oc,{ownsSize:ue,maximized:t,recovery:fe,panes:v,onCancelRecovery:C,onClaimSize:_e,onCreate:he,onToggleMaximized:n}),(0,$.jsxs)(`div`,{className:`relative min-h-0 flex-1 overflow-hidden bg-ink-950 p-1`,children:[v.length===0&&ee===null&&(0,$.jsxs)(`p`,{className:`p-3 text-ink-400`,children:[`No terminal open. Press `,(0,$.jsx)(`span`,{className:`text-accent`,children:`+`}),` above to start one.`]}),(0,$.jsxs)(`div`,{ref:o,className:`grid h-full gap-1`,style:w===null?{gridTemplateColumns:`repeat(${Oe.cols}, minmax(0, 1fr))`,gridTemplateRows:`repeat(${Oe.rows}, minmax(0, 1fr))`}:{gridTemplateColumns:`1fr`,gridTemplateRows:`1fr`},children:[v.length===0&&ee!==null&&(0,$.jsx)(Tc,{count:ee,cells:Oe.cells,slotRefs:_}),v.map((e,t)=>{let n=ce[e]??`term ${t+1}`,r=Oe.cells[t];return(0,$.jsx)(wc,{pane:e,index:t,label:n,cellStyle:w===null?{display:`flex`,gridColumn:`${r.colStart} / span ${r.colSpan}`,gridRow:`${r.row}`}:{display:e===w?`flex`:`none`},isActive:e===y,isZoomed:w===e,showZoom:v.length>1,isDragged:xe===e,isDropTarget:Se===e,reorderable:Ce,recovery:fe[e],onCancelRecovery:()=>C(e),onFocus:()=>me(e),onToggleZoom:()=>ge(e),onClose:()=>ve(e),onPaneDragStart:t=>Te(t,e),onPaneDragMove:Ee,onPaneDragEnd:De,onPaneDragCancel:we,bodyRef:t=>{t?u.current.set(e,t):u.current.delete(e)}},e)})]})]}),v.length>0&&(0,$.jsx)(Ec,{onKey:be})]})}export{kc as TerminalPanel}; \ No newline at end of file diff --git a/viewer-ui/dist/assets/index-Bla_aIeg.js b/viewer-ui/dist/assets/index-Bla_aIeg.js deleted file mode 100644 index 1ff4056c..00000000 --- a/viewer-ui/dist/assets/index-Bla_aIeg.js +++ /dev/null @@ -1,11 +0,0 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./Markdown-DgtyrrzA.js","./Markdown-C8LL_u4z.css"])))=>i.map(i=>d[i]); -var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),s=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:`Module`}),r},c=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;li[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},l=(n,r,a)=>(a=n==null?{}:e(i(n)),c(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var u=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var S=Array.isArray;function C(){}var w={H:null,A:null,T:null,S:null},ee=Object.prototype.hasOwnProperty;function T(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function E(e,t){return T(e.type,t,e.props)}function D(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function te(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var O=/\/+/g;function ne(e,t){return typeof e==`object`&&e&&e.key!=null?te(``+e.key):t.toString(36)}function k(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(C,C):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function re(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,re(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+ne(e,0):a,S(o)?(i=``,c!=null&&(i=c.replace(O,`$&/`)+`/`),re(o,r,i,``,function(e){return e})):o!=null&&(D(o)&&(o=E(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(O,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(S(e))for(var u=0;u{t.exports=u()})),f=o((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0>>1,a=e[r];if(0>>1;ri(c,n))li(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(li(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m)if(n(c)!==null)m=!0,S||(S=!0,D());else{var t=n(l);t!==null&&ne(x,t.startTime-e)}}var S=!1,C=-1,w=5,ee=-1;function T(){return g?!0:!(e.unstable_now()-eet&&T());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&ne(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}i=void 0}}finally{i?D():S=!1}}}var D;if(typeof y==`function`)D=function(){y(E)};else if(typeof MessageChannel<`u`){var te=new MessageChannel,O=te.port2;te.port1.onmessage=E,D=function(){O.postMessage(null)}}else D=function(){_(E,0)};function ne(t,n){C=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(C),C=-1):h=!0,ne(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,S||(S=!0,D()))),r},e.unstable_shouldYield=T,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),p=o(((e,t)=>{t.exports=f()})),m=o((e=>{var t=d();function n(e){var t=`https://react.dev/errors/`+e;if(1{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=m()})),g=o((e=>{var t=p(),n=d(),r=h();function i(e){var t=`https://react.dev/errors/`+e;if(1ce||(e.current=se[ce],se[ce]=null,ce--)}function P(e,t){ce++,se[ce]=e.current,e.current=t}var le=M(null),ue=M(null),de=M(null),fe=M(null);function pe(e,t){switch(P(de,t),P(ue,e),P(le,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Vd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Vd(t),e=Hd(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}N(le),P(le,e)}function me(){N(le),N(ue),N(de)}function he(e){e.memoizedState!==null&&P(fe,e);var t=le.current,n=Hd(t,e.type);t!==n&&(P(ue,e),P(le,n))}function ge(e){ue.current===e&&(N(le),N(ue)),fe.current===e&&(N(fe),Qf._currentValue=oe)}var _e,ve;function ye(e){if(_e===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);_e=t&&t[1]||``,ve=-1)`:-1i||c[r]!==l[i]){var u=` -`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(``)&&(u=u.replace(``,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{be=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?ye(n):``}function Se(e,t){switch(e.tag){case 26:case 27:case 5:return ye(e.type);case 16:return ye(`Lazy`);case 13:return e.child!==t&&t!==null?ye(`Suspense Fallback`):ye(`Suspense`);case 19:return ye(`SuspenseList`);case 0:case 15:return xe(e.type,!1);case 11:return xe(e.type.render,!1);case 1:return xe(e.type,!0);case 31:return ye(`Activity`);default:return``}}function Ce(e){try{var t=``,n=null;do t+=Se(e,n),n=e,e=e.return;while(e);return t}catch(e){return` -Error generating stack: `+e.message+` -`+e.stack}}var we=Object.prototype.hasOwnProperty,Te=t.unstable_scheduleCallback,Ee=t.unstable_cancelCallback,De=t.unstable_shouldYield,Oe=t.unstable_requestPaint,ke=t.unstable_now,Ae=t.unstable_getCurrentPriorityLevel,je=t.unstable_ImmediatePriority,Me=t.unstable_UserBlockingPriority,Ne=t.unstable_NormalPriority,Pe=t.unstable_LowPriority,Fe=t.unstable_IdlePriority,Ie=t.log,Le=t.unstable_setDisableYieldValue,Re=null,ze=null;function Be(e){if(typeof Ie==`function`&&Le(e),ze&&typeof ze.setStrictMode==`function`)try{ze.setStrictMode(Re,e)}catch{}}var Ve=Math.clz32?Math.clz32:We,He=Math.log,Ue=Math.LN2;function We(e){return e>>>=0,e===0?32:31-(He(e)/Ue|0)|0}var Ge=256,Ke=262144,qe=4194304;function Je(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function Ye(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=Je(n))):i=Je(o):i=Je(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=Je(n))):i=Je(o)):i=Je(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function Xe(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function Ze(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Qe(){var e=qe;return qe<<=1,!(qe&62914560)&&(qe=4194304),e}function $e(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function et(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function tt(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),pn=!1;if(fn)try{var mn={};Object.defineProperty(mn,"passive",{get:function(){pn=!0}}),window.addEventListener(`test`,mn,mn),window.removeEventListener(`test`,mn,mn)}catch{pn=!1}var hn=null,gn=null,_n=null;function vn(){if(_n)return _n;var e,t=gn,n=t.length,r,i=`value`in hn?hn.value:hn.textContent,a=i.length;for(e=0;e=Xn),$n=` `,er=!1;function tr(e,t){switch(e){case`keyup`:return Jn.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function nr(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var rr=!1;function ir(e,t){switch(e){case`compositionend`:return nr(t);case`keypress`:return t.which===32?(er=!0,$n):null;case`textInput`:return e=t.data,e===$n&&er?null:e;default:return null}}function ar(e,t){if(rr)return e===`compositionend`||!Yn&&tr(e,t)?(e=vn(),_n=gn=hn=null,rr=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=Dr(n)}}function kr(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?kr(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Ar(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=zt(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=zt(e.document)}return t}function jr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var Mr=fn&&`documentMode`in document&&11>=document.documentMode,Nr=null,Pr=null,Fr=null,Ir=!1;function Lr(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Ir||Nr==null||Nr!==zt(r)||(r=Nr,`selectionStart`in r&&jr(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Fr&&Er(Fr,r)||(Fr=r,r=Ed(Pr,`onSelect`),0>=o,i-=o,ki=1<<32-Ve(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),a=o(_,a,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),L&&ji(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(a,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(a,h),s=o(y,s,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(a,h),L&&ji(a,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(a,v.value,l),v!==null&&(s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return L&&ji(a,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,a,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(a,e)}),L&&ji(a,g),u}function b(e,r,o,c){if(typeof o==`object`&&o&&o.type===y&&o.key===null&&(o=o.props.children),typeof o==`object`&&o){switch(o.$$typeof){case _:a:{for(var l=o.key;r!==null;){if(r.key===l){if(l=o.type,l===y){if(r.tag===7){n(e,r.sibling),c=a(r,o.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===D&&Aa(l)===r.type){n(e,r.sibling),c=a(r,o.props),La(c,o),c.return=e,e=c;break a}n(e,r);break}else t(e,r);r=r.sibling}o.type===y?(c=gi(o.props.children,e.mode,c,o.key),c.return=e,e=c):(c=hi(o.type,o.key,o.props,null,e.mode,c),La(c,o),c.return=e,e=c)}return s(e);case v:a:{for(l=o.key;r!==null;){if(r.key===l)if(r.tag===4&&r.stateNode.containerInfo===o.containerInfo&&r.stateNode.implementation===o.implementation){n(e,r.sibling),c=a(r,o.children||[]),c.return=e,e=c;break a}else{n(e,r);break}else t(e,r);r=r.sibling}c=yi(o,e.mode,c),c.return=e,e=c}return s(e);case D:return o=Aa(o),b(e,r,o,c)}if(ae(o))return h(e,r,o,c);if(k(o)){if(l=k(o),typeof l!=`function`)throw Error(i(150));return o=l.call(o),g(e,r,o,c)}if(typeof o.then==`function`)return b(e,r,Ia(o),c);if(o.$$typeof===C)return b(e,r,ia(e,o),c);Ra(e,o)}return typeof o==`string`&&o!==``||typeof o==`number`||typeof o==`bigint`?(o=``+o,r!==null&&r.tag===6?(n(e,r.sibling),c=a(r,o),c.return=e,e=c):(n(e,r),c=_i(o,e.mode,c),c.return=e,e=c),s(e)):n(e,r)}return function(e,t,n,r){try{Fa=0;var i=b(e,t,n,r);return Pa=null,i}catch(t){if(t===wa||t===Ea)throw t;var a=di(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var Ba=za(!0),Va=za(!1),Ha=!1;function Ua(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Wa(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function Ga(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Ka(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,G&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=ci(e),si(e,null,n),t}return ii(e,r,t,n),ci(e)}function qa(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,rt(e,n)}}function Ja(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var Ya=!1;function Xa(){if(Ya){var e=ha;if(e!==null)throw e}}function Za(e,t,n,r){Ya=!1;var i=e.updateQueue;Ha=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,p=f!==s.lane;if(p?(J&f)===f:(r&f)===f){f!==0&&f===ma&&(Ya=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var h=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(h=g.payload,typeof h==`function`){d=h.call(_,d,f);break a}d=h;break a;case 3:h.flags=h.flags&-65537|128;case 0:if(h=g.payload,f=typeof h==`function`?h.call(_,d,f):h,f==null)break a;d=m({},d,f);break a;case 2:Ha=!0}}f=s.callback,f!==null&&(e.flags|=64,p&&(e.flags|=8192),p=i.callbacks,p===null?i.callbacks=[f]:p.push(f))}else p={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=p,c=d):u=u.next=p,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;p=s,s=p.next,p.next=null,i.lastBaseUpdate=p,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),Gl|=o,e.lanes=o,e.memoizedState=d}}function Qa(e,t){if(typeof e!=`function`)throw Error(i(191,e));e.call(t)}function $a(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=A.T,s={};A.T=s,Fs(e,!1,t,n);try{var c=i(),l=A.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?Ps(e,t,va(c,r),pu(e)):Ps(e,t,r,pu(e))}catch(n){Ps(e,t,{then:function(){},status:`rejected`,reason:n},pu())}finally{j.p=a,o!==null&&s.types!==null&&(o.types=s.types),A.T=o}}function ws(){}function Ts(e,t,n,r){if(e.tag!==5)throw Error(i(476));var a=Es(e).queue;Cs(e,a,t,oe,n===null?ws:function(){return Ds(e),n(r)})}function Es(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:oe,baseState:oe,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Io,lastRenderedState:oe},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Io,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function Ds(e){var t=Es(e);t.next===null&&(t=e.alternate.memoizedState),Ps(e,t.next.queue,{},pu())}function Os(){return ra(Qf)}function ks(){return H().memoizedState}function As(){return H().memoizedState}function js(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=pu();e=Ga(n);var r=Ka(t,e,n);r!==null&&(hu(r,t,n),qa(r,t,n)),t={cache:ua()},e.payload=t;return}t=t.return}}function Ms(e,t,n){var r=pu();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},Is(e)?Ls(t,n):(n=ai(e,t,n,r),n!==null&&(hu(n,e,r),Rs(n,t,r)))}function Ns(e,t,n){Ps(e,t,n,pu())}function Ps(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(Is(e))Ls(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,Tr(s,o))return ii(e,t,i,0),K===null&&ri(),!1}catch{}if(n=ai(e,t,i,r),n!==null)return hu(n,e,r),Rs(n,t,r),!0}return!1}function Fs(e,t,n,r){if(r={lane:2,revertLane:dd(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},Is(e)){if(t)throw Error(i(479))}else t=ai(e,n,r,2),t!==null&&hu(t,e,2)}function Is(e){var t=e.alternate;return e===z||t!==null&&t===z}function Ls(e,t){_o=go=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Rs(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,rt(e,n)}}var zs={readContext:ra,use:Po,useCallback:V,useContext:V,useEffect:V,useImperativeHandle:V,useLayoutEffect:V,useInsertionEffect:V,useMemo:V,useReducer:V,useRef:V,useState:V,useDebugValue:V,useDeferredValue:V,useTransition:V,useSyncExternalStore:V,useId:V,useHostTransitionStatus:V,useFormState:V,useActionState:V,useOptimistic:V,useMemoCache:V,useCacheRefresh:V};zs.useEffectEvent=V;var Bs={readContext:ra,use:Po,useCallback:function(e,t){return jo().memoizedState=[e,t===void 0?null:t],e},useContext:ra,useEffect:us,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),cs(4194308,4,gs.bind(null,t,e),n)},useLayoutEffect:function(e,t){return cs(4194308,4,e,t)},useInsertionEffect:function(e,t){cs(4,2,e,t)},useMemo:function(e,t){var n=jo();t=t===void 0?null:t;var r=e();if(vo){Be(!0);try{e()}finally{Be(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=jo();if(n!==void 0){var i=n(t);if(vo){Be(!0);try{n(t)}finally{Be(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=Ms.bind(null,z,e),[r.memoizedState,e]},useRef:function(e){var t=jo();return e={current:e},t.memoizedState=e},useState:function(e){e=Ko(e);var t=e.queue,n=Ns.bind(null,z,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:vs,useDeferredValue:function(e,t){return xs(jo(),e,t)},useTransition:function(){var e=Ko(!1);return e=Cs.bind(null,z,e.queue,!0,!1),jo().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=z,a=jo();if(L){if(n===void 0)throw Error(i(407));n=n()}else{if(n=t(),K===null)throw Error(i(349));J&127||Vo(r,t,n)}a.memoizedState=n;var o={value:n,getSnapshot:t};return a.queue=o,us(Uo.bind(null,r,o,e),[e]),r.flags|=2048,os(9,{destroy:void 0},Ho.bind(null,r,o,n,t),null),n},useId:function(){var e=jo(),t=K.identifierPrefix;if(L){var n=Ai,r=ki;n=(r&~(1<<32-Ve(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=yo++,0<\/script>`,o=o.removeChild(o.firstChild);break;case`select`:o=typeof r.is==`string`?s.createElement(`select`,{is:r.is}):s.createElement(`select`),r.multiple?o.multiple=!0:r.size&&(o.size=r.size);break;default:o=typeof r.is==`string`?s.createElement(a,{is:r.is}):s.createElement(a)}}o[lt]=t,o[ut]=r;a:for(s=t.child;s!==null;){if(s.tag===5||s.tag===6)o.appendChild(s.stateNode);else if(s.tag!==4&&s.tag!==27&&s.child!==null){s.child.return=s,s=s.child;continue}if(s===t)break a;for(;s.sibling===null;){if(s.return===null||s.return===t)break a;s=s.return}s.sibling.return=s.return,s=s.sibling}t.stateNode=o;a:switch(Pd(o,a,r),a){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&Pc(t)}}return U(t),Fc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&Pc(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(i(166));if(e=de.current,Ui(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,a=Ii,a!==null)switch(a.tag){case 27:case 5:r=a.memoizedProps}e[lt]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||Md(e.nodeValue,n)),e||Bi(t,!0)}else e=Bd(e).createTextNode(r),e[lt]=t,t.stateNode=e}return U(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=Ui(t),n!==null){if(e===null){if(!r)throw Error(i(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(i(557));e[lt]=t}else Wi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;U(t),e=!1}else n=Gi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(fo(t),t):(fo(t),null);if(t.flags&128)throw Error(i(558))}return U(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(a=Ui(t),r!==null&&r.dehydrated!==null){if(e===null){if(!a)throw Error(i(318));if(a=t.memoizedState,a=a===null?null:a.dehydrated,!a)throw Error(i(317));a[lt]=t}else Wi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;U(t),a=!1}else a=Gi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=a),a=!0;if(!a)return t.flags&256?(fo(t),t):(fo(t),null)}return fo(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,a=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(a=r.alternate.memoizedState.cachePool.pool),o=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(o=r.memoizedState.cachePool.pool),o!==a&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),Lc(t,t.updateQueue),U(t),null);case 4:return me(),e===null&&Sd(t.stateNode.containerInfo),U(t),null;case 10:return Zi(t.type),U(t),null;case 19:if(N(R),r=t.memoizedState,r===null)return U(t),null;if(a=(t.flags&128)!=0,o=r.rendering,o===null)if(a)Rc(r,!1);else{if(X!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(o=po(e),o!==null){for(t.flags|=128,Rc(r,!1),e=o.updateQueue,t.updateQueue=e,Lc(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)mi(n,e),n=n.sibling;return P(R,R.current&1|2),L&&ji(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&ke()>tu&&(t.flags|=128,a=!0,Rc(r,!1),t.lanes=4194304)}else{if(!a)if(e=po(o),e!==null){if(t.flags|=128,a=!0,e=e.updateQueue,t.updateQueue=e,Lc(t,e),Rc(r,!0),r.tail===null&&r.tailMode===`hidden`&&!o.alternate&&!L)return U(t),null}else 2*ke()-r.renderingStartTime>tu&&n!==536870912&&(t.flags|=128,a=!0,Rc(r,!1),t.lanes=4194304);r.isBackwards?(o.sibling=t.child,t.child=o):(e=r.last,e===null?t.child=o:e.sibling=o,r.last=o)}return r.tail===null?(U(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=ke(),e.sibling=null,n=R.current,P(R,a?n&1|2:n&1),L&&ji(t,r.treeForkCount),e);case 22:case 23:return fo(t),io(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(U(t),t.subtreeFlags&6&&(t.flags|=8192)):U(t),n=t.updateQueue,n!==null&&Lc(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&N(ba),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),Zi(la),U(t),null;case 25:return null;case 30:return null}throw Error(i(156,t.tag))}function Bc(e,t){switch(Pi(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Zi(la),me(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return ge(t),null;case 31:if(t.memoizedState!==null){if(fo(t),t.alternate===null)throw Error(i(340));Wi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(fo(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(i(340));Wi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return N(R),null;case 4:return me(),null;case 10:return Zi(t.type),null;case 22:case 23:return fo(t),io(),e!==null&&N(ba),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return Zi(la),null;case 25:return null;default:return null}}function Vc(e,t){switch(Pi(t),t.tag){case 3:Zi(la),me();break;case 26:case 27:case 5:ge(t);break;case 4:me();break;case 31:t.memoizedState!==null&&fo(t);break;case 13:fo(t);break;case 19:N(R);break;case 10:Zi(t.type);break;case 22:case 23:fo(t),io(),e!==null&&N(ba);break;case 24:Zi(la)}}function Hc(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){Z(t,t.return,e)}}function Uc(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){Z(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){Z(t,t.return,e)}}function Wc(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{$a(t,n)}catch(t){Z(e,e.return,t)}}}function Gc(e,t,n){n.props=qs(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){Z(e,t,n)}}function Kc(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){Z(e,t,n)}}function qc(e,t){var n=e.ref,r=e.refCleanup;if(n!==null)if(typeof r==`function`)try{r()}catch(n){Z(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){Z(e,t,n)}else n.current=null}function Jc(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){Z(e,e.return,t)}}function Yc(e,t,n){try{var r=e.stateNode;Fd(r,e.type,n,t),r[ut]=t}catch(t){Z(e,e.return,t)}}function Xc(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&Zd(e.type)||e.tag===4}function Zc(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||Xc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&Zd(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Qc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=nn));else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for(Qc(e,t,n),e=e.sibling;e!==null;)Qc(e,t,n),e=e.sibling}function $c(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode),e=e.child,e!==null))for($c(e,t,n),e=e.sibling;e!==null;)$c(e,t,n),e=e.sibling}function el(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);Pd(t,r,n),t[lt]=e,t[ut]=n}catch(t){Z(e,e.return,t)}}var tl=!1,nl=!1,rl=!1,il=typeof WeakSet==`function`?WeakSet:Set,al=null;function ol(e,t){if(e=e.containerInfo,Rd=sp,e=Ar(e),jr(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var a=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break a}var s=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||a!==0&&f.nodeType!==3||(c=s+a),f!==o||r!==0&&f.nodeType!==3||(l=s+r),f.nodeType===3&&(s+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===a&&(c=s),p===o&&++d===r&&(l=s),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n||={start:0,end:0}}else n=null;for(zd={focusedElem:e,selectionRange:n},sp=!1,al=t;al!==null;)if(t=al,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,al=e;else for(;al!==null;){switch(t=al,o=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),Pd(o,r,n),o[lt]=e,St(o),r=o;break a;case`link`:var s=Vf(`link`,`href`,a).get(r+(n.href||``));if(s){for(var c=0;cg&&(o=g,g=h,h=o);var _=Or(s,h),v=Or(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,A.T=null,n=lu,lu=null;var o=au,s=su;if(iu=0,ou=au=null,su=0,G&6)throw Error(i(331));var c=G;if(G|=4,Fl(o.current),Dl(o,o.current,s,n),G=c,id(0,!1),ze&&typeof ze.onPostCommitFiberRoot==`function`)try{ze.onPostCommitFiberRoot(Re,o)}catch{}return!0}finally{j.p=a,A.T=r,Vu(e,t)}}function Wu(e,t,n){t=xi(n,t),t=$s(e.stateNode,t,2),e=Ka(e,t,2),e!==null&&(et(e,2),rd(e))}function Z(e,t,n){if(e.tag===3)Wu(e,e,n);else for(;t!==null;){if(t.tag===3){Wu(t,e,n);break}else if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(ru===null||!ru.has(r))){e=xi(n,e),n=ec(2),r=Ka(t,n,2),r!==null&&(tc(n,r,t,e),et(r,2),rd(r));break}}t=t.return}}function Gu(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new zl;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(Ul=!0,i.add(n),e=Ku.bind(null,e,t,n),t.then(e,e))}function Ku(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,K===e&&(J&n)===n&&(X===4||X===3&&(J&62914560)===J&&300>ke()-$l?!(G&2)&&Su(e,0):ql|=n,Yl===J&&(Yl=0)),rd(e)}function qu(e,t){t===0&&(t=Qe()),e=oi(e,t),e!==null&&(et(e,t),rd(e))}function Ju(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),qu(e,n)}function Yu(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,a=e.memoizedState;a!==null&&(n=a.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(i(314))}r!==null&&r.delete(t),qu(e,n)}function Xu(e,t){return Te(e,t)}var Zu=null,Qu=null,$u=!1,ed=!1,td=!1,nd=0;function rd(e){e!==Qu&&e.next===null&&(Qu===null?Zu=Qu=e:Qu=Qu.next=e),ed=!0,$u||($u=!0,ud())}function id(e,t){if(!td&&ed){td=!0;do for(var n=!1,r=Zu;r!==null;){if(!t)if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-Ve(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,ld(r,a))}else a=J,a=Ye(r,r===K?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||Xe(r,a)||(n=!0,ld(r,a));r=r.next}while(n);td=!1}}function ad(){od()}function od(){ed=$u=!1;var e=0;nd!==0&&Gd()&&(e=nd);for(var t=ke(),n=null,r=Zu;r!==null;){var i=r.next,a=sd(r,t);a===0?(r.next=null,n===null?Zu=i:n.next=i,i===null&&(Qu=n)):(n=r,(e!==0||a&3)&&(ed=!0)),r=i}iu!==0&&iu!==5||id(e,!1),nd!==0&&(nd=0)}function sd(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&Id(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function xf(e,t,n){var r=bf;if(r&&typeof t==`string`&&t){var i=Vt(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),hf.has(i)||(hf.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),Pd(t,`link`,e),St(t),r.head.appendChild(t)))}}function Sf(e){_f.D(e),xf(`dns-prefetch`,e,null)}function Cf(e,t){_f.C(e,t),xf(`preconnect`,e,t)}function wf(e,t,n){_f.L(e,t,n);var r=bf;if(r&&e&&t){var i=`link[rel="preload"][as="`+Vt(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+Vt(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+Vt(n.imageSizes)+`"]`)):i+=`[href="`+Vt(e)+`"]`;var a=i;switch(t){case`style`:a=Af(e);break;case`script`:a=Pf(e)}mf.has(a)||(e=m({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),mf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(jf(a))||t===`script`&&r.querySelector(Ff(a))||(t=r.createElement(`link`),Pd(t,`link`,e),St(t),r.head.appendChild(t)))}}function Tf(e,t){_f.m(e,t);var n=bf;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+Vt(r)+`"][href="`+Vt(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=Pf(e)}if(!mf.has(a)&&(e=m({rel:`modulepreload`,href:e},t),mf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(Ff(a)))return}r=n.createElement(`link`),Pd(r,`link`,e),St(r),n.head.appendChild(r)}}}function Ef(e,t,n){_f.S(e,t,n);var r=bf;if(r&&e){var i=xt(r).hoistableStyles,a=Af(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(jf(a)))s.loading=5;else{e=m({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=mf.get(a))&&Rf(e,n);var c=o=r.createElement(`link`);St(c),Pd(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,Lf(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function Df(e,t){_f.X(e,t);var n=bf;if(n&&e){var r=xt(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=m({src:e,async:!0},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),St(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Of(e,t){_f.M(e,t);var n=bf;if(n&&e){var r=xt(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=m({src:e,async:!0,type:`module`},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),St(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function kf(e,t,n,r){var a=(a=de.current)?gf(a):null;if(!a)throw Error(i(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=Af(n.href),n=xt(a).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=Af(n.href);var o=xt(a).hoistableStyles,s=o.get(e);if(s||(a=a.ownerDocument||a,s={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},o.set(e,s),(o=a.querySelector(jf(e)))&&!o._p&&(s.instance=o,s.state.loading=5),mf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},mf.set(e,n),o||Nf(a,e,n,s.state))),t&&r===null)throw Error(i(528,``));return s}if(t&&r!==null)throw Error(i(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=Pf(n),n=xt(a).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(i(444,e))}}function Af(e){return`href="`+Vt(e)+`"`}function jf(e){return`link[rel="stylesheet"][`+e+`]`}function Mf(e){return m({},e,{"data-precedence":e.precedence,precedence:null})}function Nf(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),Pd(t,`link`,n),St(t),e.head.appendChild(t))}function Pf(e){return`[src="`+Vt(e)+`"]`}function Ff(e){return`script[async]`+e}function If(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+Vt(n.href)+`"]`);if(r)return t.instance=r,St(r),r;var a=m({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),St(r),Pd(r,`style`,a),Lf(r,n.precedence,e),t.instance=r;case`stylesheet`:a=Af(n.href);var o=e.querySelector(jf(a));if(o)return t.state.loading|=4,t.instance=o,St(o),o;r=Mf(n),(a=mf.get(a))&&Rf(r,a),o=(e.ownerDocument||e).createElement(`link`),St(o);var s=o;return s._p=new Promise(function(e,t){s.onload=e,s.onerror=t}),Pd(o,`link`,r),t.state.loading|=4,Lf(o,n.precedence,e),t.instance=o;case`script`:return o=Pf(n.src),(a=e.querySelector(Ff(o)))?(t.instance=a,St(a),a):(r=n,(a=mf.get(o))&&(r=m({},n),zf(r,a)),e=e.ownerDocument||e,a=e.createElement(`script`),St(a),Pd(a,`link`,r),e.head.appendChild(a),t.instance=a);case`void`:return null;default:throw Error(i(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,Lf(r,n.precedence,e));return t.instance}function Lf(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function Uf(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function Wf(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function Gf(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=Af(r.href),a=t.querySelector(jf(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=Jf.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,St(a);return}a=t.ownerDocument||t,r=Mf(r),(i=mf.get(i))&&Rf(r,i),a=a.createElement(`link`),St(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Pd(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=Jf.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var Kf=0;function qf(e,t){return e.stylesheets&&e.count===0&&Xf(e,e.stylesheets),0Kf?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function Jf(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Xf(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Yf=null;function Xf(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Yf=new Map,t.forEach(Zf,e),Yf=null,Jf.call(e))}function Zf(e,t){if(!(t.state.loading&4)){var n=Yf.get(e);if(n)var r=n.get(null);else{n=new Map,Yf.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=g()})),v=d(),y=_(),b=class extends Error{status;constructor(e,t){super(t),this.status=e}},x=e=>e instanceof b&&e.status===401,S=class extends Error{constructor(e){super(`connection lost — check your network`,{cause:e}),this.name=`NetworkError`}},C=e=>e instanceof S;async function w(e,t){try{return await fetch(e,t)}catch(e){throw new S(e)}}async function ee(e){if(!e.ok){let t=`request failed (${e.status})`;try{let n=await e.json();typeof n?.error==`string`&&(t=n.error)}catch{}throw new b(e.status,t)}let t=await e.json();if(t.version!==2)throw new b(e.status,`this page is out of date (server protocol v${t.version}) — reload`);return t}async function T(e,t){return ee(await w(e,{credentials:`same-origin`,signal:t}))}async function E(e,t,n){return ee(await w(e,{method:`POST`,credentials:`same-origin`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t),signal:n}))}var D=e=>new URLSearchParams(e).toString(),te=1e4,O={async login(e){let t=await w(`/login`,{method:`POST`,credentials:`same-origin`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:new URLSearchParams({password:e}).toString()});if(!t.ok)throw new b(t.status,t.status===429?`too many attempts — wait a minute`:`incorrect password`)},repos:e=>T(`/api/repos`,e),setAccent:e=>E(`/api/prefs`,{accent:e}).then(e=>e.accent),setSidebarWidth:e=>E(`/api/prefs`,{sidebar_width:e}).then(e=>e.sidebar_width),setUpperPct:e=>E(`/api/prefs`,{upper_pct:e}).then(e=>e.upper_pct),setActiveRepo:e=>E(`/api/prefs`,{active_repo:e},AbortSignal.timeout(te)).then(e=>e.active_repo),setMaximized:(e,t)=>E(`/api/prefs`,{maximized:{repo:e,panel:t}},AbortSignal.timeout(te)).then(e=>e.maximized),status:e=>T(`/api/status?${D({repo:e})}`),tree:(e,t)=>T(`/api/tree?${D({repo:e,path:t})}`),treeSearch:(e,t)=>T(`/api/tree/search?${D({repo:e,q:t})}`),log:(e,t)=>T(`/api/log?${D(t?{repo:e,from:t.from,skip:String(t.skip)}:{repo:e})}`),diff:(e,t)=>T(`/api/diff?${D({repo:e,path:t})}`),file:(e,t)=>T(`/api/file?${D({repo:e,path:t})}`),commit:(e,t)=>T(`/api/commit?${D({repo:e,oid:t})}`),commitFiles:(e,t)=>T(`/api/commit/files?${D({repo:e,oid:t})}`),commitFileDiff:(e,t,n)=>T(`/api/commit/file-diff?${D({repo:e,oid:t,path:n})}`),browse:e=>T(`/api/browse${e?`?${D({path:e})}`:``}`),mkdir:(e,t)=>E(`/api/mkdir`,{path:e,name:t}).then(e=>e.path),clone:(e,t)=>E(`/api/clone`,{path:e,url:t}),cloneStatus:e=>T(`/api/clone?${D({job:String(e)})}`),runningClone:()=>T(`/api/clone`),open:e=>E(`/api/repos`,{path:e}).then(e=>e.repo),close:async e=>{let t=await w(`/api/repos?${D({repo:e})}`,{method:`DELETE`,credentials:`same-origin`});if(!t.ok)throw new b(t.status,`could not close (${t.status})`)},reorderRepos:e=>E(`/api/repos/order`,{order:e}).then(e=>e.repos),reloadConfig:()=>E(`/api/reload`,{}).then(e=>e.summary)};function ne(e,t){let n=new EventSource(`/api/events?${D({repo:e})}`);return n.addEventListener(`status`,e=>{try{let n=JSON.parse(e.data);n.version===2&&t(n)}catch{}}),()=>n.close()}var k=[],re=1,ie=new Set;function ae(){let e=k;ie.forEach(t=>t(e))}function A(e){return ie.add(e),e(k),()=>{ie.delete(e)}}function j(e){let t=k.filter(t=>t.id!==e);t.length!==k.length&&(k=t,ae())}function oe(e,t){let n=k.findIndex(n=>n.kind===e&&n.message===t);if(n!==-1){let e=k[n];return k=k.map((e,t)=>t===n?{...e,bump:e.bump+1}:e),ae(),e.id}let r=re++;return k=[...k,{id:r,kind:e,message:t,bump:0}].slice(-4),ae(),r}var se={error:e=>oe(`error`,e),info:e=>oe(`info`,e),success:e=>oe(`success`,e)},ce=1e3;function M(e,t){return e===void 0||e<=0?0:e-t}function N(e,t,n){let r=M(t,n);return e===null||Math.abs(r-e)>=1e3?r:e}function P(e,t,n){if(e===void 0)return`cool`;let r=Math.max(0,t-e);return r>=n?`cool`:r<5e3?`fresh`:`warm`}function le(e,t,n){return e.some(e=>P(e,t,n)!==`cool`)}var ue={fresh:`text-accent font-bold`,warm:`text-accent`,cool:``};function de(e,t,n){let[r,i]=(0,v.useState)(()=>Date.now()+n);return(0,v.useEffect)(()=>{if(t<=0||!e)return;let r=e.map(e=>e.mtime),a=Date.now()+n;if(i(a),!le(r,a,t))return;let o=setInterval(()=>{let e=Date.now()+n;i(e),le(r,e,t)||clearInterval(o)},ce);return()=>clearInterval(o)},[e,t,n]),r}var fe=[{name:`yellow`,color:`#d9a441`},{name:`cyan`,color:`#03c4db`},{name:`green`,color:`#77c47a`},{name:`magenta`,color:`#dc8fd5`},{name:`blue`,color:`#87acfd`}],pe=`nightcrow.viewer.accent`;function me(e){if(!Number.isFinite(e))return 0;let t=fe.length;return(Math.trunc(e)%t+t)%t}function he(){try{let e=localStorage.getItem(pe);return e===null?0:me(Number(e))}catch{return 0}}function ge(e){try{localStorage.setItem(pe,String(e))}catch{}}function _e(){let[e,t]=(0,v.useState)(he);(0,v.useLayoutEffect)(()=>{document.documentElement.style.setProperty(`--color-accent`,fe[e].color)},[e]);let n=(0,v.useCallback)(()=>{t(e=>{let t=me(e+1);return ge(t),O.setAccent(t).catch(()=>{}),t})},[]),r=(0,v.useCallback)(e=>{t(t=>{let n=me(e);return n===t?t:(ge(n),n)})},[]);return{accent:fe[e],next:fe[me(e+1)],cycle:n,adopt:r}}var ve=`nightcrow.sidebarWidth`,ye=.5;function be(e){return Math.min(Math.max(Math.round(e),280),720)}function xe(e){let t=720;try{t=Math.min(t,Math.round(window.innerWidth*ye))}catch{}return Math.min(Math.max(Math.round(e),280),Math.max(t,280))}function Se(){try{let e=Number(localStorage.getItem(ve));return Number.isFinite(e)&&e>0?be(e):460}catch{return 460}}function Ce(e){try{localStorage.setItem(ve,String(e))}catch{}}function we(){let[e,t]=(0,v.useState)(Se);return{width:e,resize:(0,v.useCallback)(e=>{let n=xe(e);t(n),Ce(n)},[]),commit:(0,v.useCallback)(e=>{let n=xe(e);t(n),Ce(n),O.setSidebarWidth(n).catch(()=>{})},[]),reset:(0,v.useCallback)(()=>{let e=be(460);t(e),Ce(e),O.setSidebarWidth(e).catch(()=>{})},[]),adopt:(0,v.useCallback)(e=>{t(t=>{let n=be(e);return n===t?t:(Ce(n),n)})},[])}}function Te(e){return Number.isFinite(e)?Math.min(Math.max(e,20),85):55}function Ee(e){return Math.round(Te(e))}function De(e,t,n,r){let i=n-t;return Te(i<=0?r:(e-t)/i*100)}var Oe=`nightcrow.upperPct`;function ke(){try{let e=Number(localStorage.getItem(Oe));return Number.isFinite(e)&&e>0?Ee(e):55}catch{return 55}}function Ae(e){try{localStorage.setItem(Oe,String(e))}catch{}}function je(){let[e,t]=(0,v.useState)(ke);return{pct:e,resize:(0,v.useCallback)(e=>{t(Te(e))},[]),commit:(0,v.useCallback)(e=>{let n=Ee(e);t(n),Ae(n),O.setUpperPct(n).catch(()=>{})},[]),reset:(0,v.useCallback)(()=>{t(55),Ae(55),O.setUpperPct(55).catch(()=>{})},[]),adopt:(0,v.useCallback)(e=>{t(t=>{let n=Ee(e);return n===t?t:(Ae(n),n)})},[])}}function Me(e){let t=!1,n=null,r=()=>{if(t||n===null)return;let i=n;n=null,t=!0,e(i).catch(()=>{}).finally(()=>{t=!1,r()})};return e=>{n=e,r()}}function Ne(){let[e,t]=(0,v.useState)({}),n=(0,v.useRef)(e),r=(0,v.useCallback)(e=>{n.current=e,t(e)},[]),i=(0,v.useRef)(0),a=(0,v.useRef)(new Map),o=(0,v.useCallback)(e=>{let t=a.current.get(e);if(t)return t;let n=Me(t=>O.setMaximized(e,t===`none`?null:t));return a.current.set(e,n),n},[]),s=(0,v.useCallback)((e,t)=>{if(e==null)return;let a=n.current,s=typeof t==`function`?t(a[e]??`none`):t;i.current+=1,o(e)(s);let{[e]:c,...l}=a;r(s===`none`?l:{...a,[e]:s})},[o,r]);return{panelOf:(0,v.useCallback)(t=>t!=null&&e[t]||`none`,[e]),setFor:s,adopt:(0,v.useCallback)(e=>{Pe(n.current,e)||r(e)},[r]),writes:i}}function Pe(e,t){let n=Object.keys(e);return n.length===Object.keys(t).length&&n.every(n=>e[n]===t[n])}function Fe(){let{accent:e,next:t,cycle:n,adopt:r}=_e(),{width:i,resize:a,commit:o,reset:s,adopt:c}=we(),{pct:l,resize:u,commit:d,reset:f,adopt:p}=je(),m=Ne(),h=(0,v.useRef)(0),g=(0,v.useRef)(0),_=(0,v.useRef)(0);return{accent:e,next:t,cycle:(0,v.useCallback)(()=>{h.current+=1,n()},[n]),adoptAccent:r,accentWrites:h,sidebarWidth:i,resizeSidebar:a,commitSidebarWidth:(0,v.useCallback)(e=>{g.current+=1,o(e)},[o]),resetSidebarWidth:(0,v.useCallback)(()=>{g.current+=1,s()},[s]),bumpSidebarWrites:(0,v.useCallback)(()=>{g.current+=1},[]),adoptSidebarWidth:c,sidebarWrites:g,upperPct:l,resizeUpperPct:u,commitUpperPct:(0,v.useCallback)(e=>{_.current+=1,d(e)},[d]),resetUpperPct:(0,v.useCallback)(()=>{_.current+=1,f()},[f]),bumpUpperPctWrites:(0,v.useCallback)(()=>{_.current+=1},[]),adoptUpperPct:p,upperPctWrites:_,maximizedPanelOf:m.panelOf,setMaximizedFor:m.setFor,adoptMaximized:m.adopt,maximizedWrites:m.writes}}var Ie=400;function Le({value:e,valueAt:t,onGestureStart:n,resize:r,commit:i,reset:a,axis:o}){let s=(0,v.useRef)(0),c=(0,v.useRef)(0),l=(0,v.useRef)(!1),u=(0,v.useRef)(!1),d=(0,v.useRef)(0),[f,p]=(0,v.useState)(!1);return{dragging:f,onDragStart:(0,v.useCallback)(t=>{t.button!==0||!t.isPrimary||n()&&(s.current=o===`x`?t.clientX:t.clientY,c.current=e,l.current=!0,u.current=!1,p(!0),t.currentTarget.setPointerCapture(t.pointerId),t.preventDefault())},[e,n,o]),onDragMove:(0,v.useCallback)(e=>{if(!l.current)return;let n=o===`x`?e.clientX:e.clientY;if(!u.current&&Math.abs(n-s.current)<3)return;let i=t(e);i!==null&&(u.current=!0,c.current=i,r(i))},[t,r,o]),onDragEnd:(0,v.useCallback)(()=>{if(!l.current)return;if(l.current=!1,p(!1),u.current){i(c.current),d.current=0;return}let e=Date.now();e-d.current{l.current=!1,u.current=!1,d.current=0,p(!1)},[]),draggingRef:l}}function Re({sidebarRef:e,sidebarWidth:t,resizeSidebar:n,commitSidebarWidth:r,resetSidebarWidth:i,bumpSidebarWrites:a}){let o=(0,v.useRef)(0),s=(0,v.useCallback)(()=>{let t=e.current?.getBoundingClientRect().left;return t===void 0?!1:(o.current=t,a(),!0)},[e,a]),{dragging:c,onDragStart:l,onDragMove:u,onDragEnd:d,onDragCancel:f,draggingRef:p}=Le({value:t,valueAt:(0,v.useCallback)(e=>e.clientX-o.current,[]),onGestureStart:s,resize:n,commit:r,reset:i,axis:`x`});return{draggingSidebar:c,onSidebarDragStart:l,onSidebarDragMove:u,onSidebarDragEnd:d,onSidebarDragCancel:f,draggingRef:p}}function ze({upperRef:e,lowerRef:t,upperPct:n,resizeUpperPct:r,commitUpperPct:i,resetUpperPct:a,bumpUpperPctWrites:o}){let s=(0,v.useRef)(0),c=(0,v.useRef)(0),l=(0,v.useCallback)(()=>{let n=e.current?.getBoundingClientRect().top,r=t.current?.getBoundingClientRect().bottom;return n===void 0||r===void 0?!1:(s.current=n,c.current=r,o(),!0)},[e,t,o]),{dragging:u,onDragStart:d,onDragMove:f,onDragEnd:p,onDragCancel:m,draggingRef:h}=Le({value:n,valueAt:(0,v.useCallback)(e=>De(e.clientY,s.current,c.current,n),[n]),onGestureStart:l,resize:r,commit:i,reset:a,axis:`y`});return{draggingUpper:u,onUpperDragStart:d,onUpperDragMove:f,onUpperDragEnd:p,onUpperDragCancel:m,upperDraggingRef:h}}function Be(){let{accent:e,next:t,cycle:n,adoptAccent:r,accentWrites:i,sidebarWidth:a,resizeSidebar:o,commitSidebarWidth:s,resetSidebarWidth:c,bumpSidebarWrites:l,adoptSidebarWidth:u,sidebarWrites:d,upperPct:f,resizeUpperPct:p,commitUpperPct:m,resetUpperPct:h,bumpUpperPctWrites:g,adoptUpperPct:_,upperPctWrites:y,maximizedPanelOf:b,setMaximizedFor:x,adoptMaximized:S,maximizedWrites:C}=Fe(),w=(0,v.useRef)(null),ee=(0,v.useRef)(null),T=(0,v.useRef)(null),E=Re({sidebarRef:w,sidebarWidth:a,resizeSidebar:o,commitSidebarWidth:s,resetSidebarWidth:c,bumpSidebarWrites:l}),D=ze({upperRef:ee,lowerRef:T,upperPct:f,resizeUpperPct:p,commitUpperPct:m,resetUpperPct:h,bumpUpperPctWrites:g});return{accent:e,next:t,cycle:n,upperPct:f,maximizedPanelOf:b,setMaximizedFor:x,shell:{sidebarWidth:a,sidebarRef:w,upperRef:ee,lowerRef:T,draggingSidebar:E.draggingSidebar,onSidebarDragStart:E.onSidebarDragStart,onSidebarDragMove:E.onSidebarDragMove,onSidebarDragEnd:E.onSidebarDragEnd,onSidebarDragCancel:E.onSidebarDragCancel,draggingUpper:D.draggingUpper,onUpperDragStart:D.onUpperDragStart,onUpperDragMove:D.onUpperDragMove,onUpperDragEnd:D.onUpperDragEnd,onUpperDragCancel:D.onUpperDragCancel},guards:{adoptAccent:r,adoptSidebarWidth:u,adoptUpperPct:_,adoptMaximized:S,accentWrites:i,sidebarWrites:d,upperPctWrites:y,maximizedWrites:C,draggingRef:E.draggingRef,upperDraggingRef:D.upperDraggingRef}}}function Ve(e,t,n,r=!1){return r&&n&&t.includes(n)?n:e&&t.includes(e)?e:n&&t.includes(n)?n:t[0]??null}function He(e,t,n){if(t===n)return e;let r=e.indexOf(t),i=e.indexOf(n);if(r===-1||i===-1)return e;let a=e.filter(e=>e!==t),o=a.indexOf(n),s=r{let e=!1,p,v=new AbortController,y=()=>{let S=l.current,ee=u.current,E=d.current,te=f.current,k=m.current;return O.repos(v.signal).then(n=>{let{repos:v,hot:x,accent:C,sidebar_width:O,upper_pct:ie,active_repo:A,maximized:j,now_ms:oe,can_clone:se}=n;if(e)return;T(x),re(se),D(e=>N(e,oe,Date.now())),l.current===S&&r(C),u.current===ee&&!s.current&&i(O),d.current===E&&!c.current&&a(ie),f.current===te&&o(j),t(!0),ne(!0);let ce=g.current||_.current!==null;m.current===k&&!h.current&&!ce?b(v):b(e=>{let t=Ue(v.map(e=>e.id),e.map(e=>e.id)),n=new Map(v.map(e=>[e.id,e]));return t.map(e=>n.get(e)).filter(Boolean)});let M=A!==ae.current;ae.current=A??null,w(e=>Ve(e,v.map(e=>e.id),A,M)),e||(p=setTimeout(y,We))}).catch(r=>{e||(x(r)?(t(!1),ne(!1)):C(r)||n(r),p=setTimeout(y,We))})};return y(),()=>{e=!0,v.abort(),p&&clearTimeout(p)}},[e,t,n,r,i,a,p,l,u,d,s,c,m,h,g,_]),(0,v.useEffect)(()=>{S&&ie(S)},[S,ie]),{repos:y,setRepos:b,repo:S,setRepo:w,hot:ee,clockSkewMs:E,reposLoaded:te,canClone:k}}var Ke=4;function qe({ids:e,onReorder:t,draggingRef:n}){let r=(0,v.useRef)(null),i=(0,v.useRef)(null),a=(0,v.useRef)(null),[o,s]=(0,v.useState)(null),[c,l]=(0,v.useState)(null);return{dragging:o,target:c,onStart:(t,a)=>{t.target.closest(`button[data-tab-close]`)||t.button!==0||e.length<2||(r.current=a,i.current={x:t.clientX,y:t.clientY},n.current=!1)},onMove:e=>{let t=r.current,o=i.current;if(t===null||o===null)return;if(!n.current&&e.buttons===0){r.current=null,i.current=null;return}if(!n.current&&Math.hypot(e.clientX-o.x,e.clientY-o.y){let o=r.current,c=a.current;o!==null&&n.current&&c!==null&&t(He(e,o,c)),r.current=null,i.current=null,a.current=null,n.current=!1,s(null),l(null)}}}function Je({repos:e,setRepos:t,handle:n,writesRef:r,draggingRef:i,inFlightRef:a,pendingRef:o}){let s=(0,v.useCallback)(()=>{if(a.current||o.current===null)return;let e=o.current;o.current=null,a.current=!0;let i=r.current;O.reorderRepos(e).then(e=>{r.current===i&&t(e)}).catch(n).finally(()=>{a.current=!1,s()})},[n,t]),c=(0,v.useCallback)(e=>{r.current+=1,t(t=>{let n=Ue(t.map(e=>e.id),e),r=new Map(t.map(e=>[e.id,e]));return n.map(e=>r.get(e)).filter(Boolean)}),o.current=e,s()},[s,t]);return{...qe({ids:e.map(e=>e.id),onReorder:c,draggingRef:i}),writesRef:r,draggingRef:i,inFlightRef:a,pendingRef:o}}function Ye({authed:e,setAuthed:t,handle:n,resumeTick:r,adoptAccent:i,adoptSidebarWidth:a,adoptUpperPct:o,adoptMaximized:s,accentWrites:c,sidebarWrites:l,upperPctWrites:u,maximizedWrites:d,draggingRef:f,upperDraggingRef:p}){let m=(0,v.useRef)(0),h=(0,v.useRef)(!1),g=(0,v.useRef)(!1),_=(0,v.useRef)(null),y=Ge({authed:e,setAuthed:t,handle:n,adoptAccent:i,adoptSidebarWidth:a,adoptUpperPct:o,adoptMaximized:s,draggingRef:f,upperDraggingRef:p,accentWrites:c,sidebarWrites:l,upperPctWrites:u,maximizedWrites:d,resumeTick:r,orderWrites:m,repoDraggingRef:h,reorderInFlightRef:g,pendingReorderRef:_}),{dragging:b,target:x,onStart:S,onMove:C,onEnd:w}=Je({repos:y.repos,setRepos:y.setRepos,handle:n,writesRef:m,draggingRef:h,inFlightRef:g,pendingRef:_});return{...y,orderWrites:m,draggingRepo:b,dragOverRepo:x,onRepoDragStart:S,onRepoDragMove:C,onRepoDragEnd:w}}function Xe({repo:e,authed:t,resumeTick:n,tab:r,pane:i,setPane:a,handle:o,paneRequestRef:s}){let[c,l]=(0,v.useState)(null),u=(0,v.useRef)(i);u.current=i;let d=(0,v.useRef)(r);return d.current=r,(0,v.useLayoutEffect)(()=>{l(null)},[e,t]),(0,v.useEffect)(()=>{if(!(!e||!t))return ne(e,l)},[e,t,n]),(0,v.useEffect)(()=>{if(!e||!c)return;let t=u.current;if(d.current!==`status`||t.kind!==`diff`)return;let n=t.value.path;if(!c.files.some(e=>e.path===n)){a({kind:`empty`});return}let r=s.current,i=!0,l=()=>{let e=u.current;return i&&r===s.current&&e.kind===`diff`&&e.value.path===n};return O.diff(e,n).then(e=>{l()&&a({kind:`diff`,value:e})}).catch(e=>{l()&&o(e)}),()=>{i=!1}},[c,e,o,u,d,s,a]),{status:c,paneRef:u,tabRef:d}}function Ze({repo:e,handle:t,setPane:n,paneRequestRef:r,setCommitDrillDown:i,setMobileView:a,setPreviewRendered:o}){return{openDiff:(0,v.useCallback)(i=>{if(!e)return;a(`diff`);let o=r.current+=1;O.diff(e,i).then(e=>{o===r.current&&n({kind:`diff`,value:e})}).catch(e=>{o===r.current&&t(e)})},[e,t,n,r,a]),openFile:(0,v.useCallback)(i=>{if(!e)return;a(`diff`),o(!0);let s=r.current+=1;O.file(e,i).then(e=>{s===r.current&&n({kind:`file`,value:e})}).catch(e=>{s===r.current&&t(e)})},[e,t,n,r,a,o]),openCommit:(0,v.useCallback)(i=>{if(!e)return;a(`diff`);let o=r.current+=1;O.commit(e,i).then(e=>{o===r.current&&n({kind:`diff`,value:e})}).catch(e=>{o===r.current&&t(e)})},[e,t,n,r,a]),openCommitFileDiff:(0,v.useCallback)((i,o)=>{if(!e)return;a(`diff`);let s=r.current+=1;O.commitFileDiff(e,i,o).then(e=>{s===r.current&&n({kind:`diff`,value:e})}).catch(e=>{s===r.current&&t(e)})},[e,t,n,r,a]),openCommitFiles:(0,v.useCallback)(async o=>{if(!e)return;a(`diff`);let s=r.current+=1;try{let t=await O.commitFiles(e,o.oid);if(s!==r.current)return;if(i({commit:o,...t}),t.files.length===0){n({kind:`empty`});return}let a=await O.commit(e,o.oid);s===r.current&&n({kind:`diff`,value:a})}catch(e){s===r.current&&t(e)}},[e,t,n,r,i,a])}}function Qe({repo:e,authed:t,tab:n,filter:r,handle:i}){let[a,o]=(0,v.useState)([]),[s,c]=(0,v.useState)(!1),[l,u]=(0,v.useState)(!1),d=(0,v.useRef)(null),f=(0,v.useRef)(!1),p=(0,v.useRef)(0),m=(0,v.useCallback)(()=>{p.current+=1,f.current=!1,o([]),d.current=null,c(!1),u(!1)},[]),[h,g]=(0,v.useState)(null),_=(0,v.useRef)(a);_.current=a;let y=(0,v.useCallback)(async()=>{if(!e||f.current)return;f.current=!0;let t=p.current;try{let n=d.current,r=await O.log(e,n===null?void 0:{from:n,skip:_.current.length});if(t!==p.current)return;o(e=>[...e,...r.commits]),d.current=r.head??null,c(!r.truncated||r.head===void 0)}catch(e){t===p.current&&(i(e),u(!0))}finally{t===p.current&&(f.current=!1)}},[e,i]);(0,v.useEffect)(()=>{!e||!t||n!==`log`||a.length===0&&!s&&!l&&y()},[e,t,n,a.length,s,l,y]);let b=a.filter(e=>e.summary.toLowerCase().includes(r.toLowerCase())),x=r!==``,S=(0,v.useRef)(null);return(0,v.useEffect)(()=>{let e=S.current;if(!e)return;let t=new IntersectionObserver(e=>{e.some(e=>e.isIntersecting)&&y()},{root:e.closest(`ul`),rootMargin:`400px`});return t.observe(e),()=>t.disconnect()},[y,s,l,x,h,n,b.length]),{commits:a,logDone:s,logStalled:l,setLogStalled:u,commitDrillDown:h,setCommitDrillDown:g,resetLog:m,logSentinelRef:S,visibleCommits:b,logPagingPaused:x}}function $e(){let[e,t]=(0,v.useState)(0);return(0,v.useEffect)(()=>{let e=()=>{document.visibilityState===`visible`&&t(e=>e+1)};return document.addEventListener(`visibilitychange`,e),window.addEventListener(`online`,e),()=>{document.removeEventListener(`visibilitychange`,e),window.removeEventListener(`online`,e)}},[]),e}function et({repos:e,setRepos:t,setRepo:n,setPane:r,setTab:i,setPickerOpen:a,handle:o,orderWrites:s}){return{selectOpenedRepo:(0,v.useCallback)(e=>{s.current+=1,t(t=>t.some(t=>t.id===e.id)?t:[...t,e]),n(e.id),r({kind:`empty`}),i(`status`),a(!1)},[t,n,r,i,a,s]),closeRepo:(0,v.useCallback)(async r=>{try{await O.close(r),s.current+=1;let i=e.filter(e=>e.id!==r);t(i),n(e=>e===r?i[0]?.id??null:e)}catch(e){o(e)}},[e,t,n,o,s])}}var tt=1e3,nt=3,rt=2e3;function it(e,t){let[n,r]=(0,v.useState)(!1),i=(0,v.useRef)(!1),a=(0,v.useRef)(!1);(0,v.useEffect)(()=>(a.current=!1,()=>{a.current=!0}),[]);let o=(0,v.useCallback)(async t=>{for(;!a.current;){if(await new Promise(e=>setTimeout(e,tt)),a.current)return;let n;try{n=await O.cloneStatus(t)}catch(e){if(x(e)){i.current=!1,a.current||r(!1);return}if(e instanceof b&&e.status===404){if(a.current)return;se.error(`the clone's progress is no longer available`),i.current=!1,r(!1);return}continue}if(a.current)return;if(n.state===`done`){try{let t=await O.open(n.path);if(a.current)return;e(t)}catch(e){if(a.current)return;se.error(e instanceof Error?e.message:`could not open`)}finally{i.current=!1,a.current||r(!1)}return}if(n.state===`failed`){se.error(n.message),i.current=!1,r(!1);return}}},[e]),s=(0,v.useCallback)(async(e=()=>!1)=>{for(let t=0;t0&&await new Promise(e=>setTimeout(e,rt)),i.current||e()||a.current)return;let n;try{({job:n}=await O.runningClone())}catch(e){if(x(e))return;continue}if(n===null||e()||a.current||i.current)return;i.current=!0,r(!0),o(n);return}},[o]);return(0,v.useEffect)(()=>{if(!t)return;let e=!1;return s(()=>e),()=>{e=!0}},[t,s]),{busy:n,start:(0,v.useCallback)(async(e,t)=>{if(!(!t.trim()||i.current)){i.current=!0,r(!0);try{let{job:n}=await O.clone(e,t.trim());await o(n)}catch(e){if(i.current=!1,a.current)return;let t=e instanceof b&&e.status>=400;se.error(t?e.message:`could not confirm the clone started — check this folder before retrying`),r(!1),s()}}},[o,s])}}var at=`data:image/svg+xml,%3c?xml%20version='1.0'%20encoding='utf-8'?%3e%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='12%2057%201150%201150'%20role='img'%20aria-label='Black%20crow'%3e%3ctitle%3eBlack%20crow%3c/title%3e%3cdesc%3eMonochrome%20black%20crow%20silhouette%20on%20a%20transparent%20background,%20framed%20so%20the%20bird%20sits%20centred%20for%20use%20as%20an%20inline%20mark%20on%20a%20square%20tile.%3c/desc%3e%3cg%20fill-rule='evenodd'%20clip-rule='evenodd'%3e%3cpath%20fill='%23000000'%20d='M%20882%20147%20L%20859%20136%20L%20844%20131%20L%20831%20129%20L%20830%20128%20L%20815%20127%20L%20814%20126%20L%20796%20126%20L%20795%20127%20L%20786%20127%20L%20785%20128%20L%20775%20129%20L%20752%20136%20L%20732%20146%20L%20713%20160%20L%20701%20172%20L%20684%20172%20L%20683%20173%20L%20673%20173%20L%20672%20174%20L%20650%20176%20L%20649%20177%20L%20627%20181%20L%20602%20190%20L%20589%20197%20L%20579%20204%20L%20562%20221%20L%20562%20223%20L%20565%20223%20L%20578%20228%20L%20581%20228%20L%20612%20238%20L%20672%20252%20L%20684%20258%20L%20698%20271%20L%20702%20278%20L%20705%20288%20L%20705%20294%20L%20703%20301%20L%20699%20308%20L%20688%20318%20L%20630%20347%20L%20593%20372%20L%20561%20399%20L%20544%20416%20L%20522%20441%20L%20492%20481%20L%20461%20531%20L%20438%20576%20L%20431%20594%20L%20425%20602%20L%20405%20635%20L%20387%20668%20L%20385%20676%20L%20390%20679%20L%20368%20705%20L%20330%20755%20L%20306%20790%20L%20296%20808%20L%20289%20818%20L%20280%20838%20L%20280%20843%20L%20283%20845%20L%20292%20843%20L%20297%20840%20L%20299%20840%20L%20321%20828%20L%20322%20830%20L%20311%20844%20L%20288%20878%20L%20287%20881%20L%20259%20924%20L%20235%20965%20L%20205%201023%20L%20205%201025%20L%20197%201042%20L%20191%201061%20L%20191%201071%20L%20192%201072%20L%20198%201071%20L%20220%201056%20L%20242%201038%20L%20300%20986%20L%20302%20987%20L%20265%201040%20L%20264%201043%20L%20246%201070%20L%20235%201090%20L%20227%201112%20L%20227%201123%20L%20229%201128%20L%20234%201133%20L%20239%201135%20L%20255%201135%20L%20274%201129%20L%20279%201134%20L%20286%201137%20L%20290%201137%20L%20291%201138%20L%20310%201138%20L%20311%201137%20L%20317%201137%20L%20318%201136%20L%20326%201135%20L%20344%201129%20L%20369%201116%20L%20395%201097%20L%20420%201073%20L%20445%201042%20L%20457%201024%20L%20461%201016%20L%20464%201013%20L%20468%201011%20L%20489%20994%20L%20595%20901%20L%20601%20906%20L%20606%20913%20L%20614%20921%20L%20637%20949%20L%20639%20953%20L%20639%20956%20L%20636%20960%20L%20634%20961%20L%20619%20962%20L%20613%20965%20L%20605%20974%20L%20602%20982%20L%20602%20994%20L%20605%201001%20L%20608%201004%20L%20609%201004%20L%20609%20999%20L%20612%20992%20L%20616%20988%20L%20620%20986%20L%20627%20986%20L%20635%20983%20L%20645%20983%20L%20646%20982%20L%20655%20982%20L%20668%20986%20L%20676%20990%20L%20682%20996%20L%20685%201003%20L%20688%201006%20L%20696%201009%20L%20697%201012%20L%20697%201024%20L%20693%201033%20L%20693%201035%20L%20695%201035%20L%20700%201032%20L%20707%201025%20L%20710%201020%20L%20713%201010%20L%20713%201003%20L%20711%20998%20L%20711%20994%20L%20712%20993%20L%20719%201003%20L%20723%201005%20L%20727%201005%20L%20730%201011%20L%20730%201022%20L%20727%201031%20L%20728%201033%20L%20740%201021%20L%20743%201014%20L%20744%201003%20L%20747%20999%20L%20749%20992%20L%20748%20977%20L%20744%20968%20L%20740%20963%20L%20741%20962%20L%20755%20961%20L%20768%20964%20L%20777%20969%20L%20783%20975%20L%20786%20981%20L%20789%20984%20L%20795%20987%20L%20799%20987%20L%20801%20991%20L%20801%20997%20L%20802%20998%20L%20799%201013%20L%20802%201012%20L%20808%201007%20L%20813%201000%20L%20816%20991%20L%20816%20981%20L%20814%20976%20L%20814%20968%20L%20815%20967%20L%20819%20970%20L%20823%20970%20L%20826%20973%20L%20829%20980%20L%20830%20993%20L%20834%20990%20L%20838%20979%20L%20838%20968%20L%20832%20951%20L%20822%20940%20L%20815%20936%20L%20803%20933%20L%20776%20935%20L%20763%20931%20L%20753%20922%20L%20731%20898%20L%20703%20865%20L%20703%20863%20L%20710%20853%20L%20711%20855%20L%20707%20862%20L%20709%20862%20L%20718%20857%20L%20754%20832%20L%20793%20799%20L%20818%20774%20L%20849%20737%20L%20850%20741%20L%20845%20755%20L%20847%20755%20L%20861%20743%20L%20881%20721%20L%20906%20686%20L%20918%20665%20L%20933%20635%20L%20951%20590%20L%20971%20525%20L%20971%20521%20L%20974%20512%20L%20974%20508%20L%20978%20493%20L%20979%20482%20L%20980%20481%20L%20981%20466%20L%20982%20465%20L%20983%20441%20L%20982%20440%20L%20982%20428%20L%20981%20427%20L%20980%20414%20L%20978%20409%20L%20976%20397%20L%20970%20381%20L%20971%20379%20L%20974%20383%20L%20976%20381%20L%20977%20334%20L%20976%20333%20L%20976%20322%20L%20975%20321%20L%20974%20307%20L%20973%20306%20L%20973%20301%20L%20972%20300%20L%20969%20280%20L%20958%20243%20L%20949%20224%20L%20949%20222%20L%20939%20204%20L%20922%20181%20L%20903%20162%20Z%20M%20625%20888%20L%20656%20874%20L%20658%20874%20L%20665%20870%20L%20725%20930%20L%20728%20934%20L%20728%20940%20L%20723%20943%20L%20714%20944%20L%20707%20950%20L%20683%20951%20L%20673%20947%20L%20659%20932%20Z%20M%20787%20182%20L%20792%20182%20L%20796%20187%20L%20795%20192%20L%20791%20195%20L%20788%20195%20L%20783%20191%20L%20783%20186%20Z'/%3e%3cpath%20fill='%23000000'%20d='M%20895%20156%20L%20863%20138%20L%20838%20130%20L%20819%20127%20L%20789%20127%20L%20777%20129%20L%20753%20136%20L%20736%20144%20L%20717%20157%20L%20702%20172%20L%20652%20176%20L%20628%20181%20L%20607%20188%20L%20585%20200%20L%20562%20222%20L%20613%20238%20L%20670%20251%20L%20683%20257%20L%20697%20269%20L%20705%20286%20L%20705%20296%20L%20703%20302%20L%20698%20310%20L%20687%20319%20L%20631%20347%20L%20591%20374%20L%20568%20393%20L%20538%20423%20L%20520%20444%20L%20481%20498%20L%20457%20539%20L%20437%20579%20L%20432%20593%20L%20413%20622%20L%20386%20671%20L%20386%20677%20L%20390%20677%20L%20391%20679%20L%20374%20698%20L%20334%20750%20L%20307%20789%20L%20290%20817%20L%20280%20839%20L%20281%20844%20L%20291%20843%20L%20323%20826%20L%20325%20827%20L%20289%20877%20L%20237%20962%20L%20209%201015%20L%20198%201040%20L%20191%201063%20L%20191%201070%20L%20194%201072%20L%20213%201061%20L%20259%201023%20L%20302%20984%20L%20303%20985%20L%20262%201045%20L%20245%201072%20L%20233%201095%20L%20227%201114%20L%20228%201126%20L%20233%201132%20L%20242%201135%20L%20252%201135%20L%20274%201128%20L%20278%201133%20L%20288%201137%20L%20313%201137%20L%20343%201129%20L%20373%201113%20L%20398%201094%20L%20424%201068%20L%20441%201047%20L%20465%201012%20L%20520%20967%20L%20595%20900%20L%20611%20917%20L%20639%20952%20L%20639%20957%20L%20637%20960%20L%20632%20962%20L%20618%20963%20L%20611%20967%20L%20606%20973%20L%20602%20983%20L%20602%20992%20L%20608%201004%20L%20611%20993%20L%20619%20986%20L%20626%20986%20L%20643%20982%20L%20656%20982%20L%20675%20989%20L%20683%20997%20L%20688%201006%20L%20696%201009%20L%20697%201025%20L%20693%201034%20L%20694%201035%20L%20701%201031%20L%20710%201019%20L%20712%201013%20L%20712%20993%20L%20720%201003%20L%20727%201005%20L%20730%201009%20L%20730%201025%20L%20727%201032%20L%20732%201030%20L%20739%201022%20L%20743%201013%20L%20743%201004%20L%20749%20991%20L%20748%20978%20L%20740%20964%20L%20743%20961%20L%20757%20961%20L%20769%20964%20L%20781%20972%20L%20791%20985%20L%20798%20986%20L%20802%20994%20L%20802%201003%20L%20799%201013%20L%20810%201004%20L%20815%20994%20L%20816%20983%20L%20814%20977%20L%20814%20965%20L%20818%20969%20L%20825%20971%20L%20830%20983%20L%20830%20993%20L%20833%20991%20L%20837%20982%20L%20838%20969%20L%20834%20956%20L%20825%20943%20L%20820%20939%20L%20807%20934%20L%20774%20935%20L%20762%20931%20L%20736%20904%20L%20702%20864%20L%20715%20845%20L%20716%20847%20L%20707%20862%20L%20719%20856%20L%20744%20839%20L%20786%20805%20L%20824%20767%20L%20851%20733%20L%20852%20735%20L%20846%20755%20L%20853%20750%20L%20883%20718%20L%20906%20685%20L%20927%20647%20L%20951%20589%20L%20968%20535%20L%20976%20502%20L%20982%20461%20L%20981%20422%20L%20976%20398%20L%20968%20378%20L%20969%20376%20L%20975%20383%20L%20977%20341%20L%20970%20286%20L%20958%20244%20L%20945%20215%20L%20925%20185%20L%20906%20165%20Z%20M%20625%20888%20L%20665%20870%20L%20728%20933%20L%20729%20940%20L%20726%20943%20L%20715%20944%20L%20708%20950%20L%20692%20952%20L%20678%20950%20L%20672%20947%20L%20662%20936%20Z%20M%20785%20183%20L%20792%20182%20L%20796%20186%20L%20796%20191%20L%20791%20195%20L%20785%20194%20L%20783%20191%20L%20783%20186%20Z'/%3e%3cpath%20fill='%23000000'%20d='M%20896%20157%20L%20869%20141%20L%20837%20130%20L%20817%20127%20L%20792%20127%20L%20778%20129%20L%20751%20137%20L%20733%20146%20L%20715%20159%20L%20702%20172%20L%20653%20176%20L%20610%20187%20L%20581%20203%20L%20562%20222%20L%20625%20241%20L%20671%20251%20L%20683%20257%20L%20698%20270%20L%20705%20285%20L%20704%20300%20L%20699%20309%20L%20689%20318%20L%20628%20349%20L%20581%20382%20L%20540%20421%20L%20517%20448%20L%20478%20503%20L%20456%20541%20L%20439%20575%20L%20432%20593%20L%20412%20624%20L%20388%20667%20L%20386%20676%20L%20390%20677%20L%20391%20679%20L%20363%20712%20L%20332%20753%20L%20289%20819%20L%20281%20836%20L%20281%20844%20L%20293%20842%20L%20323%20826%20L%20325%20827%20L%20284%20885%20L%20236%20964%20L%20205%201024%20L%20197%201043%20L%20191%201064%20L%20191%201070%20L%20197%201071%20L%20210%201063%20L%20253%201028%20L%20303%20983%20L%20304%20984%20L%20259%201050%20L%20235%201091%20L%20227%201115%20L%20228%201125%20L%20232%201131%20L%20238%201134%20L%20249%201135%20L%20261%201133%20L%20274%201128%20L%20278%201133%20L%20289%201137%20L%20312%201137%20L%20342%201129%20L%20368%201116%20L%20399%201093%20L%20423%201069%20L%20443%201044%20L%20463%201013%20L%20493%20990%20L%20595%20900%20L%20612%20918%20L%20639%20952%20L%20639%20957%20L%20636%20961%20L%20616%20964%20L%20606%20973%20L%20602%20984%20L%20602%20991%20L%20608%201004%20L%20611%20993%20L%20621%20985%20L%20625%20986%20L%20639%20982%20L%20657%20982%20L%20677%20990%20L%20685%201002%20L%20690%201007%20L%20697%201010%20L%20698%201021%20L%20693%201034%20L%20694%201035%20L%20705%201027%20L%20710%201018%20L%20712%201011%20L%20712%201001%20L%20710%20994%20L%20712%20993%20L%20720%201003%20L%20729%201006%20L%20731%201020%20L%20728%201032%20L%20738%201023%20L%20743%201012%20L%20743%201003%20L%20748%20994%20L%20747%20976%20L%20740%20964%20L%20743%20961%20L%20759%20961%20L%20767%20963%20L%20780%20971%20L%20790%20984%20L%20799%20986%20L%20802%20992%20L%20802%201004%20L%20799%201012%20L%20803%201011%20L%20810%201004%20L%20815%20994%20L%20814%20965%20L%20818%20969%20L%20823%20969%20L%20830%20982%20L%20830%20992%20L%20832%20992%20L%20837%20982%20L%20837%20965%20L%20831%20950%20L%20821%20940%20L%20806%20934%20L%20772%20935%20L%20762%20931%20L%20733%20901%20L%20702%20863%20L%20717%20842%20L%20718%20844%20L%20707%20862%20L%20738%20843%20L%20780%20810%20L%20822%20769%20L%20851%20733%20L%20852%20735%20L%20846%20755%20L%20855%20748%20L%20882%20719%20L%20904%20688%20L%20930%20640%20L%20950%20591%20L%20967%20538%20L%20978%20490%20L%20982%20459%20L%20982%20434%20L%20976%20399%20L%20967%20376%20L%20969%20375%20L%20975%20383%20L%20976%20329%20L%20971%20293%20L%20960%20250%20L%20942%20210%20L%20923%20183%20Z%20M%20625%20888%20L%20665%20870%20L%20729%20934%20L%20729%20940%20L%20726%20943%20L%20716%20944%20L%20708%20950%20L%20695%20952%20L%20677%20950%20L%20666%20941%20Z%20M%20786%20182%20L%20790%20181%20L%20794%20183%20L%20797%20188%20L%20792%20195%20L%20787%20195%20L%20782%20190%20L%20782%20187%20Z'/%3e%3cpath%20fill='%23000000'%20d='M%20893%20155%20L%20873%20143%20L%20841%20131%20L%20816%20127%20L%20794%20127%20L%20779%20129%20L%20754%20136%20L%20735%20145%20L%20715%20159%20L%20702%20172%20L%20648%20177%20L%20629%20181%20L%20608%20188%20L%20584%20201%20L%20562%20222%20L%20614%20238%20L%20671%20251%20L%20685%20258%20L%20698%20270%20L%20704%20281%20L%20706%20292%20L%20703%20303%20L%20699%20309%20L%20686%20320%20L%20628%20349%20L%20590%20375%20L%20565%20396%20L%20543%20418%20L%20518%20447%20L%20490%20485%20L%20460%20534%20L%20441%20571%20L%20431%20595%20L%20405%20636%20L%20386%20672%20L%20386%20676%20L%20390%20677%20L%20391%20679%20L%20371%20702%20L%20335%20749%20L%20291%20816%20L%20280%20840%20L%20282%20844%20L%20298%20840%20L%20324%20825%20L%20326%20826%20L%20294%20870%20L%20235%20966%20L%20206%201022%20L%20198%201041%20L%20191%201065%20L%20191%201070%20L%20196%201071%20L%20214%201060%20L%20254%201027%20L%20303%20983%20L%20304%20984%20L%20256%201055%20L%20232%201098%20L%20227%201115%20L%20228%201125%20L%20232%201131%20L%20239%201134%20L%20248%201135%20L%20264%201132%20L%20274%201128%20L%20280%201134%20L%20289%201137%20L%20311%201137%20L%20339%201130%20L%20371%201114%20L%20401%201091%20L%20422%201070%20L%20440%201048%20L%20463%201013%20L%20487%20995%20L%20595%20900%20L%20613%20919%20L%20639%20951%20L%20639%20958%20L%20636%20961%20L%20619%20963%20L%20613%20966%20L%20605%20975%20L%20602%20991%20L%20604%20998%20L%20608%201003%20L%20611%20993%20L%20620%20985%20L%20624%20986%20L%20632%20983%20L%20647%20981%20L%20658%20982%20L%20677%20990%20L%20689%201006%20L%20697%201009%20L%20698%201021%20L%20694%201035%20L%20703%201029%20L%20710%201018%20L%20712%201010%20L%20712%201002%20L%20710%20997%20L%20711%20992%20L%20720%201003%20L%20728%201005%20L%20730%201008%20L%20731%201021%20L%20728%201032%20L%20738%201023%20L%20742%201014%20L%20743%201003%20L%20748%20994%20L%20748%20980%20L%20742%20966%20L%20739%20963%20L%20741%20961%20L%20753%20960%20L%20770%20964%20L%20780%20971%20L%20790%20984%20L%20799%20986%20L%20802%20992%20L%20802%201005%20L%20799%201012%20L%20804%201010%20L%20809%201005%20L%20815%20993%20L%20813%20964%20L%20817%20968%20L%20823%20969%20L%20826%20972%20L%20830%20982%20L%20831%20992%20L%20834%20989%20L%20838%20975%20L%20837%20966%20L%20831%20950%20L%20821%20940%20L%20804%20934%20L%20779%20936%20L%20763%20932%20L%20731%20899%20L%20702%20865%20L%20702%20863%20L%20717%20842%20L%20718%20844%20L%20708%20862%20L%20749%20835%20L%20781%20809%20L%20820%20771%20L%20851%20733%20L%20852%20736%20L%20846%20755%20L%20857%20746%20L%20881%20720%20L%20907%20683%20L%20928%20644%20L%20945%20604%20L%20967%20537%20L%20978%20489%20L%20982%20455%20L%20982%20437%20L%20979%20413%20L%20975%20396%20L%20967%20377%20L%20968%20374%20L%20975%20382%20L%20976%20332%20L%20972%20299%20L%20962%20257%20L%20954%20235%20L%20943%20212%20L%20923%20183%20Z%20M%20624%20888%20L%20666%20870%20L%20729%20934%20L%20729%20940%20L%20726%20943%20L%20716%20944%20L%20706%20951%20L%20684%20952%20L%20677%20950%20L%20665%20940%20Z%20M%20701%20220%20L%20710%20219%20L%20717%20221%20L%20704%20223%20L%20704%20221%20Z%20M%20666%20217%20L%20679%20216%20L%20689%20218%20L%20685%20220%20L%20675%20220%20Z%20M%20658%20210%20L%20661%20208%20L%20686%20205%20L%20706%20206%20L%20725%20209%20L%20733%20217%20L%20741%20220%20L%20738%20221%20L%20696%20214%20L%20661%20212%20Z%20M%20788%20181%20L%20793%20182%20L%20797%20188%20L%20792%20195%20L%20786%20195%20L%20782%20190%20L%20783%20185%20Z'/%3e%3cpath%20fill='%23000000'%20d='M%20893%20155%20L%20864%20139%20L%20836%20130%20L%20813%20127%20L%20796%20127%20L%20774%20130%20L%20749%20138%20L%20735%20145%20L%20720%20155%20L%20702%20172%20L%20687%20173%20L%20700%20174%20L%20694%20181%20L%20674%20184%20L%20670%20180%20L%20665%20183%20L%20657%20184%20L%20639%20196%20L%20641%20198%20L%20647%20198%20L%20648%20194%20L%20654%20193%20L%20686%20204%20L%20724%20208%20L%20740%20213%20L%20751%20222%20L%20750%20223%20L%20693%20214%20L%20671%20212%20L%20639%20212%20L%20636%20211%20L%20634%20207%20L%20629%20207%20L%20626%20210%20L%20617%20210%20L%20614%20208%20L%20604%20207%20L%20594%20213%20L%20582%20216%20L%20580%20214%20L%20581%20210%20L%20576%20208%20L%20586%20200%20L%20565%20218%20L%20563%20222%20L%20618%20239%20L%20671%20251%20L%20682%20256%20L%20689%20261%20L%20702%20276%20L%20706%20288%20L%20704%20301%20L%20700%20308%20L%20683%20322%20L%20630%20348%20L%20585%20379%20L%20563%20398%20L%20523%20441%20L%20497%20475%20L%20459%20536%20L%20443%20567%20L%20431%20595%20L%20411%20626%20L%20387%20670%20L%20386%20676%20L%20390%20677%20L%20391%20679%20L%20368%20706%20L%20333%20752%20L%20290%20818%20L%20281%20837%20L%20282%20844%20L%20300%20839%20L%20324%20825%20L%20326%20826%20L%20285%20884%20L%20242%20954%20L%20207%201020%20L%20192%201060%20L%20192%201071%20L%20196%201071%20L%20211%201062%20L%20240%201039%20L%20303%20983%20L%20305%20984%20L%20277%201023%20L%20242%201078%20L%20228%201110%20L%20227%201119%20L%20231%201130%20L%20239%201134%20L%20255%201134%20L%20274%201128%20L%20280%201134%20L%20285%201136%20L%20310%201137%20L%20339%201130%20L%20374%201112%20L%20394%201097%20L%20421%201071%20L%20445%201041%20L%20463%201013%20L%20488%20994%20L%20595%20900%20L%20614%20920%20L%20639%20951%20L%20639%20958%20L%20636%20961%20L%20619%20963%20L%20613%20966%20L%20607%20972%20L%20603%20980%20L%20602%20989%20L%20603%20995%20L%20608%201003%20L%20610%20994%20L%20620%20985%20L%20623%20986%20L%20631%20983%20L%20647%20981%20L%20665%20984%20L%20676%20989%20L%20689%201006%20L%20695%201007%20L%20697%201009%20L%20698%201023%20L%20694%201035%20L%20699%201032%20L%20709%201020%20L%20712%201010%20L%20710%20997%20L%20711%20992%20L%20719%201002%20L%20725%201005%20L%20727%201004%20L%20730%201008%20L%20731%201022%20L%20728%201032%20L%20739%201021%20L%20743%201010%20L%20743%201003%20L%20748%20993%20L%20748%20981%20L%20746%20974%20L%20739%20963%20L%20741%20961%20L%20755%20960%20L%20770%20964%20L%20779%20970%20L%20789%20983%20L%20799%20986%20L%20802%20992%20L%20802%201005%20L%20799%201012%20L%20808%201006%20L%20815%20993%20L%20813%20964%20L%20817%20968%20L%20825%20970%20L%20829%20978%20L%20831%20992%20L%20837%20981%20L%20837%20966%20L%20832%20952%20L%20822%20941%20L%20813%20936%20L%20803%20934%20L%20776%20936%20L%20763%20932%20L%20723%20890%20L%20702%20865%20L%20702%20863%20L%20717%20841%20L%20719%20842%20L%20708%20862%20L%20742%20840%20L%20782%20808%20L%20818%20773%20L%20851%20733%20L%20852%20736%20L%20846%20754%20L%20850%20752%20L%20881%20720%20L%20905%20686%20L%20932%20635%20L%20950%20590%20L%20969%20529%20L%20977%20494%20L%20982%20454%20L%20980%20419%20L%20975%20396%20L%20967%20377%20L%20968%20374%20L%20975%20382%20L%20976%20334%20L%20969%20284%20L%20959%20248%20L%20941%20209%20L%20921%20181%20Z%20M%20624%20888%20L%20666%20870%20L%20729%20934%20L%20729%20941%20L%20724%20944%20L%20716%20944%20L%20707%20951%20L%20683%20952%20L%20673%20948%20L%20659%20933%20Z%20M%20563%20618%20L%20569%20614%20L%20579%20621%20L%20576%20626%20L%20572%20627%20L%20568%20623%20L%20565%20623%20Z%20M%20575%20603%20L%20578%20603%20L%20588%20613%20L%20588%20619%20L%20585%20621%20L%20582%20620%20L%20575%20613%20L%20576%20612%20L%20573%20605%20Z%20M%20748%20398%20L%20752%20412%20L%20752%20432%20L%20748%20444%20L%20732%20470%20L%20716%20484%20L%20698%20493%20L%20685%20494%20L%20682%20491%20L%20707%20445%20L%20685%20473%20L%20667%20492%20L%20647%20508%20L%20632%20517%20L%20619%20519%20L%20615%20517%20L%20615%20513%20L%20643%20470%20L%20603%20514%20L%20589%20526%20L%20569%20538%20L%20558%20540%20L%20554%20539%20L%20552%20535%20L%20577%20498%20L%20549%20528%20L%20526%20546%20L%20508%20554%20L%20498%20555%20L%20495%20552%20L%20501%20541%20L%20482%20555%20L%20471%20558%20L%20464%20558%20L%20461%20560%20L%20460%20559%20L%20461%20555%20L%20469%20547%20L%20473%20537%20L%20491%20512%20L%20511%20497%20L%20582%20434%20L%20617%20408%20L%20632%20399%20L%20659%20386%20L%20677%20380%20L%20695%20377%20L%20712%20377%20L%20725%20380%20L%20739%20388%20Z%20M%20625%20216%20L%20683%20216%20L%20737%20222%20L%20742%20224%20L%20733%20226%20L%20721%20225%20L%20713%20230%20L%20711%20228%20L%20706%20228%20L%20694%20237%20L%20690%20233%20L%20690%20225%20L%20679%20228%20L%20674%20232%20L%20668%20232%20L%20659%20229%20L%20649%20222%20L%20632%20219%20L%20632%20217%20Z%20M%20788%20181%20L%20793%20182%20L%20797%20187%20L%20795%20193%20L%20792%20195%20L%20786%20195%20L%20782%20190%20L%20783%20185%20Z'/%3e%3cpath%20fill='%23000000'%20d='M%20607%20972%20L%20603%20981%20L%20603%20995%20L%20608%201003%20L%20610%20994%20L%20616%20987%20L%20620%20985%20L%20627%20985%20L%20635%20982%20L%20655%20981%20L%20676%20989%20L%20683%20996%20L%20689%201006%20L%20695%201007%20L%20697%201009%20L%20698%201024%20L%20694%201034%20L%20699%201032%20L%20706%201025%20L%20712%201010%20L%20710%20994%20L%20709%20997%20L%20706%20994%20L%20700%20994%20L%20694%20997%20L%20672%20976%20L%20658%20974%20L%20647%20967%20L%20642%20974%20L%20638%20974%20L%20636%20972%20L%20636%20961%20L%20629%20963%20L%20631%20972%20L%20625%20977%20L%20620%20977%20L%20613%20967%20L%20614%20966%20Z%20M%20686%20966%20L%20704%20980%20L%20719%201002%20L%20727%201004%20L%20730%201007%20L%20731%201023%20L%20728%201032%20L%20731%201030%20L%20741%201017%20L%20743%201003%20L%20748%20992%20L%20748%20981%20L%20742%20967%20L%20740%20965%20L%20742%20968%20L%20735%20972%20L%20726%20965%20L%20705%20960%20L%20724%20968%20L%20740%20983%20L%20742%20987%20L%20740%20991%20L%20732%20991%20L%20728%20996%20L%20726%20996%20L%20721%20992%20L%20713%20979%20Z%20M%20784%20943%20L%20784%20946%20L%20798%20950%20L%20819%20969%20L%20825%20970%20L%20830%20981%20L%20831%20992%20L%20837%20981%20L%20837%20966%20L%20829%20948%20L%20825%20944%20L%20830%20950%20L%20821%20957%20L%20813%20951%20L%20794%20942%20Z%20M%20659%20933%20L%20662%20937%20L%20658%20942%20L%20658%20945%20L%20663%20947%20L%20666%20945%20L%20667%20946%20L%20668%20944%20L%20670%20946%20Z%20M%20643%20913%20L%20647%20918%20L%20644%20921%20L%20642%20928%20L%20629%20938%20L%20627%20936%20L%20636%20947%20L%20634%20945%20L%20641%20933%20L%20651%20923%20L%20657%20930%20Z%20M%20936%20310%20L%20934%20309%20L%20945%20338%20L%20949%20368%20L%20949%20381%20L%20947%20383%20L%20935%20367%20L%20914%20348%20L%20929%20377%20L%20934%20397%20L%20936%20414%20L%20935%20440%20L%20933%20442%20L%20930%20440%20L%20924%20419%20L%20912%20395%20L%20909%20393%20L%20910%20410%20L%20907%20433%20L%20899%20458%20L%20895%20462%20L%20893%20460%20L%20892%20445%20L%20888%20427%20L%20873%20390%20L%20873%20416%20L%20871%20430%20L%20866%20448%20L%20862%20454%20L%20859%20452%20L%20853%20433%20L%20842%20410%20L%20823%20382%20L%20807%20365%20L%20805%20366%20L%20811%20394%20L%20812%20416%20L%20810%20425%20L%20806%20428%20L%20801%20423%20L%20790%20403%20L%20772%20383%20L%20759%20372%20L%20733%20357%20L%20724%20355%20L%20705%20346%20L%20693%20344%20L%20669%20344%20L%20646%20349%20L%20636%20353%20L%20662%20349%20L%20677%20349%20L%20699%20353%20L%20708%20356%20L%20725%20366%20L%20736%20376%20L%20745%20389%20L%20753%20415%20L%20752%20442%20L%20743%20476%20L%20723%20520%20L%20693%20569%20L%20646%20631%20L%20605%20676%20L%20568%20709%20L%20565%20710%20L%20562%20706%20L%20562%20692%20L%20567%20665%20L%20578%20636%20L%20602%20619%20L%20622%20602%20L%20645%20578%20L%20670%20546%20L%20630%20588%20L%20603%20610%20L%20574%20628%20L%20560%20633%20L%20556%20633%20L%20555%20631%20L%20563%20617%20L%20596%20572%20L%20647%20509%20L%20636%20515%20L%20562%20609%20L%20541%20630%20L%20520%20645%20L%20502%20653%20L%20490%20653%20L%20536%20590%20L%20513%20617%20L%20478%20651%20L%20454%20665%20L%20440%20669%20L%20436%20667%20L%20488%20596%20L%20459%20630%20L%20436%20653%20L%20411%20671%20L%20392%20678%20L%20372%20701%20L%20319%20772%20L%20289%20820%20L%20281%20838%20L%20282%20844%20L%20295%20841%20L%20324%20825%20L%20326%20826%20L%20289%20878%20L%20242%20954%20L%20209%201016%20L%20193%201056%20L%20191%201069%20L%20192%201071%20L%20196%201071%20L%20219%201056%20L%20303%20983%20L%20305%20984%20L%20255%201057%20L%20234%201094%20L%20228%201111%20L%20228%201124%20L%20234%201132%20L%20240%201134%20L%20254%201134%20L%20275%201128%20L%20279%201133%20L%20291%201137%20L%20316%201136%20L%20344%201128%20L%20364%201118%20L%20389%201101%20L%20403%201089%20L%20427%201064%20L%20447%201038%20L%20463%201013%20L%20489%20993%20L%20594%20901%20L%20595%20899%20L%20593%20894%20L%20588%20891%20L%20567%20868%20L%20569%20865%20L%20576%20867%20L%20586%20873%20L%20589%20867%20L%20607%20867%20L%20619%20880%20L%20622%20887%20L%20626%20891%20L%20624%20889%20L%20626%20886%20L%20666%20870%20L%20705%20909%20L%20710%20905%20L%20713%20905%20L%20716%20908%20L%20716%20911%20L%20712%20916%20L%20728%20932%20L%20730%20939%20L%20727%20943%20L%20717%20944%20L%20710%20949%20L%20712%20948%20L%20725%20951%20L%20739%20963%20L%20740%20961%20L%20756%20960%20L%20774%20966%20L%20784%20975%20L%20789%20983%20L%20800%20987%20L%20802%20991%20L%20802%201005%20L%20799%201012%20L%20806%201008%20L%20811%201002%20L%20815%20993%20L%20813%20968%20L%20812%20973%20L%20807%20971%20L%20796%20976%20L%20791%20973%20L%20788%20967%20L%20779%20958%20L%20767%20952%20L%20762%20941%20L%20756%20940%20L%20751%20932%20L%20749%20931%20L%20743%20936%20L%20738%20936%20L%20736%20933%20L%20741%20925%20L%20739%20915%20L%20742%20912%20L%20745%20914%20L%20702%20865%20L%20702%20863%20L%20717%20841%20L%20719%20842%20L%20709%20861%20L%20746%20837%20L%20783%20807%20L%20826%20764%20L%20851%20732%20L%20853%20734%20L%20846%20754%20L%20851%20751%20L%20874%20728%20L%20890%20708%20L%20908%20681%20L%20938%20621%20L%20960%20560%20L%20971%20521%20L%20979%20481%20L%20982%20444%20L%20977%20405%20L%20970%20382%20L%20966%20375%20L%20968%20374%20L%20974%20381%20L%20975%20368%20L%20974%20372%20L%20968%20364%20L%20966%20354%20L%20957%20337%20Z%20M%20247%201077%20L%20251%201085%20L%20246%201088%20L%20245%201091%20L%20240%201092%20L%20239%201085%20L%20241%201081%20Z%20M%20370%20935%20L%20371%20937%20L%20326%20997%20L%20301%201034%20L%20289%201044%20L%20286%201042%20L%20283%201045%20L%20277%201045%20L%20273%201043%20L%20264%201052%20L%20261%201049%20L%20270%201037%20L%20271%201038%20L%20281%201027%20L%20293%201010%20L%20326%20971%20Z%20M%20726%20921%20L%20728%20921%20L%20731%20926%20L%20729%20933%20L%20722%20926%20Z%20M%20729%20897%20L%20734%20902%20L%20732%20906%20L%20726%20902%20L%20726%20899%20Z%20M%20715%20882%20L%20720%20886%20L%20718%20890%20L%20711%20887%20Z%20M%20605%20762%20L%20608%20761%20L%20607%20760%20L%20609%20757%20L%20610%20759%20L%20617%20761%20L%20620%20764%20L%20611%20770%20L%20607%20770%20L%20606%20765%20L%20608%20765%20Z%20M%20611%20754%20L%20617%20750%20L%20620%20751%20L%20621%20749%20L%20625%20749%20L%20628%20752%20L%20628%20756%20L%20623%20761%20Z%20M%20549%20710%20L%20551%20713%20L%20551%20722%20L%20506%20774%20L%20403%20878%20L%20309%20963%20L%20307%20961%20L%20313%20953%20L%20322%20945%20L%20340%20922%20L%20375%20885%20Z%20M%20695%20704%20L%20696%20708%20L%20698%20709%20L%20695%20710%20L%20695%20713%20L%20692%20715%20L%20688%20710%20Z%20M%20704%20694%20L%20706%20695%20L%20706%20698%20L%20710%20698%20L%20713%20703%20L%20708%20708%20L%20707%20714%20L%20705%20716%20L%20701%20716%20L%20697%20708%20L%20699%20706%20L%20697%20705%20L%20701%20701%20L%20704%20705%20L%20706%20704%20L%20706%20700%20L%20702%20697%20Z%20M%20552%20678%20L%20554%20681%20L%20552%20701%20L%20465%20787%20L%20361%20883%20L%20316%20927%20L%20253%20993%20L%20211%201042%20L%20211%201036%20L%20222%201011%20L%20261%20942%20L%20292%20898%20L%20338%20843%20L%20374%20807%20L%20387%20807%20L%20403%20801%20L%20419%20792%20L%20456%20766%20L%20500%20728%20Z%20M%20704%20654%20L%20706%20658%20L%20711%20658%20L%20714%20661%20L%20715%20673%20L%20713%20676%20L%20705%20676%20L%20702%20674%20L%20704%20677%20L%20704%20681%20L%20702%20682%20L%20704%20685%20L%20700%20686%20L%20694%20678%20L%20691%20678%20L%20688%20674%20L%20692%20670%20L%20692%20664%20L%20694%20661%20Z%20M%20565%20642%20L%20558%20663%20L%20545%20677%20L%20494%20726%20L%20443%20767%20L%20401%20792%20L%20392%20795%20L%20387%20794%20L%20405%20766%20L%20455%20707%20L%20388%20767%20L%20355%20794%20L%20304%20827%20L%20299%20826%20L%20302%20817%20L%20316%20795%20L%20360%20739%20L%20398%20700%20L%20425%20677%20L%20428%20679%20L%20443%20679%20L%20461%20673%20L%20476%20664%20L%20493%20666%20L%20517%20657%20L%20542%20641%20L%20546%20643%20Z%20M%20563%20221%20L%20572%20225%20L%20571%20223%20L%20574%20220%20L%20585%20217%20L%20596%20218%20L%20607%20215%20L%20653%20214%20L%20706%20218%20L%20749%20224%20L%20749%20226%20L%20735%20233%20L%20732%20238%20L%20732%20249%20L%20693%20242%20L%20620%20239%20L%20672%20251%20L%20688%20260%20L%20701%20274%20L%20706%20287%20L%20704%20301%20L%20707%20297%20L%20711%20300%20L%20713%20309%20L%20720%20324%20L%20723%20327%20L%20724%20308%20L%20726%20304%20L%20735%20318%20L%20757%20341%20L%20753%20319%20L%20754%20313%20L%20760%20317%20L%20786%20345%20L%20784%20324%20L%20775%20300%20L%20779%20300%20L%20800%20317%20L%20800%20312%20L%20787%20283%20L%20772%20264%20L%20778%20263%20L%20800%20271%20L%20803%20270%20L%20770%20236%20L%20772%20234%20L%20797%20234%20L%20807%20230%20L%20801%20230%20L%20800%20228%20L%20811%20218%20L%20818%20203%20L%20818%20193%20L%20812%20179%20L%20798%20168%20L%20784%20166%20L%20760%20173%20L%20747%20173%20L%20712%20163%20L%20718%20157%20L%20703%20171%20L%20711%20173%20L%20720%20178%20L%20723%20182%20L%20720%20186%20L%20695%20190%20L%20680%20190%20L%20670%20193%20L%20656%20193%20L%20687%20204%20L%20729%20209%20L%20742%20214%20L%20753%20223%20L%20752%20224%20L%20729%20219%20L%20669%20212%20L%20610%20213%20L%20576%20218%20L%20565%20221%20L%20565%20219%20Z%20M%20771%20189%20L%20772%20201%20L%20775%20207%20L%20780%20212%20L%20787%20215%20L%20796%20216%20L%20796%20218%20L%20784%20219%20L%20776%20215%20L%20771%20210%20L%20768%20204%20L%20768%20194%20Z%20M%20788%20181%20L%20793%20182%20L%20797%20187%20L%20796%20192%20L%20790%20196%20L%20786%20195%20L%20782%20191%20L%20782%20186%20Z'/%3e%3c/g%3e%3c/svg%3e`,ot=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),st=o(((e,t)=>{t.exports=ot()})),F=st();function ct({className:e}){return(0,F.jsx)(`span`,{className:`block overflow-hidden rounded-[20.7%] bg-accent ${e??``}`,children:(0,F.jsx)(`img`,{src:at,alt:``,"aria-hidden":`true`,className:`h-full w-full`})})}function lt({maximized:e}){return(0,F.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`h-4 w-4`,children:e?(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(`path`,{d:`M8 3v3a2 2 0 0 1-2 2H3`}),(0,F.jsx)(`path`,{d:`M21 8h-3a2 2 0 0 1-2-2V3`}),(0,F.jsx)(`path`,{d:`M3 16h3a2 2 0 0 1 2 2v3`}),(0,F.jsx)(`path`,{d:`M16 21v-3a2 2 0 0 1 2-2h3`})]}):(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(`path`,{d:`M8 3H5a2 2 0 0 0-2 2v3`}),(0,F.jsx)(`path`,{d:`M21 8V5a2 2 0 0 0-2-2h-3`}),(0,F.jsx)(`path`,{d:`M3 16v3a2 2 0 0 0 2 2h3`}),(0,F.jsx)(`path`,{d:`M16 21h3a2 2 0 0 0 2-2v-3`})]})})}function ut({open:e}){return(0,F.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2.5`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`h-3.5 w-3.5 shrink-0 transition-transform ${e?`rotate-90`:``}`,children:(0,F.jsx)(`path`,{d:`m9 18 6-6-6-6`})})}function dt({className:e=`h-4 w-4`}){return(0,F.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`shrink-0 ${e}`,children:[(0,F.jsx)(`path`,{d:`M18 6 6 18`}),(0,F.jsx)(`path`,{d:`m6 6 12 12`})]})}function ft({className:e=`h-4 w-4`}){return(0,F.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`shrink-0 ${e}`,children:[(0,F.jsx)(`path`,{d:`M5 12h14`}),(0,F.jsx)(`path`,{d:`M12 5v14`})]})}function pt(){return(0,F.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`h-4 w-4`,children:[(0,F.jsx)(`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`}),(0,F.jsx)(`path`,{d:`M12 3v18`})]})}function mt(){return(0,F.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`h-4 w-4`,children:[(0,F.jsx)(`path`,{d:`M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z`}),(0,F.jsx)(`circle`,{cx:`12`,cy:`12`,r:`3`})]})}function ht(){return(0,F.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`h-4 w-4`,children:[(0,F.jsx)(`circle`,{cx:`11`,cy:`11`,r:`8`}),(0,F.jsx)(`path`,{d:`m21 21-4.3-4.3`})]})}function gt({className:e=`h-4 w-4`}){return(0,F.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`shrink-0 ${e}`,children:[(0,F.jsx)(`path`,{d:`M3 6h.01`}),(0,F.jsx)(`path`,{d:`M3 12h.01`}),(0,F.jsx)(`path`,{d:`M3 18h.01`}),(0,F.jsx)(`path`,{d:`M8 6h13`}),(0,F.jsx)(`path`,{d:`M8 12h13`}),(0,F.jsx)(`path`,{d:`M8 18h13`})]})}function _t({className:e=`h-4 w-4`}){return(0,F.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`shrink-0 ${e}`,children:[(0,F.jsx)(`path`,{d:`M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z`}),(0,F.jsx)(`path`,{d:`M14 2v4a2 2 0 0 0 2 2h4`}),(0,F.jsx)(`path`,{d:`M10 9H8`}),(0,F.jsx)(`path`,{d:`M16 13H8`}),(0,F.jsx)(`path`,{d:`M16 17H8`})]})}function vt({className:e=`h-4 w-4`}){return(0,F.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`shrink-0 ${e}`,children:[(0,F.jsx)(`path`,{d:`m4 17 6-6-6-6`}),(0,F.jsx)(`path`,{d:`M12 19h8`})]})}function yt({className:e=`h-4 w-4`}){return(0,F.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`shrink-0 ${e}`,children:[(0,F.jsx)(`rect`,{x:`2`,y:`3`,width:`20`,height:`14`,rx:`2`}),(0,F.jsx)(`path`,{d:`M12 17v4`}),(0,F.jsx)(`path`,{d:`M8 21h8`}),(0,F.jsx)(`path`,{d:`m9 13 6-6`}),(0,F.jsx)(`path`,{d:`M9 10v3h3`}),(0,F.jsx)(`path`,{d:`M15 10V7h-3`})]})}function bt({className:e=`h-4 w-4`}){return(0,F.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`shrink-0 ${e}`,children:[(0,F.jsx)(`path`,{d:`M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4`}),(0,F.jsx)(`path`,{d:`m16 17 5-5-5-5`}),(0,F.jsx)(`path`,{d:`M21 12H9`})]})}function xt({className:e=`h-4 w-4`}){return(0,F.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`shrink-0 ${e}`,children:[(0,F.jsx)(`path`,{d:`M3 12a9 9 0 0 1 9-9 9 9 0 0 1 6.7 3H21`}),(0,F.jsx)(`path`,{d:`M21 3v6h-6`}),(0,F.jsx)(`path`,{d:`M21 12a9 9 0 0 1-9 9 9 9 0 0 1-6.7-3H3`}),(0,F.jsx)(`path`,{d:`M3 21v-6h6`})]})}function St({repos:e,currentId:t,onSelect:n,onCloseProject:r,onOpenPicker:i,className:a=``}){let[o,s]=(0,v.useState)(!1),c=(0,v.useRef)(null),l=e.find(e=>e.id===t);return(0,v.useEffect)(()=>{if(!o)return;let e=e=>{e.key===`Escape`&&(s(!1),c.current?.focus())};return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[o]),(0,F.jsxs)(`div`,{className:`relative ${a}`,children:[(0,F.jsxs)(`button`,{ref:c,onClick:()=>s(e=>!e),"aria-haspopup":`menu`,"aria-expanded":o,title:l?.display_path??`Select a project`,className:`flex max-w-[9rem] items-center gap-1 rounded-sm bg-ink-700 py-0.5 pl-2 pr-1 text-ink-50`,children:[(0,F.jsx)(`span`,{className:`truncate`,children:l?.name??`No project`}),(0,F.jsx)(ut,{open:o})]}),o&&(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(`div`,{className:`fixed inset-0 z-40`,onClick:()=>s(!1)}),(0,F.jsxs)(`div`,{role:`menu`,className:`absolute left-0 z-50 mt-1 max-h-[70vh] w-56 max-w-[80vw] overflow-y-auto rounded-md border border-ink-700 bg-ink-900 py-1 shadow-lg`,children:[e.length===0&&(0,F.jsx)(`p`,{className:`px-3 py-1.5 text-ink-400`,children:`No projects open.`}),e.map(e=>(0,F.jsxs)(`div`,{className:`flex items-center ${e.id===t?`bg-ink-700 text-ink-50`:`text-ink-200`}`,children:[(0,F.jsx)(`button`,{role:`menuitem`,onClick:()=>{n(e.id),s(!1)},title:e.display_path,className:`min-w-0 flex-1 truncate py-1.5 pl-3 pr-1 text-left hover:text-accent`,children:e.name}),(0,F.jsx)(`button`,{onClick:()=>r(e.id),"aria-label":`close ${e.name}`,title:`Close project`,className:`mr-1 flex h-6 w-6 shrink-0 items-center justify-center rounded-sm text-ink-400 hover:text-removed`,children:(0,F.jsx)(dt,{className:`h-3.5 w-3.5`})})]},e.id)),(0,F.jsx)(`div`,{className:`my-1 border-t border-ink-800`}),(0,F.jsxs)(`button`,{role:`menuitem`,onClick:()=>{i(),s(!1)},className:`flex w-full items-center gap-1 px-3 py-1.5 text-left text-ink-400 hover:text-ink-200`,children:[(0,F.jsx)(ft,{className:`h-3.5 w-3.5`}),`open`]})]})]})]})}function Ct(){let[e,t]=(0,v.useState)(!1),n=(0,v.useRef)(!1);return{reload:(0,v.useCallback)(async()=>{if(!n.current){n.current=!0,t(!0);try{se.success(await O.reloadConfig())}catch(e){se.error(e instanceof Error?e.message:`could not reload the config`)}finally{n.current=!1,t(!1)}}},[]),pending:e}}function wt({repos:e,repo:t,setRepo:n,setPane:r,closeRepo:i,setPickerOpen:a,cloning:o,accent:s,next:c,cycle:l,draggingRepo:u,dragOverRepo:d,onRepoDragStart:f,onRepoDragMove:p,onRepoDragEnd:m}){let{reload:h,pending:g}=Ct();return(0,F.jsxs)(`header`,{className:`flex items-center gap-2 border-b border-ink-700 bg-ink-900 px-[12.8px] py-[8.8px]`,children:[(0,F.jsx)(ct,{className:`h-[22px] w-[22px] shrink-0`}),(0,F.jsx)(`span`,{className:`text-[16px] font-medium tracking-[0.04em] text-ink-50`,children:`nightcrow`}),(0,F.jsx)(`span`,{className:`hidden font-sans text-[10px] uppercase tracking-[0.18em] text-ink-400 sm:inline`,children:`web viewer`}),(0,F.jsx)(St,{className:`md:hidden`,repos:e,currentId:t,onSelect:e=>{n(e),r()},onCloseProject:i,onOpenPicker:()=>a(!0)}),(0,F.jsx)(`nav`,{className:`-my-[8.8px] hidden items-stretch self-stretch overflow-x-auto pl-1 md:flex`,children:e.map(a=>(0,F.jsxs)(`div`,{"data-repo-id":a.id,onPointerDown:e=>f(e,a.id),onPointerMove:p,onPointerUp:m,onPointerCancel:m,onLostPointerCapture:m,className:`flex items-center border-r border-ink-700 whitespace-nowrap ${e.length>1?`touch-none`:``} ${u===a.id?`opacity-60`:``} ${d===a.id?`bg-ink-800 ring-1 ring-inset ring-accent`:``} ${a.id===t?`bg-ink-950 text-ink-50 shadow-[inset_0_2px_0_0_var(--color-accent)]`:`text-ink-400 hover:bg-ink-850 hover:text-ink-200`}`,title:a.display_path,children:[(0,F.jsx)(`button`,{onClick:()=>{n(a.id),r()},className:`self-stretch pl-3 pr-1`,children:a.name}),(0,F.jsx)(`button`,{onClick:e=>{e.stopPropagation(),i(a.id)},"data-tab-close":!0,title:`Close project`,"aria-label":`close ${a.name}`,className:`mr-1 flex h-5 w-5 items-center justify-center rounded-sm text-ink-400 hover:bg-ink-700 hover:text-removed`,children:(0,F.jsx)(dt,{className:`h-3.5 w-3.5`})})]},a.id))}),(0,F.jsxs)(`button`,{onClick:()=>a(!0),title:`Open a project`,className:`hidden shrink-0 items-center gap-1 rounded-sm px-2 py-0.5 text-ink-400 hover:text-ink-200 md:inline-flex`,children:[(0,F.jsx)(ft,{className:`h-3.5 w-3.5`}),`open`]}),o&&(0,F.jsxs)(`span`,{role:`status`,title:`A clone is running on the server`,className:`flex shrink-0 items-center gap-1.5 px-2 py-0.5 text-ink-400`,children:[(0,F.jsx)(`span`,{"aria-hidden":`true`,className:`h-1.5 w-1.5 animate-pulse rounded-full bg-accent`}),`Cloning…`]}),(0,F.jsx)(`button`,{onClick:l,title:`Accent: ${s.name} (click for ${c.name})`,"aria-label":`accent colour: ${s.name}, click for ${c.name}`,className:`ml-auto flex h-6 w-6 shrink-0 items-center justify-center rounded-sm`,children:(0,F.jsx)(`span`,{"aria-hidden":`true`,className:`h-3 w-3 rounded-full bg-accent ring-1 ring-ink-600`})}),(0,F.jsx)(`button`,{onClick:h,disabled:g,title:`Reload config.toml on the server (does not reload this page)`,"aria-label":`reload the server config`,className:`ml-1 flex h-6 w-6 shrink-0 items-center justify-center rounded-sm text-ink-400 hover:bg-ink-700 hover:text-ink-200 disabled:cursor-progress disabled:text-ink-500 disabled:hover:bg-transparent`,children:(0,F.jsx)(xt,{className:`h-3.5 w-3.5 ${g?`animate-spin`:``}`})}),(0,F.jsx)(`a`,{href:`/logout`,title:`Sign out`,"aria-label":`sign out`,className:`ml-1 flex h-6 w-6 shrink-0 items-center justify-center rounded-sm text-ink-400 hover:bg-ink-700 hover:text-ink-200`,children:(0,F.jsx)(bt,{className:`h-3.5 w-3.5`})})]})}function Tt(){let e=new Map;return{start(t){let n=(e.get(t)??0)+1;return e.set(t,n),n},isCurrent(t,n){return e.get(t)===n}}}var Et={children:{},expanded:new Set};function Dt(e,t,n){return{...e,children:{...e.children,[t]:n}}}function Ot(e,t){let n=new Set(e.expanded);return n.delete(t)||n.add(t),{...e,expanded:n}}function kt(e,t){let n=new Set(e.expanded);return t.forEach(e=>n.add(e)),{...e,expanded:n}}function At(e){let t=[],n=``;for(let r of e.split(`/`))n=n?`${n}/${r}`:r,t.push(n);return t}var jt=180,Mt={items:[],truncated:!1};function Nt({repo:e,authed:t,tab:n,filter:r,filterOpen:i,handle:a}){let[o,s]=(0,v.useState)(Et),[c,l]=(0,v.useState)(Mt),[u,d]=(0,v.useState)(!1),[f]=(0,v.useState)(Tt);(0,v.useEffect)(()=>{if(!e||!t||n!==`tree`||!i||!r){l(Mt),d(!1);return}d(!0);let o=!0,s=setTimeout(()=>{O.treeSearch(e,r).then(e=>{o&&l({items:e.matches,truncated:e.truncated})}).catch(e=>{o&&a(e)}).finally(()=>{o&&d(!1)})},jt);return()=>{o=!1,clearTimeout(s)}},[e,t,n,r,i,a]);let p=(0,v.useCallback)(t=>{if(!e)return;let n=f.start(t);O.tree(e,t).then(e=>{f.isCurrent(t,n)&&s(n=>Dt(n,t,e.entries))}).catch(e=>{f.isCurrent(t,n)&&a(e)})},[e,a,f]);(0,v.useEffect)(()=>{!e||!t||n!==`tree`||p(``)},[e,t,n,p]);let m=(0,v.useCallback)(e=>{let t=!o.expanded.has(e);s(t=>Ot(t,e)),t&&!(e in o.children)&&p(e)},[o,p]),h=(0,v.useCallback)(e=>{let t=At(e);s(e=>kt(e,t)),t.forEach(e=>{e in o.children||p(e)})},[o,p]);return{treeChildren:o.children,treeExpanded:o.expanded,treeMatches:c.items,treeTruncated:c.truncated,treeSearchLoading:u,loadTreeChildren:p,toggleTreeDir:m,revealTreeDir:h}}function Pt(e,t){let n=[],r=(i,a)=>{for(let o of e[i]??[]){let e=i?`${i}/${o.name}`:o.name;n.push({path:e,name:o.name,is_dir:o.is_dir,depth:a}),o.is_dir&&t.has(e)&&r(e,a+1)}};return r(``,0),n}function Ft({path:e,from:t,className:n}){return(0,F.jsx)(`span`,{className:`whitespace-nowrap ${n??``}`,title:t?`${t} → ${e}`:e,children:t?`${t} → ${e}`:e})}function It(e){let t=Math.max(0,Math.floor(Date.now()/1e3-e));return t<60?`${t}s`:t<3600?`${Math.floor(t/60)}m`:t<86400?`${Math.floor(t/3600)}h`:t<86400*30?`${Math.floor(t/86400)}d`:t<86400*365?`${Math.floor(t/(86400*30))}mo`:`${Math.floor(t/(86400*365))}y`}function Lt(e){return e===`+`?`bg-added/10`:e===`-`?`bg-removed/10`:``}function Rt(e){return e===`?`?`text-ink-400`:e===`D`?`text-removed`:e===`A`?`text-added`:`text-accent`}function zt({status:e,files:t,now:n,hotWindowMs:r,openDiff:i}){return e===null?(0,F.jsx)(`li`,{className:`px-3 py-2 text-ink-400`,children:`Loading…`}):(0,F.jsxs)(F.Fragment,{children:[t.map(e=>(0,F.jsx)(`li`,{children:(0,F.jsxs)(`button`,{onClick:()=>i(e.path),className:`flex w-max min-w-full gap-2 px-3 py-0.5 text-left hover:bg-ink-850`,children:[(0,F.jsxs)(`span`,{className:`shrink-0`,children:[(0,F.jsx)(`span`,{className:Rt(e.index),children:e.index===` `?` `:e.index}),(0,F.jsx)(`span`,{className:Rt(e.worktree),children:e.worktree===` `?` `:e.worktree})]}),(0,F.jsx)(Ft,{path:e.path,from:e.old_path,className:ue[P(e.mtime,n,r)]})]})},e.path)),e.truncated&&(0,F.jsxs)(`li`,{className:`px-3 py-1 text-accent`,children:[`Showing the first `,e.files.length,` changed files.`]})]})}function Bt({visibleCommits:e,commits:t,aheadOids:n,commitDrillDown:r,visibleCommitFiles:i,logDone:a,logStalled:o,logPagingPaused:s,setLogStalled:c,logSentinelRef:l,openCommitFiles:u,openCommit:d,openCommitFileDiff:f,setCommitDrillDown:p,setPaneEmpty:m,bumpPaneRequest:h}){return(0,F.jsxs)(F.Fragment,{children:[!r&&e.map(e=>(0,F.jsx)(`li`,{children:(0,F.jsxs)(`button`,{onClick:()=>void u(e),title:`${e.author} · ${e.summary}`,className:`flex w-max min-w-full items-baseline gap-2 px-3 py-0.5 text-left hover:bg-ink-850`,children:[(0,F.jsx)(`span`,{className:`w-2 shrink-0 text-added`,children:n.has(e.oid)?`↑`:``}),(0,F.jsx)(`span`,{className:`shrink-0 text-accent`,children:e.short_id}),(0,F.jsx)(`span`,{className:`w-10 shrink-0 text-right text-ink-400`,children:It(e.time)}),(0,F.jsx)(`span`,{className:`max-w-[6rem] shrink-0 truncate text-ink-400`,children:e.author}),(0,F.jsx)(`span`,{className:`whitespace-nowrap`,children:e.summary})]})},e.oid)),!r&&!a&&!o&&!s&&(0,F.jsx)(`li`,{ref:l,className:`px-3 py-1 text-ink-400`,"aria-hidden":`true`,children:`loading…`}),!r&&!a&&!o&&s&&(0,F.jsxs)(`li`,{className:`px-3 py-1 text-ink-400`,children:[`filtering `,t.length,` loaded commits — clear the filter to load more`]}),!r&&o&&(0,F.jsx)(`li`,{className:`px-3 py-1`,children:(0,F.jsx)(`button`,{onClick:()=>c(!1),className:`text-ink-400 hover:text-accent`,children:`could not load more — retry`})}),r&&(0,F.jsxs)(F.Fragment,{children:[(0,F.jsxs)(`li`,{className:`sticky top-0 z-10 flex w-max min-w-full items-center gap-1 bg-ink-900 px-2 py-1 text-ink-400`,children:[(0,F.jsx)(`button`,{onClick:()=>{h(),p(null),m()},className:`rounded-sm px-1 hover:text-accent`,title:`Back to commit log`,children:`< log`}),(0,F.jsx)(`span`,{className:`text-ink-600`,children:`·`}),(0,F.jsx)(`span`,{className:`shrink-0 text-accent`,children:r.commit.short_id}),(0,F.jsx)(`button`,{onClick:()=>d(r.commit.oid),className:`rounded-sm px-1 hover:text-accent`,title:`Show the complete commit diff`,children:`all changes`})]}),i.map(e=>(0,F.jsx)(`li`,{children:(0,F.jsxs)(`button`,{onClick:()=>f(r.commit.oid,e.path),className:`flex w-max min-w-full gap-2 px-3 py-0.5 text-left hover:bg-ink-850`,children:[(0,F.jsx)(`span`,{className:Rt(e.index),children:e.index}),(0,F.jsx)(Ft,{path:e.path,from:e.old_path})]})},e.path)),r.files.length===0&&(0,F.jsx)(`li`,{className:`px-3 py-2 text-ink-400`,children:`No changed files.`}),r.files.length>0&&i.length===0&&(0,F.jsx)(`li`,{className:`px-3 py-2 text-ink-400`,children:`No matching files.`}),r.truncated&&(0,F.jsxs)(`li`,{className:`px-3 py-1 text-accent`,children:[`Showing the first `,r.files.length,` files.`]})]})]})}function Vt({treeSearching:e,treeMatches:t,treeTruncated:n,treeSearchLoading:r,treeRows:i,treeExpanded:a,openFile:o,revealTreeDir:s,toggleTreeDir:c}){return e?(0,F.jsxs)(F.Fragment,{children:[t.map(e=>(0,F.jsx)(`li`,{children:(0,F.jsx)(`button`,{onClick:()=>{e.is_dir?s(e.path):o(e.path)},title:e.path,className:`w-max min-w-full whitespace-nowrap px-3 py-0.5 text-left hover:bg-ink-850`,children:e.is_dir?(0,F.jsxs)(`span`,{className:`text-accent`,children:[e.path,`/`]}):e.path})},e.path)),t.length===0&&(0,F.jsx)(`li`,{className:`px-3 py-0.5 text-ink-400`,children:r?`searching…`:`no matches`}),n&&(0,F.jsxs)(`li`,{className:`px-3 py-0.5 text-ink-400`,children:[`showing the first `,t.length,` matches`]})]}):(0,F.jsx)(F.Fragment,{children:i.map(e=>(0,F.jsx)(`li`,{children:(0,F.jsxs)(`button`,{onClick:()=>e.is_dir?c(e.path):o(e.path),title:e.path,style:{paddingLeft:`${e.depth*.75+.5}rem`},className:`flex w-max min-w-full items-center gap-1 py-0.5 pr-3 text-left hover:bg-ink-850`,children:[e.is_dir?(0,F.jsx)(ut,{open:a.has(e.path)}):(0,F.jsx)(`span`,{className:`h-3.5 w-3.5 shrink-0`}),(0,F.jsx)(`span`,{className:`whitespace-nowrap ${e.is_dir?`text-accent`:``}`,children:e.is_dir?`${e.name}/`:e.name})]})},e.path))})}function Ht(e){let{tab:t,setTab:n,filter:r,setFilter:i,filterOpen:a,setFilterOpen:o,status:s,files:c,now:l,hotWindowMs:u,setPane:d,openDiff:f,openFile:p,openCommit:m,openCommitFileDiff:h,openCommitFiles:g,repo:_,authed:v,handle:y,sidebarRef:b,draggingSidebar:x,onSidebarDragStart:S,onSidebarDragMove:C,onSidebarDragEnd:w,onSidebarDragCancel:ee,filesMax:T,bumpPaneRequest:E,commits:D,logDone:te,logStalled:O,setLogStalled:ne,commitDrillDown:k,setCommitDrillDown:re,resetLog:ie,logSentinelRef:ae,visibleCommits:A,logPagingPaused:j,aheadOids:oe,visibleCommitFiles:se,mobileView:ce}=e,M=Nt({repo:_,authed:v,tab:t,filter:r,filterOpen:a,handle:y}),N=t===`tree`&&a&&r!==``,P=Pt(M.treeChildren,M.treeExpanded);return(0,F.jsxs)(`section`,{ref:b,className:`relative min-h-0 flex-col overflow-hidden ${ce===`files`?`flex`:`hidden md:flex`} ${T?`md:flex`:`border-ink-700 md:border-r`}`,children:[!T&&(0,F.jsx)(`div`,{role:`separator`,"aria-orientation":`vertical`,"aria-label":`Resize the file sidebar (double-click to reset)`,title:`Drag to resize · double-click to reset`,onPointerDown:S,onPointerMove:C,onPointerUp:w,onPointerCancel:ee,onLostPointerCapture:w,className:`absolute -right-px top-0 z-10 hidden h-full w-1.5 cursor-col-resize touch-none md:block ${x?`bg-accent`:`hover:bg-accent`}`}),(0,F.jsxs)(`div`,{className:`flex shrink-0 items-stretch border-b border-ink-700 px-2`,children:[[`status`,`log`,`tree`].map(e=>(0,F.jsx)(`button`,{onClick:()=>{e!==t&&(E(),t===`log`&&(re(null),ie()),n(e),d({kind:`empty`}))},"aria-current":e===t?`page`:void 0,className:`-mb-px border-b-2 px-2 py-1 ${e===t?`border-accent text-ink-50`:`border-transparent text-ink-400 hover:text-ink-200`}`,children:e},e)),(0,F.jsx)(`button`,{onClick:()=>{a&&i(``),o(e=>!e)},"aria-pressed":a,title:a?`Hide the filter`:`Filter the list`,"aria-label":a?`Hide the filter`:`Filter the list`,className:`my-1 ml-auto flex shrink-0 items-center rounded-sm px-1.5 hover:text-accent ${a?`text-ink-50`:`text-ink-400`}`,children:(0,F.jsx)(ht,{})})]}),a&&(0,F.jsx)(`input`,{value:r,onChange:e=>i(e.target.value),placeholder:`filter…`,autoFocus:!0,className:`mx-2 mb-1 shrink-0 rounded-sm bg-ink-850 px-2 py-1 outline-none placeholder:text-ink-400 focus:ring-1 focus:ring-accent`}),(0,F.jsxs)(`ul`,{className:`min-h-0 flex-1 overflow-auto`,children:[t===`status`&&(0,F.jsx)(zt,{status:s,files:c,now:l,hotWindowMs:u,openDiff:f}),t===`log`&&(0,F.jsx)(Bt,{visibleCommits:A,commits:D,aheadOids:oe,commitDrillDown:k,visibleCommitFiles:se,logDone:te,logStalled:O,logPagingPaused:j,setLogStalled:ne,logSentinelRef:ae,openCommitFiles:g,openCommit:m,openCommitFileDiff:h,setCommitDrillDown:re,setPaneEmpty:()=>d({kind:`empty`}),bumpPaneRequest:E}),t===`tree`&&(0,F.jsx)(Vt,{treeSearching:N,treeMatches:M.treeMatches,treeTruncated:M.treeTruncated,treeSearchLoading:M.treeSearchLoading,treeRows:P,treeExpanded:M.treeExpanded,openFile:p,revealTreeDir:M.revealTreeDir,toggleTreeDir:M.toggleTreeDir})]})]})}function Ut(e){let t=[],n=[],r=[],i=()=>{let e=Math.max(n.length,r.length);for(let i=0;i{t(e=>e===`split`?`unified`:`split`)},[])}}var Gt=[`.md`,`.markdown`],Kt=[`.html`,`.htm`];function qt(e){let t=e.toLowerCase();return Gt.some(e=>t.endsWith(e))}function Jt(e){let t=e.toLowerCase();return Kt.some(e=>t.endsWith(e))}function Yt(e){return qt(e)||Jt(e)}function Xt(e){return e.map(e=>e.map(e=>e.t).join(``)).join(` -`)}var Zt=3;function Qt(e){let t=e<1?1:String(Math.floor(e)).length;return Math.max(t,Zt)}function $t(e){let t=0;for(let n of e)for(let e of n.lines)t=Math.max(t,e.old_lineno??0,e.new_lineno??0);return Qt(t)}function en({nos:e,digits:t,tint:n=``}){return(0,F.jsx)(`span`,{className:`sticky left-0 shrink-0 select-none bg-ink-950`,children:(0,F.jsx)(`span`,{className:`flex gap-[1ch] px-[1ch] text-ink-400 ${n}`,children:e.map((e,n)=>(0,F.jsx)(`span`,{className:`text-right`,style:{width:`${t}ch`},children:e??``},n))})})}function tn({line:e}){return(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(`span`,{className:`text-ink-400 select-none`,children:e.kind}),e.spans.map((e,t)=>(0,F.jsx)(`span`,{style:{color:e.c},children:e.t},t))]})}function nn({line:e,digits:t,side:n}){if(e===null)return(0,F.jsxs)(`div`,{className:`flex bg-ink-900/40`,children:[(0,F.jsx)(en,{nos:[void 0],digits:t,tint:`bg-ink-900/40`}),(0,F.jsx)(`span`,{className:`whitespace-pre pr-3`,children:` `})]});let r=Lt(e.kind);return(0,F.jsxs)(`div`,{className:`flex ${r}`,children:[(0,F.jsx)(en,{nos:[n===`old`?e.old_lineno:e.new_lineno],digits:t,tint:r}),(0,F.jsx)(`span`,{className:`whitespace-pre pr-3`,children:(0,F.jsx)(tn,{line:e})})]})}function rn({cells:e,digits:t,side:n,border:r}){return(0,F.jsx)(`div`,{className:`min-w-0 flex-none overflow-x-auto md:flex-1 md:basis-1/2 ${r?`border-t border-ink-800 md:border-t-0 md:border-l`:``}`,children:(0,F.jsx)(`div`,{className:`w-max min-w-full`,children:e.map((e,r)=>(0,F.jsx)(nn,{line:e,digits:t,side:n},r))})})}function an({lines:e,digits:t}){let n=Ut(e);return(0,F.jsxs)(`div`,{className:`flex flex-col md:flex-row`,children:[(0,F.jsx)(rn,{cells:n.map(e=>e.left),digits:t,side:`old`,border:!1}),(0,F.jsx)(rn,{cells:n.map(e=>e.right),digits:t,side:`new`,border:!0})]})}function on({diff:e,split:t}){let n=$t(e.hunks);return(0,F.jsxs)(`div`,{className:`p-1`,children:[e.hunks.length===0&&(0,F.jsx)(`p`,{className:`p-3 text-ink-400`,children:`No changes.`}),e.hunks.map((e,r)=>{let i=(0,F.jsxs)(`div`,{className:`bg-ink-850 px-3 py-0.5 text-ink-400`,children:[e.file_path?`${e.file_path} `:``,e.header]});return(0,F.jsx)(`div`,{className:`mb-2`,children:t?(0,F.jsxs)(F.Fragment,{children:[i,(0,F.jsx)(an,{lines:e.lines,digits:n})]}):(0,F.jsxs)(`div`,{className:`w-max min-w-full`,children:[i,e.lines.map((e,t)=>{let r=Lt(e.kind);return(0,F.jsxs)(`div`,{className:`flex ${r}`,children:[(0,F.jsx)(en,{nos:[e.old_lineno,e.new_lineno],digits:n,tint:r}),(0,F.jsx)(`span`,{className:`whitespace-pre pr-3`,children:(0,F.jsx)(tn,{line:e})})]},t)})]})},r)}),e.truncated&&(0,F.jsx)(`p`,{className:`p-3 text-accent`,children:`Diff truncated — it exceeded the server's size ceiling.`})]})}var sn=`modulepreload`,cn=function(e,t){return new URL(e,t).href},ln={},un=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,import.meta.url).href}r=o(t.map(t=>{if(t=cn(t,n),t=s(t),t in ln)return;ln[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:sn,r||(i.as=`script`),i.crossOrigin=``,i.href=t,a&&i.setAttribute(`nonce`,a),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})},dn=(0,v.lazy)(()=>un(()=>import(`./Markdown-DgtyrrzA.js`).then(e=>({default:e.MarkdownView})),__vite__mapDeps([0,1]),import.meta.url)),fn=(0,v.lazy)(()=>un(()=>import(`./Html-S4u0H_mA.js`).then(e=>({default:e.HtmlView})),[],import.meta.url));function pn({lines:e}){let t=Qt(e.length);return(0,F.jsx)(`pre`,{className:`w-max min-w-full py-2 text-ink-200`,children:e.map((e,n)=>(0,F.jsxs)(`div`,{className:`flex`,children:[(0,F.jsx)(en,{nos:[n+1],digits:t}),(0,F.jsx)(`span`,{className:`whitespace-pre pr-3`,children:e.length===0?` `:e.map((e,t)=>(0,F.jsx)(`span`,{style:{color:e.c},children:e.t},t))})]},n))})}function mn({pane:e,previewRendered:t,setPreviewRendered:n,filesMax:r,setMaximized:i,status:a,className:o=``}){let s=Wt();return(0,F.jsxs)(`section`,{className:`min-h-0 min-w-0 flex-col ${o}`,children:[(0,F.jsxs)(`div`,{className:`flex shrink-0 items-center gap-2 bg-ink-850 px-3 py-0.5 text-ink-400`,children:[e.kind===`file`&&(0,F.jsx)(Ft,{path:e.value.path}),(0,F.jsxs)(`div`,{className:`ml-auto flex shrink-0 items-center gap-1`,children:[e.kind===`diff`&&(0,F.jsx)(`button`,{onClick:s.toggle,"aria-pressed":s.layout===`split`,title:s.layout===`split`?`Switch to unified diff`:`Switch to split diff`,"aria-label":s.layout===`split`?`Switch to unified diff`:`Switch to split diff`,className:`flex shrink-0 items-center rounded-sm px-1.5 py-0.5 hover:text-accent ${s.layout===`split`?`text-accent`:``}`,children:(0,F.jsx)(pt,{})}),e.kind===`file`&&Yt(e.value.path)&&(0,F.jsx)(`button`,{onClick:()=>n(e=>!e),"aria-pressed":t,title:t?`Show raw source`:`Show the rendered page`,"aria-label":t?`Show raw source`:`Show the rendered page`,className:`flex shrink-0 items-center rounded-sm px-1.5 py-0.5 hover:text-accent ${t?`text-accent`:``}`,children:(0,F.jsx)(mt,{})}),(0,F.jsx)(`button`,{onClick:()=>i(r?`none`:`files`),"aria-pressed":r,title:r?`Restore the layout`:`Maximize the file pane`,"aria-label":r?`Restore the layout`:`Maximize the file pane`,className:`hidden shrink-0 items-center rounded-sm px-1.5 py-0.5 hover:text-accent md:flex`,children:(0,F.jsx)(lt,{maximized:r})})]})]}),(0,F.jsxs)(`div`,{className:`min-h-0 flex-1 overflow-auto`,children:[e.kind===`empty`&&(0,F.jsx)(`p`,{className:`p-4 text-ink-400`,children:a===null?`Loading…`:`Select a file or commit.`}),e.kind===`file`&&(0,F.jsxs)(F.Fragment,{children:[Yt(e.value.path)&&t?(0,F.jsx)(v.Suspense,{fallback:(0,F.jsx)(`p`,{className:`p-4 text-ink-400`,children:`Rendering…`}),children:Jt(e.value.path)?(0,F.jsx)(fn,{source:Xt(e.value.lines)}):(0,F.jsx)(dn,{source:Xt(e.value.lines)})}):(0,F.jsx)(pn,{lines:e.value.lines}),e.value.truncated&&(0,F.jsx)(`p`,{className:`p-3 text-accent`,children:`File truncated — it exceeded the server's size ceiling.`})]}),e.kind===`diff`&&(0,F.jsx)(on,{diff:e.value,split:s.layout===`split`})]})]})}var hn=(0,v.lazy)(()=>un(()=>import(`./Terminal-DmlNBLUk.js`).then(e=>({default:e.TerminalPanel})),[],import.meta.url));function gn(e){let{repo:t,current:n,status:r,files:i,now:a,hotWindowMs:o,pane:s,setPane:c,tab:l,setTab:u,filter:d,setFilter:f,filterOpen:p,setFilterOpen:m,openDiff:h,openFile:g,openCommit:_,openCommitFileDiff:y,openCommitFiles:b,authed:x,handle:S,sidebarWidth:C,sidebarRef:w,draggingSidebar:ee,onSidebarDragStart:T,onSidebarDragMove:E,onSidebarDragEnd:D,onSidebarDragCancel:te,upperRef:O,lowerRef:ne,draggingUpper:k,onUpperDragStart:re,onUpperDragMove:ie,onUpperDragEnd:ae,onUpperDragCancel:A,filesMax:j,bumpPaneRequest:oe,commits:se,logDone:ce,logStalled:M,setLogStalled:N,commitDrillDown:P,setCommitDrillDown:le,resetLog:ue,logSentinelRef:de,visibleCommits:fe,logPagingPaused:pe,aheadOids:me,visibleCommitFiles:he,previewRendered:ge,setPreviewRendered:_e,maximized:ve,setMaximized:be,mobileView:xe,setMobileView:Se}=e;return(0,v.useEffect)(()=>te,[t,te]),(0,v.useEffect)(()=>A,[A]),(0,F.jsxs)(F.Fragment,{children:[ee&&(0,F.jsx)(`div`,{className:`fixed inset-0 z-50 cursor-col-resize`}),k&&(0,F.jsx)(`div`,{className:`fixed inset-0 z-50 cursor-row-resize`}),(0,F.jsxs)(`main`,{ref:O,className:`grid min-h-0 grid-cols-1 md:grid-cols-[var(--nc-sidebar)_1fr] ${xe===`terminal`?`hidden md:grid`:``} ${ee||k?`select-none`:``}`,style:{"--nc-sidebar":j?`0px`:`min(${C}px, ${ye*100}vw)`},children:[(0,F.jsx)(Ht,{tab:l,setTab:u,filter:d,setFilter:f,filterOpen:p,setFilterOpen:m,status:r,files:i,now:a,hotWindowMs:o,setPane:c,openDiff:h,openFile:g,openCommit:_,openCommitFileDiff:y,openCommitFiles:b,repo:t,authed:x,handle:S,sidebarRef:w,draggingSidebar:ee,onSidebarDragStart:T,onSidebarDragMove:E,onSidebarDragEnd:D,onSidebarDragCancel:te,filesMax:j,bumpPaneRequest:oe,commits:se,logDone:ce,logStalled:M,setLogStalled:N,commitDrillDown:P,setCommitDrillDown:le,resetLog:ue,logSentinelRef:de,visibleCommits:fe,logPagingPaused:pe,aheadOids:me,visibleCommitFiles:he,mobileView:xe},t),(0,F.jsx)(mn,{pane:s,previewRendered:ge,setPreviewRendered:_e,filesMax:j,setMaximized:be,status:r,className:xe===`diff`?`flex`:`hidden md:flex`})]}),(0,F.jsx)(v.Suspense,{fallback:null,children:(0,F.jsx)(hn,{repo:t,maximized:ve===`terminal`,onToggleMaximized:()=>be(e=>e===`terminal`?`none`:`terminal`),className:xe===`terminal`?`flex`:`hidden md:flex`,sectionRef:ne,showDivider:ve===`none`,draggingUpper:k,onUpperDragStart:re,onUpperDragMove:ie,onUpperDragEnd:ae,onUpperDragCancel:A})}),(0,F.jsx)(`nav`,{"aria-label":`Switch view`,className:`flex shrink-0 items-stretch border-t border-ink-700 bg-ink-900 md:hidden`,children:[[`files`,`Files`,gt],[`diff`,`Diff`,_t],[`terminal`,`Terminal`,vt]].map(([e,t,n])=>(0,F.jsxs)(`button`,{onClick:()=>Se(e),"aria-current":xe===e?`page`:void 0,className:`flex min-h-11 flex-1 flex-col items-center justify-center gap-0.5 py-1 text-[11px] ${xe===e?`text-accent shadow-[inset_0_2px_0_0_var(--color-accent)]`:`text-ink-400`}`,children:[(0,F.jsx)(n,{className:`h-5 w-5`}),t]},e))}),(0,F.jsxs)(`footer`,{className:`flex shrink-0 items-center gap-3 border-t border-ink-700 bg-ink-900 px-3 py-1 text-ink-400`,children:[(0,F.jsx)(`span`,{className:`truncate`,children:n?.display_path}),r?.branch&&(0,F.jsx)(`span`,{className:`text-accent`,children:r.branch}),r?.tracking&&(0,F.jsxs)(`span`,{children:[`↑`,r.tracking.ahead,` ↓`,r.tracking.behind]}),(0,F.jsx)(`span`,{className:`ml-auto`,children:r?(0,F.jsx)(`span`,{className:`text-added`,children:`● live`}):`connecting…`})]})]})}function _n({onClose:e,onOpened:t,canClone:n,cloning:r,onClone:i}){let[a,o]=(0,v.useState)(null),[s,c]=(0,v.useState)(null),[l,u]=(0,v.useState)(null),[d,f]=(0,v.useState)(!1),[p,m]=(0,v.useState)(``),[h,g]=(0,v.useState)(!1),[_,y]=(0,v.useState)(``),[b,x]=(0,v.useState)(0);(0,v.useEffect)(()=>{let e=!1;return O.browse(a??void 0).then(t=>{e||(c(t),u(null))}).catch(t=>{e||u(t instanceof Error?t.message:`could not browse`)}),()=>{e=!0}},[a,b]);let S=e=>o(`${s.path.replace(/\/$/,``)}/${e}`),C=async()=>{if(s){f(!0);try{t(await O.open(s.path))}catch(e){se.error(e instanceof Error?e.message:`could not open`),f(!1)}}},w=async()=>{if(!s)return;let e=p.trim();if(e){g(!0);try{await O.mkdir(s.path,e),m(``),x(e=>e+1)}catch(e){se.error(e instanceof Error?e.message:`could not create folder`)}finally{g(!1)}}};return(0,F.jsx)(`div`,{className:`fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4`,onClick:e,children:(0,F.jsxs)(`div`,{className:`flex max-h-[80vh] w-[34rem] max-w-full flex-col rounded-md border border-ink-700 bg-ink-900`,onClick:e=>e.stopPropagation(),children:[(0,F.jsxs)(`div`,{className:`flex shrink-0 items-center gap-2 border-b border-ink-700 px-3 py-2`,children:[(0,F.jsx)(`span`,{className:`font-medium text-ink-50`,children:`Open a project`}),(0,F.jsx)(`button`,{onClick:e,"aria-label":`close`,className:`ml-auto flex h-6 w-6 items-center justify-center rounded-sm text-ink-400 hover:text-ink-200`,children:(0,F.jsx)(dt,{})})]}),(0,F.jsx)(`div`,{className:`shrink-0 truncate border-b border-ink-700 px-3 py-1.5 text-ink-400`,children:s?.path??`…`}),(0,F.jsxs)(`ul`,{className:`h-72 min-h-0 overflow-y-auto`,children:[s?.parent&&(0,F.jsx)(`li`,{children:(0,F.jsx)(`button`,{onClick:()=>o(s.parent),className:`w-full px-3 py-1 text-left text-ink-400 hover:bg-ink-850`,children:`../`})}),s?.entries.map(e=>(0,F.jsx)(`li`,{children:(0,F.jsxs)(`button`,{onClick:()=>S(e.name),className:`flex w-full items-center gap-2 px-3 py-1 text-left hover:bg-ink-850`,children:[(0,F.jsxs)(`span`,{className:`truncate text-accent`,children:[e.name,`/`]}),e.is_repo&&(0,F.jsx)(`span`,{className:`rounded-sm bg-ink-700 px-1 text-[0.65rem] text-ink-200`,children:`git`})]})},e.name)),s&&s.entries.length===0&&(0,F.jsx)(`li`,{className:`px-3 py-1 text-ink-400`,children:`No sub-folders.`})]}),l&&(0,F.jsx)(`p`,{className:`shrink-0 px-3 py-1 text-removed`,children:l}),(0,F.jsxs)(`div`,{className:`flex shrink-0 items-center gap-2 border-t border-ink-700 px-3 py-2`,children:[(0,F.jsx)(`input`,{value:p,onChange:e=>m(e.target.value),onKeyDown:e=>{e.key===`Enter`&&w()},placeholder:`New folder name`,"aria-label":`new folder name`,className:`min-w-0 flex-1 rounded-sm border border-ink-700 bg-ink-950 px-2 py-1 text-ink-50 placeholder:text-ink-400 focus:border-ink-600 focus:outline-none`}),(0,F.jsx)(`button`,{onClick:w,disabled:!s||!p.trim()||h,className:`shrink-0 rounded-sm border border-ink-700 px-2 py-1 text-ink-200 hover:bg-ink-850 disabled:opacity-50`,children:h?`Creating…`:`Create`})]}),(0,F.jsxs)(`div`,{className:`flex shrink-0 items-center gap-2 border-t border-ink-700 px-3 py-2`,children:[(0,F.jsx)(`input`,{value:_,onChange:e=>y(e.target.value),onKeyDown:e=>{e.key===`Enter`&&s&&i(s.path,_)},disabled:!n,placeholder:n?`Clone a git URL here`:`git is not installed on the server`,"aria-label":`git URL to clone`,spellCheck:!1,autoCapitalize:`none`,autoCorrect:`off`,className:`min-w-0 flex-1 rounded-sm border border-ink-700 bg-ink-950 px-2 py-1 text-ink-50 placeholder:text-ink-400 focus:border-ink-600 focus:outline-none disabled:opacity-50`}),(0,F.jsx)(`button`,{onClick:()=>s&&i(s.path,_),disabled:!n||!s||!_.trim()||r,title:n?void 0:`the server has no git on its PATH`,className:`shrink-0 rounded-sm border border-ink-700 px-2 py-1 text-ink-200 hover:bg-ink-850 disabled:opacity-50`,children:r?`Cloning…`:`Clone`})]}),(0,F.jsxs)(`div`,{className:`flex shrink-0 items-center gap-2 border-t border-ink-700 px-3 py-2`,children:[(0,F.jsx)(`span`,{className:`truncate text-ink-400`,children:s?s.path:``}),(0,F.jsx)(`button`,{onClick:C,disabled:!s||d,className:`ml-auto shrink-0 rounded-md bg-ink-50 px-3 py-1 font-semibold text-ink-950 hover:bg-white disabled:opacity-50`,children:d?`Opening…`:`Open`})]})]})})}function vn(){return(0,F.jsx)(`div`,{className:`flex h-full items-center justify-center p-6`,children:(0,F.jsxs)(`div`,{className:`flex flex-col items-center gap-3 text-ink-400`,children:[(0,F.jsx)(ct,{className:`h-12 w-12 animate-pulse`}),(0,F.jsx)(`span`,{className:`text-[0.72rem] tracking-[0.18em] uppercase`,children:`Loading…`})]})})}function yn({onSuccess:e}){let[t,n]=(0,v.useState)(``),[r,i]=(0,v.useState)(null),[a,o]=(0,v.useState)(!1);return(0,F.jsx)(`div`,{className:`flex h-full items-center justify-center p-6`,children:(0,F.jsxs)(`form`,{onSubmit:async n=>{n.preventDefault(),o(!0),i(null);try{await O.login(t),e()}catch(e){i(e instanceof Error?e.message:`login failed`)}finally{o(!1)}},className:`w-[17rem] max-w-[86vw]`,children:[(0,F.jsx)(ct,{className:`mx-auto mb-3 block h-10 w-10`}),(0,F.jsx)(`h1`,{className:`text-center text-lg font-medium tracking-wide text-ink-50`,children:`nightcrow`}),(0,F.jsx)(`p`,{className:`mt-1 mb-5 text-center text-[0.62rem] tracking-[0.18em] text-ink-400 uppercase`,children:`web viewer`}),r&&(0,F.jsx)(`p`,{className:`mb-2.5 text-center text-removed`,children:r}),(0,F.jsx)(`input`,{type:`password`,autoFocus:!0,value:t,onChange:e=>n(e.target.value),placeholder:`password`,className:`mb-2 w-full rounded-md border border-ink-700 bg-ink-900 px-2.5 py-1.5 outline-none placeholder:text-ink-400 focus:border-accent focus:ring-[3px] focus:ring-accent/15`}),(0,F.jsx)(`button`,{type:`submit`,disabled:a,className:`w-full rounded-md bg-ink-50 py-1.5 font-semibold text-ink-950 hover:bg-white disabled:opacity-50`,children:a?`Signing in…`:`Sign in`})]})})}function bn(e,t){return e?`grid-rows-[auto_minmax(0,1fr)_auto_auto] ${t===`terminal`?`md:grid-rows-[auto_minmax(0,0fr)_minmax(0,1fr)_auto]`:t===`files`?`md:grid-rows-[auto_minmax(0,1fr)_minmax(0,0fr)_auto]`:`md:grid-rows-[auto_minmax(0,var(--nc-upper))_minmax(0,var(--nc-lower))_auto]`}`:`grid-rows-[auto_1fr]`}function xn(){let[e,t]=(0,v.useState)(null),[n,r]=(0,v.useState)(`status`),[i,a]=(0,v.useState)(``),[o,s]=(0,v.useState)(!1),[c,l]=(0,v.useState)({kind:`empty`}),[u,d]=(0,v.useState)(`files`),f=(0,v.useRef)(0),p=(0,v.useCallback)(()=>{f.current+=1},[]),[m,h]=(0,v.useState)(!1),{accent:g,next:_,cycle:y,upperPct:b,maximizedPanelOf:S,setMaximizedFor:C,shell:w,guards:ee}=Be(),[T,E]=(0,v.useState)(!0),D=(0,v.useCallback)(e=>{if(x(e)){t(!1);return}se.error(e instanceof Error?e.message:`request failed`)},[]),te=$e(),{repos:O,setRepos:ne,repo:k,setRepo:re,hot:ie,clockSkewMs:ae,reposLoaded:A,canClone:j,orderWrites:oe,draggingRepo:ce,dragOverRepo:M,onRepoDragStart:N,onRepoDragMove:P,onRepoDragEnd:le}=Ye({authed:e,setAuthed:t,handle:D,resumeTick:te,...ee}),ue=ie?.enabled?ie.window_secs*1e3:0;de(void 0,ue,ae??0);let{status:fe}=Xe({repo:k,authed:e,resumeTick:te,tab:n,pane:c,setPane:l,handle:D,paneRequestRef:f}),pe=de(fe?.files,ue,ae??0),me=S(k),he=(0,v.useCallback)(e=>C(k,e),[C,k]),{commits:ge,logDone:_e,logStalled:ve,setLogStalled:ye,commitDrillDown:be,setCommitDrillDown:xe,resetLog:Se,logSentinelRef:Ce,visibleCommits:we,logPagingPaused:Te}=Qe({repo:k,authed:e,tab:n,filter:i,handle:D}),{openDiff:Ee,openFile:De,openCommit:Oe,openCommitFileDiff:ke,openCommitFiles:Ae}=Ze({repo:k,handle:D,setPane:l,paneRequestRef:f,setCommitDrillDown:xe,setMobileView:d,setPreviewRendered:E}),{selectOpenedRepo:je,closeRepo:Me}=et({repos:O,setRepos:ne,setRepo:re,setPane:l,setTab:r,setPickerOpen:h,handle:D,orderWrites:oe}),{busy:Ne,start:Pe}=it(je,e===!0);if((0,v.useLayoutEffect)(()=>{p(),xe(null),l({kind:`empty`}),Se()},[k,Se,p,xe]),e===null)return(0,F.jsx)(vn,{});if(!e)return(0,F.jsx)(yn,{onSuccess:()=>t(!0)});if(!A)return(0,F.jsx)(vn,{});let Fe=O.find(e=>e.id===k),Ie=i.toLowerCase(),Le=(fe?.files??[]).filter(e=>e.path.toLowerCase().includes(Ie)),Re=(be?.files??[]).filter(e=>e.path.toLowerCase().includes(Ie)||e.old_path?.toLowerCase().includes(Ie)),ze=new Set(ge.slice(0,fe?.tracking?.ahead??0).map(e=>e.oid)),Ve=me===`files`;return(0,F.jsxs)(`div`,{className:`nc-fade grid h-full ${bn(k,me)}`,style:{"--nc-upper":`${b}fr`,"--nc-lower":`${100-b}fr`},children:[(0,F.jsx)(wt,{repos:O,repo:k,setRepo:e=>{re(e),l({kind:`empty`})},setPane:()=>l({kind:`empty`}),closeRepo:Me,setPickerOpen:h,cloning:Ne,accent:g,next:_,cycle:y,draggingRepo:ce,dragOverRepo:M,onRepoDragStart:N,onRepoDragMove:P,onRepoDragEnd:le}),k?(0,F.jsx)(gn,{repo:k,repos:O,current:Fe,status:fe,files:Le,now:pe,hotWindowMs:ue,pane:c,setPane:l,tab:n,setTab:r,filter:i,setFilter:a,filterOpen:o,setFilterOpen:s,openDiff:Ee,openFile:De,openCommit:Oe,openCommitFileDiff:ke,openCommitFiles:Ae,authed:e,handle:D,...w,filesMax:Ve,bumpPaneRequest:p,commits:ge,logDone:_e,logStalled:ve,setLogStalled:ye,commitDrillDown:be,setCommitDrillDown:xe,resetLog:Se,logSentinelRef:Ce,visibleCommits:we,logPagingPaused:Te,aheadOids:ze,visibleCommitFiles:Re,previewRendered:T,setPreviewRendered:E,maximized:me,setMaximized:he,mobileView:u,setMobileView:d}):(0,F.jsx)(`div`,{className:`flex items-center justify-center p-6 text-center text-ink-400`,children:(0,F.jsxs)(`span`,{children:[`No repository open. Click`,` `,(0,F.jsx)(`span`,{className:`text-ink-200`,children:`+ open`}),` above to add one.`]})}),m&&(0,F.jsx)(_n,{onClose:()=>h(!1),onOpened:je,canClone:j,cloning:Ne,onClone:Pe})]})}var Sn={error:7e3,info:5e3,success:5e3},Cn={error:`text-removed`,info:`text-accent`,success:`text-added`};function wn(){let[e,t]=(0,v.useState)([]);return(0,v.useEffect)(()=>A(t),[]),e.length===0?null:(0,F.jsx)(`div`,{className:`pointer-events-none fixed right-3 top-3 z-[60] flex w-80 max-w-[calc(100vw-1.5rem)] flex-col gap-2`,"aria-live":`polite`,children:e.map(e=>(0,F.jsx)(Tn,{toast:e},e.id))})}function Tn({toast:e}){let[t,n]=(0,v.useState)(!1);return(0,v.useEffect)(()=>{if(t)return;let n=setTimeout(()=>j(e.id),Sn[e.kind]);return()=>clearTimeout(n)},[e.id,e.kind,e.bump,t]),(0,F.jsxs)(`div`,{role:e.kind===`error`?`alert`:`status`,className:`nc-fade pointer-events-auto flex items-start gap-2 rounded-md border border-ink-700 bg-ink-850 px-3 py-2 text-xs shadow-lg`,onMouseEnter:()=>n(!0),onMouseLeave:()=>n(!1),children:[(0,F.jsx)(`span`,{className:`min-w-0 flex-1 break-words ${Cn[e.kind]}`,children:e.message}),(0,F.jsx)(`button`,{type:`button`,onClick:()=>j(e.id),"aria-label":`dismiss`,className:`mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-sm text-ink-400 hover:bg-ink-700 hover:text-ink-200`,children:(0,F.jsx)(dt,{className:`h-3 w-3`})})]})}(0,y.createRoot)(document.getElementById(`root`)).render((0,F.jsxs)(v.StrictMode,{children:[(0,F.jsx)(xn,{}),(0,F.jsx)(wn,{})]}));export{st as a,se as c,s as d,l as f,dt as i,d as l,lt as n,Ue as o,ft as r,He as s,yt as t,o as u}; \ No newline at end of file diff --git a/viewer-ui/dist/assets/index-C2FlDlUR.js b/viewer-ui/dist/assets/index-C2FlDlUR.js new file mode 100644 index 00000000..27918bf2 --- /dev/null +++ b/viewer-ui/dist/assets/index-C2FlDlUR.js @@ -0,0 +1,11 @@ +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./Markdown-DGCrBeLL.js","./Markdown-C8LL_u4z.css"])))=>i.map(i=>d[i]); +var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),s=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:`Module`}),r},c=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;li[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},l=(n,r,a)=>(a=n==null?{}:e(i(n)),c(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var u=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var S=Array.isArray;function C(){}var w={H:null,A:null,T:null,S:null},ee=Object.prototype.hasOwnProperty;function T(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function E(e,t){return T(e.type,t,e.props)}function D(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function te(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var O=/\/+/g;function k(e,t){return typeof e==`object`&&e&&e.key!=null?te(``+e.key):t.toString(36)}function A(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(C,C):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function ne(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,ne(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+k(e,0):a,S(o)?(i=``,c!=null&&(i=c.replace(O,`$&/`)+`/`),ne(o,r,i,``,function(e){return e})):o!=null&&(D(o)&&(o=E(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(O,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(S(e))for(var u=0;u{t.exports=u()})),f=o((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0>>1,a=e[r];if(0>>1;ri(c,n))li(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(li(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m)if(n(c)!==null)m=!0,S||(S=!0,D());else{var t=n(l);t!==null&&k(x,t.startTime-e)}}var S=!1,C=-1,w=5,ee=-1;function T(){return g?!0:!(e.unstable_now()-eet&&T());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&k(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}i=void 0}}finally{i?D():S=!1}}}var D;if(typeof y==`function`)D=function(){y(E)};else if(typeof MessageChannel<`u`){var te=new MessageChannel,O=te.port2;te.port1.onmessage=E,D=function(){O.postMessage(null)}}else D=function(){_(E,0)};function k(t,n){C=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(C),C=-1):h=!0,k(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,S||(S=!0,D()))),r},e.unstable_shouldYield=T,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),p=o(((e,t)=>{t.exports=f()})),m=o((e=>{var t=d();function n(e){var t=`https://react.dev/errors/`+e;if(1{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=m()})),g=o((e=>{var t=p(),n=d(),r=h();function i(e){var t=`https://react.dev/errors/`+e;if(1se||(e.current=oe[se],oe[se]=null,se--)}function F(e,t){se++,oe[se]=e.current,e.current=t}var ce=N(null),le=N(null),ue=N(null),de=N(null);function fe(e,t){switch(F(ue,t),F(le,e),F(ce,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Vd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Vd(t),e=Hd(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}P(ce),F(ce,e)}function pe(){P(ce),P(le),P(ue)}function me(e){e.memoizedState!==null&&F(de,e);var t=ce.current,n=Hd(t,e.type);t!==n&&(F(le,e),F(ce,n))}function he(e){le.current===e&&(P(ce),P(le)),de.current===e&&(P(de),Qf._currentValue=ae)}var ge,_e;function ve(e){if(ge===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);ge=t&&t[1]||``,_e=-1)`:-1i||c[r]!==l[i]){var u=` +`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(``)&&(u=u.replace(``,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{ye=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?ve(n):``}function xe(e,t){switch(e.tag){case 26:case 27:case 5:return ve(e.type);case 16:return ve(`Lazy`);case 13:return e.child!==t&&t!==null?ve(`Suspense Fallback`):ve(`Suspense`);case 19:return ve(`SuspenseList`);case 0:case 15:return be(e.type,!1);case 11:return be(e.type.render,!1);case 1:return be(e.type,!0);case 31:return ve(`Activity`);default:return``}}function Se(e){try{var t=``,n=null;do t+=xe(e,n),n=e,e=e.return;while(e);return t}catch(e){return` +Error generating stack: `+e.message+` +`+e.stack}}var Ce=Object.prototype.hasOwnProperty,we=t.unstable_scheduleCallback,Te=t.unstable_cancelCallback,Ee=t.unstable_shouldYield,De=t.unstable_requestPaint,Oe=t.unstable_now,ke=t.unstable_getCurrentPriorityLevel,Ae=t.unstable_ImmediatePriority,je=t.unstable_UserBlockingPriority,Me=t.unstable_NormalPriority,Ne=t.unstable_LowPriority,Pe=t.unstable_IdlePriority,Fe=t.log,Ie=t.unstable_setDisableYieldValue,Le=null,Re=null;function ze(e){if(typeof Fe==`function`&&Ie(e),Re&&typeof Re.setStrictMode==`function`)try{Re.setStrictMode(Le,e)}catch{}}var Be=Math.clz32?Math.clz32:Ue,Ve=Math.log,He=Math.LN2;function Ue(e){return e>>>=0,e===0?32:31-(Ve(e)/He|0)|0}var We=256,Ge=262144,Ke=4194304;function qe(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function Je(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=qe(n))):i=qe(o):i=qe(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=qe(n))):i=qe(o)):i=qe(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function Ye(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function Xe(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Ze(){var e=Ke;return Ke<<=1,!(Ke&62914560)&&(Ke=4194304),e}function Qe(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function $e(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function et(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),pn=!1;if(fn)try{var mn={};Object.defineProperty(mn,"passive",{get:function(){pn=!0}}),window.addEventListener(`test`,mn,mn),window.removeEventListener(`test`,mn,mn)}catch{pn=!1}var hn=null,gn=null,_n=null;function vn(){if(_n)return _n;var e,t=gn,n=t.length,r,i=`value`in hn?hn.value:hn.textContent,a=i.length;for(e=0;e=Xn),$n=` `,er=!1;function tr(e,t){switch(e){case`keyup`:return Jn.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function nr(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var rr=!1;function ir(e,t){switch(e){case`compositionend`:return nr(t);case`keypress`:return t.which===32?(er=!0,$n):null;case`textInput`:return e=t.data,e===$n&&er?null:e;default:return null}}function ar(e,t){if(rr)return e===`compositionend`||!Yn&&tr(e,t)?(e=vn(),_n=gn=hn=null,rr=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=Dr(n)}}function kr(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?kr(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Ar(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=zt(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=zt(e.document)}return t}function jr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var Mr=fn&&`documentMode`in document&&11>=document.documentMode,Nr=null,Pr=null,Fr=null,Ir=!1;function Lr(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Ir||Nr==null||Nr!==zt(r)||(r=Nr,`selectionStart`in r&&jr(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Fr&&Er(Fr,r)||(Fr=r,r=Ed(Pr,`onSelect`),0>=o,i-=o,ki=1<<32-Be(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),a=o(_,a,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),L&&ji(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(a,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(a,h),s=o(y,s,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(a,h),L&&ji(a,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(a,v.value,l),v!==null&&(s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return L&&ji(a,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,a,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(a,e)}),L&&ji(a,g),u}function b(e,r,o,c){if(typeof o==`object`&&o&&o.type===y&&o.key===null&&(o=o.props.children),typeof o==`object`&&o){switch(o.$$typeof){case _:a:{for(var l=o.key;r!==null;){if(r.key===l){if(l=o.type,l===y){if(r.tag===7){n(e,r.sibling),c=a(r,o.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===D&&Aa(l)===r.type){n(e,r.sibling),c=a(r,o.props),La(c,o),c.return=e,e=c;break a}n(e,r);break}else t(e,r);r=r.sibling}o.type===y?(c=gi(o.props.children,e.mode,c,o.key),c.return=e,e=c):(c=hi(o.type,o.key,o.props,null,e.mode,c),La(c,o),c.return=e,e=c)}return s(e);case v:a:{for(l=o.key;r!==null;){if(r.key===l)if(r.tag===4&&r.stateNode.containerInfo===o.containerInfo&&r.stateNode.implementation===o.implementation){n(e,r.sibling),c=a(r,o.children||[]),c.return=e,e=c;break a}else{n(e,r);break}else t(e,r);r=r.sibling}c=yi(o,e.mode,c),c.return=e,e=c}return s(e);case D:return o=Aa(o),b(e,r,o,c)}if(ie(o))return h(e,r,o,c);if(A(o)){if(l=A(o),typeof l!=`function`)throw Error(i(150));return o=l.call(o),g(e,r,o,c)}if(typeof o.then==`function`)return b(e,r,Ia(o),c);if(o.$$typeof===C)return b(e,r,ia(e,o),c);Ra(e,o)}return typeof o==`string`&&o!==``||typeof o==`number`||typeof o==`bigint`?(o=``+o,r!==null&&r.tag===6?(n(e,r.sibling),c=a(r,o),c.return=e,e=c):(n(e,r),c=_i(o,e.mode,c),c.return=e,e=c),s(e)):n(e,r)}return function(e,t,n,r){try{Fa=0;var i=b(e,t,n,r);return Pa=null,i}catch(t){if(t===wa||t===Ea)throw t;var a=di(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var Ba=za(!0),Va=za(!1),Ha=!1;function Ua(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Wa(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function Ga(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Ka(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,G&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=ci(e),si(e,null,n),t}return ii(e,r,t,n),ci(e)}function qa(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,nt(e,n)}}function Ja(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var Ya=!1;function Xa(){if(Ya){var e=ha;if(e!==null)throw e}}function Za(e,t,n,r){Ya=!1;var i=e.updateQueue;Ha=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,p=f!==s.lane;if(p?(J&f)===f:(r&f)===f){f!==0&&f===ma&&(Ya=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var h=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(h=g.payload,typeof h==`function`){d=h.call(_,d,f);break a}d=h;break a;case 3:h.flags=h.flags&-65537|128;case 0:if(h=g.payload,f=typeof h==`function`?h.call(_,d,f):h,f==null)break a;d=m({},d,f);break a;case 2:Ha=!0}}f=s.callback,f!==null&&(e.flags|=64,p&&(e.flags|=8192),p=i.callbacks,p===null?i.callbacks=[f]:p.push(f))}else p={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=p,c=d):u=u.next=p,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;p=s,s=p.next,p.next=null,i.lastBaseUpdate=p,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),Gl|=o,e.lanes=o,e.memoizedState=d}}function Qa(e,t){if(typeof e!=`function`)throw Error(i(191,e));e.call(t)}function $a(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=j.T,s={};j.T=s,Fs(e,!1,t,n);try{var c=i(),l=j.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?Ps(e,t,va(c,r),pu(e)):Ps(e,t,r,pu(e))}catch(n){Ps(e,t,{then:function(){},status:`rejected`,reason:n},pu())}finally{M.p=a,o!==null&&s.types!==null&&(o.types=s.types),j.T=o}}function ws(){}function Ts(e,t,n,r){if(e.tag!==5)throw Error(i(476));var a=Es(e).queue;Cs(e,a,t,ae,n===null?ws:function(){return Ds(e),n(r)})}function Es(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:ae,baseState:ae,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Io,lastRenderedState:ae},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Io,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function Ds(e){var t=Es(e);t.next===null&&(t=e.alternate.memoizedState),Ps(e,t.next.queue,{},pu())}function Os(){return ra(Qf)}function ks(){return H().memoizedState}function As(){return H().memoizedState}function js(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=pu();e=Ga(n);var r=Ka(t,e,n);r!==null&&(hu(r,t,n),qa(r,t,n)),t={cache:ua()},e.payload=t;return}t=t.return}}function Ms(e,t,n){var r=pu();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},Is(e)?Ls(t,n):(n=ai(e,t,n,r),n!==null&&(hu(n,e,r),Rs(n,t,r)))}function Ns(e,t,n){Ps(e,t,n,pu())}function Ps(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(Is(e))Ls(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,Tr(s,o))return ii(e,t,i,0),K===null&&ri(),!1}catch{}if(n=ai(e,t,i,r),n!==null)return hu(n,e,r),Rs(n,t,r),!0}return!1}function Fs(e,t,n,r){if(r={lane:2,revertLane:dd(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},Is(e)){if(t)throw Error(i(479))}else t=ai(e,n,r,2),t!==null&&hu(t,e,2)}function Is(e){var t=e.alternate;return e===z||t!==null&&t===z}function Ls(e,t){_o=go=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Rs(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,nt(e,n)}}var zs={readContext:ra,use:Po,useCallback:V,useContext:V,useEffect:V,useImperativeHandle:V,useLayoutEffect:V,useInsertionEffect:V,useMemo:V,useReducer:V,useRef:V,useState:V,useDebugValue:V,useDeferredValue:V,useTransition:V,useSyncExternalStore:V,useId:V,useHostTransitionStatus:V,useFormState:V,useActionState:V,useOptimistic:V,useMemoCache:V,useCacheRefresh:V};zs.useEffectEvent=V;var Bs={readContext:ra,use:Po,useCallback:function(e,t){return jo().memoizedState=[e,t===void 0?null:t],e},useContext:ra,useEffect:us,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),cs(4194308,4,gs.bind(null,t,e),n)},useLayoutEffect:function(e,t){return cs(4194308,4,e,t)},useInsertionEffect:function(e,t){cs(4,2,e,t)},useMemo:function(e,t){var n=jo();t=t===void 0?null:t;var r=e();if(vo){ze(!0);try{e()}finally{ze(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=jo();if(n!==void 0){var i=n(t);if(vo){ze(!0);try{n(t)}finally{ze(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=Ms.bind(null,z,e),[r.memoizedState,e]},useRef:function(e){var t=jo();return e={current:e},t.memoizedState=e},useState:function(e){e=Ko(e);var t=e.queue,n=Ns.bind(null,z,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:vs,useDeferredValue:function(e,t){return xs(jo(),e,t)},useTransition:function(){var e=Ko(!1);return e=Cs.bind(null,z,e.queue,!0,!1),jo().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=z,a=jo();if(L){if(n===void 0)throw Error(i(407));n=n()}else{if(n=t(),K===null)throw Error(i(349));J&127||Vo(r,t,n)}a.memoizedState=n;var o={value:n,getSnapshot:t};return a.queue=o,us(Uo.bind(null,r,o,e),[e]),r.flags|=2048,os(9,{destroy:void 0},Ho.bind(null,r,o,n,t),null),n},useId:function(){var e=jo(),t=K.identifierPrefix;if(L){var n=Ai,r=ki;n=(r&~(1<<32-Be(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=yo++,0<\/script>`,o=o.removeChild(o.firstChild);break;case`select`:o=typeof r.is==`string`?s.createElement(`select`,{is:r.is}):s.createElement(`select`),r.multiple?o.multiple=!0:r.size&&(o.size=r.size);break;default:o=typeof r.is==`string`?s.createElement(a,{is:r.is}):s.createElement(a)}}o[lt]=t,o[ut]=r;a:for(s=t.child;s!==null;){if(s.tag===5||s.tag===6)o.appendChild(s.stateNode);else if(s.tag!==4&&s.tag!==27&&s.child!==null){s.child.return=s,s=s.child;continue}if(s===t)break a;for(;s.sibling===null;){if(s.return===null||s.return===t)break a;s=s.return}s.sibling.return=s.return,s=s.sibling}t.stateNode=o;a:switch(Pd(o,a,r),a){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&Pc(t)}}return U(t),Fc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&Pc(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(i(166));if(e=ue.current,Ui(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,a=Ii,a!==null)switch(a.tag){case 27:case 5:r=a.memoizedProps}e[lt]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||Md(e.nodeValue,n)),e||Bi(t,!0)}else e=Bd(e).createTextNode(r),e[lt]=t,t.stateNode=e}return U(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=Ui(t),n!==null){if(e===null){if(!r)throw Error(i(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(i(557));e[lt]=t}else Wi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;U(t),e=!1}else n=Gi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(fo(t),t):(fo(t),null);if(t.flags&128)throw Error(i(558))}return U(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(a=Ui(t),r!==null&&r.dehydrated!==null){if(e===null){if(!a)throw Error(i(318));if(a=t.memoizedState,a=a===null?null:a.dehydrated,!a)throw Error(i(317));a[lt]=t}else Wi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;U(t),a=!1}else a=Gi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=a),a=!0;if(!a)return t.flags&256?(fo(t),t):(fo(t),null)}return fo(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,a=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(a=r.alternate.memoizedState.cachePool.pool),o=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(o=r.memoizedState.cachePool.pool),o!==a&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),Lc(t,t.updateQueue),U(t),null);case 4:return pe(),e===null&&Sd(t.stateNode.containerInfo),U(t),null;case 10:return Zi(t.type),U(t),null;case 19:if(P(R),r=t.memoizedState,r===null)return U(t),null;if(a=(t.flags&128)!=0,o=r.rendering,o===null)if(a)Rc(r,!1);else{if(X!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(o=po(e),o!==null){for(t.flags|=128,Rc(r,!1),e=o.updateQueue,t.updateQueue=e,Lc(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)mi(n,e),n=n.sibling;return F(R,R.current&1|2),L&&ji(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&Oe()>tu&&(t.flags|=128,a=!0,Rc(r,!1),t.lanes=4194304)}else{if(!a)if(e=po(o),e!==null){if(t.flags|=128,a=!0,e=e.updateQueue,t.updateQueue=e,Lc(t,e),Rc(r,!0),r.tail===null&&r.tailMode===`hidden`&&!o.alternate&&!L)return U(t),null}else 2*Oe()-r.renderingStartTime>tu&&n!==536870912&&(t.flags|=128,a=!0,Rc(r,!1),t.lanes=4194304);r.isBackwards?(o.sibling=t.child,t.child=o):(e=r.last,e===null?t.child=o:e.sibling=o,r.last=o)}return r.tail===null?(U(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=Oe(),e.sibling=null,n=R.current,F(R,a?n&1|2:n&1),L&&ji(t,r.treeForkCount),e);case 22:case 23:return fo(t),io(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(U(t),t.subtreeFlags&6&&(t.flags|=8192)):U(t),n=t.updateQueue,n!==null&&Lc(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&P(ba),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),Zi(la),U(t),null;case 25:return null;case 30:return null}throw Error(i(156,t.tag))}function Bc(e,t){switch(Pi(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Zi(la),pe(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return he(t),null;case 31:if(t.memoizedState!==null){if(fo(t),t.alternate===null)throw Error(i(340));Wi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(fo(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(i(340));Wi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return P(R),null;case 4:return pe(),null;case 10:return Zi(t.type),null;case 22:case 23:return fo(t),io(),e!==null&&P(ba),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return Zi(la),null;case 25:return null;default:return null}}function Vc(e,t){switch(Pi(t),t.tag){case 3:Zi(la),pe();break;case 26:case 27:case 5:he(t);break;case 4:pe();break;case 31:t.memoizedState!==null&&fo(t);break;case 13:fo(t);break;case 19:P(R);break;case 10:Zi(t.type);break;case 22:case 23:fo(t),io(),e!==null&&P(ba);break;case 24:Zi(la)}}function Hc(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){Z(t,t.return,e)}}function Uc(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){Z(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){Z(t,t.return,e)}}function Wc(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{$a(t,n)}catch(t){Z(e,e.return,t)}}}function Gc(e,t,n){n.props=qs(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){Z(e,t,n)}}function Kc(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){Z(e,t,n)}}function qc(e,t){var n=e.ref,r=e.refCleanup;if(n!==null)if(typeof r==`function`)try{r()}catch(n){Z(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){Z(e,t,n)}else n.current=null}function Jc(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){Z(e,e.return,t)}}function Yc(e,t,n){try{var r=e.stateNode;Fd(r,e.type,n,t),r[ut]=t}catch(t){Z(e,e.return,t)}}function Xc(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&Zd(e.type)||e.tag===4}function Zc(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||Xc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&Zd(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Qc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=nn));else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for(Qc(e,t,n),e=e.sibling;e!==null;)Qc(e,t,n),e=e.sibling}function $c(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode),e=e.child,e!==null))for($c(e,t,n),e=e.sibling;e!==null;)$c(e,t,n),e=e.sibling}function el(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);Pd(t,r,n),t[lt]=e,t[ut]=n}catch(t){Z(e,e.return,t)}}var tl=!1,nl=!1,rl=!1,il=typeof WeakSet==`function`?WeakSet:Set,al=null;function ol(e,t){if(e=e.containerInfo,Rd=sp,e=Ar(e),jr(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var a=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break a}var s=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||a!==0&&f.nodeType!==3||(c=s+a),f!==o||r!==0&&f.nodeType!==3||(l=s+r),f.nodeType===3&&(s+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===a&&(c=s),p===o&&++d===r&&(l=s),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n||={start:0,end:0}}else n=null;for(zd={focusedElem:e,selectionRange:n},sp=!1,al=t;al!==null;)if(t=al,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,al=e;else for(;al!==null;){switch(t=al,o=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),Pd(o,r,n),o[lt]=e,St(o),r=o;break a;case`link`:var s=Vf(`link`,`href`,a).get(r+(n.href||``));if(s){for(var c=0;cg&&(o=g,g=h,h=o);var _=Or(s,h),v=Or(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,j.T=null,n=lu,lu=null;var o=au,s=su;if(iu=0,ou=au=null,su=0,G&6)throw Error(i(331));var c=G;if(G|=4,Fl(o.current),Dl(o,o.current,s,n),G=c,id(0,!1),Re&&typeof Re.onPostCommitFiberRoot==`function`)try{Re.onPostCommitFiberRoot(Le,o)}catch{}return!0}finally{M.p=a,j.T=r,Vu(e,t)}}function Wu(e,t,n){t=xi(n,t),t=$s(e.stateNode,t,2),e=Ka(e,t,2),e!==null&&($e(e,2),rd(e))}function Z(e,t,n){if(e.tag===3)Wu(e,e,n);else for(;t!==null;){if(t.tag===3){Wu(t,e,n);break}else if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(ru===null||!ru.has(r))){e=xi(n,e),n=ec(2),r=Ka(t,n,2),r!==null&&(tc(n,r,t,e),$e(r,2),rd(r));break}}t=t.return}}function Gu(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new zl;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(Ul=!0,i.add(n),e=Ku.bind(null,e,t,n),t.then(e,e))}function Ku(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,K===e&&(J&n)===n&&(X===4||X===3&&(J&62914560)===J&&300>Oe()-$l?!(G&2)&&Su(e,0):ql|=n,Yl===J&&(Yl=0)),rd(e)}function qu(e,t){t===0&&(t=Ze()),e=oi(e,t),e!==null&&($e(e,t),rd(e))}function Ju(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),qu(e,n)}function Yu(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,a=e.memoizedState;a!==null&&(n=a.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(i(314))}r!==null&&r.delete(t),qu(e,n)}function Xu(e,t){return we(e,t)}var Zu=null,Qu=null,$u=!1,ed=!1,td=!1,nd=0;function rd(e){e!==Qu&&e.next===null&&(Qu===null?Zu=Qu=e:Qu=Qu.next=e),ed=!0,$u||($u=!0,ud())}function id(e,t){if(!td&&ed){td=!0;do for(var n=!1,r=Zu;r!==null;){if(!t)if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-Be(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,ld(r,a))}else a=J,a=Je(r,r===K?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||Ye(r,a)||(n=!0,ld(r,a));r=r.next}while(n);td=!1}}function ad(){od()}function od(){ed=$u=!1;var e=0;nd!==0&&Gd()&&(e=nd);for(var t=Oe(),n=null,r=Zu;r!==null;){var i=r.next,a=sd(r,t);a===0?(r.next=null,n===null?Zu=i:n.next=i,i===null&&(Qu=n)):(n=r,(e!==0||a&3)&&(ed=!0)),r=i}iu!==0&&iu!==5||id(e,!1),nd!==0&&(nd=0)}function sd(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&Id(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function xf(e,t,n){var r=bf;if(r&&typeof t==`string`&&t){var i=Vt(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),hf.has(i)||(hf.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),Pd(t,`link`,e),St(t),r.head.appendChild(t)))}}function Sf(e){_f.D(e),xf(`dns-prefetch`,e,null)}function Cf(e,t){_f.C(e,t),xf(`preconnect`,e,t)}function wf(e,t,n){_f.L(e,t,n);var r=bf;if(r&&e&&t){var i=`link[rel="preload"][as="`+Vt(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+Vt(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+Vt(n.imageSizes)+`"]`)):i+=`[href="`+Vt(e)+`"]`;var a=i;switch(t){case`style`:a=Af(e);break;case`script`:a=Pf(e)}mf.has(a)||(e=m({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),mf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(jf(a))||t===`script`&&r.querySelector(Ff(a))||(t=r.createElement(`link`),Pd(t,`link`,e),St(t),r.head.appendChild(t)))}}function Tf(e,t){_f.m(e,t);var n=bf;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+Vt(r)+`"][href="`+Vt(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=Pf(e)}if(!mf.has(a)&&(e=m({rel:`modulepreload`,href:e},t),mf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(Ff(a)))return}r=n.createElement(`link`),Pd(r,`link`,e),St(r),n.head.appendChild(r)}}}function Ef(e,t,n){_f.S(e,t,n);var r=bf;if(r&&e){var i=xt(r).hoistableStyles,a=Af(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(jf(a)))s.loading=5;else{e=m({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=mf.get(a))&&Rf(e,n);var c=o=r.createElement(`link`);St(c),Pd(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,Lf(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function Df(e,t){_f.X(e,t);var n=bf;if(n&&e){var r=xt(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=m({src:e,async:!0},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),St(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Of(e,t){_f.M(e,t);var n=bf;if(n&&e){var r=xt(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=m({src:e,async:!0,type:`module`},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),St(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function kf(e,t,n,r){var a=(a=ue.current)?gf(a):null;if(!a)throw Error(i(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=Af(n.href),n=xt(a).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=Af(n.href);var o=xt(a).hoistableStyles,s=o.get(e);if(s||(a=a.ownerDocument||a,s={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},o.set(e,s),(o=a.querySelector(jf(e)))&&!o._p&&(s.instance=o,s.state.loading=5),mf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},mf.set(e,n),o||Nf(a,e,n,s.state))),t&&r===null)throw Error(i(528,``));return s}if(t&&r!==null)throw Error(i(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=Pf(n),n=xt(a).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(i(444,e))}}function Af(e){return`href="`+Vt(e)+`"`}function jf(e){return`link[rel="stylesheet"][`+e+`]`}function Mf(e){return m({},e,{"data-precedence":e.precedence,precedence:null})}function Nf(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),Pd(t,`link`,n),St(t),e.head.appendChild(t))}function Pf(e){return`[src="`+Vt(e)+`"]`}function Ff(e){return`script[async]`+e}function If(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+Vt(n.href)+`"]`);if(r)return t.instance=r,St(r),r;var a=m({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),St(r),Pd(r,`style`,a),Lf(r,n.precedence,e),t.instance=r;case`stylesheet`:a=Af(n.href);var o=e.querySelector(jf(a));if(o)return t.state.loading|=4,t.instance=o,St(o),o;r=Mf(n),(a=mf.get(a))&&Rf(r,a),o=(e.ownerDocument||e).createElement(`link`),St(o);var s=o;return s._p=new Promise(function(e,t){s.onload=e,s.onerror=t}),Pd(o,`link`,r),t.state.loading|=4,Lf(o,n.precedence,e),t.instance=o;case`script`:return o=Pf(n.src),(a=e.querySelector(Ff(o)))?(t.instance=a,St(a),a):(r=n,(a=mf.get(o))&&(r=m({},n),zf(r,a)),e=e.ownerDocument||e,a=e.createElement(`script`),St(a),Pd(a,`link`,r),e.head.appendChild(a),t.instance=a);case`void`:return null;default:throw Error(i(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,Lf(r,n.precedence,e));return t.instance}function Lf(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function Uf(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function Wf(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function Gf(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=Af(r.href),a=t.querySelector(jf(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=Jf.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,St(a);return}a=t.ownerDocument||t,r=Mf(r),(i=mf.get(i))&&Rf(r,i),a=a.createElement(`link`),St(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Pd(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=Jf.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var Kf=0;function qf(e,t){return e.stylesheets&&e.count===0&&Xf(e,e.stylesheets),0Kf?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function Jf(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Xf(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Yf=null;function Xf(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Yf=new Map,t.forEach(Zf,e),Yf=null,Jf.call(e))}function Zf(e,t){if(!(t.state.loading&4)){var n=Yf.get(e);if(n)var r=n.get(null);else{n=new Map,Yf.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=g()})),v=d(),y=_(),b=class extends Error{status;constructor(e,t){super(t),this.status=e}},x=e=>e instanceof b&&e.status===401,S=class extends Error{constructor(e){super(`connection lost — check your network`,{cause:e}),this.name=`NetworkError`}},C=e=>e instanceof S;async function w(e,t){try{return await fetch(e,t)}catch(e){throw new S(e)}}async function ee(e){if(!e.ok){let t=`request failed (${e.status})`;try{let n=await e.json();typeof n?.error==`string`&&(t=n.error)}catch{}throw new b(e.status,t)}let t=await e.json();if(t.version!==2)throw new b(e.status,`this page is out of date (server protocol v${t.version}) — reload`);return t}async function T(e,t){return ee(await w(e,{credentials:`same-origin`,signal:t}))}async function E(e,t,n){return ee(await w(e,{method:`POST`,credentials:`same-origin`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t),signal:n}))}var D=e=>new URLSearchParams(e).toString(),te=1e4,O={async login(e){let t=await w(`/login`,{method:`POST`,credentials:`same-origin`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:new URLSearchParams({password:e}).toString()});if(!t.ok)throw new b(t.status,t.status===429?`too many attempts — wait a minute`:`incorrect password`)},repos:e=>T(`/api/repos`,e),setAccent:e=>E(`/api/prefs`,{accent:e}).then(e=>e.accent),setSidebarWidth:e=>E(`/api/prefs`,{sidebar_width:e}).then(e=>e.sidebar_width),setUpperPct:e=>E(`/api/prefs`,{upper_pct:e}).then(e=>e.upper_pct),setActiveRepo:e=>E(`/api/prefs`,{active_repo:e},AbortSignal.timeout(te)).then(e=>e.active_repo),setMaximized:(e,t)=>E(`/api/prefs`,{maximized:{repo:e,panel:t}},AbortSignal.timeout(te)).then(e=>e.maximized),status:e=>T(`/api/status?${D({repo:e})}`),tree:(e,t)=>T(`/api/tree?${D({repo:e,path:t})}`),treeSearch:(e,t)=>T(`/api/tree/search?${D({repo:e,q:t})}`),log:(e,t)=>T(`/api/log?${D(t?{repo:e,from:t.from,skip:String(t.skip)}:{repo:e})}`),diff:(e,t)=>T(`/api/diff?${D({repo:e,path:t})}`),file:(e,t)=>T(`/api/file?${D({repo:e,path:t})}`),commit:(e,t)=>T(`/api/commit?${D({repo:e,oid:t})}`),commitFiles:(e,t)=>T(`/api/commit/files?${D({repo:e,oid:t})}`),commitFileDiff:(e,t,n)=>T(`/api/commit/file-diff?${D({repo:e,oid:t,path:n})}`),browse:e=>T(`/api/browse${e?`?${D({path:e})}`:``}`),mkdir:(e,t)=>E(`/api/mkdir`,{path:e,name:t}).then(e=>e.path),clone:(e,t)=>E(`/api/clone`,{path:e,url:t}),cloneStatus:e=>T(`/api/clone?${D({job:String(e)})}`),runningClone:()=>T(`/api/clone`),open:e=>E(`/api/repos`,{path:e}).then(e=>e.repo),close:async e=>{let t=await w(`/api/repos?${D({repo:e})}`,{method:`DELETE`,credentials:`same-origin`});if(!t.ok)throw new b(t.status,`could not close (${t.status})`)},reorderRepos:e=>E(`/api/repos/order`,{order:e}).then(e=>e.repos),reloadConfig:()=>E(`/api/reload`,{}).then(e=>e.summary)};function k(e,t){let n=new EventSource(`/api/events?${D({repo:e})}`);return n.addEventListener(`status`,e=>{try{let n=JSON.parse(e.data);n.version===2&&t(n)}catch{}}),()=>n.close()}var A=[],ne=1,re=new Set;function ie(){let e=A;re.forEach(t=>t(e))}function j(e){return re.add(e),e(A),()=>{re.delete(e)}}function M(e){let t=A.filter(t=>t.id!==e);t.length!==A.length&&(A=t,ie())}function ae(e,t){let n=A.findIndex(n=>n.kind===e&&n.message===t);if(n!==-1){let e=A[n];return A=A.map((e,t)=>t===n?{...e,bump:e.bump+1}:e),ie(),e.id}let r=ne++;return A=[...A,{id:r,kind:e,message:t,bump:0}].slice(-4),ie(),r}var oe={error:e=>ae(`error`,e),info:e=>ae(`info`,e),success:e=>ae(`success`,e)},se=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),N=o(((e,t)=>{t.exports=se()})),P=N();function F({className:e=`h-4 w-4`}){return(0,P.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`shrink-0 ${e}`,children:[(0,P.jsx)(`path`,{d:`M18 6 6 18`}),(0,P.jsx)(`path`,{d:`m6 6 12 12`})]})}function ce({className:e=`h-4 w-4`}){return(0,P.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`shrink-0 ${e}`,children:[(0,P.jsx)(`path`,{d:`M5 12h14`}),(0,P.jsx)(`path`,{d:`M12 5v14`})]})}function le(){return(0,P.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`h-4 w-4`,children:[(0,P.jsx)(`circle`,{cx:`11`,cy:`11`,r:`8`}),(0,P.jsx)(`path`,{d:`m21 21-4.3-4.3`})]})}function ue({className:e=`h-4 w-4`}){return(0,P.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`shrink-0 ${e}`,children:[(0,P.jsx)(`path`,{d:`M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4`}),(0,P.jsx)(`path`,{d:`m16 17 5-5-5-5`}),(0,P.jsx)(`path`,{d:`M21 12H9`})]})}function de({className:e=`h-4 w-4`}){return(0,P.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`shrink-0 ${e}`,children:[(0,P.jsx)(`path`,{d:`M3 12a9 9 0 0 1 9-9 9 9 0 0 1 6.7 3H21`}),(0,P.jsx)(`path`,{d:`M21 3v6h-6`}),(0,P.jsx)(`path`,{d:`M21 12a9 9 0 0 1-9 9 9 9 0 0 1-6.7-3H3`}),(0,P.jsx)(`path`,{d:`M3 21v-6h6`})]})}function fe({onClose:e,onOpened:t,canClone:n,cloning:r,onClone:i}){let[a,o]=(0,v.useState)(null),[s,c]=(0,v.useState)(null),[l,u]=(0,v.useState)(null),[d,f]=(0,v.useState)(!1),[p,m]=(0,v.useState)(``),[h,g]=(0,v.useState)(!1),[_,y]=(0,v.useState)(``),[b,x]=(0,v.useState)(0);(0,v.useEffect)(()=>{let e=!1;return O.browse(a??void 0).then(t=>{e||(c(t),u(null))}).catch(t=>{e||u(t instanceof Error?t.message:`could not browse`)}),()=>{e=!0}},[a,b]);let S=e=>o(`${s.path.replace(/\/$/,``)}/${e}`),C=async()=>{if(s){f(!0);try{t(await O.open(s.path))}catch(e){oe.error(e instanceof Error?e.message:`could not open`),f(!1)}}},w=async()=>{if(!s)return;let e=p.trim();if(e){g(!0);try{await O.mkdir(s.path,e),m(``),x(e=>e+1)}catch(e){oe.error(e instanceof Error?e.message:`could not create folder`)}finally{g(!1)}}};return(0,P.jsx)(`div`,{className:`fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4`,onClick:e,children:(0,P.jsxs)(`div`,{className:`flex max-h-[80vh] w-[34rem] max-w-full flex-col rounded-md border border-ink-700 bg-ink-900`,onClick:e=>e.stopPropagation(),children:[(0,P.jsxs)(`div`,{className:`flex shrink-0 items-center gap-2 border-b border-ink-700 px-3 py-2`,children:[(0,P.jsx)(`span`,{className:`font-medium text-ink-50`,children:`Open a project`}),(0,P.jsx)(`button`,{onClick:e,"aria-label":`close`,className:`ml-auto flex h-6 w-6 items-center justify-center rounded-sm text-ink-400 hover:text-ink-200`,children:(0,P.jsx)(F,{})})]}),(0,P.jsx)(`div`,{className:`shrink-0 truncate border-b border-ink-700 px-3 py-1.5 text-ink-400`,children:s?.path??`…`}),(0,P.jsxs)(`ul`,{className:`h-72 min-h-0 overflow-y-auto`,children:[s?.parent&&(0,P.jsx)(`li`,{children:(0,P.jsx)(`button`,{onClick:()=>o(s.parent),className:`w-full px-3 py-1 text-left text-ink-400 hover:bg-ink-850`,children:`../`})}),s?.entries.map(e=>(0,P.jsx)(`li`,{children:(0,P.jsxs)(`button`,{onClick:()=>S(e.name),className:`flex w-full items-center gap-2 px-3 py-1 text-left hover:bg-ink-850`,children:[(0,P.jsxs)(`span`,{className:`truncate text-accent`,children:[e.name,`/`]}),e.is_repo&&(0,P.jsx)(`span`,{className:`rounded-sm bg-ink-700 px-1 text-[0.65rem] text-ink-200`,children:`git`})]})},e.name)),s&&s.entries.length===0&&(0,P.jsx)(`li`,{className:`px-3 py-1 text-ink-400`,children:`No sub-folders.`})]}),l&&(0,P.jsx)(`p`,{className:`shrink-0 px-3 py-1 text-removed`,children:l}),(0,P.jsxs)(`div`,{className:`flex shrink-0 items-center gap-2 border-t border-ink-700 px-3 py-2`,children:[(0,P.jsx)(`input`,{value:p,onChange:e=>m(e.target.value),onKeyDown:e=>{e.key===`Enter`&&w()},placeholder:`New folder name`,"aria-label":`new folder name`,className:`min-w-0 flex-1 rounded-sm border border-ink-700 bg-ink-950 px-2 py-1 text-ink-50 placeholder:text-ink-400 focus:border-ink-600 focus:outline-none`}),(0,P.jsx)(`button`,{onClick:w,disabled:!s||!p.trim()||h,className:`shrink-0 rounded-sm border border-ink-700 px-2 py-1 text-ink-200 hover:bg-ink-850 disabled:opacity-50`,children:h?`Creating…`:`Create`})]}),(0,P.jsxs)(`div`,{className:`flex shrink-0 items-center gap-2 border-t border-ink-700 px-3 py-2`,children:[(0,P.jsx)(`input`,{value:_,onChange:e=>y(e.target.value),onKeyDown:e=>{e.key===`Enter`&&s&&i(s.path,_)},disabled:!n,placeholder:n?`Clone a git URL here`:`git is not installed on the server`,"aria-label":`git URL to clone`,spellCheck:!1,autoCapitalize:`none`,autoCorrect:`off`,className:`min-w-0 flex-1 rounded-sm border border-ink-700 bg-ink-950 px-2 py-1 text-ink-50 placeholder:text-ink-400 focus:border-ink-600 focus:outline-none disabled:opacity-50`}),(0,P.jsx)(`button`,{onClick:()=>s&&i(s.path,_),disabled:!n||!s||!_.trim()||r,title:n?void 0:`the server has no git on its PATH`,className:`shrink-0 rounded-sm border border-ink-700 px-2 py-1 text-ink-200 hover:bg-ink-850 disabled:opacity-50`,children:r?`Cloning…`:`Clone`})]}),(0,P.jsxs)(`div`,{className:`flex shrink-0 items-center gap-2 border-t border-ink-700 px-3 py-2`,children:[(0,P.jsx)(`span`,{className:`truncate text-ink-400`,children:s?s.path:``}),(0,P.jsx)(`button`,{onClick:C,disabled:!s||d,className:`ml-auto shrink-0 rounded-md bg-ink-50 px-3 py-1 font-semibold text-ink-950 hover:bg-white disabled:opacity-50`,children:d?`Opening…`:`Open`})]})]})})}var pe=`data:image/svg+xml,%3c?xml%20version='1.0'%20encoding='utf-8'?%3e%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='12%2057%201150%201150'%20role='img'%20aria-label='Black%20crow'%3e%3ctitle%3eBlack%20crow%3c/title%3e%3cdesc%3eMonochrome%20black%20crow%20silhouette%20on%20a%20transparent%20background,%20framed%20so%20the%20bird%20sits%20centred%20for%20use%20as%20an%20inline%20mark%20on%20a%20square%20tile.%3c/desc%3e%3cg%20fill-rule='evenodd'%20clip-rule='evenodd'%3e%3cpath%20fill='%23000000'%20d='M%20882%20147%20L%20859%20136%20L%20844%20131%20L%20831%20129%20L%20830%20128%20L%20815%20127%20L%20814%20126%20L%20796%20126%20L%20795%20127%20L%20786%20127%20L%20785%20128%20L%20775%20129%20L%20752%20136%20L%20732%20146%20L%20713%20160%20L%20701%20172%20L%20684%20172%20L%20683%20173%20L%20673%20173%20L%20672%20174%20L%20650%20176%20L%20649%20177%20L%20627%20181%20L%20602%20190%20L%20589%20197%20L%20579%20204%20L%20562%20221%20L%20562%20223%20L%20565%20223%20L%20578%20228%20L%20581%20228%20L%20612%20238%20L%20672%20252%20L%20684%20258%20L%20698%20271%20L%20702%20278%20L%20705%20288%20L%20705%20294%20L%20703%20301%20L%20699%20308%20L%20688%20318%20L%20630%20347%20L%20593%20372%20L%20561%20399%20L%20544%20416%20L%20522%20441%20L%20492%20481%20L%20461%20531%20L%20438%20576%20L%20431%20594%20L%20425%20602%20L%20405%20635%20L%20387%20668%20L%20385%20676%20L%20390%20679%20L%20368%20705%20L%20330%20755%20L%20306%20790%20L%20296%20808%20L%20289%20818%20L%20280%20838%20L%20280%20843%20L%20283%20845%20L%20292%20843%20L%20297%20840%20L%20299%20840%20L%20321%20828%20L%20322%20830%20L%20311%20844%20L%20288%20878%20L%20287%20881%20L%20259%20924%20L%20235%20965%20L%20205%201023%20L%20205%201025%20L%20197%201042%20L%20191%201061%20L%20191%201071%20L%20192%201072%20L%20198%201071%20L%20220%201056%20L%20242%201038%20L%20300%20986%20L%20302%20987%20L%20265%201040%20L%20264%201043%20L%20246%201070%20L%20235%201090%20L%20227%201112%20L%20227%201123%20L%20229%201128%20L%20234%201133%20L%20239%201135%20L%20255%201135%20L%20274%201129%20L%20279%201134%20L%20286%201137%20L%20290%201137%20L%20291%201138%20L%20310%201138%20L%20311%201137%20L%20317%201137%20L%20318%201136%20L%20326%201135%20L%20344%201129%20L%20369%201116%20L%20395%201097%20L%20420%201073%20L%20445%201042%20L%20457%201024%20L%20461%201016%20L%20464%201013%20L%20468%201011%20L%20489%20994%20L%20595%20901%20L%20601%20906%20L%20606%20913%20L%20614%20921%20L%20637%20949%20L%20639%20953%20L%20639%20956%20L%20636%20960%20L%20634%20961%20L%20619%20962%20L%20613%20965%20L%20605%20974%20L%20602%20982%20L%20602%20994%20L%20605%201001%20L%20608%201004%20L%20609%201004%20L%20609%20999%20L%20612%20992%20L%20616%20988%20L%20620%20986%20L%20627%20986%20L%20635%20983%20L%20645%20983%20L%20646%20982%20L%20655%20982%20L%20668%20986%20L%20676%20990%20L%20682%20996%20L%20685%201003%20L%20688%201006%20L%20696%201009%20L%20697%201012%20L%20697%201024%20L%20693%201033%20L%20693%201035%20L%20695%201035%20L%20700%201032%20L%20707%201025%20L%20710%201020%20L%20713%201010%20L%20713%201003%20L%20711%20998%20L%20711%20994%20L%20712%20993%20L%20719%201003%20L%20723%201005%20L%20727%201005%20L%20730%201011%20L%20730%201022%20L%20727%201031%20L%20728%201033%20L%20740%201021%20L%20743%201014%20L%20744%201003%20L%20747%20999%20L%20749%20992%20L%20748%20977%20L%20744%20968%20L%20740%20963%20L%20741%20962%20L%20755%20961%20L%20768%20964%20L%20777%20969%20L%20783%20975%20L%20786%20981%20L%20789%20984%20L%20795%20987%20L%20799%20987%20L%20801%20991%20L%20801%20997%20L%20802%20998%20L%20799%201013%20L%20802%201012%20L%20808%201007%20L%20813%201000%20L%20816%20991%20L%20816%20981%20L%20814%20976%20L%20814%20968%20L%20815%20967%20L%20819%20970%20L%20823%20970%20L%20826%20973%20L%20829%20980%20L%20830%20993%20L%20834%20990%20L%20838%20979%20L%20838%20968%20L%20832%20951%20L%20822%20940%20L%20815%20936%20L%20803%20933%20L%20776%20935%20L%20763%20931%20L%20753%20922%20L%20731%20898%20L%20703%20865%20L%20703%20863%20L%20710%20853%20L%20711%20855%20L%20707%20862%20L%20709%20862%20L%20718%20857%20L%20754%20832%20L%20793%20799%20L%20818%20774%20L%20849%20737%20L%20850%20741%20L%20845%20755%20L%20847%20755%20L%20861%20743%20L%20881%20721%20L%20906%20686%20L%20918%20665%20L%20933%20635%20L%20951%20590%20L%20971%20525%20L%20971%20521%20L%20974%20512%20L%20974%20508%20L%20978%20493%20L%20979%20482%20L%20980%20481%20L%20981%20466%20L%20982%20465%20L%20983%20441%20L%20982%20440%20L%20982%20428%20L%20981%20427%20L%20980%20414%20L%20978%20409%20L%20976%20397%20L%20970%20381%20L%20971%20379%20L%20974%20383%20L%20976%20381%20L%20977%20334%20L%20976%20333%20L%20976%20322%20L%20975%20321%20L%20974%20307%20L%20973%20306%20L%20973%20301%20L%20972%20300%20L%20969%20280%20L%20958%20243%20L%20949%20224%20L%20949%20222%20L%20939%20204%20L%20922%20181%20L%20903%20162%20Z%20M%20625%20888%20L%20656%20874%20L%20658%20874%20L%20665%20870%20L%20725%20930%20L%20728%20934%20L%20728%20940%20L%20723%20943%20L%20714%20944%20L%20707%20950%20L%20683%20951%20L%20673%20947%20L%20659%20932%20Z%20M%20787%20182%20L%20792%20182%20L%20796%20187%20L%20795%20192%20L%20791%20195%20L%20788%20195%20L%20783%20191%20L%20783%20186%20Z'/%3e%3cpath%20fill='%23000000'%20d='M%20895%20156%20L%20863%20138%20L%20838%20130%20L%20819%20127%20L%20789%20127%20L%20777%20129%20L%20753%20136%20L%20736%20144%20L%20717%20157%20L%20702%20172%20L%20652%20176%20L%20628%20181%20L%20607%20188%20L%20585%20200%20L%20562%20222%20L%20613%20238%20L%20670%20251%20L%20683%20257%20L%20697%20269%20L%20705%20286%20L%20705%20296%20L%20703%20302%20L%20698%20310%20L%20687%20319%20L%20631%20347%20L%20591%20374%20L%20568%20393%20L%20538%20423%20L%20520%20444%20L%20481%20498%20L%20457%20539%20L%20437%20579%20L%20432%20593%20L%20413%20622%20L%20386%20671%20L%20386%20677%20L%20390%20677%20L%20391%20679%20L%20374%20698%20L%20334%20750%20L%20307%20789%20L%20290%20817%20L%20280%20839%20L%20281%20844%20L%20291%20843%20L%20323%20826%20L%20325%20827%20L%20289%20877%20L%20237%20962%20L%20209%201015%20L%20198%201040%20L%20191%201063%20L%20191%201070%20L%20194%201072%20L%20213%201061%20L%20259%201023%20L%20302%20984%20L%20303%20985%20L%20262%201045%20L%20245%201072%20L%20233%201095%20L%20227%201114%20L%20228%201126%20L%20233%201132%20L%20242%201135%20L%20252%201135%20L%20274%201128%20L%20278%201133%20L%20288%201137%20L%20313%201137%20L%20343%201129%20L%20373%201113%20L%20398%201094%20L%20424%201068%20L%20441%201047%20L%20465%201012%20L%20520%20967%20L%20595%20900%20L%20611%20917%20L%20639%20952%20L%20639%20957%20L%20637%20960%20L%20632%20962%20L%20618%20963%20L%20611%20967%20L%20606%20973%20L%20602%20983%20L%20602%20992%20L%20608%201004%20L%20611%20993%20L%20619%20986%20L%20626%20986%20L%20643%20982%20L%20656%20982%20L%20675%20989%20L%20683%20997%20L%20688%201006%20L%20696%201009%20L%20697%201025%20L%20693%201034%20L%20694%201035%20L%20701%201031%20L%20710%201019%20L%20712%201013%20L%20712%20993%20L%20720%201003%20L%20727%201005%20L%20730%201009%20L%20730%201025%20L%20727%201032%20L%20732%201030%20L%20739%201022%20L%20743%201013%20L%20743%201004%20L%20749%20991%20L%20748%20978%20L%20740%20964%20L%20743%20961%20L%20757%20961%20L%20769%20964%20L%20781%20972%20L%20791%20985%20L%20798%20986%20L%20802%20994%20L%20802%201003%20L%20799%201013%20L%20810%201004%20L%20815%20994%20L%20816%20983%20L%20814%20977%20L%20814%20965%20L%20818%20969%20L%20825%20971%20L%20830%20983%20L%20830%20993%20L%20833%20991%20L%20837%20982%20L%20838%20969%20L%20834%20956%20L%20825%20943%20L%20820%20939%20L%20807%20934%20L%20774%20935%20L%20762%20931%20L%20736%20904%20L%20702%20864%20L%20715%20845%20L%20716%20847%20L%20707%20862%20L%20719%20856%20L%20744%20839%20L%20786%20805%20L%20824%20767%20L%20851%20733%20L%20852%20735%20L%20846%20755%20L%20853%20750%20L%20883%20718%20L%20906%20685%20L%20927%20647%20L%20951%20589%20L%20968%20535%20L%20976%20502%20L%20982%20461%20L%20981%20422%20L%20976%20398%20L%20968%20378%20L%20969%20376%20L%20975%20383%20L%20977%20341%20L%20970%20286%20L%20958%20244%20L%20945%20215%20L%20925%20185%20L%20906%20165%20Z%20M%20625%20888%20L%20665%20870%20L%20728%20933%20L%20729%20940%20L%20726%20943%20L%20715%20944%20L%20708%20950%20L%20692%20952%20L%20678%20950%20L%20672%20947%20L%20662%20936%20Z%20M%20785%20183%20L%20792%20182%20L%20796%20186%20L%20796%20191%20L%20791%20195%20L%20785%20194%20L%20783%20191%20L%20783%20186%20Z'/%3e%3cpath%20fill='%23000000'%20d='M%20896%20157%20L%20869%20141%20L%20837%20130%20L%20817%20127%20L%20792%20127%20L%20778%20129%20L%20751%20137%20L%20733%20146%20L%20715%20159%20L%20702%20172%20L%20653%20176%20L%20610%20187%20L%20581%20203%20L%20562%20222%20L%20625%20241%20L%20671%20251%20L%20683%20257%20L%20698%20270%20L%20705%20285%20L%20704%20300%20L%20699%20309%20L%20689%20318%20L%20628%20349%20L%20581%20382%20L%20540%20421%20L%20517%20448%20L%20478%20503%20L%20456%20541%20L%20439%20575%20L%20432%20593%20L%20412%20624%20L%20388%20667%20L%20386%20676%20L%20390%20677%20L%20391%20679%20L%20363%20712%20L%20332%20753%20L%20289%20819%20L%20281%20836%20L%20281%20844%20L%20293%20842%20L%20323%20826%20L%20325%20827%20L%20284%20885%20L%20236%20964%20L%20205%201024%20L%20197%201043%20L%20191%201064%20L%20191%201070%20L%20197%201071%20L%20210%201063%20L%20253%201028%20L%20303%20983%20L%20304%20984%20L%20259%201050%20L%20235%201091%20L%20227%201115%20L%20228%201125%20L%20232%201131%20L%20238%201134%20L%20249%201135%20L%20261%201133%20L%20274%201128%20L%20278%201133%20L%20289%201137%20L%20312%201137%20L%20342%201129%20L%20368%201116%20L%20399%201093%20L%20423%201069%20L%20443%201044%20L%20463%201013%20L%20493%20990%20L%20595%20900%20L%20612%20918%20L%20639%20952%20L%20639%20957%20L%20636%20961%20L%20616%20964%20L%20606%20973%20L%20602%20984%20L%20602%20991%20L%20608%201004%20L%20611%20993%20L%20621%20985%20L%20625%20986%20L%20639%20982%20L%20657%20982%20L%20677%20990%20L%20685%201002%20L%20690%201007%20L%20697%201010%20L%20698%201021%20L%20693%201034%20L%20694%201035%20L%20705%201027%20L%20710%201018%20L%20712%201011%20L%20712%201001%20L%20710%20994%20L%20712%20993%20L%20720%201003%20L%20729%201006%20L%20731%201020%20L%20728%201032%20L%20738%201023%20L%20743%201012%20L%20743%201003%20L%20748%20994%20L%20747%20976%20L%20740%20964%20L%20743%20961%20L%20759%20961%20L%20767%20963%20L%20780%20971%20L%20790%20984%20L%20799%20986%20L%20802%20992%20L%20802%201004%20L%20799%201012%20L%20803%201011%20L%20810%201004%20L%20815%20994%20L%20814%20965%20L%20818%20969%20L%20823%20969%20L%20830%20982%20L%20830%20992%20L%20832%20992%20L%20837%20982%20L%20837%20965%20L%20831%20950%20L%20821%20940%20L%20806%20934%20L%20772%20935%20L%20762%20931%20L%20733%20901%20L%20702%20863%20L%20717%20842%20L%20718%20844%20L%20707%20862%20L%20738%20843%20L%20780%20810%20L%20822%20769%20L%20851%20733%20L%20852%20735%20L%20846%20755%20L%20855%20748%20L%20882%20719%20L%20904%20688%20L%20930%20640%20L%20950%20591%20L%20967%20538%20L%20978%20490%20L%20982%20459%20L%20982%20434%20L%20976%20399%20L%20967%20376%20L%20969%20375%20L%20975%20383%20L%20976%20329%20L%20971%20293%20L%20960%20250%20L%20942%20210%20L%20923%20183%20Z%20M%20625%20888%20L%20665%20870%20L%20729%20934%20L%20729%20940%20L%20726%20943%20L%20716%20944%20L%20708%20950%20L%20695%20952%20L%20677%20950%20L%20666%20941%20Z%20M%20786%20182%20L%20790%20181%20L%20794%20183%20L%20797%20188%20L%20792%20195%20L%20787%20195%20L%20782%20190%20L%20782%20187%20Z'/%3e%3cpath%20fill='%23000000'%20d='M%20893%20155%20L%20873%20143%20L%20841%20131%20L%20816%20127%20L%20794%20127%20L%20779%20129%20L%20754%20136%20L%20735%20145%20L%20715%20159%20L%20702%20172%20L%20648%20177%20L%20629%20181%20L%20608%20188%20L%20584%20201%20L%20562%20222%20L%20614%20238%20L%20671%20251%20L%20685%20258%20L%20698%20270%20L%20704%20281%20L%20706%20292%20L%20703%20303%20L%20699%20309%20L%20686%20320%20L%20628%20349%20L%20590%20375%20L%20565%20396%20L%20543%20418%20L%20518%20447%20L%20490%20485%20L%20460%20534%20L%20441%20571%20L%20431%20595%20L%20405%20636%20L%20386%20672%20L%20386%20676%20L%20390%20677%20L%20391%20679%20L%20371%20702%20L%20335%20749%20L%20291%20816%20L%20280%20840%20L%20282%20844%20L%20298%20840%20L%20324%20825%20L%20326%20826%20L%20294%20870%20L%20235%20966%20L%20206%201022%20L%20198%201041%20L%20191%201065%20L%20191%201070%20L%20196%201071%20L%20214%201060%20L%20254%201027%20L%20303%20983%20L%20304%20984%20L%20256%201055%20L%20232%201098%20L%20227%201115%20L%20228%201125%20L%20232%201131%20L%20239%201134%20L%20248%201135%20L%20264%201132%20L%20274%201128%20L%20280%201134%20L%20289%201137%20L%20311%201137%20L%20339%201130%20L%20371%201114%20L%20401%201091%20L%20422%201070%20L%20440%201048%20L%20463%201013%20L%20487%20995%20L%20595%20900%20L%20613%20919%20L%20639%20951%20L%20639%20958%20L%20636%20961%20L%20619%20963%20L%20613%20966%20L%20605%20975%20L%20602%20991%20L%20604%20998%20L%20608%201003%20L%20611%20993%20L%20620%20985%20L%20624%20986%20L%20632%20983%20L%20647%20981%20L%20658%20982%20L%20677%20990%20L%20689%201006%20L%20697%201009%20L%20698%201021%20L%20694%201035%20L%20703%201029%20L%20710%201018%20L%20712%201010%20L%20712%201002%20L%20710%20997%20L%20711%20992%20L%20720%201003%20L%20728%201005%20L%20730%201008%20L%20731%201021%20L%20728%201032%20L%20738%201023%20L%20742%201014%20L%20743%201003%20L%20748%20994%20L%20748%20980%20L%20742%20966%20L%20739%20963%20L%20741%20961%20L%20753%20960%20L%20770%20964%20L%20780%20971%20L%20790%20984%20L%20799%20986%20L%20802%20992%20L%20802%201005%20L%20799%201012%20L%20804%201010%20L%20809%201005%20L%20815%20993%20L%20813%20964%20L%20817%20968%20L%20823%20969%20L%20826%20972%20L%20830%20982%20L%20831%20992%20L%20834%20989%20L%20838%20975%20L%20837%20966%20L%20831%20950%20L%20821%20940%20L%20804%20934%20L%20779%20936%20L%20763%20932%20L%20731%20899%20L%20702%20865%20L%20702%20863%20L%20717%20842%20L%20718%20844%20L%20708%20862%20L%20749%20835%20L%20781%20809%20L%20820%20771%20L%20851%20733%20L%20852%20736%20L%20846%20755%20L%20857%20746%20L%20881%20720%20L%20907%20683%20L%20928%20644%20L%20945%20604%20L%20967%20537%20L%20978%20489%20L%20982%20455%20L%20982%20437%20L%20979%20413%20L%20975%20396%20L%20967%20377%20L%20968%20374%20L%20975%20382%20L%20976%20332%20L%20972%20299%20L%20962%20257%20L%20954%20235%20L%20943%20212%20L%20923%20183%20Z%20M%20624%20888%20L%20666%20870%20L%20729%20934%20L%20729%20940%20L%20726%20943%20L%20716%20944%20L%20706%20951%20L%20684%20952%20L%20677%20950%20L%20665%20940%20Z%20M%20701%20220%20L%20710%20219%20L%20717%20221%20L%20704%20223%20L%20704%20221%20Z%20M%20666%20217%20L%20679%20216%20L%20689%20218%20L%20685%20220%20L%20675%20220%20Z%20M%20658%20210%20L%20661%20208%20L%20686%20205%20L%20706%20206%20L%20725%20209%20L%20733%20217%20L%20741%20220%20L%20738%20221%20L%20696%20214%20L%20661%20212%20Z%20M%20788%20181%20L%20793%20182%20L%20797%20188%20L%20792%20195%20L%20786%20195%20L%20782%20190%20L%20783%20185%20Z'/%3e%3cpath%20fill='%23000000'%20d='M%20893%20155%20L%20864%20139%20L%20836%20130%20L%20813%20127%20L%20796%20127%20L%20774%20130%20L%20749%20138%20L%20735%20145%20L%20720%20155%20L%20702%20172%20L%20687%20173%20L%20700%20174%20L%20694%20181%20L%20674%20184%20L%20670%20180%20L%20665%20183%20L%20657%20184%20L%20639%20196%20L%20641%20198%20L%20647%20198%20L%20648%20194%20L%20654%20193%20L%20686%20204%20L%20724%20208%20L%20740%20213%20L%20751%20222%20L%20750%20223%20L%20693%20214%20L%20671%20212%20L%20639%20212%20L%20636%20211%20L%20634%20207%20L%20629%20207%20L%20626%20210%20L%20617%20210%20L%20614%20208%20L%20604%20207%20L%20594%20213%20L%20582%20216%20L%20580%20214%20L%20581%20210%20L%20576%20208%20L%20586%20200%20L%20565%20218%20L%20563%20222%20L%20618%20239%20L%20671%20251%20L%20682%20256%20L%20689%20261%20L%20702%20276%20L%20706%20288%20L%20704%20301%20L%20700%20308%20L%20683%20322%20L%20630%20348%20L%20585%20379%20L%20563%20398%20L%20523%20441%20L%20497%20475%20L%20459%20536%20L%20443%20567%20L%20431%20595%20L%20411%20626%20L%20387%20670%20L%20386%20676%20L%20390%20677%20L%20391%20679%20L%20368%20706%20L%20333%20752%20L%20290%20818%20L%20281%20837%20L%20282%20844%20L%20300%20839%20L%20324%20825%20L%20326%20826%20L%20285%20884%20L%20242%20954%20L%20207%201020%20L%20192%201060%20L%20192%201071%20L%20196%201071%20L%20211%201062%20L%20240%201039%20L%20303%20983%20L%20305%20984%20L%20277%201023%20L%20242%201078%20L%20228%201110%20L%20227%201119%20L%20231%201130%20L%20239%201134%20L%20255%201134%20L%20274%201128%20L%20280%201134%20L%20285%201136%20L%20310%201137%20L%20339%201130%20L%20374%201112%20L%20394%201097%20L%20421%201071%20L%20445%201041%20L%20463%201013%20L%20488%20994%20L%20595%20900%20L%20614%20920%20L%20639%20951%20L%20639%20958%20L%20636%20961%20L%20619%20963%20L%20613%20966%20L%20607%20972%20L%20603%20980%20L%20602%20989%20L%20603%20995%20L%20608%201003%20L%20610%20994%20L%20620%20985%20L%20623%20986%20L%20631%20983%20L%20647%20981%20L%20665%20984%20L%20676%20989%20L%20689%201006%20L%20695%201007%20L%20697%201009%20L%20698%201023%20L%20694%201035%20L%20699%201032%20L%20709%201020%20L%20712%201010%20L%20710%20997%20L%20711%20992%20L%20719%201002%20L%20725%201005%20L%20727%201004%20L%20730%201008%20L%20731%201022%20L%20728%201032%20L%20739%201021%20L%20743%201010%20L%20743%201003%20L%20748%20993%20L%20748%20981%20L%20746%20974%20L%20739%20963%20L%20741%20961%20L%20755%20960%20L%20770%20964%20L%20779%20970%20L%20789%20983%20L%20799%20986%20L%20802%20992%20L%20802%201005%20L%20799%201012%20L%20808%201006%20L%20815%20993%20L%20813%20964%20L%20817%20968%20L%20825%20970%20L%20829%20978%20L%20831%20992%20L%20837%20981%20L%20837%20966%20L%20832%20952%20L%20822%20941%20L%20813%20936%20L%20803%20934%20L%20776%20936%20L%20763%20932%20L%20723%20890%20L%20702%20865%20L%20702%20863%20L%20717%20841%20L%20719%20842%20L%20708%20862%20L%20742%20840%20L%20782%20808%20L%20818%20773%20L%20851%20733%20L%20852%20736%20L%20846%20754%20L%20850%20752%20L%20881%20720%20L%20905%20686%20L%20932%20635%20L%20950%20590%20L%20969%20529%20L%20977%20494%20L%20982%20454%20L%20980%20419%20L%20975%20396%20L%20967%20377%20L%20968%20374%20L%20975%20382%20L%20976%20334%20L%20969%20284%20L%20959%20248%20L%20941%20209%20L%20921%20181%20Z%20M%20624%20888%20L%20666%20870%20L%20729%20934%20L%20729%20941%20L%20724%20944%20L%20716%20944%20L%20707%20951%20L%20683%20952%20L%20673%20948%20L%20659%20933%20Z%20M%20563%20618%20L%20569%20614%20L%20579%20621%20L%20576%20626%20L%20572%20627%20L%20568%20623%20L%20565%20623%20Z%20M%20575%20603%20L%20578%20603%20L%20588%20613%20L%20588%20619%20L%20585%20621%20L%20582%20620%20L%20575%20613%20L%20576%20612%20L%20573%20605%20Z%20M%20748%20398%20L%20752%20412%20L%20752%20432%20L%20748%20444%20L%20732%20470%20L%20716%20484%20L%20698%20493%20L%20685%20494%20L%20682%20491%20L%20707%20445%20L%20685%20473%20L%20667%20492%20L%20647%20508%20L%20632%20517%20L%20619%20519%20L%20615%20517%20L%20615%20513%20L%20643%20470%20L%20603%20514%20L%20589%20526%20L%20569%20538%20L%20558%20540%20L%20554%20539%20L%20552%20535%20L%20577%20498%20L%20549%20528%20L%20526%20546%20L%20508%20554%20L%20498%20555%20L%20495%20552%20L%20501%20541%20L%20482%20555%20L%20471%20558%20L%20464%20558%20L%20461%20560%20L%20460%20559%20L%20461%20555%20L%20469%20547%20L%20473%20537%20L%20491%20512%20L%20511%20497%20L%20582%20434%20L%20617%20408%20L%20632%20399%20L%20659%20386%20L%20677%20380%20L%20695%20377%20L%20712%20377%20L%20725%20380%20L%20739%20388%20Z%20M%20625%20216%20L%20683%20216%20L%20737%20222%20L%20742%20224%20L%20733%20226%20L%20721%20225%20L%20713%20230%20L%20711%20228%20L%20706%20228%20L%20694%20237%20L%20690%20233%20L%20690%20225%20L%20679%20228%20L%20674%20232%20L%20668%20232%20L%20659%20229%20L%20649%20222%20L%20632%20219%20L%20632%20217%20Z%20M%20788%20181%20L%20793%20182%20L%20797%20187%20L%20795%20193%20L%20792%20195%20L%20786%20195%20L%20782%20190%20L%20783%20185%20Z'/%3e%3cpath%20fill='%23000000'%20d='M%20607%20972%20L%20603%20981%20L%20603%20995%20L%20608%201003%20L%20610%20994%20L%20616%20987%20L%20620%20985%20L%20627%20985%20L%20635%20982%20L%20655%20981%20L%20676%20989%20L%20683%20996%20L%20689%201006%20L%20695%201007%20L%20697%201009%20L%20698%201024%20L%20694%201034%20L%20699%201032%20L%20706%201025%20L%20712%201010%20L%20710%20994%20L%20709%20997%20L%20706%20994%20L%20700%20994%20L%20694%20997%20L%20672%20976%20L%20658%20974%20L%20647%20967%20L%20642%20974%20L%20638%20974%20L%20636%20972%20L%20636%20961%20L%20629%20963%20L%20631%20972%20L%20625%20977%20L%20620%20977%20L%20613%20967%20L%20614%20966%20Z%20M%20686%20966%20L%20704%20980%20L%20719%201002%20L%20727%201004%20L%20730%201007%20L%20731%201023%20L%20728%201032%20L%20731%201030%20L%20741%201017%20L%20743%201003%20L%20748%20992%20L%20748%20981%20L%20742%20967%20L%20740%20965%20L%20742%20968%20L%20735%20972%20L%20726%20965%20L%20705%20960%20L%20724%20968%20L%20740%20983%20L%20742%20987%20L%20740%20991%20L%20732%20991%20L%20728%20996%20L%20726%20996%20L%20721%20992%20L%20713%20979%20Z%20M%20784%20943%20L%20784%20946%20L%20798%20950%20L%20819%20969%20L%20825%20970%20L%20830%20981%20L%20831%20992%20L%20837%20981%20L%20837%20966%20L%20829%20948%20L%20825%20944%20L%20830%20950%20L%20821%20957%20L%20813%20951%20L%20794%20942%20Z%20M%20659%20933%20L%20662%20937%20L%20658%20942%20L%20658%20945%20L%20663%20947%20L%20666%20945%20L%20667%20946%20L%20668%20944%20L%20670%20946%20Z%20M%20643%20913%20L%20647%20918%20L%20644%20921%20L%20642%20928%20L%20629%20938%20L%20627%20936%20L%20636%20947%20L%20634%20945%20L%20641%20933%20L%20651%20923%20L%20657%20930%20Z%20M%20936%20310%20L%20934%20309%20L%20945%20338%20L%20949%20368%20L%20949%20381%20L%20947%20383%20L%20935%20367%20L%20914%20348%20L%20929%20377%20L%20934%20397%20L%20936%20414%20L%20935%20440%20L%20933%20442%20L%20930%20440%20L%20924%20419%20L%20912%20395%20L%20909%20393%20L%20910%20410%20L%20907%20433%20L%20899%20458%20L%20895%20462%20L%20893%20460%20L%20892%20445%20L%20888%20427%20L%20873%20390%20L%20873%20416%20L%20871%20430%20L%20866%20448%20L%20862%20454%20L%20859%20452%20L%20853%20433%20L%20842%20410%20L%20823%20382%20L%20807%20365%20L%20805%20366%20L%20811%20394%20L%20812%20416%20L%20810%20425%20L%20806%20428%20L%20801%20423%20L%20790%20403%20L%20772%20383%20L%20759%20372%20L%20733%20357%20L%20724%20355%20L%20705%20346%20L%20693%20344%20L%20669%20344%20L%20646%20349%20L%20636%20353%20L%20662%20349%20L%20677%20349%20L%20699%20353%20L%20708%20356%20L%20725%20366%20L%20736%20376%20L%20745%20389%20L%20753%20415%20L%20752%20442%20L%20743%20476%20L%20723%20520%20L%20693%20569%20L%20646%20631%20L%20605%20676%20L%20568%20709%20L%20565%20710%20L%20562%20706%20L%20562%20692%20L%20567%20665%20L%20578%20636%20L%20602%20619%20L%20622%20602%20L%20645%20578%20L%20670%20546%20L%20630%20588%20L%20603%20610%20L%20574%20628%20L%20560%20633%20L%20556%20633%20L%20555%20631%20L%20563%20617%20L%20596%20572%20L%20647%20509%20L%20636%20515%20L%20562%20609%20L%20541%20630%20L%20520%20645%20L%20502%20653%20L%20490%20653%20L%20536%20590%20L%20513%20617%20L%20478%20651%20L%20454%20665%20L%20440%20669%20L%20436%20667%20L%20488%20596%20L%20459%20630%20L%20436%20653%20L%20411%20671%20L%20392%20678%20L%20372%20701%20L%20319%20772%20L%20289%20820%20L%20281%20838%20L%20282%20844%20L%20295%20841%20L%20324%20825%20L%20326%20826%20L%20289%20878%20L%20242%20954%20L%20209%201016%20L%20193%201056%20L%20191%201069%20L%20192%201071%20L%20196%201071%20L%20219%201056%20L%20303%20983%20L%20305%20984%20L%20255%201057%20L%20234%201094%20L%20228%201111%20L%20228%201124%20L%20234%201132%20L%20240%201134%20L%20254%201134%20L%20275%201128%20L%20279%201133%20L%20291%201137%20L%20316%201136%20L%20344%201128%20L%20364%201118%20L%20389%201101%20L%20403%201089%20L%20427%201064%20L%20447%201038%20L%20463%201013%20L%20489%20993%20L%20594%20901%20L%20595%20899%20L%20593%20894%20L%20588%20891%20L%20567%20868%20L%20569%20865%20L%20576%20867%20L%20586%20873%20L%20589%20867%20L%20607%20867%20L%20619%20880%20L%20622%20887%20L%20626%20891%20L%20624%20889%20L%20626%20886%20L%20666%20870%20L%20705%20909%20L%20710%20905%20L%20713%20905%20L%20716%20908%20L%20716%20911%20L%20712%20916%20L%20728%20932%20L%20730%20939%20L%20727%20943%20L%20717%20944%20L%20710%20949%20L%20712%20948%20L%20725%20951%20L%20739%20963%20L%20740%20961%20L%20756%20960%20L%20774%20966%20L%20784%20975%20L%20789%20983%20L%20800%20987%20L%20802%20991%20L%20802%201005%20L%20799%201012%20L%20806%201008%20L%20811%201002%20L%20815%20993%20L%20813%20968%20L%20812%20973%20L%20807%20971%20L%20796%20976%20L%20791%20973%20L%20788%20967%20L%20779%20958%20L%20767%20952%20L%20762%20941%20L%20756%20940%20L%20751%20932%20L%20749%20931%20L%20743%20936%20L%20738%20936%20L%20736%20933%20L%20741%20925%20L%20739%20915%20L%20742%20912%20L%20745%20914%20L%20702%20865%20L%20702%20863%20L%20717%20841%20L%20719%20842%20L%20709%20861%20L%20746%20837%20L%20783%20807%20L%20826%20764%20L%20851%20732%20L%20853%20734%20L%20846%20754%20L%20851%20751%20L%20874%20728%20L%20890%20708%20L%20908%20681%20L%20938%20621%20L%20960%20560%20L%20971%20521%20L%20979%20481%20L%20982%20444%20L%20977%20405%20L%20970%20382%20L%20966%20375%20L%20968%20374%20L%20974%20381%20L%20975%20368%20L%20974%20372%20L%20968%20364%20L%20966%20354%20L%20957%20337%20Z%20M%20247%201077%20L%20251%201085%20L%20246%201088%20L%20245%201091%20L%20240%201092%20L%20239%201085%20L%20241%201081%20Z%20M%20370%20935%20L%20371%20937%20L%20326%20997%20L%20301%201034%20L%20289%201044%20L%20286%201042%20L%20283%201045%20L%20277%201045%20L%20273%201043%20L%20264%201052%20L%20261%201049%20L%20270%201037%20L%20271%201038%20L%20281%201027%20L%20293%201010%20L%20326%20971%20Z%20M%20726%20921%20L%20728%20921%20L%20731%20926%20L%20729%20933%20L%20722%20926%20Z%20M%20729%20897%20L%20734%20902%20L%20732%20906%20L%20726%20902%20L%20726%20899%20Z%20M%20715%20882%20L%20720%20886%20L%20718%20890%20L%20711%20887%20Z%20M%20605%20762%20L%20608%20761%20L%20607%20760%20L%20609%20757%20L%20610%20759%20L%20617%20761%20L%20620%20764%20L%20611%20770%20L%20607%20770%20L%20606%20765%20L%20608%20765%20Z%20M%20611%20754%20L%20617%20750%20L%20620%20751%20L%20621%20749%20L%20625%20749%20L%20628%20752%20L%20628%20756%20L%20623%20761%20Z%20M%20549%20710%20L%20551%20713%20L%20551%20722%20L%20506%20774%20L%20403%20878%20L%20309%20963%20L%20307%20961%20L%20313%20953%20L%20322%20945%20L%20340%20922%20L%20375%20885%20Z%20M%20695%20704%20L%20696%20708%20L%20698%20709%20L%20695%20710%20L%20695%20713%20L%20692%20715%20L%20688%20710%20Z%20M%20704%20694%20L%20706%20695%20L%20706%20698%20L%20710%20698%20L%20713%20703%20L%20708%20708%20L%20707%20714%20L%20705%20716%20L%20701%20716%20L%20697%20708%20L%20699%20706%20L%20697%20705%20L%20701%20701%20L%20704%20705%20L%20706%20704%20L%20706%20700%20L%20702%20697%20Z%20M%20552%20678%20L%20554%20681%20L%20552%20701%20L%20465%20787%20L%20361%20883%20L%20316%20927%20L%20253%20993%20L%20211%201042%20L%20211%201036%20L%20222%201011%20L%20261%20942%20L%20292%20898%20L%20338%20843%20L%20374%20807%20L%20387%20807%20L%20403%20801%20L%20419%20792%20L%20456%20766%20L%20500%20728%20Z%20M%20704%20654%20L%20706%20658%20L%20711%20658%20L%20714%20661%20L%20715%20673%20L%20713%20676%20L%20705%20676%20L%20702%20674%20L%20704%20677%20L%20704%20681%20L%20702%20682%20L%20704%20685%20L%20700%20686%20L%20694%20678%20L%20691%20678%20L%20688%20674%20L%20692%20670%20L%20692%20664%20L%20694%20661%20Z%20M%20565%20642%20L%20558%20663%20L%20545%20677%20L%20494%20726%20L%20443%20767%20L%20401%20792%20L%20392%20795%20L%20387%20794%20L%20405%20766%20L%20455%20707%20L%20388%20767%20L%20355%20794%20L%20304%20827%20L%20299%20826%20L%20302%20817%20L%20316%20795%20L%20360%20739%20L%20398%20700%20L%20425%20677%20L%20428%20679%20L%20443%20679%20L%20461%20673%20L%20476%20664%20L%20493%20666%20L%20517%20657%20L%20542%20641%20L%20546%20643%20Z%20M%20563%20221%20L%20572%20225%20L%20571%20223%20L%20574%20220%20L%20585%20217%20L%20596%20218%20L%20607%20215%20L%20653%20214%20L%20706%20218%20L%20749%20224%20L%20749%20226%20L%20735%20233%20L%20732%20238%20L%20732%20249%20L%20693%20242%20L%20620%20239%20L%20672%20251%20L%20688%20260%20L%20701%20274%20L%20706%20287%20L%20704%20301%20L%20707%20297%20L%20711%20300%20L%20713%20309%20L%20720%20324%20L%20723%20327%20L%20724%20308%20L%20726%20304%20L%20735%20318%20L%20757%20341%20L%20753%20319%20L%20754%20313%20L%20760%20317%20L%20786%20345%20L%20784%20324%20L%20775%20300%20L%20779%20300%20L%20800%20317%20L%20800%20312%20L%20787%20283%20L%20772%20264%20L%20778%20263%20L%20800%20271%20L%20803%20270%20L%20770%20236%20L%20772%20234%20L%20797%20234%20L%20807%20230%20L%20801%20230%20L%20800%20228%20L%20811%20218%20L%20818%20203%20L%20818%20193%20L%20812%20179%20L%20798%20168%20L%20784%20166%20L%20760%20173%20L%20747%20173%20L%20712%20163%20L%20718%20157%20L%20703%20171%20L%20711%20173%20L%20720%20178%20L%20723%20182%20L%20720%20186%20L%20695%20190%20L%20680%20190%20L%20670%20193%20L%20656%20193%20L%20687%20204%20L%20729%20209%20L%20742%20214%20L%20753%20223%20L%20752%20224%20L%20729%20219%20L%20669%20212%20L%20610%20213%20L%20576%20218%20L%20565%20221%20L%20565%20219%20Z%20M%20771%20189%20L%20772%20201%20L%20775%20207%20L%20780%20212%20L%20787%20215%20L%20796%20216%20L%20796%20218%20L%20784%20219%20L%20776%20215%20L%20771%20210%20L%20768%20204%20L%20768%20194%20Z%20M%20788%20181%20L%20793%20182%20L%20797%20187%20L%20796%20192%20L%20790%20196%20L%20786%20195%20L%20782%20191%20L%20782%20186%20Z'/%3e%3c/g%3e%3c/svg%3e`;function me({className:e}){return(0,P.jsx)(`span`,{className:`block overflow-hidden rounded-[20.7%] bg-accent ${e??``}`,children:(0,P.jsx)(`img`,{src:pe,alt:``,"aria-hidden":`true`,className:`h-full w-full`})})}function he({open:e}){return(0,P.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2.5`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`h-3.5 w-3.5 shrink-0 transition-transform ${e?`rotate-90`:``}`,children:(0,P.jsx)(`path`,{d:`m9 18 6-6-6-6`})})}function ge({className:e=`h-4 w-4`}){return(0,P.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`shrink-0 ${e}`,children:[(0,P.jsx)(`path`,{d:`M3 6h.01`}),(0,P.jsx)(`path`,{d:`M3 12h.01`}),(0,P.jsx)(`path`,{d:`M3 18h.01`}),(0,P.jsx)(`path`,{d:`M8 6h13`}),(0,P.jsx)(`path`,{d:`M8 12h13`}),(0,P.jsx)(`path`,{d:`M8 18h13`})]})}function _e({className:e=`h-4 w-4`}){return(0,P.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`shrink-0 ${e}`,children:[(0,P.jsx)(`path`,{d:`M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z`}),(0,P.jsx)(`path`,{d:`M14 2v4a2 2 0 0 0 2 2h4`}),(0,P.jsx)(`path`,{d:`M10 9H8`}),(0,P.jsx)(`path`,{d:`M16 13H8`}),(0,P.jsx)(`path`,{d:`M16 17H8`})]})}function ve({className:e=`h-4 w-4`}){return(0,P.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`shrink-0 ${e}`,children:[(0,P.jsx)(`path`,{d:`m4 17 6-6-6-6`}),(0,P.jsx)(`path`,{d:`M12 19h8`})]})}function ye({repos:e,currentId:t,onSelect:n,onCloseProject:r,onOpenPicker:i,className:a=``}){let[o,s]=(0,v.useState)(!1),c=(0,v.useRef)(null),l=e.find(e=>e.id===t);return(0,v.useEffect)(()=>{if(!o)return;let e=e=>{e.key===`Escape`&&(s(!1),c.current?.focus())};return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[o]),(0,P.jsxs)(`div`,{className:`relative ${a}`,children:[(0,P.jsxs)(`button`,{ref:c,onClick:()=>s(e=>!e),"aria-haspopup":`menu`,"aria-expanded":o,title:l?.display_path??`Select a project`,className:`flex max-w-[9rem] items-center gap-1 rounded-sm bg-ink-700 py-0.5 pl-2 pr-1 text-ink-50`,children:[(0,P.jsx)(`span`,{className:`truncate`,children:l?.name??`No project`}),(0,P.jsx)(he,{open:o})]}),o&&(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(`div`,{className:`fixed inset-0 z-40`,onClick:()=>s(!1)}),(0,P.jsxs)(`div`,{role:`menu`,className:`absolute left-0 z-50 mt-1 max-h-[70vh] w-56 max-w-[80vw] overflow-y-auto rounded-md border border-ink-700 bg-ink-900 py-1 shadow-lg`,children:[e.length===0&&(0,P.jsx)(`p`,{className:`px-3 py-1.5 text-ink-400`,children:`No projects open.`}),e.map(e=>(0,P.jsxs)(`div`,{className:`flex items-center ${e.id===t?`bg-ink-700 text-ink-50`:`text-ink-200`}`,children:[(0,P.jsx)(`button`,{role:`menuitem`,onClick:()=>{n(e.id),s(!1)},title:e.display_path,className:`min-w-0 flex-1 truncate py-1.5 pl-3 pr-1 text-left hover:text-accent`,children:e.name}),(0,P.jsx)(`button`,{onClick:()=>r(e.id),"aria-label":`close ${e.name}`,title:`Close project`,className:`mr-1 flex h-6 w-6 shrink-0 items-center justify-center rounded-sm text-ink-400 hover:text-removed`,children:(0,P.jsx)(F,{className:`h-3.5 w-3.5`})})]},e.id)),(0,P.jsx)(`div`,{className:`my-1 border-t border-ink-800`}),(0,P.jsxs)(`button`,{role:`menuitem`,onClick:()=>{i(),s(!1)},className:`flex w-full items-center gap-1 px-3 py-1.5 text-left text-ink-400 hover:text-ink-200`,children:[(0,P.jsx)(ce,{className:`h-3.5 w-3.5`}),`open`]})]})]})]})}function be(){let[e,t]=(0,v.useState)(!1),n=(0,v.useRef)(!1);return{reload:(0,v.useCallback)(async()=>{if(!n.current){n.current=!0,t(!0);try{oe.success(await O.reloadConfig())}catch(e){oe.error(e instanceof Error?e.message:`could not reload the config`)}finally{n.current=!1,t(!1)}}},[]),pending:e}}function xe({repos:e,repo:t,onSelectRepo:n,onCloseRepo:r,onOpenPicker:i,cloning:a,accent:o,next:s,cycle:c,draggingRepo:l,dragOverRepo:u,onRepoDragStart:d,onRepoDragMove:f,onRepoDragEnd:p}){let{reload:m,pending:h}=be();return(0,P.jsxs)(`header`,{className:`flex items-center gap-2 border-b border-ink-700 bg-ink-900 px-[12.8px] py-[8.8px]`,children:[(0,P.jsx)(me,{className:`h-[22px] w-[22px] shrink-0`}),(0,P.jsx)(`span`,{className:`text-[16px] font-medium tracking-[0.04em] text-ink-50`,children:`nightcrow`}),(0,P.jsx)(`span`,{className:`hidden font-sans text-[10px] uppercase tracking-[0.18em] text-ink-400 sm:inline`,children:`web viewer`}),(0,P.jsx)(ye,{className:`md:hidden`,repos:e,currentId:t,onSelect:n,onCloseProject:r,onOpenPicker:i}),(0,P.jsx)(`nav`,{className:`-my-[8.8px] hidden items-stretch self-stretch overflow-x-auto pl-1 md:flex`,children:e.map(i=>(0,P.jsxs)(`div`,{"data-repo-id":i.id,onPointerDown:e=>d(e,i.id),onPointerMove:f,onPointerUp:p,onPointerCancel:p,onLostPointerCapture:p,className:`flex items-center border-r border-ink-700 whitespace-nowrap ${e.length>1?`touch-none`:``} ${l===i.id?`opacity-60`:``} ${u===i.id?`bg-ink-800 ring-1 ring-inset ring-accent`:``} ${i.id===t?`bg-ink-950 text-ink-50 shadow-[inset_0_2px_0_0_var(--color-accent)]`:`text-ink-400 hover:bg-ink-850 hover:text-ink-200`}`,title:i.display_path,children:[(0,P.jsx)(`button`,{onClick:()=>{n(i.id)},className:`self-stretch pl-3 pr-1`,children:i.name}),(0,P.jsx)(`button`,{onClick:e=>{e.stopPropagation(),r(i.id)},"data-tab-close":!0,title:`Close project`,"aria-label":`close ${i.name}`,className:`mr-1 flex h-5 w-5 items-center justify-center rounded-sm text-ink-400 hover:bg-ink-700 hover:text-removed`,children:(0,P.jsx)(F,{className:`h-3.5 w-3.5`})})]},i.id))}),(0,P.jsxs)(`button`,{onClick:i,title:`Open a project`,className:`hidden shrink-0 items-center gap-1 rounded-sm px-2 py-0.5 text-ink-400 hover:text-ink-200 md:inline-flex`,children:[(0,P.jsx)(ce,{className:`h-3.5 w-3.5`}),`open`]}),a&&(0,P.jsxs)(`span`,{role:`status`,title:`A clone is running on the server`,className:`flex shrink-0 items-center gap-1.5 px-2 py-0.5 text-ink-400`,children:[(0,P.jsx)(`span`,{"aria-hidden":`true`,className:`h-1.5 w-1.5 animate-pulse rounded-full bg-accent`}),`Cloning…`]}),(0,P.jsx)(`button`,{onClick:c,title:`Accent: ${o.name} (click for ${s.name})`,"aria-label":`accent colour: ${o.name}, click for ${s.name}`,className:`ml-auto flex h-6 w-6 shrink-0 items-center justify-center rounded-sm`,children:(0,P.jsx)(`span`,{"aria-hidden":`true`,className:`h-3 w-3 rounded-full bg-accent ring-1 ring-ink-600`})}),(0,P.jsx)(`button`,{onClick:m,disabled:h,title:`Reload config.toml on the server (does not reload this page)`,"aria-label":`reload the server config`,className:`ml-1 flex h-6 w-6 shrink-0 items-center justify-center rounded-sm text-ink-400 hover:bg-ink-700 hover:text-ink-200 disabled:cursor-progress disabled:text-ink-500 disabled:hover:bg-transparent`,children:(0,P.jsx)(de,{className:`h-3.5 w-3.5 ${h?`animate-spin`:``}`})}),(0,P.jsx)(`a`,{href:`/logout`,title:`Sign out`,"aria-label":`sign out`,className:`ml-1 flex h-6 w-6 shrink-0 items-center justify-center rounded-sm text-ink-400 hover:bg-ink-700 hover:text-ink-200`,children:(0,P.jsx)(ue,{className:`h-3.5 w-3.5`})})]})}function Se(){return(0,P.jsx)(`div`,{className:`flex h-full items-center justify-center p-6`,children:(0,P.jsxs)(`div`,{className:`flex flex-col items-center gap-3 text-ink-400`,children:[(0,P.jsx)(me,{className:`h-12 w-12 animate-pulse`}),(0,P.jsx)(`span`,{className:`text-[0.72rem] tracking-[0.18em] uppercase`,children:`Loading…`})]})})}function Ce({onSuccess:e}){let[t,n]=(0,v.useState)(``),[r,i]=(0,v.useState)(null),[a,o]=(0,v.useState)(!1);return(0,P.jsx)(`div`,{className:`flex h-full items-center justify-center p-6`,children:(0,P.jsxs)(`form`,{onSubmit:async n=>{n.preventDefault(),o(!0),i(null);try{await O.login(t),e()}catch(e){i(e instanceof Error?e.message:`login failed`)}finally{o(!1)}},className:`w-[17rem] max-w-[86vw]`,children:[(0,P.jsx)(me,{className:`mx-auto mb-3 block h-10 w-10`}),(0,P.jsx)(`h1`,{className:`text-center text-lg font-medium tracking-wide text-ink-50`,children:`nightcrow`}),(0,P.jsx)(`p`,{className:`mt-1 mb-5 text-center text-[0.62rem] tracking-[0.18em] text-ink-400 uppercase`,children:`web viewer`}),r&&(0,P.jsx)(`p`,{className:`mb-2.5 text-center text-removed`,children:r}),(0,P.jsx)(`input`,{type:`password`,autoFocus:!0,value:t,onChange:e=>n(e.target.value),placeholder:`password`,className:`mb-2 w-full rounded-md border border-ink-700 bg-ink-900 px-2.5 py-1.5 outline-none placeholder:text-ink-400 focus:border-accent focus:ring-[3px] focus:ring-accent/15`}),(0,P.jsx)(`button`,{type:`submit`,disabled:a,className:`w-full rounded-md bg-ink-50 py-1.5 font-semibold text-ink-950 hover:bg-white disabled:opacity-50`,children:a?`Signing in…`:`Sign in`})]})})}var we=`nightcrow.sidebarWidth`,Te=.5;function Ee(e){return Math.min(Math.max(Math.round(e),280),720)}function De(e){let t=720;try{t=Math.min(t,Math.round(window.innerWidth*Te))}catch{}return Math.min(Math.max(Math.round(e),280),Math.max(t,280))}function Oe(){try{let e=Number(localStorage.getItem(we));return Number.isFinite(e)&&e>0?Ee(e):460}catch{return 460}}function ke(e){try{localStorage.setItem(we,String(e))}catch{}}function Ae(){let[e,t]=(0,v.useState)(Oe);return{width:e,resize:(0,v.useCallback)(e=>{let n=De(e);t(n),ke(n)},[]),commit:(0,v.useCallback)(e=>{let n=De(e);t(n),ke(n),O.setSidebarWidth(n).catch(()=>{})},[]),reset:(0,v.useCallback)(()=>{let e=Ee(460);t(e),ke(e),O.setSidebarWidth(e).catch(()=>{})},[]),adopt:(0,v.useCallback)(e=>{t(t=>{let n=Ee(e);return n===t?t:(ke(n),n)})},[])}}function je(){let e=new Map;return{start(t){let n=(e.get(t)??0)+1;return e.set(t,n),n},isCurrent(t,n){return e.get(t)===n}}}var Me={children:{},expanded:new Set};function Ne(e,t,n){return{...e,children:{...e.children,[t]:n}}}function Pe(e,t){let n=new Set(e.expanded);return n.delete(t)||n.add(t),{...e,expanded:n}}function Fe(e,t){let n=new Set(e.expanded);return t.forEach(e=>n.add(e)),{...e,expanded:n}}function Ie(e){let t=[],n=``;for(let r of e.split(`/`))n=n?`${n}/${r}`:r,t.push(n);return t}var Le=180,Re={items:[],truncated:!1};function ze({repo:e,authed:t,tab:n,filter:r,filterOpen:i,handle:a}){let[o,s]=(0,v.useState)(Me),[c,l]=(0,v.useState)(Re),[u,d]=(0,v.useState)(!1),[f]=(0,v.useState)(je);(0,v.useEffect)(()=>{if(!e||!t||n!==`tree`||!i||!r){l(Re),d(!1);return}d(!0);let o=!0,s=setTimeout(()=>{O.treeSearch(e,r).then(e=>{o&&l({items:e.matches,truncated:e.truncated})}).catch(e=>{o&&a(e)}).finally(()=>{o&&d(!1)})},Le);return()=>{o=!1,clearTimeout(s)}},[e,t,n,r,i,a]);let p=(0,v.useCallback)(t=>{if(!e)return;let n=f.start(t);O.tree(e,t).then(e=>{f.isCurrent(t,n)&&s(n=>Ne(n,t,e.entries))}).catch(e=>{f.isCurrent(t,n)&&a(e)})},[e,a,f]);(0,v.useEffect)(()=>{!e||!t||n!==`tree`||p(``)},[e,t,n,p]);let m=(0,v.useCallback)(e=>{let t=!o.expanded.has(e);s(t=>Pe(t,e)),t&&!(e in o.children)&&p(e)},[o,p]),h=(0,v.useCallback)(e=>{let t=Ie(e);s(e=>Fe(e,t)),t.forEach(e=>{e in o.children||p(e)})},[o,p]);return{treeChildren:o.children,treeExpanded:o.expanded,treeMatches:c.items,treeTruncated:c.truncated,treeSearchLoading:u,loadTreeChildren:p,toggleTreeDir:m,revealTreeDir:h}}function Be(e,t){let n=[],r=(i,a)=>{for(let o of e[i]??[]){let e=i?`${i}/${o.name}`:o.name;n.push({path:e,name:o.name,is_dir:o.is_dir,depth:a}),o.is_dir&&t.has(e)&&r(e,a+1)}};return r(``,0),n}function Ve({path:e,from:t,className:n}){return(0,P.jsx)(`span`,{className:`whitespace-nowrap ${n??``}`,title:t?`${t} → ${e}`:e,children:t?`${t} → ${e}`:e})}var He=1e3;function Ue(e,t){return e===void 0||e<=0?0:e-t}function We(e,t,n){let r=Ue(t,n);return e===null||Math.abs(r-e)>=1e3?r:e}function Ge(e,t,n){if(e===void 0)return`cool`;let r=Math.max(0,t-e);return r>=n?`cool`:r<5e3?`fresh`:`warm`}function Ke(e,t,n){return e.some(e=>Ge(e,t,n)!==`cool`)}var qe={fresh:`text-accent font-bold`,warm:`text-accent`,cool:``};function Je(e,t,n){let[r,i]=(0,v.useState)(()=>Date.now()+n);return(0,v.useEffect)(()=>{if(t<=0||!e)return;let r=e.map(e=>e.mtime),a=Date.now()+n;if(i(a),!Ke(r,a,t))return;let o=setInterval(()=>{let e=Date.now()+n;i(e),Ke(r,e,t)||clearInterval(o)},He);return()=>clearInterval(o)},[e,t,n]),r}function Ye(e){let t=Math.max(0,Math.floor(Date.now()/1e3-e));return t<60?`${t}s`:t<3600?`${Math.floor(t/60)}m`:t<86400?`${Math.floor(t/3600)}h`:t<86400*30?`${Math.floor(t/86400)}d`:t<86400*365?`${Math.floor(t/(86400*30))}mo`:`${Math.floor(t/(86400*365))}y`}function Xe(e){return e===`+`?`bg-added/10`:e===`-`?`bg-removed/10`:``}function Ze(e){return e===`?`?`text-ink-400`:e===`D`?`text-removed`:e===`A`?`text-added`:`text-accent`}function Qe({status:e,files:t,now:n,hotWindowMs:r,openDiff:i}){return e===null?(0,P.jsx)(`li`,{className:`px-3 py-2 text-ink-400`,children:`Loading…`}):(0,P.jsxs)(P.Fragment,{children:[t.map(e=>(0,P.jsx)(`li`,{children:(0,P.jsxs)(`button`,{onClick:()=>i(e.path),className:`flex w-max min-w-full gap-2 px-3 py-0.5 text-left hover:bg-ink-850`,children:[(0,P.jsxs)(`span`,{className:`shrink-0`,children:[(0,P.jsx)(`span`,{className:Ze(e.index),children:e.index===` `?` `:e.index}),(0,P.jsx)(`span`,{className:Ze(e.worktree),children:e.worktree===` `?` `:e.worktree})]}),(0,P.jsx)(Ve,{path:e.path,from:e.old_path,className:qe[Ge(e.mtime,n,r)]})]})},e.path)),e.truncated&&(0,P.jsxs)(`li`,{className:`px-3 py-1 text-accent`,children:[`Showing the first `,e.files.length,` changed files.`]})]})}function $e({visibleCommits:e,commits:t,aheadOids:n,commitDrillDown:r,visibleCommitFiles:i,logDone:a,logStalled:o,logPagingPaused:s,setLogStalled:c,logSentinelRef:l,openCommitFiles:u,openCommit:d,openCommitFileDiff:f,setCommitDrillDown:p,setPaneEmpty:m,bumpPaneRequest:h}){return(0,P.jsxs)(P.Fragment,{children:[!r&&e.map(e=>(0,P.jsx)(`li`,{children:(0,P.jsxs)(`button`,{onClick:()=>void u(e),title:`${e.author} · ${e.summary}`,className:`flex w-max min-w-full items-baseline gap-2 px-3 py-0.5 text-left hover:bg-ink-850`,children:[(0,P.jsx)(`span`,{className:`w-2 shrink-0 text-added`,children:n.has(e.oid)?`↑`:``}),(0,P.jsx)(`span`,{className:`shrink-0 text-accent`,children:e.short_id}),(0,P.jsx)(`span`,{className:`w-10 shrink-0 text-right text-ink-400`,children:Ye(e.time)}),(0,P.jsx)(`span`,{className:`max-w-[6rem] shrink-0 truncate text-ink-400`,children:e.author}),(0,P.jsx)(`span`,{className:`whitespace-nowrap`,children:e.summary})]})},e.oid)),!r&&!a&&!o&&!s&&(0,P.jsx)(`li`,{ref:l,className:`px-3 py-1 text-ink-400`,"aria-hidden":`true`,children:`loading…`}),!r&&!a&&!o&&s&&(0,P.jsxs)(`li`,{className:`px-3 py-1 text-ink-400`,children:[`filtering `,t.length,` loaded commits — clear the filter to load more`]}),!r&&o&&(0,P.jsx)(`li`,{className:`px-3 py-1`,children:(0,P.jsx)(`button`,{onClick:()=>c(!1),className:`text-ink-400 hover:text-accent`,children:`could not load more — retry`})}),r&&(0,P.jsxs)(P.Fragment,{children:[(0,P.jsxs)(`li`,{className:`sticky top-0 z-10 flex w-max min-w-full items-center gap-1 bg-ink-900 px-2 py-1 text-ink-400`,children:[(0,P.jsx)(`button`,{onClick:()=>{h(),p(null),m()},className:`rounded-sm px-1 hover:text-accent`,title:`Back to commit log`,children:`< log`}),(0,P.jsx)(`span`,{className:`text-ink-600`,children:`·`}),(0,P.jsx)(`span`,{className:`shrink-0 text-accent`,children:r.commit.short_id}),(0,P.jsx)(`button`,{onClick:()=>d(r.commit.oid),className:`rounded-sm px-1 hover:text-accent`,title:`Show the complete commit diff`,children:`all changes`})]}),i.map(e=>(0,P.jsx)(`li`,{children:(0,P.jsxs)(`button`,{onClick:()=>f(r.commit.oid,e.path),className:`flex w-max min-w-full gap-2 px-3 py-0.5 text-left hover:bg-ink-850`,children:[(0,P.jsx)(`span`,{className:Ze(e.index),children:e.index}),(0,P.jsx)(Ve,{path:e.path,from:e.old_path})]})},e.path)),r.files.length===0&&(0,P.jsx)(`li`,{className:`px-3 py-2 text-ink-400`,children:`No changed files.`}),r.files.length>0&&i.length===0&&(0,P.jsx)(`li`,{className:`px-3 py-2 text-ink-400`,children:`No matching files.`}),r.truncated&&(0,P.jsxs)(`li`,{className:`px-3 py-1 text-accent`,children:[`Showing the first `,r.files.length,` files.`]})]})]})}function et({treeSearching:e,treeMatches:t,treeTruncated:n,treeSearchLoading:r,treeRows:i,treeExpanded:a,openFile:o,revealTreeDir:s,toggleTreeDir:c}){return e?(0,P.jsxs)(P.Fragment,{children:[t.map(e=>(0,P.jsx)(`li`,{children:(0,P.jsx)(`button`,{onClick:()=>{e.is_dir?s(e.path):o(e.path)},title:e.path,className:`w-max min-w-full whitespace-nowrap px-3 py-0.5 text-left hover:bg-ink-850`,children:e.is_dir?(0,P.jsxs)(`span`,{className:`text-accent`,children:[e.path,`/`]}):e.path})},e.path)),t.length===0&&(0,P.jsx)(`li`,{className:`px-3 py-0.5 text-ink-400`,children:r?`searching…`:`no matches`}),n&&(0,P.jsxs)(`li`,{className:`px-3 py-0.5 text-ink-400`,children:[`showing the first `,t.length,` matches`]})]}):(0,P.jsx)(P.Fragment,{children:i.map(e=>(0,P.jsx)(`li`,{children:(0,P.jsxs)(`button`,{onClick:()=>e.is_dir?c(e.path):o(e.path),title:e.path,style:{paddingLeft:`${e.depth*.75+.5}rem`},className:`flex w-max min-w-full items-center gap-1 py-0.5 pr-3 text-left hover:bg-ink-850`,children:[e.is_dir?(0,P.jsx)(he,{open:a.has(e.path)}):(0,P.jsx)(`span`,{className:`h-3.5 w-3.5 shrink-0`}),(0,P.jsx)(`span`,{className:`whitespace-nowrap ${e.is_dir?`text-accent`:``}`,children:e.is_dir?`${e.name}/`:e.name})]})},e.path))})}function tt(e){let{tab:t,setTab:n,filter:r,setFilter:i,filterOpen:a,setFilterOpen:o,status:s,files:c,now:l,hotWindowMs:u,setPane:d,openDiff:f,openFile:p,openCommit:m,openCommitFileDiff:h,openCommitFiles:g,repo:_,authed:v,handle:y,sidebarRef:b,draggingSidebar:x,onSidebarDragStart:S,onSidebarDragMove:C,onSidebarDragEnd:w,onSidebarDragCancel:ee,filesMax:T,bumpPaneRequest:E,commits:D,logDone:te,logStalled:O,setLogStalled:k,commitDrillDown:A,setCommitDrillDown:ne,resetLog:re,logSentinelRef:ie,visibleCommits:j,logPagingPaused:M,aheadOids:ae,visibleCommitFiles:oe,mobileView:se}=e,N=ze({repo:_,authed:v,tab:t,filter:r,filterOpen:a,handle:y}),F=t===`tree`&&a&&r!==``,ce=Be(N.treeChildren,N.treeExpanded);return(0,P.jsxs)(`section`,{ref:b,className:`relative min-h-0 flex-col overflow-hidden ${se===`files`?`flex`:`hidden md:flex`} ${T?`md:flex`:`border-ink-700 md:border-r`}`,children:[!T&&(0,P.jsx)(`div`,{role:`separator`,"aria-orientation":`vertical`,"aria-label":`Resize the file sidebar (double-click to reset)`,title:`Drag to resize · double-click to reset`,onPointerDown:S,onPointerMove:C,onPointerUp:w,onPointerCancel:ee,onLostPointerCapture:w,className:`absolute -right-px top-0 z-10 hidden h-full w-1.5 cursor-col-resize touch-none md:block ${x?`bg-accent`:`hover:bg-accent`}`}),(0,P.jsxs)(`div`,{className:`flex shrink-0 items-stretch border-b border-ink-700 px-2`,children:[[`status`,`log`,`tree`].map(e=>(0,P.jsx)(`button`,{onClick:()=>{e!==t&&(E(),t===`log`&&(ne(null),re()),n(e),d({kind:`empty`}))},"aria-current":e===t?`page`:void 0,className:`-mb-px border-b-2 px-2 py-1 ${e===t?`border-accent text-ink-50`:`border-transparent text-ink-400 hover:text-ink-200`}`,children:e},e)),(0,P.jsx)(`button`,{onClick:()=>{a&&i(``),o(e=>!e)},"aria-pressed":a,title:a?`Hide the filter`:`Filter the list`,"aria-label":a?`Hide the filter`:`Filter the list`,className:`my-1 ml-auto flex shrink-0 items-center rounded-sm px-1.5 hover:text-accent ${a?`text-ink-50`:`text-ink-400`}`,children:(0,P.jsx)(le,{})})]}),a&&(0,P.jsx)(`input`,{value:r,onChange:e=>i(e.target.value),placeholder:`filter…`,autoFocus:!0,className:`mx-2 mb-1 shrink-0 rounded-sm bg-ink-850 px-2 py-1 outline-none placeholder:text-ink-400 focus:ring-1 focus:ring-accent`}),(0,P.jsxs)(`ul`,{className:`min-h-0 flex-1 overflow-auto`,children:[t===`status`&&(0,P.jsx)(Qe,{status:s,files:c,now:l,hotWindowMs:u,openDiff:f}),t===`log`&&(0,P.jsx)($e,{visibleCommits:j,commits:D,aheadOids:ae,commitDrillDown:A,visibleCommitFiles:oe,logDone:te,logStalled:O,logPagingPaused:M,setLogStalled:k,logSentinelRef:ie,openCommitFiles:g,openCommit:m,openCommitFileDiff:h,setCommitDrillDown:ne,setPaneEmpty:()=>d({kind:`empty`}),bumpPaneRequest:E}),t===`tree`&&(0,P.jsx)(et,{treeSearching:F,treeMatches:N.treeMatches,treeTruncated:N.treeTruncated,treeSearchLoading:N.treeSearchLoading,treeRows:ce,treeExpanded:N.treeExpanded,openFile:p,revealTreeDir:N.revealTreeDir,toggleTreeDir:N.toggleTreeDir})]})]})}function nt(e){let t=[],n=[],r=[],i=()=>{let e=Math.max(n.length,r.length);for(let i=0;i{t(e=>e===`split`?`unified`:`split`)},[])}}var it=[`.md`,`.markdown`],at=[`.html`,`.htm`];function ot(e){let t=e.toLowerCase();return it.some(e=>t.endsWith(e))}function st(e){let t=e.toLowerCase();return at.some(e=>t.endsWith(e))}function ct(e){return ot(e)||st(e)}function lt(e){return e.map(e=>e.map(e=>e.t).join(``)).join(` +`)}var ut=3;function dt(e){let t=e<1?1:String(Math.floor(e)).length;return Math.max(t,ut)}function ft(e){let t=0;for(let n of e)for(let e of n.lines)t=Math.max(t,e.old_lineno??0,e.new_lineno??0);return dt(t)}function pt({maximized:e}){return(0,P.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`h-4 w-4`,children:e?(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(`path`,{d:`M8 3v3a2 2 0 0 1-2 2H3`}),(0,P.jsx)(`path`,{d:`M21 8h-3a2 2 0 0 1-2-2V3`}),(0,P.jsx)(`path`,{d:`M3 16h3a2 2 0 0 1 2 2v3`}),(0,P.jsx)(`path`,{d:`M16 21v-3a2 2 0 0 1 2-2h3`})]}):(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(`path`,{d:`M8 3H5a2 2 0 0 0-2 2v3`}),(0,P.jsx)(`path`,{d:`M21 8V5a2 2 0 0 0-2-2h-3`}),(0,P.jsx)(`path`,{d:`M3 16v3a2 2 0 0 0 2 2h3`}),(0,P.jsx)(`path`,{d:`M16 21h3a2 2 0 0 0 2-2v-3`})]})})}function mt(){return(0,P.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`h-4 w-4`,children:[(0,P.jsx)(`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`}),(0,P.jsx)(`path`,{d:`M12 3v18`})]})}function ht(){return(0,P.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`h-4 w-4`,children:[(0,P.jsx)(`path`,{d:`M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z`}),(0,P.jsx)(`circle`,{cx:`12`,cy:`12`,r:`3`})]})}function gt({className:e=`h-4 w-4`}){return(0,P.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,"aria-hidden":`true`,focusable:`false`,className:`shrink-0 ${e}`,children:[(0,P.jsx)(`rect`,{x:`2`,y:`3`,width:`20`,height:`14`,rx:`2`}),(0,P.jsx)(`path`,{d:`M12 17v4`}),(0,P.jsx)(`path`,{d:`M8 21h8`}),(0,P.jsx)(`path`,{d:`m9 13 6-6`}),(0,P.jsx)(`path`,{d:`M9 10v3h3`}),(0,P.jsx)(`path`,{d:`M15 10V7h-3`})]})}function _t({nos:e,digits:t,tint:n=``}){return(0,P.jsx)(`span`,{className:`sticky left-0 shrink-0 select-none bg-ink-950`,children:(0,P.jsx)(`span`,{className:`flex gap-[1ch] px-[1ch] text-ink-400 ${n}`,children:e.map((e,n)=>(0,P.jsx)(`span`,{className:`text-right`,style:{width:`${t}ch`},children:e??``},n))})})}function vt({line:e}){return(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(`span`,{className:`text-ink-400 select-none`,children:e.kind}),e.spans.map((e,t)=>(0,P.jsx)(`span`,{style:{color:e.c},children:e.t},t))]})}function yt({line:e,digits:t,side:n}){if(e===null)return(0,P.jsxs)(`div`,{className:`flex bg-ink-900/40`,children:[(0,P.jsx)(_t,{nos:[void 0],digits:t,tint:`bg-ink-900/40`}),(0,P.jsx)(`span`,{className:`whitespace-pre pr-3`,children:` `})]});let r=Xe(e.kind);return(0,P.jsxs)(`div`,{className:`flex ${r}`,children:[(0,P.jsx)(_t,{nos:[n===`old`?e.old_lineno:e.new_lineno],digits:t,tint:r}),(0,P.jsx)(`span`,{className:`whitespace-pre pr-3`,children:(0,P.jsx)(vt,{line:e})})]})}function bt({cells:e,digits:t,side:n,border:r}){return(0,P.jsx)(`div`,{className:`min-w-0 flex-none overflow-x-auto md:flex-1 md:basis-1/2 ${r?`border-t border-ink-800 md:border-t-0 md:border-l`:``}`,children:(0,P.jsx)(`div`,{className:`w-max min-w-full`,children:e.map((e,r)=>(0,P.jsx)(yt,{line:e,digits:t,side:n},r))})})}function xt({lines:e,digits:t}){let n=nt(e);return(0,P.jsxs)(`div`,{className:`flex flex-col md:flex-row`,children:[(0,P.jsx)(bt,{cells:n.map(e=>e.left),digits:t,side:`old`,border:!1}),(0,P.jsx)(bt,{cells:n.map(e=>e.right),digits:t,side:`new`,border:!0})]})}function St({diff:e,split:t}){let n=ft(e.hunks);return(0,P.jsxs)(`div`,{className:`p-1`,children:[e.hunks.length===0&&(0,P.jsx)(`p`,{className:`p-3 text-ink-400`,children:`No changes.`}),e.hunks.map((e,r)=>{let i=(0,P.jsxs)(`div`,{className:`bg-ink-850 px-3 py-0.5 text-ink-400`,children:[e.file_path?`${e.file_path} `:``,e.header]});return(0,P.jsx)(`div`,{className:`mb-2`,children:t?(0,P.jsxs)(P.Fragment,{children:[i,(0,P.jsx)(xt,{lines:e.lines,digits:n})]}):(0,P.jsxs)(`div`,{className:`w-max min-w-full`,children:[i,e.lines.map((e,t)=>{let r=Xe(e.kind);return(0,P.jsxs)(`div`,{className:`flex ${r}`,children:[(0,P.jsx)(_t,{nos:[e.old_lineno,e.new_lineno],digits:n,tint:r}),(0,P.jsx)(`span`,{className:`whitespace-pre pr-3`,children:(0,P.jsx)(vt,{line:e})})]},t)})]})},r)}),e.truncated&&(0,P.jsx)(`p`,{className:`p-3 text-accent`,children:`Diff truncated — it exceeded the server's size ceiling.`})]})}var Ct=`modulepreload`,wt=function(e,t){return new URL(e,t).href},Tt={},Et=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,import.meta.url).href}r=o(t.map(t=>{if(t=wt(t,n),t=s(t),t in Tt)return;Tt[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:Ct,r||(i.as=`script`),i.crossOrigin=``,i.href=t,a&&i.setAttribute(`nonce`,a),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})},Dt=(0,v.lazy)(()=>Et(()=>import(`./Markdown-DGCrBeLL.js`).then(e=>({default:e.MarkdownView})),__vite__mapDeps([0,1]),import.meta.url)),Ot=(0,v.lazy)(()=>Et(()=>import(`./Html-Day0K_6r.js`).then(e=>({default:e.HtmlView})),[],import.meta.url));function kt({lines:e}){let t=dt(e.length);return(0,P.jsx)(`pre`,{className:`w-max min-w-full py-2 text-ink-200`,children:e.map((e,n)=>(0,P.jsxs)(`div`,{className:`flex`,children:[(0,P.jsx)(_t,{nos:[n+1],digits:t}),(0,P.jsx)(`span`,{className:`whitespace-pre pr-3`,children:e.length===0?` `:e.map((e,t)=>(0,P.jsx)(`span`,{style:{color:e.c},children:e.t},t))})]},n))})}function At({pane:e,previewRendered:t,setPreviewRendered:n,filesMax:r,setMaximized:i,status:a,className:o=``}){let s=rt();return(0,P.jsxs)(`section`,{className:`min-h-0 min-w-0 flex-col ${o}`,children:[(0,P.jsxs)(`div`,{className:`flex shrink-0 items-center gap-2 bg-ink-850 px-3 py-0.5 text-ink-400`,children:[e.kind===`file`&&(0,P.jsx)(Ve,{path:e.value.path}),(0,P.jsxs)(`div`,{className:`ml-auto flex shrink-0 items-center gap-1`,children:[e.kind===`diff`&&(0,P.jsx)(`button`,{onClick:s.toggle,"aria-pressed":s.layout===`split`,title:s.layout===`split`?`Switch to unified diff`:`Switch to split diff`,"aria-label":s.layout===`split`?`Switch to unified diff`:`Switch to split diff`,className:`flex shrink-0 items-center rounded-sm px-1.5 py-0.5 hover:text-accent ${s.layout===`split`?`text-accent`:``}`,children:(0,P.jsx)(mt,{})}),e.kind===`file`&&ct(e.value.path)&&(0,P.jsx)(`button`,{onClick:()=>n(e=>!e),"aria-pressed":t,title:t?`Show raw source`:`Show the rendered page`,"aria-label":t?`Show raw source`:`Show the rendered page`,className:`flex shrink-0 items-center rounded-sm px-1.5 py-0.5 hover:text-accent ${t?`text-accent`:``}`,children:(0,P.jsx)(ht,{})}),(0,P.jsx)(`button`,{onClick:()=>i(r?`none`:`files`),"aria-pressed":r,title:r?`Restore the layout`:`Maximize the file pane`,"aria-label":r?`Restore the layout`:`Maximize the file pane`,className:`hidden shrink-0 items-center rounded-sm px-1.5 py-0.5 hover:text-accent md:flex`,children:(0,P.jsx)(pt,{maximized:r})})]})]}),(0,P.jsxs)(`div`,{className:`min-h-0 flex-1 overflow-auto`,children:[e.kind===`empty`&&(0,P.jsx)(`p`,{className:`p-4 text-ink-400`,children:a===null?`Loading…`:`Select a file or commit.`}),e.kind===`file`&&(0,P.jsxs)(P.Fragment,{children:[ct(e.value.path)&&t?(0,P.jsx)(v.Suspense,{fallback:(0,P.jsx)(`p`,{className:`p-4 text-ink-400`,children:`Rendering…`}),children:st(e.value.path)?(0,P.jsx)(Ot,{source:lt(e.value.lines)}):(0,P.jsx)(Dt,{source:lt(e.value.lines)})}):(0,P.jsx)(kt,{lines:e.value.lines}),e.value.truncated&&(0,P.jsx)(`p`,{className:`p-3 text-accent`,children:`File truncated — it exceeded the server's size ceiling.`})]}),e.kind===`diff`&&(0,P.jsx)(St,{diff:e.value,split:s.layout===`split`})]})]})}var jt=[{key:`files`,label:`Files`,icon:ge},{key:`diff`,label:`Diff`,icon:_e},{key:`terminal`,label:`Terminal`,icon:ve}];function Mt({view:e,onSelect:t}){return(0,P.jsx)(`nav`,{"aria-label":`Switch view`,className:`flex shrink-0 items-stretch border-t border-ink-700 bg-ink-900 md:hidden`,children:jt.map(({key:n,label:r,icon:i})=>(0,P.jsxs)(`button`,{onClick:()=>t(n),"aria-current":e===n?`page`:void 0,className:`flex min-h-11 flex-1 flex-col items-center justify-center gap-0.5 py-1 text-[11px] ${e===n?`text-accent shadow-[inset_0_2px_0_0_var(--color-accent)]`:`text-ink-400`}`,children:[(0,P.jsx)(i,{className:`h-5 w-5`}),r]},n))})}var Nt=(0,v.lazy)(()=>Et(()=>import(`./Terminal-mF7xsueX.js`).then(e=>({default:e.TerminalPanel})),[],import.meta.url));function Pt({repository:{id:e,current:t,status:n},sidebar:r,filePane:i,layout:{sidebarWidth:a,sidebarRef:o,draggingSidebar:s,onSidebarDragStart:c,onSidebarDragMove:l,onSidebarDragEnd:u,onSidebarDragCancel:d,upperRef:f,lowerRef:p,draggingUpper:m,onUpperDragStart:h,onUpperDragMove:g,onUpperDragEnd:_,onUpperDragCancel:y,maximized:b,setMaximized:x,mobileView:S,setMobileView:C}}){let w=b===`files`;return(0,v.useEffect)(()=>d,[e,d]),(0,v.useEffect)(()=>y,[y]),(0,P.jsxs)(P.Fragment,{children:[s&&(0,P.jsx)(`div`,{className:`fixed inset-0 z-50 cursor-col-resize`}),m&&(0,P.jsx)(`div`,{className:`fixed inset-0 z-50 cursor-row-resize`}),(0,P.jsxs)(`main`,{ref:f,className:`grid min-h-0 grid-cols-1 md:grid-cols-[var(--nc-sidebar)_1fr] ${S===`terminal`?`hidden md:grid`:``} ${s||m?`select-none`:``}`,style:{"--nc-sidebar":w?`0px`:`min(${a}px, ${Te*100}vw)`},children:[(0,P.jsx)(tt,{...r,repo:e,status:n,sidebarRef:o,draggingSidebar:s,onSidebarDragStart:c,onSidebarDragMove:l,onSidebarDragEnd:u,onSidebarDragCancel:d,filesMax:w,mobileView:S},e),(0,P.jsx)(At,{...i,filesMax:w,setMaximized:x,status:n,className:S===`diff`?`flex`:`hidden md:flex`})]}),(0,P.jsx)(v.Suspense,{fallback:null,children:(0,P.jsx)(Nt,{repo:e,maximized:b===`terminal`,onToggleMaximized:()=>x(e=>e===`terminal`?`none`:`terminal`),className:S===`terminal`?`flex`:`hidden md:flex`,sectionRef:p,showDivider:b===`none`,draggingUpper:m,onUpperDragStart:h,onUpperDragMove:g,onUpperDragEnd:_,onUpperDragCancel:y})}),(0,P.jsx)(Mt,{view:S,onSelect:C}),(0,P.jsxs)(`footer`,{className:`flex shrink-0 items-center gap-3 border-t border-ink-700 bg-ink-900 px-3 py-1 text-ink-400`,children:[(0,P.jsx)(`span`,{className:`truncate`,children:t?.display_path}),n?.branch&&(0,P.jsx)(`span`,{className:`text-accent`,children:n.branch}),n?.tracking&&(0,P.jsxs)(`span`,{children:[`↑`,n.tracking.ahead,` ↓`,n.tracking.behind]}),(0,P.jsx)(`span`,{className:`ml-auto`,children:n?(0,P.jsx)(`span`,{className:`text-added`,children:`● live`}):`connecting…`})]})]})}function Ft(e,t){return e?`grid-rows-[auto_minmax(0,1fr)_auto_auto] ${t===`terminal`?`md:grid-rows-[auto_minmax(0,0fr)_minmax(0,1fr)_auto]`:t===`files`?`md:grid-rows-[auto_minmax(0,1fr)_minmax(0,0fr)_auto]`:`md:grid-rows-[auto_minmax(0,var(--nc-upper))_minmax(0,var(--nc-lower))_auto]`}`:`grid-rows-[auto_1fr]`}var It=1e3,Lt=3,Rt=2e3;function zt(e,t){let[n,r]=(0,v.useState)(!1),i=(0,v.useRef)(!1),a=(0,v.useRef)(!1);(0,v.useEffect)(()=>(a.current=!1,()=>{a.current=!0}),[]);let o=(0,v.useCallback)(async t=>{for(;!a.current;){if(await new Promise(e=>setTimeout(e,It)),a.current)return;let n;try{n=await O.cloneStatus(t)}catch(e){if(x(e)){i.current=!1,a.current||r(!1);return}if(e instanceof b&&e.status===404){if(a.current)return;oe.error(`the clone's progress is no longer available`),i.current=!1,r(!1);return}continue}if(a.current)return;if(n.state===`done`){try{let t=await O.open(n.path);if(a.current)return;e(t)}catch(e){if(a.current)return;oe.error(e instanceof Error?e.message:`could not open`)}finally{i.current=!1,a.current||r(!1)}return}if(n.state===`failed`){oe.error(n.message),i.current=!1,r(!1);return}}},[e]),s=(0,v.useCallback)(async(e=()=>!1)=>{for(let t=0;t0&&await new Promise(e=>setTimeout(e,Rt)),i.current||e()||a.current)return;let n;try{({job:n}=await O.runningClone())}catch(e){if(x(e))return;continue}if(n===null||e()||a.current||i.current)return;i.current=!0,r(!0),o(n);return}},[o]);return(0,v.useEffect)(()=>{if(!t)return;let e=!1;return s(()=>e),()=>{e=!0}},[t,s]),{busy:n,start:(0,v.useCallback)(async(e,t)=>{if(!(!t.trim()||i.current)){i.current=!0,r(!0);try{let{job:n}=await O.clone(e,t.trim());await o(n)}catch(e){if(i.current=!1,a.current)return;let t=e instanceof b&&e.status>=400;oe.error(t?e.message:`could not confirm the clone started — check this folder before retrying`),r(!1),s()}}},[o,s])}}function Bt(e,t,n,r=!1){return r&&n&&t.includes(n)?n:e&&t.includes(e)?e:n&&t.includes(n)?n:t[0]??null}function Vt(e){let t=!1,n=null,r=()=>{if(t||n===null)return;let i=n;n=null,t=!0,e(i).catch(()=>{}).finally(()=>{t=!1,r()})};return e=>{n=e,r()}}function Ht(e,t,n){if(t===n)return e;let r=e.indexOf(t),i=e.indexOf(n);if(r===-1||i===-1)return e;let a=e.filter(e=>e!==t),o=a.indexOf(n),s=r{if(e===!1)return;let p=!1,v,y=new AbortController,S=()=>{let e=l.current,ee=u.current,E=d.current,te=f.current,A=m.current;return O.repos(y.signal).then(n=>{let{repos:y,hot:x,accent:C,sidebar_width:O,upper_pct:re,active_repo:j,maximized:M,now_ms:ae,can_clone:oe}=n;if(p)return;T(x),ne(oe),D(e=>We(e,ae,Date.now())),l.current===e&&r(C),u.current===ee&&!s.current&&i(O),d.current===E&&!c.current&&a(re),f.current===te&&o(M),t(!0),k(!0);let se=g.current||_.current!==null;m.current===A&&!h.current&&!se?b(y):b(e=>{let t=Ut(y.map(e=>e.id),e.map(e=>e.id)),n=new Map(y.map(e=>[e.id,e]));return t.map(e=>n.get(e)).filter(Boolean)});let N=j!==ie.current;ie.current=j??null,w(e=>Bt(e,y.map(e=>e.id),j,N)),p||(v=setTimeout(S,Wt))}).catch(e=>{if(!p){if(x(e)){t(!1),k(!1);return}else C(e)||n(e);v=setTimeout(S,Wt)}})};return S(),()=>{p=!0,y.abort(),v&&clearTimeout(v)}},[e,t,n,r,i,a,p,l,u,d,f,s,c,o,m,h,g,_]),(0,v.useEffect)(()=>{S&&re(S)},[S,re]),{repos:y,setRepos:b,repo:S,setRepo:w,hot:ee,clockSkewMs:E,reposLoaded:te,canClone:A}}var Kt=4;function qt({ids:e,onReorder:t,draggingRef:n}){let r=(0,v.useRef)(null),i=(0,v.useRef)(null),a=(0,v.useRef)(null),[o,s]=(0,v.useState)(null),[c,l]=(0,v.useState)(null);return{dragging:o,target:c,onStart:(t,a)=>{t.target.closest(`button[data-tab-close]`)||t.button!==0||e.length<2||(r.current=a,i.current={x:t.clientX,y:t.clientY},n.current=!1)},onMove:e=>{let t=r.current,o=i.current;if(t===null||o===null)return;if(!n.current&&e.buttons===0){r.current=null,i.current=null;return}if(!n.current&&Math.hypot(e.clientX-o.x,e.clientY-o.y){let o=r.current,c=a.current;o!==null&&n.current&&c!==null&&t(Ht(e,o,c)),r.current=null,i.current=null,a.current=null,n.current=!1,s(null),l(null)}}}function Jt({repos:e,setRepos:t,handle:n,writesRef:r,draggingRef:i,inFlightRef:a,pendingRef:o}){let s=(0,v.useCallback)(()=>{if(a.current||o.current===null)return;let e=o.current;o.current=null,a.current=!0;let i=r.current;O.reorderRepos(e).then(e=>{r.current===i&&t(e)}).catch(n).finally(()=>{a.current=!1,s()})},[n,t]),c=(0,v.useCallback)(e=>{r.current+=1,t(t=>{let n=Ut(t.map(e=>e.id),e),r=new Map(t.map(e=>[e.id,e]));return n.map(e=>r.get(e)).filter(Boolean)}),o.current=e,s()},[s,t]);return{...qt({ids:e.map(e=>e.id),onReorder:c,draggingRef:i}),writesRef:r,draggingRef:i,inFlightRef:a,pendingRef:o}}function Yt({authed:e,setAuthed:t,handle:n,resumeTick:r,adoptAccent:i,adoptSidebarWidth:a,adoptUpperPct:o,adoptMaximized:s,accentWrites:c,sidebarWrites:l,upperPctWrites:u,maximizedWrites:d,draggingRef:f,upperDraggingRef:p}){let m=(0,v.useRef)(0),h=(0,v.useRef)(!1),g=(0,v.useRef)(!1),_=(0,v.useRef)(null),y=Gt({authed:e,setAuthed:t,handle:n,adoptAccent:i,adoptSidebarWidth:a,adoptUpperPct:o,adoptMaximized:s,draggingRef:f,upperDraggingRef:p,accentWrites:c,sidebarWrites:l,upperPctWrites:u,maximizedWrites:d,resumeTick:r,orderWrites:m,repoDraggingRef:h,reorderInFlightRef:g,pendingReorderRef:_}),{dragging:b,target:x,onStart:S,onMove:C,onEnd:w}=Jt({repos:y.repos,setRepos:y.setRepos,handle:n,writesRef:m,draggingRef:h,inFlightRef:g,pendingRef:_});return{...y,orderWrites:m,draggingRepo:b,dragOverRepo:x,onRepoDragStart:S,onRepoDragMove:C,onRepoDragEnd:w}}function Xt({repos:e,setRepos:t,setRepo:n,setPane:r,setTab:i,setPickerOpen:a,handle:o,orderWrites:s}){return{selectOpenedRepo:(0,v.useCallback)(e=>{s.current+=1,t(t=>t.some(t=>t.id===e.id)?t:[...t,e]),n(e.id),r({kind:`empty`}),i(`status`),a(!1)},[t,n,r,i,a,s]),closeRepo:(0,v.useCallback)(async r=>{try{await O.close(r),s.current+=1;let i=e.filter(e=>e.id!==r);t(i),n(e=>e===r?i[0]?.id??null:e)}catch(e){o(e)}},[e,t,n,o,s])}}function Zt({repo:e,authed:t,tab:n,filter:r,handle:i}){let[a,o]=(0,v.useState)([]),[s,c]=(0,v.useState)(!1),[l,u]=(0,v.useState)(!1),d=(0,v.useRef)(null),f=(0,v.useRef)(!1),p=(0,v.useRef)(0),m=(0,v.useCallback)(()=>{p.current+=1,f.current=!1,o([]),d.current=null,c(!1),u(!1)},[]),[h,g]=(0,v.useState)(null),_=(0,v.useRef)(a);_.current=a;let y=(0,v.useCallback)(async()=>{if(!e||f.current)return;f.current=!0;let t=p.current;try{let n=d.current,r=await O.log(e,n===null?void 0:{from:n,skip:_.current.length});if(t!==p.current)return;o(e=>[...e,...r.commits]),d.current=r.head??null,c(!r.truncated||r.head===void 0)}catch(e){t===p.current&&(i(e),u(!0))}finally{t===p.current&&(f.current=!1)}},[e,i]);(0,v.useEffect)(()=>{!e||!t||n!==`log`||a.length===0&&!s&&!l&&y()},[e,t,n,a.length,s,l,y]);let b=a.filter(e=>e.summary.toLowerCase().includes(r.toLowerCase())),x=r!==``,S=(0,v.useRef)(null);return(0,v.useEffect)(()=>{let e=S.current;if(!e)return;let t=new IntersectionObserver(e=>{e.some(e=>e.isIntersecting)&&y()},{root:e.closest(`ul`),rootMargin:`400px`});return t.observe(e),()=>t.disconnect()},[y,s,l,x,h,n,b.length]),{commits:a,logDone:s,logStalled:l,setLogStalled:u,commitDrillDown:h,setCommitDrillDown:g,resetLog:m,logSentinelRef:S,visibleCommits:b,logPagingPaused:x}}function Qt({repo:e,handle:t,setPane:n,paneRequestRef:r,setCommitDrillDown:i,setMobileView:a,setPreviewRendered:o}){return{openDiff:(0,v.useCallback)(i=>{if(!e)return;a(`diff`);let o=r.current+=1;O.diff(e,i).then(e=>{o===r.current&&n({kind:`diff`,value:e})}).catch(e=>{o===r.current&&t(e)})},[e,t,n,r,a]),openFile:(0,v.useCallback)(i=>{if(!e)return;a(`diff`),o(!0);let s=r.current+=1;O.file(e,i).then(e=>{s===r.current&&n({kind:`file`,value:e})}).catch(e=>{s===r.current&&t(e)})},[e,t,n,r,a,o]),openCommit:(0,v.useCallback)(i=>{if(!e)return;a(`diff`);let o=r.current+=1;O.commit(e,i).then(e=>{o===r.current&&n({kind:`diff`,value:e})}).catch(e=>{o===r.current&&t(e)})},[e,t,n,r,a]),openCommitFileDiff:(0,v.useCallback)((i,o)=>{if(!e)return;a(`diff`);let s=r.current+=1;O.commitFileDiff(e,i,o).then(e=>{s===r.current&&n({kind:`diff`,value:e})}).catch(e=>{s===r.current&&t(e)})},[e,t,n,r,a]),openCommitFiles:(0,v.useCallback)(async o=>{if(!e)return;a(`diff`);let s=r.current+=1;try{let t=await O.commitFiles(e,o.oid);if(s!==r.current)return;if(i({commit:o,...t}),t.files.length===0){n({kind:`empty`});return}let a=await O.commit(e,o.oid);s===r.current&&n({kind:`diff`,value:a})}catch(e){s===r.current&&t(e)}},[e,t,n,r,i,a])}}function $t({repo:e,authed:t,resumeTick:n,tab:r,pane:i,setPane:a,handle:o,paneRequestRef:s}){let[c,l]=(0,v.useState)(null),u=(0,v.useRef)(i);u.current=i;let d=(0,v.useRef)(r);return d.current=r,(0,v.useLayoutEffect)(()=>{l(null)},[e,t]),(0,v.useEffect)(()=>{if(!(!e||!t))return k(e,l)},[e,t,n]),(0,v.useEffect)(()=>{if(!e||!c)return;let t=u.current;if(d.current!==`status`||t.kind!==`diff`)return;let n=t.value.path;if(!c.files.some(e=>e.path===n)){a({kind:`empty`});return}let r=s.current,i=!0,l=()=>{let e=u.current;return i&&r===s.current&&e.kind===`diff`&&e.value.path===n};return O.diff(e,n).then(e=>{l()&&a({kind:`diff`,value:e})}).catch(e=>{l()&&o(e)}),()=>{i=!1}},[c,e,o,u,d,s,a]),{status:c,paneRef:u,tabRef:d}}function en({repo:e,repos:t,authed:n,hot:r,clockSkewMs:i,resumeTick:a,handle:o,shell:s,maximizedPanelOf:c,setMaximizedFor:l}){let[u,d]=(0,v.useState)(`status`),[f,p]=(0,v.useState)(``),[m,h]=(0,v.useState)(!1),[g,_]=(0,v.useState)({kind:`empty`}),[y,b]=(0,v.useState)(`files`),[x,S]=(0,v.useState)(!0),C=(0,v.useRef)(0),w=(0,v.useCallback)(()=>{C.current+=1},[]),ee=(0,v.useCallback)(()=>_({kind:`empty`}),[]),{status:T}=$t({repo:e,authed:n,resumeTick:a,tab:u,pane:g,setPane:_,handle:o,paneRequestRef:C}),E=r?.enabled?r.window_secs*1e3:0,D=Je(T?.files,E,i??0),te=c(e),O=(0,v.useCallback)(t=>l(e,t),[e,l]),k=Zt({repo:e,authed:n,tab:u,filter:f,handle:o}),A=Qt({repo:e,handle:o,setPane:_,paneRequestRef:C,setCommitDrillDown:k.setCommitDrillDown,setMobileView:b,setPreviewRendered:S});(0,v.useLayoutEffect)(()=>{w(),k.setCommitDrillDown(null),ee(),k.resetLog()},[e,w,ee,k.setCommitDrillDown,k.resetLog]);let ne=f.toLowerCase(),re=(0,v.useMemo)(()=>(T?.files??[]).filter(e=>e.path.toLowerCase().includes(ne)),[T?.files,ne]),ie=(0,v.useMemo)(()=>(k.commitDrillDown?.files??[]).filter(e=>e.path.toLowerCase().includes(ne)||e.old_path?.toLowerCase().includes(ne)),[k.commitDrillDown?.files,ne]),j=(0,v.useMemo)(()=>new Set(k.commits.slice(0,T?.tracking?.ahead??0).map(e=>e.oid)),[k.commits,T?.tracking?.ahead]);return{setPane:_,setTab:d,clearPane:ee,maximized:te,repoShell:e?{repository:{id:e,current:t.find(t=>t.id===e),status:T},sidebar:{tab:u,setTab:d,filter:f,setFilter:p,filterOpen:m,setFilterOpen:h,files:re,now:D,hotWindowMs:E,setPane:_,...A,authed:n,handle:o,bumpPaneRequest:w,...k,aheadOids:j,visibleCommitFiles:ie},filePane:{pane:g,previewRendered:x,setPreviewRendered:S},layout:{...s,maximized:te,setMaximized:O,mobileView:y,setMobileView:b}}:null}}function tn(){let[e,t]=(0,v.useState)(0);return(0,v.useEffect)(()=>{let e=()=>{document.visibilityState===`visible`&&t(e=>e+1)};return document.addEventListener(`visibilitychange`,e),window.addEventListener(`online`,e),()=>{document.removeEventListener(`visibilitychange`,e),window.removeEventListener(`online`,e)}},[]),e}var nn=[{name:`yellow`,color:`#d9a441`},{name:`cyan`,color:`#03c4db`},{name:`green`,color:`#77c47a`},{name:`magenta`,color:`#dc8fd5`},{name:`blue`,color:`#87acfd`}],rn=`nightcrow.viewer.accent`;function an(e){if(!Number.isFinite(e))return 0;let t=nn.length;return(Math.trunc(e)%t+t)%t}function on(){try{let e=localStorage.getItem(rn);return e===null?0:an(Number(e))}catch{return 0}}function sn(e){try{localStorage.setItem(rn,String(e))}catch{}}function cn(){let[e,t]=(0,v.useState)(on);(0,v.useLayoutEffect)(()=>{document.documentElement.style.setProperty(`--color-accent`,nn[e].color)},[e]);let n=(0,v.useCallback)(()=>{t(e=>{let t=an(e+1);return sn(t),O.setAccent(t).catch(()=>{}),t})},[]),r=(0,v.useCallback)(e=>{t(t=>{let n=an(e);return n===t?t:(sn(n),n)})},[]);return{accent:nn[e],next:nn[an(e+1)],cycle:n,adopt:r}}function ln(e){return Number.isFinite(e)?Math.min(Math.max(e,20),85):55}function un(e){return Math.round(ln(e))}function dn(e,t,n,r){let i=n-t;return ln(i<=0?r:(e-t)/i*100)}var fn=`nightcrow.upperPct`;function pn(){try{let e=Number(localStorage.getItem(fn));return Number.isFinite(e)&&e>0?un(e):55}catch{return 55}}function mn(e){try{localStorage.setItem(fn,String(e))}catch{}}function hn(){let[e,t]=(0,v.useState)(pn);return{pct:e,resize:(0,v.useCallback)(e=>{t(ln(e))},[]),commit:(0,v.useCallback)(e=>{let n=un(e);t(n),mn(n),O.setUpperPct(n).catch(()=>{})},[]),reset:(0,v.useCallback)(()=>{t(55),mn(55),O.setUpperPct(55).catch(()=>{})},[]),adopt:(0,v.useCallback)(e=>{t(t=>{let n=un(e);return n===t?t:(mn(n),n)})},[])}}function gn(){let[e,t]=(0,v.useState)({}),n=(0,v.useRef)(e),r=(0,v.useCallback)(e=>{n.current=e,t(e)},[]),i=(0,v.useRef)(0),a=(0,v.useRef)(new Map),o=(0,v.useCallback)(e=>{let t=a.current.get(e);if(t)return t;let n=Vt(t=>O.setMaximized(e,t===`none`?null:t));return a.current.set(e,n),n},[]),s=(0,v.useCallback)((e,t)=>{if(e==null)return;let a=n.current,s=typeof t==`function`?t(a[e]??`none`):t;i.current+=1,o(e)(s);let{[e]:c,...l}=a;r(s===`none`?l:{...a,[e]:s})},[o,r]);return{panelOf:(0,v.useCallback)(t=>t!=null&&e[t]||`none`,[e]),setFor:s,adopt:(0,v.useCallback)(e=>{_n(n.current,e)||r(e)},[r]),writes:i}}function _n(e,t){let n=Object.keys(e);return n.length===Object.keys(t).length&&n.every(n=>e[n]===t[n])}function vn(){let{accent:e,next:t,cycle:n,adopt:r}=cn(),{width:i,resize:a,commit:o,reset:s,adopt:c}=Ae(),{pct:l,resize:u,commit:d,reset:f,adopt:p}=hn(),m=gn(),h=(0,v.useRef)(0),g=(0,v.useRef)(0),_=(0,v.useRef)(0);return{accent:e,next:t,cycle:(0,v.useCallback)(()=>{h.current+=1,n()},[n]),adoptAccent:r,accentWrites:h,sidebarWidth:i,resizeSidebar:a,commitSidebarWidth:(0,v.useCallback)(e=>{g.current+=1,o(e)},[o]),resetSidebarWidth:(0,v.useCallback)(()=>{g.current+=1,s()},[s]),bumpSidebarWrites:(0,v.useCallback)(()=>{g.current+=1},[]),adoptSidebarWidth:c,sidebarWrites:g,upperPct:l,resizeUpperPct:u,commitUpperPct:(0,v.useCallback)(e=>{_.current+=1,d(e)},[d]),resetUpperPct:(0,v.useCallback)(()=>{_.current+=1,f()},[f]),bumpUpperPctWrites:(0,v.useCallback)(()=>{_.current+=1},[]),adoptUpperPct:p,upperPctWrites:_,maximizedPanelOf:m.panelOf,setMaximizedFor:m.setFor,adoptMaximized:m.adopt,maximizedWrites:m.writes}}var yn=400;function bn({value:e,valueAt:t,onGestureStart:n,resize:r,commit:i,reset:a,axis:o}){let s=(0,v.useRef)(0),c=(0,v.useRef)(0),l=(0,v.useRef)(!1),u=(0,v.useRef)(!1),d=(0,v.useRef)(0),[f,p]=(0,v.useState)(!1);return{dragging:f,onDragStart:(0,v.useCallback)(t=>{t.button!==0||!t.isPrimary||n()&&(s.current=o===`x`?t.clientX:t.clientY,c.current=e,l.current=!0,u.current=!1,p(!0),t.currentTarget.setPointerCapture(t.pointerId),t.preventDefault())},[e,n,o]),onDragMove:(0,v.useCallback)(e=>{if(!l.current)return;let n=o===`x`?e.clientX:e.clientY;if(!u.current&&Math.abs(n-s.current)<3)return;let i=t(e);i!==null&&(u.current=!0,c.current=i,r(i))},[t,r,o]),onDragEnd:(0,v.useCallback)(()=>{if(!l.current)return;if(l.current=!1,p(!1),u.current){i(c.current),d.current=0;return}let e=Date.now();e-d.current{l.current=!1,u.current=!1,d.current=0,p(!1)},[]),draggingRef:l}}function xn({sidebarRef:e,sidebarWidth:t,resizeSidebar:n,commitSidebarWidth:r,resetSidebarWidth:i,bumpSidebarWrites:a}){let o=(0,v.useRef)(0),s=(0,v.useCallback)(()=>{let t=e.current?.getBoundingClientRect().left;return t===void 0?!1:(o.current=t,a(),!0)},[e,a]),{dragging:c,onDragStart:l,onDragMove:u,onDragEnd:d,onDragCancel:f,draggingRef:p}=bn({value:t,valueAt:(0,v.useCallback)(e=>e.clientX-o.current,[]),onGestureStart:s,resize:n,commit:r,reset:i,axis:`x`});return{draggingSidebar:c,onSidebarDragStart:l,onSidebarDragMove:u,onSidebarDragEnd:d,onSidebarDragCancel:f,draggingRef:p}}function Sn({upperRef:e,lowerRef:t,upperPct:n,resizeUpperPct:r,commitUpperPct:i,resetUpperPct:a,bumpUpperPctWrites:o}){let s=(0,v.useRef)(0),c=(0,v.useRef)(0),l=(0,v.useCallback)(()=>{let n=e.current?.getBoundingClientRect().top,r=t.current?.getBoundingClientRect().bottom;return n===void 0||r===void 0?!1:(s.current=n,c.current=r,o(),!0)},[e,t,o]),{dragging:u,onDragStart:d,onDragMove:f,onDragEnd:p,onDragCancel:m,draggingRef:h}=bn({value:n,valueAt:(0,v.useCallback)(e=>dn(e.clientY,s.current,c.current,n),[n]),onGestureStart:l,resize:r,commit:i,reset:a,axis:`y`});return{draggingUpper:u,onUpperDragStart:d,onUpperDragMove:f,onUpperDragEnd:p,onUpperDragCancel:m,upperDraggingRef:h}}function Cn(){let{accent:e,next:t,cycle:n,adoptAccent:r,accentWrites:i,sidebarWidth:a,resizeSidebar:o,commitSidebarWidth:s,resetSidebarWidth:c,bumpSidebarWrites:l,adoptSidebarWidth:u,sidebarWrites:d,upperPct:f,resizeUpperPct:p,commitUpperPct:m,resetUpperPct:h,bumpUpperPctWrites:g,adoptUpperPct:_,upperPctWrites:y,maximizedPanelOf:b,setMaximizedFor:x,adoptMaximized:S,maximizedWrites:C}=vn(),w=(0,v.useRef)(null),ee=(0,v.useRef)(null),T=(0,v.useRef)(null),E=xn({sidebarRef:w,sidebarWidth:a,resizeSidebar:o,commitSidebarWidth:s,resetSidebarWidth:c,bumpSidebarWrites:l}),D=Sn({upperRef:ee,lowerRef:T,upperPct:f,resizeUpperPct:p,commitUpperPct:m,resetUpperPct:h,bumpUpperPctWrites:g});return{accent:e,next:t,cycle:n,upperPct:f,maximizedPanelOf:b,setMaximizedFor:x,shell:{sidebarWidth:a,sidebarRef:w,upperRef:ee,lowerRef:T,draggingSidebar:E.draggingSidebar,onSidebarDragStart:E.onSidebarDragStart,onSidebarDragMove:E.onSidebarDragMove,onSidebarDragEnd:E.onSidebarDragEnd,onSidebarDragCancel:E.onSidebarDragCancel,draggingUpper:D.draggingUpper,onUpperDragStart:D.onUpperDragStart,onUpperDragMove:D.onUpperDragMove,onUpperDragEnd:D.onUpperDragEnd,onUpperDragCancel:D.onUpperDragCancel},guards:{adoptAccent:r,adoptSidebarWidth:u,adoptUpperPct:_,adoptMaximized:S,accentWrites:i,sidebarWrites:d,upperPctWrites:y,maximizedWrites:C,draggingRef:E.draggingRef,upperDraggingRef:D.upperDraggingRef}}}function wn(){let[e,t]=(0,v.useState)(null),[n,r]=(0,v.useState)(!1),i=(0,v.useCallback)(e=>{if(x(e)){t(!1);return}oe.error(e instanceof Error?e.message:`request failed`)},[]),a=tn(),o=Cn(),s=Yt({authed:e,setAuthed:t,handle:i,resumeTick:a,...o.guards}),c=en({repo:s.repo,repos:s.repos,authed:e,hot:s.hot,clockSkewMs:s.clockSkewMs,resumeTick:a,handle:i,shell:o.shell,maximizedPanelOf:o.maximizedPanelOf,setMaximizedFor:o.setMaximizedFor}),{selectOpenedRepo:l,closeRepo:u}=Xt({repos:s.repos,setRepos:s.setRepos,setRepo:s.setRepo,setPane:c.setPane,setTab:c.setTab,setPickerOpen:r,handle:i,orderWrites:s.orderWrites}),{busy:d,start:f}=zt(l,e===!0),p=(0,v.useCallback)(e=>{s.setRepo(e),c.clearPane()},[s.setRepo,c.clearPane]),m=(0,v.useCallback)(()=>r(!0),[]),h=(0,v.useCallback)(()=>r(!1),[]);return{authed:e,login:(0,v.useCallback)(()=>t(null),[]),reposLoaded:s.reposLoaded,rows:Ft(s.repo,c.maximized),upperPct:o.upperPct,header:{repos:s.repos,repo:s.repo,onSelectRepo:p,onCloseRepo:u,onOpenPicker:m,cloning:d,accent:o.accent,next:o.next,cycle:o.cycle,draggingRepo:s.draggingRepo,dragOverRepo:s.dragOverRepo,onRepoDragStart:s.onRepoDragStart,onRepoDragMove:s.onRepoDragMove,onRepoDragEnd:s.onRepoDragEnd},repoShell:c.repoShell,picker:n?{onClose:h,onOpened:l,canClone:s.canClone,cloning:d,onClone:f}:null}}function Tn(){let e=wn();return e.authed===null?(0,P.jsx)(Se,{}):e.authed?e.reposLoaded?(0,P.jsxs)(`div`,{className:`nc-fade grid h-full ${e.rows}`,style:{"--nc-upper":`${e.upperPct}fr`,"--nc-lower":`${100-e.upperPct}fr`},children:[(0,P.jsx)(xe,{...e.header}),e.repoShell?(0,P.jsx)(Pt,{...e.repoShell}):(0,P.jsx)(`div`,{className:`flex items-center justify-center p-6 text-center text-ink-400`,children:(0,P.jsxs)(`span`,{children:[`No repository open. Click`,` `,(0,P.jsx)(`span`,{className:`text-ink-200`,children:`+ open`}),` above to add one.`]})}),e.picker&&(0,P.jsx)(fe,{...e.picker})]}):(0,P.jsx)(Se,{}):(0,P.jsx)(Ce,{onSuccess:e.login})}var En={error:7e3,info:5e3,success:5e3},Dn={error:`text-removed`,info:`text-accent`,success:`text-added`};function On(){let[e,t]=(0,v.useState)([]);return(0,v.useEffect)(()=>j(t),[]),e.length===0?null:(0,P.jsx)(`div`,{className:`pointer-events-none fixed right-3 top-3 z-[60] flex w-80 max-w-[calc(100vw-1.5rem)] flex-col gap-2`,"aria-live":`polite`,children:e.map(e=>(0,P.jsx)(kn,{toast:e},e.id))})}function kn({toast:e}){let[t,n]=(0,v.useState)(!1);return(0,v.useEffect)(()=>{if(t)return;let n=setTimeout(()=>M(e.id),En[e.kind]);return()=>clearTimeout(n)},[e.id,e.kind,e.bump,t]),(0,P.jsxs)(`div`,{role:e.kind===`error`?`alert`:`status`,className:`nc-fade pointer-events-auto flex items-start gap-2 rounded-md border border-ink-700 bg-ink-850 px-3 py-2 text-xs shadow-lg`,onMouseEnter:()=>n(!0),onMouseLeave:()=>n(!1),children:[(0,P.jsx)(`span`,{className:`min-w-0 flex-1 break-words ${Dn[e.kind]}`,children:e.message}),(0,P.jsx)(`button`,{type:`button`,onClick:()=>M(e.id),"aria-label":`dismiss`,className:`mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-sm text-ink-400 hover:bg-ink-700 hover:text-ink-200`,children:(0,P.jsx)(F,{className:`h-3 w-3`})})]})}(0,y.createRoot)(document.getElementById(`root`)).render((0,P.jsxs)(v.StrictMode,{children:[(0,P.jsx)(Tn,{}),(0,P.jsx)(On,{})]}));export{ce as a,oe as c,s as d,l as f,pt as i,d as l,Ht as n,F as o,gt as r,N as s,Ut as t,o as u}; \ No newline at end of file diff --git a/viewer-ui/dist/index.html b/viewer-ui/dist/index.html index 0a572d79..0e1c0b24 100644 --- a/viewer-ui/dist/index.html +++ b/viewer-ui/dist/index.html @@ -14,7 +14,7 @@ nightcrow - + diff --git a/viewer-ui/src/api.contract.test.ts b/viewer-ui/src/api.contract.test.ts index df2c8b75..989548cb 100644 --- a/viewer-ui/src/api.contract.test.ts +++ b/viewer-ui/src/api.contract.test.ts @@ -1,7 +1,7 @@ /** * Contract test against the server's own output. * - * `api.ts` and `src/web/viewer/dto.rs` describe one protocol twice, by hand, so + * `api.ts` and `src/web/viewer/dto/` describe one protocol twice, by hand, so * a field renamed on one side goes unnoticed until something renders blank. * `api.fixture.json` is generated from the Rust DTOs * (`UPDATE_API_FIXTURE=1 cargo test the_wire_fixture`) and committed; the diff --git a/viewer-ui/src/api/terminal.test.ts b/viewer-ui/src/api/terminal.test.ts new file mode 100644 index 00000000..f51732aa --- /dev/null +++ b/viewer-ui/src/api/terminal.test.ts @@ -0,0 +1,99 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { + decodeTerminalControlFrame, + decodeTerminalOutputFrame, + sendTerminalMessage, + type TerminalClientMessage, + type TerminalServerMessage, +} from "./terminal"; + +afterEach(() => vi.unstubAllGlobals()); + +describe("terminal control protocol", () => { + it("decodes every server control variant", () => { + const messages: TerminalServerMessage[] = [ + { type: "hello", client: 3, panes: 2 }, + { type: "pending", count: 2 }, + { + type: "created", + pane: 7, + rows: 24, + cols: 80, + client: 3, + title: "shell", + }, + { type: "created", pane: 4, rows: 24, cols: 80 }, + { type: "exited", pane: 7 }, + { type: "resized", pane: 7, rows: 32, cols: 120 }, + { type: "size_owner", owned: true }, + { type: "reordered", order: [7, 4] }, + { type: "zoomed", pane: null }, + { + type: "recovery", + pane: 7, + state: "waiting", + detail: "retrying", + deadline_epoch: 1_700_000_000, + attempt: 2, + }, + { type: "recovery", pane: 4, state: "cancelled", attempt: 0 }, + { type: "error", message: "capacity reached" }, + ]; + + for (const message of messages) { + expect(decodeTerminalControlFrame(JSON.stringify(message))).toEqual( + message, + ); + } + }); + + it("rejects malformed and unknown controls", () => { + expect(decodeTerminalControlFrame("{")).toBeNull(); + expect(decodeTerminalControlFrame(`{"type":"future"}`)).toBeNull(); + expect( + decodeTerminalControlFrame( + `{"type":"created","pane":1,"rows":"24","cols":80}`, + ), + ).toBeNull(); + expect( + decodeTerminalControlFrame( + `{"type":"reordered","order":[1,"2"]}`, + ), + ).toBeNull(); + }); + + it("sends typed client controls only on an open socket", () => { + vi.stubGlobal("WebSocket", { OPEN: 1, CONNECTING: 0 }); + const message: TerminalClientMessage = { + type: "clear_key_report", + pane: 7, + key: { trusted: true, repeat: false, code: "KeyL", since_ms: 3 }, + }; + const sent: string[] = []; + const open = { + readyState: WebSocket.OPEN, + send: (data: string) => sent.push(data), + } as unknown as WebSocket; + const connecting = { + readyState: WebSocket.CONNECTING, + send: (data: string) => sent.push(data), + } as unknown as WebSocket; + + expect(sendTerminalMessage(open, message)).toBe(true); + expect(sendTerminalMessage(connecting, message)).toBe(false); + expect(sendTerminalMessage(null, message)).toBe(false); + expect(sent).toHaveLength(1); + expect(JSON.parse(sent[0])).toEqual(message); + }); +}); + +describe("terminal output protocol", () => { + it("decodes the little-endian pane prefix and rejects short frames", () => { + const frame = new Uint8Array([0x78, 0x56, 0x34, 0x12, 65, 66]).buffer; + const decoded = decodeTerminalOutputFrame(frame); + + expect(decoded?.pane).toBe(0x12345678); + expect([...new Uint8Array(decoded?.data ?? [])]).toEqual([65, 66]); + expect(decodeTerminalOutputFrame(new ArrayBuffer(3))).toBeNull(); + }); +}); diff --git a/viewer-ui/src/api/terminal.ts b/viewer-ui/src/api/terminal.ts new file mode 100644 index 00000000..039853d8 --- /dev/null +++ b/viewer-ui/src/api/terminal.ts @@ -0,0 +1,139 @@ +import type { ClearKeyReport } from "../lib/clearKeyProbe"; +import type { RecoveryFrame } from "../lib/recovery"; + +export interface PaneSize { + rows: number; + cols: number; +} + +export type TerminalClientMessage = + | ({ type: "create" } & PaneSize) + | { type: "input"; pane: number; data: string } + | ({ type: "resize"; pane: number } & PaneSize) + | { type: "close"; pane: number } + | { type: "reorder"; order: number[] } + | { type: "zoom"; pane: number | null } + | { type: "claim_size" } + | { type: "cancel_recovery"; pane: number } + | { type: "start"; sizes: PaneSize[] } + | ({ type: "clear_key_report"; pane: number } & ClearKeyReport); + +export type TerminalServerMessage = + | { + type: "created"; + pane: number; + rows: number; + cols: number; + client?: number; + title?: string; + } + | { type: "exited"; pane: number } + | { type: "resized"; pane: number; rows: number; cols: number } + | { type: "hello"; client: number; panes: number } + | { type: "size_owner"; owned: boolean } + | { type: "error"; message: string } + | { type: "reordered"; order: number[] } + | { type: "zoomed"; pane: number | null } + | { type: "pending"; count: number } + | ({ type: "recovery" } & RecoveryFrame); + +type JsonObject = Record; + +function isInteger(value: unknown): value is number { + return typeof value === "number" && Number.isSafeInteger(value); +} + +function isUnsigned(value: unknown): value is number { + return isInteger(value) && value >= 0; +} + +/** Decode and validate the server's JSON control boundary. */ +export function decodeTerminalControlFrame( + frame: string, +): TerminalServerMessage | null { + let decoded: unknown; + try { + decoded = JSON.parse(frame); + } catch { + return null; + } + if (!decoded || typeof decoded !== "object" || Array.isArray(decoded)) { + return null; + } + + const message = decoded as JsonObject; + let valid: boolean; + switch (message.type) { + case "created": + valid = + isUnsigned(message.pane) && + isUnsigned(message.rows) && + isUnsigned(message.cols) && + (message.client === undefined || isUnsigned(message.client)) && + (message.title === undefined || typeof message.title === "string"); + break; + case "exited": + valid = isUnsigned(message.pane); + break; + case "resized": + valid = + isUnsigned(message.pane) && + isUnsigned(message.rows) && + isUnsigned(message.cols); + break; + case "hello": + valid = isUnsigned(message.client) && isUnsigned(message.panes); + break; + case "size_owner": + valid = typeof message.owned === "boolean"; + break; + case "error": + valid = typeof message.message === "string"; + break; + case "reordered": + valid = Array.isArray(message.order) && message.order.every(isUnsigned); + break; + case "zoomed": + valid = message.pane === null || isUnsigned(message.pane); + break; + case "pending": + valid = isUnsigned(message.count); + break; + case "recovery": + valid = + isUnsigned(message.pane) && + typeof message.state === "string" && + (message.detail === undefined || typeof message.detail === "string") && + (message.deadline_epoch === undefined || + isInteger(message.deadline_epoch)) && + isUnsigned(message.attempt); + break; + default: + return null; + } + return valid ? (message as TerminalServerMessage) : null; +} + +/** Send only while the socket can accept a control frame; reconnects do not queue. */ +export function sendTerminalMessage( + socket: WebSocket | null, + message: TerminalClientMessage, +): boolean { + if (!socket || socket.readyState !== WebSocket.OPEN) return false; + socket.send(JSON.stringify(message)); + return true; +} + +export interface TerminalOutputFrame { + pane: number; + data: Uint8Array; +} + +/** Decode the little-endian pane prefix used by binary PTY output frames. */ +export function decodeTerminalOutputFrame( + frame: ArrayBuffer, +): TerminalOutputFrame | null { + if (frame.byteLength < 4) return null; + const pane = new DataView(frame).getUint32(0, true); + return { pane, data: new Uint8Array(frame, 4) }; +} diff --git a/viewer-ui/src/components/FilePane.tsx b/viewer-ui/src/components/FilePane.tsx index 0e0d4077..026a0371 100644 --- a/viewer-ui/src/components/FilePane.tsx +++ b/viewer-ui/src/components/FilePane.tsx @@ -2,7 +2,7 @@ import { Suspense, lazy } from "react"; import { useDiffLayout } from "../lib/diffLayout"; import { fileViewSource, isHtmlPath, isPreviewablePath } from "../lib/fileView"; import { digitsFor } from "../lib/gutter"; -import { MaximizeIcon, PreviewIcon, SplitViewIcon } from "./icons"; +import { MaximizeIcon, PreviewIcon, SplitViewIcon } from "./icons/layout"; import { DiffView } from "./DiffView"; import { LineNos } from "./LineNos"; import { PathLabel } from "./PathLabel"; diff --git a/viewer-ui/src/components/FolderPicker.tsx b/viewer-ui/src/components/FolderPicker.tsx index afa32c72..652f2c86 100644 --- a/viewer-ui/src/components/FolderPicker.tsx +++ b/viewer-ui/src/components/FolderPicker.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { api, type Browse, type Repo } from "../api"; import { toast } from "../lib/toast"; -import { XIcon } from "./icons"; +import { XIcon } from "./icons/actions"; export function FolderPicker({ onClose, diff --git a/viewer-ui/src/components/Header.tsx b/viewer-ui/src/components/Header.tsx index 4914f859..dc24ed9a 100644 --- a/viewer-ui/src/components/Header.tsx +++ b/viewer-ui/src/components/Header.tsx @@ -1,16 +1,15 @@ import { Mark } from "./Mark"; import { ProjectMenu } from "./ProjectMenu"; -import { LogOutIcon, PlusIcon, RefreshIcon, XIcon } from "./icons"; +import { LogOutIcon, PlusIcon, RefreshIcon, XIcon } from "./icons/actions"; import { useReloadConfig } from "../hooks/useReloadConfig"; import type { Repo } from "../api"; export interface HeaderProps { repos: Repo[]; repo: string | null; - setRepo: (id: string) => void; - setPane: () => void; - closeRepo: (id: string) => void; - setPickerOpen: (open: boolean) => void; + onSelectRepo: (id: string) => void; + onCloseRepo: (id: string) => void; + onOpenPicker: () => void; /** A clone is running on the server. Shown here rather than in the folder * picker because the job outlives that dialog. */ cloning: boolean; @@ -27,10 +26,9 @@ export interface HeaderProps { export function Header({ repos, repo, - setRepo, - setPane, - closeRepo, - setPickerOpen, + onSelectRepo, + onCloseRepo, + onOpenPicker, cloning, accent, next, @@ -55,12 +53,9 @@ export function Header({ className="md:hidden" repos={repos} currentId={repo} - onSelect={(id) => { - setRepo(id); - setPane(); - }} - onCloseProject={closeRepo} - onOpenPicker={() => setPickerOpen(true)} + onSelect={onSelectRepo} + onCloseProject={onCloseRepo} + onOpenPicker={onOpenPicker} /> + ))} + + ); +} diff --git a/viewer-ui/src/components/RepoShell.tsx b/viewer-ui/src/components/RepoShell.tsx index 51a3af6a..70969d35 100644 --- a/viewer-ui/src/components/RepoShell.tsx +++ b/viewer-ui/src/components/RepoShell.tsx @@ -1,14 +1,14 @@ import { Suspense, lazy, useEffect } from "react"; import type { CSSProperties } from "react"; import { MAX_SIDEBAR_VIEWPORT_FRACTION } from "../hooks/ui/sidebar"; -import type { PointerEvent as ReactPointerEvent } from "react"; import { Sidebar } from "./Sidebar"; +import type { SidebarProps } from "./Sidebar"; import { FilePane } from "./FilePane"; -import type { ChangedFile, Commit, Repo, Status } from "../api"; -import type { CommitDrillDown } from "../hooks/useLog"; -import type { Maximized, Pane, Tab } from "../types"; -import type { MobileView } from "../types"; -import { FileTextIcon, ListIcon, TerminalIcon } from "./icons"; +import type { FilePaneProps } from "./FilePane"; +import type { Repo, Status } from "../api"; +import type { ShellLayout } from "../hooks/useShellLayout"; +import type { Maximized, MobileView } from "../types"; +import { RepoMobileNav } from "./RepoMobileNav"; // Keep xterm out of the initial login and git-viewer bundle. const TerminalPanel = lazy(() => @@ -16,89 +16,43 @@ const TerminalPanel = lazy(() => ); export interface RepoShellProps { - repo: string; - repos: Repo[]; - current: Repo | undefined; - status: Status | null; - files: ChangedFile[]; - now: number; - hotWindowMs: number; - pane: Pane; - setPane: React.Dispatch>; - tab: Tab; - setTab: (t: Tab) => void; - filter: string; - setFilter: (v: string) => void; - filterOpen: boolean; - setFilterOpen: React.Dispatch>; - openDiff: (path: string) => void; - openFile: (path: string) => void; - openCommit: (oid: string) => void; - openCommitFileDiff: (oid: string, path: string) => void; - openCommitFiles: (commit: Commit) => void; - authed: boolean | null; - handle: (err: unknown) => void; - sidebarWidth: number; - sidebarRef: React.RefObject; - draggingSidebar: boolean; - onSidebarDragStart: (e: ReactPointerEvent) => void; - onSidebarDragMove: (e: ReactPointerEvent) => void; - onSidebarDragEnd: () => void; - onSidebarDragCancel: () => void; - /** The diff panel and the terminal panel — the two edges of the vertical - * split the divider between them measures against. */ - upperRef: React.RefObject; - lowerRef: React.RefObject; - draggingUpper: boolean; - onUpperDragStart: (e: ReactPointerEvent) => void; - onUpperDragMove: (e: ReactPointerEvent) => void; - onUpperDragEnd: () => void; - onUpperDragCancel: () => void; - filesMax: boolean; - bumpPaneRequest: () => void; - commits: Commit[]; - logDone: boolean; - logStalled: boolean; - setLogStalled: (v: boolean | ((prev: boolean) => boolean)) => void; - commitDrillDown: CommitDrillDown | null; - setCommitDrillDown: (v: CommitDrillDown | null) => void; - resetLog: () => void; - logSentinelRef: React.RefObject; - visibleCommits: Commit[]; - logPagingPaused: boolean; - aheadOids: Set; - visibleCommitFiles: CommitDrillDown["files"]; - previewRendered: boolean; - setPreviewRendered: React.Dispatch>; - maximized: Maximized; - setMaximized: (next: Maximized | ((prev: Maximized) => Maximized)) => void; - mobileView: MobileView; - setMobileView: (view: MobileView) => void; + repository: { + id: string; + current: Repo | undefined; + status: Status | null; + }; + sidebar: Omit< + SidebarProps, + | "sidebarRef" + | "draggingSidebar" + | "onSidebarDragStart" + | "onSidebarDragMove" + | "onSidebarDragEnd" + | "onSidebarDragCancel" + | "filesMax" + | "mobileView" + | "repo" + | "status" + >; + filePane: Pick< + FilePaneProps, + "pane" | "previewRendered" | "setPreviewRendered" + >; + layout: ShellLayout & { + maximized: Maximized; + setMaximized: ( + next: Maximized | ((previous: Maximized) => Maximized), + ) => void; + mobileView: MobileView; + setMobileView: (view: MobileView) => void; + }; } -export function RepoShell(props: RepoShellProps) { - const { - repo, - current, - status, - files, - now, - hotWindowMs, - pane, - setPane, - tab, - setTab, - filter, - setFilter, - filterOpen, - setFilterOpen, - openDiff, - openFile, - openCommit, - openCommitFileDiff, - openCommitFiles, - authed, - handle, +export function RepoShell({ + repository: { id: repo, current, status }, + sidebar, + filePane, + layout: { sidebarWidth, sidebarRef, draggingSidebar, @@ -113,28 +67,13 @@ export function RepoShell(props: RepoShellProps) { onUpperDragMove, onUpperDragEnd, onUpperDragCancel, - filesMax, - bumpPaneRequest, - commits, - logDone, - logStalled, - setLogStalled, - commitDrillDown, - setCommitDrillDown, - resetLog, - logSentinelRef, - visibleCommits, - logPagingPaused, - aheadOids, - visibleCommitFiles, - previewRendered, - setPreviewRendered, maximized, setMaximized, mobileView, setMobileView, - } = props; - + }, +}: RepoShellProps) { + const filesMax = maximized === "files"; // The drag separator lives inside the keyed `Sidebar`, and the project can // change without the user letting go — another device switches it. The // separator then unmounts mid-drag and its pointerup never arrives, leaving @@ -178,25 +117,9 @@ export function RepoShell(props: RepoShellProps) { under the other until a reload. */} - +