Move ui, keymap & commands to helix-view

gui
Blaž Hrastnik 2 years ago
parent 11b8f068da
commit 1aa2b027d7
No known key found for this signature in database
GPG Key ID: 1238B9C4AD889640

15
Cargo.lock generated

@ -878,24 +878,17 @@ dependencies = [
"anyhow", "anyhow",
"arc-swap", "arc-swap",
"chrono", "chrono",
"content_inspector",
"crossterm", "crossterm",
"fern", "fern",
"futures-util", "futures-util",
"fuzzy-matcher",
"grep-regex",
"grep-searcher",
"helix-core", "helix-core",
"helix-dap", "helix-dap",
"helix-loader", "helix-loader",
"helix-lsp", "helix-lsp",
"helix-tui", "helix-tui",
"helix-view", "helix-view",
"ignore",
"log", "log",
"once_cell", "once_cell",
"pulldown-cmark",
"retain_mut",
"ropey", "ropey",
"serde", "serde",
"serde_json", "serde_json",
@ -942,15 +935,23 @@ dependencies = [
"bitflags", "bitflags",
"chardetng", "chardetng",
"clipboard-win", "clipboard-win",
"content_inspector",
"crossterm", "crossterm",
"futures-util", "futures-util",
"fuzzy-matcher",
"grep-regex",
"grep-searcher",
"helix-core", "helix-core",
"helix-dap", "helix-dap",
"helix-graphics", "helix-graphics",
"helix-loader",
"helix-lsp", "helix-lsp",
"helix-tui", "helix-tui",
"ignore",
"log", "log",
"once_cell", "once_cell",
"pulldown-cmark",
"retain_mut",
"serde", "serde",
"serde_json", "serde_json",
"slotmap", "slotmap",

@ -51,27 +51,12 @@ fern = "0.6"
chrono = { version = "0.4", default-features = false, features = ["clock"] } chrono = { version = "0.4", default-features = false, features = ["clock"] }
log = "0.4" log = "0.4"
# File picker
fuzzy-matcher = "0.3"
ignore = "0.4"
# markdown doc rendering
pulldown-cmark = { version = "0.9", default-features = false }
# file type detection
content_inspector = "0.2.4"
# config # config
toml = "0.5" toml = "0.5"
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
# ripgrep for global search
grep-regex = "0.1.9"
grep-searcher = "0.1.8"
# Remove once retain_mut lands in stable rust
retain_mut = "0.1.7"
[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100 [target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100
signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }

@ -4,18 +4,20 @@ use helix_core::{
pos_at_coords, syntax, Selection, pos_at_coords, syntax, Selection,
}; };
#[cfg(feature = "lsp")]
use crate::commands::apply_workspace_edit;
#[cfg(feature = "lsp")] #[cfg(feature = "lsp")]
use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap}; use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
#[cfg(feature = "lsp")] #[cfg(feature = "lsp")]
use helix_view::commands::apply_workspace_edit;
#[cfg(feature = "lsp")]
use serde_json::json; use serde_json::json;
use helix_view::{align_view, editor::ConfigEvent, graphics::Rect, theme, Align, Editor}; use helix_view::{
align_view, editor::ConfigEvent, graphics::Rect, theme, true_color, Align, Editor,
};
use crate::{args::Args, config::Config};
use crate::{ use helix_view::{
args::Args,
config::Config,
keymap::Keymaps, keymap::Keymaps,
ui::{self, overlay::overlayed}, ui::{self, overlay::overlayed},
}; };
@ -96,7 +98,7 @@ impl Application {
&helix_loader::runtime_dir(), &helix_loader::runtime_dir(),
)); ));
let true_color = config.editor.true_color || crate::true_color(); let true_color = config.editor.true_color || true_color();
let theme = config let theme = config
.theme .theme
.as_ref() .as_ref()
@ -358,7 +360,7 @@ impl Application {
} }
fn true_color(&self) -> bool { fn true_color(&self) -> bool {
self.config.load().editor.true_color || crate::true_color() self.config.load().editor.true_color || true_color()
} }
#[cfg(windows)] #[cfg(windows)]

@ -1,6 +1,7 @@
use anyhow::Result; use anyhow::Result;
use helix_core::Position; use helix_core::Position;
use std::path::{Path, PathBuf}; use helix_view::args::parse_file;
use std::path::PathBuf;
#[derive(Default)] #[derive(Default)]
pub struct Args { pub struct Args {
@ -65,37 +66,3 @@ impl Args {
Ok(args) Ok(args)
} }
} }
/// Parse arg into [`PathBuf`] and position.
pub(crate) fn parse_file(s: &str) -> (PathBuf, Position) {
let def = || (PathBuf::from(s), Position::default());
if Path::new(s).exists() {
return def();
}
split_path_row_col(s)
.or_else(|| split_path_row(s))
.unwrap_or_else(def)
}
/// Split file.rs:10:2 into [`PathBuf`], row and col.
///
/// Does not validate if file.rs is a file or directory.
fn split_path_row_col(s: &str) -> Option<(PathBuf, Position)> {
let mut s = s.rsplitn(3, ':');
let col: usize = s.next()?.parse().ok()?;
let row: usize = s.next()?.parse().ok()?;
let path = s.next()?.into();
let pos = Position::new(row.saturating_sub(1), col.saturating_sub(1));
Some((path, pos))
}
/// Split file.rs:10 into [`PathBuf`] and row.
///
/// Does not validate if file.rs is a file or directory.
fn split_path_row(s: &str) -> Option<(PathBuf, Position)> {
let (path, row) = s.rsplit_once(':')?;
let row: usize = row.parse().ok()?;
let path = path.into();
let pos = Position::new(row.saturating_sub(1), 0);
Some((path, pos))
}

