async picker syntax highlighting

pull/6446/merge
Pascal Kuthe 2 years ago committed by Blaž Hrastnik
parent c6f169b1f8
commit 2f2306475c

@ -1,7 +1,9 @@
use crate::{ use crate::{
alt, alt,
compositor::{Component, Compositor, Context, Event, EventResult}, compositor::{self, Component, Compositor, Context, Event, EventResult},
ctrl, key, shift, ctrl,
job::Callback,
key, shift,
ui::{ ui::{
self, self,
document::{render_document, LineDecoration, LinePos, TextRenderer}, document::{render_document, LineDecoration, LinePos, TextRenderer},
@ -9,7 +11,7 @@ use crate::{
EditorView, EditorView,
}, },
}; };
use futures_util::future::BoxFuture; use futures_util::{future::BoxFuture, FutureExt};
use tui::{ use tui::{
buffer::Buffer as Surface, buffer::Buffer as Surface,
layout::Constraint, layout::Constraint,
@ -26,7 +28,7 @@ use std::{collections::HashMap, io::Read, path::PathBuf};
use crate::ui::{Prompt, PromptEvent}; use crate::ui::{Prompt, PromptEvent};
use helix_core::{ use helix_core::{
movement::Direction, text_annotations::TextAnnotations, movement::Direction, text_annotations::TextAnnotations,
unicode::segmentation::UnicodeSegmentation, Position, unicode::segmentation::UnicodeSegmentation, Position, Syntax,
}; };
use helix_view::{ use helix_view::{
editor::Action, editor::Action,
@ -122,7 +124,7 @@ impl Preview<'_, '_> {
} }
} }
impl<T: Item> FilePicker<T> { impl<T: Item + 'static> FilePicker<T> {
pub fn new( pub fn new(
options: Vec<T>, options: Vec<T>,
editor_data: T::Data, editor_data: T::Data,
@ -208,29 +210,67 @@ impl<T: Item> FilePicker<T> {
} }
fn handle_idle_timeout(&mut self, cx: &mut Context) -> EventResult { 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 // Try to find a document in the cache
let doc = self let doc = match &current_file {
.current_file(cx.editor) PathOrId::Id(doc_id) => doc_mut!(cx.editor, doc_id),
.and_then(|(path, _range)| match path { PathOrId::Path(path) => match self.preview_cache.get_mut(path) {
PathOrId::Id(doc_id) => Some(doc_mut!(cx.editor, &doc_id)), Some(CachedPreview::Document(ref mut doc)) => doc,
PathOrId::Path(path) => match self.preview_cache.get_mut(&path) { _ => return EventResult::Consumed(None),
Some(CachedPreview::Document(doc)) => Some(doc), },
_ => None, };
},
}); let mut callback: Option<compositor::Callback> = None;
// Then attempt to highlight it if it has no language set // 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(); 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))?; self.picker.required_size((picker_width, height))?;
Some((width, height)) Some((width, height))
} }
fn id(&self) -> Option<&'static str> {
Some("file-picker")
}
} }
#[derive(PartialEq, Eq, Debug)] #[derive(PartialEq, Eq, Debug)]
@ -945,17 +989,16 @@ impl<T: Item + Send + 'static> Component for DynamicPicker<T> {
cx.jobs.callback(async move { cx.jobs.callback(async move {
let new_options = new_options.await?; let new_options = new_options.await?;
let callback = let callback = Callback::EditorCompositor(Box::new(move |editor, compositor| {
crate::job::Callback::EditorCompositor(Box::new(move |editor, compositor| { // Wrapping of pickers in overlay is done outside the picker code,
// Wrapping of pickers in overlay is done outside the picker code, // so this is fragile and will break if wrapped in some other widget.
// so this is fragile and will break if wrapped in some other widget. let picker = match compositor.find_id::<Overlay<DynamicPicker<T>>>(Self::ID) {
let picker = match compositor.find_id::<Overlay<DynamicPicker<T>>>(Self::ID) { Some(overlay) => &mut overlay.content.file_picker.picker,
Some(overlay) => &mut overlay.content.file_picker.picker, None => return,
None => return, };
}; picker.set_options(new_options);
picker.set_options(new_options); editor.reset_idle_timer();
editor.reset_idle_timer(); }));
}));
anyhow::Ok(callback) anyhow::Ok(callback)
}); });
EventResult::Consumed(None) EventResult::Consumed(None)

@ -154,9 +154,9 @@ pub struct Document {
/// The document's default line ending. /// The document's default line ending.
pub line_ending: LineEnding, pub line_ending: LineEnding,
syntax: Option<Syntax>, pub syntax: Option<Syntax>,
/// Corresponding language scope name. Usually `source.<lang>`. /// Corresponding language scope name. Usually `source.<lang>`.
pub(crate) language: Option<Arc<LanguageConfiguration>>, pub language: Option<Arc<LanguageConfiguration>>,
/// Pending changes since last history commit. /// Pending changes since last history commit.
changes: ChangeSet, changes: ChangeSet,
@ -869,12 +869,20 @@ impl Document {
/// Detect the programming language based on the file type. /// Detect the programming language based on the file type.
pub fn detect_language(&mut self, config_loader: Arc<syntax::Loader>) { pub fn detect_language(&mut self, config_loader: Arc<syntax::Loader>) {
if let Some(path) = &self.path { self.set_language(
let language_config = config_loader self.detect_language_config(&config_loader),
.language_config_for_file_name(path) Some(config_loader),
.or_else(|| config_loader.language_config_for_shebang(self.text())); );
self.set_language(language_config, 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 /// Detect the indentation used in the file, or otherwise defaults to the language indentation

Loading…
Cancel
Save