2020-09-21 11:24:16 +02:00
|
|
|
use anyhow::Error;
|
|
|
|
|
2020-10-19 10:18:03 +02:00
|
|
|
use std::borrow::Cow;
|
2020-09-21 11:24:16 +02:00
|
|
|
|
2021-03-16 10:27:57 +01:00
|
|
|
use crate::{Document, DocumentId};
|
2020-10-01 21:16:24 +02:00
|
|
|
use helix_core::{
|
|
|
|
graphemes::{grapheme_width, RopeGraphemes},
|
2020-10-22 07:35:07 +02:00
|
|
|
Position, RopeSlice,
|
2020-10-01 21:16:24 +02:00
|
|
|
};
|
2021-02-19 09:46:43 +01:00
|
|
|
use slotmap::DefaultKey as Key;
|
2020-10-01 21:16:24 +02:00
|
|
|
use tui::layout::Rect;
|
2020-09-21 11:24:16 +02:00
|
|
|
|
2020-10-14 05:03:10 +02:00
|
|
|
pub const PADDING: usize = 5;
|
|
|
|
|
2020-09-21 11:24:16 +02:00
|
|
|
pub struct View {
|
2021-02-19 09:46:43 +01:00
|
|
|
pub id: Key,
|
2021-03-23 09:47:40 +01:00
|
|
|
pub doc: DocumentId,
|
2020-10-06 10:32:30 +02:00
|
|
|
pub first_line: usize,
|
2021-02-03 11:36:54 +01:00
|
|
|
pub area: Rect,
|
2020-09-21 11:24:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl View {
|
2021-03-23 09:47:40 +01:00
|
|
|
pub fn new(doc: DocumentId) -> Result<Self, Error> {
|
2020-09-29 11:02:27 +02:00
|
|
|
let view = Self {
|
2021-02-19 09:46:43 +01:00
|
|
|
id: Key::default(),
|
2020-10-22 07:35:07 +02:00
|
|
|
doc,
|
2020-09-21 11:24:16 +02:00
|
|
|
first_line: 0,
|
2021-02-03 11:36:54 +01:00
|
|
|
area: Rect::default(), // will get calculated upon inserting into tree
|
2020-09-21 11:24:16 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
Ok(view)
|
|
|
|
}
|
|
|
|
|
2021-03-23 09:47:40 +01:00
|
|
|
pub fn ensure_cursor_in_view(&mut self, doc: &Document) {
|
2021-03-16 10:27:57 +01:00
|
|
|
let cursor = doc.selection().cursor();
|
|
|
|
let line = doc.text().char_to_line(cursor);
|
2021-02-03 11:36:54 +01:00
|
|
|
let document_end = self.first_line + (self.area.height as usize).saturating_sub(2);
|
2020-09-21 11:24:16 +02:00
|
|
|
|
|
|
|
// TODO: side scroll
|
|
|
|
|
2020-10-14 05:03:10 +02:00
|
|
|
if line > document_end.saturating_sub(PADDING) {
|
2020-09-21 11:24:16 +02:00
|
|
|
// scroll down
|
2020-10-14 05:03:10 +02:00
|
|
|
self.first_line += line - (document_end.saturating_sub(PADDING));
|
|
|
|
} else if line < self.first_line + PADDING {
|
2020-09-21 11:24:16 +02:00
|
|
|
// scroll up
|
2020-10-14 05:03:10 +02:00
|
|
|
self.first_line = line.saturating_sub(PADDING);
|
2020-09-21 11:24:16 +02:00
|
|
|
}
|
|
|
|
}
|
2020-10-01 21:16:24 +02:00
|
|
|
|
|
|
|
/// Calculates the last visible line on screen
|
|
|
|
#[inline]
|
2021-03-23 09:47:40 +01:00
|
|
|
pub fn last_line(&self, doc: &Document) -> usize {
|
2021-03-16 07:30:29 +01:00
|
|
|
let height = self.area.height.saturating_sub(1); // - 1 for statusline
|
2020-10-01 21:16:24 +02:00
|
|
|
std::cmp::min(
|
2021-03-16 07:30:29 +01:00
|
|
|
self.first_line + height as usize,
|
2021-03-16 10:27:57 +01:00
|
|
|
doc.text().len_lines() - 1,
|
2020-10-01 21:16:24 +02:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Translates a document position to an absolute position in the terminal.
|
|
|
|
/// Returns a (line, col) position if the position is visible on screen.
|
|
|
|
// TODO: Could return width as well for the character width at cursor.
|
2021-03-23 09:47:40 +01:00
|
|
|
pub fn screen_coords_at_pos(
|
|
|
|
&self,
|
|
|
|
doc: &Document,
|
|
|
|
text: RopeSlice,
|
|
|
|
pos: usize,
|
|
|
|
) -> Option<Position> {
|
2020-10-01 21:16:24 +02:00
|
|
|
let line = text.char_to_line(pos);
|
|
|
|
|
2021-03-23 09:47:40 +01:00
|
|
|
if line < self.first_line || line > self.last_line(doc) {
|
2020-10-01 21:16:24 +02:00
|
|
|
// Line is not visible on screen
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
let line_start = text.line_to_char(line);
|
|
|
|
let line_slice = text.slice(line_start..pos);
|
|
|
|
let mut col = 0;
|
2021-03-23 09:47:40 +01:00
|
|
|
let tab_width = doc.tab_width();
|
2020-10-01 21:16:24 +02:00
|
|
|
|
2021-02-18 10:34:22 +01:00
|
|
|
for grapheme in RopeGraphemes::new(line_slice) {
|
2020-10-01 21:16:24 +02:00
|
|
|
if grapheme == "\t" {
|
2021-03-22 05:47:39 +01:00
|
|
|
col += tab_width;
|
2020-10-01 21:16:24 +02:00
|
|
|
} else {
|
|
|
|
let grapheme = Cow::from(grapheme);
|
|
|
|
col += grapheme_width(&grapheme);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let row = line - self.first_line as usize;
|
|
|
|
|
|
|
|
Some(Position::new(row, col))
|
|
|
|
}
|
2020-10-22 07:35:07 +02:00
|
|
|
|
2021-02-18 10:34:22 +01:00
|
|
|
// pub fn traverse<F>(&self, text: RopeSlice, start: usize, end: usize, fun: F)
|
2020-10-23 06:51:08 +02:00
|
|
|
// where
|
|
|
|
// F: Fn(usize, usize),
|
|
|
|
// {
|
|
|
|
// let start = self.screen_coords_at_pos(text, start);
|
|
|
|
// let end = self.screen_coords_at_pos(text, end);
|
|
|
|
|
|
|
|
// match (start, end) {
|
|
|
|
// // fully on screen
|
|
|
|
// (Some(start), Some(end)) => {
|
|
|
|
// // we want to calculate ends of lines for each char..
|
|
|
|
// }
|
|
|
|
// // from start to end of screen
|
|
|
|
// (Some(start), None) => {}
|
|
|
|
// // from start of screen to end
|
|
|
|
// (None, Some(end)) => {}
|
|
|
|
// // not on screen
|
|
|
|
// (None, None) => return,
|
|
|
|
// }
|
|
|
|
// }
|
2020-09-21 11:24:16 +02:00
|
|
|
}
|