app.rs 25.2 KB
Newer Older
1
use std::collections::HashMap;
2

3
4
use tui::widgets::ListState;

zyno42's avatar
zyno42 committed
5
use log::{error, trace, warn};
6

7
8
use tapasco::{
    device::PEParameter,
zyno42's avatar
zyno42 committed
9
10
11
    device::{status::Interrupt, Device},
    pe::PE,
    tlkm::TLKM,
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
};

use chrono::{TimeZone, Utc};

use snafu::{ResultExt, Snafu};

#[derive(Debug, Snafu)]
pub enum Error {
    #[snafu(display("Failed to initialize TLKM object: {}", source))]
    TLKMInit { source: tapasco::tlkm::Error },

    #[snafu(display("Failed to decode/acquire TLKM device or one of its PEs: {}", source))]
    DeviceInit { source: tapasco::device::Error },
}

type Result<T, E = Error> = std::result::Result<T, E>;

29
30
31
// Import the Subcommand enum from super module as AccessMode to clarify intent
pub use super::Command as AccessMode;

32
33
// TODO: It would be nice to see when/how many interrupts were triggered.

zyno42's avatar
zyno42 committed
34
35
#[derive(Debug, PartialEq)]
pub enum InputMode {
zyno42's avatar
zyno42 committed
36
    Navigation,
zyno42's avatar
zyno42 committed
37
38
    Edit,
}
39

zyno42's avatar
zyno42 committed
40
41
42
43
44
#[derive(Debug, PartialEq)]
pub enum InputFrame {
    PEList,
    RegisterList,
}
45

46
pub struct App<'a> {
47
    tlkm_device: Device,
48
49
    bitstream_info: String,
    platform_info: String,
50
51
52
53
54
    pub access_mode: AccessMode,
    pub input: String,
    pub input_mode: InputMode,
    pub focus: InputFrame,
    pub title: String,
55
56
57
    pub tabs: TabsState<'a>,
    pub pe_infos: StatefulList<String>,
    pub pes: Vec<(usize, PE)>, // Plural of Processing Element (PEs)
58
59
    pub register_list: ListState,
    pub local_memory_list: ListState,
60
    pub messages: Vec<String>,
61
62
63
}

impl<'a> App<'a> {
64
65
66
    pub fn new(device_id: u32, access_mode: AccessMode) -> Result<App<'a>> {
        trace!("Creating new App for Tapasco state");

67
        // Get Tapasco Loadable Linux Kernel Module
68
        let tlkm = TLKM::new().context(TLKMInitSnafu {})?;
69
        // Allocate the device with the given ID
70
        let tlkm_device = tlkm
zyno42's avatar
zyno42 committed
71
            .device_alloc(device_id, &HashMap::new())
72
            .context(TLKMInitSnafu {})?;
73
74
75
76
77
78
79
80

        // For some access modes we need to take some special care to use them
        let access_mode_str = match access_mode {
            AccessMode::Monitor {} => {
                // Monitor Mode: In order to observe other Tapasco Host applications which need exclusive
                // access we implement a monitor mode where registers cannot be modified. For this
                // no special access is necessary. This is a no-op.
                "Monitor"
zyno42's avatar
zyno42 committed
81
            }
82
83
84
85
86
            // TODO: 3. When Issue #296 is fixed, enable debug mode here again, too.
            //AccessMode::Debug {} => {
            //    // Change device access to exclusive to be able to acquire PEs
            //    tlkm_device
            //        .change_access(tapasco::tlkm::tlkm_access::TlkmAccessExclusive)
87
            //        .context(DeviceInitSnafu {})?;
88
89
            //    "Debug"
            //}
90
91
92
93
            AccessMode::Unsafe {} => {
                // Change device access to exclusive to be able to acquire PEs
                warn!("Running in Unsafe Mode");
                "Unsafe"
zyno42's avatar
zyno42 committed
94
            }
95
96
97
98
99
        };
        trace!("Access Mode is: {}", access_mode_str);

