mirror of https://github.com/helix-editor/helix
async picker syntax highlighting
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 ¤t_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…
Reference in New Issue