@@ -1,7 +1,9 @@
1use crate::{
1use crate::{
3− compositor::{Component, Compositor, Context, Event, EventResult},
3+ compositor::{self, Component, Compositor, Context, Event, EventResult},
4− ctrl, key, shift,
6+ key, shift,
7 document::{render_document, LineDecoration, LinePos, TextRenderer},
9 document::{render_document, LineDecoration, LinePos, TextRenderer},
@@ -9,7 +11,7 @@ use crate::{
9 EditorView,
11 EditorView,
12−use futures_util::future::BoxFuture;
14+use futures_util::{future::BoxFuture, FutureExt};
14 buffer::Buffer as Surface,
16 buffer::Buffer as Surface,
15 layout::Constraint,
17 layout::Constraint,
@@ -26,7 +28,7 @@ use std::{collections::HashMap, io::Read, path::PathBuf};
26use crate::ui::{Prompt, PromptEvent};
28use crate::ui::{Prompt, PromptEvent};
27use helix_core::{
29use helix_core::{
28 movement::Direction, text_annotations::TextAnnotations,
30 movement::Direction, text_annotations::TextAnnotations,
29− unicode::segmentation::UnicodeSegmentation, Position,
31+ unicode::segmentation::UnicodeSegmentation, Position, Syntax,
31use helix_view::{
33use helix_view::{
32 editor::Action,
34 editor::Action,
@@ -122,7 +124,7 @@ impl Preview<'_, '_> {
125−impl<T: Item> FilePicker<T> {
127+impl<T: Item + 'static> FilePicker<T> {
126 pub fn new(
128 pub fn new(
127 options: Vec<T>,
129 options: Vec<T>,
128 editor_data: T::Data,
130 editor_data: T::Data,
@@ -208,29 +210,67 @@ impl<T: Item> FilePicker<T> {
210 fn handle_idle_timeout(&mut self, cx: &mut Context) -> EventResult {
212 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)
211 // Try to find a document in the cache
217 // Try to find a document in the cache
213− .current_file(cx.editor)
218+ let doc = match ¤t_file {
214− .and_then(|(path, _range)| match path {
215− PathOrId::Id(doc_id) => Some(doc_mut!(cx.editor, &doc_id)),
219+ PathOrId::Id(doc_id) => doc_mut!(cx.editor, doc_id),
216− PathOrId::Path(path) => match self.preview_cache.get_mut(&path) {
220+ PathOrId::Path(path) => match self.preview_cache.get_mut(path) {
217− Some(CachedPreview::Document(doc)) => Some(doc),
221+ Some(CachedPreview::Document(ref mut doc)) => doc,
218− _ => None,
222+ _ => return EventResult::Consumed(None),
226+ let mut callback: Option<compositor::Callback> = None;
222 // Then attempt to highlight it if it has no language set
228 // 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();
225 let loader = cx.editor.syn_loader.clone();
233 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");
244+ let Some(Overlay { content: picker, .. }) = compositor.find::<Overlay<Self>>() else {
245+ log::info!("picker closed before syntax highlighting finished");
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,
258+ doc.syntax = Some(syntax);
260+ Callback::EditorCompositor(Box::new(callback))
262+ let tmp: compositor::Callback = Box::new(move |_, ctx| {
264+ .callback(job.map(|res| res.map_err(anyhow::Error::from)))
266+ callback = Some(Box::new(tmp))
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
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
233− EventResult::Consumed(None)
273+ EventResult::Consumed(callback)
@@ -373,6 +413,10 @@ impl<T: Item + 'static> Component for FilePicker<T> {
373 self.picker.required_size((picker_width, height))?;
413 self.picker.required_size((picker_width, height))?;
374 Some((width, height))
414 Some((width, height))
417+ fn id(&self) -> Option<&'static str> {
378#[derive(PartialEq, Eq, Debug)]
422#[derive(PartialEq, Eq, Debug)]
@@ -945,17 +989,16 @@ impl<T: Item + Send + 'static> Component for DynamicPicker<T> {
946 cx.jobs.callback(async move {
990 cx.jobs.callback(async move {
947 let new_options = new_options.await?;
991 let new_options = new_options.await?;
949− crate::job::Callback::EditorCompositor(Box::new(move |editor, compositor| {
992+ let callback = Callback::EditorCompositor(Box::new(move |editor, compositor| {
950− // Wrapping of pickers in overlay is done outside the picker code,
993+ // 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.
994+ // 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) {
995+ let picker = match compositor.find_id::<Overlay<DynamicPicker<T>>>(Self::ID) {
953− Some(overlay) => &mut overlay.content.file_picker.picker,
996+ Some(overlay) => &mut overlay.content.file_picker.picker,
954− None => return,
997+ None => return,
956− picker.set_options(new_options);
999+ picker.set_options(new_options);
957− editor.reset_idle_timer();
1000+ editor.reset_idle_timer();
959 anyhow::Ok(callback)
1002 anyhow::Ok(callback)
961 EventResult::Consumed(None)
1004 EventResult::Consumed(None)