use super::impl_agent::merge_custom_ai_inspector_agents;
use super::{
EguiState, FocusState, OverlayState, RenderLoopState, TriggerState, UpdateState, WatcherState,
WindowState,
};
use crate::badge::BadgeState;
use crate::config::Config;
use crate::input::InputHandler;
use crate::keybindings::{KeyCombo, KeybindingRegistry};
use crate::smart_selection::SmartSelectionCache;
use crate::status_bar::StatusBarUI;
use crate::tab::TabManager;
use crate::tab_bar_ui::TabBarUI;
use anyhow::Result;
use par_term_acp::discover_agents;
use std::sync::Arc;
use tokio::runtime::Runtime;
use winit::window::Window;
impl WindowState {
pub(crate) fn parse_custom_action_prefix_combo(prefix_key: &str) -> Option<KeyCombo> {
let trimmed = prefix_key.trim();
if trimmed.is_empty() {
return None;
}
match crate::keybindings::parser::parse_key_combo(trimmed) {
Ok(combo) => Some(combo),
Err(error) => {
log::warn!(
"Invalid custom action prefix key '{}': {}",
prefix_key,
error
);
None
}
}
}
pub fn new(config: Config, runtime: Arc<Runtime>) -> Self {
let keybinding_registry = KeybindingRegistry::from_config(&config.keybindings);
let custom_action_prefix_combo =
Self::parse_custom_action_prefix_combo(&config.custom_action_prefix_key);
let shaders_dir = Config::shaders_dir();
let tmux_prefix_key = crate::tmux::PrefixKey::parse(&config.tmux_prefix_key);
let mut input_handler = InputHandler::new();
input_handler
.update_option_key_modes(config.left_option_key_mode, config.right_option_key_mode);
let badge_state = BadgeState::new(&config);
let overlay_ui = crate::app::window_state::overlay_ui_state::OverlayUiState::new(&config);
let config_dir = dirs::config_dir().unwrap_or_default().join("par-term");
let discovered_agents = discover_agents(&config_dir);
let available_agents = merge_custom_ai_inspector_agents(
discovered_agents,
&config.ai_inspector.ai_inspector_custom_agents,
);
Self {
config,
window: None,
renderer: None,
input_handler,
runtime,
tab_manager: TabManager::new(),
tab_bar_ui: TabBarUI::new(),
status_bar_ui: StatusBarUI::new(),
debug: crate::app::window_state::debug_state::DebugState::new(),
cursor_anim: crate::app::window_state::cursor_anim_state::CursorAnimState::default(),
is_fullscreen: false,
egui: EguiState::default(),
shader_state: crate::app::window_state::shader_state::ShaderState::new(shaders_dir),
overlay_ui,
agent_state: super::agent_state::AgentState::new(available_agents),
is_recording: false,
is_shutting_down: false,
window_index: 1,
focus_state: FocusState::default(),
render_loop: RenderLoopState::default(),
watcher_state: WatcherState::default(),
clipboard_image_click_guard: None,
overlay_state: OverlayState::default(),
keybinding_registry,
custom_action_prefix_combo,
custom_action_prefix_state: crate::tmux::PrefixState::default(),
smart_selection_cache: SmartSelectionCache::new(),
tmux_state: crate::app::tmux_handler::tmux_state::TmuxState::new(tmux_prefix_key),
broadcast_input: false,
badge_state,
copy_mode: crate::copy_mode::CopyModeState::new(),
file_transfer_state: crate::app::file_transfers::FileTransferState::default(),
update_state: UpdateState::default(),
trigger_state: TriggerState::default(),
pending_snap_size: None,
scratch_prettifier_block_ids: std::collections::HashSet::new(),
last_workflow_context: std::sync::Arc::new(std::sync::Mutex::new(None)),
}
}
pub(crate) fn format_title(&self, base_title: &str) -> String {
if self.config.show_window_number {
format!("{} [{}]", base_title, self.window_index)
} else {
base_title.to_string()
}
}
pub(crate) fn extract_columns(line: &str, start_col: usize, end_col: Option<usize>) -> String {
let mut extracted = String::new();
let end_bound = end_col.unwrap_or(usize::MAX);
if start_col > end_bound {
return extracted;
}
for (idx, ch) in line.chars().enumerate() {
if idx > end_bound {
break;
}
if idx >= start_col {
extracted.push(ch);
}
}
extracted
}
pub(crate) async fn initialize_async(
&mut self,
window: Window,
first_tab_cwd: Option<String>,
) -> Result<()> {
use crate::app::window_state::renderer_init::RendererInitParams;
window.set_ime_allowed(true);
log::debug!("IME enabled for character input");
if self.config.auto_dark_mode {
let is_dark = window
.theme()
.is_none_or(|t| t == winit::window::Theme::Dark);
if self.config.apply_system_theme(is_dark) {
log::info!(
"Auto dark mode: detected {} system theme, using theme: {}",
if is_dark { "dark" } else { "light" },
self.config.theme
);
}
}
{
let is_dark = window
.theme()
.is_none_or(|t| t == winit::window::Theme::Dark);
if self.config.apply_system_tab_style(is_dark) {
log::info!(
"Auto tab style: detected {} system theme, applying {} tab style",
if is_dark { "dark" } else { "light" },
if is_dark {
self.config.dark_tab_style.display_name()
} else {
self.config.light_tab_style.display_name()
}
);
}
}
let window = Arc::new(window);
self.init_egui(&window, false);
let theme = self.config.load_theme();
let metadata = self
.config
.shader
.custom_shader
.as_ref()
.and_then(|name| self.shader_state.shader_metadata_cache.get(name).cloned());
let cursor_metadata = self.config.shader.cursor_shader.as_ref().and_then(|name| {
self.shader_state
.cursor_shader_metadata_cache
.get(name)
.cloned()
});
let params = RendererInitParams::from_config(
&self.config,
&theme,
metadata.as_ref(),
cursor_metadata.as_ref(),
);
let mut renderer = params.create_renderer(Arc::clone(&window)).await?;
#[cfg(target_os = "macos")]
{
if let Err(e) = crate::macos_metal::configure_metal_layer_for_performance(&window) {
log::warn!("Failed to configure Metal layer: {}", e);
log::warn!(
"Continuing anyway - may experience reduced FPS or missing transparency on macOS"
);
}
if let Err(e) = crate::macos_metal::set_layer_opacity(&window, 1.0) {
log::warn!("Failed to set initial Metal layer opacity: {}", e);
}
if self.config.window.blur_enabled
&& self.config.window.window_opacity < 1.0
&& let Err(e) =
crate::macos_blur::set_window_blur(&window, self.config.window.blur_radius)
{
log::warn!("Failed to set initial window blur: {}", e);
}
}
self.apply_cursor_shader_config(&mut renderer, ¶ms);
let initial_tab_bar_height = self.tab_bar_ui.get_height(1, &self.config);
let initial_tab_bar_width = self.tab_bar_ui.get_width(1, &self.config);
let (initial_cols, initial_rows) = renderer.grid_size();
log::info!(
"Tab bar init: mode={:?}, position={:?}, height={:.1}, width={:.1}, initial_grid={}x{}, content_offset_y_before={:.1}",
self.config.tab_bar_mode,
self.config.tab_bar_position,
initial_tab_bar_height,
initial_tab_bar_width,
initial_cols,
initial_rows,
renderer.content_offset_y()
);
self.apply_tab_bar_offsets(&mut renderer, initial_tab_bar_height, initial_tab_bar_width);
let (renderer_cols, renderer_rows) = renderer.grid_size();
let cell_width = renderer.cell_width();
let cell_height = renderer.cell_height();
self.window = Some(Arc::clone(&window));
self.renderer = Some(renderer);
self.init_shader_watcher();
self.init_config_watcher();
self.init_config_update_watcher();
self.init_screenshot_request_watcher();
self.status_bar_ui.sync_monitor_state(&self.config);
log::info!(
"Creating first tab with grid size {}x{} (accounting for tab bar)",
renderer_cols,
renderer_rows
);
let tab_id = self.tab_manager.new_tab_with_cwd(
&self.config,
Arc::clone(&self.runtime),
first_tab_cwd,
Some((renderer_cols, renderer_rows)), )?;
if let Some(tab) = self.tab_manager.get_tab_mut(tab_id) {
let width_px = (renderer_cols as f32 * cell_width) as usize;
let height_px = (renderer_rows as f32 * cell_height) as usize;
if let Ok(mut term) = tab.terminal.try_write() {
term.set_cell_dimensions(cell_width as u32, cell_height as u32);
if let Err(e) =
term.resize_with_pixels(renderer_cols, renderer_rows, width_px, height_px)
{
crate::debug_error!("TERMINAL", "resize_with_pixels failed (init): {e}");
}
log::info!(
"Initial terminal dimensions: {}x{} ({}x{} px)",
renderer_cols,
renderer_rows,
width_px,
height_px
);
}
tab.start_refresh_task(
Arc::clone(&self.runtime),
Arc::clone(&window),
self.config.max_fps,
self.config.inactive_tab_fps,
);
}
if self.overlay_ui.ai_inspector.open {
self.try_auto_connect_agent();
}
if self.config.should_prompt_integrations(crate::VERSION) {
log::info!("Integrations not installed - showing welcome dialog");
self.overlay_ui.integrations_ui.show_dialog();
self.focus_state.needs_redraw = true;
window.request_redraw();
}
Ok(())
}
}