aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-04-05 16:24:20 +0300
committerCan Celik <ogulcancelik@gmail.com>2026-04-05 16:38:50 +0300
commitb2e6929aa8199574565fd4adf1e631c68d479e6c (patch)
tree99964965fe84f9ea31a0dec7ede2393631dc7f50 /src
parent822c3b9e2d1edb7481fa558497dd33c7f5dca9ee (diff)
refactor: remove vt100 backend and make ghostty default
Diffstat (limited to 'src')
-rw-r--r--src/app/mod.rs3
-rw-r--r--src/detect.rs26
-rw-r--r--src/ghostty/mod.rs1
-rw-r--r--src/input/encode.rs4
-rw-r--r--src/input/mod.rs1
-rw-r--r--src/main.rs2
-rw-r--r--src/pane.rs539
-rw-r--r--src/pty_callbacks.rs450
-rw-r--r--src/selection.rs7
-rw-r--r--src/terminal_theme.rs1
10 files changed, 49 insertions, 985 deletions
diff --git a/src/app/mod.rs b/src/app/mod.rs
index 064b153a..65a1cc4a 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -774,12 +774,10 @@ impl App {
return;
}
- #[cfg(feature = "ghostty-vt")]
if let Some((ws_idx, pane_id)) = self.last_focus {
self.send_pane_focus_event(ws_idx, pane_id, crate::ghostty::FocusEvent::Lost);
}
if let Some((ws_idx, pane_id)) = current_focus {
- #[cfg(feature = "ghostty-vt")]
self.send_pane_focus_event(ws_idx, pane_id, crate::ghostty::FocusEvent::Gained);
self.emit_event(crate::api::schema::EventEnvelope {
event: crate::api::schema::EventKind::WorkspaceFocused,
@@ -812,7 +810,6 @@ impl App {
self.last_focus = current_focus;
}
- #[cfg(feature = "ghostty-vt")]
fn send_pane_focus_event(
&self,
ws_idx: usize,
diff --git a/src/detect.rs b/src/detect.rs
index bc59fe25..b7ffa441 100644
--- a/src/detect.rs
+++ b/src/detect.rs
@@ -1,6 +1,6 @@
//! Agent state detection via screen content pattern matching.
//!
-//! Each pane's `vt100::Screen` content is read periodically and matched
+//! Each pane's visible terminal content is read periodically and matched
//! against known agent output patterns to determine state.
/// The detected state of a terminal pane.
@@ -1177,31 +1177,19 @@ mod tests {
let _ = tpgid;
}
- // ---- VT100 integration ----
-
#[test]
- fn vt100_screen_content_works_with_detection() {
- let mut parser = vt100::Parser::new(24, 80, 0);
- parser.process(b"Working...");
- let content = parser.screen().contents();
- assert_eq!(detect_pi(&content), AgentState::Working);
+ fn visible_screen_content_works_with_detection() {
+ assert_eq!(detect_pi("Working..."), AgentState::Working);
}
#[test]
- fn vt100_screen_with_ansi_colors() {
- let mut parser = vt100::Parser::new(24, 80, 0);
- // Red "Working..." with ANSI codes — contents() strips formatting
- parser.process(b"\x1b[31mWorking...\x1b[0m");
- let content = parser.screen().contents();
- assert_eq!(detect_pi(&content), AgentState::Working);
+ fn ansi_colored_content_still_detects_working() {
+ assert_eq!(detect_pi("\x1b[31mWorking...\x1b[0m"), AgentState::Working);
}
#[test]
- fn vt100_screen_claude_prompt_box() {
- let mut parser = vt100::Parser::new(24, 80, 0);
+ fn visible_claude_prompt_box_is_idle() {
let screen = "Task complete.\n─────────────\n❯ \n─────────────";
- parser.process(screen.as_bytes());
- let content = parser.screen().contents();
- assert_eq!(detect_claude(&content), AgentState::Idle);
+ assert_eq!(detect_claude(screen), AgentState::Idle);
}
}
diff --git a/src/ghostty/mod.rs b/src/ghostty/mod.rs
index c5214472..e2bd409c 100644
--- a/src/ghostty/mod.rs
+++ b/src/ghostty/mod.rs
@@ -1,4 +1,3 @@
-#![cfg(feature = "ghostty-vt")]
#![allow(dead_code)]
#[allow(
diff --git a/src/input/encode.rs b/src/input/encode.rs
index 610cc694..4aa68e17 100644
--- a/src/input/encode.rs
+++ b/src/input/encode.rs
@@ -39,6 +39,7 @@ pub fn encode_cursor_key(code: KeyCode, application_cursor: bool) -> Vec<u8> {
}
}
+#[allow(dead_code)] // exercised in input unit tests; pane runtime uses backend helpers
pub fn encode_mouse_scroll(
kind: MouseEventKind,
column: u16,
@@ -56,6 +57,7 @@ pub fn encode_mouse_scroll(
encode_mouse_cb(button, false, column, row, modifiers, encoding)
}
+#[allow(dead_code)] // exercised in input unit tests; pane runtime uses backend helpers
pub fn encode_mouse_button(
kind: MouseEventKind,
column: u16,
@@ -78,6 +80,7 @@ pub fn encode_mouse_button(
encode_mouse_cb(button, release, column, row, modifiers, encoding)
}
+#[allow(dead_code)] // only reached through mouse encoding helpers above
fn encode_mouse_cb(
base_button: u16,
release: bool,
@@ -129,6 +132,7 @@ fn encode_mouse_cb(
}
}
+#[allow(dead_code)] // only reached through mouse encoding helpers above
fn push_mouse_codepoint(bytes: &mut Vec<u8>, value: u32) -> Option<()> {
let ch = char::from_u32(value)?;
let mut buf = [0u8; 4];
diff --git a/src/input/mod.rs b/src/input/mod.rs
index 4e34674c..109fd312 100644
--- a/src/input/mod.rs
+++ b/src/input/mod.rs
@@ -4,6 +4,7 @@ mod parse;
#[cfg(test)]
pub use encode::encode_key;
+#[allow(unused_imports)]
pub use encode::{
encode_cursor_key, encode_mouse_button, encode_mouse_scroll, encode_terminal_key,
};
diff --git a/src/main.rs b/src/main.rs
index b0f986bd..4fdfc9de 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -24,7 +24,6 @@ mod cli;
mod config;
mod detect;
mod events;
-#[cfg(feature = "ghostty-vt")]
mod ghostty;
mod input;
mod integration;
@@ -32,7 +31,6 @@ mod layout;
mod pane;
mod persist;
mod platform;
-mod pty_callbacks;
mod raw_input;
mod release_notes;
mod selection;
diff --git a/src/pane.rs b/src/pane.rs
index f71822bd..02bf5033 100644
--- a/src/pane.rs
+++ b/src/pane.rs
@@ -2,24 +2,20 @@ use std::cell::Cell;
use std::io::{BufWriter, Read, Write};
use std::sync::{
atomic::{AtomicBool, AtomicU16, AtomicU32, Ordering},
- Arc, Mutex, RwLock,
+ Arc, Mutex,
};
use bytes::Bytes;
use portable_pty::{native_pty_system, CommandBuilder, PtySize};
-#[cfg(feature = "ghostty-vt")]
use ratatui::style::{Color, Modifier, Style};
use ratatui::{layout::Rect, Frame};
use tokio::sync::{mpsc, Notify};
use tracing::{debug, error, info, warn};
-use tui_term::widget::PseudoTerminal;
-#[cfg(feature = "ghostty-vt")]
use unicode_width::UnicodeWidthStr;
use crate::detect::{Agent, AgentState};
use crate::events::AppEvent;
use crate::layout::PaneId;
-use crate::pty_callbacks::PtyResponses;
const CLAUDE_WORKING_HOLD: std::time::Duration = std::time::Duration::from_millis(1200);
const RELEASE_REACQUIRE_SUPPRESSION: std::time::Duration = std::time::Duration::from_secs(1);
@@ -289,29 +285,17 @@ struct ProcessBytesResult {
request_render: bool,
}
-#[cfg_attr(feature = "ghostty-vt", allow(dead_code))]
-struct VtPaneTerminal {
- parser: Arc<RwLock<vt100::Parser<PtyResponses>>>,
- screen_content: Arc<RwLock<String>>,
- mouse_alternate_scroll: Arc<AtomicBool>,
-}
-
-#[cfg(feature = "ghostty-vt")]
struct GhosttyPaneTerminal {
core: Mutex<GhosttyPaneCore>,
}
-#[cfg(feature = "ghostty-vt")]
struct GhosttyPaneCore {
terminal: crate::ghostty::Terminal,
render_state: crate::ghostty::RenderState,
}
-enum PaneTerminal {
- #[cfg_attr(feature = "ghostty-vt", allow(dead_code))]
- Vt(VtPaneTerminal),
- #[cfg(feature = "ghostty-vt")]
- Ghostty(GhosttyPaneTerminal),
+struct PaneTerminal {
+ ghostty: GhosttyPaneTerminal,
}
impl PaneTerminal {
@@ -321,119 +305,63 @@ impl PaneTerminal {
bytes: &[u8],
response_writer: &mpsc::Sender<Bytes>,
) -> ProcessBytesResult {
- match self {
- Self::Vt(vt) => vt.process_pty_bytes(pane_id, bytes, response_writer),
- #[cfg(feature = "ghostty-vt")]
- Self::Ghostty(ghostty) => ghostty.process_pty_bytes(pane_id, bytes, response_writer),
- }
+ self.ghostty
+ .process_pty_bytes(pane_id, bytes, response_writer)
}
fn resize(&self, rows: u16, cols: u16) {
- match self {
- Self::Vt(vt) => vt.resize(rows, cols),
- #[cfg(feature = "ghostty-vt")]
- Self::Ghostty(ghostty) => ghostty.resize(rows, cols),
- }
+ self.ghostty.resize(rows, cols);
}
fn scroll_up(&self, lines: usize) {
- match self {
- Self::Vt(vt) => vt.scroll_up(lines),
- #[cfg(feature = "ghostty-vt")]
- Self::Ghostty(ghostty) => ghostty.scroll_up(lines),
- }
+ self.ghostty.scroll_up(lines);
}
fn scroll_down(&self, lines: usize) {
- match self {
- Self::Vt(vt) => vt.scroll_down(lines),
- #[cfg(feature = "ghostty-vt")]
- Self::Ghostty(ghostty) => ghostty.scroll_down(lines),
- }
+ self.ghostty.scroll_down(lines);
}
fn scroll_reset(&self) {
- match self {
- Self::Vt(vt) => vt.scroll_reset(),
- #[cfg(feature = "ghostty-vt")]
- Self::Ghostty(ghostty) => ghostty.scroll_reset(),
- }
+ self.ghostty.scroll_reset();
}
fn set_scroll_offset_from_bottom(&self, lines: usize) {
- match self {
- Self::Vt(vt) => vt.set_scroll_offset_from_bottom(lines),
- #[cfg(feature = "ghostty-vt")]
- Self::Ghostty(ghostty) => ghostty.set_scroll_offset_from_bottom(lines),
- }
+ self.ghostty.set_scroll_offset_from_bottom(lines);
}
fn scroll_metrics(&self) -> Option<ScrollMetrics> {
- match self {
- Self::Vt(vt) => vt.scroll_metrics(),
- #[cfg(feature = "ghostty-vt")]
- Self::Ghostty(ghostty) => ghostty.scroll_metrics(),
- }
+ self.ghostty.scroll_metrics()
}
fn input_state(&self) -> Option<InputState> {
- match self {
- Self::Vt(vt) => vt.input_state(),
- #[cfg(feature = "ghostty-vt")]
- Self::Ghostty(ghostty) => ghostty.input_state(),
- }
+ self.ghostty.input_state()
}
fn visible_text(&self) -> String {
- match self {
- Self::Vt(vt) => vt.visible_text(),
- #[cfg(feature = "ghostty-vt")]
- Self::Ghostty(ghostty) => ghostty.visible_text(),
- }
+ self.ghostty.visible_text()
}
fn recent_text(&self, lines: usize) -> String {
- match self {
- Self::Vt(vt) => vt.recent_text(lines),
- #[cfg(feature = "ghostty-vt")]
- Self::Ghostty(ghostty) => ghostty.recent_text(lines),
- }
+ self.ghostty.recent_text(lines)
}
fn extract_selection(&self, selection: &crate::selection::Selection) -> Option<String> {
- match self {
- Self::Vt(vt) => vt.extract_selection(selection),
- #[cfg(feature = "ghostty-vt")]
- Self::Ghostty(ghostty) => ghostty.extract_selection(selection),
- }
+ self.ghostty.extract_selection(selection)
}
fn render(&self, frame: &mut Frame, area: Rect) {
- match self {
- Self::Vt(vt) => vt.render(frame, area),
- #[cfg(feature = "ghostty-vt")]
- Self::Ghostty(ghostty) => ghostty.render(frame, area),
- }
+ self.ghostty.render(frame, area);
}
fn apply_host_terminal_theme(&self, theme: crate::terminal_theme::TerminalTheme) {
- let _ = theme;
- match self {
- Self::Vt(_) => {}
- #[cfg(feature = "ghostty-vt")]
- Self::Ghostty(ghostty) => ghostty.apply_host_terminal_theme(theme),
- }
+ self.ghostty.apply_host_terminal_theme(theme);
}
fn keyboard_protocol(
&self,
fallback: crate::input::KeyboardProtocol,
) -> crate::input::KeyboardProtocol {
- match self {
- Self::Vt(_) => fallback,
- #[cfg(feature = "ghostty-vt")]
- Self::Ghostty(ghostty) => ghostty.keyboard_protocol().unwrap_or(fallback),
- }
+ self.ghostty.keyboard_protocol().unwrap_or(fallback)
}
fn encode_terminal_key(
@@ -441,11 +369,7 @@ impl PaneTerminal {
key: crate::input::TerminalKey,
protocol: crate::input::KeyboardProtocol,
) -> Vec<u8> {
- match self {
- Self::Vt(vt) => vt.encode_terminal_key(key, protocol),
- #[cfg(feature = "ghostty-vt")]
- Self::Ghostty(ghostty) => ghostty.encode_terminal_key(key, protocol),
- }
+ self.ghostty.encode_terminal_key(key, protocol)
}
fn encode_mouse_button(
@@ -455,19 +379,8 @@ impl PaneTerminal {
row: u16,
modifiers: crossterm::event::KeyModifiers,
) -> Option<Vec<u8>> {
- match self {
- Self::Vt(vt) => vt.input_state().and_then(|input_state| {
- crate::input::encode_mouse_button(
- kind,
- column,
- row,
- modifiers,
- input_state.mouse_protocol_encoding,
- )
- }),
- #[cfg(feature = "ghostty-vt")]
- Self::Ghostty(ghostty) => ghostty.encode_mouse_button(kind, column, row, modifiers),
- }
+ self.ghostty
+ .encode_mouse_button(kind, column, row, modifiers)
}
fn encode_mouse_wheel(
@@ -477,219 +390,11 @@ impl PaneTerminal {
row: u16,
modifiers: crossterm::event::KeyModifiers,
) -> Option<Vec<u8>> {
- match self {
- Self::Vt(vt) => vt.input_state().and_then(|input_state| {
- crate::input::encode_mouse_scroll(
- kind,
- column,
- row,
- modifiers,
- input_state.mouse_protocol_encoding,
- )
- }),
- #[cfg(feature = "ghostty-vt")]
- Self::Ghostty(ghostty) => ghostty.encode_mouse_wheel(kind, column, row, modifiers),
- }
- }
-}
-
-#[cfg_attr(feature = "ghostty-vt", allow(dead_code))]
-impl VtPaneTerminal {
- fn new(
- parser: Arc<RwLock<vt100::Parser<PtyResponses>>>,
- screen_content: Arc<RwLock<String>>,
- mouse_alternate_scroll: Arc<AtomicBool>,
- ) -> Self {
- Self {
- parser,
- screen_content,
- mouse_alternate_scroll,
- }
- }
-
- fn process_pty_bytes(
- &self,
- pane_id: PaneId,
- bytes: &[u8],
- response_writer: &mpsc::Sender<Bytes>,
- ) -> ProcessBytesResult {
- let resp = if let Ok(mut parser) = self.parser.write() {
- parser.process(bytes);
-
- let scrollback = parser.screen().scrollback();
- if scrollback > 0 {
- parser.screen_mut().set_scrollback(0);
- }
- let content = parser.screen().contents();
- if scrollback > 0 {
- parser.screen_mut().set_scrollback(scrollback);
- }
- if let Ok(mut screen_content) = self.screen_content.write() {
- *screen_content = content;
- }
- parser.callbacks_mut().take()
- } else {
- error!(pane = pane_id.raw(), "parser lock poisoned in reader");
- return ProcessBytesResult {
- request_render: false,
- };
- };
-
- if !resp.is_empty() {
- if let Err(e) = response_writer.try_send(Bytes::from(resp)) {
- warn!(pane = pane_id.raw(), err = %e, "dropped terminal query response");
- }
- }
-
- ProcessBytesResult {
- request_render: true,
- }
- }
-
- fn resize(&self, rows: u16, cols: u16) {
- if let Ok(mut parser) = self.parser.write() {
- parser.screen_mut().set_size(rows, cols);
- }
- }
-
- fn scroll_up(&self, lines: usize) {
- if let Ok(mut parser) = self.parser.write() {
- let current = parser.screen().scrollback();
- parser.screen_mut().set_scrollback(current + lines);
- }
- }
-
- fn scroll_down(&self, lines: usize) {
- if let Ok(mut parser) = self.parser.write() {
- let current = parser.screen().scrollback();
- parser
- .screen_mut()
- .set_scrollback(current.saturating_sub(lines));
- }
- }
-
- fn scroll_reset(&self) {
- if let Ok(mut parser) = self.parser.write() {
- parser.screen_mut().set_scrollback(0);
- }
- }
-
- fn set_scroll_offset_from_bottom(&self, lines: usize) {
- if let Ok(mut parser) = self.parser.write() {
- parser.screen_mut().set_scrollback(lines);
- }
- }
-
- fn scroll_metrics(&self) -> Option<ScrollMetrics> {
- let Ok(mut parser) = self.parser.write() else {
- return None;
- };
- let max_offset_from_bottom = max_scrollback(&mut parser);
- let screen = parser.screen();
- let (viewport_rows, _) = screen.size();
- Some(ScrollMetrics {
- offset_from_bottom: screen.scrollback(),
- max_offset_from_bottom,
- viewport_rows: viewport_rows as usize,
- })
- }
-
- fn input_state(&self) -> Option<InputState> {
- let Ok(parser) = self.parser.read() else {
- return None;
- };
- let screen = parser.screen();
- Some(InputState {
- alternate_screen: screen.alternate_screen(),
- application_cursor: screen.application_cursor(),
- bracketed_paste: screen.bracketed_paste(),
- focus_reporting: false,
- mouse_protocol_mode: match screen.mouse_protocol_mode() {
- vt100::MouseProtocolMode::None => crate::input::MouseProtocolMode::None,
- vt100::MouseProtocolMode::Press => crate::input::MouseProtocolMode::Press,
- vt100::MouseProtocolMode::PressRelease => {
- crate::input::MouseProtocolMode::PressRelease
- }
- vt100::MouseProtocolMode::ButtonMotion => {
- crate::input::MouseProtocolMode::ButtonMotion
- }
- vt100::MouseProtocolMode::AnyMotion => crate::input::MouseProtocolMode::AnyMotion,
- },
- mouse_protocol_encoding: match screen.mouse_protocol_encoding() {
- vt100::MouseProtocolEncoding::Default => {
- crate::input::MouseProtocolEncoding::Default
- }
- vt100::MouseProtocolEncoding::Utf8 => crate::input::MouseProtocolEncoding::Utf8,
- vt100::MouseProtocolEncoding::Sgr => crate::input::MouseProtocolEncoding::Sgr,
- },
- mouse_alternate_scroll: self.mouse_alternate_scroll.load(Ordering::Relaxed),
- })
- }
-
- fn visible_text(&self) -> String {
- let Ok(content) = self.screen_content.read() else {
- return String::new();
- };
- let mut rows: Vec<String> = content
- .lines()
- .map(|line| line.trim_end().to_string())
- .collect();
- trim_trailing_blank_rows(&mut rows);
- let text = rows.join("\n");
- if text.is_empty() {
- text
- } else {
- format!("{text}\n")
- }
- }
-
- fn recent_text(&self, lines: usize) -> String {
- self.parser
- .write()
- .map(|mut parser| recent_text_from_parser(&mut parser, lines))
- .unwrap_or_default()
- }
-
- fn extract_selection(&self, selection: &crate::selection::Selection) -> Option<String> {
- self.parser
- .read()
- .map(|parser| crate::selection::extract_text(parser.screen(), selection))
- .ok()
- }
-
- fn render(&self, frame: &mut Frame, area: Rect) {
- if let Ok(parser) = self.parser.read() {
- let show_cursor = parser.screen().scrollback() == 0;
- let pt = PseudoTerminal::new(parser.screen())
- .cursor(tui_term::widget::Cursor::default().visibility(show_cursor));
- frame.render_widget(pt, area);
- }
- }
-
- fn encode_terminal_key(
- &self,
- key: crate::input::TerminalKey,
- protocol: crate::input::KeyboardProtocol,
- ) -> Vec<u8> {
- let application_cursor = self
- .input_state()
- .map(|state| state.application_cursor)
- .unwrap_or(false);
- if matches!(
- key.code,
- crossterm::event::KeyCode::Up
- | crossterm::event::KeyCode::Down
- | crossterm::event::KeyCode::Left
- | crossterm::event::KeyCode::Right
- ) && key.modifiers.is_empty()
- {
- return crate::input::encode_cursor_key(key.code, application_cursor);
- }
- crate::input::encode_terminal_key(key, protocol)
+ self.ghostty
+ .encode_mouse_wheel(kind, column, row, modifiers)
}
}
-#[cfg(feature = "ghostty-vt")]
impl GhosttyPaneTerminal {
fn new(
mut terminal: crate::ghostty::Terminal,
@@ -1019,8 +724,6 @@ impl GhosttyPaneTerminal {
}
}
-#[cfg(feature = "ghostty-vt")]
-#[cfg(feature = "ghostty-vt")]
fn ghostty_key_event_from_terminal_key(
key: crate::input::TerminalKey,
) -> Option<crate::ghostty::KeyEvent> {
@@ -1055,12 +758,10 @@ fn ghostty_key_event_from_terminal_key(
Some(event)
}
-#[cfg(feature = "ghostty-vt")]
fn ghostty_prefers_herdr_text_encoding(key: crate::input::TerminalKey) -> bool {
matches!(key.code, crossterm::event::KeyCode::Char(_))
}
-#[cfg(feature = "ghostty-vt")]
fn ghostty_mods_from_key_modifiers(modifiers: crossterm::event::KeyModifiers) -> u16 {
let mut ghostty_mods = 0u16;
if modifiers.contains(crossterm::event::KeyModifiers::SHIFT) {
@@ -1078,7 +779,6 @@ fn ghostty_mods_from_key_modifiers(modifiers: crossterm::event::KeyModifiers) ->
ghostty_mods
}
-#[cfg(feature = "ghostty-vt")]
fn ghostty_mouse_encoder_for_terminal(
terminal: &crate::ghostty::Terminal,
) -> Option<crate::ghostty::MouseEncoder> {
@@ -1090,7 +790,6 @@ fn ghostty_mouse_encoder_for_terminal(
Some(encoder)
}
-#[cfg(feature = "ghostty-vt")]
fn ghostty_mouse_event_from_button_kind(
kind: crossterm::event::MouseEventKind,
column: u16,
@@ -1148,7 +847,6 @@ fn ghostty_mouse_event_from_button_kind(
Some(event)
}
-#[cfg(feature = "ghostty-vt")]
fn ghostty_mouse_event_from_wheel_kind(
kind: crossterm::event::MouseEventKind,
column: u16,
@@ -1170,7 +868,6 @@ fn ghostty_mouse_event_from_wheel_kind(
Some(event)
}
-#[cfg(feature = "ghostty-vt")]
fn ghostty_key_text(key: crate::input::TerminalKey) -> Option<String> {
match key.code {
crossterm::event::KeyCode::Char(c) => Some(
@@ -1183,7 +880,6 @@ fn ghostty_key_text(key: crate::input::TerminalKey) -> Option<String> {
}
}
-#[cfg(feature = "ghostty-vt")]
fn ghostty_unshifted_codepoint(key: crate::input::TerminalKey) -> Option<u32> {
match key.code {
crossterm::event::KeyCode::Char(c) => Some(c as u32),
@@ -1191,7 +887,6 @@ fn ghostty_unshifted_codepoint(key: crate::input::TerminalKey) -> Option<u32> {
}
}
-#[cfg(feature = "ghostty-vt")]
fn ghostty_key_from_crossterm_key_code(
code: crossterm::event::KeyCode,
shifted_codepoint: Option<u32>,
@@ -1234,7 +929,6 @@ fn ghostty_key_from_crossterm_key_code(
}
}
-#[cfg(feature = "ghostty-vt")]
fn ghostty_key_from_char(c: char, shifted_codepoint: Option<u32>) -> Option<u32> {
use crate::ghostty::ffi;
@@ -1297,7 +991,6 @@ fn ghostty_key_from_char(c: char, shifted_codepoint: Option<u32>) -> Option<u32>
}
}
-#[cfg(feature = "ghostty-vt")]
fn ghostty_unshifted_ascii_pair(c: char) -> Option<char> {
Some(match c {
'!' => '1',
@@ -1325,7 +1018,6 @@ fn ghostty_unshifted_ascii_pair(c: char) -> Option<char> {
})
}
-#[cfg(feature = "ghostty-vt")]
fn ghostty_visible_text(core: &mut GhosttyPaneCore) -> Result<String, crate::ghostty::Error> {
let GhosttyPaneCore {
terminal,
@@ -1344,7 +1036,6 @@ fn ghostty_visible_text(core: &mut GhosttyPaneCore) -> Result<String, crate::gho
Ok(lines_to_text(lines))
}
-#[cfg(feature = "ghostty-vt")]
fn ghostty_recent_text(
core: &GhosttyPaneCore,
lines: usize,
@@ -1360,7 +1051,6 @@ fn ghostty_recent_text(
Ok(recent_text_from_rows(&rows, lines))
}
-#[cfg(feature = "ghostty-vt")]
fn ghostty_extract_selection(
core: &mut GhosttyPaneCore,
selection: &crate::selection::Selection,
@@ -1395,7 +1085,6 @@ fn ghostty_extract_selection(
Ok(lines.join("\n"))
}
-#[cfg(feature = "ghostty-vt")]
fn selection_cols_for_row(selection: &crate::selection::Selection, row: u16) -> (u16, u16) {
let ((start_row, start_col), (end_row, end_col)) = selection.ordered_cells();
if start_row == end_row {
@@ -1409,7 +1098,6 @@ fn selection_cols_for_row(selection: &crate::selection::Selection, row: u16) ->
}
}
-#[cfg(feature = "ghostty-vt")]
fn ghostty_screen_row(
core: &GhosttyPaneCore,
cols: u16,
@@ -1431,7 +1119,6 @@ fn ghostty_screen_row(
Ok(line.trim_end().to_string())
}
-#[cfg(feature = "ghostty-vt")]
fn ghostty_line_from_cells(
cells: &mut crate::ghostty::RowCellIter<'_>,
) -> Result<String, crate::ghostty::Error> {
@@ -1442,7 +1129,6 @@ fn ghostty_line_from_cells(
Ok(line.trim_end().to_string())
}
-#[cfg(feature = "ghostty-vt")]
fn ghostty_cell_symbol(
cells: &crate::ghostty::RowCellIter<'_>,
) -> Result<String, crate::ghostty::Error> {
@@ -1462,7 +1148,6 @@ fn ghostty_cell_symbol(
Ok(text)
}
-#[cfg(feature = "ghostty-vt")]
fn ghostty_blank_symbol_for_width(wide: crate::ghostty::CellWide) -> &'static str {
match wide {
crate::ghostty::CellWide::Wide => " ",
@@ -1471,7 +1156,6 @@ fn ghostty_blank_symbol_for_width(wide: crate::ghostty::CellWide) -> &'static st
}
}
-#[cfg(feature = "ghostty-vt")]
fn ghostty_normalize_buffer_symbol(symbol: &str, wide: crate::ghostty::CellWide) -> String {
let expected_width = match wide {
crate::ghostty::CellWide::Wide => 2,
@@ -1485,7 +1169,6 @@ fn ghostty_normalize_buffer_symbol(symbol: &str, wide: crate::ghostty::CellWide)
ghostty_blank_symbol_for_width(wide).to_string()
}
-#[cfg(feature = "ghostty-vt")]
fn ghostty_buffer_symbol(
cells: &crate::ghostty::RowCellIter<'_>,
wide: crate::ghostty::CellWide,
@@ -1500,7 +1183,6 @@ fn ghostty_buffer_symbol(
Ok(ghostty_normalize_buffer_symbol(&symbol, wide))
}
-#[cfg(feature = "ghostty-vt")]
fn ghostty_reset_cell(
cell: &mut ratatui::buffer::Cell,
default_fg: Option<Color>,
@@ -1516,7 +1198,6 @@ fn ghostty_reset_cell(
}
}
-#[cfg(feature = "ghostty-vt")]
fn ghostty_cell_style(
cells: &crate::ghostty::RowCellIter<'_>,
default_fg: Option<Color>,
@@ -1572,7 +1253,6 @@ fn ghostty_cell_style(
style.add_modifier(modifiers)
}
-#[cfg(feature = "ghostty-vt")]
fn ghostty_color(color: crate::ghostty::RgbColor) -> Color {
Color::Rgb(color.r, color.g, color.b)
}
@@ -1608,44 +1288,6 @@ fn trim_trailing_blank_rows(rows: &mut Vec<String>) {
}
}
-fn max_scrollback(parser: &mut vt100::Parser<PtyResponses>) -> usize {
- let screen = parser.screen_mut();
- let original_scrollback = screen.scrollback();
- screen.set_scrollback(usize::MAX);
- let max_scrollback = screen.scrollback();
- screen.set_scrollback(original_scrollback);
- max_scrollback
-}
-
-fn parser_rows(parser: &mut vt100::Parser<PtyResponses>, lines: usize) -> Vec<String> {
- let max_scrollback = max_scrollback(parser);
- let screen = parser.screen_mut();
- let original_scrollback = screen.scrollback();
-
- let (_, cols) = screen.size();
- screen.set_scrollback(0);
- let visible_rows: Vec<String> = screen
- .rows(0, cols)
- .map(|row| row.trim_end().to_string())
- .collect();
- let extra_rows = lines.saturating_sub(visible_rows.len()).min(max_scrollback);
-
- let mut rows = Vec::with_capacity(extra_rows + visible_rows.len());
- if extra_rows > 0 {
- for offset in (1..=extra_rows).rev() {
- screen.set_scrollback(offset);
- if let Some(row) = screen.rows(0, cols).next() {
- rows.push(row.trim_end().to_string());
- }
- }
- }
-
- screen.set_scrollback(original_scrollback);
- rows.extend(visible_rows);
- trim_trailing_blank_rows(&mut rows);
- rows
-}
-
fn recent_text_from_rows(rows: &[String], lines: usize) -> String {
let start = rows.len().saturating_sub(lines);
let text = rows[start..].join("\n");
@@ -1656,11 +1298,6 @@ fn recent_text_from_rows(rows: &[String], lines: usize) -> String {
}
}
-fn recent_text_from_parser(parser: &mut vt100::Parser<PtyResponses>, lines: usize) -> String {
- let rows = parser_rows(parser, lines);
- recent_text_from_rows(&rows, lines)
-}
-
fn wait_for_processes_to_exit(pids: &[u32], timeout: std::time::Duration) -> bool {
let deadline = std::time::Instant::now() + timeout;
loop {
@@ -1756,41 +1393,14 @@ impl PaneRuntime {
// --- Writer channel ---
let (input_tx, mut input_rx) = mpsc::channel::<Bytes>(32);
- // Live screen snapshot for detection (decoupled from parser scrollback)
- #[cfg_attr(feature = "ghostty-vt", allow(unused_variables))]
- let screen_content = Arc::new(RwLock::new(String::new()));
-
- #[cfg(not(feature = "ghostty-vt"))]
- let terminal = {
- let responses = PtyResponses::new();
- let kitty_keyboard_flags = responses.kitty_keyboard_flags.clone();
- let mouse_alternate_scroll = responses.mouse_alternate_scroll.clone();
- let parser = Arc::new(RwLock::new(vt100::Parser::new_with_callbacks(
- rows, cols, 10000, responses,
- )));
- (
- Arc::new(PaneTerminal::Vt(VtPaneTerminal::new(
- parser,
- screen_content.clone(),
- mouse_alternate_scroll,
- ))),
- kitty_keyboard_flags,
- )
- };
-
- #[cfg(feature = "ghostty-vt")]
- let terminal = {
- let terminal = crate::ghostty::Terminal::new(cols, rows, 10000)
- .map_err(|e| std::io::Error::other(e.to_string()))?;
- let pane_terminal = GhosttyPaneTerminal::new(terminal, input_tx.clone())?;
- pane_terminal.apply_host_terminal_theme(_host_terminal_theme);
- (
- Arc::new(PaneTerminal::Ghostty(pane_terminal)),
- Arc::new(AtomicU16::new(0)),
- )
- };
-
- let (terminal, kitty_keyboard_flags) = terminal;
+ let terminal = crate::ghostty::Terminal::new(cols, rows, 10000)
+ .map_err(|e| std::io::Error::other(e.to_string()))?;
+ let pane_terminal = GhosttyPaneTerminal::new(terminal, input_tx.clone())?;
+ pane_terminal.apply_host_terminal_theme(_host_terminal_theme);
+ let terminal = Arc::new(PaneTerminal {
+ ghostty: pane_terminal,
+ });
+ let kitty_keyboard_flags = Arc::new(AtomicU16::new(0));
let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".into());
let mut cmd = CommandBuilder::new(&shell);
@@ -2141,7 +1751,6 @@ impl PaneRuntime {
self.send_bytes(Bytes::from(payload)).await
}
- #[cfg(feature = "ghostty-vt")]
pub fn try_send_focus_event(&self, event: crate::ghostty::FocusEvent) -> bool {
if !self
.input_state()
@@ -2229,7 +1838,6 @@ impl PaneRuntime {
mod tests {
use super::*;
- #[cfg(feature = "ghostty-vt")]
#[test]
fn ghostty_keyboard_protocol_tracks_live_terminal_flags() {
let (tx, _rx) = mpsc::channel(4);
@@ -2243,7 +1851,6 @@ mod tests {
);
}
- #[cfg(feature = "ghostty-vt")]
#[test]
fn ghostty_plain_text_chars_still_encode_as_text() {
let (tx, _rx) = mpsc::channel(4);
@@ -2261,7 +1868,6 @@ mod tests {
assert_eq!(encoded, b"a");
}
- #[cfg(feature = "ghostty-vt")]
#[test]
fn ghostty_char_keys_still_use_herdr_encoding() {
let (tx, _rx) = mpsc::channel(4);
@@ -2280,7 +1886,6 @@ mod tests {
assert_eq!(encoded, vec![1]);
}
- #[cfg(feature = "ghostty-vt")]
#[test]
fn ghostty_key_encoding_honors_application_cursor_mode() {
let (tx, _rx) = mpsc::channel(4);
@@ -2301,7 +1906,6 @@ mod tests {
assert_eq!(encoded, b"\x1bOA");
}
- #[cfg(feature = "ghostty-vt")]
#[test]
fn ghostty_mouse_button_encoding_uses_live_terminal_state() {
let (tx, _rx) = mpsc::channel(4);
@@ -2319,7 +1923,6 @@ mod tests {
assert_eq!(encoded.as_deref(), Some(&b"\x1b[<0;12;10m"[..]));
}
- #[cfg(feature = "ghostty-vt")]
#[test]
fn ghostty_mouse_drag_encoding_uses_motion_reporting_state() {
let (tx, _rx) = mpsc::channel(4);
@@ -2337,7 +1940,6 @@ mod tests {
assert_eq!(encoded.as_deref(), Some(&b"\x1b[<36;5;7M"[..]));
}
- #[cfg(feature = "ghostty-vt")]
#[test]
fn ghostty_normalize_buffer_symbol_uses_ratatui_width_contract() {
assert_eq!(
@@ -2362,7 +1964,6 @@ mod tests {
);
}
- #[cfg(feature = "ghostty-vt")]
#[tokio::test]
async fn focus_events_are_forwarded_when_enabled() {
let (tx, mut rx) = mpsc::channel(4);
@@ -2372,9 +1973,9 @@ mod tests {
.mode_set(crate::ghostty::MODE_FOCUS_EVENT, true)
.unwrap();
let runtime = PaneRuntime {
- terminal: Arc::new(PaneTerminal::Ghostty(
- GhosttyPaneTerminal::new(terminal, tx.clone()).unwrap(),
- )),
+ terminal: Arc::new(PaneTerminal {
+ ghostty: GhosttyPaneTerminal::new(terminal, tx.clone()).unwrap(),
+ }),
sender: tx,
resize_tx,
current_size: Cell::new((80, 24)),
@@ -2389,16 +1990,15 @@ mod tests {
assert_eq!(rx.recv().await.unwrap(), Bytes::from_static(b"\x1b[I"));
}
- #[cfg(feature = "ghostty-vt")]
#[tokio::test]
async fn focus_events_are_suppressed_when_disabled() {
let (tx, mut rx) = mpsc::channel(4);
let (resize_tx, _resize_rx) = mpsc::channel(1);
let terminal = crate::ghostty::Terminal::new(80, 24, 0).unwrap();
let runtime = PaneRuntime {
- terminal: Arc::new(PaneTerminal::Ghostty(
- GhosttyPaneTerminal::new(terminal, tx.clone()).unwrap(),
- )),
+ terminal: Arc::new(PaneTerminal {
+ ghostty: GhosttyPaneTerminal::new(terminal, tx.clone()).unwrap(),
+ }),
sender: tx,
resize_tx,
current_size: Cell::new((80, 24)),
@@ -2418,29 +2018,6 @@ mod tests {
}
#[test]
- fn recent_text_reconstructs_scrollback_tail() {
- let responses = PtyResponses::new();
- let mut parser = vt100::Parser::new_with_callbacks(3, 10, 100, responses);
- parser.process(b"a\r\nb\r\nc\r\nd\r\ne");
-
- let recent = recent_text_from_parser(&mut parser, 4);
- assert_eq!(recent, "b\nc\nd\ne\n");
- }
-
- #[test]
- fn max_scrollback_reports_clamped_history_without_changing_position() {
- let responses = PtyResponses::new();
- let mut parser = vt100::Parser::new_with_callbacks(3, 10, 100, responses);
- parser.process(b"a\r\nb\r\nc\r\nd\r\ne");
- parser.screen_mut().set_scrollback(1);
-
- let max = max_scrollback(&mut parser);
-
- assert_eq!(max, 2);
- assert_eq!(parser.screen().scrollback(), 1);
- }
-
- #[test]
fn trim_trailing_blank_rows_drops_empty_viewport_tail() {
let mut rows = vec!["hello".to_string(), "".to_string(), " ".to_string()];
trim_trailing_blank_rows(&mut rows);
@@ -2448,48 +2025,6 @@ mod tests {
}
#[test]
- fn alternate_screen_does_not_accumulate_host_scrollback() {
- let responses = PtyResponses::new();
- let mut parser = vt100::Parser::new_with_callbacks(3, 10, 100, responses);
- parser.process(b"\x1b[?1049h1\r\n2\r\n3\r\n4");
-
- assert!(parser.screen().alternate_screen());
- assert_eq!(max_scrollback(&mut parser), 0);
- assert_eq!(recent_text_from_parser(&mut parser, 4), "2\n3\n4\n");
- }
-
- #[test]
- fn normal_screen_top_anchored_scroll_regions_feed_scrollback() {
- let responses = PtyResponses::new();
- let mut parser = vt100::Parser::new_with_callbacks(5, 10, 100, responses);
- parser.process(b"1\r\n2\r\n3\r\n4\r\n5");
- parser.process(b"\x1b[1;3r\x1b[3;1H\r\nX");
-
- assert_eq!(max_scrollback(&mut parser), 1);
- }
-
- #[test]
- fn normal_screen_non_top_anchored_scroll_regions_do_not_feed_scrollback() {
- let responses = PtyResponses::new();
- let mut parser = vt100::Parser::new_with_callbacks(5, 10, 100, responses);
- parser.process(b"1\r\n2\r\n3\r\n4\r\n5");
- parser.process(b"\x1b[2;4r\x1b[4;1H\r\nX");
-
- assert_eq!(max_scrollback(&mut parser), 0);
- }
-
- #[test]
- fn alternate_screen_scroll_regions_do_not_create_host_scrollback() {
- let responses = PtyResponses::new();
- let mut parser = vt100::Parser::new_with_callbacks(5, 10, 100, responses);
- parser.process(b"\x1b[?1049h1\r\n2\r\n3\r\n4\r\n5");
- parser.process(b"\x1b[1;3r\x1b[3;1H\r\nX");
-
- assert!(parser.screen().alternate_screen());
- assert_eq!(max_scrollback(&mut parser), 0);
- }
-
- #[test]
fn claude_working_is_sticky_for_short_gap() {
let now = std::time::Instant::now();
let mut last_working = None;
diff --git a/src/pty_callbacks.rs b/src/pty_callbacks.rs
deleted file mode 100644
index 53483e51..00000000
--- a/src/pty_callbacks.rs
+++ /dev/null
@@ -1,450 +0,0 @@
-//! Terminal query responses.
-//!
-//! Programs like fzf, htop, and vim send escape sequences asking "what terminal am I?"
-//! and "where's the cursor?". A real terminal responds immediately. We need to do the same.
-//!
-//! There are ~15 query types that real programs use. We handle the common ones.
-//! The vt100 parser handles all OUTPUT sequences (formatting, cursor, etc.) — those
-//! don't need responses. Only QUERY sequences need us to reply.
-
-use std::sync::{
- atomic::{AtomicBool, AtomicU16, Ordering},
- Arc, Mutex,
-};
-
-/// Collects response bytes that need to be written back to the PTY.
-/// Also tracks whether the child has requested the Kitty keyboard protocol.
-#[derive(Clone)]
-pub struct PtyResponses {
- pending: Arc<Mutex<Vec<u8>>>,
- /// Stack of kitty keyboard enhancement flags pushed by child programs.
- /// Each push adds a flags value; pop removes the top. This correctly handles
- /// nested programs (e.g. fish pushes mode 1, neovim pushes mode 3, neovim
- /// pops → fish's mode 1 is restored).
- kitty_stack: Arc<Mutex<Vec<u16>>>,
- /// Derived from kitty_stack: true when stack is non-empty.
- /// Kept for quick feature checks.
- pub kitty_keyboard: Arc<AtomicBool>,
- /// Exact active kitty keyboard flags from the top of the stack.
- pub kitty_keyboard_flags: Arc<AtomicU16>,
- /// Tracks DECSET 1007 (alternate scroll mode).
- /// Default on, matching Ghostty/xterm-style behavior for fullscreen apps.
- pub mouse_alternate_scroll: Arc<AtomicBool>,
-}
-
-impl Default for PtyResponses {
- fn default() -> Self {
- Self {
- pending: Arc::default(),
- kitty_stack: Arc::default(),
- kitty_keyboard: Arc::new(AtomicBool::new(false)),
- kitty_keyboard_flags: Arc::new(AtomicU16::new(0)),
- mouse_alternate_scroll: Arc::new(AtomicBool::new(true)),
- }
- }
-}
-
-#[cfg_attr(feature = "ghostty-vt", allow(dead_code))]
-impl PtyResponses {
- pub fn new() -> Self {
- Self::default()
- }
-
- /// Take any pending response bytes (clears the buffer).
- pub fn take(&self) -> Vec<u8> {
- let mut pending = self.pending.lock().unwrap();
- std::mem::take(&mut *pending)
- }
-
- fn push(&self, bytes: &[u8]) {
- self.pending.lock().unwrap().extend_from_slice(bytes);
- }
-
- fn has_private_mode(params: &[&[u16]], mode: u16) -> bool {
- params
- .iter()
- .any(|param| param.len() == 1 && param[0] == mode)
- }
-}
-
-impl vt100::Callbacks for PtyResponses {
- fn unhandled_csi(
- &mut self,
- screen: &mut vt100::Screen,
- i1: Option<u8>,
- _i2: Option<u8>,
- params: &[&[u16]],
- c: char,
- ) {
- let param0 = params.first().and_then(|p| p.first()).copied().unwrap_or(0);
-
- match (i1, c) {
- // === Device Attributes ===
-
- // DA1: \e[c or \e[0c → "what terminal are you?"
- // Respond as VT220 with ANSI color
- (None, 'c') if param0 == 0 => {
- self.push(b"\x1b[?62;22c");
- }
-
- // DA2: \e[>c or \e[>0c → "secondary device attributes"
- (Some(b'>'), 'c') => {
- // Type 0 (VT100), firmware version 0, ROM version 0
- self.push(b"\x1b[>0;0;0c");
- }
-
- // === Cursor / Status Reports ===
-
- // DSR: \e[Nn where N selects the report type
- (None, 'n') => match param0 {
- // CPR: \e[6n → cursor position report
- 5 => {
- // Device status: "OK"
- self.push(b"\x1b[0n");
- }
- 6 => {
- let (row, col) = screen.cursor_position();
- let response = format!("\x1b[{};{}R", row + 1, col + 1);
- self.push(response.as_bytes());
- }
- _ => {}
- },
-
- // DECXCPR: \e[?6n → extended cursor position report (with page)
- (Some(b'?'), 'n') if param0 == 6 => {
- let (row, col) = screen.cursor_position();
- let response = format!("\x1b[?{};{}R", row + 1, col + 1);
- self.push(response.as_bytes());
- }
-
- // === Mode Queries (DECRQM) ===
-
- // DECRQM: \e[?Np → "is DEC private mode N set?"
- // Response: \e[?N;Ps$y where Ps = 1 (set), 2 (reset), 0 (unknown)
- (Some(b'?'), 'p') => {
- let state = match param0 {
- 1007 => {
- if self.mouse_alternate_scroll.load(Ordering::Relaxed) {
- 1
- } else {
- 2
- }
- }
- _ => 2,
- };
- let response = format!("\x1b[?{param0};{state}$y");
- self.push(response.as_bytes());
- }
-
- // ANSI DECRQM: \e[Np → "is ANSI mode N set?"
- (None, 'p') => {
- let response = format!("\x1b[{param0};2$y");
- self.push(response.as_bytes());
- }
-
- // === Keyboard Protocol ===
-
- // DECSET/DECRST 1007: alternate scroll mode.
- (Some(b'?'), 'h') if Self::has_private_mode(params, 1007) => {
- self.mouse_alternate_scroll.store(true, Ordering::Relaxed);
- }
- (Some(b'?'), 'l') if Self::has_private_mode(params, 1007) => {
- self.mouse_alternate_scroll.store(false, Ordering::Relaxed);
- }
-
- // Kitty keyboard query: \e[?u → "what keyboard flags are active?"
- (Some(b'?'), 'u') => {
- let stack = self.kitty_stack.lock().unwrap();
- let flags = stack.last().copied().unwrap_or(0);
- self.push(format!("\x1b[?{flags}u").as_bytes());
- }
-
- // Kitty keyboard push: \e[>Nu → child wants Kitty key encoding
- (Some(b'>'), 'u') => {
- let mut stack = self.kitty_stack.lock().unwrap();
- stack.push(param0);
- self.kitty_keyboard.store(true, Ordering::Relaxed);
- self.kitty_keyboard_flags.store(param0, Ordering::Relaxed);
- }
-
- // Kitty keyboard pop: \e[<Nu → child reverts to previous mode
- // N = number of entries to pop (default 1)
- (Some(b'<'), 'u') => {
- let mut stack = self.kitty_stack.lock().unwrap();
- let count = (param0 as usize).max(1);
- for _ in 0..count {
- if stack.pop().is_none() {
- break;
- }
- }
- let flags = stack.last().copied().unwrap_or(0);
- self.kitty_keyboard
- .store(!stack.is_empty(), Ordering::Relaxed);
- self.kitty_keyboard_flags.store(flags, Ordering::Relaxed);
- }
-
- // === Terminal Identification ===
-
- // XTVERSION: \e[>q → "what terminal version?"
- (Some(b'>'), 'q') => {
- // Respond in DCS format: \eP>|herdr 0.1\e\\
- self.push(b"\x1bP>|herdr 0.1\x1b\\");
- }
-
- _ => {}
- }
- }
-
- fn unhandled_osc(&mut self, _screen: &mut vt100::Screen, params: &[&[u8]]) {
- let Some(cmd) = params.first() else { return };
-
- match *cmd {
- // OSC 10 ; ? ST → query foreground color
- b"10" => {
- if params.get(1) == Some(&&b"?"[..]) {
- // Respond with a default light foreground
- self.push(b"\x1b]10;rgb:cccc/cccc/cccc\x1b\\");
- }
- }
- // OSC 11 ; ? ST → query background color
- b"11" => {
- if params.get(1) == Some(&&b"?"[..]) {
- // Respond with a default dark background
- self.push(b"\x1b]11;rgb:1e1e/1e1e/2e2e\x1b\\");
- }
- }
- // OSC 12 ; ? ST → query cursor color
- b"12" => {
- if params.get(1) == Some(&&b"?"[..]) {
- self.push(b"\x1b]12;rgb:cccc/cccc/cccc\x1b\\");
- }
- }
- _ => {}
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- fn make_parser(responses: PtyResponses) -> vt100::Parser<PtyResponses> {
- vt100::Parser::new_with_callbacks(24, 80, 0, responses)
- }
-
- #[test]
- fn responds_to_da1() {
- let r = PtyResponses::new();
- let mut p = make_parser(r.clone());
- p.process(b"\x1b[c");
- assert_eq!(r.take(), b"\x1b[?62;22c");
- }
-
- #[test]
- fn responds_to_da1_explicit_zero() {
- let r = PtyResponses::new();
- let mut p = make_parser(r.clone());
- p.process(b"\x1b[0c");
- assert_eq!(r.take(), b"\x1b[?62;22c");
- }
-
- #[test]
- fn responds_to_da2() {
- let r = PtyResponses::new();
- let mut p = make_parser(r.clone());
- p.process(b"\x1b[>c");
- assert_eq!(r.take(), b"\x1b[>0;0;0c");
- }
-
- #[test]
- fn responds_to_cpr() {
- let r = PtyResponses::new();
- let mut p = make_parser(r.clone());
- p.process(b"\x1b[5;10H"); // move cursor to row 5, col 10
- p.process(b"\x1b[6n");
- assert_eq!(r.take(), b"\x1b[5;10R");
- }
-
- #[test]
- fn responds_to_dsr_status() {
- let r = PtyResponses::new();
- let mut p = make_parser(r.clone());
- p.process(b"\x1b[5n"); // device status report
- assert_eq!(r.take(), b"\x1b[0n"); // "OK"
- }
-
- #[test]
- fn responds_to_extended_cpr() {
- let r = PtyResponses::new();
- let mut p = make_parser(r.clone());
- p.process(b"\x1b[3;7H"); // move cursor
- p.process(b"\x1b[?6n"); // extended CPR
- assert_eq!(r.take(), b"\x1b[?3;7R");
- }
-
- #[test]
- fn responds_to_decrqm_private() {
- let r = PtyResponses::new();
- let mut p = make_parser(r.clone());
- p.process(b"\x1b[?25p"); // query: is cursor visible (mode 25)?
- assert_eq!(r.take(), b"\x1b[?25;2$y"); // "reset" (2)
- }
-
- #[test]
- fn alternate_scroll_defaults_on_and_reports_set() {
- let r = PtyResponses::new();
- let mut p = make_parser(r.clone());
-
- assert!(r.mouse_alternate_scroll.load(Ordering::Relaxed));
- p.process(b"\x1b[?1007p");
- assert_eq!(r.take(), b"\x1b[?1007;1$y");
- }
-
- #[test]
- fn decset_decrst_1007_updates_alternate_scroll_mode() {
- let r = PtyResponses::new();
- let mut p = make_parser(r.clone());
-
- p.process(b"\x1b[?1007l");
- assert!(!r.mouse_alternate_scroll.load(Ordering::Relaxed));
- p.process(b"\x1b[?1007p");
- assert_eq!(r.take(), b"\x1b[?1007;2$y");
-
- p.process(b"\x1b[?1007h");
- assert!(r.mouse_alternate_scroll.load(Ordering::Relaxed));
- p.process(b"\x1b[?1007p");
- assert_eq!(r.take(), b"\x1b[?1007;1$y");
- }
-
- #[test]
- fn responds_to_kitty_keyboard_query() {
- let r = PtyResponses::new();
- let mut p = make_parser(r.clone());
- p.process(b"\x1b[?u");
- assert_eq!(r.take(), b"\x1b[?0u");
- }
-
- #[test]
- fn kitty_push_pop_stack() {
- let r = PtyResponses::new();
- let mut p = make_parser(r.clone());
-
- // Initially off
- assert!(!r.kitty_keyboard.load(Ordering::Relaxed));
- assert_eq!(r.kitty_keyboard_flags.load(Ordering::Relaxed), 0);
-
- // Fish pushes mode 1
- p.process(b"\x1b[>1u");
- assert!(r.kitty_keyboard.load(Ordering::Relaxed));
- assert_eq!(r.kitty_keyboard_flags.load(Ordering::Relaxed), 1);
- p.process(b"\x1b[?u");
- assert_eq!(r.take(), b"\x1b[?1u");
-
- // Neovim pushes mode 3
- p.process(b"\x1b[>3u");
- assert!(r.kitty_keyboard.load(Ordering::Relaxed));
- assert_eq!(r.kitty_keyboard_flags.load(Ordering::Relaxed), 3);
- p.process(b"\x1b[?u");
- assert_eq!(r.take(), b"\x1b[?3u");
-
- // Neovim pops → fish's mode 1 is restored
- p.process(b"\x1b[<u");
- assert!(r.kitty_keyboard.load(Ordering::Relaxed));
- assert_eq!(r.kitty_keyboard_flags.load(Ordering::Relaxed), 1);
- p.process(b"\x1b[?u");
- assert_eq!(r.take(), b"\x1b[?1u");
-
- // Fish pops → back to legacy
- p.process(b"\x1b[<u");
- assert!(!r.kitty_keyboard.load(Ordering::Relaxed));
- assert_eq!(r.kitty_keyboard_flags.load(Ordering::Relaxed), 0);
- p.process(b"\x1b[?u");
- assert_eq!(r.take(), b"\x1b[?0u");
- }
-
- #[test]
- fn kitty_pop_on_empty_is_harmless() {
- let r = PtyResponses::new();
- let mut p = make_parser(r.clone());
-
- // Pop with nothing on stack — should not crash
- p.process(b"\x1b[<u");
- assert!(!r.kitty_keyboard.load(Ordering::Relaxed));
- assert_eq!(r.kitty_keyboard_flags.load(Ordering::Relaxed), 0);
- }
-
- #[test]
- fn kitty_pop_count() {
- let r = PtyResponses::new();
- let mut p = make_parser(r.clone());
-
- // Push three times
- p.process(b"\x1b[>1u");
- p.process(b"\x1b[>3u");
- p.process(b"\x1b[>5u");
- assert!(r.kitty_keyboard.load(Ordering::Relaxed));
- assert_eq!(r.kitty_keyboard_flags.load(Ordering::Relaxed), 5);
-
- // Pop 2 at once
- p.process(b"\x1b[<2u");
- assert!(r.kitty_keyboard.load(Ordering::Relaxed));
- assert_eq!(r.kitty_keyboard_flags.load(Ordering::Relaxed), 1);
- p.process(b"\x1b[?u");
- assert_eq!(r.take(), b"\x1b[?1u"); // only first push remains
-
- // Pop last one
- p.process(b"\x1b[<u");
- assert!(!r.kitty_keyboard.load(Ordering::Relaxed));
- assert_eq!(r.kitty_keyboard_flags.load(Ordering::Relaxed), 0);
- }
-
- #[test]
- fn responds_to_xtversion() {
- let r = PtyResponses::new();
- let mut p = make_parser(r.clone());
- p.process(b"\x1b[>q");
- assert_eq!(r.take(), b"\x1bP>|herdr 0.1\x1b\\");
- }
-
- #[test]
- fn responds_to_osc_fg_color_query() {
- let r = PtyResponses::new();
- let mut p = make_parser(r.clone());
- p.process(b"\x1b]10;?\x1b\\");
- assert_eq!(r.take(), b"\x1b]10;rgb:cccc/cccc/cccc\x1b\\");
- }
-
- #[test]
- fn responds_to_osc_bg_color_query() {
- let r = PtyResponses::new();
- let mut p = make_parser(r.clone());
- p.process(b"\x1b]11;?\x1b\\");
- assert_eq!(r.take(), b"\x1b]11;rgb:1e1e/1e1e/2e2e\x1b\\");
- }
-
- #[test]
- fn responds_to_osc_cursor_color_query() {
- let r = PtyResponses::new();
- let mut p = make_parser(r.clone());
- p.process(b"\x1b]12;?\x1b\\");
- assert_eq!(r.take(), b"\x1b]12;rgb:cccc/cccc/cccc\x1b\\");
- }
-
- #[test]
- fn no_response_for_regular_output() {
- let r = PtyResponses::new();
- let mut p = make_parser(r.clone());
- p.process(b"hello world\x1b[31mred\x1b[0m");
- assert!(r.take().is_empty());
- }
-
- #[test]
- fn multiple_queries_accumulate() {
- let r = PtyResponses::new();
- let mut p = make_parser(r.clone());
- p.process(b"\x1b[c\x1b[6n");
- let bytes = r.take();
- assert!(bytes.starts_with(b"\x1b[?62;22c"));
- assert!(bytes.ends_with(b"\x1b[1;1R"));
- }
-}
diff --git a/src/selection.rs b/src/selection.rs
index 78c9a2f3..5531a189 100644
--- a/src/selection.rs
+++ b/src/selection.rs
@@ -142,13 +142,6 @@ impl Selection {
}
}
-/// Extract selected text from a vt100 screen.
-pub fn extract_text(screen: &vt100::Screen, selection: &Selection) -> String {
- let ((sr, sc), (er, ec)) = selection.ordered();
- // contents_between end_col is exclusive in vt100
- screen.contents_between(sr, sc, er, ec + 1)
-}
-
/// Write text to the system clipboard via OSC 52.
///
/// OSC 52 format: `ESC ] 52 ; c ; <base64> BEL`
diff --git a/src/terminal_theme.rs b/src/terminal_theme.rs
index e0b4b9d5..523e3e38 100644
--- a/src/terminal_theme.rs
+++ b/src/terminal_theme.rs
@@ -47,7 +47,6 @@ pub fn parse_default_color_response(sequence: &str) -> Option<(DefaultColorKind,
Some((kind, parse_rgb_color(value)?))
}
-#[cfg_attr(not(feature = "ghostty-vt"), allow(dead_code))]
pub fn osc_set_default_color_sequence(kind: DefaultColorKind, color: RgbColor) -> String {
let command = match kind {
DefaultColorKind::Foreground => 10,