@ -1,5 +1,5 @@
use crate::keymap::{default::default, merge_keys, Keymap};
use helix_view::document::Mode; use helix_view::document::Mode;
use helix_view::keymap::{default::default, Keymap};
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Display; use std::fmt::Display;
@ -27,6 +27,15 @@ impl Default for Config {
} }
} }
/// Merge default config keys with user overwritten keys for custom user config.
pub fn merge_keys(mut config: Config) -> Config {
let mut delta = std::mem::replace(&mut config.keys, default());
for (mode, keys) in &mut config.keys {
keys.merge(delta.remove(mode).unwrap_or_default())
}
config
}
#[derive(Debug)] #[derive(Debug)]
pub enum ConfigLoadError { pub enum ConfigLoadError {
BadConfig(TomlError), BadConfig(TomlError),
@ -63,10 +72,9 @@ mod tests {
#[test] #[test]
fn parsing_keymaps_config_file() { fn parsing_keymaps_config_file() {
use crate::keymap;
use crate::keymap::Keymap;
use helix_core::hashmap; use helix_core::hashmap;
use helix_view::document::Mode; use helix_view::document::Mode;
use helix_view::keymap::{self, Keymap};
let sample_keymaps = r#" let sample_keymaps = r#"
[keys.insert] [keys.insert]
@ -104,4 +112,104 @@ mod tests {
let default_keys = Config::default().keys; let default_keys = Config::default().keys;
assert_eq!(default_keys, default()); assert_eq!(default_keys, default());
} }
use arc_swap::access::Constant;
use helix_core::hashmap;
#[test]
fn merge_partial_keys() {
let config = Config {
keys: hashmap! {
Mode::Normal => Keymap::new(
keymap!({ "Normal mode"
"i" => normal_mode,
"无" => insert_mode,
"z" => jump_backward,
"g" => { "Merge into goto mode"
"$" => goto_line_end,
"g" => delete_char_forward,
},
})
)
},
..Default::default()
};
let mut merged_config = merge_keys(config.clone());
assert_ne!(config, merged_config);
let mut keymap = Keymaps::new(Box::new(Constant(merged_config.keys.clone())));
assert_eq!(
keymap.get(Mode::Normal, key!('i')),
KeymapResult::Matched(MappableCommand::normal_mode),
"Leaf should replace leaf"
);
assert_eq!(
keymap.get(Mode::Normal, key!('无')),
KeymapResult::Matched(MappableCommand::insert_mode),
"New leaf should be present in merged keymap"
);
// Assumes that z is a node in the default keymap
assert_eq!(
keymap.get(Mode::Normal, key!('z')),
KeymapResult::Matched(MappableCommand::jump_backward),
"Leaf should replace node"
);
let keymap = merged_config.keys.get_mut(&Mode::Normal).unwrap();
// Assumes that `g` is a node in default keymap
assert_eq!(
keymap.root().search(&[key!('g'), key!('$')]).unwrap(),
&KeyTrie::Leaf(MappableCommand::goto_line_end),
"Leaf should be present in merged subnode"
);
// Assumes that `gg` is in default keymap
assert_eq!(
keymap.root().search(&[key!('g'), key!('g')]).unwrap(),
&KeyTrie::Leaf(MappableCommand::delete_char_forward),
"Leaf should replace old leaf in merged subnode"
);
// Assumes that `ge` is in default keymap
assert_eq!(
keymap.root().search(&[key!('g'), key!('e')]).unwrap(),
&KeyTrie::Leaf(MappableCommand::goto_last_line),
"Old leaves in subnode should be present in merged node"
);
assert!(merged_config.keys.get(&Mode::Normal).unwrap().len() > 1);
assert!(merged_config.keys.get(&Mode::Insert).unwrap().len() > 0);
}
#[test]
fn order_should_be_set() {
let config = Config {
keys: hashmap! {
Mode::Normal => Keymap::new(
keymap!({ "Normal mode"
"space" => { ""
"s" => { ""
"v" => vsplit,
"c" => hsplit,
},
},
})
)
},
..Default::default()
};
let mut merged_config = merge_keys(config.clone());
assert_ne!(config, merged_config);
let keymap = merged_config.keys.get_mut(&Mode::Normal).unwrap();
// Make sure mapping works
assert_eq!(
keymap
.root()
.search(&[key!(' '), key!('s'), key!('v')])
.unwrap(),
&KeyTrie::Leaf(MappableCommand::vsplit),
"Leaf should be present in merged subnode"
);
// Make sure an order was set during merge
let node = keymap.root().search(&[helix_view::key!(' ')]).unwrap();
assert!(!node.node().unwrap().order().is_empty())
}
} }

@ -1,127 +0,0 @@
#[macro_export]
macro_rules! key {
($key:ident) => {
::helix_view::input::KeyEvent {
code: ::helix_view::keyboard::KeyCode::$key,
modifiers: ::helix_view::keyboard::KeyModifiers::NONE,
}
};
($($ch:tt)*) => {
::helix_view::input::KeyEvent {
code: ::helix_view::keyboard::KeyCode::Char($($ch)*),
modifiers: ::helix_view::keyboard::KeyModifiers::NONE,
}
};
}
#[macro_export]
macro_rules! shift {
($key:ident) => {
::helix_view::input::KeyEvent {
code: ::helix_view::keyboard::KeyCode::$key,
modifiers: ::helix_view::keyboard::KeyModifiers::SHIFT,
}
};
($($ch:tt)*) => {
::helix_view::input::KeyEvent {
code: ::helix_view::keyboard::KeyCode::Char($($ch)*),
modifiers: ::helix_view::keyboard::KeyModifiers::SHIFT,
}
};
}
#[macro_export]
macro_rules! ctrl {
($key:ident) => {
::helix_view::input::KeyEvent {
code: ::helix_view::keyboard::KeyCode::$key,
modifiers: ::helix_view::keyboard::KeyModifiers::CONTROL,
}
};
($($ch:tt)*) => {
::helix_view::input::KeyEvent {
code: ::helix_view::keyboard::KeyCode::Char($($ch)*),
modifiers: ::helix_view::keyboard::KeyModifiers::CONTROL,
}
};
}
#[macro_export]
macro_rules! alt {
($key:ident) => {
::helix_view::input::KeyEvent {
code: ::helix_view::keyboard::KeyCode::$key,
modifiers: ::helix_view::keyboard::KeyModifiers::ALT,
}
};
($($ch:tt)*) => {
::helix_view::input::KeyEvent {
code: ::helix_view::keyboard::KeyCode::Char($($ch)*),
modifiers: ::helix_view::keyboard::KeyModifiers::ALT,
}
};
}
/// Macro for defining the root of a `Keymap` object. Example:
///
/// ```
/// # use helix_core::hashmap;
/// # use helix_term::keymap;
/// # use helix_term::keymap::Keymap;
/// let normal_mode = keymap!({ "Normal mode"
/// "i" => insert_mode,
/// "g" => { "Goto"
/// "g" => goto_file_start,
/// "e" => goto_file_end,
/// },
/// "j" | "down" => move_line_down,
/// });
/// let keymap = Keymap::new(normal_mode);
/// ```
#[macro_export]
macro_rules! keymap {
(@trie $cmd:ident) => {
$crate::keymap::KeyTrie::Leaf($crate::commands::MappableCommand::$cmd)
};
(@trie
{ $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }
) => {
keymap!({ $label $(sticky=$sticky)? $($($key)|+ => $value,)+ })
};
(@trie [$($cmd:ident),* $(,)?]) => {
$crate::keymap::KeyTrie::Sequence(vec![$($crate::commands::Command::$cmd),*])
};
(
{ $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }
) => {
// modified from the hashmap! macro
{
let _cap = hashmap!(@count $($($key),+),*);
let mut _map = ::std::collections::HashMap::with_capacity(_cap);
let mut _order = ::std::vec::Vec::with_capacity(_cap);
$(
$(
let _key = $key.parse::<::helix_view::input::KeyEvent>().unwrap();
let _duplicate = _map.insert(
_key,
keymap!(@trie $value)
);
assert!(_duplicate.is_none(), "Duplicate key found: {:?}", _duplicate.unwrap());
_order.push(_key);
)+
)*
let mut _node = $crate::keymap::KeyTrieNode::new($label, _map, _order);
$( _node.is_sticky = $sticky; )?
$crate::keymap::KeyTrie::Node(_node)
}
};
}
pub use alt;
pub use ctrl;
pub use key;
pub use keymap;
pub use shift;

@ -3,20 +3,5 @@ extern crate helix_view;
pub mod application; pub mod application;
pub mod args; pub mod args;
pub mod commands;
pub mod config; pub mod config;
pub mod health; pub mod health;
pub mod keymap;
pub mod ui;
pub use keymap::macros::*;
#[cfg(not(windows))]
fn true_color() -> bool {
std::env::var("COLORTERM")
.map(|v| matches!(v.as_str(), "truecolor" | "24bit"))
.unwrap_or(false)
}
#[cfg(windows)]
fn true_color() -> bool {
true
}

@ -23,6 +23,7 @@ helix-core = { version = "0.6", path = "../helix-core" }
helix-graphics = { version = "0.6", path = "../helix-graphics" } helix-graphics = { version = "0.6", path = "../helix-graphics" }
helix-lsp = { version = "0.6", path = "../helix-lsp", optional = true } helix-lsp = { version = "0.6", path = "../helix-lsp", optional = true }
helix-dap = { version = "0.6", path = "../helix-dap", optional = true } helix-dap = { version = "0.6", path = "../helix-dap", optional = true }
helix-loader = { version = "0.6", path = "../helix-loader" }
tokio-stream = { version = "0.1", optional = true } tokio-stream = { version = "0.1", optional = true }
tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"], optional = true } tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"], optional = true }
@ -46,6 +47,23 @@ serde_json = "1.0"
toml = "0.5" toml = "0.5"
log = "~0.4" log = "~0.4"
# Command dependencies
# File picker
fuzzy-matcher = "0.3"
ignore = "0.4"
# markdown doc rendering
pulldown-cmark = { version = "0.9", default-features = false }
# file type detection
content_inspector = "0.2.4"
# ripgrep for global search
grep-regex = "0.1.9"
grep-searcher = "0.1.8"
# Remove once retain_mut lands in stable rust
retain_mut = "0.1.7"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
which = "4.2" which = "4.2"

