1use std::process::Child;
15use std::sync::Mutex;
16
17use crate::ported::jobs::stat;
18use crate::ported::jobs::{deletejob, CURJOB, MAXJOB, PREVJOB, THISJOB};
19use crate::ported::zsh_h::job;
20
21pub fn printjob_delete_tail(tab: &mut [job], idx: usize) {
39 if idx >= tab.len() || (tab[idx].stat & stat::DONE) == 0 {
40 return;
41 }
42 deletejob(&mut tab[idx], false); let mut cj = CURJOB.get_or_init(|| Mutex::new(-1)).lock().unwrap();
44 let mut pj = PREVJOB.get_or_init(|| Mutex::new(-1)).lock().unwrap();
45 if *cj == idx as i32 {
46 *cj = *pj;
48 *pj = idx as i32;
49 }
50 let need_setprev = *pj == idx as i32; drop(cj);
52 drop(pj);
53 if need_setprev {
54 setprevjob_locked(tab); }
56}
57
58fn setprevjob_locked(tab: &[job]) {
63 let maxjob = *MAXJOB
64 .get_or_init(|| Mutex::new(0))
65 .lock()
66 .expect("maxjob poisoned");
67 let curjob = *CURJOB.get_or_init(|| Mutex::new(-1)).lock().unwrap();
68 let thisjob = *THISJOB.get_or_init(|| Mutex::new(-1)).lock().unwrap();
69 let pick = |want_stopped: bool| -> i32 {
70 for i in (1..=maxjob).rev() {
71 if i >= tab.len() {
72 continue;
73 }
74 let j = &tab[i];
75 let stat_ok = if want_stopped {
76 (j.stat & (stat::INUSE | stat::STOPPED)) == (stat::INUSE | stat::STOPPED)
77 } else {
78 (j.stat & stat::INUSE) != 0
79 };
80 if stat_ok && (j.stat & stat::SUBJOB) == 0 && i as i32 != curjob && i as i32 != thisjob
81 {
82 return i as i32;
83 }
84 }
85 -1
86 };
87 let mut found = pick(true); if found < 0 {
89 found = pick(false); }
91 *PREVJOB.get_or_init(|| Mutex::new(-1)).lock().unwrap() = found; }
93
94#[derive(Clone, Copy, Debug, PartialEq, Eq)]
98pub enum JobState {
99 Running,
101 Stopped,
103 Done,
105}
106
107#[derive(Debug)]
109pub struct JobInfo {
110 pub id: usize,
112 pub pid: i32,
114 pub child: Option<Child>,
116 pub command: String,
118 pub state: JobState,
120 pub is_current: bool,
122}
123
124pub struct JobTable {
129 jobs: Vec<Option<JobInfo>>,
131 current_id: Option<usize>,
133 next_id: usize,
135}
136
137impl Default for JobTable {
138 fn default() -> Self {
139 Self::new()
140 }
141}
142
143impl JobTable {
144 pub fn new() -> Self {
146 JobTable {
147 jobs: Vec::with_capacity(16),
148 current_id: None,
149 next_id: 1,
150 }
151 }
152
153 pub fn peek_next_id(&self) -> usize {
159 self.next_id
160 }
161
162 pub fn add_job(&mut self, child: Child, command: String, state: JobState) -> usize {
164 let id = self.next_id;
165 self.next_id += 1;
166
167 let pid = child.id() as i32;
168 let job = JobInfo {
169 id,
170 pid,
171 child: Some(child),
172 command,
173 state,
174 is_current: true,
175 };
176
177 if let Some(cur_id) = self.current_id {
179 if let Some(j) = self.get_mut_internal(cur_id) {
180 j.is_current = false;
181 }
182 }
183
184 let slot = self.get_free_slot();
186 if slot >= self.jobs.len() {
187 self.jobs.resize_with(slot + 1, || None);
188 }
189 self.jobs[slot] = Some(job);
190 self.current_id = Some(id);
191
192 id
193 }
194
195 pub fn add_pid_job(&mut self, pid: i32, command: String, state: JobState) -> usize {
200 let id = self.next_id;
201 self.next_id += 1;
202 let job = JobInfo {
203 id,
204 pid,
205 child: None,
206 command,
207 state,
208 is_current: true,
209 };
210 if let Some(cur_id) = self.current_id {
211 if let Some(j) = self.get_mut_internal(cur_id) {
212 j.is_current = false;
213 }
214 }
215 let slot = self.get_free_slot();
216 if slot >= self.jobs.len() {
217 self.jobs.resize_with(slot + 1, || None);
218 }
219 self.jobs[slot] = Some(job);
220 self.current_id = Some(id);
221 id
222 }
223
224 fn get_free_slot(&self) -> usize {
225 for (i, slot) in self.jobs.iter().enumerate() {
226 if slot.is_none() {
227 return i;
228 }
229 }
230 self.jobs.len()
231 }
232
233 fn get_mut_internal(&mut self, id: usize) -> Option<&mut JobInfo> {
234 self.jobs.iter_mut().flatten().find(|job| job.id == id)
235 }
236
237 pub fn get(&self, id: usize) -> Option<&JobInfo> {
239 self.jobs
240 .iter()
241 .flatten()
242 .find(|&job| job.id == id)
243 .map(|v| v as _)
244 }
245
246 pub fn get_mut(&mut self, id: usize) -> Option<&mut JobInfo> {
248 self.get_mut_internal(id)
249 }
250
251 pub fn remove(&mut self, id: usize) -> Option<JobInfo> {
253 for slot in self.jobs.iter_mut() {
254 if slot.as_ref().map(|j| j.id == id).unwrap_or(false) {
255 let job = slot.take();
256 if self.current_id == Some(id) {
257 self.current_id = None;
258 }
259 return job;
260 }
261 }
262 None
263 }
264
265 pub fn list(&self) -> Vec<&JobInfo> {
267 self.jobs.iter().filter_map(|j| j.as_ref()).collect()
268 }
269
270 pub fn iter(&self) -> impl Iterator<Item = (usize, &JobInfo)> {
272 self.jobs
273 .iter()
274 .filter_map(|j| j.as_ref().map(|job| (job.id, job)))
275 }
276
277 pub fn count(&self) -> usize {
279 self.jobs.iter().filter(|j| j.is_some()).count()
280 }
281
282 pub fn is_empty(&self) -> bool {
284 self.count() == 0
285 }
286
287 pub fn current(&self) -> Option<&JobInfo> {
289 self.current_id.and_then(|id| self.get(id))
290 }
291
292 pub fn reap_finished(&mut self) -> Vec<JobInfo> {
294 let mut finished = Vec::new();
295
296 for job in self.jobs.iter_mut().flatten() {
297 if let Some(ref mut child) = job.child {
298 match child.try_wait() {
300 Ok(Some(_status)) => {
301 job.state = JobState::Done;
303 }
304 Ok(None) => {
305 }
307 Err(_) => {
308 job.state = JobState::Done;
310 }
311 }
312 }
313 }
314
315 for slot in self.jobs.iter_mut() {
317 if slot
318 .as_ref()
319 .map(|j| j.state == JobState::Done)
320 .unwrap_or(false)
321 {
322 if let Some(job) = slot.take() {
323 finished.push(job);
324 }
325 }
326 }
327
328 finished
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335
336 #[test]
337 fn test_job_table_new() {
338 let _g = crate::test_util::global_state_lock();
339 let table = JobTable::new();
340 assert!(table.is_empty());
341 }
342
343 #[test]
344 fn test_job_state_enum() {
345 let _g = crate::test_util::global_state_lock();
346 let state = JobState::Running;
347 assert_eq!(state, JobState::Running);
348 assert_ne!(state, JobState::Stopped);
349 assert_ne!(state, JobState::Done);
350 }
351
352 #[test]
353 fn test_add_pid_job_assigns_id() {
354 let _g = crate::test_util::global_state_lock();
355 let mut t = JobTable::new();
356 let id1 = t.add_pid_job(1234, "cmd1".into(), JobState::Running);
357 let id2 = t.add_pid_job(5678, "cmd2".into(), JobState::Running);
358 assert_ne!(id1, id2);
359 assert_eq!(t.list().len(), 2);
360 assert_eq!(t.current().map(|j| j.id), Some(id2));
361 }
362
363 #[test]
364 fn test_remove_drops_current() {
365 let _g = crate::test_util::global_state_lock();
366 let mut t = JobTable::new();
367 let id = t.add_pid_job(99, "x".into(), JobState::Running);
368 assert!(t.remove(id).is_some());
369 assert!(t.is_empty());
370 assert!(t.current().is_none());
371 }
372}