aboutsummaryrefslogtreecommitdiff
path: root/helix-term
diff options
context:
space:
mode:
authorPascal Kuthe2023-05-12 14:42:00 +0000
committerBlaž Hrastnik2023-05-18 06:23:37 +0000
commit2f2306475cac7ee9385b816424137421c13bf4c2 (patch)
tree13203943b250e2cb9f7acb5ec2388863a8b8177c /helix-term
parentc6f169b1f87d01d72713e84bd331743e4da40f5f (diff)
async picker syntax highlighting
Diffstat (limited to 'helix-term')
-rw-r--r--helix-term/src/ui/picker.rs107
1 files changed, 75 insertions, 32 deletions
diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs
index e7a7de90..6120bfd1 100644
--- a/helix-term/src/ui/picker.rs
+++ b/helix-term/src/ui/picker.rs
@@ -1,7 +1,9 @@
use crate::{
alt,
- compositor::{Component, Compositor, Context, Event, EventResult},
- ctrl, key, shift,
+ compositor::{self, Component, Compositor, Context, Event, EventResult},
+ ctrl,
+ job::Callback,
+ key, shift,
ui::{
self,
document::{render_document, LineDecoration, LinePos, TextRenderer},
@@ -9,7 +11,7 @@ use crate::{
EditorView,
},
};
-use futures_util::future::BoxFuture;
+use futures_util::{future::BoxFuture, FutureExt};
use tui::{
buffer::Buffer as Surface,
layout::Constraint,
@@ -26,7 +28,7 @@ use std::{collections::HashMap, io::Read, path::PathBuf};
use crate::ui::{Prompt, PromptEvent};
use helix_core::{
movement::Direction, text_annotations::TextAnnotations,
- unicode::segmentation::UnicodeSegmentation, Position,
+ unicode::segmentation::UnicodeSegmentation, Position, Syntax,
};
use helix_view::{
editor::Action,
@@ -122,7 +124,7 @@ impl Preview<'_, '_> {
}
}
-impl<T: Item> FilePicker<T> {
+impl<T: Item + 'static> FilePicker<T> {
pub fn new(
options: Vec<T>,
editor_data: T::Data,
@@ -208,29 +210,67 @@ impl<T: Item> FilePicker<T> {
}
fn handle_idle_timeout(&mut self, cx: &mut Context) -> EventResult {
+ let Some((current_file, _)) = self.current_file(cx.editor) else {
+ return EventResult::Consumed(None)
+ };
+
// Try to find a document in the cache
- let doc = self
- .current_file(cx.editor)
- .and_then(|(path, _range)| match path {
- PathOrId::Id(doc_id) => Some(doc_mut!(cx.editor, &doc_id)),
- PathOrId::Path(path) => match self.preview_cache.get_mut(&path) {
- Some(CachedPreview::Document(doc)) => Some(doc),
- _ => None,
- },
- });
+ let doc = match &current_file {
+ PathOrId::Id(doc_id) => doc_mut!(cx.editor, doc_id),
+ PathOrId::Path(path) => match self.preview_cache.get_mut(path) {
+ Some(CachedPreview::Document(ref mut doc)) => doc,
+ _ => return EventResult::Consumed(None),
+ },
+ };
+
+ let mut callback: Option<compositor::Callback> = None;
// Then attempt to highlight it if it has no language set
- if let Some(doc) = doc {
- if doc.language_config().is_none() {
+ if doc.language_config().is_none() {
+ if let Some(language_config) = doc.detect_language_config(&cx.editor.syn_loader) {
+ doc.language = Some(language_config.clone());
+ let text = doc.text().clone();
let loader = cx.editor.syn_loader.clone();
- doc.detect_language(loader);
+ let job = tokio::task::spawn_blocking(move || {
+ let syntax = language_config
+ .highlight_config(&loader.scopes())
+ .and_then(|highlight_config| Syntax::new(&text, highlight_config, loader));
+ let callback = move |editor: &mut Editor, compositor: &mut Compositor| {
+ let Some(syntax) = syntax else {
+ log::info!("highlighting picker item failed");
+ return
+ };
+ log::info!("hmm1");
+ let Some(Overlay { content: picker, .. }) = compositor.find::<Overlay<Self>>() else {
+ log::info!("picker closed before syntax highlighting finished");
+ return
+ };
+ log::info!("hmm2");
+ // Try to find a document in the cache
+ let doc = match current_file {
+ PathOrId::Id(doc_id) => doc_mut!(editor, &doc_id),
+ PathOrId::Path(path) => match picker.preview_cache.get_mut(&path) {
+ Some(CachedPreview::Document(ref mut doc)) => doc,
+ _ => return,
+ },
+ };
+ log::info!("yay");
+ doc.syntax = Some(syntax);
+ };
+ Callback::EditorCompositor(Box::new(callback))
+ });
+ let tmp: compositor::Callback = Box::new(move |_, ctx| {
+ ctx.jobs
+ .callback(job.map(|res| res.map_err(anyhow::Error::from)))
+ });
+ callback = Some(Box::new(tmp))
}
-
- // QUESTION: do we want to compute inlay hints in pickers too ? Probably not for now
- // but it could be interesting in the future
}
- EventResult::Consumed(None)
+ // QUESTION: do we want to compute inlay hints in pickers too ? Probably not for now
+ // but it could be interesting in the future
+
+ EventResult::Consumed(callback)
}
}
@@ -373,6 +413,10 @@ impl<T: Item + 'static> Component for FilePicker<T> {
self.picker.required_size((picker_width, height))?;
Some((width, height))
}
+
+ fn id(&self) -> Option<&'static str> {
+ Some("file-picker")
+ }
}
#[derive(PartialEq, Eq, Debug)]
@@ -945,17 +989,16 @@ impl<T: Item + Send + 'static> Component for DynamicPicker<T> {
cx.jobs.callback(async move {
let new_options = new_options.await?;
- let callback =
- crate::job::Callback::EditorCompositor(Box::new(move |editor, compositor| {
- // Wrapping of pickers in overlay is done outside the picker code,
- // so this is fragile and will break if wrapped in some other widget.
- let picker = match compositor.find_id::<Overlay<DynamicPicker<T>>>(Self::ID) {
- Some(overlay) => &mut overlay.content.file_picker.picker,
- None => return,
- };
- picker.set_options(new_options);
- editor.reset_idle_timer();
- }));
+ let callback = Callback::EditorCompositor(Box::new(move |editor, compositor| {
+ // Wrapping of pickers in overlay is done outside the picker code,
+ // so this is fragile and will break if wrapped in some other widget.
+ let picker = match compositor.find_id::<Overlay<DynamicPicker<T>>>(Self::ID) {
+ Some(overlay) => &mut overlay.content.file_picker.picker,
+ None => return,
+ };
+ picker.set_options(new_options);
+ editor.reset_idle_timer();
+ }));
anyhow::Ok(callback)
});
EventResult::Consumed(None)