use anyhow::Error; use std::{borrow::Cow, path::PathBuf}; use crate::theme::Theme; use helix_core::{ graphemes::{grapheme_width, RopeGraphemes}, Position, RopeSlice, State, }; use tui::layout::Rect; pub struct View { pub state: State, pub first_line: u16, pub size: (u16, u16), pub theme: Theme, // TODO: share one instance } impl View { pub fn open(path: PathBuf, size: (u16, u16)) -> Result { let theme = Theme::default(); let state = State::load(path, theme.scopes())?; let view = Self { state, first_line: 0, size, // TODO: pass in from term theme, }; Ok(view) } pub fn ensure_cursor_in_view(&mut self) { let cursor = self.state.selection().cursor(); let line = self.state.doc().char_to_line(cursor) as u16; let document_end = self.first_line + self.size.1.saturating_sub(1) - 1; let padding = 5u16; // TODO: side scroll if line > document_end.saturating_sub(padding) { // scroll down self.first_line += line - (document_end.saturating_sub(padding)); } else if line < self.first_line + padding { // scroll up self.first_line = line.saturating_sub(padding); } } /// Calculates the last visible line on screen #[inline] pub fn last_line(&self, viewport: Rect) -> usize { std::cmp::min( (self.first_line + viewport.height) as usize, 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. pub fn screen_coords_at_pos( &self, text: &RopeSlice, pos: usize, viewport: Rect, ) -> Option { let line = text.char_to_line(pos); if line < self.first_line as usize || line > self.last_line(viewport) { // 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" { // TODO: this should be const TAB_WIDTH col += 4; } else { let grapheme = Cow::from(grapheme); col += grapheme_width(&grapheme); } } let row = line - self.first_line as usize; Some(Position::new(row, col)) } }