From 42d70fb7a5b9ee917d531ae1c3d1a49ec50c23e8 Mon Sep 17 00:00:00 2001 From: Gesa Hentschke Date: Thu, 24 Aug 2023 20:38:51 +0200 Subject: [PATCH] use formatDocument as fallback for willSaveWaitUntil if server does not support willSaveWaitUntil the code can be formatted prior to save. fixes #761 --- org.eclipse.lsp4e/.project | 5 +++ org.eclipse.lsp4e/META-INF/MANIFEST.MF | 4 ++- .../org.eclipse.lsp4e.DefaultFormatOnSave.xml | 8 +++++ org.eclipse.lsp4e/build.properties | 3 +- .../eclipse/lsp4e/DefaultFormatOnSave.java | 36 +++++++++++++++++++ .../lsp4e/DocumentContentSynchronizer.java | 33 +++++++++++++++++ .../src/org/eclipse/lsp4e/IFormatOnSave.java | 34 ++++++++++++++++++ .../org/eclipse/lsp4e/LSPEclipseUtils.java | 2 +- 8 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 org.eclipse.lsp4e/OSGI-INF/org.eclipse.lsp4e.DefaultFormatOnSave.xml create mode 100644 org.eclipse.lsp4e/src/org/eclipse/lsp4e/DefaultFormatOnSave.java create mode 100644 org.eclipse.lsp4e/src/org/eclipse/lsp4e/IFormatOnSave.java diff --git a/org.eclipse.lsp4e/.project b/org.eclipse.lsp4e/.project index 6486494b5..31c95a6d2 100644 --- a/org.eclipse.lsp4e/.project +++ b/org.eclipse.lsp4e/.project @@ -25,6 +25,11 @@ + + org.eclipse.pde.ds.core.builder + + + org.eclipse.m2e.core.maven2Nature diff --git a/org.eclipse.lsp4e/META-INF/MANIFEST.MF b/org.eclipse.lsp4e/META-INF/MANIFEST.MF index 54d16fb9d..d61b4ac1e 100644 --- a/org.eclipse.lsp4e/META-INF/MANIFEST.MF +++ b/org.eclipse.lsp4e/META-INF/MANIFEST.MF @@ -41,7 +41,8 @@ Require-Bundle: org.eclipse.core.runtime;bundle-version="3.12.0", org.eclipse.ui.browser, org.eclipse.ui.intro;bundle-version="3.5.200", com.google.guava;bundle-version="30.1.0", - org.eclipse.e4.core.commands + org.eclipse.e4.core.commands, + org.eclipse.compare.core Bundle-ClassPath: . Bundle-Localization: plugin Bundle-ActivationPolicy: lazy @@ -57,3 +58,4 @@ Bundle-Vendor: Eclipse.org Import-Package: com.google.common.base, com.google.gson;version="2.7.0" Automatic-Module-Name: org.eclipse.lsp4e +Service-Component: OSGI-INF/org.eclipse.lsp4e.DefaultFormatOnSave.xml diff --git a/org.eclipse.lsp4e/OSGI-INF/org.eclipse.lsp4e.DefaultFormatOnSave.xml b/org.eclipse.lsp4e/OSGI-INF/org.eclipse.lsp4e.DefaultFormatOnSave.xml new file mode 100644 index 000000000..0d804594d --- /dev/null +++ b/org.eclipse.lsp4e/OSGI-INF/org.eclipse.lsp4e.DefaultFormatOnSave.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/org.eclipse.lsp4e/build.properties b/org.eclipse.lsp4e/build.properties index cce94c934..69bab11a2 100644 --- a/org.eclipse.lsp4e/build.properties +++ b/org.eclipse.lsp4e/build.properties @@ -7,5 +7,6 @@ bin.includes = META-INF/,\ icons/,\ .options,\ resources/,\ - schema/ + schema/,\ + OSGI-INF/ src.includes = schema/ diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/DefaultFormatOnSave.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/DefaultFormatOnSave.java new file mode 100644 index 000000000..26e22e1bc --- /dev/null +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/DefaultFormatOnSave.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2023 Contributors to the Eclipse Foundation. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * See git history + *******************************************************************************/ +package org.eclipse.lsp4e; + +import org.eclipse.core.filebuffers.ITextFileBuffer; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.Region; +import org.osgi.service.component.annotations.Component; + +@Component(property = { "service.ranking:Integer=0" }) +public class DefaultFormatOnSave implements IFormatOnSave { + + @Override + public boolean isEnabledFor(IDocument document) { + return false; + } + + /** + * Formats the full file + */ + @Override + public IRegion[] getFormattingRegions(ITextFileBuffer buffer) { + return new IRegion[] { new Region(0, buffer.getDocument().getLength()) }; + } + +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/DocumentContentSynchronizer.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/DocumentContentSynchronizer.java index deb5c560f..4ee1d484f 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/DocumentContentSynchronizer.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/DocumentContentSynchronizer.java @@ -16,6 +16,7 @@ import java.io.File; import java.net.URI; import java.util.Collections; +import java.util.ConcurrentModificationException; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -31,6 +32,7 @@ import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.ServiceCaller; import org.eclipse.core.runtime.content.IContentType; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jface.preference.IPreferenceStore; @@ -38,6 +40,9 @@ import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentListener; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.MultiTextSelection; +import org.eclipse.lsp4e.operations.format.LSPFormatter; import org.eclipse.lsp4e.ui.Messages; import org.eclipse.lsp4j.DidChangeTextDocumentParams; import org.eclipse.lsp4j.DidCloseTextDocumentParams; @@ -65,12 +70,16 @@ final class DocumentContentSynchronizer implements IDocumentListener { private final @NonNull IDocument document; private final @NonNull URI fileUri; private final TextDocumentSyncKind syncKind; + private final LSPFormatter formatter = new LSPFormatter(); private int version = 0; private DidChangeTextDocumentParams changeParams; private long openSaveStamp; private IPreferenceStore store; + + private final ServiceCaller formatOnSave = new ServiceCaller<>(getClass(), IFormatOnSave.class); + public DocumentContentSynchronizer(@NonNull LanguageServerWrapper languageServerWrapper, @NonNull LanguageServer languageServer, @NonNull IDocument document, TextDocumentSyncKind syncKind) { @@ -221,6 +230,9 @@ private int lsToWillSaveWaitUntilTimeout() { public void documentAboutToBeSaved() { if (!serverSupportsWillSaveWaitUntil()) { + if (formatOnSave != null) { + formatOnSave.call(f -> formatDocument(f.isEnabledFor(document), f.getFormattingRegions(LSPEclipseUtils.toBuffer(document)))); + } return; } @@ -257,6 +269,27 @@ public void documentAboutToBeSaved() { } } + private void formatDocument(boolean enable, IRegion[] formattingRegions) { + if (enable && document != null && formattingRegions != null) { + try { + var textSelection = new MultiTextSelection(document, formattingRegions); + formatter.requestFormatting(document, textSelection).get(lsToWillSaveWaitUntilTimeout(), TimeUnit.SECONDS) + .ifPresent(edits -> { + try { + edits.apply(); + } catch (final ConcurrentModificationException ex) { + ServerMessageHandler.showMessage(Messages.LSPFormatHandler_DiscardedFormat, new MessageParams(MessageType.Error, Messages.LSPFormatHandler_DiscardedFormatResponse)); + } catch (BadLocationException e) { + LanguageServerPlugin.logError(e); + } + }); + } catch (BadLocationException | InterruptedException | ExecutionException + | TimeoutException e) { + LanguageServerPlugin.logError(e); + } + } + } + public void documentSaved(IFileBuffer buffer) { if (openSaveStamp >= buffer.getModificationStamp()) { return; diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/IFormatOnSave.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/IFormatOnSave.java new file mode 100644 index 000000000..06f9c0273 --- /dev/null +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/IFormatOnSave.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2023 Contributors to the Eclipse Foundation. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * See git history + *******************************************************************************/ +package org.eclipse.lsp4e; + +import org.eclipse.core.filebuffers.ITextFileBuffer; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; + +public interface IFormatOnSave { + + /** + * Checks whether formatting shall be performed prior to file buffer saving. + * @param document + * @return true if document buffer shall be formatted prior saving + */ + boolean isEnabledFor(IDocument document); + + /** + * Get the regions to be formatted (e.g. full file, lines edited this session, lines edited vs. source control baseline). + * @param buffer the buffer to compare contents from + * @return regions to be formatted before saving. + */ + IRegion[] getFormattingRegions(ITextFileBuffer buffer); + +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LSPEclipseUtils.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LSPEclipseUtils.java index 4f29822fb..6b2b12efa 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LSPEclipseUtils.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LSPEclipseUtils.java @@ -355,7 +355,7 @@ public static CallHierarchyPrepareParams toCallHierarchyPrepareParams(int offset } - private static ITextFileBuffer toBuffer(IDocument document) { + public static ITextFileBuffer toBuffer(IDocument document) { ITextFileBufferManager bufferManager = FileBuffers.getTextFileBufferManager(); if (bufferManager == null) return null;