1use std::process::Child;
15use std::sync::Mutex;
16
17use crate::ported::jobs::{deletejob, CURJOB, MAXJOB, PREVJOB, THISJOB};
18use crate::ported::jobs::stat;
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
81 && (j.stat & stat::SUBJOB) == 0
82 && i as i32 != curjob
83 && i as i32 != thisjob
84 {
85 return i as i32;
86 }
87 }
88 -1
89 };
90 let mut found = pick(true); if found < 0 {
92 found = pick(false); }
94 *PREVJOB.get_or_init(|| Mutex::new(-1)).lock().unwrap() = found; }
96
97#[derive(Clone, Copy, Debug, PartialEq, Eq)]
101pub enum JobState {
102 Running,
104 Stopped,
106 Done,
108}
109
110#[derive(Debug)]
112pub struct JobInfo {
113 pub id: usize,
115 pub pid: i32,
117 pub child: Option<Child>,
119 pub command: String,
121 pub state: JobState,
123 pub is_current: bool,
125}
126
127pub struct JobTable {
132 jobs: Vec<Option<JobInfo>>,
134 current_id: Option<usize>,
136 next_id: usize,
138}
139
140impl Default for JobTable {
141 fn default() -> Self {
142 Self::new()
143 }
144}
145
146impl JobTable {
147 pub fn new() -> Self {
149 JobTable {
150 jobs: Vec::with_capacity(16),
151 current_id: None,
152 next_id: 1,
153 }
154 }
155
156 pub fn peek_next_id(&self) -> usize {
162 self.next_id
163 }
164
165 pub fn add_job(&mut self, child: Child, command: String, state: JobState) -> usize {
167 let id = self.next_id;
168 self.next_id += 1;
169
170 let pid = child.id() as i32;
171 let job = JobInfo {
172 id,
173 pid,
174 child: Some(child),
175 command,
176 state,
177 is_current: true,
178 };
179
180 if let Some(cur_id) = self.current_id {
182 if let Some(j) = self.get_mut_internal(cur_id) {
183 j.is_current = false;
184 }
185 }
186
187 let slot = self.get_free_slot();
189 if slot >= self.jobs.len() {
190 self.jobs.resize_with(slot + 1, || None);
191 }
192 self.jobs[slot] = Some(job);
193 self.current_id = Some(id);
194
195 id
196 }
197
198 pub fn add_pid_job(&mut self, pid: i32, command: String, state: JobState) -> usize {
203 let id = self.next_id;
204 self.next_id += 1;
205 let job = JobInfo {
206 id,
207 pid,
208 child: None,
209 command,
210 state,
211 is_current: true,
212 };
213 if let Some(cur_id) = self.current_id {
214 if let Some(j) = self.get_mut_internal(cur_id) {
215 j.is_current = false;
216 }
217 }
218 let slot = self.get_free_slot();
219 if slot >= self.jobs.len() {
220 self.jobs.resize_with(slot + 1, || None);
221 }
222 self.jobs[slot] = Some(job);
223 self.current_id = Some(id);
224 id
225 }
226
227 fn get_free_slot(&self) -> usize {
228 for (i, slot) in self.jobs.iter().enumerate() {
229 if slot.is_none() {
230 return i;
231 }
232 }
233 self.jobs.len()
234 }
235
236 fn get_mut_internal(&mut self, id: usize) -> Option<&mut JobInfo> {
237 self.jobs.iter_mut().flatten().find(|job| job.id == id)
238 }
239
240 pub fn get(&self, id: usize) -> Option<&JobInfo> {
242 self.jobs
243 .iter()
244 .flatten()
245 .find(|&job| job.id == id)
246 .map(|v| v as _)
247 }
248
249 pub fn get_mut(&mut self, id: usize) -> Option<&mut JobInfo> {
251 self.get_mut_internal(id)
252 }
253
254 pub fn remove(&mut self, id: usize) -> Option<JobInfo> {
256 for slot in self.jobs.iter_mut() {
257 if slot.as_ref().map(|j| j.id == id).unwrap_or(false) {
258 let job = slot.take();
259 if self.current_id == Some(id) {
260 self.current_id = None;
261 }
262 return job;
263 }
264 }
265 None
266 }
267
268 pub fn list(&self) -> Vec<&JobInfo> {
270 self.jobs.iter().filter_map(|j| j.as_ref()).collect()
271 }
272
273 pub fn iter(&self) -> impl Iterator<Item = (usize, &JobInfo)> {
275 self.jobs
276 .iter()
277 .filter_map(|j| j.as_ref().map(|job| (job.id, job)))
278 }
279
280 pub fn count(&self) -> usize {
282 self.jobs.iter().filter(|j| j.is_some()).count()
283 }
284
285 pub fn is_empty(&self) -> bool {
287 self.count() == 0
288 }
289
290 pub fn current(&self) -> Option<&JobInfo> {
292 self.current_id.and_then(|id| self.get(id))
293 }
294
295 pub fn reap_finished(&mut self) -> Vec<JobInfo> {
297 let mut finished = Vec::new();
298
299 for job in self.jobs.iter_mut().flatten() {
300 if let Some(ref mut child) = job.child {
301 match child.try_wait() {
303 Ok(Some(_status)) => {
304 job.state = JobState::Done;
306 }
307 Ok(None) => {
308 }
310 Err(_) => {
311 job.state = JobState::Done;
313 }
314 }
315 }
316 }
317
318 for slot in self.jobs.iter_mut() {
320 if slot
321 .as_ref()
322 .map(|j| j.state == JobState::Done)
323 .unwrap_or(false)
324 {
325 if let Some(job) = slot.take() {
326 finished.push(job);
327 }
328 }
329 }
330
331 finished
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338
339 #[test]
340 fn test_job_table_new() {
341 let _g = crate::test_util::global_state_lock();
342 let table = JobTable::new();
343 assert!(table.is_empty());
344 }
345
346 #[test]
347 fn test_job_state_enum() {
348 let _g = crate::test_util::global_state_lock();
349 let state = JobState::Running;
350 assert_eq!(state, JobState::Running);
351 assert_ne!(state, JobState::Stopped);
352 assert_ne!(state, JobState::Done);
353 }
354
355 #[test]
356 fn test_add_pid_job_assigns_id() {
357 let _g = crate::test_util::global_state_lock();
358 let mut t = JobTable::new();
359 let id1 = t.add_pid_job(1234, "cmd1".into(), JobState::Running);
360 let id2 = t.add_pid_job(5678, "cmd2".into(), JobState::Running);
361 assert_ne!(id1, id2);
362 assert_eq!(t.list().len(), 2);
363 assert_eq!(t.current().map(|j| j.id), Some(id2));
364 }
365
366 #[test]
367 fn test_remove_drops_current() {
368 let _g = crate::test_util::global_state_lock();
369 let mut t = JobTable::new();
370 let id = t.add_pid_job(99, "x".into(), JobState::Running);
371 assert!(t.remove(id).is_some());
372 assert!(t.is_empty());
373 assert!(t.current().is_none());
374 }
375}