        // Empty string where input is stored
        let input = String::new();
zyno42's avatar
zyno42 committed
100
101
        // Initialize App in Navigation mode
        let input_mode = InputMode::Navigation;
102
103
104
        // Initialize UI with focus on the PE list
        let focus = InputFrame::PEList;

zyno42's avatar
zyno42 committed
105
106
107
108
109
        let tabs = TabsState::new(vec![
            "Peek & Poke PEs",
            "Platform Components",
            "Bitstream & Device Info",
        ]);
110
        let title = format!("TaPaSCo Debugger - {} Mode", access_mode_str);
111

112
        let tlkm_version = tlkm.version().context(TLKMInitSnafu {})?;
zyno42's avatar
zyno42 committed
113
114
115
116
117
118
119
120
121
122
        let platform_base = tlkm_device
            .status()
            .platform_base
            .clone()
            .expect("Could not get platform_base!");
        let arch_base = tlkm_device
            .status()
            .arch_base
            .clone()
            .expect("Could not get arch_base!");
123
124

        // Parse info about PEs from the status core
125
126
127
        // Preallocate these vectors to set the acquired PE at the right position later
        let mut pe_infos = Vec::with_capacity(tlkm_device.status().pe.len());
        let mut pes: Vec<(usize, PE)> = Vec::with_capacity(tlkm_device.status().pe.len());
128

129
        for (index, pe) in tlkm_device.status().pe.iter().enumerate() {
130
            // Calculate the "real" address using arch base address plus PE offset
zyno42's avatar
zyno42 committed
131
            let address = arch_base.base + pe.offset;
132
            pe_infos.push(
133
134
                format!("Slot {}: {} (ID: {})    Address: 0x{:016x} ({}), Size: 0x{:x} ({} Bytes), Interrupts: {}, Debug: {:?}",
                        index, pe.name, pe.id, address, address, pe.size, pe.size, App::parse_interrupts(&pe.interrupts), pe.debug));
135
136
137

            // Acquire the PE to be able to show its registers etc.
            // Warning: casting to usize can panic! On a <32-bit system..
zyno42's avatar
zyno42 committed
138
139
            let pe = tlkm_device
                .acquire_pe_without_job(pe.id as usize)
140
                .context(DeviceInitSnafu {})?;
141
142
143
144
145
146
147
148
149
            // TODO: There is no way to check that you really got the PE that you wanted
            // so I have to use this workaround to set it at the ID of the PE struct which
            // confusingly is NOT the pe.id from above which is stored at type_id inside the PE.
            let pe_real_id = *pe.id();
            //pes[pe_real_id] = pe;
            pes.push((pe_real_id, pe));
        }

        // Preselect the first element if the device's bitstream contains at least one PE
zyno42's avatar
zyno42 committed
150
        let pe_infos = if pe_infos.is_empty() {
151
            StatefulList::with_items(pe_infos)
zyno42's avatar
zyno42 committed
152
153
        } else {
            StatefulList::with_items_selected(pe_infos, 0)
154
155
        };

156
157
158
        // There are theoretically endless registers and local memory
        let register_list = ListState::default();
        let local_memory_list = ListState::default();
159

160
161
162
        // Parse bitstream info from the Status core
        let mut bitstream_info = String::new();
        // TODO: decode vendor and product IDs
zyno42's avatar
zyno42 committed
163
164
165
166
167
168
169
        bitstream_info += &format!(
            "Device ID: {} ({}),\nVendor: {}, Product: {},\n\n",
            tlkm_device.id(),
            tlkm_device.name(),
            tlkm_device.vendor(),
            tlkm_device.product()
        );
170

zyno42's avatar
zyno42 committed
171
172
        bitstream_info += &format!(
            "Bitstream generated at: {} ({})\n\n",
zyno42's avatar
zyno42 committed
173
174
175
176
177
            if let Ok(i) = tlkm_device.status().timestamp.try_into() {
                format!("{}", Utc.timestamp(i, 0).format("%Y-%m-%d"))
            } else {
                "the future".to_string()
            },
zyno42's avatar
zyno42 committed
178
179
            tlkm_device.status().timestamp
        );
180

181
        for v in &tlkm_device.status().versions {
zyno42's avatar
zyno42 committed
182
183
184
185
            bitstream_info += &format!(
                "{} Version: {}.{}{}\n",
                v.software, v.year, v.release, v.extra_version
            );
186
        }
187
        bitstream_info += &format!("TLKM Version: {}\n\n", tlkm_version);
188

189
        for c in &tlkm_device.status().clocks {
zyno42's avatar
zyno42 committed
190
            bitstream_info += &format!("{} Clock Frequency: {} MHz\n", c.name, c.frequency_mhz);
191
192
193
194
195
        }

