aboutsummaryrefslogtreecommitdiff
path: root/helix-term
diff options
context:
space:
mode:
authorPascal Kuthe2023-11-30 23:03:26 +0000
committerBlaž Hrastnik2024-01-23 02:20:19 +0000
commit13ed4f6c4748019787d24c2b686d417b71604242 (patch)
tree8263b77ee05a22dfc85410345069efb3668b1877 /helix-term
parent7d7ace551cd58f0b6d65af7a6dfa8f896d94724a (diff)
Add hook/event system
Diffstat (limited to 'helix-term')
-rw-r--r--helix-term/Cargo.toml2
-rw-r--r--helix-term/src/application.rs24
-rw-r--r--helix-term/src/commands.rs26
-rw-r--r--helix-term/src/events.rs20
-rw-r--r--helix-term/src/handlers.rs15
-rw-r--r--helix-term/src/job.rs55
-rw-r--r--helix-term/src/lib.rs4
-rw-r--r--helix-term/src/ui/editor.rs24
8 files changed, 140 insertions, 30 deletions
diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml
index 21c35553..7bdd433e 100644
--- a/helix-term/Cargo.toml
+++ b/helix-term/Cargo.toml
@@ -15,7 +15,7 @@ homepage.workspace = true
[features]
default = ["git"]
unicode-lines = ["helix-core/unicode-lines"]
-integration = []
+integration = ["helix-event/integration_test"]
git = ["helix-vcs/git"]
[[bin]]
diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index 290441b4..8215eeaa 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -1,6 +1,10 @@
use arc_swap::{access::Map, ArcSwap};
use futures_util::Stream;
-use helix_core::{pos_at_coords, syntax, Selection};
+use helix_core::{
+ chars::char_is_word,
+ diagnostic::{DiagnosticTag, NumberOrString},
+ pos_at_coords, syntax, Selection,
+};
use helix_lsp::{
lsp::{self, notification::Notification},
util::lsp_range_to_range,
@@ -24,6 +28,7 @@ use crate::{
commands::apply_workspace_edit,
compositor::{Compositor, Event},
config::Config,
+ handlers,
job::Jobs,
keymap::Keymaps,
ui::{self, overlay::overlaid},
@@ -138,6 +143,7 @@ impl Application {
let area = terminal.size().expect("couldn't get terminal size");
let mut compositor = Compositor::new(area);
let config = Arc::new(ArcSwap::from_pointee(config));
+ let handlers = handlers::setup(config.clone());
let mut editor = Editor::new(
area,
theme_loader.clone(),
@@ -145,6 +151,7 @@ impl Application {
Arc::new(Map::new(Arc::clone(&config), |config: &Config| {
&config.editor
})),
+ handlers,
);
let keys = Box::new(Map::new(Arc::clone(&config), |config: &Config| {
@@ -321,10 +328,21 @@ impl Application {
Some(event) = input_stream.next() => {
self.handle_terminal_events(event).await;
}
- Some(callback) = self.jobs.futures.next() => {
- self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
+ Some(callback) = self.jobs.callbacks.recv() => {
+ self.jobs.handle_callback(&mut self.editor, &mut self.compositor, Ok(Some(callback)));
self.render().await;
}
+ Some(msg) = self.jobs.status_messages.recv() => {
+ let severity = match msg.severity{
+ helix_event::status::Severity::Hint => Severity::Hint,
+ helix_event::status::Severity::Info => Severity::Info,
+ helix_event::status::Severity::Warning => Severity::Warning,
+ helix_event::status::Severity::Error => Severity::Error,
+ };
+ // TODO: show multiple status messages at once to avoid clobbering
+ self.editor.status_msg = Some((msg.message, severity));
+ helix_event::request_redraw();
+ }
Some(callback) = self.jobs.wait_futures.next() => {
self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
self.render().await;
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 53783e4e..48ceb23b 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -88,7 +88,7 @@ pub struct Context<'a> {
pub count: Option<NonZeroUsize>,
pub editor: &'a mut Editor,
- pub callback: Option<crate::compositor::Callback>,
+ pub callback: Vec<crate::compositor::Callback>,
pub on_next_key_callback: Option<OnKeyCallback>,
pub jobs: &'a mut Jobs,
}
@@ -96,16 +96,18 @@ pub struct Context<'a> {
impl<'a> Context<'a> {
/// Push a new component onto the compositor.
pub fn push_layer(&mut self, component: Box<dyn Component>) {
- self.callback = Some(Box::new(|compositor: &mut Compositor, _| {
- compositor.push(component)
- }));
+ self.callback
+ .push(Box::new(|compositor: &mut Compositor, _| {
+ compositor.push(component)
+ }));
}
/// Call `replace_or_push` on the Compositor
pub fn replace_or_push_layer<T: Component>(&mut self, id: &'static str, component: T) {
- self.callback = Some(Box::new(move |compositor: &mut Compositor, _| {
- compositor.replace_or_push(id, component);
- }));
+ self.callback
+ .push(Box::new(move |compositor: &mut Compositor, _| {
+ compositor.replace_or_push(id, component);
+ }));
}
#[inline]
@@ -2934,7 +2936,7 @@ pub fn command_palette(cx: &mut Context) {
let register = cx.register;
let count = cx.count;
- cx.callback = Some(Box::new(
+ cx.callback.push(Box::new(
move |compositor: &mut Compositor, cx: &mut compositor::Context| {
let keymap = compositor.find::<ui::EditorView>().unwrap().keymaps.map()
[&cx.editor.mode]
@@ -2954,7 +2956,7 @@ pub fn command_palette(cx: &mut Context) {
register,
count,
editor: cx.editor,
- callback: None,
+ callback: Vec::new(),
on_next_key_callback: None,
jobs: cx.jobs,
};
@@ -2982,7 +2984,7 @@ pub fn command_palette(cx: &mut Context) {
fn last_picker(cx: &mut Context) {
// TODO: last picker does not seem to work well with buffer_picker
- cx.callback = Some(Box::new(|compositor, cx| {
+ cx.callback.push(Box::new(|compositor, cx| {
if let Some(picker) = compositor.last_picker.take() {
compositor.push(picker);
} else {
@@ -3494,6 +3496,7 @@ fn hunk_range(hunk: Hunk, text: RopeSlice) -> Range {
}
pub mod insert {
+ use crate::events::PostInsertChar;
use super::*;
pub type Hook = fn(&Rope, &Selection, char) -> Option<Transaction>;
pub type PostHook = fn(&mut Context, char);
@@ -3627,6 +3630,7 @@ pub mod insert {
for hook in &[language_server_completion, signature_help] {
hook(cx, c);
}
+ helix_event::dispatch(PostInsertChar { c, cx });
}
pub fn smart_tab(cx: &mut Context) {
@@ -5820,7 +5824,7 @@ fn replay_macro(cx: &mut Context) {
cx.editor.macro_replaying.push(reg);
let count = cx.count();
- cx.callback = Some(Box::new(move |compositor, cx| {
+ cx.callback.push(Box::new(move |compositor, cx| {
for _ in 0..count {
for &key in keys.iter() {
compositor.handle_event(&compositor::Event::Key(key), cx);
diff --git a/helix-term/src/events.rs b/helix-term/src/events.rs
new file mode 100644
index 00000000..49b44f77
--- /dev/null
+++ b/helix-term/src/events.rs
@@ -0,0 +1,20 @@
+use helix_event::{events, register_event};
+use helix_view::document::Mode;
+use helix_view::events::{DocumentDidChange, SelectionDidChange};
+
+use crate::commands;
+use crate::keymap::MappableCommand;
+
+events! {
+ OnModeSwitch<'a, 'cx> { old_mode: Mode, new_mode: Mode, cx: &'a mut commands::Context<'cx> }
+ PostInsertChar<'a, 'cx> { c: char, cx: &'a mut commands::Context<'cx> }
+ PostCommand<'a, 'cx> { command: & 'a MappableCommand, cx: &'a mut commands::Context<'cx> }
+}
+
+pub fn register() {
+ register_event::<OnModeSwitch>();
+ register_event::<PostInsertChar>();
+ register_event::<PostCommand>();
+ register_event::<DocumentDidChange>();
+ register_event::<SelectionDidChange>();
+}
diff --git a/helix-term/src/handlers.rs b/helix-term/src/handlers.rs
new file mode 100644
index 00000000..ab2d724f
--- /dev/null
+++ b/helix-term/src/handlers.rs
@@ -0,0 +1,15 @@
+use std::sync::Arc;
+
+use arc_swap::ArcSwap;
+
+use crate::config::Config;
+use crate::events;
+
+
+ }
+pub fn setup(config: Arc<ArcSwap<Config>>) -> Handlers {
+ events::register();
+ let handlers = Handlers {
+ };
+ handlers
+}
diff --git a/helix-term/src/job.rs b/helix-term/src/job.rs
index 19f2521a..72ed892d 100644
--- a/helix-term/src/job.rs
+++ b/helix-term/src/job.rs
@@ -1,13 +1,37 @@
+use helix_event::status::StatusMessage;
+use helix_event::{runtime_local, send_blocking};
use helix_view::Editor;
+use once_cell::sync::OnceCell;
use crate::compositor::Compositor;
use futures_util::future::{BoxFuture, Future, FutureExt};
use futures_util::stream::{FuturesUnordered, StreamExt};
+use tokio::sync::mpsc::{channel, Receiver, Sender};
pub type EditorCompositorCallback = Box<dyn FnOnce(&mut Editor, &mut Compositor) + Send>;
pub type EditorCallback = Box<dyn FnOnce(&mut Editor) + Send>;
+runtime_local! {
+ static JOB_QUEUE: OnceCell<Sender<Callback>> = OnceCell::new();
+}
+
+pub async fn dispatch_callback(job: Callback) {
+ let _ = JOB_QUEUE.wait().send(job).await;
+}
+
+pub async fn dispatch(job: impl FnOnce(&mut Editor, &mut Compositor) + Send + 'static) {
+ let _ = JOB_QUEUE
+ .wait()
+ .send(Callback::EditorCompositor(Box::new(job)))
+ .await;
+}
+
+pub fn dispatch_blocking(job: impl FnOnce(&mut Editor, &mut Compositor) + Send + 'static) {
+ let jobs = JOB_QUEUE.wait();
+ send_blocking(jobs, Callback::EditorCompositor(Box::new(job)))
+}
+
pub enum Callback {
EditorCompositor(EditorCompositorCallback),
Editor(EditorCallback),
@@ -21,11 +45,11 @@ pub struct Job {
pub wait: bool,
}
-#[derive(Default)]
pub struct Jobs {
- pub futures: FuturesUnordered<JobFuture>,
- /// These are the ones that need to complete before we exit.
+ /// jobs that need to complete before we exit.
pub wait_futures: FuturesUnordered<JobFuture>,
+ pub callbacks: Receiver<Callback>,
+ pub status_messages: Receiver<StatusMessage>,
}
impl Job {
@@ -52,8 +76,16 @@ impl Job {
}
impl Jobs {
+ #[allow(clippy::new_without_default)]
pub fn new() -> Self {
- Self::default()
+ let (tx, rx) = channel(1024);
+ let _ = JOB_QUEUE.set(tx);
+ let status_messages = helix_event::status::setup();
+ Self {
+ wait_futures: FuturesUnordered::new(),
+ callbacks: rx,
+ status_messages,
+ }
}
pub fn spawn<F: Future<Output = anyhow::Result<()>> + Send + 'static>(&mut self, f: F) {
@@ -85,18 +117,17 @@ 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);
} else {
- self.futures.push(j.future);
+ tokio::spawn(async move {
+ match j.future.await {
+ Ok(Some(cb)) => dispatch_callback(cb).await,
+ Ok(None) => (),
+ Err(err) => helix_event::status::report(err).await,
+ }
+ });
}
}
diff --git a/helix-term/src/lib.rs b/helix-term/src/lib.rs
index a1d60329..b1413ed0 100644
--- a/helix-term/src/lib.rs
+++ b/helix-term/src/lib.rs
@@ -6,13 +6,17 @@ pub mod args;
pub mod commands;
pub mod compositor;
pub mod config;
+pub mod events;
pub mod health;
pub mod job;
pub mod keymap;
pub mod ui;
+
use std::path::Path;
use futures_util::Future;
+mod handlers;
+
use ignore::DirEntry;
use url::Url;
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs
index 24fcdb01..9f186d14 100644
--- a/helix-term/src/ui/editor.rs
+++ b/helix-term/src/ui/editor.rs
@@ -2,6 +2,7 @@ use crate::{
commands::{self, OnKeyCallback},
compositor::{Component, Context, Event, EventResult},
job::{self, Callback},
+ events::{OnModeSwitch, PostCommand},
key,
keymap::{KeymapResult, Keymaps},
ui::{
@@ -835,11 +836,18 @@ impl EditorView {
let mut execute_command = |command: &commands::MappableCommand| {
command.execute(cxt);
+ helix_event::dispatch(PostCommand { command, cx: cxt });
let current_mode = cxt.editor.mode();
match (last_mode, current_mode) {
(Mode::Normal, Mode::Insert) => {
// HAXX: if we just entered insert mode from normal, clear key buf
// and record the command that got us into this mode.
+ if current_mode != last_mode {
+ helix_event::dispatch(OnModeSwitch {
+ old_mode: last_mode,
+ new_mode: current_mode,
+ cx: cxt,
+ });
// how we entered insert mode is important, and we should track that so
// we can repeat the side effect.
@@ -1004,7 +1012,7 @@ impl EditorView {
}
let area = completion.area(size, editor);
- editor.last_completion = None;
+ editor.last_completion = Some(CompleteAction::Triggered);
self.last_insert.1.push(InsertEvent::TriggerCompletion);
// TODO : propagate required size on resize to completion too
@@ -1265,7 +1273,7 @@ impl Component for EditorView {
editor: context.editor,
count: None,
register: None,
- callback: None,
+ callback: Vec::new(),
on_next_key_callback: None,
jobs: context.jobs,
};
@@ -1375,7 +1383,7 @@ impl Component for EditorView {
}
// appease borrowck
- let callback = cx.callback.take();
+ let callbacks = take(&mut cx.callback);
// if the command consumed the last view, skip the render.
// on the next loop cycle the Application will then terminate.
@@ -1394,6 +1402,16 @@ impl Component for EditorView {
if mode != Mode::Insert {
doc.append_changes_to_history(view);
}
+ let callback = if callbacks.is_empty() {
+ None
+ } else {
+ let callback: crate::compositor::Callback = Box::new(move |compositor, cx| {
+ for callback in callbacks {
+ callback(compositor, cx)
+ }
+ });
+ Some(callback)
+ };
EventResult::Consumed(callback)
}