mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-22 01:16:18 +04:00
async picker syntax highlighting
This commit is contained in:
parent
c6f169b1f8
commit
2f2306475c
@ -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 @@
|
||||
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 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 @@ fn placeholder(&self) -> &str {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Item> FilePicker<T> {
|
||||
impl<T: Item + 'static> FilePicker<T> {
|
||||
pub fn new(
|
||||
options: Vec<T>,
|
||||
editor_data: T::Data,
|
||||
@ -208,29 +210,67 @@ fn get_preview<'picker, 'editor>(
|
||||
}
|
||||
|
||||
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<compositor::Callback> = 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::<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 @@ fn required_size(&mut self, (width, height): (u16, u16)) -> Option<(u16, u16)> {
|
||||
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 @@ fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
|
||||
|
||||
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::<Overlay<DynamicPicker<T>>>(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::<Overlay<DynamicPicker<T>>>(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)
|
||||
|
@ -154,9 +154,9 @@ pub struct Document {
|
||||
/// The document's default line ending.
|
||||
pub line_ending: LineEnding,
|
||||
|
||||
syntax: Option<Syntax>,
|
||||
pub syntax: Option<Syntax>,
|
||||
/// Corresponding language scope name. Usually `source.<lang>`.
|
||||
pub(crate) language: Option<Arc<LanguageConfiguration>>,
|
||||
pub language: Option<Arc<LanguageConfiguration>>,
|
||||
|
||||
/// Pending changes since last history commit.
|
||||
changes: ChangeSet,
|
||||
@ -869,12 +869,20 @@ impl Future<Output = Result<DocumentSavedEvent, anyhow::Error>> + 'static + Send
|
||||
|
||||
/// Detect the programming language based on the file type.
|
||||
pub fn detect_language(&mut self, config_loader: Arc<syntax::Loader>) {
|
||||
if let Some(path) = &self.path {
|
||||
let language_config = config_loader
|
||||
.language_config_for_file_name(path)
|
||||
.or_else(|| config_loader.language_config_for_shebang(self.text()));
|
||||
self.set_language(language_config, Some(config_loader));
|
||||
}
|
||||
self.set_language(
|
||||
self.detect_language_config(&config_loader),
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user