From 935cfeae576f734e6cbd455bfa39df014700ae86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 21 Sep 2020 18:24:16 +0900 Subject: [PATCH] Split parts of helix-term into helix-view. It still largely depends on term for some types but I plan to change that later. --- Cargo.lock | 12 ++- Cargo.toml | 1 + README.md | 9 ++ helix-core/src/lib.rs | 2 + {helix-term => helix-core}/src/macros.rs | 1 + helix-core/src/state.rs | 2 +- helix-term/Cargo.toml | 4 +- helix-term/src/editor.rs | 68 ++++--------- helix-term/src/main.rs | 5 - helix-view/Cargo.toml | 18 ++++ {helix-term => helix-view}/src/commands.rs | 4 +- {helix-term => helix-view}/src/keymap.rs | 105 ++++++++++----------- helix-view/src/lib.rs | 6 ++ {helix-term => helix-view}/src/theme.rs | 83 +++++++++++++++- helix-view/src/view.rs | 48 ++++++++++ 15 files changed, 250 insertions(+), 118 deletions(-) rename {helix-term => helix-core}/src/macros.rs (96%) create mode 100644 helix-view/Cargo.toml rename {helix-term => helix-view}/src/commands.rs (98%) rename {helix-term => helix-view}/src/keymap.rs (51%) create mode 100644 helix-view/src/lib.rs rename {helix-term => helix-view}/src/theme.rs (65%) create mode 100644 helix-view/src/view.rs diff --git a/Cargo.lock b/Cargo.lock index f9b52d7db..f37c951cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -410,12 +410,22 @@ dependencies = [ "argh", "crossterm", "helix-core", - "helix-syntax", + "helix-view", "num_cpus", "smol", "tui", ] +[[package]] +name = "helix-view" +version = "0.1.0" +dependencies = [ + "anyhow", + "crossterm", + "helix-core", + "tui", +] + [[package]] name = "hermit-abi" version = "0.1.15" diff --git a/Cargo.toml b/Cargo.toml index db3530c94..25dbe725d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "helix-core", + "helix-view", "helix-term", "helix-syntax", ] diff --git a/README.md b/README.md index 325ff5224..3a0ac3175 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,12 @@ +# Helix + +| Crate | Description | +| ----------- | ----------- | +| helix-core | Core editing primitives, functional. | +| helix-syntax | Tree-sitter grammars | +| helix-view | UI abstractions for use in backends, imperative shell. | +| helix-term | Terminal UI | + - server-client architecture via gRPC, UI separate from core - multi cursor based editing and slicing - WASM based plugins (builtin LSP & fuzzy file finder) diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index c617fdbf0..e443168e7 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -1,5 +1,7 @@ #![allow(unused)] +pub mod config; pub mod graphemes; +pub mod macros; mod position; mod selection; pub mod state; diff --git a/helix-term/src/macros.rs b/helix-core/src/macros.rs similarity index 96% rename from helix-term/src/macros.rs rename to helix-core/src/macros.rs index 3b22a7863..1321ea5f2 100644 --- a/helix-term/src/macros.rs +++ b/helix-core/src/macros.rs @@ -1,3 +1,4 @@ +#[macro_export] macro_rules! hashmap { (@single $($x:tt)*) => (()); (@count $($rest:expr),*) => (<[()]>::len(&[$(hashmap!(@single $rest)),*])); diff --git a/helix-core/src/state.rs b/helix-core/src/state.rs index 5b5f06c0b..79e15eff3 100644 --- a/helix-core/src/state.rs +++ b/helix-core/src/state.rs @@ -4,7 +4,7 @@ use anyhow::Error; use std::path::PathBuf; -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq, Eq, Hash)] pub enum Mode { Normal, Insert, diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index e0b96ead5..d32fc0b9b 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -12,12 +12,12 @@ path = "src/main.rs" [dependencies] helix-core = { path = "../helix-core" } -helix-syntax = { path = "../helix-syntax" } +helix-view = { path = "../helix-view", features = ["term"]} anyhow = "1" argh = "0.1.3" -crossterm = { version = "0.17", features = ["event-stream"] } smol = "1" num_cpus = "1.13.0" tui = { version = "0.10.0", default-features = false, features = ["crossterm"] } +crossterm = { version = "0.17", features = ["event-stream"] } diff --git a/helix-term/src/editor.rs b/helix-term/src/editor.rs index 0542d2173..e29e7ee8e 100644 --- a/helix-term/src/editor.rs +++ b/helix-term/src/editor.rs @@ -1,10 +1,11 @@ -use crate::{commands, keymap, theme::Theme, Args}; +use crate::Args; use helix_core::{ state::coords_at_pos, state::Mode, syntax::{HighlightConfiguration, HighlightEvent, Highlighter}, State, }; +use helix_view::{commands, keymap, View}; use std::{ io::{self, stdout, Write}, @@ -31,19 +32,12 @@ type Terminal = tui::Terminal>; static EX: smol::Executor = smol::Executor::new(); -pub struct View { - pub state: State, - pub first_line: u16, - pub size: (u16, u16), -} - pub struct Editor { terminal: Terminal, view: Option, size: (u16, u16), surface: Surface, cache: Surface, - theme: Theme, } impl Editor { @@ -53,7 +47,6 @@ impl Editor { let mut terminal = Terminal::new(backend)?; let size = terminal::size().unwrap(); let area = Rect::new(0, 0, size.0, size.1); - let theme = Theme::default(); let mut editor = Editor { terminal, @@ -61,7 +54,6 @@ impl Editor { size, surface: Surface::empty(area), cache: Surface::empty(area), - theme, // TODO; move to state }; @@ -73,20 +65,7 @@ impl Editor { } pub fn open(&mut self, path: PathBuf) -> Result<(), Error> { - let mut state = State::load(path)?; - state - .syntax - .as_mut() - .unwrap() - .configure(self.theme.scopes()); - - let view = View { - state, - first_line: 0, - size: self.size, - }; - - self.view = Some(view); + self.view = Some(View::open(path, self.size)?); Ok(()) } @@ -102,7 +81,7 @@ impl Editor { // clear with background color self.surface - .set_style(area, self.theme.get("ui.background")); + .set_style(area, view.theme.get("ui.background").into()); let offset = 5 + 1; // 5 linenr + 1 gutter let viewport = Rect::new(offset, 0, self.size.0, self.size.1 - 1); // - 1 for statusline @@ -161,7 +140,9 @@ impl Editor { use helix_core::graphemes::{grapheme_width, RopeGraphemes}; let style = match spans.first() { - Some(span) => self.theme.get(self.theme.scopes()[span.0].as_str()), + Some(span) => { + view.theme.get(view.theme.scopes()[span.0].as_str()).into() + } None => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender }; @@ -202,7 +183,7 @@ impl Editor { } let mut line = 0; - let style = self.theme.get("ui.linenr"); + let style: Style = view.theme.get("ui.linenr").into(); for i in view.first_line..(last_line as u16) { self.surface .set_stringn(0, line, format!("{:>5}", i + 1), 5, style); // lavender @@ -241,7 +222,7 @@ impl Editor { }; self.surface.set_style( Rect::new(0, self.size.1 - 1, self.size.0, 1), - self.theme.get("ui.statusline"), + view.theme.get("ui.statusline").into(), ); // TODO: unfocused one with different color let text_color = Style::default().fg(Color::Rgb(219, 191, 239)); // lilac @@ -296,7 +277,9 @@ impl Editor { self.cache = Surface::empty(area); // TODO: simplistic ensure cursor in view for now - self.ensure_cursor_in_view(); + if let Some(view) = &mut self.view { + view.ensure_cursor_in_view() + }; self.render(); } @@ -333,18 +316,19 @@ impl Editor { _ => (), // skip } // TODO: simplistic ensure cursor in view for now - self.ensure_cursor_in_view(); + view.ensure_cursor_in_view(); self.render(); } Mode::Normal => { // TODO: handle modes and sequences (`gg`) - if let Some(command) = keymap.get(&event) { + let keys = vec![event]; + if let Some(command) = keymap[&Mode::Normal].get(&keys) { // TODO: handle count other than 1 command(view, 1); // TODO: simplistic ensure cursor in view for now - self.ensure_cursor_in_view(); + view.ensure_cursor_in_view(); self.render(); } @@ -361,26 +345,6 @@ impl Editor { } } - fn ensure_cursor_in_view(&mut self) { - if let Some(view) = &mut self.view { - let cursor = view.state.selection().cursor(); - let line = view.state.doc().char_to_line(cursor) as u16; - let document_end = view.first_line + self.size.1.saturating_sub(1) - 1; - - let padding = 5u16; - - // TODO: side scroll - - if line > document_end.saturating_sub(padding) { - // scroll down - view.first_line += line - (document_end.saturating_sub(padding)); - } else if line < view.first_line + padding { - // scroll up - view.first_line = line.saturating_sub(padding); - } - } - } - pub async fn run(&mut self) -> Result<(), Error> { enable_raw_mode()?; diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index aca046419..b691eb650 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -1,11 +1,6 @@ #![allow(unused)] -#[macro_use] -mod macros; -mod commands; mod editor; -mod keymap; -mod theme; use editor::Editor; diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml new file mode 100644 index 000000000..379576259 --- /dev/null +++ b/helix-view/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "helix-view" +version = "0.1.0" +authors = ["Blaž Hrastnik "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +term = ["tui", "crossterm"] + +[dependencies] +anyhow = "1.0.32" +helix-core = { path = "../helix-core" } + +# Conversion traits +tui = { version = "0.10.0", default-features = false, features = ["crossterm"], optional = true} +crossterm = { version = "0.17", features = ["event-stream"], optional = true} diff --git a/helix-term/src/commands.rs b/helix-view/src/commands.rs similarity index 98% rename from helix-term/src/commands.rs rename to helix-view/src/commands.rs index 269a7743b..560167c9d 100644 --- a/helix-term/src/commands.rs +++ b/helix-view/src/commands.rs @@ -1,10 +1,10 @@ use helix_core::{ graphemes, state::{Direction, Granularity, Mode, State}, - ChangeSet, Range, Selection, Tendril, Transaction, + Range, Selection, Tendril, Transaction, }; -use crate::editor::View; +use crate::view::View; /// A command is a function that takes the current state and a count, and does a side-effect on the /// state (usually by creating and applying a transaction). diff --git a/helix-term/src/keymap.rs b/helix-view/src/keymap.rs similarity index 51% rename from helix-term/src/keymap.rs rename to helix-view/src/keymap.rs index d52ccca47..705357a8a 100644 --- a/helix-term/src/keymap.rs +++ b/helix-view/src/keymap.rs @@ -1,9 +1,5 @@ use crate::commands::{self, Command}; -use crossterm::{ - event::{KeyCode, KeyEvent as Key, KeyModifiers as Modifiers}, - execute, - style::Print, -}; +use helix_core::{hashmap, state}; use std::collections::HashMap; // Kakoune-inspired: @@ -79,56 +75,57 @@ use std::collections::HashMap; // } // } -type Keymap = HashMap; +#[cfg(feature = "term")] +pub use crossterm::event::{KeyCode, KeyEvent as Key, KeyModifiers as Modifiers}; -pub fn default() -> Keymap { +// TODO: could be trie based +type Keymap = HashMap, Command>; +type Keymaps = HashMap; + +pub fn default() -> Keymaps { hashmap!( - Key { - code: KeyCode::Char('h'), - modifiers: Modifiers::NONE - } => commands::move_char_left as Command, - Key { - code: KeyCode::Char('j'), - modifiers: Modifiers::NONE - } => commands::move_line_down as Command, - Key { - code: KeyCode::Char('k'), - modifiers: Modifiers::NONE - } => commands::move_line_up as Command, - Key { - code: KeyCode::Char('l'), - modifiers: Modifiers::NONE - } => commands::move_char_right as Command, - Key { - code: KeyCode::Char('i'), - modifiers: Modifiers::NONE - } => commands::insert_mode as Command, - Key { - code: KeyCode::Char('I'), - modifiers: Modifiers::SHIFT, - } => commands::prepend_to_line as Command, - Key { - code: KeyCode::Char('a'), - modifiers: Modifiers::NONE - } => commands::append_mode as Command, - Key { - code: KeyCode::Char('A'), - modifiers: Modifiers::SHIFT, - } => commands::append_to_line as Command, - Key { - code: KeyCode::Char('o'), - modifiers: Modifiers::NONE - } => commands::open_below as Command, - Key { - code: KeyCode::Esc, - modifiers: Modifiers::NONE - } => commands::normal_mode as Command, + state::Mode::Normal => + hashmap!( + vec![Key { + code: KeyCode::Char('h'), + modifiers: Modifiers::NONE + }] => commands::move_char_left as Command, + vec![Key { + code: KeyCode::Char('j'), + modifiers: Modifiers::NONE + }] => commands::move_line_down as Command, + vec![Key { + code: KeyCode::Char('k'), + modifiers: Modifiers::NONE + }] => commands::move_line_up as Command, + vec![Key { + code: KeyCode::Char('l'), + modifiers: Modifiers::NONE + }] => commands::move_char_right as Command, + vec![Key { + code: KeyCode::Char('i'), + modifiers: Modifiers::NONE + }] => commands::insert_mode as Command, + vec![Key { + code: KeyCode::Char('I'), + modifiers: Modifiers::SHIFT, + }] => commands::prepend_to_line as Command, + vec![Key { + code: KeyCode::Char('a'), + modifiers: Modifiers::NONE + }] => commands::append_mode as Command, + vec![Key { + code: KeyCode::Char('A'), + modifiers: Modifiers::SHIFT, + }] => commands::append_to_line as Command, + vec![Key { + code: KeyCode::Char('o'), + modifiers: Modifiers::NONE + }] => commands::open_below as Command, + vec![Key { + code: KeyCode::Esc, + modifiers: Modifiers::NONE + }] => commands::normal_mode as Command, + ) ) - - // hashmap!( - // Key { - // code: KeyCode::Esc, - // modifiers: Modifiers::NONE - // } => commands::normal_mode as Command, - // ) } diff --git a/helix-view/src/lib.rs b/helix-view/src/lib.rs new file mode 100644 index 000000000..2a000f329 --- /dev/null +++ b/helix-view/src/lib.rs @@ -0,0 +1,6 @@ +pub mod commands; +pub mod keymap; +pub mod theme; +pub mod view; + +pub use view::View; diff --git a/helix-term/src/theme.rs b/helix-view/src/theme.rs similarity index 65% rename from helix-term/src/theme.rs rename to helix-view/src/theme.rs index 4b2f102eb..d61457d73 100644 --- a/helix-term/src/theme.rs +++ b/helix-view/src/theme.rs @@ -1,5 +1,86 @@ +use helix_core::hashmap; use std::collections::HashMap; -use tui::style::{Color, Style}; + +#[cfg(feature = "term")] +pub use tui::style::{Color, Style}; + +// #[derive(Clone, Copy, PartialEq, Eq, Default, Hash)] +// pub struct Color { +// pub r: u8, +// pub g: u8, +// pub b: u8, +// } + +// impl Color { +// pub fn new(r: u8, g: u8, b: u8) -> Self { +// Self { r, g, b } +// } +// } + +// #[cfg(feature = "term")] +// impl Into for Color { +// fn into(self) -> tui::style::Color { +// tui::style::Color::Rgb(self.r, self.g, self.b) +// } +// } + +// impl std::str::FromStr for Color { +// type Err = (); + +// /// Tries to parse a string (`'#FFFFFF'` or `'FFFFFF'`) into RGB. +// fn from_str(input: &str) -> Result { +// let input = input.trim(); +// let input = match (input.chars().next(), input.len()) { +// (Some('#'), 7) => &input[1..], +// (_, 6) => input, +// _ => return Err(()), +// }; + +// u32::from_str_radix(&input, 16) +// .map(|s| Color { +// r: ((s >> 16) & 0xFF) as u8, +// g: ((s >> 8) & 0xFF) as u8, +// b: (s & 0xFF) as u8, +// }) +// .map_err(|_| ()) +// } +// } + +// #[derive(Clone, Copy, PartialEq, Eq, Default, Hash)] +// pub struct Style { +// pub fg: Option, +// pub bg: Option, +// // TODO: modifiers (bold, underline, italic, etc) +// } + +// impl Style { +// pub fn fg(mut self, fg: Color) -> Self { +// self.fg = Some(fg); +// self +// } + +// pub fn bg(mut self, bg: Color) -> Self { +// self.bg = Some(bg); +// self +// } +// } + +// #[cfg(feature = "term")] +// impl Into for Style { +// fn into(self) -> tui::style::Style { +// let style = tui::style::Style::default(); + +// if let Some(fg) = self.fg { +// style.fg(fg.into()); +// } + +// if let Some(bg) = self.bg { +// style.bg(bg.into()); +// } + +// style +// } +// } /// Color theme for syntax highlighting. pub struct Theme { diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs new file mode 100644 index 000000000..3f7a99740 --- /dev/null +++ b/helix-view/src/view.rs @@ -0,0 +1,48 @@ +use anyhow::Error; + +use std::path::PathBuf; + +use crate::theme::Theme; +use helix_core::State; + +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 mut state = State::load(path)?; + let theme = Theme::default(); + state.syntax.as_mut().unwrap().configure(theme.scopes()); + + let view = View { + 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); + } + } +}