diff --git a/helix-syntax/languages/tree-sitter-c-sharp b/helix-syntax/languages/tree-sitter-c-sharp index 47cde3884..1349bbfd7 160000 --- a/helix-syntax/languages/tree-sitter-c-sharp +++ b/helix-syntax/languages/tree-sitter-c-sharp @@ -1 +1 @@ -Subproject commit 47cde3884c0b52307ca032317bed3dc06185a047 +Subproject commit 1349bbfd7e84655abdf94d320c115ba754fda53e diff --git a/helix-syntax/languages/tree-sitter-javascript b/helix-syntax/languages/tree-sitter-javascript index 3d5493495..c4f5c8f65 160000 --- a/helix-syntax/languages/tree-sitter-javascript +++ b/helix-syntax/languages/tree-sitter-javascript @@ -1 +1 @@ -Subproject commit 3d5493495b62b4ff8e8c24aee7519dd904e25813 +Subproject commit c4f5c8f657b03be92eada845e9ebbf869fb4b4d2 diff --git a/helix-syntax/languages/tree-sitter-toml b/helix-syntax/languages/tree-sitter-toml index 42c9ff20c..e1aa4dd51 160000 --- a/helix-syntax/languages/tree-sitter-toml +++ b/helix-syntax/languages/tree-sitter-toml @@ -1 +1 @@ -Subproject commit 42c9ff20c0371bed7f514036e823f10793caacec +Subproject commit e1aa4dd51bfa83fbde26565e0b80f5ed17b0bdc8 diff --git a/helix-syntax/languages/tree-sitter-typescript b/helix-syntax/languages/tree-sitter-typescript index 07a12bdf0..767165ad5 160000 --- a/helix-syntax/languages/tree-sitter-typescript +++ b/helix-syntax/languages/tree-sitter-typescript @@ -1 +1 @@ -Subproject commit 07a12bdf024d66d267bd7f96870f8bbbaceaa5d9 +Subproject commit 767165ad527cbe98d513c7eb16161d1251bfd4e3 diff --git a/helix-term/src/editor.rs b/helix-term/src/editor.rs index a3bc8b3b0..4386834ac 100644 --- a/helix-term/src/editor.rs +++ b/helix-term/src/editor.rs @@ -1,5 +1,5 @@ use clap::ArgMatches as Args; -use helix_core::{state::coords_at_pos, state::Mode, syntax::HighlightEvent, Range, State}; +use helix_core::{state::Mode, syntax::HighlightEvent, Range, State}; use helix_view::{commands, keymap, View}; use std::{ @@ -24,6 +24,8 @@ use tui::{backend::CrosstermBackend, buffer::Buffer as Surface, layout::Rect, style::Style}; +const TAB_WIDTH: usize = 4; + type Terminal = tui::Terminal>; static EX: smol::Executor = smol::Executor::new(); @@ -85,10 +87,7 @@ fn render(&mut self) { // TODO: inefficient, should feed chunks.iter() to tree_sitter.parse_with(|offset, pos|) let source_code = view.state.doc().to_string(); - let last_line = std::cmp::min( - (view.first_line + viewport.height - 1) as usize, - view.state.doc().len_lines() - 1, - ); + let last_line = view.last_line(viewport); let range = { // calculate viewport byte ranges @@ -172,6 +171,8 @@ fn render(&mut self) { if line >= viewport.height { break 'outer; } + } else if grapheme == "\t" { + visual_x += (TAB_WIDTH as u16); } else { // Cow will prevent allocations if span contained in a single slice // which should really be the majority case @@ -281,12 +282,16 @@ fn render(&mut self) { // render the cursor let pos = view.state.selection().cursor(); - let coords = coords_at_pos(&view.state.doc().slice(..), pos); + + let pos = view + .screen_coords_at_pos(&view.state.doc().slice(..), pos, area) + .expect("Cursor is out of bounds."); + execute!( stdout, cursor::MoveTo( - coords.col as u16 + viewport.x, - coords.row as u16 - view.first_line + viewport.y, + pos.col as u16 + viewport.x, + pos.row as u16 - view.first_line + viewport.y, ) ); } diff --git a/helix-view/src/commands.rs b/helix-view/src/commands.rs index 1fb9c90e6..6dd1101cf 100644 --- a/helix-view/src/commands.rs +++ b/helix-view/src/commands.rs @@ -301,6 +301,10 @@ pub fn insert_char(view: &mut View, c: char) { // TODO: need to store into history if successful } +pub fn insert_tab(view: &mut View, _count: usize) { + insert_char(view, '\t'); +} + pub fn insert_newline(view: &mut View, _count: usize) { insert_char(view, '\n'); } diff --git a/helix-view/src/keymap.rs b/helix-view/src/keymap.rs index d4ab85a2c..ef23ff2a4 100644 --- a/helix-view/src/keymap.rs +++ b/helix-view/src/keymap.rs @@ -157,6 +157,10 @@ pub fn default() -> Keymaps { code: KeyCode::Enter, modifiers: Modifiers::NONE }] => commands::insert_newline, + vec![Key { + code: KeyCode::Tab, + modifiers: Modifiers::NONE + }] => commands::insert_tab, ) ) } diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index d96752d04..ac342d61c 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -1,9 +1,13 @@ use anyhow::Error; -use std::path::PathBuf; +use std::{borrow::Cow, path::PathBuf}; use crate::theme::Theme; -use helix_core::State; +use helix_core::{ + graphemes::{grapheme_width, RopeGraphemes}, + Position, RopeSlice, State, +}; +use tui::layout::Rect; pub struct View { pub state: State, @@ -44,4 +48,48 @@ pub fn ensure_cursor_in_view(&mut self) { 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 { + 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)) + } }