1use log::error;
2use ratatui::{
3 layout::Rect,
4 text::Line,
5 widgets::{Paragraph, Wrap},
6};
7
8use crate::{
9 config::{BorderSetting, PreviewConfig, PreviewSetting, ShowCondition, Side},
10 preview::Preview,
11 utils::text::wrapped_line_height,
12};
13
14#[derive(Debug)]
15pub struct PreviewUI {
16 pub view: Preview,
17 pub config: PreviewConfig,
18 layout_idx: usize,
19 pub(crate) area: Rect,
21 pub scroll: [u16; 2],
22 offset: usize,
23 target: Option<usize>,
24 attained_target: bool,
25
26 show: bool,
27}
28
29impl PreviewUI {
30 pub fn new(view: Preview, mut config: PreviewConfig, [ui_width, ui_height]: [u16; 2]) -> Self {
31 for x in &mut config.layout {
32 if let Some(b) = &mut x.border
33 && b.sides.is_none()
34 && !b.is_empty()
35 {
36 b.sides = Some(x.layout.side.opposite())
37 }
38 }
39
40 let show = match config.show {
41 ShowCondition::Free(x) => {
42 if let Some(l) = config.layout.first() {
43 match l.layout.side {
44 Side::Bottom | Side::Top => ui_height >= x,
45 _ => ui_width >= x,
46 }
47 } else {
48 false
49 }
50 }
51 ShowCondition::Bool(x) => x,
52 };
53
54 if config.layout.is_empty() {
56 let mut s = PreviewSetting::default();
57 s.layout.max = 0;
58 config.layout.push(s);
59 }
60
61 Self {
62 view,
63 config,
64 layout_idx: 0,
65 scroll: Default::default(),
66 offset: 0,
67 area: Rect::default(),
68 target: None,
69 attained_target: false,
70 show,
71 }
72 }
73
74 pub fn update_dimensions(&mut self, area: &Rect) {
75 let mut height = area.height;
76 height -= self.config.border.height().min(height);
77 self.area.height = height;
78
79 let mut width = area.width;
80 width -= self.config.border.width().min(width);
81 self.area.width = width;
82 }
83
84 pub fn reevaluate_show_condition(&mut self, [ui_width, ui_height]: [u16; 2], hide: bool) {
85 match self.config.show {
86 ShowCondition::Free(x) => {
87 if let Some(setting) = self.setting() {
88 let l = &setting.layout;
89
90 let show = match l.side {
91 Side::Bottom | Side::Top => ui_height >= x,
92 _ => ui_width >= x,
93 };
94 log::debug!(
95 "Evaluated ShowCondition(Free({x})) against {ui_width}x{ui_height} => {show}"
96 );
97 if !hide && !show {
98 return;
99 }
100
101 self.show(show);
102 };
103 }
104 ShowCondition::Bool(show) => {
105 if !hide && !show {
106 return;
107 }
108 self.show(show);
109 }
110 };
111 }
112
113 pub fn setting(&self) -> Option<&PreviewSetting> {
116 if let ret = &self.config.layout[self.layout_idx]
118 && ret.layout.max != 0
119 {
120 Some(&ret)
121 } else {
122 None
123 }
124 }
125
126 pub fn visible(&self) -> bool {
127 self.setting().is_some() && self.show
128 }
129
130 pub fn command(&self) -> &str {
131 self.setting().map(|x| x.command.as_str()).unwrap_or("")
132 }
133
134 pub fn border(&self) -> &BorderSetting {
135 self.setting()
136 .and_then(|s| s.border.as_ref())
137 .unwrap_or(&self.config.border)
138 }
139
140 pub fn get_initial_command(&self) -> &str {
141 let x = self.command();
142 if !x.is_empty() {
143 return x;
144 }
145
146 self.config
147 .layout
148 .iter()
149 .map(|l| l.command.as_str())
150 .find(|cmd| !cmd.is_empty())
151 .unwrap_or("")
152 }
153
154 pub fn cycle_layout(&mut self) {
156 self.layout_idx = (self.layout_idx + 1) % self.config.layout.len()
157 }
158 pub fn set_layout(&mut self, idx: u8) -> bool {
159 let idx = idx as usize;
160 if idx < self.config.layout.len() {
161 let changed = self.layout_idx != idx;
162 self.layout_idx = idx;
163 changed
164 } else {
165 error!("Layout idx {idx} out of bounds, ignoring.");
166 false
167 }
168 }
169
170 pub fn show(&mut self, show: bool) -> bool {
173 log::trace!("toggle preview with: {show}");
174 let changed = self.show != show;
175 self.show = show;
176 changed
177 }
178
179 pub fn toggle_show(&mut self) {
180 self.show = !self.show;
181 }
182
183 pub fn wrap(&mut self, wrap: bool) {
184 self.config.wrap = wrap;
185 }
186 pub fn is_wrap(&self) -> bool {
187 self.config.wrap
188 }
189 pub fn offset(&self) -> usize {
190 self.config.initial.header_lines + self.offset
191 }
192 pub fn target_line(&self) -> Option<usize> {
193 self.target
194 }
195
196 pub fn up(&mut self, n: u16) {
198 let total_lines = self.view.len();
199 let n = n as usize;
200
201 if self.offset >= n {
202 self.offset -= n;
203 } else if self.config.scroll_wrap {
204 self.offset = total_lines.saturating_sub(n - self.offset);
205 } else {
206 self.offset = 0;
207 }
208 }
209 pub fn down(&mut self, n: u16) {
210 let total_lines = self.view.len();
211 let n = n as usize;
212
213 if self.offset + n > total_lines {
214 if self.config.scroll_wrap {
215 self.offset = 0;
216 } else {
217 self.offset = total_lines;
218 }
219 } else {
220 self.offset += n;
221 }
222 }
223
224 pub fn scroll(&mut self, horizontal: bool, val: i8) {
225 let a = &mut self.scroll[horizontal as usize];
226
227 if val == 0 {
228 *a = 0;
229 } else {
230 let new = (*a as i8 + val).clamp(0, u16::MAX as i8);
231 *a = new as u16;
232 }
233 }
234
235 pub fn set_target(&mut self, target: Option<isize>) {
236 let results = self.view.results().lines;
237 let line_count = results.len();
238
239 let Some(mut target) = target else {
240 self.target = None;
241 self.offset = 0;
242 return;
243 };
244
245 target += self.config.initial.offset;
246
247 self.target = Some(if target < 0 {
248 line_count.saturating_sub(target.unsigned_abs())
249 } else {
250 target as usize
251 });
252
253 let index = self.target.unwrap();
254
255 self.offset = if index >= results.len() {
256 self.attained_target = false;
257 results.len().saturating_sub(self.area.height as usize / 2)
258 } else {
259 self.attained_target = true;
260 self.target_to_offset(index, &results)
261 };
262
263 log::trace!("Preview initial offset: {}, index: {}", self.offset, index);
264 }
265
266 fn target_to_offset(&self, mut target: usize, results: &Vec<Line>) -> usize {
267 let mut lines_above =
270 self.config
271 .initial
272 .percentage
273 .complement()
274 .compute_clamped(self.area.height, 0, 0);
275
276 while target > 0 && lines_above > 0 {
278 let prev = results
279 .get(target)
280 .map(|x| wrapped_line_height(x, self.area.width))
281 .unwrap_or(1);
282 if prev > lines_above {
283 break;
284 } else {
285 target -= 1;
286 lines_above -= prev;
287 }
288 }
289
290 target
291 }
292 pub fn make_preview(&mut self) -> Paragraph<'_> {
295 let results = self.view.results();
296 let rl = results.lines.len();
297 let height = self.area.height as usize;
298
299 if let Some(target) = self.target
300 && !self.attained_target
301 && target < rl
302 {
303 self.offset = self.target_to_offset(target, &results.lines);
304 self.attained_target = true;
305 };
306
307 let mut results = results.into_iter();
308
309 if height == 0 {
310 return Paragraph::new(Vec::new());
311 }
312
313 let mut lines = Vec::with_capacity(height);
314
315 for _ in 0..self.config.initial.header_lines.min(height) {
316 if let Some(line) = results.next() {
317 lines.push(line);
318 } else {
319 break;
320 };
321 }
322
323 let mut results = results.skip(self.offset);
324
325 for _ in self.config.initial.header_lines..height {
326 if let Some(line) = results.next() {
327 lines.push(line);
328 }
329 }
330
331 let mut preview = Paragraph::new(lines);
332 preview = preview.block(self.border().as_block());
333 if self.config.wrap {
334 preview = preview
335 .wrap(Wrap { trim: false })
336 .scroll(self.scroll.into());
337 }
338 preview
339 }
340}