use anyhow::Result;
use std::env;
use std::path::PathBuf;
use crate::{session, workspace};
pub fn session_log_path() -> std::path::PathBuf {
if let Ok(path) = env::var("SIVTR_SESSION_LOG") {
let path = PathBuf::from(path);
if path.exists() {
return path;
}
}
if let Ok(Some(path)) = workspace::current_terminal_log_path() {
return path;
}
workspace::legacy_session_dir().join("session.log")
}
pub fn workspace_session_log_path() -> Result<Option<std::path::PathBuf>> {
if let Ok(path) = env::var("SIVTR_SESSION_LOG") {
return Ok(Some(PathBuf::from(path)));
}
workspace::current_terminal_log_path()
}
pub fn flush_state_path() -> std::path::PathBuf {
session_log_path().with_extension("state")
}
pub fn capture_file_path() -> std::path::PathBuf {
if let Ok(path) = env::var("SIVTR_CAPTURE_FILE") {
return PathBuf::from(path);
}
session_log_path().with_extension("capture")
}
pub fn read_session_log() -> Result<Option<String>> {
let log = session_log_path();
if log.exists() {
let entries = session::load_entries(&log)?;
if !entries.is_empty() {
return Ok(Some(session::render_entries_ansi(&entries)));
}
}
Ok(None)
}
#[cfg(windows)]
pub struct ConsoleSnapshot {
pub content: String,
pub width: usize,
}
#[cfg(windows)]
pub fn capture_console_buffer() -> Result<ConsoleSnapshot> {
capture_windows_console()
}
#[cfg(windows)]
fn capture_windows_console() -> Result<ConsoleSnapshot> {
use std::ptr;
use winapi::um::fileapi::{CreateFileW, OPEN_EXISTING};
use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE};
use winapi::um::processenv::GetStdHandle;
use winapi::um::winbase::STD_OUTPUT_HANDLE;
use winapi::um::wincon::{
GetConsoleScreenBufferInfo, ReadConsoleOutputAttribute, ReadConsoleOutputCharacterW,
CONSOLE_SCREEN_BUFFER_INFO, COORD,
};
use winapi::um::winnt::{FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE};
unsafe {
let conout: Vec<u16> = "CONOUT$\0".encode_utf16().collect();
let conout_handle = CreateFileW(
conout.as_ptr(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
ptr::null_mut(),
OPEN_EXISTING,
0,
ptr::null_mut(),
);
let (handle, should_close) =
if !conout_handle.is_null() && conout_handle != INVALID_HANDLE_VALUE {
(conout_handle, true)
} else {
let std_handle = GetStdHandle(STD_OUTPUT_HANDLE);
(std_handle, false)
};
if handle.is_null() || handle == INVALID_HANDLE_VALUE {
anyhow::bail!("Failed to get console handle");
}
let mut info: CONSOLE_SCREEN_BUFFER_INFO = std::mem::zeroed();
if GetConsoleScreenBufferInfo(handle, &mut info) == 0 {
if should_close {
CloseHandle(handle);
}
anyhow::bail!("Failed to get console buffer info");
}
let width = info.dwSize.X as usize;
if width == 0 {
return Ok(ConsoleSnapshot {
content: String::new(),
width: 0,
});
}
let cursor_row = info.dwCursorPosition.Y as usize;
let content_rows = (cursor_row + 1).min(info.dwSize.Y as usize);
let default_attr = info.wAttributes;
let mut result = String::with_capacity(content_rows * (width + 20));
let mut char_buf = vec![0u16; width];
let mut attr_buf = vec![0u16; width];
for row in 0..content_rows {
let mut chars_read: u32 = 0;
let mut attrs_read: u32 = 0;
let coord = COORD {
X: 0,
Y: row as i16,
};
ReadConsoleOutputCharacterW(
handle,
char_buf.as_mut_ptr(),
width as u32,
coord,
&mut chars_read,
);
ReadConsoleOutputAttribute(
handle,
attr_buf.as_mut_ptr(),
width as u32,
coord,
&mut attrs_read,
);
let line_str = String::from_utf16_lossy(&char_buf[..chars_read as usize]);
let trimmed = line_str.trim_end();
if trimmed.is_empty() {
result.push('\n');
continue;
}
let trimmed_len = trimmed.len();
let mut prev_attr = default_attr;
let mut byte_idx: usize = 0;
let mut cell_idx: usize = 0;
for ch in line_str.chars() {
if byte_idx >= trimmed_len {
break;
}
let attr = if cell_idx < attrs_read as usize {
attr_buf[cell_idx]
} else {
default_attr
};
if attr != prev_attr || cell_idx == 0 {
if attr != default_attr {
result.push_str(&win_attr_to_ansi(attr));
} else if prev_attr != default_attr {
result.push_str("\x1b[0m");
}
prev_attr = attr;
}
result.push(ch);
byte_idx += ch.len_utf8();
if unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1) > 1 {
cell_idx += 2;
} else {
cell_idx += 1;
}
}
if prev_attr != default_attr {
result.push_str("\x1b[0m");
}
result.push('\n');
}
if should_close {
CloseHandle(handle);
}
let trimmed = result.trim_end_matches('\n').to_string();
Ok(ConsoleSnapshot {
content: trimmed,
width,
})
}
}
#[cfg(windows)]
fn win_attr_to_ansi(attr: u16) -> String {
const WIN_TO_ANSI: [u8; 8] = [0, 4, 2, 6, 1, 5, 3, 7];
let fg_bits = (attr & 0x07) as usize;
let fg_intense = (attr & 0x08) != 0;
let bg_bits = ((attr >> 4) & 0x07) as usize;
let bg_intense = (attr & 0x80) != 0;
let fg_ansi = WIN_TO_ANSI[fg_bits];
let bg_ansi = WIN_TO_ANSI[bg_bits];
let mut codes = Vec::new();
if fg_intense {
codes.push(format!("{}", 90 + fg_ansi)); } else {
codes.push(format!("{}", 30 + fg_ansi)); }
if bg_bits != 0 || bg_intense {
if bg_intense {
codes.push(format!("{}", 100 + bg_ansi));
} else {
codes.push(format!("{}", 40 + bg_ansi));
}
}
format!("\x1b[{}m", codes.join(";"))
}