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

8
Cargo.lock generated

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

@ -18,7 +18,9 @@ default-members = [
]
[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" }
nucleo = "0.2.0"

@ -381,6 +381,18 @@ impl Application {
pub fn handle_config_events(&mut self, config_event: ConfigEvent) {
match config_event {
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,
// 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
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))?;
self.syn_loader = std::sync::Arc::new(syntax::Loader::new(syntax_config));
@ -427,7 +440,10 @@ impl Application {
let theme = config
.theme
.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
.load(theme)
.map_err(|e| {
@ -437,13 +453,13 @@ impl Application {
.ok()
.filter(|theme| (true_color || theme.is_16_color()))
})
})
.unwrap_or_else(|| self.theme_loader.default_theme(true_color));
self.editor.set_theme(theme);
Ok(())
}
// TODO: @Matt - consider querying the engine for keybindings
fn refresh_config(&mut self) {
let mut refresh_config = || -> Result<(), Error> {
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::{
compositor,
keymap::{KeyTrie, KeymapResult},
keymap::KeymapResult,
ui::{self, PromptEvent},
};
@ -41,6 +42,7 @@ pub struct NoEngine;
// This will be the boundary layer between the editor and the engine.
pub struct ScriptingEngine;
// Macro to automatically dispatch to hopefully get some inlining
macro_rules! manual_dispatch {
($kind:expr, $raw:tt ($($args:expr),* $(,)?) ) => {
match $kind {
@ -131,6 +133,42 @@ impl ScriptingEngine {
.flat_map(|kind| manual_dispatch!(kind, available_commands()))
.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 {
@ -155,6 +193,7 @@ pub trait PluginSystem {
/// 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.
#[inline(always)]
fn handle_keymap_event(
&self,
_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
/// is available here. Returns a bool indicating whether the function exists or not.
#[inline(always)]
fn call_function_if_global_exists(
&self,
_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
/// 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`.
#[inline(always)]
fn call_typed_command_if_global_exists<'a>(
&self,
_cx: &mut compositor::Context,
@ -190,12 +231,35 @@ pub trait PluginSystem {
}
/// Given an identifier, extract the documentation from the engine.
#[inline(always)]
fn get_doc_for_identifier(&self, _ident: &str) -> Option<String> {
None
}
/// 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>> {
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},
};
// TODO: Move the main configuration function to use this instead
pub fn helix_component_module() -> BuiltInModule {
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
}
@ -242,6 +67,7 @@ impl SteelDynamicComponent {
) -> WrappedDynComponent {
let s = Self::new(name, state, render, h);
// TODO: Add guards here for the
WrappedDynComponent {
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.
cx.editor.unset_theme_preview();
} 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()) {
bail!("Unsupported theme: theme requires true color support");
}
@ -902,15 +905,25 @@ fn theme(
}
PromptEvent::Validate => {
if let Some(theme_name) = args.first() {
let theme = cx
.editor
.theme_loader
.load(theme_name)
.map_err(|err| anyhow::anyhow!("Could not load theme: {}", err))?;
// let theme = cx
// .editor
// .theme_loader
// .load(theme_name)
// .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()) {
bail!("Unsupported theme: theme requires true color support");
}
cx.editor.set_theme(theme);
};
} else {
let name = cx.editor.theme.name().to_string();
@ -3087,28 +3100,6 @@ pub(super) fn command_mode(cx: &mut Context) {
let words = shellwords.words();
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(
input,
TYPABLE_COMMAND_LIST

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

@ -32,20 +32,31 @@ use crate::ui::{self, CompletionItem, Popup};
use super::Handlers;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum TriggerKind {
pub(crate) enum TriggerKind {
Auto,
TriggerChar,
Manual,
}
#[derive(Debug, Clone, Copy)]
struct Trigger {
pub(crate) struct Trigger {
pos: usize,
view: ViewId,
doc: DocumentId,
kind: TriggerKind,
}
impl Trigger {
pub(crate) fn new(pos: usize, view: ViewId, doc: DocumentId, kind: TriggerKind) -> Self {
Self {
pos,
view,
doc,
kind,
}
}
}
#[derive(Debug)]
pub(super) struct CompletionHandler {
/// 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,
compositor: &mut Compositor,
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) {
if j.wait {
self.wait_futures.push(j.future);

@ -278,22 +278,36 @@ impl Completion {
// always present here
let mut item = item.unwrap().clone();
let language_server = language_server!(item);
let offset_encoding = language_server.offset_encoding();
// let language_server = language_server!(item);
// let offset_encoding = language_server.offset_encoding();
let language_server = editor
.language_servers
.get_by_id(item.language_server_id)
.unwrap();
// let language_server = editor
// .language_servers
// .get_by_id(item.language_server_id)
// .unwrap();
let mut language_server_option = None;
// resolve item if not yet resolved
if !item.resolved {
let language_server = language_server!(item);
// let offset_encoding = language_server.offset_encoding();
if let Some(resolved) =
Self::resolve_completion_item(language_server, item.item.clone())
{
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
doc.restore(view, &savepoint, true);
// save an undo checkpoint before the completion

@ -270,6 +270,11 @@ pub mod completers {
for rt_dir in helix_loader::runtime_dirs() {
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("base16_default".into());
names.sort();

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

Loading…
Cancel
Save