mirror of
https://github.com/sigmasternchen/xtext-core
synced 2025-03-15 08:18:55 +00:00
[#1672] Added LSP support for foldings
Signed-off-by: Mark Sujew <mark.sujew@typefox.io>
This commit is contained in:
parent
bc53c4efdb
commit
ab57ca53b4
14 changed files with 769 additions and 0 deletions
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue