mirror of https://github.com/helix-editor/helix
deprecate old keybinding scheme
parent
baa753176a
commit
0b107d6f41
|
@ -2337,6 +2337,229 @@ fn global_search(cx: &mut Context) {
|
|||
);
|
||||
}
|
||||
|
||||
fn search_in_directory(cx: &mut Context, search_root: PathBuf) {
|
||||
#[derive(Debug)]
|
||||
struct FileResult {
|
||||
path: PathBuf,
|
||||
/// 0 indexed lines
|
||||
line_num: usize,
|
||||
}
|
||||
|
||||
impl FileResult {
|
||||
fn new(path: &Path, line_num: usize) -> Self {
|
||||
Self {
|
||||
path: path.to_path_buf(),
|
||||
line_num,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ui::menu::Item for FileResult {
|
||||
type Data = Option<PathBuf>;
|
||||
|
||||
fn format(&self, current_path: &Self::Data) -> Row {
|
||||
let relative_path = helix_core::path::get_relative_path(&self.path)
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
if current_path
|
||||
.as_ref()
|
||||
.map(|p| p == &self.path)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
format!("{} (*)", relative_path).into()
|
||||
} else {
|
||||
relative_path.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let config = cx.editor.config();
|
||||
let smart_case = config.search.smart_case;
|
||||
let file_picker_config = config.file_picker.clone();
|
||||
|
||||
let reg = cx.register.unwrap_or('/');
|
||||
let completions = search_completions(cx, Some(reg));
|
||||
ui::regex_prompt(
|
||||
cx,
|
||||
"global-search:".into(),
|
||||
Some(reg),
|
||||
move |_editor: &Editor, input: &str| {
|
||||
completions
|
||||
.iter()
|
||||
.filter(|comp| comp.starts_with(input))
|
||||
.map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
|
||||
.collect()
|
||||
},
|
||||
move |cx, regex, event| {
|
||||
if event != PromptEvent::Validate {
|
||||
return;
|
||||
}
|
||||
cx.editor.registers.last_search_register = reg;
|
||||
|
||||
let current_path = doc_mut!(cx.editor).path().cloned();
|
||||
let documents: Vec<_> = cx
|
||||
.editor
|
||||
.documents()
|
||||
.map(|doc| (doc.path().cloned(), doc.text().to_owned()))
|
||||
.collect();
|
||||
|
||||
if let Ok(matcher) = RegexMatcherBuilder::new()
|
||||
.case_smart(smart_case)
|
||||
.build(regex.as_str())
|
||||
{
|
||||
let search_root = search_root.clone();
|
||||
|
||||
if !search_root.exists() {
|
||||
cx.editor
|
||||
.set_error("Current working directory does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
let (picker, injector) = Picker::stream(current_path);
|
||||
|
||||
let dedup_symlinks = file_picker_config.deduplicate_links;
|
||||
let absolute_root = search_root
|
||||
.canonicalize()
|
||||
.unwrap_or_else(|_| search_root.clone());
|
||||
let injector_ = injector.clone();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let searcher = SearcherBuilder::new()
|
||||
.binary_detection(BinaryDetection::quit(b'\x00'))
|
||||
.build();
|
||||
WalkBuilder::new(search_root)
|
||||
.hidden(file_picker_config.hidden)
|
||||
.parents(file_picker_config.parents)
|
||||
.ignore(file_picker_config.ignore)
|
||||
.follow_links(file_picker_config.follow_symlinks)
|
||||
.git_ignore(file_picker_config.git_ignore)
|
||||
.git_global(file_picker_config.git_global)
|
||||
.git_exclude(file_picker_config.git_exclude)
|
||||
.max_depth(file_picker_config.max_depth)
|
||||
.filter_entry(move |entry| {
|
||||
filter_picker_entry(entry, &absolute_root, dedup_symlinks)
|
||||
})
|
||||
.build_parallel()
|
||||
.run(|| {
|
||||
let mut searcher = searcher.clone();
|
||||
let matcher = matcher.clone();
|
||||
let injector = injector_.clone();
|
||||
let documents = &documents;
|
||||
Box::new(move |entry: Result<DirEntry, ignore::Error>| -> WalkState {
|
||||
let entry = match entry {
|
||||
Ok(entry) => entry,
|
||||
Err(_) => return WalkState::Continue,
|
||||
};
|
||||
|
||||
match entry.file_type() {
|
||||
Some(entry) if entry.is_file() => {}
|
||||
// skip everything else
|
||||
_ => return WalkState::Continue,
|
||||
};
|
||||
|
||||
let mut stop = false;
|
||||
let sink = sinks::UTF8(|line_num, _| {
|
||||
stop = injector
|
||||
.push(FileResult::new(entry.path(), line_num as usize - 1))
|
||||
.is_err();
|
||||
|
||||
Ok(!stop)
|
||||
});
|
||||
let doc = documents.iter().find(|&(doc_path, _)| {
|
||||
doc_path
|
||||
.as_ref()
|
||||
.map_or(false, |doc_path| doc_path == entry.path())
|
||||
});
|
||||
|
||||
let result = if let Some((_, doc)) = doc {
|
||||
// there is already a buffer for this file
|
||||
// search the buffer instead of the file because it's faster
|
||||
// and captures new edits without requiring a save
|
||||
if searcher.multi_line_with_matcher(&matcher) {
|
||||
// in this case a continous buffer is required
|
||||
// convert the rope to a string
|
||||
let text = doc.to_string();
|
||||
searcher.search_slice(&matcher, text.as_bytes(), sink)
|
||||
} else {
|
||||
searcher.search_reader(
|
||||
&matcher,
|
||||
RopeReader::new(doc.slice(..)),
|
||||
sink,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
searcher.search_path(&matcher, entry.path(), sink)
|
||||
};
|
||||
|
||||
if let Err(err) = result {
|
||||
log::error!(
|
||||
"Global search error: {}, {}",
|
||||
entry.path().display(),
|
||||
err
|
||||
);
|
||||
}
|
||||
if stop {
|
||||
WalkState::Quit
|
||||
} else {
|
||||
WalkState::Continue
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
cx.jobs.callback(async move {
|
||||
let call = move |_: &mut Editor, compositor: &mut Compositor| {
|
||||
let picker = Picker::with_stream(
|
||||
picker,
|
||||
injector,
|
||||
move |cx, FileResult { path, line_num }, action| {
|
||||
let doc = match cx.editor.open(path, action) {
|
||||
Ok(id) => doc_mut!(cx.editor, &id),
|
||||
Err(e) => {
|
||||
cx.editor.set_error(format!(
|
||||
"Failed to open file '{}': {}",
|
||||
path.display(),
|
||||
e
|
||||
));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let line_num = *line_num;
|
||||
let view = view_mut!(cx.editor);
|
||||
let text = doc.text();
|
||||
if line_num >= text.len_lines() {
|
||||
cx.editor.set_error(
|
||||
"The line you jumped to does not exist anymore because the file has changed.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
let start = text.line_to_char(line_num);
|
||||
let end = text.line_to_char((line_num + 1).min(text.len_lines()));
|
||||
|
||||
doc.set_selection(view.id, Selection::single(start, end));
|
||||
if action.align_view(view, doc.id()) {
|
||||
align_view(doc, view, Align::Center);
|
||||
}
|
||||
},
|
||||
)
|
||||
.with_preview(
|
||||
|_editor, FileResult { path, line_num }| {
|
||||
Some((path.clone().into(), Some((*line_num, *line_num))))
|
||||
},
|
||||
);
|
||||
compositor.push(Box::new(overlaid(picker)))
|
||||
};
|
||||
Ok(Callback::EditorCompositor(Box::new(call)))
|
||||
})
|
||||
} else {
|
||||
// Otherwise do nothing
|
||||
// log::warn!("Global Search Invalid Pattern")
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
enum Extend {
|
||||
Above,
|
||||
Below,
|
||||
|
|
|
@ -64,23 +64,6 @@ impl ScriptingEngine {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_keybindings() -> Option<HashMap<Mode, KeyTrie>> {
|
||||
let mut map = HashMap::new();
|
||||
|
||||
// Overlay these in reverse, so the precedence applies correctly
|
||||
for kind in PLUGIN_PRECEDENCE.iter().rev() {
|
||||
if let Some(keybindings) = manual_dispatch!(kind, get_keybindings()) {
|
||||
map.extend(keybindings);
|
||||
}
|
||||
}
|
||||
|
||||
if map.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(map)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_keymap_event(
|
||||
editor: &mut ui::EditorView,
|
||||
mode: Mode,
|
||||
|
@ -169,12 +152,6 @@ pub trait PluginSystem {
|
|||
/// run anything here that could modify the context before the main editor is available.
|
||||
fn run_initialization_script(&self, _cx: &mut Context) {}
|
||||
|
||||
/// Fetch the keybindings so that these can be loaded in to the keybinding map. These are
|
||||
/// keybindings that overwrite the default ones.
|
||||
fn get_keybindings(&self) -> Option<HashMap<Mode, KeyTrie>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Allow the engine to directly handle a keymap event. This is some of the tightest integration
|
||||
/// with the engine, directly intercepting any keymap events. By default, this just delegates to the
|
||||
/// editors default keybindings.
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use helix_core::{
|
||||
extensions::steel_implementations::{rope_module, SteelRopeSlice},
|
||||
graphemes,
|
||||
path::expand_tilde,
|
||||
shellwords::Shellwords,
|
||||
Range, Selection, Tendril,
|
||||
};
|
||||
use helix_loader::{current_working_dir, set_current_working_dir};
|
||||
use helix_view::{
|
||||
document::Mode,
|
||||
editor::{Action, ConfigEvent},
|
||||
|
@ -16,7 +18,7 @@ use serde_json::Value;
|
|||
use steel::{
|
||||
gc::unsafe_erased_pointers::CustomReference,
|
||||
rerrs::ErrorKind,
|
||||
rvals::{as_underlying_type, AsRefMutSteelValFromRef, FromSteelVal, IntoSteelVal},
|
||||
rvals::{as_underlying_type, AsRefMutSteelValFromRef, FromSteelVal, IntoSteelVal, SteelString},
|
||||
steel_vm::{engine::Engine, register_fn::RegisterFn},
|
||||
SteelErr, SteelVal,
|
||||
};
|
||||
|
@ -97,6 +99,8 @@ thread_local! {
|
|||
|
||||
pub static REVERSE_BUFFER_MAP: SteelVal =
|
||||
SteelVal::boxed(SteelVal::empty_hashmap());
|
||||
|
||||
pub static GLOBAL_KEYBINDING_MAP: SteelVal = get_keymap().into_steelval().unwrap();
|
||||
}
|
||||
|
||||
fn load_keymap_api(engine: &mut Engine, api: KeyMapApi) {
|
||||
|
@ -119,6 +123,11 @@ fn load_keymap_api(engine: &mut Engine, api: KeyMapApi) {
|
|||
REVERSE_BUFFER_MAP.with(|x| x.clone()),
|
||||
);
|
||||
|
||||
module.register_value(
|
||||
"*global-keybinding-map*",
|
||||
GLOBAL_KEYBINDING_MAP.with(|x| x.clone()),
|
||||
);
|
||||
|
||||
engine.register_module(module);
|
||||
}
|
||||
|
||||
|
@ -198,10 +207,6 @@ impl super::PluginSystem for SteelScriptingEngine {
|
|||
run_initialization_script(cx);
|
||||
}
|
||||
|
||||
fn get_keybindings(&self) -> Option<HashMap<Mode, KeyTrie>> {
|
||||
crate::commands::engine::scheme::SharedKeyBindingsEventQueue::get()
|
||||
}
|
||||
|
||||
fn handle_keymap_event(
|
||||
&self,
|
||||
editor: &mut ui::EditorView,
|
||||
|
@ -423,7 +428,8 @@ impl SteelScriptingEngine {
|
|||
}
|
||||
}
|
||||
|
||||
None
|
||||
// Refer to the global keybinding map for the rest
|
||||
Some(GLOBAL_KEYBINDING_MAP.with(|x| x.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -654,8 +660,8 @@ fn run_initialization_script(cx: &mut Context) {
|
|||
});
|
||||
}
|
||||
|
||||
pub static KEYBINDING_QUEUE: Lazy<SharedKeyBindingsEventQueue> =
|
||||
Lazy::new(|| SharedKeyBindingsEventQueue::new());
|
||||
// pub static KEYBINDING_QUEUE: Lazy<SharedKeyBindingsEventQueue> =
|
||||
// Lazy::new(|| SharedKeyBindingsEventQueue::new());
|
||||
|
||||
pub static CALLBACK_QUEUE: Lazy<CallbackQueue> = Lazy::new(|| CallbackQueue::new());
|
||||
|
||||
|
@ -725,44 +731,44 @@ impl CallbackQueue {
|
|||
/// In order to send events from the engine back to the configuration, we can created a shared
|
||||
/// queue that the engine and the config push and pull from. Alternatively, we could use a channel
|
||||
/// directly, however this was easy enough to set up.
|
||||
pub struct SharedKeyBindingsEventQueue {
|
||||
raw_bindings: Arc<Mutex<Vec<String>>>,
|
||||
}
|
||||
// pub struct SharedKeyBindingsEventQueue {
|
||||
// raw_bindings: Arc<Mutex<Vec<String>>>,
|
||||
// }
|
||||
|
||||
impl SharedKeyBindingsEventQueue {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
raw_bindings: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
// impl SharedKeyBindingsEventQueue {
|
||||
// pub fn new() -> Self {
|
||||
// Self {
|
||||
// raw_bindings: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())),
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn merge(other_as_json: String) {
|
||||
KEYBINDING_QUEUE
|
||||
.raw_bindings
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(other_as_json);
|
||||
}
|
||||
// pub fn merge(other_as_json: String) {
|
||||
// KEYBINDING_QUEUE
|
||||
// .raw_bindings
|
||||
// .lock()
|
||||
// .unwrap()
|
||||
// .push(other_as_json);
|
||||
// }
|
||||
|
||||
pub fn get() -> Option<HashMap<Mode, KeyTrie>> {
|
||||
let guard = KEYBINDING_QUEUE.raw_bindings.lock().unwrap();
|
||||
// pub fn get() -> Option<HashMap<Mode, KeyTrie>> {
|
||||
// let guard = KEYBINDING_QUEUE.raw_bindings.lock().unwrap();
|
||||
|
||||
if let Some(first) = guard.get(0).clone() {
|
||||
let mut initial = serde_json::from_str(first).unwrap();
|
||||
// if let Some(first) = guard.get(0).clone() {
|
||||
// let mut initial = serde_json::from_str(first).unwrap();
|
||||
|
||||
// while let Some(remaining_event) = guard.pop_front() {
|
||||
for remaining_event in guard.iter() {
|
||||
let bindings = serde_json::from_str(remaining_event).unwrap();
|
||||
// // while let Some(remaining_event) = guard.pop_front() {
|
||||
// for remaining_event in guard.iter() {
|
||||
// let bindings = serde_json::from_str(remaining_event).unwrap();
|
||||
|
||||
merge_keys(&mut initial, bindings);
|
||||
}
|
||||
// merge_keys(&mut initial, bindings);
|
||||
// }
|
||||
|
||||
return Some(initial);
|
||||
}
|
||||
// return Some(initial);
|
||||
// }
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
// None
|
||||
// }
|
||||
// }
|
||||
|
||||
impl Custom for PromptEvent {}
|
||||
|
||||
|
@ -1122,7 +1128,7 @@ fn configure_engine() -> std::rc::Rc<std::cell::RefCell<steel::steel_vm::engine:
|
|||
);
|
||||
|
||||
let mut module = BuiltInModule::new("helix/core/keybindings".to_string());
|
||||
module.register_fn("set-keybindings!", SharedKeyBindingsEventQueue::merge);
|
||||
// module.register_fn("set-keybindings!", SharedKeyBindingsEventQueue::merge);
|
||||
|
||||
RegisterFn::<
|
||||
_,
|
||||
|
@ -1278,6 +1284,11 @@ fn configure_engine() -> std::rc::Rc<std::cell::RefCell<steel::steel_vm::engine:
|
|||
module.register_fn("run-in-engine!", run_in_engine);
|
||||
module.register_fn("get-helix-scm-path", get_helix_scm_path);
|
||||
module.register_fn("get-init-scm-path", get_init_scm_path);
|
||||
|
||||
module.register_fn("get-helix-cwd", get_helix_cwd);
|
||||
|
||||
module.register_fn("search-in-directory", search_in_directory);
|
||||
|
||||
module.register_fn("block-on-shell-command", run_shell_command_text);
|
||||
|
||||
module.register_fn("cx->current-file", current_path);
|
||||
|
@ -1721,6 +1732,13 @@ pub fn cx_pos_within_text(cx: &mut Context) -> usize {
|
|||
pos
|
||||
}
|
||||
|
||||
pub fn get_helix_cwd(cx: &mut Context) -> Option<String> {
|
||||
helix_loader::current_working_dir()
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.map(|x| x.into())
|
||||
}
|
||||
|
||||
// Special newline...
|
||||
pub fn custom_insert_newline(cx: &mut Context, indent: String) {
|
||||
let (view, doc) = current_ref!(cx.editor);
|
||||
|
@ -1819,3 +1837,8 @@ pub fn custom_insert_newline(cx: &mut Context, indent: String) {
|
|||
let (view, doc) = current!(cx.editor);
|
||||
doc.apply(&transaction, view.id);
|
||||
}
|
||||
|
||||
fn search_in_directory(cx: &mut Context, directory: String) {
|
||||
let search_path = expand_tilde(&PathBuf::from(directory));
|
||||
crate::commands::search_in_directory(cx, search_path);
|
||||
}
|
||||
|
|
|
@ -3111,13 +3111,15 @@ pub(super) fn command_mode(cx: &mut Context) {
|
|||
// .collect()
|
||||
fuzzy_match(
|
||||
input,
|
||||
TYPABLE_COMMAND_LIST.iter().map(|command| Cow::from(command.name)).chain(crate::commands::engine::ScriptingEngine::available_commands()),
|
||||
TYPABLE_COMMAND_LIST
|
||||
.iter()
|
||||
.map(|command| Cow::from(command.name))
|
||||
.chain(crate::commands::engine::ScriptingEngine::available_commands()),
|
||||
false,
|
||||
)
|
||||
.into_iter()
|
||||
.map(|(name, _)| (0.., name.into()))
|
||||
.collect()
|
||||
|
||||
} else {
|
||||
// Otherwise, use the command's completer and the last shellword
|
||||
// as completion input.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::keymap;
|
||||
use crate::keymap::{merge_keys, KeyTrie, Keymaps};
|
||||
use crate::keymap::{merge_keys, KeyTrie};
|
||||
use helix_loader::merge_toml_values;
|
||||
use helix_view::document::Mode;
|
||||
use serde::Deserialize;
|
||||
|
@ -59,7 +59,6 @@ impl Config {
|
|||
pub fn load(
|
||||
global: Result<String, ConfigLoadError>,
|
||||
local: Result<String, ConfigLoadError>,
|
||||
engine_overlay: Option<HashMap<Mode, KeyTrie>>,
|
||||
) -> Result<Config, ConfigLoadError> {
|
||||
let global_config: Result<ConfigRaw, ConfigLoadError> =
|
||||
global.and_then(|file| toml::from_str(&file).map_err(ConfigLoadError::BadConfig));
|
||||
|
@ -76,10 +75,6 @@ impl Config {
|
|||
merge_keys(&mut keys, local_keys)
|
||||
}
|
||||
|
||||
if let Some(overlay) = engine_overlay {
|
||||
merge_keys(&mut keys, overlay);
|
||||
}
|
||||
|
||||
let editor = match (global.editor, local.editor) {
|
||||
(None, None) => helix_view::editor::Config::default(),
|
||||
(None, Some(val)) | (Some(val), None) => {
|
||||
|
@ -107,10 +102,6 @@ impl Config {
|
|||
merge_keys(&mut keys, keymap);
|
||||
}
|
||||
|
||||
if let Some(overlay) = engine_overlay {
|
||||
merge_keys(&mut keys, overlay);
|
||||
}
|
||||
|
||||
Config {
|
||||
theme: config.theme,
|
||||
keys,
|
||||
|
@ -134,9 +125,7 @@ impl Config {
|
|||
let local_config = fs::read_to_string(helix_loader::workspace_config_file())
|
||||
.map_err(ConfigLoadError::Error);
|
||||
|
||||
let bindings = crate::commands::engine::ScriptingEngine::get_keybindings();
|
||||
|
||||
Config::load(global_config, local_config, bindings)
|
||||
Config::load(global_config, local_config)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,7 +135,7 @@ mod tests {
|
|||
|
||||
impl Config {
|
||||
fn load_test(config: &str) -> Config {
|
||||
Config::load(Ok(config.to_owned()), Err(ConfigLoadError::default()), None).unwrap()
|
||||
Config::load(Ok(config.to_owned()), Err(ConfigLoadError::default())).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue