From 2f2306475cac7ee9385b816424137421c13bf4c2 Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Fri, 12 May 2023 16:42:00 +0200 Subject: async picker syntax highlighting --- helix-term/src/ui/picker.rs | 107 +++++++++++++++++++++++++++++++------------- 1 file changed, 75 insertions(+), 32 deletions(-) (limited to 'helix-term/src/ui') 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 FilePicker { +impl FilePicker { pub fn new( options: Vec, editor_data: T::Data, @@ -208,29 +210,67 @@ impl FilePicker { } 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 ¤t_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 = 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::>() 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 Component for FilePicker { 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 Component for DynamicPicker { 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::>>(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::>>(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) -- cgit v1.2.3-70-g09d2