Skip to main content

proc_cli/commands/
list.rs

1//! `proc list` - List processes
2//!
3//! Examples:
4//!   proc list                  # List all processes
5//!   proc list node             # Filter by name
6//!   proc list --in             # Processes in current directory
7//!   proc list --in /project    # Processes in /project
8//!   proc list --min-cpu 10     # Processes using >10% CPU
9
10use crate::core::{resolve_in_dir, sort_processes, Process, ProcessStatus, SortKey};
11use crate::error::Result;
12use crate::ui::Printer;
13use clap::Args;
14use std::path::PathBuf;
15
16/// List processes
17#[derive(Args, Debug)]
18pub struct ListCommand {
19    /// Process name or pattern to filter by
20    pub name: Option<String>,
21
22    /// Filter by directory (defaults to current directory if no path given)
23    #[arg(long = "in", short = 'i', num_args = 0..=1, default_missing_value = ".")]
24    pub in_dir: Option<String>,
25
26    /// Filter by executable path
27    #[arg(long, short = 'p')]
28    pub path: Option<String>,
29
30    /// Only show processes using more than this CPU %
31    #[arg(long)]
32    pub min_cpu: Option<f32>,
33
34    /// Only show processes using more than this memory (MB)
35    #[arg(long)]
36    pub min_mem: Option<f64>,
37
38    /// Filter by status: running, sleeping, stopped, zombie
39    #[arg(long)]
40    pub status: Option<String>,
41
42    /// Only show processes running longer than this (seconds)
43    #[arg(long)]
44    pub min_uptime: Option<u64>,
45
46    /// Only show children of this parent PID
47    #[arg(long)]
48    pub parent: Option<u32>,
49
50    /// Output as JSON
51    #[arg(long, short = 'j')]
52    pub json: bool,
53
54    /// Show verbose output with command line, cwd, and parent PID
55    #[arg(long, short = 'v')]
56    pub verbose: bool,
57
58    /// Limit the number of results
59    #[arg(long, short = 'n')]
60    pub limit: Option<usize>,
61
62    /// Sort by: cpu, mem, pid, name
63    #[arg(long, short = 's', value_enum, default_value_t = SortKey::Cpu)]
64    pub sort: SortKey,
65}
66
67impl ListCommand {
68    /// Executes the list command, displaying processes matching the filters.
69    pub fn execute(&self) -> Result<()> {
70        let printer = Printer::from_flags(self.json, self.verbose);
71
72        // Get base process list (supports comma-separated names)
73        let mut processes = if let Some(ref name) = self.name {
74            let names: Vec<String> = name
75                .split(',')
76                .map(|s| s.trim().to_string())
77                .filter(|s| !s.is_empty())
78                .collect();
79            let mut all = Vec::new();
80            let mut seen = std::collections::HashSet::new();
81            for n in &names {
82                if let Ok(found) = Process::find_by_name(n) {
83                    for p in found {
84                        if seen.insert(p.pid) {
85                            all.push(p);
86                        }
87                    }
88                }
89            }
90            all
91        } else {
92            Process::find_all()?
93        };
94
95        // Resolve --in filter path
96        let in_dir_filter = resolve_in_dir(&self.in_dir);
97
98        // Resolve path filter
99        let path_filter: Option<PathBuf> = self.path.as_ref().map(|p| {
100            let path = PathBuf::from(p);
101            if path.is_relative() {
102                std::env::current_dir()
103                    .unwrap_or_else(|_| PathBuf::from("."))
104                    .join(path)
105            } else {
106                path
107            }
108        });
109
110        // Apply filters
111        processes.retain(|p| {
112            // Directory filter (--in)
113            if let Some(ref dir_path) = in_dir_filter {
114                if let Some(ref proc_cwd) = p.cwd {
115                    let proc_path = PathBuf::from(proc_cwd);
116                    if !proc_path.starts_with(dir_path) {
117                        return false;
118                    }
119                } else {
120                    return false;
121                }
122            }
123
124            // Path filter (executable path)
125            if let Some(ref exe_path) = path_filter {
126                if let Some(ref proc_exe) = p.exe_path {
127                    let proc_path = PathBuf::from(proc_exe);
128                    if !proc_path.starts_with(exe_path) {
129                        return false;
130                    }
131                } else {
132                    return false;
133                }
134            }
135
136            // CPU filter
137            if let Some(min_cpu) = self.min_cpu {
138                if p.cpu_percent < min_cpu {
139                    return false;
140                }
141            }
142
143            // Memory filter
144            if let Some(min_mem) = self.min_mem {
145                if p.memory_mb < min_mem {
146                    return false;
147                }
148            }
149
150            // Status filter
151            if let Some(ref status) = self.status {
152                let status_match = match status.to_lowercase().as_str() {
153                    "running" => matches!(p.status, ProcessStatus::Running),
154                    "sleeping" | "sleep" => matches!(p.status, ProcessStatus::Sleeping),
155                    "stopped" | "stop" => matches!(p.status, ProcessStatus::Stopped),
156                    "zombie" => matches!(p.status, ProcessStatus::Zombie),
157                    _ => true,
158                };
159                if !status_match {
160                    return false;
161                }
162            }
163
164            // Uptime filter
165            if let Some(min_uptime) = self.min_uptime {
166                if let Some(start_time) = p.start_time {
167                    let now = std::time::SystemTime::now()
168                        .duration_since(std::time::UNIX_EPOCH)
169                        .map(|d| d.as_secs())
170                        .unwrap_or(0);
171                    if now.saturating_sub(start_time) < min_uptime {
172                        return false;
173                    }
174                } else {
175                    return false;
176                }
177            }
178
179            // Parent PID filter
180            if let Some(ppid) = self.parent {
181                if p.parent_pid != Some(ppid) {
182                    return false;
183                }
184            }
185
186            true
187        });
188
189        // Sort processes
190        sort_processes(&mut processes, self.sort);
191
192        // Apply limit if specified
193        if let Some(limit) = self.limit {
194            processes.truncate(limit);
195        }
196
197        // Build context string for output (e.g., "in /path/to/dir")
198        let context = in_dir_filter
199            .as_ref()
200            .map(|p| format!("in {}", p.display()));
201
202        printer.print_processes_with_context(&processes, context.as_deref());
203        Ok(())
204    }
205}