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

@ -51,27 +51,12 @@ fern = "0.6"
chrono = { version = "0.4", default-features = false, features = ["clock"] }
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
toml = "0.5"
serde_json = "1.0"
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
signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }

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

@ -1,6 +1,7 @@
use anyhow::Result;
use helix_core::Position;
use std::path::{Path, PathBuf};
use helix_view::args::parse_file;
use std::path::PathBuf;
#[derive(Default)]
pub struct Args {
@ -65,37 +66,3 @@ impl 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::keymap::{default::default, Keymap};
use serde::Deserialize;
use std::collections::HashMap;
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)]
pub enum ConfigLoadError {
BadConfig(TomlError),
@ -63,10 +72,9 @@ mod tests {
#[test]
fn parsing_keymaps_config_file() {
use crate::keymap;
use crate::keymap::Keymap;
use helix_core::hashmap;
use helix_view::document::Mode;
use helix_view::keymap::{self, Keymap};
let sample_keymaps = r#"
[keys.insert]
@ -104,4 +112,104 @@ mod tests {
let default_keys = Config::default().keys;
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 args;
pub mod commands;
pub mod config;
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-lsp = { version = "0.6", path = "../helix-lsp", 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 = { 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"
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]
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 crate::editor::Breakpoint;
use crate::ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent, Text};
use helix_core::syntax::{DebugArgumentValue, DebugConfigCompletion};
use helix_dap::{self as dap, Client};
use helix_lsp::block_on;
use helix_view::editor::Breakpoint;
use helix_view::{
use crate::{
compositor::{self, Compositor},
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 tokio_stream::wrappers::UnboundedReceiverStream;
@ -18,7 +18,8 @@ use std::path::PathBuf;
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(
cx: &mut Context,
@ -474,7 +475,7 @@ pub fn dap_variables(cx: &mut Context) {
let text_style = theme.get("ui.text.focus");
for scope in scopes.iter() {
// use helix_view::graphics::Style;
// use crate::graphics::Style;
use tui::text::{Span, Spans};
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 helix_core::Selection;
use helix_view::editor::Action;
use crate::editor::Action;
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;
@ -255,7 +255,7 @@ pub fn code_action(cx: &mut Context) {
});
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,
horizontal: 1,
});

@ -10,6 +10,16 @@ pub use dap::*;
pub use lsp::*;
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::{
comment, coords_at_pos, find_first_non_whitespace_char, find_root, graphemes,
history::UndoKind,
@ -29,18 +39,8 @@ use helix_core::{
LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril,
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},
job::{self, Job, Jobs},
};
@ -56,8 +56,13 @@ use crate::{
};
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::{
borrow::Cow,
@ -77,7 +82,7 @@ pub struct Context<'a> {
pub count: Option<NonZeroUsize>,
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 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
/// :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) {
#[cfg(not(windows))]
signal_hook::low_level::raise(signal_hook::consts::signal::SIGTSTP).unwrap();
// #[cfg(not(windows))]
// signal_hook::low_level::raise(signal_hook::consts::signal::SIGTSTP).unwrap();
}
fn add_newline_above(cx: &mut Context) {
@ -4557,7 +4562,7 @@ fn record_macro(cx: &mut Context) {
fn replay_macro(cx: &mut Context) {
let reg = cx.register.unwrap_or('@');
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,
Err(err) => {
cx.editor.set_error(format!("Invalid macro: {}", err));

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

@ -2,12 +2,11 @@ pub mod default;
pub mod macros;
pub use crate::commands::MappableCommand;
use crate::config::Config;
use crate::{document::Mode, info::Info, input::KeyEvent};
use arc_swap::{
access::{DynAccess, DynGuard},
ArcSwap,
};
use helix_view::{document::Mode, info::Info, input::KeyEvent};
use serde::Deserialize;
use std::{
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)]
mod tests {
use super::macros::keymap;
use super::*;
use arc_swap::access::Constant;
use helix_core::hashmap;
#[test]
@ -392,103 +381,6 @@ mod tests {
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]
fn aliased_modes_are_same_in_default_keymap() {
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]
pub mod macros;
pub mod args;
pub mod backend {
#[cfg(feature = "term")]
pub mod term;
}
pub mod clipboard;
pub mod commands;
pub mod compositor;
pub mod document;
pub mod editor;
pub mod ui;
pub use helix_graphics as graphics;
pub mod gutter;
pub mod job;
pub mod keymap;
pub use keymap::macros::*;
pub mod handlers {
#[cfg(feature = "dap")]
pub mod dap;
@ -47,6 +52,17 @@ slotmap::new_key_type! {
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 {
Top,
Center,

@ -61,3 +61,69 @@ macro_rules! doc {
$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 helix_view::editor::CompleteAction;
use crate::compositor::{Component, Context, Event, EventResult, RenderContext};
use crate::editor::CompleteAction;
use std::borrow::Cow;
use helix_core::{Change, Transaction};
use helix_view::{
use crate::{
graphics::Rect,
input::{KeyCode, KeyEvent},
Document, Editor,

@ -4,7 +4,7 @@ use crate::{
ui::{Completion, ProgressSpinners},
};
use helix_view::compositor::{Component, Context, Event, EventResult, RenderContext};
use crate::compositor::{Component, Context, Event, EventResult, RenderContext};
use helix_core::{
coords_at_pos, encoding,
@ -17,7 +17,7 @@ use helix_core::{
unicode::width::UnicodeWidthStr,
LineEnding, Position, Range, Selection, Transaction,
};
use helix_view::{
use crate::{
document::{Mode, SCRATCH_BUFFER_NAME},
editor::{CompleteAction, CursorShapeConfig},
graphics::{CursorKind, Modifier, Rect, Style},
@ -928,7 +928,7 @@ impl EditorView {
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()
|| !cx.editor.config().auto_completion
|| doc!(cx.editor).mode != Mode::Insert
@ -1163,7 +1163,7 @@ impl Component for EditorView {
fn handle_event(
&mut self,
event: Event,
context: &mut helix_view::compositor::Context,
context: &mut crate::compositor::Context,
) -> EventResult {
let mut cx = commands::Context {
editor: context.editor,
@ -1314,7 +1314,7 @@ impl Component for EditorView {
// render status msg
if let Some((status_msg, severity)) = &cx.editor.status_msg {
status_msg_width = status_msg.width();
use helix_view::editor::Severity;
use crate::editor::Severity;
let style = if *severity == Severity::Error {
cx.editor.theme.get("error")
} else {
@ -1361,7 +1361,7 @@ impl Component for EditorView {
if let Some((reg, _)) = cx.editor.macro_recording {
let disp = format!("[{}]", reg);
let style = style
.fg(helix_view::graphics::Color::Yellow)
.fg(crate::graphics::Color::Yellow)
.add_modifier(Modifier::BOLD);
cx.surface.set_string(
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 std::sync::Arc;
@ -9,7 +9,7 @@ use helix_core::{
syntax::{self, HighlightEvent, Syntax},
Rope,
};
use helix_view::{
use crate::{
graphics::{Margin, Rect, Style},
Theme,
};

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

@ -19,9 +19,9 @@ pub use prompt::{Prompt, PromptEvent};
pub use spinner::{ProgressSpinners, Spinner};
pub use text::Text;
use crate::{Document, Editor, View};
use helix_core::regex::Regex;
use helix_core::regex::RegexBuilder;
use helix_view::{Document, Editor, View};
use std::path::PathBuf;
@ -30,7 +30,7 @@ pub fn prompt(
prompt: std::borrow::Cow<'static, str>,
history_register: Option<char>,
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);
// Calculate initial completion
@ -55,7 +55,7 @@ pub fn regex_prompt(
prompt,
history_register,
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 {
PromptEvent::Abort => {
let (view, doc) = current!(cx.editor);
@ -111,7 +111,7 @@ pub fn regex_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 std::time::Instant;
@ -188,12 +188,12 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi
}
pub mod completers {
use crate::document::SCRATCH_BUFFER_NAME;
use crate::theme;
use crate::ui::prompt::Completion;
use crate::{editor::Config, Editor};
use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
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 std::borrow::Cow;
use std::cmp::Reverse;

@ -1,10 +1,10 @@
use helix_core::Position;
use helix_view::{
use crate::{
graphics::{CursorKind, Rect},
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
pub struct Overlay<T> {

@ -2,7 +2,7 @@ use crate::{
ctrl, key, shift,
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 fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
@ -20,7 +20,7 @@ use std::{
use crate::ui::{Prompt, PromptEvent};
use helix_core::{movement::Direction, Position};
use helix_view::{
use crate::{
editor::Action,
graphics::{Color, CursorKind, Margin, Modifier, Rect, Style},
Document, Editor,

@ -1,8 +1,8 @@
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_view::{
use crate::{
graphics::{Margin, Rect},
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 helix_view::input::KeyEvent;
use helix_view::keyboard::KeyCode;
use std::{borrow::Cow, ops::RangeFrom};
use tui::widgets::{Block, Borders, Widget};
use helix_core::{
unicode::segmentation::GraphemeCursor, unicode::width::UnicodeWidthStr, Position,
};
use helix_view::{
use crate::{
graphics::{CursorKind, Margin, Rect},
Editor,
};
use helix_core::{
unicode::segmentation::GraphemeCursor, unicode::width::UnicodeWidthStr, Position,
};
pub type Completion = (RangeFrom<usize>, Cow<'static, str>);

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