on master help sign in
Split parts of helix-term into helix-view.

It still largely depends on term for some types but I plan to change that later.

commit935cfeae576f734e6cbd455bfa39df014700ae86 authorBlaž Hrastnik <blaz@mxxn.io> dateMon 21 Sep 2020, 18:24 (6 years ago) parent48330dd
15 files changed+250−118
Cargo.lock+11−1 Cargo.toml+1 README.md+9 helix-core/src/lib.rs+2 helix-term/src/macros.rs → helix-core/src/macros.rs+1 helix-core/src/state.rs+1−1 helix-term/Cargo.toml+2−2 helix-term/src/editor.rs+16−52 helix-term/src/main.rs−5 helix-view/Cargo.toml+18 helix-term/src/commands.rs → helix-view/src/commands.rs+2−2 helix-term/src/keymap.rs → helix-view/src/keymap.rs+51−54 helix-view/src/lib.rs+6 helix-term/src/theme.rs → helix-view/src/theme.rs+82−1 helix-view/src/view.rs+48
Cargo.lock+11−1
@@ -410,12 +410,22 @@ dependencies = [
410410 "argh",
411411 "crossterm",
412412 "helix-core",
413− "helix-syntax",
413+ "helix-view",
414414 "num_cpus",
415415 "smol",
416416 "tui",
417417]
418418
419+[[package]]
420+name = "helix-view"
421+version = "0.1.0"
422+dependencies = [
423+ "anyhow",
424+ "crossterm",
425+ "helix-core",
426+ "tui",
427+]
428+
419429[[package]]
420430name = "hermit-abi"
421431version = "0.1.15"
Cargo.toml+1
@@ -1,6 +1,7 @@
11[workspace]
22members = [
33 "helix-core",
4+ "helix-view",
45 "helix-term",
56 "helix-syntax",
67]
README.md+9
@@ -1,3 +1,12 @@
1+# Helix
2+
3+| Crate | Description |
4+| ----------- | ----------- |
5+| helix-core | Core editing primitives, functional. |
6+| helix-syntax | Tree-sitter grammars |
7+| helix-view | UI abstractions for use in backends, imperative shell. |
8+| helix-term | Terminal UI |
9+
110- server-client architecture via gRPC, UI separate from core
211- multi cursor based editing and slicing
312- WASM based plugins (builtin LSP & fuzzy file finder)
helix-core/src/lib.rs+2
@@ -1,5 +1,7 @@
11#![allow(unused)]
2+pub mod config;
23pub mod graphemes;
4+pub mod macros;
35mod position;
46mod selection;
57pub mod state;
helix-term/src/macros.rs → helix-core/src/macros.rsrenamed+1
@@ -1,3 +1,4 @@
1+#[macro_export]
12macro_rules! hashmap {
23 (@single $($x:tt)*) => (());
34 (@count $($rest:expr),*) => (<[()]>::len(&[$(hashmap!(@single $rest)),*]));
helix-core/src/state.rs+1−1
@@ -4,7 +4,7 @@ use anyhow::Error;
44
55use std::path::PathBuf;
66
7−#[derive(Copy, Clone)]
7+#[derive(Copy, Clone, PartialEq, Eq, Hash)]
88pub enum Mode {
99 Normal,
1010 Insert,
helix-term/Cargo.toml+2−2
@@ -12,12 +12,12 @@ path = "src/main.rs"
1212
1313[dependencies]
1414helix-core = { path = "../helix-core" }
15−helix-syntax = { path = "../helix-syntax" }
15+helix-view = { path = "../helix-view", features = ["term"]}
1616
1717anyhow = "1"
1818argh = "0.1.3"
19−crossterm = { version = "0.17", features = ["event-stream"] }
2019
2120smol = "1"
2221num_cpus = "1.13.0"
2322tui = { version = "0.10.0", default-features = false, features = ["crossterm"] }
23+crossterm = { version = "0.17", features = ["event-stream"] }
helix-term/src/editor.rs+16−52
@@ -1,10 +1,11 @@
1−use crate::{commands, keymap, theme::Theme, Args};
1+use crate::Args;
22use helix_core::{
33 state::coords_at_pos,
44 state::Mode,
55 syntax::{HighlightConfiguration, HighlightEvent, Highlighter},
66 State,
77};
8+use helix_view::{commands, keymap, View};
89
910use std::{
1011 io::{self, stdout, Write},
@@ -31,19 +32,12 @@ type Terminal = tui::Terminal<CrosstermBackend<std::io::Stdout>>;
3132
3233static EX: smol::Executor = smol::Executor::new();
3334
34−pub struct View {
35− pub state: State,
36− pub first_line: u16,
37− pub size: (u16, u16),
38−}
39−
4035pub struct Editor {
4136 terminal: Terminal,
4237 view: Option<View>,
4338 size: (u16, u16),
4439 surface: Surface,
4540 cache: Surface,
46− theme: Theme,
4741}
4842
4943impl Editor {
@@ -53,7 +47,6 @@ impl Editor {
5347 let mut terminal = Terminal::new(backend)?;
5448 let size = terminal::size().unwrap();
5549 let area = Rect::new(0, 0, size.0, size.1);
56− let theme = Theme::default();
5750
5851 let mut editor = Editor {
5952 terminal,
@@ -61,7 +54,6 @@ impl Editor {
6154 size,
6255 surface: Surface::empty(area),
6356 cache: Surface::empty(area),
64− theme,
6557 // TODO; move to state
6658 };
6759
@@ -73,20 +65,7 @@ impl Editor {
7365 }
7466
7567 pub fn open(&mut self, path: PathBuf) -> Result<(), Error> {
76− let mut state = State::load(path)?;
77− state
78− .syntax
79− .as_mut()
80− .unwrap()
81− .configure(self.theme.scopes());
82−
83− let view = View {
84− state,
85− first_line: 0,
86− size: self.size,
87− };
88−
89− self.view = Some(view);
68+ self.view = Some(View::open(path, self.size)?);
9069 Ok(())
9170 }
9271
@@ -102,7 +81,7 @@ impl Editor {
10281
10382 // clear with background color
10483 self.surface
105− .set_style(area, self.theme.get("ui.background"));
84+ .set_style(area, view.theme.get("ui.background").into());
10685
10786 let offset = 5 + 1; // 5 linenr + 1 gutter
10887 let viewport = Rect::new(offset, 0, self.size.0, self.size.1 - 1); // - 1 for statusline
@@ -161,7 +140,9 @@ impl Editor {
161140 use helix_core::graphemes::{grapheme_width, RopeGraphemes};
162141
163142 let style = match spans.first() {
164− Some(span) => self.theme.get(self.theme.scopes()[span.0].as_str()),
143+ Some(span) => {
144+ view.theme.get(view.theme.scopes()[span.0].as_str()).into()
145+ }
165146 None => Style::default().fg(Color::Rgb(164, 160, 232)), // lavender
166147 };
167148
@@ -202,7 +183,7 @@ impl Editor {
202183 }
203184
204185 let mut line = 0;
205− let style = self.theme.get("ui.linenr");
186+ let style: Style = view.theme.get("ui.linenr").into();
206187 for i in view.first_line..(last_line as u16) {
207188 self.surface
208189 .set_stringn(0, line, format!("{:>5}", i + 1), 5, style); // lavender
@@ -241,7 +222,7 @@ impl Editor {
241222 };
242223 self.surface.set_style(
243224 Rect::new(0, self.size.1 - 1, self.size.0, 1),
244− self.theme.get("ui.statusline"),
225+ view.theme.get("ui.statusline").into(),
245226 );
246227 // TODO: unfocused one with different color
247228 let text_color = Style::default().fg(Color::Rgb(219, 191, 239)); // lilac
@@ -296,7 +277,9 @@ impl Editor {
296277 self.cache = Surface::empty(area);
297278
298279 // TODO: simplistic ensure cursor in view for now
299− self.ensure_cursor_in_view();
280+ if let Some(view) = &mut self.view {
281+ view.ensure_cursor_in_view()
282+ };
300283
301284 self.render();
302285 }
@@ -333,18 +316,19 @@ impl Editor {
333316 _ => (), // skip
334317 }
335318 // TODO: simplistic ensure cursor in view for now
336− self.ensure_cursor_in_view();
319+ view.ensure_cursor_in_view();
337320
338321 self.render();
339322 }
340323 Mode::Normal => {
341324 // TODO: handle modes and sequences (`gg`)
342− if let Some(command) = keymap.get(&event) {
325+ let keys = vec![event];
326+ if let Some(command) = keymap[&Mode::Normal].get(&keys) {
343327 // TODO: handle count other than 1
344328 command(view, 1);
345329
346330 // TODO: simplistic ensure cursor in view for now
347− self.ensure_cursor_in_view();
331+ view.ensure_cursor_in_view();
348332
349333 self.render();
350334 }
@@ -361,26 +345,6 @@ impl Editor {
361345 }
362346 }
363347
364− fn ensure_cursor_in_view(&mut self) {
365− if let Some(view) = &mut self.view {
366− let cursor = view.state.selection().cursor();
367− let line = view.state.doc().char_to_line(cursor) as u16;
368− let document_end = view.first_line + self.size.1.saturating_sub(1) - 1;
369−
370− let padding = 5u16;
371−
372− // TODO: side scroll
373−
374− if line > document_end.saturating_sub(padding) {
375− // scroll down
376− view.first_line += line - (document_end.saturating_sub(padding));
377− } else if line < view.first_line + padding {
378− // scroll up
379− view.first_line = line.saturating_sub(padding);
380− }
381− }
382− }
383−
384348 pub async fn run(&mut self) -> Result<(), Error> {
385349 enable_raw_mode()?;
386350
helix-term/src/main.rs−5
@@ -1,11 +1,6 @@
11#![allow(unused)]
2−#[macro_use]
3−mod macros;
42
5−mod commands;
63mod editor;
7−mod keymap;
8−mod theme;
94
105use editor::Editor;
116
helix-view/Cargo.tomlnew file+18
@@ -0,0 +1,18 @@
1+[package]
2+name = "helix-view"
3+version = "0.1.0"
4+authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5+edition = "2018"
6+
7+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+[features]
10+term = ["tui", "crossterm"]
11+
12+[dependencies]
13+anyhow = "1.0.32"
14+helix-core = { path = "../helix-core" }
15+
16+# Conversion traits
17+tui = { version = "0.10.0", default-features = false, features = ["crossterm"], optional = true}
18+crossterm = { version = "0.17", features = ["event-stream"], optional = true}
helix-term/src/commands.rs → helix-view/src/commands.rsrenamed+2−2
@@ -1,10 +1,10 @@
11use helix_core::{
22 graphemes,
33 state::{Direction, Granularity, Mode, State},
4− ChangeSet, Range, Selection, Tendril, Transaction,
4+ Range, Selection, Tendril, Transaction,
55};
66
7−use crate::editor::View;
7+use crate::view::View;
88
99/// A command is a function that takes the current state and a count, and does a side-effect on the
1010/// state (usually by creating and applying a transaction).
helix-term/src/keymap.rs → helix-view/src/keymap.rsrenamed+51−54
@@ -1,9 +1,5 @@
11use crate::commands::{self, Command};
2−use crossterm::{
3− event::{KeyCode, KeyEvent as Key, KeyModifiers as Modifiers},
4− execute,
5− style::Print,
6−};
2+use helix_core::{hashmap, state};
73use std::collections::HashMap;
84
95// Kakoune-inspired:
@@ -79,56 +75,57 @@ use std::collections::HashMap;
7975// }
8076// }
8177
82−type Keymap = HashMap<Key, Command>;
78+#[cfg(feature = "term")]
79+pub use crossterm::event::{KeyCode, KeyEvent as Key, KeyModifiers as Modifiers};
8380
84−pub fn default() -> Keymap {
81+// TODO: could be trie based
82+type Keymap = HashMap<Vec<Key>, Command>;
83+type Keymaps = HashMap<state::Mode, Keymap>;
84+
85+pub fn default() -> Keymaps {
8586 hashmap!(
86− Key {
87− code: KeyCode::Char('h'),
88− modifiers: Modifiers::NONE
89− } => commands::move_char_left as Command,
90− Key {
91− code: KeyCode::Char('j'),
92− modifiers: Modifiers::NONE
93− } => commands::move_line_down as Command,
94− Key {
95− code: KeyCode::Char('k'),
96− modifiers: Modifiers::NONE
97− } => commands::move_line_up as Command,
98− Key {
99− code: KeyCode::Char('l'),
100− modifiers: Modifiers::NONE
101− } => commands::move_char_right as Command,
102− Key {
103− code: KeyCode::Char('i'),
104− modifiers: Modifiers::NONE
105− } => commands::insert_mode as Command,
106− Key {
107− code: KeyCode::Char('I'),
108− modifiers: Modifiers::SHIFT,
109− } => commands::prepend_to_line as Command,
110− Key {
111− code: KeyCode::Char('a'),
112− modifiers: Modifiers::NONE
113− } => commands::append_mode as Command,
114− Key {
115− code: KeyCode::Char('A'),
116− modifiers: Modifiers::SHIFT,
117− } => commands::append_to_line as Command,
118− Key {
119− code: KeyCode::Char('o'),
120− modifiers: Modifiers::NONE
121− } => commands::open_below as Command,
122− Key {
123− code: KeyCode::Esc,
124− modifiers: Modifiers::NONE
125− } => commands::normal_mode as Command,
87+ state::Mode::Normal =>
88+ hashmap!(
89+ vec![Key {
90+ code: KeyCode::Char('h'),
91+ modifiers: Modifiers::NONE
92+ }] => commands::move_char_left as Command,
93+ vec![Key {
94+ code: KeyCode::Char('j'),
95+ modifiers: Modifiers::NONE
96+ }] => commands::move_line_down as Command,
97+ vec![Key {
98+ code: KeyCode::Char('k'),
99+ modifiers: Modifiers::NONE
100+ }] => commands::move_line_up as Command,
101+ vec![Key {
102+ code: KeyCode::Char('l'),
103+ modifiers: Modifiers::NONE
104+ }] => commands::move_char_right as Command,
105+ vec![Key {
106+ code: KeyCode::Char('i'),
107+ modifiers: Modifiers::NONE
108+ }] => commands::insert_mode as Command,
109+ vec![Key {
110+ code: KeyCode::Char('I'),
111+ modifiers: Modifiers::SHIFT,
112+ }] => commands::prepend_to_line as Command,
113+ vec![Key {
114+ code: KeyCode::Char('a'),
115+ modifiers: Modifiers::NONE
116+ }] => commands::append_mode as Command,
117+ vec![Key {
118+ code: KeyCode::Char('A'),
119+ modifiers: Modifiers::SHIFT,
120+ }] => commands::append_to_line as Command,
121+ vec![Key {
122+ code: KeyCode::Char('o'),
123+ modifiers: Modifiers::NONE
124+ }] => commands::open_below as Command,
125+ vec![Key {
126+ code: KeyCode::Esc,
127+ modifiers: Modifiers::NONE
128+ }] => commands::normal_mode as Command,
129+ )
126130 )
127−
128− // hashmap!(
129− // Key {
130− // code: KeyCode::Esc,
131− // modifiers: Modifiers::NONE
132− // } => commands::normal_mode as Command,
133− // )
134131}
helix-view/src/lib.rsnew file+6
@@ -0,0 +1,6 @@
1+pub mod commands;
2+pub mod keymap;
3+pub mod theme;
4+pub mod view;
5+
6+pub use view::View;
helix-term/src/theme.rs → helix-view/src/theme.rsrenamed+82−1
@@ -1,5 +1,86 @@
1+use helix_core::hashmap;
12use std::collections::HashMap;
2−use tui::style::{Color, Style};
3+
4+#[cfg(feature = "term")]
5+pub use tui::style::{Color, Style};
6+
7+// #[derive(Clone, Copy, PartialEq, Eq, Default, Hash)]
8+// pub struct Color {
9+// pub r: u8,
10+// pub g: u8,
11+// pub b: u8,
12+// }
13+
14+// impl Color {
15+// pub fn new(r: u8, g: u8, b: u8) -> Self {
16+// Self { r, g, b }
17+// }
18+// }
19+
20+// #[cfg(feature = "term")]
21+// impl Into<tui::style::Color> for Color {
22+// fn into(self) -> tui::style::Color {
23+// tui::style::Color::Rgb(self.r, self.g, self.b)
24+// }
25+// }
26+
27+// impl std::str::FromStr for Color {
28+// type Err = ();
29+
30+// /// Tries to parse a string (`'#FFFFFF'` or `'FFFFFF'`) into RGB.
31+// fn from_str(input: &str) -> Result<Self, Self::Err> {
32+// let input = input.trim();
33+// let input = match (input.chars().next(), input.len()) {
34+// (Some('#'), 7) => &input[1..],
35+// (_, 6) => input,
36+// _ => return Err(()),
37+// };
38+
39+// u32::from_str_radix(&input, 16)
40+// .map(|s| Color {
41+// r: ((s >> 16) & 0xFF) as u8,
42+// g: ((s >> 8) & 0xFF) as u8,
43+// b: (s & 0xFF) as u8,
44+// })
45+// .map_err(|_| ())
46+// }
47+// }
48+
49+// #[derive(Clone, Copy, PartialEq, Eq, Default, Hash)]
50+// pub struct Style {
51+// pub fg: Option<Color>,
52+// pub bg: Option<Color>,
53+// // TODO: modifiers (bold, underline, italic, etc)
54+// }
55+
56+// impl Style {
57+// pub fn fg(mut self, fg: Color) -> Self {
58+// self.fg = Some(fg);
59+// self
60+// }
61+
62+// pub fn bg(mut self, bg: Color) -> Self {
63+// self.bg = Some(bg);
64+// self
65+// }
66+// }
67+
68+// #[cfg(feature = "term")]
69+// impl Into<tui::style::Style> for Style {
70+// fn into(self) -> tui::style::Style {
71+// let style = tui::style::Style::default();
72+
73+// if let Some(fg) = self.fg {
74+// style.fg(fg.into());
75+// }
76+
77+// if let Some(bg) = self.bg {
78+// style.bg(bg.into());
79+// }
80+
81+// style
82+// }
83+// }
384
485/// Color theme for syntax highlighting.
586pub struct Theme {
helix-view/src/view.rsnew file+48
@@ -0,0 +1,48 @@
1+use anyhow::Error;
2+
3+use std::path::PathBuf;
4+
5+use crate::theme::Theme;
6+use helix_core::State;
7+
8+pub struct View {
9+ pub state: State,
10+ pub first_line: u16,
11+ pub size: (u16, u16),
12+ pub theme: Theme, // TODO: share one instance
13+}
14+
15+impl View {
16+ pub fn open(path: PathBuf, size: (u16, u16)) -> Result<View, Error> {
17+ let mut state = State::load(path)?;
18+ let theme = Theme::default();
19+ state.syntax.as_mut().unwrap().configure(theme.scopes());
20+
21+ let view = View {
22+ state,
23+ first_line: 0,
24+ size, // TODO: pass in from term
25+ theme,
26+ };
27+
28+ Ok(view)
29+ }
30+
31+ pub fn ensure_cursor_in_view(&mut self) {
32+ let cursor = self.state.selection().cursor();
33+ let line = self.state.doc().char_to_line(cursor) as u16;
34+ let document_end = self.first_line + self.size.1.saturating_sub(1) - 1;
35+
36+ let padding = 5u16;
37+
38+ // TODO: side scroll
39+
40+ if line > document_end.saturating_sub(padding) {
41+ // scroll down
42+ self.first_line += line - (document_end.saturating_sub(padding));
43+ } else if line < self.first_line + padding {
44+ // scroll up
45+ self.first_line = line.saturating_sub(padding);
46+ }
47+ }
48+}