        // Parse platform info from the Status core
        let mut platform_info = String::new();

zyno42's avatar
zyno42 committed
196
197
198
199
        platform_info += &format!(
            "Platform Base: 0x{:012x} (Size: 0x{:x} ({} Bytes))\n\n",
            platform_base.base, platform_base.size, platform_base.size
        );
200

201
        for p in &tlkm_device.status().platform {
zyno42's avatar
zyno42 committed
202
203
204
205
206
207
208
209
210
211
            let address = platform_base.base + p.offset;
            platform_info += &format!(
                "{}:\n  Address: 0x{:012x} ({}), Size: 0x{:x} ({} Bytes)\n  Interrupts: {}\n\n",
                p.name.trim_start_matches("PLATFORM_COMPONENT_"),
                address,
                address,
                p.size,
                p.size,
                App::parse_interrupts(&p.interrupts)
            );
212
213
        }

214
215
216
217
218
219
        // Setup a new Vector to store (event) messages. It's kind of like logging but as there
        // already is a real logger and we cannot add another logging implementation, we have to
        // provide something a little bit different and simpler to inform users about things like
        // started PEs.
        let messages: Vec<String> = Vec::new();

220
        trace!("Constructed App");
221
222

        Ok(App {
223
            tlkm_device,
224
225
            bitstream_info,
            platform_info,
226
227
228
229
230
231
            access_mode,
            input,
            input_mode,
            focus,
            title,
            tabs,
232
233
            pe_infos,
            pes,
234
235
            register_list,
            local_memory_list,
236
            messages,
237
238
239
240
241
242
243
244
245
246
247
248
        })
    }

    pub fn next_tab(&mut self) {
        self.tabs.next();
    }

    pub fn previous_tab(&mut self) {
        self.tabs.previous();
    }

    pub fn on_up(&mut self) {
zyno42's avatar
zyno42 committed
249
250
251
252
253
        if self.tabs.index == 0 {
            match self.focus {
                InputFrame::PEList => self.pe_infos.previous(),
                InputFrame::RegisterList => self.register_list.previous(),
            };
254
        };
255
256
257
    }

    pub fn on_down(&mut self) {
zyno42's avatar
zyno42 committed
258
259
260
261
262
        if self.tabs.index == 0 {
            match self.focus {
                InputFrame::PEList => self.pe_infos.next(),
                InputFrame::RegisterList => self.register_list.next(),
            };
263
        };
264
265
266
    }

    pub fn on_escape(&mut self) {
zyno42's avatar
zyno42 committed
267
268
        if self.tabs.index == 0 {
            match self.input_mode {
zyno42's avatar
zyno42 committed
269
                InputMode::Navigation => {
zyno42's avatar
zyno42 committed
270
271
272
273
274
275
276
277
278
                    match self.focus {
                        InputFrame::PEList => self.pe_infos.unselect(),
                        InputFrame::RegisterList => {
                            self.register_list.unselect();
                            self.focus = InputFrame::PEList;
                        }
                    };
                }
                InputMode::Edit => {
zyno42's avatar
zyno42 committed
279
                    self.input_mode = InputMode::Navigation;
zyno42's avatar
zyno42 committed
280
281
282
                    self.input.clear();
                }
            };
283
        };
284
285
    }

