aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPascal Kuthe2023-05-12 14:42:00 +0000
committerBlaž Hrastnik2023-05-18 06:23:37 +0000
commit2f2306475cac7ee9385b816424137421c13bf4c2 (patch)
tree13203943b250e2cb9f7acb5ec2388863a8b8177c
parentc6f169b1f87d01d72713e84bd331743e4da40f5f (diff)
async picker syntax highlighting
-rw-r--r--helix-term/src/ui/picker.rs107
-rw-r--r--helix-view/src/document.rs24
2 files changed, 91 insertions, 40 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)
diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs
index e29e52cc..770341dc 100644
--- a/helix-view/src/document.rs
+++ b/helix-view/src/document.rs
@@ -154,9 +154,9 @@ pub struct Document {
/// The document's default line ending.
pub line_ending: LineEnding,
- syntax: Option<Syntax>,
+ pub syntax: Option<Syntax>,
/// Corresponding language scope name. Usually `source.<lang>`.
- pub(crate) language: Option<Arc<LanguageConfiguration>>,
+ pub language: Option<Arc<LanguageConfiguration>>,
/// Pending changes since last history commit.
changes: ChangeSet,
@@ -869,12 +869,20 @@ impl Document {
/// Detect the programming language based on the file type.
pub fn detect_language(&mut self, config_loader: Arc<syntax::Loader>) {
- if let Some(path) = &self.path {
- let language_config = config_loader
- .language_config_for_file_name(path)
- .or_else(|| config_loader.language_config_for_shebang(self.text()));
- self.set_language(language_config, Some(config_loader));
- }
+ self.set_language(
+ self.detect_language_config(&config_loader),
+ Some(config_loader),
+ );
+ }
+
+ /// Detect the programming language based on the file type.
+ pub fn detect_language_config(
+ &self,
+ config_loader: &syntax::Loader,
+ ) -> Option<Arc<helix_core::syntax::LanguageConfiguration>> {
+ config_loader
+ .language_config_for_file_name(self.path.as_ref()?)
+ .or_else(|| config_loader.language_config_for_shebang(self.text()))
}
/// Detect the indentation used in the file, or otherwise defaults to the language indentation