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 log::debug!("{index}");
145
146 let results = self.view.results().lines;
149 let mut lines_above =
150 self.config
151 .scroll
152 .percentage
153 .complement()
154 .compute_clamped(self.area.height, 0, 0);
155 while index > 0 && lines_above > 0 {
157 let prev = wrapped_line_height(&results[index], self.area.width);
158 if prev > lines_above {
159 break;
160 } else {
161 index -= 1;
162 lines_above -= prev;
163 }
164 }
165 self.offset = index as u16;
166 log::debug!("{}", self.offset);
167 }
168
169 pub fn make_preview(&self) -> Paragraph<'_> {
172 let mut results = self.view.results().into_iter();
173 let height = self.area.height as usize;
174 if height == 0 {
175 return Paragraph::new(Vec::new());
176 }
177
178 let mut lines = Vec::with_capacity(height);
179
180 for _ in 0..self.config.scroll.header_lines.min(height) {
181 if let Some(line) = results.next() {
182 lines.push(line);
183 } else {
184 break;
185 };
186 }
187 let mut results = results.skip(self.offset as usize);
188 for _ in self.config.scroll.header_lines..height {
189 if let Some(line) = results.next() {
190 lines.push(line);
191 }
192 }
193
194 let mut preview = Paragraph::new(lines);
195 preview = preview.block(self.config.border.as_block());
196 if self.config.wrap {
197 preview = preview.wrap(Wrap { trim: true }).scroll(self.scroll.into());
198 }
199 preview
200 }
201}