286
    pub fn on_enter(&mut self) {
zyno42's avatar
zyno42 committed
287
288
289
        if self.tabs.index == 0 {
            match self.access_mode {
                AccessMode::Monitor {} => {}
290
291
292
                // TODO: 4. Replace the second next line with the next line:
                // AccessMode::Debug {} | AccessMode::Unsafe {} => {
                AccessMode::Unsafe {} => {
zyno42's avatar
zyno42 committed
293
                    match self.input_mode {
zyno42's avatar
zyno42 committed
294
                        InputMode::Navigation => {
zyno42's avatar
zyno42 committed
295
296
297
298
299
300
                            match self.focus {
                                // Change the focused component to the register list of the selected PE
                                InputFrame::PEList => match self.pe_infos.state.selected() {
                                    Some(_) => {
                                        self.focus = InputFrame::RegisterList;
                                        self.register_list.next();
301
                                    }
zyno42's avatar
zyno42 committed
302
303
304
305
306
                                    _ => self.pe_infos.next(),
                                },
                                // Enter Edit Mode for a new register value
                                InputFrame::RegisterList => {
                                    self.input_mode = InputMode::Edit;
307
                                }
zyno42's avatar
zyno42 committed
308
309
310
311
312
                            };
                        }
                        InputMode::Edit => {
                            // If the input cannot be parsed correctly, simply do nothing until
                            // we either hit Escape or enter a valid decimal integer.
zyno42's avatar
zyno42 committed
313
314
315
316
317
318
319
320
321
322
                            let new_value: Option<u64> =
                                if let Some(hex_string) = self.input.strip_prefix("0x") {
                                    u64::from_str_radix(hex_string, 16).ok()
                                } else if let Ok(new_value) = self.input.parse::<u64>() {
                                    Some(new_value)
                                } else if let Ok(new_value) = self.input.parse::<i64>() {
                                    Some(new_value as u64) // explicitly use as casting
                                } else {
                                    None
                                };
323
324

                            if let Some(new_value) = new_value {
zyno42's avatar
zyno42 committed
325
326
327
328
329
330
331
332
333
                                self.input.clear();

                                // Ignore the error because unless the code changes, there will
                                // be no error returned by this function.
                                if let Err(e) = self.pes
                                    .get_mut(self.pe_infos.state.selected()
                                             .expect("There should have been a selected PE. This is a bug."))
                                    .expect("There should have been a PE for the selection. This is a bug.")
                                    .1 // ignore the index, select the PE from the tuple
334
                                    .set_arg(self.register_list.selected().unwrap(),
335
                                             PEParameter::Single64(new_value)) {
zyno42's avatar
zyno42 committed
336
337
338
339
340
341
                                    // but log this error in case the code changes
                                    error!("Error setting argument: {}.
                                            This is probably due to libtapasco having changed something
                                            important. You should fix this app.", e);
                                }

zyno42's avatar
zyno42 committed
342
343
344
345
346
347
                                self.messages.push(format!(
                                    "In slot {} set argument register {} to new value: {}.",
                                    self.pe_infos.state.selected().unwrap(),
                                    self.register_list.selected().unwrap(),
                                    new_value
                                ));
348

zyno42's avatar
zyno42 committed
349
                                self.input_mode = InputMode::Navigation;
zyno42's avatar
zyno42 committed
350
351
352
353
354
                            }
                        }
                    };
                }
            };
355
        };
356
357
    }

358
    pub fn get_bitstream_info(&self) -> &str {
359
360
361
        &self.bitstream_info
    }

362
    pub fn get_platform_info(&self) -> &str {
363
364
365
        &self.platform_info
    }

366
367
    fn get_current_pe(&self) -> Option<&PE> {
        let pe_slot = match self.pe_infos.state.selected() {
368
            Some(n) => n,
369
            _ => return None,
370
371
        };

372
        // Get the PE with the real ID of the selected slot
zyno42's avatar
zyno42 committed
373
374
375
        let (_, pe) = self
            .pes
            .iter()
376
377
378
379
380
381
            .filter(|(id, _)| *id == pe_slot)
            .take(1)
            .collect::<Vec<&(usize, PE)>>()
            .get(0)
            .expect("There should be a PE with the selected ID. This is a bug.");

382
383
        Some(pe)
    }
384

zyno42's avatar
zyno42 committed
385
386
    pub fn start_current_pe(&self) -> String {
        assert!(self.access_mode == AccessMode::Unsafe {}, "Unsafe access mode necessary to start a PE! This function should not have been callable. This is a bug.");
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408

        if let Some(pe) = self.get_current_pe() {
            // This does not work because `libtapasco` does a really god job of protecting its PEs
            // and access to them with Rust's ownership rules.
            //
            // Besides, this might not be the correct PE when there are multiple PEs with the same
            // TypeID.
            //match self.tlkm_device.acquire_pe(*pe.type_id()) {
            //    Ok(mut pe) => {
            //        trace!("Acquired PE: {:?}", pe);
            //        if let Ok(_) = pe.start(vec![]) {
            //            trace!("Started PE: {:?}", pe);
            //            if let Ok(_) = pe.release(true, true) {
            //                trace!("Starting PE: {:?}", pe);
            //            }
            //        }
            //    },
            //    Err(e) => error!("Could not acquire PE: {}", e),
            //}
            //
            trace!("Starting PE with ID: {}.", pe.id());

zyno42's avatar
zyno42 committed
409
410
411
            let offset = (*pe.offset())
                .try_into()
                .expect("Expected to be able to cast the PE offset.");
412
413
414
415
416
417
418
419
420
421
422
423

            unsafe {
                // Access PE memory just like in `libtapasco`:
                //use volatile::Volatile;
                //let ptr = pe.memory().as_ptr().offset(offset);
                //let volatile_ptr: *mut Volatile<u32> = ptr as *mut Volatile<u32>;
                //(*volatile_ptr).write(1);
                //
                // but instead of the `volatile` crate use std::ptr:
                let ptr = pe.memory().as_ptr().offset(offset);
                (ptr as *mut u32).write_volatile(1);
            }
424

zyno42's avatar
zyno42 committed
425
426
427
428
429
430
431
            return format!(
                "Send start signal to PE in slot: {}.",
                self.pe_infos
                    .state
                    .selected()
                    .expect("There needs to be a selected PE. This is a bug.")
            );
432
433
        }

zyno42's avatar
zyno42 committed
434
        "No PE selected.".to_string()
435
    }
436
437
438

    pub fn get_status_registers(&mut self) -> String {
        if let Some(pe) = self.get_current_pe() {
zyno42's avatar
zyno42 committed
439
440
441
            let (global_interrupt, local_interrupt) = pe
                .interrupt_status()
                .expect("Expected to get PE interrupt status.");
442
443
444
445
446
447
448
449
450
451
452
            let return_value = pe.return_value();

            // TODO: I have to get these from the runtime because there are no registers for that.
            //let is_running = false;
            //let interrupt_pending = false;

            let mut result = String::new();
            //result += &format!("PE is running: {}", is_running);
            result += &format!("Local  Interrupt Enabled: {}\n", local_interrupt);
            result += &format!("Global Interrupt Enabled: {}\n", global_interrupt);
            //result += &format!("Interrupt pending: {}", interrupt_pending);
zyno42's avatar
zyno42 committed
453
454
            result += &format!(
                "Return: 0x{:16x} (i32: {:10})\n",
zyno42's avatar
zyno42 committed
455
456
                return_value,
                return_value as i32 // explicitly use as casting
zyno42's avatar
zyno42 committed
457
            );
458

zyno42's avatar
zyno42 committed
459
            return result;
460
        }
zyno42's avatar
zyno42 committed
461
462

        "No PE selected.".to_string()
463
    }
464

465
466
467
    pub fn get_argument_registers(&mut self, number_of_lines: usize) -> Vec<String> {
        let number_of_registers = self.register_list.selected().unwrap_or(0) + number_of_lines;

468
        if let Some(pe) = self.get_current_pe() {
469
            let argument_registers = (0..number_of_registers)
zyno42's avatar
zyno42 committed
470
471
472
473
474
475
476
477
                .map(|i| {
                    match pe
                        .read_arg(i, 8)
                        .expect("Expected to be able to read PE registers!")
                    {
                        PEParameter::Single64(u) => u,
                        _ => unreachable!(),
                    }
478
479
480
481
482
483
484
485
                })
                .collect::<Vec<u64>>();

            let mut result = Vec::new();
            for (i, a) in argument_registers.iter().enumerate() {
                result.push(format!("Arg#{:02}: 0x{:16x} ({:20})\n", i, a, a));
            }

zyno42's avatar
zyno42 committed
486
            return result;
487
        }
488
489

        vec!["No PE selected.".to_string()]
490
491
    }

492
    pub fn dump_current_pe_local_memory(&self, number_of_lines: usize) -> Vec<String> {
493
494
495
496
497
498
        if let Some(pe) = self.get_current_pe() {
            let local_memory = match pe.local_memory() {
                Some(m) => m,
                _ => return vec!["No local memory for this PE.".to_string()],
            };

499
            let mut memory_cells: Vec<u8> = vec![0_u8; 16 * number_of_lines];
500
            match local_memory.dma().copy_from(0, &mut memory_cells) {
zyno42's avatar
zyno42 committed
501
                Ok(_) => {}
502
503
504
505
506
507
                _ => return vec!["Could not read PE Local Memory!".to_string()],
            }

            let mut result: Vec<String> = Vec::new();
            for (i, s) in memory_cells.chunks_exact(16).enumerate() {
                // format bytes like hexdump
zyno42's avatar
zyno42 committed
508
509
510
511
512
513
514
515
                result.push(format!(
                    "{:08x}: {}\n",
                    16 * i,
                    s.iter()
                        .map(|x| format!("{:02x}", x))
                        .collect::<Vec<String>>()
                        .join(" ")
                ));
516
517
518
519
520
521
522
523
            }

            result
        } else {
            vec!["No PE selected.".to_string()]
        }
    }

zyno42's avatar
zyno42 committed
524
    fn parse_interrupts(interrupts: &[Interrupt]) -> String {
525
        let mut result = String::new();
526

zyno42's avatar
zyno42 committed
527
        if interrupts.is_empty() {
528
            result += "None";
zyno42's avatar
zyno42 committed
529
        } else {
530
531
532
533
534
535
536
537
538
539
540
            result += "[ ";

            for (index, interrupt) in interrupts.iter().enumerate() {
                if index > 0 {
                    result += ", ";
                }

                result += &format!("{}:{}", interrupt.mapping, interrupt.name);
            }

            result += " ]";
541
542
543
544
545
        }

        result
    }

546
    pub fn get_dmaengine_statistics(&self) -> String {
547
        let dmaengine_memory = unsafe {
zyno42's avatar
zyno42 committed
548
549
550
551
            match self
                .tlkm_device
                .get_platform_component_memory("PLATFORM_COMPONENT_DMA0")
            {
552
553
554
                Ok(m) => m,
                Err(_) => return "No DMAEngine found!".to_string(),
            }
555
        };
556

557
558
559
560
561
562
563
564
        let status_registers: Vec<(&str, isize)> = vec![
            ("Number of Read Requests        ", 48),
            ("Number of Write Requests       ", 56),
            ("Cycles since last Read Request ", 64),
            ("Cycles between Read Requests   ", 72),
            ("Cycles since last Write Request", 88),
            ("Cycles between Write Requests  ", 96),
        ];
565

566
567
568
569
570
        let mut result = String::new();
        for (index, r) in status_registers.iter().enumerate() {
            unsafe {
                // Create a const pointer to the u64 register at the offset in the platform address
                // space of the DMAEngine
571
                let dmaengine_register_ptr = dmaengine_memory.as_ptr().offset(r.1).cast::<u64>();
572
573
574
575
576
577
                // Read IO register with volatile, see:
                // https://doc.rust-lang.org/std/ptr/fn.read_volatile.html
                let dmaengine_register = dmaengine_register_ptr.read_volatile();

                if index < 2 {
                    // Calculating ms doesn't make sense for the number of Reads/Writes
zyno42's avatar
zyno42 committed
578
579
580
581
                    result += &format!(
                        "{}    {:016x}    ({:20})\n",
                        r.0, dmaengine_register, dmaengine_register
                    );
582
583
584
                } else {
                    // Warning: This assumes the host frequency to be 250MHz which should be the case
                    // everywhere.
zyno42's avatar
zyno42 committed
585
586
587
588
589
590
591
                    result += &format!(
                        "{}    {:016x}    ({:20} = {:9} ns)\n",
                        r.0,
                        dmaengine_register,
                        dmaengine_register,
                        dmaengine_register * 4
                    );
592
                }
593
594
            }

595
596
597
598
            // Add a newline every second line
            if index % 2 == 1 {
                result += "\n";
            }
599
600
        }

601
        result
602
603
604
605
606
607
608
609
610
611
612
613
614
615
    }
}

