@@ -35,7 +35,7 @@ use crate::{
35 ui::{self, Completion, Picker, Popup, Prompt, PromptEvent},
35 ui::{self, Completion, Picker, Popup, Prompt, PromptEvent},
38−use crate::application::{LspCallbackWrapper, LspCallbacks};
38+use crate::application::{LspCallback, LspCallbackWrapper, LspCallbacks};
39use futures_util::FutureExt;
39use futures_util::FutureExt;
40use std::{fmt, future::Future, path::Display, str::FromStr};
40use std::{fmt, future::Future, path::Display, str::FromStr};
@@ -1145,11 +1145,12 @@ mod cmd {
1147 fn write_impl<P: AsRef<Path>>(
1147 fn write_impl<P: AsRef<Path>>(
1148− view: &View,
1148+ cx: &mut compositor::Context,
1149− doc: &mut Document,
1150 path: Option<P>,
1149 path: Option<P>,
1151 ) -> Result<tokio::task::JoinHandle<Result<(), anyhow::Error>>, anyhow::Error> {
1150 ) -> Result<tokio::task::JoinHandle<Result<(), anyhow::Error>>, anyhow::Error> {
1152 use anyhow::anyhow;
1151 use anyhow::anyhow;
1152+ let callbacks = &mut cx.callbacks;
1153+ let (view, doc) = current!(cx.editor);
1154 if let Some(path) = path {
1155 if let Some(path) = path {
1155 if let Err(err) = doc.set_path(path.as_ref()) {
1156 if let Err(err) = doc.set_path(path.as_ref()) {
@@ -1163,15 +1164,21 @@ mod cmd {
1163 .language_config()
1164 .language_config()
1164 .map(|config| config.auto_format)
1165 .map(|config| config.auto_format)
1165 .unwrap_or_default();
1166 .unwrap_or_default();
1166− if autofmt {
1167+ let fmt = if autofmt {
1167− doc.format(view.id); // TODO: merge into save
1168+ doc.format().map(|fmt| {
1169+ let shared = fmt.shared();
1170+ let callback = make_format_callback(doc.id(), doc.version(), true, shared.clone());
1171+ callbacks.push(callback);
1169− Ok(tokio::spawn(doc.save()))
1177+ Ok(tokio::spawn(doc.format_and_save(fmt)))
1172 fn write(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1180 fn write(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1173− let (view, doc) = current!(cx.editor);
1174− if let Err(e) = write_impl(view, doc, args.first()) {
1181+ if let Err(e) = write_impl(cx, args.first()) {
1175 cx.editor.set_error(e.to_string());
1182 cx.editor.set_error(e.to_string());
@@ -1181,9 +1188,12 @@ mod cmd {
1183 fn format(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1190 fn format(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1184− let (view, doc) = current!(cx.editor);
1191+ let (_, doc) = current!(cx.editor);
1186− doc.format(view.id)
1193+ if let Some(format) = doc.format() {
1194+ let callback = make_format_callback(doc.id(), doc.version(), false, format);
1195+ cx.callbacks.push(callback);
1189 fn set_indent_style(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1199 fn set_indent_style(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
@@ -1288,8 +1298,7 @@ mod cmd {
1290 fn write_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1300 fn write_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1291− let (view, doc) = current!(cx.editor);
1292− match write_impl(view, doc, args.first()) {
1301+ match write_impl(cx, args.first()) {
1293 Ok(handle) => {
1302 Ok(handle) => {
1294 if let Err(e) = helix_lsp::block_on(handle) {
1303 if let Err(e) = helix_lsp::block_on(handle) {
1295 cx.editor.set_error(e.to_string());
1304 cx.editor.set_error(e.to_string());
@@ -1305,7 +1314,7 @@ mod cmd {
1306 fn force_write_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1315 fn force_write_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) {
1307 let (view, doc) = current!(cx.editor);
1316 let (view, doc) = current!(cx.editor);
1308− match write_impl(view, doc, args.first()) {
1317+ match write_impl(cx, args.first()) {
1309 Ok(handle) => {
1318 Ok(handle) => {
1310 if let Err(e) = helix_lsp::block_on(handle) {
1319 if let Err(e) = helix_lsp::block_on(handle) {
1311 cx.editor.set_error(e.to_string());
1320 cx.editor.set_error(e.to_string());
@@ -1902,6 +1911,35 @@ fn append_to_line(cx: &mut Context) {
1902 doc.set_selection(view.id, selection);
1911 doc.set_selection(view.id, selection);
1914+// Creates an LspCallback that waits for formatting changes to be computed. When they're done,
1915+// it applies them, but only if the doc hasn't changed.
1916+fn make_format_callback(
1917+ doc_id: DocumentId,
1919+ set_unmodified: bool,
1920+ format: impl Future<Output = helix_lsp::util::LspFormatting> + Send + 'static,
1922+ Box::pin(async move {
1923+ let format = format.await;
1924+ let call: LspCallbackWrapper =
1925+ Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
1926+ let view_id = view!(editor).id;
1927+ if let Some(doc) = editor.document_mut(doc_id) {
1928+ if doc.version() == doc_version {
1929+ doc.apply(&Transaction::from(format), view_id);
1930+ doc.append_changes_to_history(view_id);
1931+ if set_unmodified {
1932+ doc.reset_modified();
1935+ log::info!("discarded formatting changes because the document changed");
1905enum Open {
1943enum Open {