aboutsummaryrefslogtreecommitdiffstats
path: root/teleterm/src/session_list.rs
diff options
context:
space:
mode:
Diffstat (limited to 'teleterm/src/session_list.rs')
-rw-r--r--teleterm/src/session_list.rs342
1 files changed, 342 insertions, 0 deletions
diff --git a/teleterm/src/session_list.rs b/teleterm/src/session_list.rs
new file mode 100644
index 0000000..7fd4d0a
--- /dev/null
+++ b/teleterm/src/session_list.rs
@@ -0,0 +1,342 @@
+pub struct SessionList {
+ sessions: Vec<crate::protocol::Session>,
+ offset: usize,
+ size: crate::term::Size,
+}
+
+impl SessionList {
+ pub fn new(
+ sessions: Vec<crate::protocol::Session>,
+ size: crate::term::Size,
+ ) -> Self {
+ let mut by_name = std::collections::HashMap::new();
+ for session in sessions {
+ if !by_name.contains_key(&session.username) {
+ by_name.insert(session.username.clone(), vec![]);
+ }
+ by_name.get_mut(&session.username).unwrap().push(session);
+ }
+ let mut names: Vec<_> = by_name.keys().cloned().collect();
+ names.sort_by(|a: &String, b: &String| {
+ let a_idle =
+ by_name[a].iter().min_by_key(|session| session.idle_time);
+ let b_idle =
+ by_name[b].iter().min_by_key(|session| session.idle_time);
+ // these unwraps are safe because we know that none of the vecs in
+ // the map can be empty
+ a_idle.unwrap().idle_time.cmp(&b_idle.unwrap().idle_time)
+ });
+ for name in &names {
+ if let Some(sessions) = by_name.get_mut(name) {
+ sessions.sort_by_key(|s| s.idle_time);
+ }
+ }
+
+ let mut sorted = vec![];
+ for name in names {
+ let sessions = by_name.remove(&name).unwrap();
+ for session in sessions {
+ sorted.push(session);
+ }
+ }
+
+ Self {
+ sessions: sorted,
+ offset: 0,
+ size,
+ }
+ }
+
+ pub fn visible_sessions(&self) -> &[crate::protocol::Session] {
+ let start = self.offset;
+ let end = self.offset + self.limit();
+ let end = end.min(self.sessions.len());
+ &self.sessions[start..end]
+ }
+
+ pub fn visible_sessions_with_chars(
+ &self,
+ ) -> impl Iterator<Item = (char, &crate::protocol::Session)> {
+ self.visible_sessions()
+ .iter()
+ .enumerate()
+ .map(move |(i, s)| (self.idx_to_char(i).unwrap(), s))
+ }
+
+ pub fn size(&self) -> crate::term::Size {
+ self.size
+ }
+
+ pub fn resize(&mut self, size: crate::term::Size) {
+ self.size = size;
+ }
+
+ pub fn id_for(&self, c: char) -> Option<&str> {
+ self.char_to_idx(c).and_then(|i| {
+ self.sessions.get(i + self.offset).map(|s| s.id.as_ref())
+ })
+ }
+
+ pub fn next_page(&mut self) {
+ let inc = self.limit();
+ if self.offset + inc < self.sessions.len() {
+ self.offset += inc;
+ }
+ }
+
+ pub fn prev_page(&mut self) {
+ let dec = self.limit();
+ if self.offset >= dec {
+ self.offset -= dec;
+ }
+ }
+
+ pub fn current_page(&self) -> usize {
+ self.offset / self.limit() + 1
+ }
+
+ pub fn total_pages(&self) -> usize {
+ if self.sessions.is_empty() {
+ 1
+ } else {
+ (self.sessions.len() - 1) / self.limit() + 1
+ }
+ }
+
+ fn idx_to_char(&self, mut i: usize) -> Option<char> {
+ if i >= self.limit() {
+ return None;
+ }
+
+ // 'q' shouldn't be a list option, since it is bound to quit
+ if i >= 16 {
+ i += 1;
+ }
+
+ #[allow(clippy::cast_possible_truncation)]
+ Some(std::char::from_u32(('a' as u32) + (i as u32)).unwrap())
+ }
+
+ fn char_to_idx(&self, c: char) -> Option<usize> {
+ if c == 'q' {
+ return None;
+ }
+
+ let i = ((c as i32) - ('a' as i32)) as isize;
+ if i < 0 {
+ return None;
+ }
+ #[allow(clippy::cast_sign_loss)]
+ let mut i = i as usize;
+
+ // 'q' shouldn't be a list option, since it is bound to quit
+ if i > 16 {
+ i -= 1;
+ }
+
+ if i >= self.limit() {
+ return None;
+ }
+
+ Some(i)
+ }
+
+ fn limit(&self) -> usize {
+ let limit = self.size.rows as usize - 6;
+
+ // enough for a-z except q - if we want to allow more than this, we'll
+ // need to come up with a better way of choosing streams
+ if limit > 25 {
+ 25
+ } else {
+ limit
+ }
+ }
+}
+
+#[cfg(test)]
+#[allow(clippy::cognitive_complexity)]
+#[allow(clippy::redundant_clone)]
+#[allow(clippy::shadow_unrelated)]
+mod test {
+ use super::*;
+
+ fn session(username: &str, idle: u32) -> crate::protocol::Session {
+ crate::protocol::Session {
+ id: format!("{}", uuid::Uuid::new_v4()),
+ username: username.to_string(),
+ term_type: "screen".to_string(),
+ size: crate::term::Size { rows: 24, cols: 80 },
+ idle_time: idle,
+ title: "title".to_string(),
+ watchers: 0,
+ }
+ }
+
+ #[test]
+ fn test_session_list_sorting() {
+ let size = crate::term::Size { rows: 24, cols: 80 };
+
+ let session1 = session("doy", 35);
+ let session2 = session("doy", 3);
+ let mut session3 = session("sartak", 12);
+ let session4 = session("sartak", 100);
+ let mut session5 = session("toft", 5);
+ let mut sessions = vec![
+ session1.clone(),
+ session2.clone(),
+ session3.clone(),
+ session4.clone(),
+ session5.clone(),
+ ];
+
+ assert_eq!(
+ SessionList::new(sessions.clone(), size.clone()).sessions,
+ vec![
+ session2.clone(),
+ session1.clone(),
+ session5.clone(),
+ session3.clone(),
+ session4.clone(),
+ ]
+ );
+
+ session3.idle_time = 2;
+ sessions[2].idle_time = 2;
+ assert_eq!(
+ SessionList::new(sessions.clone(), size.clone()).sessions,
+ vec![
+ session3.clone(),
+ session4.clone(),
+ session2.clone(),
+ session1.clone(),
+ session5.clone(),
+ ]
+ );
+
+ session5.idle_time = 1;
+ sessions[4].idle_time = 1;
+ assert_eq!(
+ SessionList::new(sessions.clone(), size.clone()).sessions,
+ vec![
+ session5.clone(),
+ session3.clone(),
+ session4.clone(),
+ session2.clone(),
+ session1.clone(),
+ ]
+ );
+ }
+
+ #[test]
+ fn test_session_list_pagination() {
+ let size = crate::term::Size { rows: 11, cols: 80 };
+ let sessions = vec![
+ session("doy", 0),
+ session("doy", 1),
+ session("doy", 2),
+ session("doy", 3),
+ session("doy", 4),
+ session("doy", 5),
+ session("doy", 6),
+ session("doy", 7),
+ session("doy", 8),
+ session("doy", 9),
+ session("doy", 10),
+ ];
+ let mut list = SessionList::new(sessions.clone(), size);
+ assert_eq!(list.limit(), 5);
+ assert_eq!(list.total_pages(), 3);
+ assert_eq!(list.current_page(), 1);
+
+ list.next_page();
+ assert_eq!(list.limit(), 5);
+ assert_eq!(list.total_pages(), 3);
+ assert_eq!(list.current_page(), 2);
+
+ list.next_page();
+ assert_eq!(list.limit(), 5);
+ assert_eq!(list.total_pages(), 3);
+ assert_eq!(list.current_page(), 3);
+
+ list.next_page();
+ assert_eq!(list.limit(), 5);
+ assert_eq!(list.total_pages(), 3);
+ assert_eq!(list.current_page(), 3);
+
+ list.prev_page();
+ assert_eq!(list.limit(), 5);
+ assert_eq!(list.total_pages(), 3);
+ assert_eq!(list.current_page(), 2);
+
+ list.prev_page();
+ assert_eq!(list.limit(), 5);
+ assert_eq!(list.total_pages(), 3);
+ assert_eq!(list.current_page(), 1);
+
+ list.prev_page();
+ assert_eq!(list.limit(), 5);
+ assert_eq!(list.total_pages(), 3);
+ assert_eq!(list.current_page(), 1);
+
+ let id = list.id_for('a').unwrap();
+ assert_eq!(id, sessions[0].id);
+ let id = list.id_for('e').unwrap();
+ assert_eq!(id, sessions[4].id);
+ let id = list.id_for('f');
+ assert!(id.is_none());
+
+ list.next_page();
+ let id = list.id_for('a').unwrap();
+ assert_eq!(id, sessions[5].id);
+
+ list.next_page();
+ let id = list.id_for('a').unwrap();
+ assert_eq!(id, sessions[10].id);
+ let id = list.id_for('b');
+ assert!(id.is_none());
+
+ let size = crate::term::Size { rows: 24, cols: 80 };
+ let sessions = vec![
+ session("doy", 0),
+ session("doy", 1),
+ session("doy", 2),
+ session("doy", 3),
+ session("doy", 4),
+ session("doy", 5),
+ session("doy", 6),
+ session("doy", 7),
+ session("doy", 8),
+ session("doy", 9),
+ session("doy", 10),
+ session("doy", 11),
+ session("doy", 12),
+ session("doy", 13),
+ session("doy", 14),
+ session("doy", 15),
+ session("doy", 16),
+ session("doy", 17),
+ session("doy", 18),
+ session("doy", 19),
+ session("doy", 20),
+ session("doy", 21),
+ ];
+ let list = SessionList::new(sessions.clone(), size);
+ assert_eq!(list.limit(), 18);
+ assert_eq!(list.total_pages(), 2);
+ assert_eq!(list.current_page(), 1);
+
+ let id = list.id_for('a').unwrap();
+ assert_eq!(id, sessions[0].id);
+ let id = list.id_for('p').unwrap();
+ assert_eq!(id, sessions[15].id);
+ let id = list.id_for('q');
+ assert!(id.is_none());
+ let id = list.id_for('r').unwrap();
+ assert_eq!(id, sessions[16].id);
+ let id = list.id_for('s').unwrap();
+ assert_eq!(id, sessions[17].id);
+ let id = list.id_for('t');
+ assert!(id.is_none());
+ }
+}