// The following code is taken from libtui-rs demo:
// https://github.com/fdehau/tui-rs/blob/v0.15.0/examples/util/mod.rs
// licensed under MIT License by Florian Dehau, see:
// https://spdx.org/licenses/MIT.html
pub struct TabsState<'a> {
    pub titles: Vec<&'a str>,
    pub index: usize,
}

impl<'a> TabsState<'a> {
    pub fn new(titles: Vec<&'a str>) -> TabsState {
zyno42's avatar
zyno42 committed
616
        TabsState { titles, index: 0 }
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
    }

    pub fn next(&mut self) {
        self.index = (self.index + 1) % self.titles.len();
    }

    pub fn previous(&mut self) {
        self.index = if self.index == 0 {
            self.titles.len() - 1
        } else {
            self.index - 1
        }
    }
}

pub struct StatefulList<T> {
    pub state: ListState,
    pub items: Vec<T>,
}

637
638
impl<T> Default for StatefulList<T> {
    fn default() -> Self {
zyno42's avatar
zyno42 committed
639
        Self {
640
641
642
643
            state: ListState::default(),
            items: Vec::new(),
        }
    }
644
}
645

646
impl<T> StatefulList<T> {
zyno42's avatar
zyno42 committed
647
648
    pub fn with_items(items: Vec<T>) -> Self {
        Self {
649
650
651
652
653
            state: ListState::default(),
            items,
        }
    }

zyno42's avatar
zyno42 committed
654
655
    pub fn with_items_selected(items: Vec<T>, selected: usize) -> Self {
        let mut list = Self {
656
657
658
659
660
661
662
            state: ListState::default(),
            items,
        };
        list.state.select(Some(selected));

        list
    }
663
}
664

