1use log::error;
2use ratatui::{
3 layout::Rect,
4 widgets::{Paragraph, Wrap},
5};
6
7use crate::{
8 config::{PreviewConfig, PreviewLayout},
9 preview::Preview,
10 utils::text::wrapped_line_height,
11};
12
13#[derive(Debug)]
14pub struct PreviewUI {
15 pub view: Preview,
16 pub config: PreviewConfig,
17 pub layout_idx: usize,
18 pub area: Rect,
20 pub scroll: [u16; 2],
21 offset: u16,
22 target: Option<usize>,
23}
24
25impl PreviewUI {
26 pub fn new(view: Preview, config: PreviewConfig) -> Self {
27 Self {
28 view,
29 config,
30 layout_idx: 0,
31 scroll: Default::default(),
32 offset: 0,
33 area: Rect::default(),
34 target: None,
35 }
36 }
37 pub fn update_dimensions(&mut self, area: &Rect) {
38 let mut height = area.height;
39 height -= self.config.border.height().min(height);
40 self.area.height = height;
41
42 let mut width = area.width;
43 width -= self.config.border.width().min(width);
44 self.area.width = width;
45 }
46
47 pub fn layout(&self) -> Option<&PreviewLayout> {
50 if !self.config.show || self.config.layout.is_empty() {
51 None
52 } else {
53 let ret = &self.config.layout[self.layout_idx].layout;
54 if ret.max == 0 { None } else { Some(ret) }
55 }
56 }
57 pub fn command(&self) -> &str {
58 if self.config.layout.is_empty() {
59 ""
60 } else {
61 self.config.layout[self.layout_idx].command.as_str()
62 }
63 }
64 pub fn cycle_layout(&mut self) {
65 self.layout_idx = (self.layout_idx + 1) % self.config.layout.len()
66 }
67 pub fn set_layout(&mut self, idx: u8) -> bool {
68 let idx = idx as usize;
69 if idx <= self.config.layout.len() {
70 let changed = self.layout_idx != idx;
71 self.layout_idx = idx;
72 changed
73 } else {
74 error!("Layout idx {idx} out of bounds, ignoring.");
75 false
76 }
77 }
78
79 pub fn is_show(&self) -> bool {
81 self.layout().is_some()
82 }
83 pub fn show(&mut self, show: bool) -> bool {
85 let previous = self.config.show;
86 self.config.show = show;
87 previous != show
88 }
89 pub fn toggle_show(&mut self) {
90 self.config.show = !self.config.show;
91 }
92
93 pub fn wrap(&mut self, wrap: bool) {
94 self.config.wrap = wrap;
95 }
96 pub fn is_wrap(&self) -> bool {
97 self.config.wrap
98 }
99
100 pub fn up(&mut self, n: u16) {
102 if self.offset >= n {
103 self.offset -= n;
104 } else if self.config.scroll_wrap {
105 let total_lines = self.view.len() as u16;
106 self.offset = total_lines.saturating_sub(n - self.offset);
107 } else {
108 self.offset = 0;
109 }
110 }
111 pub fn down(&mut self, n: u16) {
112 let total_lines = self.view.len() as u16;
113
114 if self.offset + n > total_lines {
115 if self.config.scroll_wrap {
116 self.offset = 0;
117 } else {
118 self.offset = total_lines;
119 }
120 } else {
121 self.offset += n;
122 }
123 }
124
125 pub fn scroll(&mut self, horizontal: bool, val: i8) {
126 let a = &mut self.scroll[horizontal as usize];
127
128 if val == 0 {
129 *a = 0;
130 } else {
131 let new = (*a as i8 + val).clamp(0, u16::MAX as i8);
132 *a = new as u16;
133 }
134 }
135
136 pub fn set_target(&mut self, mut target: isize) {
137 target += self.config.scroll.offset;
138 self.target = Some(if target < 0 {
139 self.view.len().saturating_sub(target.unsigned_abs())
140 } else {
141 self.view.len().min(target.unsigned_abs())
142 });
143 let mut index = self.target.unwrap();
144
145 let results = self.view.results().lines;
148 let mut lines_above =
149 self.config
150 .scroll
151 .percentage
152 .complement()
153 .compute_clamped(self.area.height, 0, 0);
154 while index > 0 && lines_above > 0 {
156 let prev = wrapped_line_height(&results[index], self.area.width);
157 if prev > lines_above {
158 break;
159 } else {
160 index -= 1;
161 lines_above -= prev;
162 }
163 }
164 self.offset = index as u16;
165 log::trace!("offset: {}, index: {}", self.offset, self.target.unwrap());
166 }
167
168 pub fn make_preview(&self) -> Paragraph<'_> {
171 let mut results = self.view.results().into_iter();
172 let height = self.area.height as usize;
173 if height == 0 {
174 return Paragraph::new(Vec::new());
175 }
176
177 let mut lines = Vec::with_capacity(height);
178
179 for _ in 0..self.config.scroll.header_lines.min(height) {
180 if let Some(line) = results.next() {
181 lines.push(line);
182 } else {
183 break;
184 };
185 }
186 let mut results = results.skip(self.offset as usize);
187 for _ in self.config.scroll.header_lines..height {
188 if let Some(line) = results.next() {
189 lines.push(line);
190 }
191 }
192
193 let mut preview = Paragraph::new(lines);
194 preview = preview.block(self.config.border.as_block());
195 if self.config.wrap {
196 preview = preview.wrap(Wrap { trim: true }).scroll(self.scroll.into());
197 }
198 preview
199 }
200}