1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
|
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<Self, Error> {
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 - 1) 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<Position> {
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))
}
}
|