Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion org.eclipse.lsp4e.test/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Tests for language server bundle (Incubation)
Bundle-SymbolicName: org.eclipse.lsp4e.test;singleton:=true
Bundle-Version: 0.16.3.qualifier
Bundle-Version: 0.16.4.qualifier
Fragment-Host: org.eclipse.lsp4e
Bundle-Vendor: Eclipse LSP4E
Bundle-RequiredExecutionEnvironment: JavaSE-21
Expand Down
2 changes: 1 addition & 1 deletion org.eclipse.lsp4e.test/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</parent>
<artifactId>org.eclipse.lsp4e.test</artifactId>
<packaging>eclipse-test-plugin</packaging>
<version>0.16.3-SNAPSHOT</version>
<version>0.16.4-SNAPSHOT</version>

<properties>
<os-jvm-flags /> <!-- for the default case -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;

Expand Down Expand Up @@ -57,6 +58,29 @@ public void testFormattingInvalidDocument() throws Exception {
assertTrue(edits.isEmpty());
}

/**
* Verifies that when the language server reports no formatting edits
* (empty edit list), {@link LSPFormatter} returns an empty Optional
* and leaves the document content unchanged.
*/
@Test
public void testFormattingEmptyEditsYieldEmptyOptional() throws Exception {
MockLanguageServer.INSTANCE.setFormattingTextEdits(Collections.emptyList());

IFile file = TestUtils.createUniqueTestFile(project, "Formatting Other Text");
IEditorPart editor = TestUtils.openEditor(file);
ITextViewer viewer = LSPEclipseUtils.getTextViewer(editor);

final var formatter = new LSPFormatter();
ISelection selection = viewer.getSelectionProvider().getSelection();

Optional<VersionedEdits> edits = formatter.requestFormatting(viewer.getDocument(), (ITextSelection) selection).get();
assertTrue(edits.isEmpty());
assertEquals("Formatting Other Text", viewer.getDocument().get());

TestUtils.closeEditor(editor, false);
}

@Test
public void testFormattingNoChanges() throws Exception {
MockLanguageServer.INSTANCE.setFormattingTextEdits(Collections.emptyList());
Expand All @@ -69,14 +93,10 @@ public void testFormattingNoChanges() throws Exception {
ISelection selection = viewer.getSelectionProvider().getSelection();

Optional<VersionedEdits> edits = formatter.requestFormatting(viewer.getDocument(), (ITextSelection) selection).get();
assertTrue(edits.isPresent());
editor.getSite().getShell().getDisplay().syncExec(() -> {
try {
edits.get().apply();
} catch (ConcurrentModificationException | BadLocationException e) {
fail(e.getMessage());
}
});

// No formatting edits produced -> no VersionedEdits returned
assertTrue(edits.isEmpty());

final var textEditor = (ITextEditor) editor;
textEditor.getDocumentProvider().getDocument(textEditor.getEditorInput());
assertEquals("Formatting Other Text", viewer.getDocument().get());
Expand Down Expand Up @@ -243,7 +263,8 @@ public void testSelectiveFormattingWithIncapableServer() throws Exception {
@Test
public void testOutdatedFormatting()
throws CoreException, InterruptedException, ExecutionException, BadLocationException {
MockLanguageServer.INSTANCE.setFormattingTextEdits(Collections.emptyList());
// Use a non-empty edit list so that a VersionedEdits is produced
MockLanguageServer.INSTANCE.setFormattingTextEdits(List.of(new TextEdit(new Range(new Position(0, 0), new Position(0, 0)), "X")));

IFile file = TestUtils.createUniqueTestFile(project, "Formatting Other Text");
IEditorPart editor = TestUtils.openEditor(file);
Expand Down
2 changes: 1 addition & 1 deletion org.eclipse.lsp4e/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Language Server Protocol client for Eclipse IDE (Incubation)
Bundle-SymbolicName: org.eclipse.lsp4e;singleton:=true
Bundle-Version: 0.19.2.qualifier
Bundle-Version: 0.19.3.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-21
Require-Bundle: org.eclipse.core.runtime;bundle-version="3.12.0",
org.eclipse.equinox.common;bundle-version="3.8.0",
Expand Down
2 changes: 1 addition & 1 deletion org.eclipse.lsp4e/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</parent>
<artifactId>org.eclipse.lsp4e</artifactId>
<packaging>eclipse-plugin</packaging>
<version>0.19.2-SNAPSHOT</version>
<version>0.19.3-SNAPSHOT</version>

<build>
<plugins>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
package org.eclipse.lsp4e.operations.format;

import java.net.URI;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
Expand All @@ -31,6 +33,7 @@
import org.eclipse.lsp4j.FormattingOptions;
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.ui.editors.text.EditorsUI;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;

Expand All @@ -49,20 +52,21 @@ public CompletableFuture<Optional<VersionedEdits>> requestFormatting(IDocument d

DocumentFormattingParams params = getFullFormatParams(formatOptions, docId);

// TODO: Could refine this algorithm: at present this grabs the first non-null response but the most functional
// implementation (if a text selection is present) would try all the servers in turn to see if they supported
// range formatting, falling back to a full format if unavailable
// **NOTE:** We let LanguageServers.computeFirst() see the *raw* edit lists so that servers which
// advertise formatting but return no edits (empty list) are treated as "no result" and formatting
// can fall through to the next server (e.g. Vue LS after TS LS on .vue files).
long modificationStamp = DocumentUtil.getDocumentModificationStamp(document);
return executor.computeFirst((w, ls) -> w.getServerCapabilitiesAsync().thenCompose(capabilities -> {
if (isDocumentRangeFormattingSupported(capabilities) && (textSelection.getLength() > 0 || !isDocumentFormattingSupported(capabilities))) {
return ls.getTextDocumentService().rangeFormatting(rangeParams)
.thenApply(edits -> new VersionedEdits(modificationStamp, edits, document));
return (CompletableFuture<@Nullable List<? extends TextEdit>>) ls.getTextDocumentService()
.rangeFormatting(rangeParams);
} else if (isDocumentFormattingSupported(capabilities)) {
return ls.getTextDocumentService().formatting(params)
.thenApply(edits -> new VersionedEdits(modificationStamp, edits, document));
return (CompletableFuture<@Nullable List<? extends TextEdit>>) ls.getTextDocumentService()
.formatting(params);
}
return CompletableFuture.<VersionedEdits>completedFuture(null);
}));
return CompletableFuture.completedFuture(null);
})).thenApply(
optionalEdits -> optionalEdits.map(edits -> new VersionedEdits(modificationStamp, edits, document)));
}

public static DocumentFormattingParams getFullFormatParams(FormattingOptions formatOptions,
Expand Down
Loading