artifactize_core/monitor/
session.rs1use crate::agent::session::{
4 document::{Anchor, Mode, Move, Position, Scroll},
5 live::{Reader, Source, Window},
6};
7
8pub struct Live {
9 pub serial: u64,
10 pub source: Source,
11 pub reader: Option<Reader>,
12 pub scroll: Scroll,
13 pub window: Window,
14 width: usize,
15 height: usize,
16 anchor: Anchor,
17 position: Position,
18 revision: u64,
19 rendered_width: usize,
20 dirty: bool,
21 expanded: std::collections::BTreeSet<crate::agent::session::transcript::BlockId>,
22}
23
24pub struct Job {
25 pub serial: u64,
26 pub revision: u64,
27 pub width: usize,
28 pub height: usize,
29 pub position: Position,
30 pub reader: Reader,
31 pub expanded: std::collections::BTreeSet<crate::agent::session::transcript::BlockId>,
32}
33impl Live {
34 pub fn new(serial: u64, source: Source) -> Self {
35 Self {
36 serial,
37 source: source.clone(),
38 reader: Some(Reader::new(source)),
39 scroll: Scroll::default(),
40 window: Window::default(),
41 width: 0,
42 height: 0,
43 anchor: Anchor::default(),
44 position: Position::Bottom,
45 revision: 0,
46 rendered_width: 0,
47 dirty: true,
48 expanded: std::collections::BTreeSet::new(),
49 }
50 }
51 pub fn geometry(&mut self, width: usize, height: usize) {
52 if self.width != width || self.height != height {
53 self.width = width;
54 self.height = height;
55 self.revision += 1;
58 self.dirty = true;
59 }
60 }
61 pub fn invalidate(&mut self) {
62 self.dirty = true;
63 }
64
65 pub fn refresh(&mut self, source: &Source) -> bool {
69 if self.source.reference != source.reference {
70 return false;
71 }
72 self.source = source.clone();
73 if let Some(reader) = &mut self.reader {
74 reader.source = source.clone();
75 }
76 self.invalidate();
77 true
78 }
79 #[cfg(any(test, feature = "test-support"))]
80 pub fn expanded(&self, id: crate::agent::session::transcript::BlockId) -> bool {
81 self.expanded.contains(&id)
82 }
83 pub fn toggle(&mut self, id: crate::agent::session::transcript::BlockId) {
84 if !self.expanded.remove(&id) {
85 self.expanded.insert(id);
86 }
87 self.revision += 1;
88 self.dirty = true;
89 }
90 pub fn toggle_visible(&mut self) {
91 if let Some((_, id)) = self.window.groups.first().copied() {
92 self.toggle(id);
93 }
94 }
95 pub fn movement(&mut self, movement: Move) {
96 let before = self.scroll.top;
97 self.scroll.apply(movement);
98 let delta = self.scroll.top as isize - before as isize;
99 self.position = match movement {
100 Move::Bottom => Position::Bottom,
101 Move::Top => Position::Anchor(Anchor::default()),
102 Move::Up(_) | Move::Down(_) if self.scroll.mode == Mode::Following => Position::Bottom,
103 Move::Up(_) | Move::Down(_) => match self.position {
104 Position::Anchor(anchor) => Position::Relative {
105 anchor,
106 rows: delta,
107 width: self.rendered_width.max(1),
108 },
109 Position::Relative {
110 anchor,
111 rows,
112 width,
113 } => Position::Relative {
114 anchor,
115 rows: rows.saturating_add(delta),
116 width,
117 },
118 _ => Position::Relative {
119 anchor: self.anchor,
120 rows: self.scroll.top as isize - self.window.top as isize,
121 width: self.rendered_width.max(1),
122 },
123 },
124 };
125 self.revision += 1;
126 self.dirty = true;
127 }
128 pub fn job(&mut self) -> Option<Job> {
129 if !self.dirty || self.width == 0 || self.height == 0 {
130 return None;
131 }
132 let reader = self.reader.take()?;
133 self.dirty = false;
134 Some(Job {
135 serial: self.serial,
136 revision: self.revision,
137 width: self.width,
138 height: self.height,
139 position: self.position,
140 reader,
141 expanded: self.expanded.clone(),
142 })
143 }
144 pub fn finish(&mut self, job: Job, window: Window) {
145 let mut reader = job.reader;
146 reader.source = self.source.clone();
147 self.reader = Some(reader);
148 if window.reset {
150 self.scroll = Scroll::default();
151 self.position = Position::Bottom;
152 self.dirty = true;
153 }
154 if self.revision != job.revision {
155 self.dirty = true;
156 return;
157 }
158 if window.loading && window.rows.is_empty() && window.status.is_none() {
159 self.window.loading = true;
161 self.position = window.position;
162 } else {
163 self.scroll
164 .update(window.total, self.height, Some(window.top));
165 self.anchor = window.anchor;
166 self.rendered_width = job.width;
167 if let Some(reader) = &mut self.reader {
168 reader.commit(job.width);
169 }
170 self.position = match self.scroll.mode {
171 Mode::Following => Position::Bottom,
172 Mode::Paused => Position::Anchor(self.anchor),
173 };
174 self.window = window;
175 }
176 self.dirty |= self.window.loading;
177 }
178 pub fn indicator(&self) -> String {
179 let position = if self.scroll.total == 0 {
180 "0/0".into()
181 } else {
182 format!("{}/{}", self.scroll.top + 1, self.scroll.total)
183 };
184 format!(
185 "{position} {} {}{}",
186 if self.scroll.at_bottom() {
187 "bottom"
188 } else {
189 "history"
190 },
191 if self.scroll.mode == Mode::Following {
192 "following"
193 } else {
194 "paused"
195 },
196 if self.window.loading {
197 " · indexing…"
198 } else {
199 ""
200 }
201 )
202 }
203}