pull/8675/merge^2
mattwparas 1 year ago
parent 4550faf50f
commit 0b5501d175

8
Cargo.lock generated

@ -1720,9 +1720,9 @@ dependencies = [
[[package]] [[package]]
name = "im-lists" name = "im-lists"
version = "0.4.0" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08f93ebe9d5265409edc0b5c2ebd96bf7dcd4125c1626bff0ece34b9300e490a" checksum = "dbe1ea6399f751563e6f5d88bff90a5c7418f8e7abbdd34708412be793a73949"
[[package]] [[package]]
name = "im-rc" name = "im-rc"
@ -2593,6 +2593,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]] [[package]]
name = "steel-core" name = "steel-core"
version = "0.5.0" version = "0.5.0"
source = "git+https://github.com/mattwparas/steel.git#11c124dbc8ee890429215bd5963e9a56cac7b923"
dependencies = [ dependencies = [
"abi_stable", "abi_stable",
"anyhow", "anyhow",
@ -2629,6 +2630,7 @@ dependencies = [
[[package]] [[package]]
name = "steel-derive" name = "steel-derive"
version = "0.4.0" version = "0.4.0"
source = "git+https://github.com/mattwparas/steel.git#11c124dbc8ee890429215bd5963e9a56cac7b923"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2638,6 +2640,7 @@ dependencies = [
[[package]] [[package]]
name = "steel-gen" name = "steel-gen"
version = "0.2.0" version = "0.2.0"
source = "git+https://github.com/mattwparas/steel.git#11c124dbc8ee890429215bd5963e9a56cac7b923"
dependencies = [ dependencies = [
"codegen", "codegen",
"serde", "serde",
@ -2647,6 +2650,7 @@ dependencies = [
[[package]] [[package]]
name = "steel-parser" name = "steel-parser"
version = "0.4.0" version = "0.4.0"
source = "git+https://github.com/mattwparas/steel.git#11c124dbc8ee890429215bd5963e9a56cac7b923"
dependencies = [ dependencies = [
"logos", "logos",
"num-bigint", "num-bigint",

@ -18,7 +18,9 @@ default-members = [
] ]
[workspace.dependencies] [workspace.dependencies]
steel-core = { path = "../../steel/crates/steel-core", version = "0.5.0", features = ["modules", "anyhow", "dylibs", "colors"] } # If working locally, use the local path dependency
# steel-core = { path = "../../steel/crates/steel-core", version = "0.5.0", features = ["modules", "anyhow", "dylibs", "colors"] }
steel-core = { git = "https://github.com/mattwparas/steel.git", version = "0.5.0", features = ["modules", "anyhow", "dylibs", "colors"] }
tree-sitter = { version = "0.20", git = "https://github.com/tree-sitter/tree-sitter", rev = "ab09ae20d640711174b8da8a654f6b3dec93da1a" } tree-sitter = { version = "0.20", git = "https://github.com/tree-sitter/tree-sitter", rev = "ab09ae20d640711174b8da8a654f6b3dec93da1a" }
nucleo = "0.2.0" nucleo = "0.2.0"

@ -381,6 +381,18 @@ impl Application {
pub fn handle_config_events(&mut self, config_event: ConfigEvent) { pub fn handle_config_events(&mut self, config_event: ConfigEvent) {
match config_event { match config_event {
ConfigEvent::Refresh => self.refresh_config(), ConfigEvent::Refresh => self.refresh_config(),
ConfigEvent::UpdateLanguageConfiguration => match self.refresh_language_config() {
Ok(_) => {
// If we don't stash the theme here, the syntax highlighting is broken
let current_theme = std::mem::take(&mut self.editor.theme);
self.editor.set_theme(current_theme);
self.editor.set_status("Language config refreshed");
}
Err(err) => {
self.editor.set_error(err.to_string());
}
},
// Since only the Application can make changes to Editor's config, // Since only the Application can make changes to Editor's config,
// the Editor must send up a new copy of a modified config so that // the Editor must send up a new copy of a modified config so that
@ -409,7 +421,8 @@ impl Application {
/// refresh language config after config change /// refresh language config after config change
fn refresh_language_config(&mut self) -> Result<(), Error> { fn refresh_language_config(&mut self) -> Result<(), Error> {
let syntax_config = helix_core::config::user_syntax_loader() let syntax_config = crate::commands::engine::ScriptingEngine::load_language_configuration()
.unwrap_or_else(|| helix_core::config::user_syntax_loader())
.map_err(|err| anyhow::anyhow!("Failed to load language config: {}", err))?; .map_err(|err| anyhow::anyhow!("Failed to load language config: {}", err))?;
self.syn_loader = std::sync::Arc::new(syntax::Loader::new(syntax_config)); self.syn_loader = std::sync::Arc::new(syntax::Loader::new(syntax_config));
@ -427,7 +440,10 @@ impl Application {
let theme = config let theme = config
.theme .theme
.as_ref() .as_ref()
.and_then(|theme| { .and_then(|theme| crate::commands::engine::ScriptingEngine::load_theme(theme))
.or_else(|| {
// Check the name again
config.theme.as_ref().and_then(|theme| {
self.theme_loader self.theme_loader
.load(theme) .load(theme)
.map_err(|e| { .map_err(|e| {
@ -437,13 +453,13 @@ impl Application {
.ok() .ok()
.filter(|theme| (true_color || theme.is_16_color())) .filter(|theme| (true_color || theme.is_16_color()))
}) })
})
.unwrap_or_else(|| self.theme_loader.default_theme(true_color)); .unwrap_or_else(|| self.theme_loader.default_theme(true_color));
self.editor.set_theme(theme); self.editor.set_theme(theme);
Ok(()) Ok(())
} }
// TODO: @Matt - consider querying the engine for keybindings
fn refresh_config(&mut self) { fn refresh_config(&mut self) {
let mut refresh_config = || -> Result<(), Error> { let mut refresh_config = || -> Result<(), Error> {
let default_config = Config::load_default() let default_config = Config::load_default()

@ -1,10 +1,11 @@
use helix_view::{document::Mode, input::KeyEvent}; use helix_core::syntax::Configuration;
use helix_view::{document::Mode, input::KeyEvent, Theme};
use std::{borrow::Cow, collections::HashMap}; use std::borrow::Cow;
use crate::{ use crate::{
compositor, compositor,
keymap::{KeyTrie, KeymapResult}, keymap::KeymapResult,
ui::{self, PromptEvent}, ui::{self, PromptEvent},
}; };
@ -41,6 +42,7 @@ pub struct NoEngine;
// This will be the boundary layer between the editor and the engine. // This will be the boundary layer between the editor and the engine.
pub struct ScriptingEngine; pub struct ScriptingEngine;
// Macro to automatically dispatch to hopefully get some inlining
macro_rules! manual_dispatch { macro_rules! manual_dispatch {
($kind:expr, $raw:tt ($($args:expr),* $(,)?) ) => { ($kind:expr, $raw:tt ($($args:expr),* $(,)?) ) => {
match $kind { match $kind {
@ -131,6 +133,42 @@ impl ScriptingEngine {
.flat_map(|kind| manual_dispatch!(kind, available_commands())) .flat_map(|kind| manual_dispatch!(kind, available_commands()))
.collect() .collect()
} }
pub fn load_theme(name: &str) -> Option<Theme> {
for kind in PLUGIN_PRECEDENCE {
let theme = manual_dispatch!(kind, load_theme(name));
if theme.is_some() {
return theme;
}
}
None
}
pub fn themes() -> Option<Vec<String>> {
for kind in PLUGIN_PRECEDENCE {
let themes = manual_dispatch!(kind, themes());
if themes.is_some() {
return themes;
}
}
None
}
pub fn load_language_configuration() -> Option<Result<Configuration, toml::de::Error>> {
for kind in PLUGIN_PRECEDENCE {
let config = manual_dispatch!(kind, load_language_configuration());
if config.is_some() {
return config;
}
}
None
}
} }
impl PluginSystem for NoEngine { impl PluginSystem for NoEngine {
@ -155,6 +193,7 @@ pub trait PluginSystem {
/// Allow the engine to directly handle a keymap event. This is some of the tightest integration /// 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 /// with the engine, directly intercepting any keymap events. By default, this just delegates to the
/// editors default keybindings. /// editors default keybindings.
#[inline(always)]
fn handle_keymap_event( fn handle_keymap_event(
&self, &self,
_editor: &mut ui::EditorView, _editor: &mut ui::EditorView,
@ -167,6 +206,7 @@ pub trait PluginSystem {
/// This attempts to call a function in the engine with the name `name` using the args `args`. The context /// This attempts to call a function in the engine with the name `name` using the args `args`. The context
/// is available here. Returns a bool indicating whether the function exists or not. /// is available here. Returns a bool indicating whether the function exists or not.
#[inline(always)]
fn call_function_if_global_exists( fn call_function_if_global_exists(
&self, &self,
_cx: &mut Context, _cx: &mut Context,
@ -179,6 +219,7 @@ pub trait PluginSystem {
/// This is explicitly for calling a function via the typed command interface, e.g. `:vsplit`. The context here /// This is explicitly for calling a function via the typed command interface, e.g. `:vsplit`. The context here
/// that is available is more limited than the context available in `call_function_if_global_exists`. This also /// that is available is more limited than the context available in `call_function_if_global_exists`. This also
/// gives the ability to handle in progress commands with `PromptEvent`. /// gives the ability to handle in progress commands with `PromptEvent`.
#[inline(always)]
fn call_typed_command_if_global_exists<'a>( fn call_typed_command_if_global_exists<'a>(
&self, &self,
_cx: &mut compositor::Context, _cx: &mut compositor::Context,
@ -190,12 +231,35 @@ pub trait PluginSystem {
} }
/// Given an identifier, extract the documentation from the engine. /// Given an identifier, extract the documentation from the engine.
#[inline(always)]
fn get_doc_for_identifier(&self, _ident: &str) -> Option<String> { fn get_doc_for_identifier(&self, _ident: &str) -> Option<String> {
None None
} }
/// Fuzzy match the input against the fuzzy matcher, used for handling completions on typed commands /// Fuzzy match the input against the fuzzy matcher, used for handling completions on typed commands
#[inline(always)]
fn available_commands<'a>(&self) -> Vec<Cow<'a, str>> { fn available_commands<'a>(&self) -> Vec<Cow<'a, str>> {
Vec::new() Vec::new()
} }
/// Retrieve a theme for a given name
#[inline(always)]
fn load_theme(&self, _name: &str) -> Option<Theme> {
None
}
/// Retrieve the list of themes that exist within the runtime
#[inline(always)]
fn themes(&self) -> Option<Vec<String>> {
None
}
/// Fetch the language configuration as monitored by the plugin system.
///
/// For now - this maintains backwards compatibility with the existing toml configuration,
/// and as such the toml error is exposed here.
#[inline(always)]
fn load_language_configuration(&self) -> Option<Result<Configuration, toml::de::Error>> {
None
}
} }

@ -13,185 +13,10 @@ use crate::{
ui::{Popup, Prompt, PromptEvent}, ui::{Popup, Prompt, PromptEvent},
}; };
// TODO: Move the main configuration function to use this instead
pub fn helix_component_module() -> BuiltInModule { pub fn helix_component_module() -> BuiltInModule {
let mut module = BuiltInModule::new("helix/components".to_string()); let mut module = BuiltInModule::new("helix/components".to_string());
module.register_fn("new-component!", SteelDynamicComponent::new_dyn);
module.register_fn("SteelDynamicComponent?", |object: SteelVal| {
if let SteelVal::Custom(v) = object {
if let Some(wrapped) = v.borrow().as_any_ref().downcast_ref::<BoxDynComponent>() {
return wrapped.inner.as_any().is::<SteelDynamicComponent>();
} else {
false
}
} else {
false
}
});
module.register_fn(
"SteelDynamicComponent-state",
SteelDynamicComponent::get_state,
);
module.register_fn(
"SteelDynamicComponent-render",
SteelDynamicComponent::get_render,
);
module.register_fn(
"SteelDynamicComponent-handle-event",
SteelDynamicComponent::get_handle_event,
);
module.register_fn(
"SteelDynamicComponent-should-update",
SteelDynamicComponent::should_update,
);
module.register_fn(
"SteelDynamicComponent-cursor",
SteelDynamicComponent::cursor,
);
module.register_fn(
"SteelDynamicComponent-required-size",
SteelDynamicComponent::get_required_size,
);
// engine.register_fn("WrappedComponent", WrappedDynComponent::new)
module.register_fn(
"Popup::new",
|contents: &mut WrappedDynComponent,
position: helix_core::Position|
-> WrappedDynComponent {
let inner = contents.inner.take().unwrap(); // Panic, for now
WrappedDynComponent {
inner: Some(Box::new(
Popup::<BoxDynComponent>::new("popup", BoxDynComponent::new(inner))
.position(Some(position)),
)),
}
},
);
// prompt: Cow<'static, str>,
// history_register: Option<char>,
// completion_fn: impl FnMut(&Editor, &str) -> Vec<Completion> + 'static,
// callback_fn: impl FnMut(&mut Context, &str, PromptEvent) + 'static,
module.register_fn(
"Prompt::new",
|prompt: String, callback_fn: SteelVal| -> WrappedDynComponent {
let prompt = Prompt::new(
prompt.into(),
None,
|_, _| Vec::new(),
move |cx, input, prompt_event| {
if prompt_event != PromptEvent::Validate {
return;
}
let mut ctx = Context {
register: None,
count: None,
editor: cx.editor,
callback: Vec::new(),
on_next_key_callback: None,
jobs: cx.jobs,
};
let cloned_func = callback_fn.clone();
// let thunk = move |engine: &mut Engine, cx, input| {
// engine.call_function_with_args(
// cloned_func,
// vec![cx, input],
// )
// };
ENGINE
.with(|x| {
x.borrow_mut()
.with_mut_reference::<Context, Context>(&mut ctx)
.consume(move |engine, mut args| {
// Add the string as an argument to the callback
args.push(input.into_steelval().unwrap());
engine.call_function_with_args(cloned_func.clone(), args)
})
})
.unwrap();
},
);
WrappedDynComponent {
inner: Some(Box::new(prompt)),
}
},
);
module.register_fn("Picker::new", |values: Vec<String>| todo!());
// engine.register_fn(
// "Picker::new",
// |contents: &mut Wrapped
// )
module.register_fn("Component::Text", |contents: String| WrappedDynComponent {
inner: Some(Box::new(crate::ui::Text::new(contents))),
});
// Separate this out into its own component module - This just lets us call the underlying
// component, not sure if we can go from trait object -> trait object easily but we'll see!
module.register_fn(
"Component::render",
|t: &mut WrappedDynComponent,
area: helix_view::graphics::Rect,
frame: &mut tui::buffer::Buffer,
ctx: &mut Context| {
t.inner.as_mut().unwrap().render(
area,
frame,
&mut compositor::Context {
jobs: ctx.jobs,
editor: ctx.editor,
scroll: None,
},
)
},
);
module.register_fn(
"Component::handle-event",
|s: &mut WrappedDynComponent, event: &helix_view::input::Event, ctx: &mut Context| {
s.inner.as_mut().unwrap().handle_event(
event,
&mut compositor::Context {
jobs: ctx.jobs,
editor: ctx.editor,
scroll: None,
},
)
},
);
module.register_fn("Component::should-update", |s: &mut WrappedDynComponent| {
s.inner.as_mut().unwrap().should_update()
});
module.register_fn(
"Component::cursor",
|s: &WrappedDynComponent, area: helix_view::graphics::Rect, ctx: &Editor| {
s.inner.as_ref().unwrap().cursor(area, ctx)
},
);
module.register_fn(
"Component::required-size",
|s: &mut WrappedDynComponent, viewport: (u16, u16)| {
s.inner.as_mut().unwrap().required_size(viewport)
},
);
module module
} }
@ -242,6 +67,7 @@ impl SteelDynamicComponent {
) -> WrappedDynComponent { ) -> WrappedDynComponent {
let s = Self::new(name, state, render, h); let s = Self::new(name, state, render, h);
// TODO: Add guards here for the
WrappedDynComponent { WrappedDynComponent {
inner: Some(Box::new(s)), inner: Some(Box::new(s)),
} }

File diff suppressed because it is too large Load Diff

@ -892,7 +892,10 @@ fn theme(
// Ensures that a preview theme gets cleaned up if the user backspaces until the prompt is empty. // Ensures that a preview theme gets cleaned up if the user backspaces until the prompt is empty.
cx.editor.unset_theme_preview(); cx.editor.unset_theme_preview();
} else if let Some(theme_name) = args.first() { } else if let Some(theme_name) = args.first() {
if let Ok(theme) = cx.editor.theme_loader.load(theme_name) { if let Ok(theme) = crate::commands::engine::ScriptingEngine::load_theme(theme_name)
.map(|x| Ok(x))
.unwrap_or_else(|| cx.editor.theme_loader.load(theme_name))
{
if !(true_color || theme.is_16_color()) { if !(true_color || theme.is_16_color()) {
bail!("Unsupported theme: theme requires true color support"); bail!("Unsupported theme: theme requires true color support");
} }
@ -902,15 +905,25 @@ fn theme(
} }
PromptEvent::Validate => { PromptEvent::Validate => {
if let Some(theme_name) = args.first() { if let Some(theme_name) = args.first() {
let theme = cx // let theme = cx
.editor // .editor
.theme_loader // .theme_loader
.load(theme_name) // .load(theme_name)
.map_err(|err| anyhow::anyhow!("Could not load theme: {}", err))?; // .map_err(|err| anyhow::anyhow!("Could not load theme: {}", err))?;
// if !(true_color || theme.is_16_color()) {
// bail!("Unsupported theme: theme requires true color support");
// }
// cx.editor.set_theme(theme);
if let Ok(theme) = crate::commands::engine::ScriptingEngine::load_theme(theme_name)
.map(|x| Ok(x))
.unwrap_or_else(|| cx.editor.theme_loader.load(theme_name))
{
if !(true_color || theme.is_16_color()) { if !(true_color || theme.is_16_color()) {
bail!("Unsupported theme: theme requires true color support"); bail!("Unsupported theme: theme requires true color support");
} }
cx.editor.set_theme(theme); cx.editor.set_theme(theme);
};
} else { } else {
let name = cx.editor.theme.name().to_string(); let name = cx.editor.theme.name().to_string();
@ -3087,28 +3100,6 @@ pub(super) fn command_mode(cx: &mut Context) {
let words = shellwords.words(); let words = shellwords.words();
if words.is_empty() || (words.len() == 1 && !shellwords.ends_with_whitespace()) { if words.is_empty() || (words.len() == 1 && !shellwords.ends_with_whitespace()) {
// let globals =
// crate::commands::engine::ScriptingEngine::fuzzy_match(&FUZZY_MATCHER, input)
// .into_iter()
// .map(|x| (Cow::from(x.0), x.1))
// .collect::<Vec<_>>();
// // If the command has not been finished yet, complete commands.
// let mut matches: Vec<_> = typed::TYPABLE_COMMAND_LIST
// .iter()
// .filter_map(|command| {
// FUZZY_MATCHER
// .fuzzy_match(command.name, input)
// .map(|score| (Cow::from(command.name), score))
// })
// .chain(globals)
// .collect();
// matches.sort_unstable_by_key(|(_file, score)| std::cmp::Reverse(*score));
// matches
// .into_iter()
// .map(|(name, _)| (0.., name.into()))
// .collect()
fuzzy_match( fuzzy_match(
input, input,
TYPABLE_COMMAND_LIST TYPABLE_COMMAND_LIST

@ -22,7 +22,7 @@ fn rope_ends_with(text: &str, rope: RopeSlice<'_>) -> bool {
.map_or(false, |end| end == text) .map_or(false, |end| end == text)
} }
mod completion; pub(crate) mod completion;
mod signature_help; mod signature_help;
pub fn setup(config: Arc<ArcSwap<Config>>) -> Handlers { pub fn setup(config: Arc<ArcSwap<Config>>) -> Handlers {

@ -32,20 +32,31 @@ use crate::ui::{self, CompletionItem, Popup};
use super::Handlers; use super::Handlers;
#[derive(Debug, PartialEq, Eq, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum TriggerKind { pub(crate) enum TriggerKind {
Auto, Auto,
TriggerChar, TriggerChar,
Manual, Manual,
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
struct Trigger { pub(crate) struct Trigger {
pos: usize, pos: usize,
view: ViewId, view: ViewId,
doc: DocumentId, doc: DocumentId,
kind: TriggerKind, kind: TriggerKind,
} }
impl Trigger {
pub(crate) fn new(pos: usize, view: ViewId, doc: DocumentId, kind: TriggerKind) -> Self {
Self {
pos,
view,
doc,
kind,
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub(super) struct CompletionHandler { pub(super) struct CompletionHandler {
/// currently active trigger which will cause a /// currently active trigger which will cause a
@ -284,7 +295,7 @@ fn request_completion(
}); });
} }
fn show_completion( pub(crate) fn show_completion(
editor: &mut Editor, editor: &mut Editor,
compositor: &mut Compositor, compositor: &mut Compositor,
items: Vec<CompletionItem>, items: Vec<CompletionItem>,

@ -151,13 +151,6 @@ impl Jobs {
} }
} }
// pub async fn next_job(&mut self) -> Option<anyhow::Result<Option<Callback>>> {
// tokio::select! {
// event = self.futures.next() => { event }
// event = self.wait_futures.next() => { event }
// }
// }
pub fn add(&self, j: Job) { pub fn add(&self, j: Job) {
if j.wait { if j.wait {
self.wait_futures.push(j.future); self.wait_futures.push(j.future);

@ -278,22 +278,36 @@ impl Completion {
// always present here // always present here
let mut item = item.unwrap().clone(); let mut item = item.unwrap().clone();
let language_server = language_server!(item); // let language_server = language_server!(item);
let offset_encoding = language_server.offset_encoding(); // let offset_encoding = language_server.offset_encoding();
let language_server = editor // let language_server = editor
.language_servers // .language_servers
.get_by_id(item.language_server_id) // .get_by_id(item.language_server_id)
.unwrap(); // .unwrap();
let mut language_server_option = None;
// resolve item if not yet resolved // resolve item if not yet resolved
if !item.resolved { if !item.resolved {
let language_server = language_server!(item);
// let offset_encoding = language_server.offset_encoding();
if let Some(resolved) = if let Some(resolved) =
Self::resolve_completion_item(language_server, item.item.clone()) Self::resolve_completion_item(language_server, item.item.clone())
{ {
item.item = resolved; item.item = resolved;
} }
language_server_option = Some(language_server);
}; };
// let language_server = language_server!(item);
let offset_encoding = language_server_option
.as_ref()
.map(|x| x.offset_encoding())
.unwrap_or_default();
// if more text was entered, remove it // if more text was entered, remove it
doc.restore(view, &savepoint, true); doc.restore(view, &savepoint, true);
// save an undo checkpoint before the completion // save an undo checkpoint before the completion

@ -270,6 +270,11 @@ pub mod completers {
for rt_dir in helix_loader::runtime_dirs() { for rt_dir in helix_loader::runtime_dirs() {
names.extend(theme::Loader::read_names(&rt_dir.join("themes"))); names.extend(theme::Loader::read_names(&rt_dir.join("themes")));
} }
if let Some(themes) = crate::commands::engine::ScriptingEngine::themes() {
names.extend(themes);
}
names.push("default".into()); names.push("default".into());
names.push("base16_default".into()); names.push("base16_default".into());
names.sort(); names.sort();

@ -971,6 +971,7 @@ pub enum EditorEvent {
pub enum ConfigEvent { pub enum ConfigEvent {
Refresh, Refresh,
Update(Box<Config>), Update(Box<Config>),
UpdateLanguageConfiguration,
} }
enum ThemeAction { enum ThemeAction {

Loading…
Cancel
Save