mirror of https://github.com/helix-editor/helix
Merge 7f43480cf6
into 395a71bf53
commit
e76b254650
|
@ -1395,6 +1395,10 @@ dependencies = [
|
|||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "helix"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "helix-core"
|
||||
version = "25.7.1"
|
||||
|
@ -1565,6 +1569,7 @@ dependencies = [
|
|||
"indexmap",
|
||||
"indoc",
|
||||
"libc",
|
||||
"libloading",
|
||||
"log",
|
||||
"nucleo",
|
||||
"once_cell",
|
||||
|
@ -1983,9 +1988,9 @@ checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
|||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.7"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c"
|
||||
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.53.2",
|
||||
|
|
11
Cargo.toml
11
Cargo.toml
|
@ -15,6 +15,9 @@ members = [
|
|||
"helix-stdx",
|
||||
"xtask",
|
||||
]
|
||||
exclude = [
|
||||
"helix-plugins"
|
||||
]
|
||||
|
||||
default-members = [
|
||||
"helix-term"
|
||||
|
@ -61,3 +64,11 @@ repository = "https://github.com/helix-editor/helix"
|
|||
homepage = "https://helix-editor.com"
|
||||
license = "MPL-2.0"
|
||||
rust-version = "1.82"
|
||||
|
||||
[package]
|
||||
name = "helix"
|
||||
version = "0.1.0"
|
||||
|
||||
[lib]
|
||||
name = "helix"
|
||||
path = "helix-core/src/lib.rs"
|
||||
|
|
|
@ -92,6 +92,7 @@ grep-regex = "0.1.13"
|
|||
grep-searcher = "0.1.14"
|
||||
|
||||
dashmap = "6.0"
|
||||
libloading = "0.8.8"
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100
|
||||
signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
|
||||
|
|
|
@ -26,6 +26,7 @@ use crate::{
|
|||
handlers,
|
||||
job::Jobs,
|
||||
keymap::Keymaps,
|
||||
plugins::Plugins,
|
||||
ui::{self, overlay::overlaid},
|
||||
};
|
||||
|
||||
|
@ -234,6 +235,9 @@ impl Application {
|
|||
])
|
||||
.context("build signal handler")?;
|
||||
|
||||
// after everything has been set up, load all plugins
|
||||
Plugins::load_all(&config.load());
|
||||
|
||||
let app = Self {
|
||||
compositor,
|
||||
terminal,
|
||||
|
@ -414,6 +418,9 @@ impl Application {
|
|||
|
||||
self.terminal
|
||||
.reconfigure(default_config.editor.clone().into())?;
|
||||
|
||||
Plugins::config_updated(&default_config);
|
||||
|
||||
// Store new config
|
||||
self.config.store(Arc::new(default_config));
|
||||
Ok(())
|
||||
|
|
|
@ -63,6 +63,7 @@ use crate::{
|
|||
compositor::{self, Component, Compositor},
|
||||
filter_picker_entry,
|
||||
job::Callback,
|
||||
plugins::Plugins,
|
||||
ui::{self, overlay::overlaid, Picker, PickerColumn, Popup, Prompt, PromptEvent},
|
||||
};
|
||||
|
||||
|
@ -246,7 +247,9 @@ impl MappableCommand {
|
|||
pub fn execute(&self, cx: &mut Context) {
|
||||
match &self {
|
||||
Self::Typable { name, args, doc: _ } => {
|
||||
if let Some(command) = typed::TYPABLE_COMMAND_MAP.get(name.as_str()) {
|
||||
//if Plugins::call_typed_command(name, cx, args) {
|
||||
if false {
|
||||
} else if let Some(command) = typed::TYPABLE_COMMAND_MAP.get(name.as_str()) {
|
||||
let mut cx = compositor::Context {
|
||||
editor: cx.editor,
|
||||
jobs: cx.jobs,
|
||||
|
|
|
@ -3692,7 +3692,13 @@ fn execute_command_line(
|
|||
|
||||
match typed::TYPABLE_COMMAND_MAP.get(command) {
|
||||
Some(cmd) => execute_command(cx, cmd, rest, event),
|
||||
None if event == PromptEvent::Validate => Err(anyhow!("no such command: '{command}'")),
|
||||
None if event == PromptEvent::Validate => {
|
||||
if Plugins::call_typed_command(cx, command, rest) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("no such command: '{command}'"))
|
||||
}
|
||||
}
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
@ -3813,7 +3819,10 @@ fn complete_command_line(editor: &Editor, input: &str) -> Vec<ui::prompt::Comple
|
|||
if complete_command {
|
||||
fuzzy_match(
|
||||
input,
|
||||
TYPABLE_COMMAND_LIST.iter().map(|command| command.name),
|
||||
TYPABLE_COMMAND_LIST
|
||||
.iter()
|
||||
.map(|command| command.name.to_string())
|
||||
.chain(crate::plugins::Plugins::available_commands()),
|
||||
false,
|
||||
)
|
||||
.into_iter()
|
||||
|
|
|
@ -14,6 +14,8 @@ pub struct Config {
|
|||
pub theme: Option<String>,
|
||||
pub keys: HashMap<Mode, KeyTrie>,
|
||||
pub editor: helix_view::editor::Config,
|
||||
pub plugins: Option<Vec<String>>,
|
||||
pub plugin: Option<toml::Table>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||
|
@ -22,6 +24,8 @@ pub struct ConfigRaw {
|
|||
pub theme: Option<String>,
|
||||
pub keys: Option<HashMap<Mode, KeyTrie>>,
|
||||
pub editor: Option<toml::Value>,
|
||||
pub plugins: Option<Vec<String>>,
|
||||
pub plugin: Option<toml::Table>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
|
@ -30,6 +34,8 @@ impl Default for Config {
|
|||
theme: None,
|
||||
keys: keymap::default(),
|
||||
editor: helix_view::editor::Config::default(),
|
||||
plugins: None,
|
||||
plugin: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -84,10 +90,22 @@ impl Config {
|
|||
.map_err(ConfigLoadError::BadConfig)?,
|
||||
};
|
||||
|
||||
let plugins = match (global.plugins, local.plugins) {
|
||||
(None, None) => None,
|
||||
(None, Some(val)) | (Some(val), None) => Some(val),
|
||||
(Some(global), Some(local)) => {
|
||||
let mut plugins = global.clone();
|
||||
plugins.extend(local);
|
||||
Some(plugins)
|
||||
}
|
||||
};
|
||||
|
||||
Config {
|
||||
theme: local.theme.or(global.theme),
|
||||
keys,
|
||||
editor,
|
||||
plugins,
|
||||
plugin: local.plugin.or(global.plugin), // TODO: merge
|
||||
}
|
||||
}
|
||||
// if any configs are invalid return that first
|
||||
|
@ -107,6 +125,8 @@ impl Config {
|
|||
|| Ok(helix_view::editor::Config::default()),
|
||||
|val| val.try_into().map_err(ConfigLoadError::BadConfig),
|
||||
)?,
|
||||
plugins: config.plugins,
|
||||
plugin: config.plugin,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ pub mod events;
|
|||
pub mod health;
|
||||
pub mod job;
|
||||
pub mod keymap;
|
||||
pub mod plugins;
|
||||
pub mod ui;
|
||||
|
||||
use std::path::Path;
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
use crate::{compositor::Context, config::Config};
|
||||
|
||||
use libloading::Library;
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
ffi::{c_char, CString},
|
||||
sync::Mutex,
|
||||
};
|
||||
|
||||
enum PluginInstance {}
|
||||
type InitFn = fn(&Config) -> &'static mut PluginInstance;
|
||||
type ConfigUpdatedFn = fn(&mut PluginInstance, &Config);
|
||||
type AvailableCmdsFn = fn(&mut PluginInstance) -> Vec<String>;
|
||||
type CmdFn = fn(&mut PluginInstance, &mut Context, *const c_char);
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct PluginInfo {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub lib_path: String,
|
||||
pub min_helix_version: Option<String>,
|
||||
pub dependencies: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
struct Plugin {
|
||||
info: PluginInfo,
|
||||
lib: Library,
|
||||
instance: &'static mut PluginInstance,
|
||||
}
|
||||
|
||||
static PLUGINS: Mutex<Vec<Plugin>> = Mutex::<Vec<Plugin>>::new(vec![]);
|
||||
|
||||
pub struct Plugins {}
|
||||
|
||||
impl Plugins {
|
||||
// Utils for plugin developers
|
||||
|
||||
pub fn alloc<T>() -> &'static mut T {
|
||||
let layout = std::alloc::Layout::new::<T>();
|
||||
unsafe {
|
||||
let t = std::alloc::alloc(layout) as *mut T;
|
||||
t.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_config_for<'a>(config: &'a Config, name: &str) -> Option<&'a toml::Table> {
|
||||
if let Some(config) = &config.plugin {
|
||||
if let Some(config) = config.get(name) {
|
||||
return config.as_table();
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// Internal plugin API
|
||||
|
||||
pub fn load_all(config: &Config) {
|
||||
if let Some(plugins) = &config.plugins {
|
||||
for plugin in plugins {
|
||||
Self::load(&plugin, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(info_path: &str, config: &Config) {
|
||||
let info_str = std::fs::read_to_string(info_path).unwrap();
|
||||
let info = toml::from_str::<PluginInfo>(&info_str).unwrap();
|
||||
|
||||
// TODO: do nothing if this version is too low
|
||||
// if (VERSION_AND_GIT_HASH < info.min_helix_version) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// load the dynamic lib
|
||||
let lib_path = std::path::Path::new(info_path)
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join(&info.lib_path);
|
||||
let lib = unsafe { Library::new(lib_path).unwrap() };
|
||||
|
||||
// call the plugin's init method (if it has one)
|
||||
let func_opt = unsafe { lib.get::<InitFn>(b"init") };
|
||||
let instance = func_opt.unwrap()(config);
|
||||
|
||||
PLUGINS.lock().unwrap().push(Plugin {
|
||||
info,
|
||||
lib,
|
||||
instance,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn config_updated(config: &Config) {
|
||||
for plugin in PLUGINS.lock().unwrap().iter_mut() {
|
||||
let func_opt = unsafe { plugin.lib.get::<ConfigUpdatedFn>(b"config_updated") };
|
||||
if let Ok(func) = func_opt {
|
||||
func(plugin.instance, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn available_commands() -> Vec<String> {
|
||||
let mut commands = Vec::<String>::new();
|
||||
for plugin in PLUGINS.lock().unwrap().iter_mut() {
|
||||
let func_opt = unsafe { plugin.lib.get::<AvailableCmdsFn>(b"available_commands") };
|
||||
if let Ok(func) = func_opt {
|
||||
let plugin_commands = func(plugin.instance);
|
||||
for command in plugin_commands {
|
||||
commands.push(command.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
commands
|
||||
}
|
||||
|
||||
pub fn call_typed_command(cx: &mut Context, name: &str, args: &str) -> bool {
|
||||
for plugin in PLUGINS.lock().unwrap().iter_mut() {
|
||||
let func_opt = unsafe { plugin.lib.get::<CmdFn>(name.as_bytes()) };
|
||||
if let Ok(func) = func_opt {
|
||||
func(plugin.instance, cx, CString::new(args).unwrap().as_ptr());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue