on master help sign in
Make formatting happen asynchronously.
c9be480Joe Neeman
helix-lsp/src/lib.rs+16
@@ -182,6 +182,22 @@ pub mod util {
182 }),
182 }),
183 )
183 )
184 }
184 }
185+
186+ /// The result of asking the language server to format the document. This can be turned into a
187+ /// `Transaction`, but the advantage of not doing that straight away is that this one is
188+ /// `Send` and `Sync`.
189+ #[derive(Clone, Debug)]
190+ pub struct LspFormatting {
191+ pub doc: Rope,
192+ pub edits: Vec<lsp::TextEdit>,
193+ pub offset_encoding: OffsetEncoding,
194+ }
195+
196+ impl From<LspFormatting> for Transaction {
197+ fn from(fmt: LspFormatting) -> Transaction {
198+ generate_transaction_from_edits(&fmt.doc, fmt.edits, fmt.offset_encoding)
199+ }
200+ }
185}
201}
186
202
187#[derive(Debug, PartialEq, Clone)]
203#[derive(Debug, PartialEq, Clone)]
helix-term/src/commands.rs+52−14
@@ -35,7 +35,7 @@ use crate::{
35 ui::{self, Completion, Picker, Popup, Prompt, PromptEvent},
35 ui::{self, Completion, Picker, Popup, Prompt, PromptEvent},
36};
36};
37
37
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};
41
41
@@ -1145,11 +1145,12 @@ mod cmd {
1145 }
1145 }
1146
1146
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);
1153
1154
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);
1172+ shared
1173+ })
1174+ } else {
1175+ None
1168− }
1176+ };
1169− Ok(tokio::spawn(doc.save()))
1177+ Ok(tokio::spawn(doc.format_and_save(fmt)))
1170 }
1178 }
1171
1179
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());
1176 };
1183 };
1177 }
1184 }
@@ -1181,9 +1188,12 @@ mod cmd {
1181 }
1188 }
1182
1189
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);
1185
1192
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);
1196+ }
1187 }
1197 }
1188
1198
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 {
1288 }
1298 }
1289
1299
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 {
1305
1314
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);
1903}
1912}
1904
1913
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,
1918+ doc_version: i32,
1919+ set_unmodified: bool,
1920+ format: impl Future<Output = helix_lsp::util::LspFormatting> + Send + 'static,
1921+) -> LspCallback {
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();
1933+ }
1934+ } else {
1935+ log::info!("discarded formatting changes because the document changed");
1936+ }
1937+ }
1938+ });
1939+ Ok(call)
1940+ })
1941+}
1942+
1905enum Open {
1943enum Open {
1906 Below,
1944 Below,
1907 Above,
1945 Above,
helix-view/src/document.rs+56−26
@@ -16,6 +16,7 @@ use helix_core::{
16 ChangeSet, Diagnostic, LineEnding, Rope, RopeBuilder, Selection, State, Syntax, Transaction,
16 ChangeSet, Diagnostic, LineEnding, Rope, RopeBuilder, Selection, State, Syntax, Transaction,
17 DEFAULT_LINE_ENDING,
17 DEFAULT_LINE_ENDING,
18};
18};
19+use helix_lsp::util::LspFormatting;
19
20
20use crate::{DocumentId, Theme, ViewId};
21use crate::{DocumentId, Theme, ViewId};
21
22
@@ -472,39 +473,55 @@ impl Document {
472 Ok(doc)
473 Ok(doc)
473 }
474 }
474
475
475− // TODO: remove view_id dependency here
476+ pub fn format(&self) -> Option<impl Future<Output = LspFormatting> + 'static> {
476− pub fn format(&mut self, view_id: ViewId) {
477− if let Some(language_server) = self.language_server() {
477+ if let Some(language_server) = self.language_server.clone() {
478− // TODO: await, no blocking
478+ let text = self.text.clone();
479− let transaction = helix_lsp::block_on(language_server.text_document_formatting(
479+ let id = self.identifier();
480− self.identifier(),
480+ let fut = async move {
481+ let edits = language_server
481− lsp::FormattingOptions::default(),
482+ .text_document_formatting(id, lsp::FormattingOptions::default(), None)
482− None,
483+ .await
483− ))
484+ .unwrap_or_else(|e| {
484− .map(|edits| {
485+ log::warn!("LSP formatting failed: {}", e);
485− helix_lsp::util::generate_transaction_from_edits(
486+ Default::default()
487+ });
488+ LspFormatting {
486− self.text(),
489+ doc: text,
487 edits,
490 edits,
488− language_server.offset_encoding(),
491+ offset_encoding: language_server.offset_encoding(),
489− )
492+ }
490− });
493+ };
491−
494+ Some(fut)
492− if let Ok(transaction) = transaction {
495+ } else {
493− self.apply(&transaction, view_id);
496+ None
494− self.append_changes_to_history(view_id);
495− }
496 }
497 }
497 }
498 }
498
499
500+ pub fn save(&mut self) -> impl Future<Output = Result<(), anyhow::Error>> {
501+ self.save_impl::<futures_util::future::Ready<_>>(None)
502+ }
503+
504+ pub fn format_and_save(
505+ &mut self,
506+ formatting: Option<impl Future<Output = LspFormatting>>,
507+ ) -> impl Future<Output = anyhow::Result<()>> {
508+ self.save_impl(formatting)
509+ }
510+
499 // TODO: do we need some way of ensuring two save operations on the same doc can't run at once?
511 // TODO: do we need some way of ensuring two save operations on the same doc can't run at once?
500 // or is that handled by the OS/async layer
512 // or is that handled by the OS/async layer
501 /// The `Document`'s text is encoded according to its encoding and written to the file located
513 /// The `Document`'s text is encoded according to its encoding and written to the file located
502 /// at its `path()`.
514 /// at its `path()`.
515+ ///
516+ /// If `formatting` is present, it supplies some changes that we apply to the text before saving.
517+ fn save_impl<F: Future<Output = LspFormatting>>(
518+ &mut self,
519+ formatting: Option<F>,
503− pub fn save(&mut self) -> impl Future<Output = Result<(), anyhow::Error>> {
520+ ) -> impl Future<Output = Result<(), anyhow::Error>> {
504 // we clone and move text + path into the future so that we asynchronously save the current
521 // we clone and move text + path into the future so that we asynchronously save the current
505 // state without blocking any further edits.
522 // state without blocking any further edits.
506
523
507− let text = self.text().clone();
524+ let mut text = self.text().clone();
508 let path = self.path.clone().expect("Can't save with no path set!"); // TODO: handle no path
525 let path = self.path.clone().expect("Can't save with no path set!"); // TODO: handle no path
509 let identifier = self.identifier();
526 let identifier = self.identifier();
510
527
@@ -512,10 +529,7 @@ impl Document {
512
529
513 let language_server = self.language_server.clone();
530 let language_server = self.language_server.clone();
514
531
515− // reset the modified flag
532+ self.reset_modified();
516− let history = self.history.take();
517− self.last_saved_revision = history.current_revision();
518− self.history.set(history);
519
533
520 let encoding = self.encoding;
534 let encoding = self.encoding;
521
535
@@ -531,6 +545,15 @@ impl Document {
531 }
545 }
532 }
546 }
533
547
548+ if let Some(fmt) = formatting {
549+ let success = Transaction::from(fmt.await).changes().apply(&mut text);
550+ if !success {
551+ // This shouldn't happen, because the transaction changes were generated
552+ // from the same text we're saving.
553+ log::error!("failed to apply format changes before saving");
554+ }
555+ }
556+
534 let mut file = File::create(path).await?;
557 let mut file = File::create(path).await?;
535 to_writer(&mut file, encoding, &text).await?;
558 to_writer(&mut file, encoding, &text).await?;
536
559
@@ -877,6 +900,13 @@ impl Document {
877 current_revision != self.last_saved_revision || !self.changes.is_empty()
900 current_revision != self.last_saved_revision || !self.changes.is_empty()
878 }
901 }
879
902
903+ pub fn reset_modified(&mut self) {
904+ let history = self.history.take();
905+ let current_revision = history.current_revision();
906+ self.history.set(history);
907+ self.last_saved_revision = current_revision;
908+ }
909+
880 pub fn mode(&self) -> Mode {
910 pub fn mode(&self) -> Mode {
881 self.mode
911 self.mode
882 }
912 }
helix-view/src/editor.rs+4
@@ -270,6 +270,10 @@ impl Editor {
270 self.documents.get(id)
270 self.documents.get(id)
271 }
271 }
272
272
273+ pub fn document_mut(&mut self, id: DocumentId) -> Option<&mut Document> {
274+ self.documents.get_mut(id)
275+ }
276+
273 pub fn documents(&self) -> impl Iterator<Item = &Document> {
277 pub fn documents(&self) -> impl Iterator<Item = &Document> {
274 self.documents.iter().map(|(_id, doc)| doc)
278 self.documents.iter().map(|(_id, doc)| doc)
275 }
279 }