helix-mods/helix-view/src/view.rs

94 lines
2.7 KiB
Rust
Raw Normal View History

use anyhow::Error;
2020-10-01 21:16:24 +02:00
use std::{borrow::Cow, path::PathBuf};
use crate::theme::Theme;
2020-10-01 21:16:24 +02:00
use helix_core::{
graphemes::{grapheme_width, RopeGraphemes},
2020-10-14 05:01:41 +02:00
indent::TAB_WIDTH,
History, Position, RopeSlice, State,
2020-10-01 21:16:24 +02:00
};
use tui::layout::Rect;
2020-10-14 05:03:10 +02:00
pub const PADDING: usize = 5;
pub struct View {
pub state: State,
pub history: History,
2020-10-06 10:32:30 +02:00
pub first_line: usize,
pub size: (u16, u16),
pub theme: Theme, // TODO: share one instance
}
impl View {
2020-09-29 11:02:27 +02:00
pub fn open(path: PathBuf, size: (u16, u16)) -> Result<Self, Error> {
let theme = Theme::default();
let state = State::load(path, theme.scopes())?;
2020-09-29 11:02:27 +02:00
let view = Self {
state,
first_line: 0,
2020-10-06 11:59:23 +02:00
size,
theme,
history: History::default(),
};
Ok(view)
}
pub fn ensure_cursor_in_view(&mut self) {
let cursor = self.state.selection().cursor();
2020-10-06 10:32:30 +02:00
let line = self.state.doc().char_to_line(cursor);
let document_end = self.first_line + (self.size.1 as usize).saturating_sub(1);
// TODO: side scroll
2020-10-14 05:03:10 +02:00
if line > document_end.saturating_sub(PADDING) {
// 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 {
// scroll up
2020-10-14 05:03:10 +02:00
self.first_line = line.saturating_sub(PADDING);
}
}
2020-10-01 21:16:24 +02:00
/// Calculates the last visible line on screen
#[inline]
2020-10-07 01:41:09 +02:00
pub fn last_line(&self) -> usize {
let viewport = Rect::new(6, 0, self.size.0, self.size.1 - 1); // - 1 for statusline
2020-10-01 21:16:24 +02:00
std::cmp::min(
2020-10-06 10:32:30 +02:00
self.first_line + (viewport.height as usize),
2020-10-01 21:16:24 +02:00
self.state.doc().len_lines() - 1,
)
}
/// 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.
2020-10-07 01:41:09 +02:00
pub fn screen_coords_at_pos(&self, text: &RopeSlice, pos: usize) -> Option<Position> {
2020-10-01 21:16:24 +02:00
let line = text.char_to_line(pos);
2020-10-07 01:41:09 +02:00
if line < self.first_line as usize || line > self.last_line() {
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;
for grapheme in RopeGraphemes::new(&line_slice) {
if grapheme == "\t" {
2020-10-14 05:01:41 +02: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))
}
}