1use std::{num::ParseIntError, str::FromStr};
2
3use yazi_shared::data::Data;
4
5#[derive(Clone, Copy, Debug)]
6pub enum Step {
7 Top,
8 Bot,
9 Prev,
10 Next,
11 Offset(isize),
12 Percent(i8),
13}
14
15impl Default for Step {
16 fn default() -> Self { Self::Offset(0) }
17}
18
19impl From<isize> for Step {
20 fn from(n: isize) -> Self { Self::Offset(n) }
21}
22
23impl FromStr for Step {
24 type Err = ParseIntError;
25
26 fn from_str(s: &str) -> Result<Self, Self::Err> {
27 Ok(match s {
28 "top" => Self::Top,
29 "bot" => Self::Bot,
30 "prev" => Self::Prev,
31 "next" => Self::Next,
32 s if s.ends_with('%') => Self::Percent(s[..s.len() - 1].parse()?),
33 s => Self::Offset(s.parse()?),
34 })
35 }
36}
37
38impl TryFrom<&Data> for Step {
39 type Error = ParseIntError;
40
41 fn try_from(value: &Data) -> Result<Self, Self::Error> {
42 Ok(match value {
43 Data::Integer(i) => Self::from(*i as isize),
44 Data::String(s) => s.parse()?,
45 _ => "".parse()?,
46 })
47 }
48}
49
50impl Step {
51 pub fn add(self, pos: usize, len: usize, limit: usize) -> usize {
52 if len == 0 {
53 return 0;
54 }
55
56 let off = match self {
57 Self::Top => return 0,
58 Self::Bot => return len - 1,
59 Self::Prev => -1,
60 Self::Next => 1,
61 Self::Offset(n) => n,
62 Self::Percent(0) => 0,
63 Self::Percent(n) => n as isize * limit as isize / 100,
64 };
65
66 if matches!(self, Self::Prev | Self::Next) {
67 off.saturating_add_unsigned(pos).rem_euclid(len as _) as _
68 } else if off >= 0 {
69 pos.saturating_add_signed(off)
70 } else {
71 pos.saturating_sub(off.unsigned_abs())
72 }
73 .min(len - 1)
74 }
75}