@ -0,0 +1,36 @@
use helix_core::Position;
use std::path::{Path, PathBuf};
/// Parse arg into [`PathBuf`] and position.
pub fn parse_file(s: &str) -> (PathBuf, Position) {
let def = || (PathBuf::from(s), Position::default());
if Path::new(s).exists() {
return def();
}
split_path_row_col(s)
.or_else(|| split_path_row(s))
.unwrap_or_else(def)
}
/// Split file.rs:10:2 into [`PathBuf`], row and col.
///
/// Does not validate if file.rs is a file or directory.
fn split_path_row_col(s: &str) -> Option<(PathBuf, Position)> {
let mut s = s.rsplitn(3, ':');
let col: usize = s.next()?.parse().ok()?;
let row: usize = s.next()?.parse().ok()?;
let path = s.next()?.into();
let pos = Position::new(row.saturating_sub(1), col.saturating_sub(1));
Some((path, pos))
}
/// Split file.rs:10 into [`PathBuf`] and row.
///
/// Does not validate if file.rs is a file or directory.
fn split_path_row(s: &str) -> Option<(PathBuf, Position)> {
let (path, row) = s.rsplit_once(':')?;
let row: usize = row.parse().ok()?;
let path = path.into();
let pos = Position::new(row.saturating_sub(1), 0);
Some((path, pos))
}

