on master help sign in
Merge pull request #2 from helix-editor/tab-implementation
197651eBlaž Hrastnik
helix-syntax/languages/tree-sitter-c-sharp+1−1

Submodule changed.

helix-syntax/languages/tree-sitter-javascript+1−1

Submodule changed.

helix-syntax/languages/tree-sitter-toml+1−1

Submodule changed.

helix-syntax/languages/tree-sitter-typescript+1−1

Submodule changed.

helix-term/src/editor.rs+13−8
@@ -1,5 +1,5 @@
1use clap::ArgMatches as Args;
1use clap::ArgMatches as Args;
2−use helix_core::{state::coords_at_pos, state::Mode, syntax::HighlightEvent, Range, State};
2+use helix_core::{state::Mode, syntax::HighlightEvent, Range, State};
3use helix_view::{commands, keymap, View};
3use helix_view::{commands, keymap, View};
4
4
5use std::{
5use std::{
@@ -24,6 +24,8 @@ use crossterm::{
24
24
25use tui::{backend::CrosstermBackend, buffer::Buffer as Surface, layout::Rect, style::Style};
25use tui::{backend::CrosstermBackend, buffer::Buffer as Surface, layout::Rect, style::Style};
26
26
27+const TAB_WIDTH: usize = 4;
28+
27type Terminal = tui::Terminal<CrosstermBackend<std::io::Stdout>>;
29type Terminal = tui::Terminal<CrosstermBackend<std::io::Stdout>>;
28
30
29static EX: smol::Executor = smol::Executor::new();
31static EX: smol::Executor = smol::Executor::new();
@@ -85,10 +87,7 @@ impl Editor {
85 // TODO: inefficient, should feed chunks.iter() to tree_sitter.parse_with(|offset, pos|)
87 // TODO: inefficient, should feed chunks.iter() to tree_sitter.parse_with(|offset, pos|)
86 let source_code = view.state.doc().to_string();
88 let source_code = view.state.doc().to_string();
87
89
88− let last_line = std::cmp::min(
90+ let last_line = view.last_line(viewport);
89− (view.first_line + viewport.height - 1) as usize,
90− view.state.doc().len_lines() - 1,
91− );
92
91
93 let range = {
92 let range = {
94 // calculate viewport byte ranges
93 // calculate viewport byte ranges
@@ -172,6 +171,8 @@ impl Editor {
172 if line >= viewport.height {
171 if line >= viewport.height {
173 break 'outer;
172 break 'outer;
174 }
173 }
174+ } else if grapheme == "\t" {
175+ visual_x += (TAB_WIDTH as u16);
175 } else {
176 } else {
176 // Cow will prevent allocations if span contained in a single slice
177 // Cow will prevent allocations if span contained in a single slice
177 // which should really be the majority case
178 // which should really be the majority case
@@ -281,12 +282,16 @@ impl Editor {
281
282
282 // render the cursor
283 // render the cursor
283 let pos = view.state.selection().cursor();
284 let pos = view.state.selection().cursor();
285+
286+ let pos = view
284− let coords = coords_at_pos(&view.state.doc().slice(..), pos);
287+ .screen_coords_at_pos(&view.state.doc().slice(..), pos, area)
288+ .expect("Cursor is out of bounds.");
289+
285 execute!(
290 execute!(
286 stdout,
291 stdout,
287 cursor::MoveTo(
292 cursor::MoveTo(
288− coords.col as u16 + viewport.x,
293+ pos.col as u16 + viewport.x,
289− coords.row as u16 - view.first_line + viewport.y,
294+ pos.row as u16 - view.first_line + viewport.y,
290 )
295 )
291 );
296 );
292 }
297 }
helix-view/src/commands.rs+4
@@ -301,6 +301,10 @@ pub fn insert_char(view: &mut View, c: char) {
301 // TODO: need to store into history if successful
301 // TODO: need to store into history if successful
302}
302}
303
303
304+pub fn insert_tab(view: &mut View, _count: usize) {
305+ insert_char(view, '\t');
306+}
307+
304pub fn insert_newline(view: &mut View, _count: usize) {
308pub fn insert_newline(view: &mut View, _count: usize) {
305 insert_char(view, '\n');
309 insert_char(view, '\n');
306}
310}
helix-view/src/keymap.rs+4
@@ -157,6 +157,10 @@ pub fn default() -> Keymaps {
157 code: KeyCode::Enter,
157 code: KeyCode::Enter,
158 modifiers: Modifiers::NONE
158 modifiers: Modifiers::NONE
159 }] => commands::insert_newline,
159 }] => commands::insert_newline,
160+ vec![Key {
161+ code: KeyCode::Tab,
162+ modifiers: Modifiers::NONE
163+ }] => commands::insert_tab,
160 )
164 )
161 )
165 )
162}
166}
helix-view/src/view.rs+50−2
@@ -1,9 +1,13 @@
1use anyhow::Error;
1use anyhow::Error;
2
2
3−use std::path::PathBuf;
3+use std::{borrow::Cow, path::PathBuf};
4
4
5use crate::theme::Theme;
5use crate::theme::Theme;
6−use helix_core::State;
6+use helix_core::{
7+ graphemes::{grapheme_width, RopeGraphemes},
8+ Position, RopeSlice, State,
9+};
10+use tui::layout::Rect;
7
11
8pub struct View {
12pub struct View {
9 pub state: State,
13 pub state: State,
@@ -44,4 +48,48 @@ impl View {
44 self.first_line = line.saturating_sub(padding);
48 self.first_line = line.saturating_sub(padding);
45 }
49 }
46 }
50 }
51+
52+ /// Calculates the last visible line on screen
53+ #[inline]
54+ pub fn last_line(&self, viewport: Rect) -> usize {
55+ std::cmp::min(
56+ (self.first_line + viewport.height - 1) as usize,
57+ self.state.doc().len_lines() - 1,
58+ )
59+ }
60+
61+ /// Translates a document position to an absolute position in the terminal.
62+ /// Returns a (line, col) position if the position is visible on screen.
63+ // TODO: Could return width as well for the character width at cursor.
64+ pub fn screen_coords_at_pos(
65+ &self,
66+ text: &RopeSlice,
67+ pos: usize,
68+ viewport: Rect,
69+ ) -> Option<Position> {
70+ let line = text.char_to_line(pos);
71+
72+ if line < self.first_line as usize || line > self.last_line(viewport) {
73+ // Line is not visible on screen
74+ return None;
75+ }
76+
77+ let line_start = text.line_to_char(line);
78+ let line_slice = text.slice(line_start..pos);
79+ let mut col = 0;
80+
81+ for grapheme in RopeGraphemes::new(&line_slice) {
82+ if grapheme == "\t" {
83+ // TODO: this should be const TAB_WIDTH
84+ col += 4;
85+ } else {
86+ let grapheme = Cow::from(grapheme);
87+ col += grapheme_width(&grapheme);
88+ }
89+ }
90+
91+ let row = line - self.first_line as usize;
92+
93+ Some(Position::new(row, col))
94+ }
47}
95}