1extern crate syntect;
63extern crate unsegen;
64
65mod decorating;
66mod highlighting;
67
68pub use decorating::*;
69pub use highlighting::*;
70
71pub use syntect::highlighting::{Theme, ThemeSet};
72pub use syntect::parsing::{SyntaxDefinition, SyntaxSet};
73
74use unsegen::base::{
75 basic_types::*, BoolModifyMode, Cursor, GraphemeCluster, StyleModifier, Window, WrappingMode,
76};
77use unsegen::input::{OperationResult, Scrollable};
78use unsegen::widget::{layout_linearly, Demand, Demand2D, RenderingHints, Widget};
79
80use std::cmp::{max, min};
81use std::ops::{Bound, RangeBounds};
82
83pub struct Pager<L, D = NoDecorator<L>>
92where
93 L: PagerLine,
94 D: LineDecorator,
95{
96 content: Option<PagerContent<L, D>>,
97 current_line: LineIndex,
98}
99
100impl<L, D> Default for Pager<L, D>
101where
102 L: PagerLine,
103 D: LineDecorator<Line = L>,
104{
105 fn default() -> Self {
106 Pager {
107 content: None,
108 current_line: LineIndex::new(0),
109 }
110 }
111}
112
113impl<L, D> Pager<L, D>
114where
115 L: PagerLine,
116 D: LineDecorator<Line = L>,
117{
118 pub fn new() -> Self {
120 Pager {
121 content: None,
122 current_line: LineIndex::new(0),
123 }
124 }
125
126 pub fn load(&mut self, content: PagerContent<L, D>) {
130 self.content = Some(content);
131
132 let current_line = self.current_line;
134 if !self.line_exists(current_line) {
135 let _ = self.scroll_to_end();
136 }
137 }
138
139 pub fn clear_content(&mut self) {
143 self.content = None;
144 }
145
146 pub fn content(&self) -> Option<&PagerContent<L, D>> {
148 self.content.as_ref()
149 }
150
151 pub fn content_mut(&mut self) -> Option<&mut PagerContent<L, D>> {
156 self.content.as_mut()
157 }
158
159 fn line_exists<I: Into<LineIndex>>(&mut self, line: I) -> bool {
160 let line: LineIndex = line.into();
161 if let Some(ref mut content) = self.content {
162 line.raw_value() < content.storage.len()
163 } else {
164 false
165 }
166 }
167
168 pub fn go_to_line<I: Into<LineIndex>>(&mut self, line: I) -> Result<(), PagerError> {
172 let line: LineIndex = line.into();
173 if self.line_exists(line) {
174 self.current_line = line;
175 Ok(())
176 } else {
177 Err(PagerError::NoLineWithIndex(line))
178 }
179 }
180
181 pub fn go_to_line_if<F: Fn(LineIndex, &L) -> bool>(
185 &mut self,
186 predicate: F,
187 ) -> Result<(), PagerError> {
188 let line = if let Some(ref mut content) = self.content {
189 content
190 .view(LineIndex::new(0)..)
191 .find(|&(index, ref line)| predicate(index, line))
192 .map(|(index, _)| index)
193 .ok_or(PagerError::NoLineWithPredicate)
194 } else {
195 Err(PagerError::NoContent)
196 };
197 line.and_then(|index| self.go_to_line(index))
198 }
199
200 pub fn current_line_index(&self) -> LineIndex {
202 self.current_line
203 }
204
205 pub fn current_line(&self) -> Option<&L> {
207 if let Some(ref content) = self.content {
208 content.storage.get(self.current_line_index().raw_value())
209 } else {
210 None
211 }
212 }
213
214 pub fn as_widget<'a>(&'a self) -> impl Widget + 'a {
215 PagerWidget { inner: self }
216 }
217}
218
219struct PagerWidget<'a, L, D>
220where
221 L: PagerLine,
222 D: LineDecorator<Line = L>,
223{
224 inner: &'a Pager<L, D>,
225}
226
227impl<'a, L, D> Widget for PagerWidget<'a, L, D>
228where
229 L: PagerLine,
230 D: LineDecorator<Line = L>,
231{
232 fn space_demand(&self) -> Demand2D {
233 Demand2D {
234 width: Demand::at_least(1),
235 height: Demand::at_least(1),
236 }
237 }
238 fn draw(&self, window: Window, _: RenderingHints) {
239 if let Some(ref content) = self.inner.content {
240 let height: Height = window.get_height();
241 let min_highlight_context = 40;
244 let num_adjacent_lines_to_load = max(height.into(), min_highlight_context / 2);
245 let min_line = self
246 .inner
247 .current_line
248 .checked_sub(num_adjacent_lines_to_load)
249 .unwrap_or_else(|| LineIndex::new(0));
250 let max_line = self.inner.current_line + num_adjacent_lines_to_load;
251
252 let decorator_demand = content
254 .decorator
255 .horizontal_space_demand(content.view(min_line..max_line));
256 let split_pos = layout_linearly(
257 window.get_width(),
258 Width::new(0).unwrap(),
259 &[decorator_demand, Demand::at_least(1)],
260 &[0.0, 1.0],
261 )[0];
262
263 let (mut decoration_window, mut content_window) = window
264 .split(split_pos.from_origin())
265 .expect("valid split pos");
266
267 let bg_style = content.highlight_info.default_style();
269 content_window.set_default_style(bg_style.apply_to_default());
270 content_window.fill(GraphemeCluster::space());
271
272 let mut cursor = Cursor::new(&mut content_window)
273 .position(ColIndex::new(0), RowIndex::new(0))
274 .wrapping_mode(WrappingMode::Wrap);
275
276 let num_line_wraps_until_current_line = {
277 content
278 .view(min_line..self.inner.current_line)
279 .map(|(_, line)| (cursor.num_expected_wraps(line.get_content()) + 1) as i32)
280 .sum::<i32>()
281 };
282 let num_line_wraps_from_current_line = {
283 content
284 .view(self.inner.current_line..max_line)
285 .map(|(_, line)| (cursor.num_expected_wraps(line.get_content()) + 1) as i32)
286 .sum::<i32>()
287 };
288
289 let centered_current_line_start_pos: RowIndex = (height / (2 as usize)).from_origin();
290 let best_current_line_pos_for_bottom = max(
291 centered_current_line_start_pos,
292 height.from_origin() - num_line_wraps_from_current_line,
293 );
294 let required_start_pos = min(
295 RowIndex::new(0),
296 best_current_line_pos_for_bottom - num_line_wraps_until_current_line,
297 );
298
299 cursor.move_to(ColIndex::new(0), required_start_pos);
300
301 for (line_index, line) in content.view(min_line..max_line) {
302 let line_content = line.get_content();
303 let base_style = if line_index == self.inner.current_line {
304 StyleModifier::new()
305 .invert(BoolModifyMode::Toggle)
306 .bold(true)
307 } else {
308 StyleModifier::new()
309 };
310
311 let (_, start_y) = cursor.get_position();
312 let mut last_change_pos = 0;
313 for &(change_pos, style) in content.highlight_info.get_info_for_line(line_index) {
314 cursor.write(&line_content[last_change_pos..change_pos]);
315
316 cursor.set_style_modifier(style.on_top_of(base_style));
317 last_change_pos = change_pos;
318 }
319 cursor.write(&line_content[last_change_pos..]);
320
321 cursor.set_style_modifier(base_style);
322 cursor.fill_and_wrap_line();
323 let (_, end_y) = cursor.get_position();
324
325 let range_start_y = min(max(start_y, RowIndex::new(0)), height.from_origin());
326 let range_end_y = min(max(end_y, RowIndex::new(0)), height.from_origin());
327 content.decorator.decorate(
328 &line,
329 line_index,
330 self.inner.current_line,
331 decoration_window.create_subwindow(.., range_start_y..range_end_y),
332 );
333 }
335 }
336 }
337}
338
339impl<L, D> Scrollable for Pager<L, D>
340where
341 L: PagerLine,
342 D: LineDecorator<Line = L>,
343{
344 fn scroll_backwards(&mut self) -> OperationResult {
345 if self.current_line > LineIndex::new(0) {
346 self.current_line -= 1;
347 Ok(())
348 } else {
349 Err(())
350 }
351 }
352 fn scroll_forwards(&mut self) -> OperationResult {
353 let new_line = self.current_line + 1;
354 self.go_to_line(new_line).map_err(|_| ())
355 }
356 fn scroll_to_beginning(&mut self) -> OperationResult {
357 if self.current_line == LineIndex::new(0) {
358 Err(())
359 } else {
360 self.current_line = LineIndex::new(0);
361 Ok(())
362 }
363 }
364 fn scroll_to_end(&mut self) -> OperationResult {
365 if let Some(ref content) = self.content {
366 if content.storage.is_empty() {
367 return Err(());
368 }
369 let last_line = LineIndex::new(content.storage.len() - 1);
370 if self.current_line == last_line {
371 Err(())
372 } else {
373 self.current_line = last_line;
374 Ok(())
375 }
376 } else {
377 Err(())
378 }
379 }
380}
381
382pub trait PagerLine {
385 fn get_content(&self) -> &str;
386}
387
388impl PagerLine for String {
389 fn get_content(&self) -> &str {
390 self.as_str()
391 }
392}
393
394pub struct PagerContent<L: PagerLine, D: LineDecorator> {
400 storage: Vec<L>,
401 highlight_info: HighlightInfo,
402 decorator: D,
403}
404
405impl<L: PagerLine> PagerContent<L, NoDecorator<L>> {
406 pub fn from_lines(storage: Vec<L>) -> Self {
409 PagerContent {
410 storage,
411 highlight_info: HighlightInfo::none(),
412 decorator: NoDecorator::default(),
413 }
414 }
415}
416
417impl PagerContent<String, NoDecorator<String>> {
418 pub fn from_file<F: AsRef<::std::path::Path>>(file_path: F) -> ::std::io::Result<Self> {
420 use std::io::Read;
421 let mut file = ::std::fs::File::open(file_path)?;
422 let mut contents = String::new();
423 file.read_to_string(&mut contents)?;
424
425 Ok(PagerContent {
426 storage: contents.lines().map(|s| s.to_owned()).collect::<Vec<_>>(),
427 highlight_info: HighlightInfo::none(),
428 decorator: NoDecorator::default(),
429 })
430 }
431}
432
433impl<L, D> PagerContent<L, D>
434where
435 L: PagerLine,
436 D: LineDecorator<Line = L>,
437{
438 pub fn with_highlighter<HN: Highlighter>(self, highlighter: &HN) -> PagerContent<L, D> {
440 let highlight_info =
441 highlighter.highlight(self.storage.iter().map(|l| l as &dyn PagerLine));
442 PagerContent {
443 storage: self.storage,
444 highlight_info,
445 decorator: self.decorator,
446 }
447 }
448}
449
450impl<L> PagerContent<L, NoDecorator<L>>
451where
452 L: PagerLine,
453{
454 pub fn with_decorator<DN: LineDecorator<Line = L>>(self, decorator: DN) -> PagerContent<L, DN> {
456 PagerContent {
457 storage: self.storage,
458 highlight_info: self.highlight_info,
459 decorator,
460 }
461 }
462}
463
464impl<L, D> PagerContent<L, D>
465where
466 L: PagerLine,
467 D: LineDecorator<Line = L>,
468{
469 pub fn view<'a, I: Into<LineIndex> + Clone, R: RangeBounds<I>>(
474 &'a self,
475 range: R,
476 ) -> impl DoubleEndedIterator<Item = (LineIndex, &'a L)> + 'a
477 where
478 Self: ::std::marker::Sized,
479 {
480 let start: LineIndex = match range.start_bound() {
482 Bound::Unbounded => LineIndex::new(0),
484 Bound::Included(i) => i.clone().into(),
485 Bound::Excluded(i) => i.clone().into() + 1,
486 };
487 let end: LineIndex = match range.end_bound() {
488 Bound::Unbounded => LineIndex::new(self.storage.len()),
490 Bound::Included(i) => i.clone().into() + 1,
491 Bound::Excluded(i) => i.clone().into(),
492 };
493 let ustart = start.raw_value();
494 let uend = self.storage.len().min(end.raw_value());
495 let urange = ustart..uend;
496 urange
497 .clone()
498 .zip(self.storage[urange].iter())
499 .map(|(i, l)| (LineIndex::new(i), l))
500 }
501
502 pub fn view_line<I: Into<LineIndex>>(&self, line: I) -> Option<&L> {
504 self.storage.get(line.into().raw_value())
505 }
506
507 pub fn set_decorator(&mut self, decorator: D) {
509 self.decorator = decorator;
510 }
511}
512
513#[derive(Debug)]
515pub enum PagerError {
516 NoLineWithIndex(LineIndex),
517 NoLineWithPredicate,
518 NoContent,
519}