[#1672] Added LSP support for foldings

Signed-off-by: Mark Sujew <mark.sujew@typefox.io>
This commit is contained in:
Mark Sujew 2021-02-23 23:07:43 +01:00
parent bc53c4efdb
commit ab57ca53b4
14 changed files with 769 additions and 0 deletions

View file

@ -0,0 +1,55 @@
/*******************************************************************************
* Copyright (c) 2021 TypeFox GmbH (http://www.typefox.io) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.xtext.ide.tests.editor.folding;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.ide.editor.folding.FoldingRange;
import org.eclipse.xtext.ide.editor.folding.IFoldingRangeProvider;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.testing.util.ParseHelper;
import org.eclipse.xtext.util.CancelIndicator;
import org.junit.Assert;
import com.google.inject.Inject;
/**
* @author Mark Sujew - Initial contribution and API
*/
public abstract class AbstractFoldingRangeProviderTest<T extends EObject> {
@Inject
private IFoldingRangeProvider foldingRangeProvider;
@Inject
private ParseHelper<T> parseHelper;
protected void assertFoldingRanges(CharSequence content, FoldingRange... expectedRanges) {
XtextResource resource;
try {
resource = (XtextResource) parseHelper.parse(content).eResource();
} catch (Exception e) {
throw new RuntimeException(e);
}
List<FoldingRange> foldingRanges = foldingRangeProvider.getFoldingRanges(resource, CancelIndicator.NullImpl)
.stream().collect(Collectors.toList());
Assert.assertEquals(expectedRanges.length, foldingRanges.size());
for (int i = 0; i < foldingRanges.size(); i++) {
FoldingRange range = foldingRanges.get(i);
FoldingRange expected = expectedRanges[i];
Assert.assertEquals(expected.getOffset(), range.getOffset());
Assert.assertEquals(expected.getLength(), range.getLength());
Assert.assertEquals(expected.getKind(), range.getKind());
}
}
}

View file

@ -0,0 +1,51 @@
/*******************************************************************************
* Copyright (c) 2021 TypeFox GmbH (http://www.typefox.io) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.xtext.ide.tests.editor.folding;
import org.eclipse.xtext.ide.editor.folding.FoldingRange;
import org.eclipse.xtext.ide.editor.folding.FoldingRangeKind;
import org.eclipse.xtext.ide.tests.testlanguage.TestLanguageIdeInjectorProvider;
import org.eclipse.xtext.ide.tests.testlanguage.testLanguage.Model;
import org.eclipse.xtext.testing.InjectWith;
import org.eclipse.xtext.testing.XtextRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* @author Mark Sujew - Initial contribution and API
*/
@RunWith(XtextRunner.class)
@InjectWith(TestLanguageIdeInjectorProvider.class)
public class FoldingRangeProviderTest extends AbstractFoldingRangeProviderTest<Model> {
@Test
public void foldingRangesProvider() {
String actual = "package testLanguage {"
+ "\n"
+ "\n";
int commentIndex = actual.length();
actual += "/*\n"
+ " * This s a multiline comment"
+ " */";
int commentLength = actual.length() - commentIndex;
actual += "\n";
int typeIndex = actual.length();
actual += "type myType {"
+ "\n"
+ "}";
int typeLength = actual.length() - typeIndex;
actual += "\n}";
assertFoldingRanges(actual, new FoldingRange(0, actual.length()), new FoldingRange(commentIndex, commentLength, FoldingRangeKind.COMMENT), new FoldingRange(typeIndex, typeLength));
}
}

View file

@ -0,0 +1,27 @@
/*******************************************************************************
* Copyright (c) 2021 TypeFox GmbH (http://www.typefox.io) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.xtext.ide.tests.server;
import org.junit.Test;
/**
* @author Mark Sujew - Initial contribution and API
*/
public class FoldingTest extends AbstractTestLangLanguageServerTest {
@Test
public void testFoldingService() {
testFolding(it -> {
it.setModel("/*\nMultiline Comment\n*/\ntype Foo {\nint bar\n}");
String expectedText = "[comment 0..2]\n[null 3..5]\n";
it.setExpectedFoldings(expectedText);
});
}
}

View file

@ -0,0 +1,44 @@
/*******************************************************************************
* Copyright (c) 2010, 2021 Michael Clay and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
*******************************************************************************/
package org.eclipse.xtext.ide.editor.folding;
import java.util.Collection;
import org.eclipse.xtext.util.ITextRegion;
/**
* @author Michael Clay - Initial contribution and API
* @author Sebastian Zarnekow - Introduced FoldedPosition
* @author Mark Sujew - Ported to IDE project
*
* @since 2.26
*/
public class DefaultFoldingRangeAcceptor implements IFoldingRangeAcceptor {
private Collection<FoldingRange> result;
public DefaultFoldingRangeAcceptor(Collection<FoldingRange> result) {
this.result = result;
}
public Collection<FoldingRange> getFoldingRanges() {
return result;
}
@Override
public void accept(int offset, int length, FoldingRangeKind kind, boolean initiallyFolded,
ITextRegion visualPlaceholderRegion) {
result.add(createFoldingRange(offset, length, kind, initiallyFolded, visualPlaceholderRegion));
}
protected FoldingRange createFoldingRange(int offset, int length, FoldingRangeKind kind, boolean initiallyFolded,
ITextRegion visualPlaceholderRegion) {
return new FoldingRange(offset, length, kind, initiallyFolded, visualPlaceholderRegion);
}
}

View file

@ -0,0 +1,202 @@
/*******************************************************************************
* Copyright (c) 2010, 2021 Michael Clay and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
*******************************************************************************/
package org.eclipse.xtext.ide.editor.folding;
import java.util.Collection;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.TerminalRule;
import org.eclipse.xtext.documentation.impl.AbstractMultiLineCommentProvider;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.parser.IParseResult;
import org.eclipse.xtext.resource.ILocationInFileProvider;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.util.CancelIndicator;
import org.eclipse.xtext.util.ITextRegion;
import org.eclipse.xtext.util.ITextRegionWithLineInformation;
import org.eclipse.xtext.util.LineAndColumn;
import org.eclipse.xtext.util.TextRegion;
import com.google.inject.Inject;
import com.google.inject.name.Named;
/**
* @author Michael Clay - Initial contribution and API
* @author Sebastian Zarnekow - Introduced FoldedRegion, use ILocationInFileProvider
* @author Mark Sujew - Ported to IDE project
*
* @since 2.26
*/
public class DefaultFoldingRangeProvider implements IFoldingRangeProvider {
@Inject
private ILocationInFileProvider locationInFileProvider;
@Inject(optional = true)
@Named(AbstractMultiLineCommentProvider.RULE)
private String ruleName = "ML_COMMENT";
protected static final Pattern TEXT_PATTERN_IN_COMMENT = Pattern.compile("\\w");
@Override
public SortedSet<FoldingRange> getFoldingRanges(XtextResource xtextDocument, CancelIndicator cancelIndicator) {
SortedSet<FoldingRange> result = new TreeSet<>((a, b) -> Integer.compare(a.getOffset(), b.getOffset()));
IFoldingRangeAcceptor foldingRegionAcceptor = createAcceptor(xtextDocument, result);
computeObjectFolding(xtextDocument, foldingRegionAcceptor, cancelIndicator);
computeCommentFolding(xtextDocument, foldingRegionAcceptor);
return result;
}
protected void computeObjectFolding(XtextResource resource, IFoldingRangeAcceptor foldingRangeAcceptor,
CancelIndicator cancelIndicator) {
IParseResult parseResult = resource.getParseResult();
if (parseResult != null) {
EObject rootASTElement = parseResult.getRootASTElement();
if (rootASTElement != null) {
if (cancelIndicator.isCanceled())
throw new OperationCanceledException();
if (isHandled(rootASTElement)) {
acceptObjectFolding(rootASTElement, foldingRangeAcceptor);
}
if (shouldProcessContent(rootASTElement)) {
TreeIterator<EObject> allContents = rootASTElement.eAllContents();
while (allContents.hasNext()) {
if (cancelIndicator.isCanceled())
throw new OperationCanceledException();
EObject eObject = allContents.next();
if (isHandled(eObject)) {
acceptObjectFolding(eObject, foldingRangeAcceptor);
}
if (!shouldProcessContent(eObject)) {
allContents.prune();
}
}
}
}
}
}
protected void acceptObjectFolding(EObject eObject, IFoldingRangeAcceptor foldingRangeAcceptor) {
ITextRegion region = locationInFileProvider.getFullTextRegion(eObject);
if (region != null) {
INode eObjectNode = NodeModelUtils.getNode(eObject);
ITextRegion significant = buildSignificantRegion(locationInFileProvider.getSignificantTextRegion(eObject),
eObjectNode);
foldingRangeAcceptor.accept(region.getOffset(), region.getLength(), null, false, significant);
}
}
protected ITextRegion buildSignificantRegion(ITextRegion significantRegion, INode node) {
if (significantRegion == null || node == null) {
return null;
}
int offset = significantRegion.getOffset();
int endOffset = significantRegion.getOffset() + significantRegion.getLength();
int startLine;
int endLine;
if (significantRegion instanceof ITextRegionWithLineInformation) {
ITextRegionWithLineInformation lineInfoRegion = (ITextRegionWithLineInformation) significantRegion;
startLine = lineInfoRegion.getLineNumber();
endLine = lineInfoRegion.getEndLineNumber();
} else {
startLine = NodeModelUtils.getLineAndColumn(node, offset).getLine();
endLine = NodeModelUtils.getLineAndColumn(node, endOffset).getLine();
}
if (startLine != endLine) {
/*
* The Eclipse IDE can only use the significant region if it starts and ends on the same line.
* Therefore, we here calculate the index of the end of the line.
*/
for (int index = offset; index < endOffset; index++) {
LineAndColumn lineInfo = NodeModelUtils.getLineAndColumn(node, index);
if (lineInfo.getLine() != startLine) {
return new TextRegion(offset, index - offset - 1);
}
}
}
return significantRegion;
}
protected void computeCommentFolding(XtextResource resource, IFoldingRangeAcceptor foldingRangeAcceptor) {
IParseResult parseResult = resource.getParseResult();
if (parseResult != null) {
EObject rootASTElement = parseResult.getRootASTElement();
acceptCommentNodes(rootASTElement, foldingRangeAcceptor);
}
}
protected void acceptCommentNodes(EObject eObject, IFoldingRangeAcceptor foldingRangeAcceptor) {
ICompositeNode node = NodeModelUtils.getNode(eObject);
if (node != null) {
for (INode leafNode : node.getAsTreeIterable()) {
if (leafNode.getGrammarElement() instanceof TerminalRule
&& ruleName.equalsIgnoreCase(((TerminalRule) leafNode.getGrammarElement()).getName())) {
acceptCommentFolding(leafNode, foldingRangeAcceptor);
}
}
}
}
protected void acceptCommentFolding(INode commentNode, IFoldingRangeAcceptor foldingRangeAcceptor) {
int offset = commentNode.getOffset();
int length = commentNode.getLength();
Matcher matcher = getTextPatternInComment().matcher(commentNode.getText());
if (matcher.find()) {
TextRegion significant = new TextRegion(offset + matcher.start(), 0);
foldingRangeAcceptor.accept(offset, length, FoldingRangeKind.COMMENT, significant);
} else {
foldingRangeAcceptor.accept(offset, length, FoldingRangeKind.COMMENT);
}
}
protected IFoldingRangeAcceptor createAcceptor(XtextResource resource, Collection<FoldingRange> foldingRanges) {
return new DefaultFoldingRangeAcceptor(foldingRanges);
}
protected ILocationInFileProvider getLocationInFileProvider() {
return locationInFileProvider;
}
protected String getMultilineCommentRuleName() {
return ruleName;
}
/**
* @return the regular expression that finds the first significant part of a multi line comment.
*/
protected Pattern getTextPatternInComment() {
return TEXT_PATTERN_IN_COMMENT;
}
/**
* @return <code>true</code> if the object should be folded if it spans more than one line. Default is
* <code>false</code> if and only if the object is the root object of the resource.
*/
protected boolean isHandled(EObject eObject) {
return eObject.eContainer() != null;
}
/**
* @return clients should return <code>false</code> to abort the traversal of the model.
*/
protected boolean shouldProcessContent(EObject object) {
return true;
}
}

View file

@ -0,0 +1,93 @@
/*******************************************************************************
* Copyright (c) 2021 TypeFox GmbH (http://www.typefox.io) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.xtext.ide.editor.folding;
import org.eclipse.xtext.util.ITextRegion;
import com.google.common.base.Objects;
/**
* Represents an abstraction for different folding implementations.
* Specifically Eclipse's <code>FoldingRegion</code> and the <code>FoldingRange</code> of the LSP specification.
*
* @author Mark Sujew - Initial contribution and API
*
* @since 2.26
*/
public class FoldingRange {
private final int offset;
private final int length;
private final FoldingRangeKind kind;
private final boolean initiallyFolded;
private final ITextRegion visualPlaceholderRegion;
public FoldingRange(int offset, int length) {
this(offset, length, null);
}
public FoldingRange(int offset, int length, FoldingRangeKind kind) {
this(offset, length, kind, false, null);
}
public FoldingRange(int offset, int length, FoldingRangeKind kind, boolean initiallyFolded,
ITextRegion visualPlaceholderRegion) {
this.offset = offset;
this.length = length;
this.kind = kind;
this.initiallyFolded = initiallyFolded;
this.visualPlaceholderRegion = visualPlaceholderRegion;
}
public boolean isInitiallyFolded() {
return initiallyFolded;
}
public ITextRegion getVisualPlaceholderRegion() {
return visualPlaceholderRegion;
}
public FoldingRangeKind getKind() {
return kind;
}
public int getOffset() {
return offset;
}
public int getLength() {
return length;
}
@Override
public boolean equals(Object other) {
if (other instanceof FoldingRange) {
FoldingRange range = (FoldingRange) other;
return offset == range.offset && length == range.length && initiallyFolded == range.initiallyFolded
&& Objects.equal(kind, range.kind)
&& Objects.equal(visualPlaceholderRegion, range.visualPlaceholderRegion);
}
return false;
}
@Override
public int hashCode() {
return Objects.hashCode(offset, length, initiallyFolded, kind, visualPlaceholderRegion);
}
@Override
public String toString() {
StringBuilder content = new StringBuilder();
content.append("offset=").append(offset);
content.append(", length=").append(length);
content.append(", kind=").append(kind);
content.append(", initiallyFolded=").append(initiallyFolded);
return content.toString();
}
}

View file

@ -0,0 +1,46 @@
/*******************************************************************************
* Copyright (c) 2021 TypeFox GmbH (http://www.typefox.io) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.xtext.ide.editor.folding;
/**
* Contains the possible kinds of folding ranges defined by the LSP spec.
*
* @author Mark Sujew - Initial contribution and API
*
* @since 2.26
*/
public class FoldingRangeKind {
public static final FoldingRangeKind COMMENT = new FoldingRangeKind("comment");
public static final FoldingRangeKind IMPORTS = new FoldingRangeKind("imports");
public static final FoldingRangeKind REGION = new FoldingRangeKind("region");
private String kind;
private FoldingRangeKind(String kind) {
this.kind = kind;
}
@Override
public String toString() {
return kind;
}
@Override
public boolean equals(Object other) {
if (other instanceof FoldingRangeKind) {
return this.kind.equals(((FoldingRangeKind) other).kind);
}
return false;
}
@Override
public int hashCode() {
return kind.hashCode();
}
}

View file

@ -0,0 +1,45 @@
/*******************************************************************************
* Copyright (c) 2010, 2021 Michael Clay and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
*******************************************************************************/
package org.eclipse.xtext.ide.editor.folding;
import org.eclipse.xtext.util.ITextRegion;
/**
* @author Michael Clay - Initial contribution and API
* @author Sebastian Zarnekow - Distinguish between total and identifying region
* @author Mark Sujew - Ported to IDE project
*
* @since 2.26
*/
public interface IFoldingRangeAcceptor {
default void accept(int offset, int length) {
accept(offset, length, false);
}
default void accept(int offset, int length, FoldingRangeKind kind) {
accept(offset, length, kind, null);
}
default void accept(int offset, int length, boolean initiallyFolded) {
accept(offset, length, null, initiallyFolded, null);
}
default void accept(int offset, int length, ITextRegion visualPlaceholderRegion) {
accept(offset, length, null, false, visualPlaceholderRegion);
}
default void accept(int offset, int length, FoldingRangeKind kind, ITextRegion visualPlaceholderRegion) {
accept(offset, length, kind, false, visualPlaceholderRegion);
}
void accept(int offset, int length, FoldingRangeKind kind, boolean initiallyFolded,
ITextRegion visualPlaceholderRegion);
}

View file

@ -0,0 +1,36 @@
/*******************************************************************************
* Copyright (c) 2010, 2021 Michael Clay and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
*******************************************************************************/
package org.eclipse.xtext.ide.editor.folding;
import java.util.SortedSet;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.util.CancelIndicator;
import com.google.inject.ImplementedBy;
/**
* Compute the folding regions in the given document. This is a blocking action when opening the editor in e4, so
* clients should be careful to not resolve too many cross references in their implementation.
*
* @author Michael Clay - Initial contribution and API
* @author Sebastian Zarnekow - Refactoring, introduced FoldedPosition
* @author Mark Sujew - Ported to IDE project
*
* @since 2.26
*/
@ImplementedBy(DefaultFoldingRangeProvider.class)
public interface IFoldingRangeProvider {
/**
* @return the set of <code>FoldingRanges</code> for the given document
*/
SortedSet<FoldingRange> getFoldingRanges(XtextResource xtextDocument, CancelIndicator cancelIndicator);
}

View file

@ -56,6 +56,9 @@ import org.eclipse.lsp4j.ExecuteCommandCapabilities;
import org.eclipse.lsp4j.ExecuteCommandOptions;
import org.eclipse.lsp4j.ExecuteCommandParams;
import org.eclipse.lsp4j.FileChangeType;
import org.eclipse.lsp4j.FoldingRange;
import org.eclipse.lsp4j.FoldingRangeCapabilities;
import org.eclipse.lsp4j.FoldingRangeRequestParams;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.HoverParams;
import org.eclipse.lsp4j.InitializeParams;
@ -111,6 +114,7 @@ import org.eclipse.xtext.ide.server.commands.ExecutableCommandRegistry;
import org.eclipse.xtext.ide.server.concurrent.RequestManager;
import org.eclipse.xtext.ide.server.contentassist.ContentAssistService;
import org.eclipse.xtext.ide.server.findReferences.WorkspaceResourceAccess;
import org.eclipse.xtext.ide.server.folding.FoldingRangeService;
import org.eclipse.xtext.ide.server.formatting.FormattingService;
import org.eclipse.xtext.ide.server.hover.IHoverService;
import org.eclipse.xtext.ide.server.occurrences.IDocumentHighlightService;
@ -135,6 +139,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.inject.Inject;
@ -268,8 +273,14 @@ public class LanguageServerImpl implements LanguageServer, WorkspaceService, Tex
textDocument = clientCapabilities.getTextDocument();
}
RenameCapabilities rename = null;
FoldingRangeCapabilities folding = null;
if (textDocument != null) {
rename = textDocument.getRename();
folding = textDocument.getFoldingRange();
}
if (folding != null) {
serverCapabilities.setFoldingRangeProvider(allLanguages.stream()
.anyMatch(serviceProvider -> serviceProvider.get(FoldingRangeService.class) != null));
}
Boolean prepareSupport = null;
if (rename != null) {
@ -1007,6 +1018,26 @@ public class LanguageServerImpl implements LanguageServer, WorkspaceService, Tex
options.setCancelIndicator(cancelIndicator);
return renameService.prepareRename(options);
}
/**
* @since 2.26
*/
public CompletableFuture<List<FoldingRange>> foldingRange(FoldingRangeRequestParams params) {
return requestManager.runRead(cancelIndicator -> foldingRange(params, cancelIndicator));
}
/**
* @since 2.26
*/
protected List<FoldingRange> foldingRange(FoldingRangeRequestParams params, CancelIndicator cancelIndicator) {
URI uri = getURI(params.getTextDocument());
FoldingRangeService foldingRangeService = getService(uri, FoldingRangeService.class);
if (foldingRangeService == null) {
return Lists.newArrayList();
}
return workspaceManager.doRead(uri, (document, resource) -> foldingRangeService
.createFoldingRanges(document, resource, cancelIndicator));
}
@Override
public void notify(String method, Object parameter) {

View file

@ -0,0 +1,58 @@
/*******************************************************************************
* Copyright (c) 2021 TypeFox GmbH (http://www.typefox.io) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.xtext.ide.server.folding;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.lsp4j.FoldingRange;
import org.eclipse.lsp4j.Position;
import org.eclipse.xtext.ide.editor.folding.IFoldingRangeProvider;
import org.eclipse.xtext.ide.server.Document;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.util.CancelIndicator;
import com.google.inject.Inject;
import com.google.inject.Singleton;
/**
* @author Mark Sujew - Initial contribution and API
*
* @since 2.26
*/
@Singleton
public class FoldingRangeService {
@Inject
private IFoldingRangeProvider foldingRangeProvider;
public List<FoldingRange> createFoldingRanges(Document document, XtextResource resource,
CancelIndicator cancelIndicator) {
return foldingRangeProvider.getFoldingRanges(resource, cancelIndicator).stream()
.map(range -> toFoldingRange(document, range)).filter(range -> isValidRange(range))
.collect(Collectors.toList());
}
protected FoldingRange toFoldingRange(Document document, org.eclipse.xtext.ide.editor.folding.FoldingRange range) {
int offset = range.getOffset();
int length = range.getLength();
int endOffset = offset + length;
Position start = document.getPosition(offset);
Position end = document.getPosition(endOffset);
FoldingRange result = new FoldingRange(start.getLine(), end.getLine());
if (range.getKind() != null) {
result.setKind(range.getKind().toString());
}
return result;
}
protected boolean isValidRange(FoldingRange range) {
return range.getStartLine() < range.getEndLine();
}
}

View file

@ -42,6 +42,8 @@ import org.eclipse.lsp4j.DocumentSymbol
import org.eclipse.lsp4j.DocumentSymbolParams
import org.eclipse.lsp4j.FileChangeType
import org.eclipse.lsp4j.FileEvent
import org.eclipse.lsp4j.FoldingRange
import org.eclipse.lsp4j.FoldingRangeRequestParams
import org.eclipse.lsp4j.Hover
import org.eclipse.lsp4j.HoverParams
import org.eclipse.lsp4j.InitializeParams
@ -392,6 +394,10 @@ abstract class AbstractLanguageServerTest implements Endpoint {
protected dispatch def String toExpectation(CodeLens it) {
return command.title + " " +range.toExpectation
}
protected dispatch def String toExpectation(FoldingRange it) {
return '''[«String.valueOf(kind)» «startLine»..«endLine»]'''
}
@Accessors static class TestCodeLensConfiguration extends TextDocumentPositionConfiguration {
String expectedCodeLensItems = ''
@ -711,6 +717,23 @@ abstract class AbstractLanguageServerTest implements Endpoint {
val result = new Document(1, fileInfo.contents).applyChanges(<TextEdit>newArrayList(changes.get()).reverse)
assertEqualsStricter(configuration.expectedText, result.contents)
}
/**
* @since 2.26
*/
protected def testFolding((FoldingConfiguration)=>void configurator) {
val extension configuration = new FoldingConfiguration
configuration.filePath = 'MyModel.' + fileExtension
configurator.apply(configuration)
val fileInfo = initializeContext(configuration)
val foldings = languageServer.foldingRange(new FoldingRangeRequestParams => [
textDocument = new TextDocumentIdentifier(fileInfo.uri)
]).get()
assertEqualsStricter(configuration.expectedFoldings, foldings.toExpectation);
}
override notify(String method, Object parameter) {
this.notifications.add(method -> parameter)

View file

@ -0,0 +1,15 @@
package org.eclipse.xtext.testing;
public class FoldingConfiguration extends TextDocumentConfiguration {
private String expectedFoldings;
public String getExpectedFoldings() {
return expectedFoldings;
}
public void setExpectedFoldings(String expectedFoldings) {
this.expectedFoldings = expectedFoldings;
}
}

View file

@ -53,6 +53,8 @@ import org.eclipse.lsp4j.DocumentSymbolCapabilities;
import org.eclipse.lsp4j.DocumentSymbolParams;
import org.eclipse.lsp4j.FileChangeType;
import org.eclipse.lsp4j.FileEvent;
import org.eclipse.lsp4j.FoldingRange;
import org.eclipse.lsp4j.FoldingRangeRequestParams;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.HoverParams;
import org.eclipse.lsp4j.InitializeParams;
@ -803,6 +805,21 @@ public abstract class AbstractLanguageServerTest implements Endpoint {
return (_plus + _expectation);
}
protected String _toExpectation(final FoldingRange it) {
StringConcatenation _builder = new StringConcatenation();
_builder.append("[");
String _valueOf = String.valueOf(it.getKind());
_builder.append(_valueOf);
_builder.append(" ");
int _startLine = it.getStartLine();
_builder.append(_startLine);
_builder.append("..");
int _endLine = it.getEndLine();
_builder.append(_endLine);
_builder.append("]");
return _builder.toString();
}
protected void testCodeLens(final Procedure1<? super AbstractLanguageServerTest.TestCodeLensConfiguration> configurator) {
try {
@Extension
@ -1402,6 +1419,30 @@ public abstract class AbstractLanguageServerTest implements Endpoint {
}
}
/**
* @since 2.26
*/
protected void testFolding(final Procedure1<? super FoldingConfiguration> configurator) {
try {
@Extension
final FoldingConfiguration configuration = new FoldingConfiguration();
configuration.setFilePath(("MyModel." + this.fileExtension));
configurator.apply(configuration);
final FileInfo fileInfo = this.initializeContext(configuration);
FoldingRangeRequestParams _foldingRangeRequestParams = new FoldingRangeRequestParams();
final Procedure1<FoldingRangeRequestParams> _function = (FoldingRangeRequestParams it) -> {
String _uri = fileInfo.getUri();
TextDocumentIdentifier _textDocumentIdentifier = new TextDocumentIdentifier(_uri);
it.setTextDocument(_textDocumentIdentifier);
};
FoldingRangeRequestParams _doubleArrow = ObjectExtensions.<FoldingRangeRequestParams>operator_doubleArrow(_foldingRangeRequestParams, _function);
final List<FoldingRange> foldings = this.languageServer.foldingRange(_doubleArrow).get();
this.assertEqualsStricter(configuration.getExpectedFoldings(), this.toExpectation(foldings));
} catch (Throwable _e) {
throw Exceptions.sneakyThrow(_e);
}
}
@Override
public void notify(final String method, final Object parameter) {
Pair<String, Object> _mappedTo = Pair.<String, Object>of(method, parameter);
@ -1459,6 +1500,8 @@ public abstract class AbstractLanguageServerTest implements Endpoint {
return _toExpectation((DocumentHighlight)it);
} else if (it instanceof DocumentSymbol) {
return _toExpectation((DocumentSymbol)it);
} else if (it instanceof FoldingRange) {
return _toExpectation((FoldingRange)it);
} else if (it instanceof Hover) {
return _toExpectation((Hover)it);
} else if (it instanceof Location) {