mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-24 18:36:18 +04:00
Split parts of helix-term into helix-view.
It still largely depends on term for some types but I plan to change that later.
This commit is contained in:
parent
48330ddb5f
commit
935cfeae57
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -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"
|
||||
|
@ -1,6 +1,7 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"helix-core",
|
||||
"helix-view",
|
||||
"helix-term",
|
||||
"helix-syntax",
|
||||
]
|
||||
|
@ -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)
|
||||
|
@ -1,5 +1,7 @@
|
||||
#![allow(unused)]
|
||||
pub mod config;
|
||||
pub mod graphemes;
|
||||
pub mod macros;
|
||||
mod position;
|
||||
mod selection;
|
||||
pub mod state;
|
||||
|
@ -1,3 +1,4 @@
|
||||
#[macro_export]
|
||||
macro_rules! hashmap {
|
||||
(@single $($x:tt)*) => (());
|
||||
(@count $($rest:expr),*) => (<[()]>::len(&[$(hashmap!(@single $rest)),*]));
|
@ -4,7 +4,7 @@
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Mode {
|
||||
Normal,
|
||||
Insert,
|
||||
|
@ -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"] }
|
||||
|
@ -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 @@
|
||||
|
||||
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 @@ pub fn new(mut args: Args) -> Result<Self, Error> {
|
||||
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 @@ pub fn new(mut args: Args) -> Result<Self, Error> {
|
||||
size,
|
||||
surface: Surface::empty(area),
|
||||
cache: Surface::empty(area),
|
||||
theme,
|
||||
// TODO; move to state
|
||||
};
|
||||
|
||||
@ -73,20 +65,7 @@ pub fn new(mut args: Args) -> Result<Self, Error> {
|
||||
}
|
||||
|
||||
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 @@ fn render(&mut self) {
|
||||
|
||||
// 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 @@ fn render(&mut self) {
|
||||
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 @@ fn render(&mut self) {
|
||||
}
|
||||
|
||||
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 @@ fn render(&mut self) {
|
||||
};
|
||||
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 @@ pub async fn event_loop(&mut self) {
|
||||
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 @@ pub async fn event_loop(&mut self) {
|
||||
_ => (), // 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 @@ pub async fn event_loop(&mut self) {
|
||||
}
|
||||
}
|
||||
|
||||
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()?;
|
||||
|
||||
|
@ -1,11 +1,6 @@
|
||||
#![allow(unused)]
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
mod commands;
|
||||
mod editor;
|
||||
mod keymap;
|
||||
mod theme;
|
||||
|
||||
use editor::Editor;
|
||||
|
||||
|
18
helix-view/Cargo.toml
Normal file
18
helix-view/Cargo.toml
Normal file
@ -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}
|
@ -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).
|
@ -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 @@
|
||||
// }
|
||||
// }
|
||||
|
||||
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,
|
||||
// )
|
||||
}
|
6
helix-view/src/lib.rs
Normal file
6
helix-view/src/lib.rs
Normal file
@ -0,0 +1,6 @@
|
||||
pub mod commands;
|
||||
pub mod keymap;
|
||||
pub mod theme;
|
||||
pub mod view;
|
||||
|
||||
pub use view::View;
|
@ -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 {
|
48
helix-view/src/view.rs
Normal file
48
helix-view/src/view.rs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user