use crate::platform::term::{self, TerminalProfile};
#[cfg(target_os = "windows")]
use crate::platform::term::{
is_powershell_core, is_windows_powershell_legacy, is_windows_terminal_or_conemu,
};
use anyhow::Result;
use std::io::{self, Write};
use std::thread;
use std::time::{Duration, Instant};
use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, RefreshKind, System};
fn should_use_alt_screen(profile: &TerminalProfile) -> bool {
if !profile.alt_screen {
return false;
}
#[cfg(target_os = "windows")]
{
if std::env::var("WT_SESSION").is_ok() {
return true;
}
if let Ok(term_program) = std::env::var("TERM_PROGRAM") {
if term_program.to_lowercase().contains("vscode") {
return true;
}
}
if std::env::var("ConEmuANSI").is_ok() || std::env::var("ANSICON").is_ok() {
return true;
}
if is_powershell_core() {
return true;
}
if let Ok(term_program) = std::env::var("TERM_PROGRAM") {
let term_program = term_program.to_lowercase();
if term_program.contains("warp") || term_program.contains("warpterminal") {
return true;
}
}
if let Ok(term) = std::env::var("TERM") {
let term = term.to_lowercase();
if term.contains("xterm-256color")
|| term.contains("xterm")
|| term.contains("256color")
{
return true;
}
}
if is_windows_powershell_legacy() {
return is_windows_terminal_or_conemu();
}
false
}
#[cfg(not(target_os = "windows"))]
true
}
struct AltScreenGuard {
active: bool,
}
impl AltScreenGuard {
fn new() -> Self {
enter_alternate_screen();
Self { active: true }
}
}
impl Drop for AltScreenGuard {
fn drop(&mut self) {
if self.active {
exit_alternate_screen();
}
}
}
fn enter_alternate_screen() {
print!("\x1b[2J\x1b[H");
print!("\x1b[?1049h");
print!("\x1b[?25l");
let _ = io::stdout().flush();
}
fn exit_alternate_screen() {
print!("\x1b[?25h");
print!("\x1b[?1049l");
let _ = io::stdout().flush();
}
pub struct TopOptions {
pub interval: f32,
pub limit: usize,
pub show_cpu: bool,
pub show_cmd: bool,
pub once: bool,
}
pub struct TopRenderOptions {
pub total_memory: u64,
pub used_memory: u64,
pub refresh: u64,
pub interval: f32,
pub show_cpu: bool,
pub show_cmd: bool,
pub incremental: bool,
}
pub struct ProcessView {
pub pid: u32,
pub name: String,
pub memory_bytes: u64,
pub memory_percent: f64,
pub cpu: f32,
pub cmd: String,
}
pub fn run_top(
opts: TopOptions,
render: fn(&[ProcessView], &TopRenderOptions, &mut Vec<String>),
) -> Result<()> {
let process_refresh = ProcessRefreshKind::everything();
let mut system = System::new_with_specifics(RefreshKind::new().with_processes(process_refresh));
let profile = term::global_profile();
let use_alt_screen = !opts.once && should_use_alt_screen(&profile);
let incremental = !opts.once && profile.incremental;
let _guard = if use_alt_screen {
Some(AltScreenGuard::new())
} else {
None
};
let mut tick: u64 = 0;
let mut last_frame: Vec<String> = Vec::new();
system.refresh_processes_specifics(ProcessesToUpdate::All, process_refresh);
system.refresh_memory();
if !opts.once {
thread::sleep(Duration::from_millis(100));
}
loop {
tick = tick.wrapping_add(1);
let start = Instant::now();
system.refresh_processes_specifics(ProcessesToUpdate::All, process_refresh);
system.refresh_memory();
let total_memory = system.total_memory();
let used_memory = system.used_memory();
let mut processes: Vec<ProcessView> = system
.processes()
.iter()
.map(|(pid, process)| {
let cmd = process
.cmd()
.iter()
.map(|s| s.to_string_lossy())
.collect::<Vec<_>>()
.join(" ");
let memory = process.memory();
let memory_percent = if total_memory > 0 {
(memory as f64 / total_memory as f64) * 100.0
} else {
0.0
};
let cpu_usage = process.cpu_usage();
ProcessView {
pid: pid.as_u32(),
name: process.name().to_string_lossy().into_owned(),
memory_bytes: memory,
memory_percent,
cpu: cpu_usage,
cmd,
}
})
.collect();
if opts.show_cpu {
processes.sort_by(|a, b| {
let score_a = a.memory_bytes as f64 * 0.7 + a.cpu as f64 * 1000.0 * 0.3;
let score_b = b.memory_bytes as f64 * 0.7 + b.cpu as f64 * 1000.0 * 0.3;
let score_a = if score_a.is_nan() { 0.0 } else { score_a };
let score_b = if score_b.is_nan() { 0.0 } else { score_b };
score_b
.partial_cmp(&score_a)
.unwrap_or(std::cmp::Ordering::Equal)
});
} else {
processes.sort_by(|a, b| b.memory_bytes.cmp(&a.memory_bytes));
}
processes.truncate(opts.limit.max(1));
let render_opts = TopRenderOptions {
total_memory,
used_memory,
refresh: tick,
interval: opts.interval,
show_cpu: opts.show_cpu,
show_cmd: opts.show_cmd,
incremental,
};
render(&processes, &render_opts, &mut last_frame);
if opts.once {
break;
}
let elapsed = start.elapsed();
let target_duration = Duration::from_secs_f32(opts.interval);
if elapsed < target_duration {
let remaining = target_duration - elapsed;
thread::sleep(remaining);
}
}
Ok(())
}