helix-mirror/helix-view/src/view.rs

116 lines
3.3 KiB
Rust
Raw Normal View History

use anyhow::Error;
use std::borrow::Cow;
use crate::{Document, DocumentId};
2020-10-01 23:16:24 +04:00
use helix_core::{
graphemes::{grapheme_width, RopeGraphemes},
Position, RopeSlice,
2020-10-01 23:16:24 +04:00
};
2021-02-19 12:46:43 +04:00
use slotmap::DefaultKey as Key;
2020-10-01 23:16:24 +04:00
use tui::layout::Rect;
2020-10-14 07:03:10 +04:00
pub const PADDING: usize = 5;
pub struct View {
2021-02-19 12:46:43 +04:00
pub id: Key,
pub doc: DocumentId,
2020-10-06 12:32:30 +04:00
pub first_line: usize,
pub area: Rect,
}
impl View {
pub fn new(doc: DocumentId) -> Result<Self, Error> {
2020-09-29 13:02:27 +04:00
let view = Self {
2021-02-19 12:46:43 +04:00
id: Key::default(),
doc,
first_line: 0,
area: Rect::default(), // will get calculated upon inserting into tree
};
Ok(view)
}
pub fn ensure_cursor_in_view(&mut self, doc: &Document) {
let cursor = doc.selection().cursor();
let line = doc.text().char_to_line(cursor);
let document_end = self.first_line + (self.area.height as usize).saturating_sub(2);
// TODO: side scroll
2020-10-14 07:03:10 +04:00
if line > document_end.saturating_sub(PADDING) {
// scroll down
2020-10-14 07:03:10 +04:00
self.first_line += line - (document_end.saturating_sub(PADDING));
} else if line < self.first_line + PADDING {
// scroll up
2020-10-14 07:03:10 +04:00
self.first_line = line.saturating_sub(PADDING);
}
}
2020-10-01 23:16:24 +04:00
/// Calculates the last visible line on screen
#[inline]
pub fn last_line(&self, doc: &Document) -> usize {
2021-03-16 10:30:29 +04:00
let height = self.area.height.saturating_sub(1); // - 1 for statusline
2020-10-01 23:16:24 +04:00
std::cmp::min(
2021-03-16 10:30:29 +04:00
self.first_line + height as usize,
doc.text().len_lines() - 1,
2020-10-01 23:16:24 +04: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.
pub fn screen_coords_at_pos(
&self,
doc: &Document,
text: RopeSlice,
pos: usize,
) -> Option<Position> {
2020-10-01 23:16:24 +04:00
let line = text.char_to_line(pos);
if line < self.first_line || line > self.last_line(doc) {
2020-10-01 23:16:24 +04: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;
let tab_width = doc.tab_width();
2020-10-01 23:16:24 +04:00
for grapheme in RopeGraphemes::new(line_slice) {
2020-10-01 23:16:24 +04:00
if grapheme == "\t" {
2021-03-22 08:47:39 +04:00
col += tab_width;
2020-10-01 23:16:24 +04:00
} else {
let grapheme = Cow::from(grapheme);
col += grapheme_width(&grapheme);
}
}
let row = line - self.first_line as usize;
Some(Position::new(row, col))
}
// pub fn traverse<F>(&self, text: RopeSlice, start: usize, end: usize, fun: F)
2020-10-23 08:51:08 +04: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,
// }
// }
}