1use log::error;
2use ratatui::{
3 layout::Rect,
4 widgets::{Paragraph, Wrap},
5};
6
7use crate::{
8 config::{BorderSetting, 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, mut config: PreviewConfig) -> Self {
27 for x in &mut config.layout {
29 if let Some(b) = &mut x.border
30 && b.sides.is_none()
31 {
32 b.sides = Some(x.layout.side.opposite())
33 }
34 }
35
36 Self {
37 view,
38 config,
39 layout_idx: 0,
40 scroll: Default::default(),
41 offset: 0,
42 area: Rect::default(),
43 target: None,
44 }
45 }
46 pub fn update_dimensions(&mut self, area: &Rect) {
47 let mut height = area.height;
48 height -= self.config.border.height().min(height);
49 self.area.height = height;
50
51 let mut width = area.width;
52 width -= self.config.border.width().min(width);
53 self.area.width = width;
54 }
55
56 pub fn layout(&self) -> Option<&PreviewLayout> {
59 if !self.config.show || self.config.layout.is_empty() {
60 None
61 } else {
62 let ret = &self.config.layout[self.layout_idx].layout;
63 if ret.max == 0 { None } else { Some(ret) }
64 }
65 }
66 pub fn command(&self) -> &str {
67 if self.config.layout.is_empty() {
68 ""
69 } else {
70 self.config.layout[self.layout_idx].command.as_str()
71 }
72 }
73
74 pub fn border(&self) -> &BorderSetting {
75 self.config.layout[self.layout_idx]
76 .border
77 .as_ref()
78 .unwrap_or(&self.config.border)
79 }
80
81 pub fn get_initial_command(&self) -> &str {
82 if let Some(current) = self.config.layout.get(self.layout_idx) {
83 if !current.command.is_empty() {
84 return current.command.as_str();
85 }
86 }
87
88 self.config
89 .layout
90 .iter()
91 .map(|l| l.command.as_str())
92 .find(|cmd| !cmd.is_empty())
93 .unwrap_or("")
94 }
95
96 pub fn cycle_layout(&mut self) {
97 self.layout_idx = (self.layout_idx + 1) % self.config.layout.len()
98 }
99 pub fn set_layout(&mut self, idx: u8) -> bool {
100 let idx = idx as usize;
101 if idx < self.config.layout.len() {
102 let changed = self.layout_idx != idx;
103 self.layout_idx = idx;
104 changed
105 } else {
106 error!("Layout idx {idx} out of bounds, ignoring.");
107 false
108 }
109 }
110
111 pub fn is_show(&self) -> bool {
113 self.layout().is_some()
114 }
115 pub fn show(&mut self, show: bool) -> bool {
117 let previous = self.config.show;
118 self.config.show = show;
119 previous != show
120 }
121 pub fn toggle_show(&mut self) {
122 self.config.show = !self.config.show;
123 }
124
125 pub fn wrap(&mut self, wrap: bool) {
126 self.config.wrap = wrap;
127 }
128 pub fn is_wrap(&self) -> bool {
129 self.config.wrap
130 }
131
132 pub fn up(&mut self, n: u16) {
134 if self.offset >= n {
135 self.offset -= n;
136 } else if self.config.scroll_wrap {
137 let total_lines = self.view.len() as u16;
138 self.offset = total_lines.saturating_sub(n - self.offset);
139 } else {
140 self.offset = 0;
141 }
142 }
143 pub fn down(&mut self, n: u16) {
144 let total_lines = self.view.len() as u16;
145
146 if self.offset + n > total_lines {
147 if self.config.scroll_wrap {
148 self.offset = 0;
149 } else {
150 self.offset = total_lines;
151 }
152 } else {
153 self.offset += n;
154 }
155 }
156
157 pub fn scroll(&mut self, horizontal: bool, val: i8) {
158 let a = &mut self.scroll[horizontal as usize];
159
160 if val == 0 {
161 *a = 0;
162 } else {
163 let new = (*a as i8 + val).clamp(0, u16::MAX as i8);
164 *a = new as u16;
165 }
166 }
167
168 pub fn set_target(&mut self, mut target: isize) {
169 let results = self.view.results().lines;
170 let line_count = results.len();
171
172 target += self.config.scroll.offset;
173 self.target = Some(if target < 0 {
174 line_count.saturating_sub(target.unsigned_abs())
175 } else {
176 line_count.saturating_sub(1).min(target.unsigned_abs())
177 });
178 let mut index = self.target.unwrap();
179
180 let mut lines_above =
183 self.config
184 .scroll
185 .percentage
186 .complement()
187 .compute_clamped(self.area.height, 0, 0);
188 while index > 0 && lines_above > 0 {
190 let prev = wrapped_line_height(&results[index], self.area.width);
191 if prev > lines_above {
192 break;
193 } else {
194 index -= 1;
195 lines_above -= prev;
196 }
197 }
198 self.offset = u16::try_from(index).unwrap_or(u16::MAX);
199 log::trace!("offset: {}, index: {}", self.offset, self.target.unwrap());
200 }
201
202 pub fn make_preview(&self) -> Paragraph<'_> {
205 assert!(self.is_show());
206
207 let mut results = self.view.results().into_iter();
208 let height = self.area.height as usize;
209 if height == 0 {
210 return Paragraph::new(Vec::new());
211 }
212
213 let mut lines = Vec::with_capacity(height);
214
215 for _ in 0..self.config.scroll.header_lines.min(height) {
216 if let Some(line) = results.next() {
217 lines.push(line);
218 } else {
219 break;
220 };
221 }
222 let mut results = results.skip(self.offset as usize);
223 for _ in self.config.scroll.header_lines..height {
224 if let Some(line) = results.next() {
225 lines.push(line);
226 }
227 }
228
229 let mut preview = Paragraph::new(lines);
230 preview = preview.block(self.border().as_block());
231 if self.config.wrap {
232 preview = preview.wrap(Wrap { trim: true }).scroll(self.scroll.into());
233 }
234 preview
235 }
236}