aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock12
-rw-r--r--Cargo.toml1
-rw-r--r--README.md9
-rw-r--r--helix-core/src/lib.rs2
-rw-r--r--helix-core/src/macros.rs (renamed from helix-term/src/macros.rs)1
-rw-r--r--helix-core/src/state.rs2
-rw-r--r--helix-term/Cargo.toml4
-rw-r--r--helix-term/src/editor.rs68
-rw-r--r--helix-term/src/main.rs5
-rw-r--r--helix-view/Cargo.toml18
-rw-r--r--helix-view/src/commands.rs (renamed from helix-term/src/commands.rs)4
-rw-r--r--helix-view/src/keymap.rs (renamed from helix-term/src/keymap.rs)105
-rw-r--r--helix-view/src/lib.rs6
-rw-r--r--helix-view/src/theme.rs (renamed from helix-term/src/theme.rs)83
-rw-r--r--helix-view/src/view.rs48
15 files changed, 250 insertions, 118 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f9b52d7d..f37c951c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -410,13 +410,23 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index db3530c9..25dbe725 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 325ff522..3a0ac317 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 c617fdbf..e443168e 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
index 3b22a786..1321ea5f 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 5b5f06c0..79e15eff 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 e0b96ead..d32fc0b9 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 0542d217..e29e7ee8 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<CrosstermBackend<std::io::Stdout>>;
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<View>,
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 aca04641..b691eb65 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 00000000..37957625
--- /dev/null
+++ b/helix-view/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "helix-view"
+version = "0.1.0"
+authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
+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
index 269a7743..560167c9 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
index d52ccca4..705357a8 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<Key, Command>;
+#[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<Vec<Key>, Command>;
+type Keymaps = HashMap<state::Mode, Keymap>;
+
+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 00000000..2a000f32
--- /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
index 4b2f102e..d61457d7 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<tui::style::Color> 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<Self, Self::Err> {
+// 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<Color>,
+// pub bg: Option<Color>,
+// // 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<tui::style::Style> 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 00000000..3f7a9974
--- /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<View, Error> {
+ 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);
+ }
+ }
+}