deprecate old keybinding scheme

pull/8675/merge^2
mattwparas 1 year ago
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…
Cancel
Save