@ -1,13 +1,13 @@
use super::{Context, Editor}; use super::{Context, Editor};
use crate::editor::Breakpoint;
use crate::ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent, Text}; use crate::ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent, Text};
use helix_core::syntax::{DebugArgumentValue, DebugConfigCompletion}; use crate::{
use helix_dap::{self as dap, Client};
use helix_lsp::block_on;
use helix_view::editor::Breakpoint;
use helix_view::{
compositor::{self, Compositor}, compositor::{self, Compositor},
job::{Callback, Jobs}, job::{Callback, Jobs},
}; };
use helix_core::syntax::{DebugArgumentValue, DebugConfigCompletion};
use helix_dap::{self as dap, Client};
use helix_lsp::block_on;
use serde_json::{to_value, Value}; use serde_json::{to_value, Value};
use tokio_stream::wrappers::UnboundedReceiverStream; use tokio_stream::wrappers::UnboundedReceiverStream;
@ -18,7 +18,8 @@ use std::path::PathBuf;
use anyhow::{anyhow, bail}; use anyhow::{anyhow, bail};
use helix_view::handlers::dap::{breakpoints_changed, jump_to_stack_frame, select_thread_id}; use crate::debugger;
use crate::handlers::dap::{breakpoints_changed, jump_to_stack_frame, select_thread_id};
fn thread_picker( fn thread_picker(
cx: &mut Context, cx: &mut Context,
@ -474,7 +475,7 @@ pub fn dap_variables(cx: &mut Context) {
let text_style = theme.get("ui.text.focus"); let text_style = theme.get("ui.text.focus");
for scope in scopes.iter() { for scope in scopes.iter() {
// use helix_view::graphics::Style; // use crate::graphics::Style;
use tui::text::{Span, Spans}; use tui::text::{Span, Spans};
let response = block_on(debugger.variables(scope.variables_reference)); let response = block_on(debugger.variables(scope.variables_reference));

@ -7,10 +7,10 @@ use helix_lsp::{
use super::{align_view, push_jump, Align, Context, Editor}; use super::{align_view, push_jump, Align, Context, Editor};
use helix_core::Selection; use helix_core::Selection;
use helix_view::editor::Action; use crate::editor::Action;
use crate::ui::{self, overlay::overlayed, FileLocation, FilePicker, Popup, PromptEvent}; use crate::ui::{self, overlay::overlayed, FileLocation, FilePicker, Popup, PromptEvent};
use helix_view::compositor::{self, Compositor}; use crate::compositor::{self, Compositor};
use std::borrow::Cow; use std::borrow::Cow;
@ -255,7 +255,7 @@ pub fn code_action(cx: &mut Context) {
}); });
picker.move_down(); // pre-select the first item picker.move_down(); // pre-select the first item
let popup = Popup::new("code-action", picker).margin(helix_view::graphics::Margin { let popup = Popup::new("code-action", picker).margin(crate::graphics::Margin {
vertical: 1, vertical: 1,
horizontal: 1, horizontal: 1,
}); });

@ -10,6 +10,16 @@ pub use dap::*;
pub use lsp::*; pub use lsp::*;
pub use typed::*; pub use typed::*;
use crate::{
clipboard::ClipboardType,
document::{Mode, SCRATCH_BUFFER_NAME},
editor::{Action, Motion},
info::Info,
input::KeyEvent,
keyboard::KeyCode,
view::View,
Document, DocumentId, Editor, ViewId,
};
use helix_core::{ use helix_core::{
comment, coords_at_pos, find_first_non_whitespace_char, find_root, graphemes, comment, coords_at_pos, find_first_non_whitespace_char, find_root, graphemes,
history::UndoKind, history::UndoKind,
@ -29,18 +39,8 @@ use helix_core::{
LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril, LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril,
Transaction, Transaction,
}; };
use helix_view::{
clipboard::ClipboardType,
document::{Mode, SCRATCH_BUFFER_NAME},
editor::{Action, Motion},
info::Info,
input::KeyEvent,
keyboard::KeyCode,
view::View,
Document, DocumentId, Editor, ViewId,
};
use helix_view::{ use crate::{
compositor::{self, Component, Compositor}, compositor::{self, Component, Compositor},
job::{self, Job, Jobs}, job::{self, Job, Jobs},
}; };
@ -56,8 +56,13 @@ use crate::{
}; };
use futures_util::{FutureExt, StreamExt}; use futures_util::{FutureExt, StreamExt};
use std::{collections::HashMap, fmt, future::Future};
use std::{collections::HashSet, num::NonZeroUsize}; use std::{
collections::{HashMap, HashSet},
fmt,
future::Future,
num::NonZeroUsize,
};
use std::{ use std::{
borrow::Cow, borrow::Cow,
@ -77,7 +82,7 @@ pub struct Context<'a> {
pub count: Option<NonZeroUsize>, pub count: Option<NonZeroUsize>,
pub editor: &'a mut Editor, pub editor: &'a mut Editor,
pub callback: Option<helix_view::compositor::Callback>, pub callback: Option<crate::compositor::Callback>,
pub on_next_key_callback: Option<Box<dyn FnOnce(&mut Context, KeyEvent)>>, pub on_next_key_callback: Option<Box<dyn FnOnce(&mut Context, KeyEvent)>>,
pub jobs: &'a mut Jobs, pub jobs: &'a mut Jobs,
} }
@ -126,7 +131,7 @@ impl<'a> Context<'a> {
} }
} }
use helix_view::{align_view, Align}; use crate::{align_view, Align};
/// A MappableCommand is either a static command like "jump_view_up" or a Typable command like /// A MappableCommand is either a static command like "jump_view_up" or a Typable command like
/// :format. It causes a side-effect on the state (usually by creating and applying a transaction). /// :format. It causes a side-effect on the state (usually by creating and applying a transaction).
@ -4397,8 +4402,8 @@ fn shell_prompt(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBeha
} }
fn suspend(_cx: &mut Context) { fn suspend(_cx: &mut Context) {
#[cfg(not(windows))] // #[cfg(not(windows))]
signal_hook::low_level::raise(signal_hook::consts::signal::SIGTSTP).unwrap(); // signal_hook::low_level::raise(signal_hook::consts::signal::SIGTSTP).unwrap();
} }
fn add_newline_above(cx: &mut Context) { fn add_newline_above(cx: &mut Context) {
@ -4557,7 +4562,7 @@ fn record_macro(cx: &mut Context) {
fn replay_macro(cx: &mut Context) { fn replay_macro(cx: &mut Context) {
let reg = cx.register.unwrap_or('@'); let reg = cx.register.unwrap_or('@');
let keys: Vec<KeyEvent> = if let Some([keys_str]) = cx.editor.registers.read(reg) { let keys: Vec<KeyEvent> = if let Some([keys_str]) = cx.editor.registers.read(reg) {
match helix_view::input::parse_macro(keys_str) { match crate::input::parse_macro(keys_str) {
Ok(keys) => keys, Ok(keys) => keys,
Err(err) => { Err(err) => {
cx.editor.set_error(format!("Invalid macro: {}", err)); cx.editor.set_error(format!("Invalid macro: {}", err));

@ -1,6 +1,6 @@
use super::*; use super::*;
use helix_view::editor::{Action, ConfigEvent}; use crate::editor::{Action, ConfigEvent};
use ui::completers::{self, Completer}; use ui::completers::{self, Completer};
#[derive(Clone)] #[derive(Clone)]

@ -2,12 +2,11 @@ pub mod default;
pub mod macros; pub mod macros;
pub use crate::commands::MappableCommand; pub use crate::commands::MappableCommand;
use crate::config::Config; use crate::{document::Mode, info::Info, input::KeyEvent};
use arc_swap::{ use arc_swap::{
access::{DynAccess, DynGuard}, access::{DynAccess, DynGuard},
ArcSwap, ArcSwap,
}; };
use helix_view::{document::Mode, info::Info, input::KeyEvent};
use serde::Deserialize; use serde::Deserialize;
use std::{ use std::{
borrow::Cow, borrow::Cow,
@ -361,20 +360,10 @@ impl Default for Keymaps {
} }
} }
/// Merge default config keys with user overwritten keys for custom user config.
pub fn merge_keys(mut config: Config) -> Config {
let mut delta = std::mem::replace(&mut config.keys, default());
for (mode, keys) in &mut config.keys {
keys.merge(delta.remove(mode).unwrap_or_default())
}
config
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::macros::keymap; use super::macros::keymap;
use super::*; use super::*;
use arc_swap::access::Constant;
use helix_core::hashmap; use helix_core::hashmap;
#[test] #[test]
@ -392,103 +381,6 @@ mod tests {
Keymaps::default(); Keymaps::default();
} }
#[test]
fn merge_partial_keys() {
let config = Config {
keys: hashmap! {
Mode::Normal => Keymap::new(
keymap!({ "Normal mode"
"i" => normal_mode,
"无" => insert_mode,
"z" => jump_backward,
"g" => { "Merge into goto mode"
"$" => goto_line_end,
"g" => delete_char_forward,
},
})
)
},
..Default::default()
};
let mut merged_config = merge_keys(config.clone());
assert_ne!(config, merged_config);
let mut keymap = Keymaps::new(Box::new(Constant(merged_config.keys.clone())));
assert_eq!(
keymap.get(Mode::Normal, key!('i')),
KeymapResult::Matched(MappableCommand::normal_mode),
"Leaf should replace leaf"
);
assert_eq!(
keymap.get(Mode::Normal, key!('无')),
KeymapResult::Matched(MappableCommand::insert_mode),
"New leaf should be present in merged keymap"
);
// Assumes that z is a node in the default keymap
assert_eq!(
keymap.get(Mode::Normal, key!('z')),
KeymapResult::Matched(MappableCommand::jump_backward),
"Leaf should replace node"
);
let keymap = merged_config.keys.get_mut(&Mode::Normal).unwrap();
// Assumes that `g` is a node in default keymap
assert_eq!(
keymap.root().search(&[key!('g'), key!('$')]).unwrap(),
&KeyTrie::Leaf(MappableCommand::goto_line_end),
"Leaf should be present in merged subnode"
);
// Assumes that `gg` is in default keymap
assert_eq!(
keymap.root().search(&[key!('g'), key!('g')]).unwrap(),
&KeyTrie::Leaf(MappableCommand::delete_char_forward),
"Leaf should replace old leaf in merged subnode"
);
// Assumes that `ge` is in default keymap
assert_eq!(
keymap.root().search(&[key!('g'), key!('e')]).unwrap(),
&KeyTrie::Leaf(MappableCommand::goto_last_line),
"Old leaves in subnode should be present in merged node"
);
assert!(merged_config.keys.get(&Mode::Normal).unwrap().len() > 1);
assert!(merged_config.keys.get(&Mode::Insert).unwrap().len() > 0);
}
#[test]
fn order_should_be_set() {
let config = Config {
keys: hashmap! {
Mode::Normal => Keymap::new(
keymap!({ "Normal mode"
"space" => { ""
"s" => { ""
"v" => vsplit,
"c" => hsplit,
},
},
})
)
},
..Default::default()
};
let mut merged_config = merge_keys(config.clone());
assert_ne!(config, merged_config);
let keymap = merged_config.keys.get_mut(&Mode::Normal).unwrap();
// Make sure mapping works
assert_eq!(
keymap
.root()
.search(&[key!(' '), key!('s'), key!('v')])
.unwrap(),
&KeyTrie::Leaf(MappableCommand::vsplit),
"Leaf should be present in merged subnode"
);
// Make sure an order was set during merge
let node = keymap.root().search(&[crate::key!(' ')]).unwrap();
assert!(!node.node().unwrap().order().is_empty())
}
#[test] #[test]
fn aliased_modes_are_same_in_default_keymap() { fn aliased_modes_are_same_in_default_keymap() {
let keymaps = Keymaps::default().map(); let keymaps = Keymaps::default().map();

@ -0,0 +1,60 @@
/// Macro for defining the root of a `Keymap` object. Example:
///
/// ```
/// # use helix_core::hashmap;
/// # use helix_term::keymap;
/// # use helix_term::keymap::Keymap;
/// let normal_mode = keymap!({ "Normal mode"
/// "i" => insert_mode,
/// "g" => { "Goto"
/// "g" => goto_file_start,
/// "e" => goto_file_end,
/// },
/// "j" | "down" => move_line_down,
/// });
/// let keymap = Keymap::new(normal_mode);
/// ```
#[macro_export]
macro_rules! keymap {
(@trie $cmd:ident) => {
$crate::keymap::KeyTrie::Leaf($crate::commands::MappableCommand::$cmd)
};
(@trie
{ $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }
) => {
keymap!({ $label $(sticky=$sticky)? $($($key)|+ => $value,)+ })
};
(@trie [$($cmd:ident),* $(,)?]) => {
$crate::keymap::KeyTrie::Sequence(vec![$($crate::commands::Command::$cmd),*])
};
(
{ $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }
) => {
// modified from the hashmap! macro
{
let _cap = hashmap!(@count $($($key),+),*);
let mut _map = ::std::collections::HashMap::with_capacity(_cap);
let mut _order = ::std::vec::Vec::with_capacity(_cap);
$(
$(
let _key = $key.parse::<$crate::input::KeyEvent>().unwrap();
let _duplicate = _map.insert(
_key,
keymap!(@trie $value)
);
assert!(_duplicate.is_none(), "Duplicate key found: {:?}", _duplicate.unwrap());
_order.push(_key);
)+
)*
let mut _node = $crate::keymap::KeyTrieNode::new($label, _map, _order);
$( _node.is_sticky = $sticky; )?
$crate::keymap::KeyTrie::Node(_node)
}
};
}
pub use crate::{alt, ctrl, key, shift};
pub use keymap;

@ -1,17 +1,22 @@
#[macro_use] #[macro_use]
pub mod macros; pub mod macros;
pub mod args;
pub mod backend { pub mod backend {
#[cfg(feature = "term")] #[cfg(feature = "term")]
pub mod term; pub mod term;
} }
pub mod clipboard; pub mod clipboard;
pub mod commands;
pub mod compositor; pub mod compositor;
pub mod document; pub mod document;
pub mod editor; pub mod editor;
pub mod ui;
pub use helix_graphics as graphics; pub use helix_graphics as graphics;
pub mod gutter; pub mod gutter;
pub mod job; pub mod job;
pub mod keymap;
pub use keymap::macros::*;
pub mod handlers { pub mod handlers {
#[cfg(feature = "dap")] #[cfg(feature = "dap")]
pub mod dap; pub mod dap;
@ -47,6 +52,17 @@ slotmap::new_key_type! {
pub struct ViewId; pub struct ViewId;
} }
#[cfg(not(windows))]
pub fn true_color() -> bool {
std::env::var("COLORTERM")
.map(|v| matches!(v.as_str(), "truecolor" | "24bit"))
.unwrap_or(false)
}
#[cfg(windows)]
pub fn true_color() -> bool {
true
}
pub enum Align { pub enum Align {
Top, Top,
Center, Center,

@ -61,3 +61,69 @@ macro_rules! doc {
$crate::current_ref!($editor).1 $crate::current_ref!($editor).1
}}; }};
} }
// Keymap macros
#[macro_export]
macro_rules! key {
($key:ident) => {
$crate::input::KeyEvent {
code: $crate::keyboard::KeyCode::$key,
modifiers: $crate::keyboard::KeyModifiers::NONE,
}
};
($($ch:tt)*) => {
$crate::input::KeyEvent {
code: $crate::keyboard::KeyCode::Char($($ch)*),
modifiers: $crate::keyboard::KeyModifiers::NONE,
}
};
}
#[macro_export]
macro_rules! shift {
($key:ident) => {
$crate::input::KeyEvent {
code: $crate::keyboard::KeyCode::$key,
modifiers: $crate::keyboard::KeyModifiers::SHIFT,
}
};
($($ch:tt)*) => {
$crate::input::KeyEvent {
code: $crate::keyboard::KeyCode::Char($($ch)*),
modifiers: $crate::keyboard::KeyModifiers::SHIFT,
}
};
}
#[macro_export]
macro_rules! ctrl {
($key:ident) => {
$crate::input::KeyEvent {
code: $crate::keyboard::KeyCode::$key,
modifiers: $crate::keyboard::KeyModifiers::CONTROL,
}
};
($($ch:tt)*) => {
$crate::input::KeyEvent {
code: $crate::keyboard::KeyCode::Char($($ch)*),
modifiers: $crate::keyboard::KeyModifiers::CONTROL,
}
};
}
#[macro_export]
macro_rules! alt {
($key:ident) => {
$crate::input::KeyEvent {
code: $crate::keyboard::KeyCode::$key,
modifiers: $crate::keyboard::KeyModifiers::ALT,
}
};
($($ch:tt)*) => {
$crate::input::KeyEvent {
code: $crate::keyboard::KeyCode::Char($($ch)*),
modifiers: $crate::keyboard::KeyModifiers::ALT,
}
};
}

@ -1,10 +1,10 @@
use helix_view::compositor::{Component, Context, Event, EventResult, RenderContext}; use crate::compositor::{Component, Context, Event, EventResult, RenderContext};
use helix_view::editor::CompleteAction; use crate::editor::CompleteAction;
use std::borrow::Cow; use std::borrow::Cow;
use helix_core::{Change, Transaction}; use helix_core::{Change, Transaction};
use helix_view::{ use crate::{
graphics::Rect, graphics::Rect,
input::{KeyCode, KeyEvent}, input::{KeyCode, KeyEvent},
Document, Editor, Document, Editor,

@ -4,7 +4,7 @@ use crate::{
ui::{Completion, ProgressSpinners}, ui::{Completion, ProgressSpinners},
}; };
use helix_view::compositor::{Component, Context, Event, EventResult, RenderContext}; use crate::compositor::{Component, Context, Event, EventResult, RenderContext};
use helix_core::{ use helix_core::{
coords_at_pos, encoding, coords_at_pos, encoding,
@ -17,7 +17,7 @@ use helix_core::{
unicode::width::UnicodeWidthStr, unicode::width::UnicodeWidthStr,
LineEnding, Position, Range, Selection, Transaction, LineEnding, Position, Range, Selection, Transaction,
}; };
use helix_view::{ use crate::{
document::{Mode, SCRATCH_BUFFER_NAME}, document::{Mode, SCRATCH_BUFFER_NAME},
editor::{CompleteAction, CursorShapeConfig}, editor::{CompleteAction, CursorShapeConfig},
graphics::{CursorKind, Modifier, Rect, Style}, graphics::{CursorKind, Modifier, Rect, Style},
@ -928,7 +928,7 @@ impl EditorView {
editor.clear_idle_timer(); // don't retrigger editor.clear_idle_timer(); // don't retrigger
} }
pub fn handle_idle_timeout(&mut self, cx: &mut helix_view::compositor::Context) -> EventResult { pub fn handle_idle_timeout(&mut self, cx: &mut crate::compositor::Context) -> EventResult {
if self.completion.is_some() if self.completion.is_some()
|| !cx.editor.config().auto_completion || !cx.editor.config().auto_completion
|| doc!(cx.editor).mode != Mode::Insert || doc!(cx.editor).mode != Mode::Insert
@ -1163,7 +1163,7 @@ impl Component for EditorView {
fn handle_event( fn handle_event(
&mut self, &mut self,
event: Event, event: Event,
context: &mut helix_view::compositor::Context, context: &mut crate::compositor::Context,
) -> EventResult { ) -> EventResult {
let mut cx = commands::Context { let mut cx = commands::Context {
editor: context.editor, editor: context.editor,
@ -1314,7 +1314,7 @@ impl Component for EditorView {
// render status msg // render status msg
if let Some((status_msg, severity)) = &cx.editor.status_msg { if let Some((status_msg, severity)) = &cx.editor.status_msg {
status_msg_width = status_msg.width(); status_msg_width = status_msg.width();
use helix_view::editor::Severity; use crate::editor::Severity;
let style = if *severity == Severity::Error { let style = if *severity == Severity::Error {
cx.editor.theme.get("error") cx.editor.theme.get("error")
} else { } else {
@ -1361,7 +1361,7 @@ impl Component for EditorView {
if let Some((reg, _)) = cx.editor.macro_recording { if let Some((reg, _)) = cx.editor.macro_recording {
let disp = format!("[{}]", reg); let disp = format!("[{}]", reg);
let style = style let style = style
.fg(helix_view::graphics::Color::Yellow) .fg(crate::graphics::Color::Yellow)
.add_modifier(Modifier::BOLD); .add_modifier(Modifier::BOLD);
cx.surface.set_string( cx.surface.set_string(
area.x + area.width.saturating_sub(3), area.x + area.width.saturating_sub(3),

@ -1,4 +1,4 @@
use helix_view::compositor::{Component, RenderContext}; use crate::compositor::{Component, RenderContext};
use tui::text::{Span, Spans, Text}; use tui::text::{Span, Spans, Text};
use std::sync::Arc; use std::sync::Arc;
@ -9,7 +9,7 @@ use helix_core::{
syntax::{self, HighlightEvent, Syntax}, syntax::{self, HighlightEvent, Syntax},
Rope, Rope,
}; };
use helix_view::{ use crate::{
graphics::{Margin, Rect, Style}, graphics::{Margin, Rect, Style},
Theme, Theme,
}; };

@ -1,5 +1,5 @@
use crate::{ctrl, key, shift}; use crate::{ctrl, key, shift};
use helix_view::compositor::{ use crate::compositor::{
Callback, Component, Compositor, Context, Event, EventResult, RenderContext, Callback, Component, Compositor, Context, Event, EventResult, RenderContext,
}; };
use tui::widgets::Table; use tui::widgets::Table;
@ -9,7 +9,7 @@ pub use tui::widgets::{Cell, Row};
use fuzzy_matcher::skim::SkimMatcherV2 as Matcher; use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
use fuzzy_matcher::FuzzyMatcher; use fuzzy_matcher::FuzzyMatcher;
use helix_view::{graphics::Rect, Editor}; use crate::{graphics::Rect, Editor};
use tui::layout::Constraint; use tui::layout::Constraint;
pub trait Item { pub trait Item {

@ -19,9 +19,9 @@ pub use prompt::{Prompt, PromptEvent};
pub use spinner::{ProgressSpinners, Spinner}; pub use spinner::{ProgressSpinners, Spinner};
pub use text::Text; pub use text::Text;
use crate::{Document, Editor, View};
use helix_core::regex::Regex; use helix_core::regex::Regex;
use helix_core::regex::RegexBuilder; use helix_core::regex::RegexBuilder;
use helix_view::{Document, Editor, View};
use std::path::PathBuf; use std::path::PathBuf;
@ -30,7 +30,7 @@ pub fn prompt(
prompt: std::borrow::Cow<'static, str>, prompt: std::borrow::Cow<'static, str>,
history_register: Option<char>, history_register: Option<char>,
completion_fn: impl FnMut(&Editor, &str) -> Vec<prompt::Completion> + 'static, completion_fn: impl FnMut(&Editor, &str) -> Vec<prompt::Completion> + 'static,
callback_fn: impl FnMut(&mut helix_view::compositor::Context, &str, PromptEvent) + 'static, callback_fn: impl FnMut(&mut crate::compositor::Context, &str, PromptEvent) + 'static,
) { ) {
let mut prompt = Prompt::new(prompt, history_register, completion_fn, callback_fn); let mut prompt = Prompt::new(prompt, history_register, completion_fn, callback_fn);
// Calculate initial completion // Calculate initial completion
@ -55,7 +55,7 @@ pub fn regex_prompt(
prompt, prompt,
history_register, history_register,
completion_fn, completion_fn,
move |cx: &mut helix_view::compositor::Context, input: &str, event: PromptEvent| { move |cx: &mut crate::compositor::Context, input: &str, event: PromptEvent| {
match event { match event {
PromptEvent::Abort => { PromptEvent::Abort => {
let (view, doc) = current!(cx.editor); let (view, doc) = current!(cx.editor);
@ -111,7 +111,7 @@ pub fn regex_prompt(
cx.push_layer(Box::new(prompt)); cx.push_layer(Box::new(prompt));
} }
pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePicker<PathBuf> { pub fn file_picker(root: PathBuf, config: &crate::editor::Config) -> FilePicker<PathBuf> {
use ignore::{types::TypesBuilder, WalkBuilder}; use ignore::{types::TypesBuilder, WalkBuilder};
use std::time::Instant; use std::time::Instant;
@ -188,12 +188,12 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi
} }
pub mod completers { pub mod completers {
use crate::document::SCRATCH_BUFFER_NAME;
use crate::theme;
use crate::ui::prompt::Completion; use crate::ui::prompt::Completion;
use crate::{editor::Config, Editor};
use fuzzy_matcher::skim::SkimMatcherV2 as Matcher; use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
use fuzzy_matcher::FuzzyMatcher; use fuzzy_matcher::FuzzyMatcher;
use helix_view::document::SCRATCH_BUFFER_NAME;
use helix_view::theme;
use helix_view::{editor::Config, Editor};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::borrow::Cow; use std::borrow::Cow;
use std::cmp::Reverse; use std::cmp::Reverse;

@ -1,10 +1,10 @@
use helix_core::Position; use helix_core::Position;
use helix_view::{ use crate::{
graphics::{CursorKind, Rect}, graphics::{CursorKind, Rect},
Editor, Editor,
}; };
use helix_view::compositor::{Component, Context, Event, EventResult, RenderContext}; use crate::compositor::{Component, Context, Event, EventResult, RenderContext};
/// Contains a component placed in the center of the parent component /// Contains a component placed in the center of the parent component
pub struct Overlay<T> { pub struct Overlay<T> {

@ -2,7 +2,7 @@ use crate::{
ctrl, key, shift, ctrl, key, shift,
ui::{self, EditorView}, ui::{self, EditorView},
}; };
use helix_view::compositor::{Component, Compositor, Context, Event, EventResult, RenderContext}; use crate::compositor::{Component, Compositor, Context, Event, EventResult, RenderContext};
use tui::widgets::{Block, BorderType, Borders}; use tui::widgets::{Block, BorderType, Borders};
use fuzzy_matcher::skim::SkimMatcherV2 as Matcher; use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
@ -20,7 +20,7 @@ use std::{
use crate::ui::{Prompt, PromptEvent}; use crate::ui::{Prompt, PromptEvent};
use helix_core::{movement::Direction, Position}; use helix_core::{movement::Direction, Position};
use helix_view::{ use crate::{
editor::Action, editor::Action,
graphics::{Color, CursorKind, Margin, Modifier, Rect, Style}, graphics::{Color, CursorKind, Margin, Modifier, Rect, Style},
Document, Editor, Document, Editor,

@ -1,8 +1,8 @@
use crate::{ctrl, key}; use crate::{ctrl, key};
use helix_view::compositor::{Callback, Component, Context, Event, EventResult, RenderContext}; use crate::compositor::{Callback, Component, Context, Event, EventResult, RenderContext};
use helix_core::Position; use helix_core::Position;
use helix_view::{ use crate::{
graphics::{Margin, Rect}, graphics::{Margin, Rect},
Editor, Editor,
}; };

@ -1,17 +1,17 @@
use helix_view::compositor::{Component, Compositor, Context, Event, EventResult, RenderContext}; use crate::compositor::{Component, Compositor, Context, Event, EventResult, RenderContext};
use crate::input::KeyEvent;
use crate::keyboard::KeyCode;
use crate::{alt, ctrl, key, shift, ui}; use crate::{alt, ctrl, key, shift, ui};
use helix_view::input::KeyEvent;
use helix_view::keyboard::KeyCode;
use std::{borrow::Cow, ops::RangeFrom}; use std::{borrow::Cow, ops::RangeFrom};
use tui::widgets::{Block, Borders, Widget}; use tui::widgets::{Block, Borders, Widget};
use helix_core::{ use crate::{
unicode::segmentation::GraphemeCursor, unicode::width::UnicodeWidthStr, Position,
};
use helix_view::{
graphics::{CursorKind, Margin, Rect}, graphics::{CursorKind, Margin, Rect},
Editor, Editor,
}; };
use helix_core::{
unicode::segmentation::GraphemeCursor, unicode::width::UnicodeWidthStr, Position,
};
pub type Completion = (RangeFrom<usize>, Cow<'static, str>); pub type Completion = (RangeFrom<usize>, Cow<'static, str>);

@ -1,6 +1,5 @@
use helix_view::compositor::{Component, RenderContext}; use crate::compositor::{Component, RenderContext};
use crate::graphics::Rect;
use helix_view::graphics::Rect;
pub struct Text { pub struct Text {
pub(crate) contents: tui::text::Text<'static>, pub(crate) contents: tui::text::Text<'static>,
Loading…
Cancel
Save