forked from gitui-org/gitui
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlogitems.rs
146 lines (127 loc) · 3.23 KB
/
logitems.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
use asyncgit::sync::{CommitId, CommitInfo};
use chrono::{DateTime, Duration, Local, NaiveDateTime, Utc};
use std::slice::Iter;
use crate::components::utils::emojifi_string;
static SLICE_OFFSET_RELOAD_THRESHOLD: usize = 100;
pub struct LogEntry {
pub time: DateTime<Local>,
pub author: String,
pub msg: String,
pub hash_short: String,
pub id: CommitId,
}
impl From<CommitInfo> for LogEntry {
fn from(c: CommitInfo) -> Self {
let time =
DateTime::<Local>::from(DateTime::<Utc>::from_utc(
NaiveDateTime::from_timestamp(c.time, 0),
Utc,
));
// Replace markdown emojis with Unicode equivalent
let author = c.author;
let mut msg = c.message;
emojifi_string(&mut msg);
Self {
author,
msg,
time,
hash_short: c.id.get_short_string(),
id: c.id,
}
}
}
impl LogEntry {
pub fn time_to_string(&self, now: DateTime<Local>) -> String {
let delta = now - self.time;
if delta < Duration::minutes(30) {
let delta_str = if delta < Duration::minutes(1) {
"<1m ago".to_string()
} else {
format!("{:0>2}m ago", delta.num_minutes())
};
format!("{: <10}", delta_str)
} else if self.time.date() == now.date() {
self.time.format("%T ").to_string()
} else {
self.time.format("%Y-%m-%d").to_string()
}
}
}
///
#[derive(Default)]
pub struct ItemBatch {
index_offset: usize,
items: Vec<LogEntry>,
}
impl ItemBatch {
fn last_idx(&self) -> usize {
self.index_offset + self.items.len()
}
///
pub const fn index_offset(&self) -> usize {
self.index_offset
}
/// shortcut to get an `Iter` of our internal items
pub fn iter(&self) -> Iter<'_, LogEntry> {
self.items.iter()
}
/// clear curent list of items
pub fn clear(&mut self) {
self.items.clear();
}
/// insert new batch of items
pub fn set_items(
&mut self,
start_index: usize,
commits: Vec<CommitInfo>,
) {
self.items.clear();
self.items.extend(commits.into_iter().map(LogEntry::from));
self.index_offset = start_index;
}
/// returns `true` if we should fetch updated list of items
pub fn needs_data(&self, idx: usize, idx_max: usize) -> bool {
let want_min =
idx.saturating_sub(SLICE_OFFSET_RELOAD_THRESHOLD);
let want_max = idx
.saturating_add(SLICE_OFFSET_RELOAD_THRESHOLD)
.min(idx_max);
let needs_data_top = want_min < self.index_offset;
let needs_data_bottom = want_max >= self.last_idx();
needs_data_bottom || needs_data_top
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_conversion(s: &str) -> String {
let mut s = s.to_string();
emojifi_string(&mut s);
s
}
#[test]
fn test_emojifi_string_conversion_cases() {
assert_eq!(
&test_conversion("It's :hammer: time!"),
"It's 🔨 time!"
);
assert_eq!(
&test_conversion(":red_circle::orange_circle::yellow_circle::green_circle::large_blue_circle::purple_circle:"),
"🔴🟠🟡🟢🔵🟣"
);
assert_eq!(
&test_conversion("It's raining :cat:s and :dog:s"),
"It's raining 🐱s and 🐶s"
);
assert_eq!(&test_conversion(":crab: rules!"), "🦀 rules!");
}
#[test]
fn test_emojifi_string_no_conversion_cases() {
assert_eq!(&test_conversion("123"), "123");
assert_eq!(
&test_conversion("This :should_not_convert:"),
"This :should_not_convert:"
);
assert_eq!(&test_conversion(":gopher:"), ":gopher:");
}
}