on master help sign in
async picker syntax highlighting
commit2f2306475cac7ee9385b816424137421c13bf4c2 authorPascal Kuthe <pascal.kuthe@semimod.de> dateFri 12 May 2023, 16:42 (3 years ago) parentc6f169b
2 files changed+91−40
helix-term/src/ui/picker.rs+75−32 helix-view/src/document.rs+16−8
helix-term/src/ui/picker.rs+75−32
@@ -1,7 +1,9 @@
11use crate::{
22 alt,
3− compositor::{Component, Compositor, Context, Event, EventResult},
4− ctrl, key, shift,
3+ compositor::{self, Component, Compositor, Context, Event, EventResult},
4+ ctrl,
5+ job::Callback,
6+ key, shift,
57 ui::{
68 self,
79 document::{render_document, LineDecoration, LinePos, TextRenderer},
@@ -9,7 +11,7 @@ use crate::{
911 EditorView,
1012 },
1113};
12−use futures_util::future::BoxFuture;
14+use futures_util::{future::BoxFuture, FutureExt};
1315use tui::{
1416 buffer::Buffer as Surface,
1517 layout::Constraint,
@@ -26,7 +28,7 @@ use std::{collections::HashMap, io::Read, path::PathBuf};
2628use crate::ui::{Prompt, PromptEvent};
2729use helix_core::{
2830 movement::Direction, text_annotations::TextAnnotations,
29− unicode::segmentation::UnicodeSegmentation, Position,
31+ unicode::segmentation::UnicodeSegmentation, Position, Syntax,
3032};
3133use helix_view::{
3234 editor::Action,
@@ -122,7 +124,7 @@ impl Preview<'_, '_> {
122124 }
123125}
124126
125−impl<T: Item> FilePicker<T> {
127+impl<T: Item + 'static> FilePicker<T> {
126128 pub fn new(
127129 options: Vec<T>,
128130 editor_data: T::Data,
@@ -208,29 +210,67 @@ impl<T: Item> FilePicker<T> {
208210 }
209211
210212 fn handle_idle_timeout(&mut self, cx: &mut Context) -> EventResult {
213+ let Some((current_file, _)) = self.current_file(cx.editor) else {
214+ return EventResult::Consumed(None)
215+ };
216+
211217 // Try to find a document in the cache
212− let doc = self
213− .current_file(cx.editor)
214− .and_then(|(path, _range)| match path {
215− PathOrId::Id(doc_id) => Some(doc_mut!(cx.editor, &doc_id)),
216− PathOrId::Path(path) => match self.preview_cache.get_mut(&path) {
217− Some(CachedPreview::Document(doc)) => Some(doc),
218− _ => None,
219− },
220− });
218+ let doc = match &current_file {
219+ PathOrId::Id(doc_id) => doc_mut!(cx.editor, doc_id),
220+ PathOrId::Path(path) => match self.preview_cache.get_mut(path) {
221+ Some(CachedPreview::Document(ref mut doc)) => doc,
222+ _ => return EventResult::Consumed(None),
223+ },
224+ };
225+
226+ let mut callback: Option<compositor::Callback> = None;
221227
222228 // Then attempt to highlight it if it has no language set
223− if let Some(doc) = doc {
224− if doc.language_config().is_none() {
229+ if doc.language_config().is_none() {
230+ if let Some(language_config) = doc.detect_language_config(&cx.editor.syn_loader) {
231+ doc.language = Some(language_config.clone());
232+ let text = doc.text().clone();
225233 let loader = cx.editor.syn_loader.clone();
226− doc.detect_language(loader);
234+ let job = tokio::task::spawn_blocking(move || {
235+ let syntax = language_config
236+ .highlight_config(&loader.scopes())
237+ .and_then(|highlight_config| Syntax::new(&text, highlight_config, loader));
238+ let callback = move |editor: &mut Editor, compositor: &mut Compositor| {
239+ let Some(syntax) = syntax else {
240+ log::info!("highlighting picker item failed");
241+ return
242+ };
243+ log::info!("hmm1");
244+ let Some(Overlay { content: picker, .. }) = compositor.find::<Overlay<Self>>() else {
245+ log::info!("picker closed before syntax highlighting finished");
246+ return
247+ };
248+ log::info!("hmm2");
249+ // Try to find a document in the cache
250+ let doc = match current_file {
251+ PathOrId::Id(doc_id) => doc_mut!(editor, &doc_id),
252+ PathOrId::Path(path) => match picker.preview_cache.get_mut(&path) {
253+ Some(CachedPreview::Document(ref mut doc)) => doc,
254+ _ => return,
255+ },
256+ };
257+ log::info!("yay");
258+ doc.syntax = Some(syntax);
259+ };
260+ Callback::EditorCompositor(Box::new(callback))
261+ });
262+ let tmp: compositor::Callback = Box::new(move |_, ctx| {
263+ ctx.jobs
264+ .callback(job.map(|res| res.map_err(anyhow::Error::from)))
265+ });
266+ callback = Some(Box::new(tmp))
227267 }
228−
229− // QUESTION: do we want to compute inlay hints in pickers too ? Probably not for now
230− // but it could be interesting in the future
231268 }
232269
233− EventResult::Consumed(None)
270+ // QUESTION: do we want to compute inlay hints in pickers too ? Probably not for now
271+ // but it could be interesting in the future
272+
273+ EventResult::Consumed(callback)
234274 }
235275}
236276
@@ -373,6 +413,10 @@ impl<T: Item + 'static> Component for FilePicker<T> {
373413 self.picker.required_size((picker_width, height))?;
374414 Some((width, height))
375415 }
416+
417+ fn id(&self) -> Option<&'static str> {
418+ Some("file-picker")
419+ }
376420}
377421
378422#[derive(PartialEq, Eq, Debug)]
@@ -945,17 +989,16 @@ impl<T: Item + Send + 'static> Component for DynamicPicker<T> {
945989
946990 cx.jobs.callback(async move {
947991 let new_options = new_options.await?;
948− let callback =
949− crate::job::Callback::EditorCompositor(Box::new(move |editor, compositor| {
950− // Wrapping of pickers in overlay is done outside the picker code,
951− // so this is fragile and will break if wrapped in some other widget.
952− let picker = match compositor.find_id::<Overlay<DynamicPicker<T>>>(Self::ID) {
953− Some(overlay) => &mut overlay.content.file_picker.picker,
954− None => return,
955− };
956− picker.set_options(new_options);
957− editor.reset_idle_timer();
958− }));
992+ let callback = Callback::EditorCompositor(Box::new(move |editor, compositor| {
993+ // Wrapping of pickers in overlay is done outside the picker code,
994+ // so this is fragile and will break if wrapped in some other widget.
995+ let picker = match compositor.find_id::<Overlay<DynamicPicker<T>>>(Self::ID) {
996+ Some(overlay) => &mut overlay.content.file_picker.picker,
997+ None => return,
998+ };
999+ picker.set_options(new_options);
1000+ editor.reset_idle_timer();
1001+ }));
9591002 anyhow::Ok(callback)
9601003 });
9611004 EventResult::Consumed(None)
helix-view/src/document.rs+16−8
@@ -154,9 +154,9 @@ pub struct Document {
154154 /// The document's default line ending.
155155 pub line_ending: LineEnding,
156156
157− syntax: Option<Syntax>,
157+ pub syntax: Option<Syntax>,
158158 /// Corresponding language scope name. Usually `source.<lang>`.
159− pub(crate) language: Option<Arc<LanguageConfiguration>>,
159+ pub language: Option<Arc<LanguageConfiguration>>,
160160
161161 /// Pending changes since last history commit.
162162 changes: ChangeSet,
@@ -869,12 +869,20 @@ impl Document {
869869
870870 /// Detect the programming language based on the file type.
871871 pub fn detect_language(&mut self, config_loader: Arc<syntax::Loader>) {
872− if let Some(path) = &self.path {
873− let language_config = config_loader
874− .language_config_for_file_name(path)
875− .or_else(|| config_loader.language_config_for_shebang(self.text()));
876− self.set_language(language_config, Some(config_loader));
877− }
872+ self.set_language(
873+ self.detect_language_config(&config_loader),
874+ Some(config_loader),
875+ );
876+ }
877+
878+ /// Detect the programming language based on the file type.
879+ pub fn detect_language_config(
880+ &self,
881+ config_loader: &syntax::Loader,
882+ ) -> Option<Arc<helix_core::syntax::LanguageConfiguration>> {
883+ config_loader
884+ .language_config_for_file_name(self.path.as_ref()?)
885+ .or_else(|| config_loader.language_config_for_shebang(self.text()))
878886 }
879887
880888 /// Detect the indentation used in the file, or otherwise defaults to the language indentation