Detailed changes
@@ -18233,7 +18233,7 @@ async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppCon
cx.update_buffer(|buffer, _| {
// This causes autoindent to be async.
- buffer.set_sync_parse_timeout(Duration::ZERO)
+ buffer.set_sync_parse_timeout(None)
});
cx.set_state("fn c() {\n d()ˇ\n}\n");
@@ -838,9 +838,6 @@ impl WindowsPlatformInner {
let peek_msg = |msg: &mut _, msg_kind| unsafe {
PeekMessageW(msg, None, 0, 0, PM_REMOVE | msg_kind).as_bool()
};
- if peek_msg(&mut msg, PM_QS_PAINT) {
- process_message(&msg);
- }
while peek_msg(&mut msg, PM_QS_INPUT) {
process_message(&msg);
}
@@ -121,7 +121,7 @@ pub struct Buffer {
autoindent_requests: Vec<Arc<AutoindentRequest>>,
wait_for_autoindent_txs: Vec<oneshot::Sender<()>>,
pending_autoindent: Option<Task<()>>,
- sync_parse_timeout: Duration,
+ sync_parse_timeout: Option<Duration>,
syntax_map: Mutex<SyntaxMap>,
reparse: Option<Task<()>>,
parse_status: (watch::Sender<ParseStatus>, watch::Receiver<ParseStatus>),
@@ -1124,7 +1124,7 @@ impl Buffer {
syntax_map,
reparse: None,
non_text_state_update_count: 0,
- sync_parse_timeout: Duration::from_millis(1),
+ sync_parse_timeout: Some(Duration::from_millis(1)),
parse_status: watch::channel(ParseStatus::Idle),
autoindent_requests: Default::default(),
wait_for_autoindent_txs: Default::default(),
@@ -1704,7 +1704,7 @@ impl Buffer {
}
#[cfg(any(test, feature = "test-support"))]
- pub fn set_sync_parse_timeout(&mut self, timeout: Duration) {
+ pub fn set_sync_parse_timeout(&mut self, timeout: Option<Duration>) {
self.sync_parse_timeout = timeout;
}
@@ -1763,6 +1763,20 @@ impl Buffer {
let mut syntax_snapshot = syntax_map.snapshot();
drop(syntax_map);
+ self.parse_status.0.send(ParseStatus::Parsing).unwrap();
+ if may_block && let Some(sync_parse_timeout) = self.sync_parse_timeout {
+ if let Ok(()) = syntax_snapshot.reparse_with_timeout(
+ &text,
+ language_registry.clone(),
+ language.clone(),
+ sync_parse_timeout,
+ ) {
+ self.did_finish_parsing(syntax_snapshot, Duration::from_millis(300), cx);
+ self.reparse = None;
+ return;
+ }
+ }
+
let parse_task = cx.background_spawn({
let language = language.clone();
let language_registry = language_registry.clone();
@@ -1772,79 +1786,43 @@ impl Buffer {
}
});
- self.parse_status.0.send(ParseStatus::Parsing).unwrap();
- if may_block {
- match cx
- .background_executor()
- .block_with_timeout(self.sync_parse_timeout, parse_task)
- {
- Ok(new_syntax_snapshot) => {
- self.did_finish_parsing(new_syntax_snapshot, cx);
- self.reparse = None;
- }
- Err(parse_task) => {
- self.reparse = Some(cx.spawn(async move |this, cx| {
- let new_syntax_map = cx.background_spawn(parse_task).await;
- this.update(cx, move |this, cx| {
- let grammar_changed = || {
- this.language.as_ref().is_none_or(|current_language| {
- !Arc::ptr_eq(&language, current_language)
- })
- };
- let language_registry_changed = || {
- new_syntax_map.contains_unknown_injections()
- && language_registry.is_some_and(|registry| {
- registry.version()
- != new_syntax_map.language_registry_version()
- })
- };
- let parse_again = this.version.changed_since(&parsed_version)
- || language_registry_changed()
- || grammar_changed();
- this.did_finish_parsing(new_syntax_map, cx);
- this.reparse = None;
- if parse_again {
- this.reparse(cx, false);
- }
+ self.reparse = Some(cx.spawn(async move |this, cx| {
+ let new_syntax_map = parse_task.await;
+ this.update(cx, move |this, cx| {
+ let grammar_changed = || {
+ this.language
+ .as_ref()
+ .is_none_or(|current_language| !Arc::ptr_eq(&language, current_language))
+ };
+ let language_registry_changed = || {
+ new_syntax_map.contains_unknown_injections()
+ && language_registry.is_some_and(|registry| {
+ registry.version() != new_syntax_map.language_registry_version()
})
- .ok();
- }));
+ };
+ let parse_again = this.version.changed_since(&parsed_version)
+ || language_registry_changed()
+ || grammar_changed();
+ this.did_finish_parsing(new_syntax_map, Duration::ZERO, cx);
+ this.reparse = None;
+ if parse_again {
+ this.reparse(cx, false);
}
- }
- } else {
- self.reparse = Some(cx.spawn(async move |this, cx| {
- let new_syntax_map = cx.background_spawn(parse_task).await;
- this.update(cx, move |this, cx| {
- let grammar_changed = || {
- this.language.as_ref().is_none_or(|current_language| {
- !Arc::ptr_eq(&language, current_language)
- })
- };
- let language_registry_changed = || {
- new_syntax_map.contains_unknown_injections()
- && language_registry.is_some_and(|registry| {
- registry.version() != new_syntax_map.language_registry_version()
- })
- };
- let parse_again = this.version.changed_since(&parsed_version)
- || language_registry_changed()
- || grammar_changed();
- this.did_finish_parsing(new_syntax_map, cx);
- this.reparse = None;
- if parse_again {
- this.reparse(cx, false);
- }
- })
- .ok();
- }));
- }
+ })
+ .ok();
+ }));
}
- fn did_finish_parsing(&mut self, syntax_snapshot: SyntaxSnapshot, cx: &mut Context<Self>) {
- self.was_changed();
+ fn did_finish_parsing(
+ &mut self,
+ syntax_snapshot: SyntaxSnapshot,
+ block_budget: Duration,
+ cx: &mut Context<Self>,
+ ) {
self.non_text_state_update_count += 1;
self.syntax_map.lock().did_parse(syntax_snapshot);
- self.request_autoindent(cx);
+ self.was_changed();
+ self.request_autoindent(cx, block_budget);
self.parse_status.0.send(ParseStatus::Idle).unwrap();
self.invalidate_tree_sitter_data(self.text.snapshot());
cx.emit(BufferEvent::Reparsed);
@@ -1902,12 +1880,12 @@ impl Buffer {
}
}
- fn request_autoindent(&mut self, cx: &mut Context<Self>) {
+ fn request_autoindent(&mut self, cx: &mut Context<Self>, block_budget: Duration) {
if let Some(indent_sizes) = self.compute_autoindents() {
let indent_sizes = cx.background_spawn(indent_sizes);
match cx
.background_executor()
- .block_with_timeout(Duration::from_micros(500), indent_sizes)
+ .block_with_timeout(block_budget, indent_sizes)
{
Ok(indent_sizes) => self.apply_autoindents(indent_sizes, cx),
Err(indent_sizes) => {
@@ -2809,7 +2787,7 @@ impl Buffer {
is_block_mode: false,
ignore_empty_lines: true,
}));
- self.request_autoindent(cx);
+ self.request_autoindent(cx, Duration::from_micros(300));
}
// Inserts newlines at the given position to create an empty line, returning the start of the new line.
@@ -622,9 +622,7 @@ async fn test_reparse(cx: &mut gpui::TestAppContext) {
)
);
- buffer.update(cx, |buffer, _| {
- buffer.set_sync_parse_timeout(Duration::ZERO)
- });
+ buffer.update(cx, |buffer, _| buffer.set_sync_parse_timeout(None));
// Perform some edits (add parameter and variable reference)
// Parsing doesn't begin until the transaction is complete
@@ -736,7 +734,7 @@ async fn test_reparse(cx: &mut gpui::TestAppContext) {
async fn test_resetting_language(cx: &mut gpui::TestAppContext) {
let buffer = cx.new(|cx| {
let mut buffer = Buffer::local("{}", cx).with_language(rust_lang(), cx);
- buffer.set_sync_parse_timeout(Duration::ZERO);
+ buffer.set_sync_parse_timeout(None);
buffer
});
@@ -2359,7 +2357,7 @@ async fn test_async_autoindents_preserve_preview(cx: &mut TestAppContext) {
let mut buffer = Buffer::local(text, cx).with_language(rust_lang(), cx);
// This causes autoindent to be async.
- buffer.set_sync_parse_timeout(Duration::ZERO);
+ buffer.set_sync_parse_timeout(None);
buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
buffer.refresh_preview();
@@ -4,7 +4,6 @@ mod syntax_map_tests;
use crate::{
Grammar, InjectionConfig, Language, LanguageId, LanguageRegistry, QUERY_CURSORS, with_parser,
};
-use anyhow::Context as _;
use collections::HashMap;
use futures::FutureExt;
use gpui::SharedString;
@@ -13,8 +12,9 @@ use std::{
cmp::{self, Ordering, Reverse},
collections::BinaryHeap,
fmt, iter,
- ops::{Deref, DerefMut, Range},
+ ops::{ControlFlow, Deref, DerefMut, Range},
sync::Arc,
+ time::{Duration, Instant},
};
use streaming_iterator::StreamingIterator;
use sum_tree::{Bias, Dimensions, SeekTarget, SumTree};
@@ -421,11 +421,38 @@ impl SyntaxSnapshot {
registry: Option<Arc<LanguageRegistry>>,
root_language: Arc<Language>,
) {
+ self.reparse_(text, registry, root_language, None).ok();
+ }
+
+ pub fn reparse_with_timeout(
+ &mut self,
+ text: &BufferSnapshot,
+ registry: Option<Arc<LanguageRegistry>>,
+ root_language: Arc<Language>,
+ budget: Duration,
+ ) -> Result<(), ParseTimeout> {
+ self.reparse_(text, registry, root_language, Some(budget))
+ }
+
+ fn reparse_(
+ &mut self,
+ text: &BufferSnapshot,
+ registry: Option<Arc<LanguageRegistry>>,
+ root_language: Arc<Language>,
+ mut budget: Option<Duration>,
+ ) -> Result<(), ParseTimeout> {
+ let budget = &mut budget;
let edit_ranges = text
.edits_since::<usize>(&self.parsed_version)
.map(|edit| edit.new)
.collect::<Vec<_>>();
- self.reparse_with_ranges(text, root_language.clone(), edit_ranges, registry.as_ref());
+ self.reparse_with_ranges(
+ text,
+ root_language.clone(),
+ edit_ranges,
+ registry.as_ref(),
+ budget,
+ )?;
if let Some(registry) = registry
&& registry.version() != self.language_registry_version
@@ -460,12 +487,14 @@ impl SyntaxSnapshot {
root_language,
resolved_injection_ranges,
Some(®istry),
- );
+ budget,
+ )?;
}
self.language_registry_version = registry.version();
}
self.update_count += 1;
+ Ok(())
}
fn reparse_with_ranges(
@@ -474,7 +503,8 @@ impl SyntaxSnapshot {
root_language: Arc<Language>,
invalidated_ranges: Vec<Range<usize>>,
registry: Option<&Arc<LanguageRegistry>>,
- ) {
+ budget: &mut Option<Duration>,
+ ) -> Result<(), ParseTimeout> {
log::trace!(
"reparse. invalidated ranges:{:?}",
LogOffsetRanges(&invalidated_ranges, text),
@@ -670,11 +700,15 @@ impl SyntaxSnapshot {
step_start_byte,
&included_ranges,
Some(old_tree.clone()),
+ budget,
);
match result {
Ok(t) => tree = t,
+ Err(e) if e.downcast_ref::<ParseTimeout>().is_some() => {
+ return Err(ParseTimeout);
+ }
Err(e) => {
- log::error!("error parsing text: {:?}", e);
+ log::error!("error parsing text: {e:?}");
continue;
}
};
@@ -723,11 +757,15 @@ impl SyntaxSnapshot {
step_start_byte,
&included_ranges,
None,
+ budget,
);
match result {
Ok(t) => tree = t,
+ Err(e) if e.downcast_ref::<ParseTimeout>().is_some() => {
+ return Err(ParseTimeout);
+ }
Err(e) => {
- log::error!("error parsing text: {:?}", e);
+ log::error!("error parsing text: {e:?}");
continue;
}
};
@@ -805,6 +843,7 @@ impl SyntaxSnapshot {
self.parsed_version = text.version.clone();
#[cfg(debug_assertions)]
self.check_invariants(text);
+ Ok(())
}
#[cfg(debug_assertions)]
@@ -1372,14 +1411,41 @@ fn join_ranges(
result
}
+#[derive(Copy, Clone, Debug)]
+pub struct ParseTimeout;
+
+impl std::error::Error for ParseTimeout {}
+
+impl std::fmt::Display for ParseTimeout {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "parse timeout")
+ }
+}
+
fn parse_text(
grammar: &Grammar,
text: &Rope,
start_byte: usize,
ranges: &[tree_sitter::Range],
old_tree: Option<Tree>,
+ parse_budget: &mut Option<Duration>,
) -> anyhow::Result<Tree> {
with_parser(|parser| {
+ let mut timed_out = false;
+ let now = Instant::now();
+ let mut progress_callback = parse_budget.map(|budget| {
+ let timed_out = &mut timed_out;
+ move |_: &_| {
+ let elapsed = now.elapsed();
+ if elapsed > budget {
+ *timed_out = true;
+ ControlFlow::Break(())
+ } else {
+ ControlFlow::Continue(())
+ }
+ }
+ });
+
let mut chunks = text.chunks_in_range(start_byte..text.len());
parser.set_included_ranges(ranges)?;
parser.set_language(&grammar.ts_language)?;
@@ -1390,9 +1456,21 @@ fn parse_text(
chunks.next().unwrap_or("").as_bytes()
},
old_tree.as_ref(),
- None,
+ progress_callback
+ .as_mut()
+ .map(|progress_callback| tree_sitter::ParseOptions {
+ progress_callback: Some(progress_callback),
+ }),
)
- .context("failed to parse")
+ .inspect(|_| {
+ if let Some(parse_budget) = parse_budget {
+ *parse_budget = parse_budget.saturating_sub(now.elapsed());
+ }
+ })
+ .ok_or_else(|| match timed_out {
+ true => anyhow::anyhow!(ParseTimeout),
+ false => anyhow::anyhow!("parsing failed"),
+ })
})
}
@@ -1053,6 +1053,7 @@ mod tests {
.unindent();
let buffer = cx.new(|cx| language::Buffer::local(text, cx).with_language(language, cx));
+ cx.run_until_parked();
let outline = buffer.read_with(cx, |buffer, _| buffer.snapshot().outline(None));
assert_eq!(
outline