Skip to content

Commit 4a7e9d5

Browse files
dead10ckpathwave
authored andcommitted
fix: write-all crash (helix-editor#4384)
When we do auto formatting, the code that takes the LSP's response and applies the changes to the document are just getting the currently focused view and giving that to the function, basically always assuming that the document that we're applying the change to is in focus, and not in a background view. This is usually fine for a single view, even if it's a buffer in the background, because it's still the same view and the selection will get updated accordingly for when you switch back to it. But it's obviously a problem for when there are multiple views, because if you don't have the target document in focus, it will ask the document to update the wrong view, hence the crash. The problem with this is picking which view to apply any selection change to. In the absence of any more data points on the views themselves, we simply pick the first view associated with the document we are saving.
1 parent 682ad54 commit 4a7e9d5

File tree

2 files changed

+32
-8
lines changed

2 files changed

+32
-8
lines changed

helix-term/src/commands.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2522,19 +2522,20 @@ fn insert_at_line_end(cx: &mut Context) {
25222522
async fn make_format_callback(
25232523
doc_id: DocumentId,
25242524
doc_version: i32,
2525+
view_id: ViewId,
25252526
format: impl Future<Output = Result<Transaction, FormatterError>> + Send + 'static,
25262527
write: Option<(Option<PathBuf>, bool)>,
25272528
) -> anyhow::Result<job::Callback> {
25282529
let format = format.await;
25292530

25302531
let call: job::Callback = Callback::Editor(Box::new(move |editor| {
2531-
if !editor.documents.contains_key(&doc_id) {
2532+
if !editor.documents.contains_key(&doc_id) || !editor.tree.contains(view_id) {
25322533
return;
25332534
}
25342535

25352536
let scrolloff = editor.config().scrolloff;
25362537
let doc = doc_mut!(editor, &doc_id);
2537-
let view = view_mut!(editor);
2538+
let view = view_mut!(editor, view_id);
25382539

25392540
if let Ok(format) = format {
25402541
if doc.version() == doc_version {

helix-term/src/commands/typed.rs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -271,14 +271,15 @@ fn write_impl(
271271
) -> anyhow::Result<()> {
272272
let editor_auto_fmt = cx.editor.config().auto_format;
273273
let jobs = &mut cx.jobs;
274-
let doc = doc_mut!(cx.editor);
274+
let (view, doc) = current!(cx.editor);
275275
let path = path.map(AsRef::as_ref);
276276

277277
let fmt = if editor_auto_fmt {
278278
doc.auto_format().map(|fmt| {
279279
let callback = make_format_callback(
280280
doc.id(),
281281
doc.version(),
282+
view.id,
282283
fmt,
283284
Some((path.map(Into::into), force)),
284285
);
@@ -344,9 +345,9 @@ fn format(
344345
return Ok(());
345346
}
346347

347-
let doc = doc!(cx.editor);
348+
let (view, doc) = current!(cx.editor);
348349
if let Some(format) = doc.format() {
349-
let callback = make_format_callback(doc.id(), doc.version(), format, None);
350+
let callback = make_format_callback(doc.id(), doc.version(), view.id, format, None);
350351
cx.jobs.callback(callback);
351352
}
352353

@@ -568,12 +569,13 @@ pub fn write_all_impl(
568569
let mut errors: Vec<&'static str> = Vec::new();
569570
let auto_format = cx.editor.config().auto_format;
570571
let jobs = &mut cx.jobs;
572+
let current_view = view!(cx.editor);
571573

572574
// save all documents
573575
let saves: Vec<_> = cx
574576
.editor
575577
.documents
576-
.values()
578+
.values_mut()
577579
.filter_map(|doc| {
578580
if !doc.is_modified() {
579581
return None;
@@ -585,10 +587,30 @@ pub fn write_all_impl(
585587
return None;
586588
}
587589

590+
// Look for a view to apply the formatting change to. If the document
591+
// is in the current view, just use that. Otherwise, since we don't
592+
// have any other metric available for better selection, just pick
593+
// the first view arbitrarily so that we still commit the document
594+
// state for undos. If somehow we have a document that has not been
595+
// initialized with any view, initialize it with the current view.
596+
let target_view = if doc.selections().contains_key(&current_view.id) {
597+
current_view.id
598+
} else if let Some(view) = doc.selections().keys().next() {
599+
*view
600+
} else {
601+
doc.ensure_view_init(current_view.id);
602+
current_view.id
603+
};
604+
588605
let fmt = if auto_format {
589606
doc.auto_format().map(|fmt| {
590-
let callback =
591-
make_format_callback(doc.id(), doc.version(), fmt, Some((None, force)));
607+
let callback = make_format_callback(
608+
doc.id(),
609+
doc.version(),
610+
target_view,
611+
fmt,
612+
Some((None, force)),
613+
);
592614
jobs.add(Job::with_callback(callback).wait_before_exiting());
593615
})
594616
} else {
@@ -598,6 +620,7 @@ pub fn write_all_impl(
598620
if fmt.is_none() {
599621
return Some(doc.id());
600622
}
623+
601624
None
602625
})
603626
.collect();

0 commit comments

Comments
 (0)