665
666
667
668
669
670
671
672
673
// Define a new trait so we can implement methods for ListState
pub trait Select {
    fn next(&mut self);
    fn previous(&mut self);
    fn unselect(&mut self);
}

impl<T> Select for StatefulList<T> {
    fn next(&mut self) {
674
675
676
677
678
679
680
        let n = match self.state.selected() {
            Some(m) => {
                if m >= self.items.len() - 1 {
                    0
                } else {
                    m + 1
                }
zyno42's avatar
zyno42 committed
681
            }
682
683
684
685
686
687
            None => 0,
        };

        self.state.select(Some(n));
    }

688
    fn previous(&mut self) {
689
690
691
692
693
694
695
        let n = match self.state.selected() {
            Some(m) => {
                if m == 0 {
                    self.items.len() - 1
                } else {
                    m - 1
                }
zyno42's avatar
zyno42 committed
696
            }
697
698
699
700
701
702
            None => self.items.len() - 1,
        };

        self.state.select(Some(n));
    }

703
    fn unselect(&mut self) {
704
705
706
        self.state.select(None);
    }
}
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736

impl Select for ListState {
    fn next(&mut self) {
        let n = match self.selected() {
            Some(m) => m + 1,
            None => 0,
        };

        self.select(Some(n));
    }

    fn previous(&mut self) {
        let n = match self.selected() {
            Some(m) => {
                if m == 0 {
                    0
                } else {
                    m - 1
                }
            }
            None => 0,
        };

        self.select(Some(n));
    }

    fn unselect(&mut self) {
        self.select(None);
    }
}