mirror of
https://github.com/sigmasternchen/xtext-core
synced 2025-03-16 08:48:55 +00:00
Merge pull request #159 from eclipse/me/default_handler_for_undefined_ws
[formatter] format leftover undefined hidden regions
This commit is contained in:
commit
806eb6df59
6 changed files with 183 additions and 80 deletions
|
@ -10,7 +10,6 @@ package org.eclipse.xtext.junit4.formatter;
|
|||
import static com.google.common.base.Preconditions.*;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -20,12 +19,11 @@ import org.eclipse.xtext.formatting2.IFormatter2;
|
|||
import org.eclipse.xtext.formatting2.debug.TextRegionAccessToString;
|
||||
import org.eclipse.xtext.formatting2.debug.TextRegionsToString;
|
||||
import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion;
|
||||
import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegionPart;
|
||||
import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccess;
|
||||
import org.eclipse.xtext.formatting2.regionaccess.ITextReplacement;
|
||||
import org.eclipse.xtext.formatting2.regionaccess.ITextSegment;
|
||||
import org.eclipse.xtext.formatting2.regionaccess.IWhitespace;
|
||||
import org.eclipse.xtext.formatting2.regionaccess.TextRegionAccessBuilder;
|
||||
import org.eclipse.xtext.formatting2.regionaccess.internal.TextRegions;
|
||||
import org.eclipse.xtext.junit4.util.ParseHelper;
|
||||
import org.eclipse.xtext.nodemodel.INode;
|
||||
import org.eclipse.xtext.nodemodel.SyntaxErrorMessage;
|
||||
|
@ -66,40 +64,13 @@ public class FormatterTester {
|
|||
private Serializer serializer;
|
||||
|
||||
protected void assertAllWhitespaceIsFormatted(ITextRegionAccess access, List<ITextReplacement> replacements) {
|
||||
List<ITextReplacement> actual = Lists.newArrayList(replacements);
|
||||
Collections.sort(actual);
|
||||
List<ITextSegment> expected = Lists.newArrayList();
|
||||
IHiddenRegion current = access.regionForRootEObject().getPreviousHiddenRegion();
|
||||
while (current != null) {
|
||||
if (current.getLength() == 0) {
|
||||
expected.add(current);
|
||||
} else {
|
||||
for (IHiddenRegionPart part : current.getParts())
|
||||
if (part instanceof IWhitespace)
|
||||
expected.add(part);
|
||||
}
|
||||
expected.addAll(current.getMergedSpaces());
|
||||
current = current.getNextHiddenRegion();
|
||||
}
|
||||
int e = 0, a = 0;
|
||||
List<ITextSegment> missing = Lists.newArrayList();
|
||||
while (e < expected.size() && a < actual.size()) {
|
||||
ITextSegment hidden = expected.get(e);
|
||||
ITextReplacement replacement = actual.get(a);
|
||||
int compareTo = hidden.compareTo(replacement);
|
||||
if (compareTo == 0) {
|
||||
e++;
|
||||
a++;
|
||||
} else if (compareTo < 1) {
|
||||
missing.add(hidden);
|
||||
e++;
|
||||
} else {
|
||||
a++;
|
||||
}
|
||||
}
|
||||
while (e < expected.size()) {
|
||||
missing.add(expected.get(e));
|
||||
e++;
|
||||
}
|
||||
List<ITextSegment> missing = TextRegions.difference(expected, replacements);
|
||||
if (!missing.isEmpty()) {
|
||||
TextRegionsToString toString = new TextRegionsToString().setTextRegionAccess(access);
|
||||
for (ITextSegment region : missing)
|
||||
|
|
|
@ -30,10 +30,12 @@ import org.eclipse.xtext.formatting2.internal.TextReplacerMerger;
|
|||
import org.eclipse.xtext.formatting2.internal.WhitespaceReplacer;
|
||||
import org.eclipse.xtext.formatting2.regionaccess.IComment;
|
||||
import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion;
|
||||
import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegionPart;
|
||||
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion;
|
||||
import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccess;
|
||||
import org.eclipse.xtext.formatting2.regionaccess.ITextReplacement;
|
||||
import org.eclipse.xtext.formatting2.regionaccess.ITextSegment;
|
||||
import org.eclipse.xtext.formatting2.regionaccess.internal.TextRegions;
|
||||
import org.eclipse.xtext.formatting2.regionaccess.internal.TextReplacement;
|
||||
import org.eclipse.xtext.grammaranalysis.impl.GrammarElementTitleSwitch;
|
||||
import org.eclipse.xtext.preferences.ITypedPreferenceValues;
|
||||
|
@ -41,6 +43,8 @@ import org.eclipse.xtext.preferences.TypedPreferenceKey;
|
|||
import org.eclipse.xtext.resource.XtextResource;
|
||||
import org.eclipse.xtext.xbase.lib.Extension;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This is an abstract base class for language-specific formatters.
|
||||
|
@ -204,12 +208,12 @@ public abstract class AbstractFormatter2 implements IFormatter2 {
|
|||
throw new IllegalStateException("No " + ITextReplacer.class.getSimpleName() + " configured for " + elementName);
|
||||
}
|
||||
|
||||
public IFormattableSubDocument createFormattableSubDocument(ITextSegment region, IFormattableDocument parent) {
|
||||
return new SubDocument(region, parent);
|
||||
public IFormattableDocument createFormattableRootDocument() {
|
||||
return new RootDocument(this);
|
||||
}
|
||||
|
||||
public IHiddenRegionFormatting createHiddenRegionFormatting() {
|
||||
return new HiddenRegionFormatting(this);
|
||||
public IFormattableSubDocument createFormattableSubDocument(ITextSegment region, IFormattableDocument parent) {
|
||||
return new SubDocument(region, parent);
|
||||
}
|
||||
|
||||
public IHiddenRegionFormatter createHiddenRegionFormatter(IHiddenRegionFormatting formatting) {
|
||||
|
@ -220,6 +224,10 @@ public abstract class AbstractFormatter2 implements IFormatter2 {
|
|||
return new DoubleHiddenRegionFormatter(f1, f2);
|
||||
}
|
||||
|
||||
public IHiddenRegionFormatting createHiddenRegionFormatting() {
|
||||
return new HiddenRegionFormatting(this);
|
||||
}
|
||||
|
||||
public IMerger<IHiddenRegionFormatting> createHiddenRegionFormattingMerger() {
|
||||
return new HiddenRegionFormattingMerger(this);
|
||||
}
|
||||
|
@ -240,10 +248,6 @@ public abstract class AbstractFormatter2 implements IFormatter2 {
|
|||
return new WhitespaceReplacer(hiddens, formatting);
|
||||
}
|
||||
|
||||
public IFormattableDocument createFormattableRootDocument() {
|
||||
return new RootDocument(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final List<ITextReplacement> format(FormatterRequest request) {
|
||||
try {
|
||||
|
@ -251,8 +255,9 @@ public abstract class AbstractFormatter2 implements IFormatter2 {
|
|||
IFormattableDocument document = createFormattableRootDocument();
|
||||
XtextResource xtextResource = request.getTextRegionAccess().getResource();
|
||||
format(xtextResource, document);
|
||||
List<ITextReplacement> replacements = document.renderToTextReplacements();
|
||||
return replacements;
|
||||
List<ITextReplacement> rendered = document.renderToTextReplacements();
|
||||
List<ITextReplacement> postprocessed = postProcess(document, rendered);
|
||||
return postprocessed;
|
||||
} finally {
|
||||
reset();
|
||||
}
|
||||
|
@ -300,6 +305,34 @@ public abstract class AbstractFormatter2 implements IFormatter2 {
|
|||
this.regionAccess = request.getTextRegionAccess();
|
||||
}
|
||||
|
||||
protected List<ITextReplacement> postProcess(IFormattableDocument document, List<ITextReplacement> replacements) {
|
||||
List<ITextSegment> expected = Lists.newArrayList();
|
||||
IHiddenRegion current = regionAccess.regionForRootEObject().getPreviousHiddenRegion();
|
||||
while (current != null) {
|
||||
if (current.isUndefined())
|
||||
expected.addAll(current.getMergedSpaces());
|
||||
current = current.getNextHiddenRegion();
|
||||
}
|
||||
if (expected.isEmpty())
|
||||
return replacements;
|
||||
List<ITextSegment> missing = TextRegions.difference(expected, replacements);
|
||||
if (missing.isEmpty())
|
||||
return replacements;
|
||||
List<ITextReplacement> result = Lists.newArrayList(replacements);
|
||||
for (ITextSegment seg : missing) {
|
||||
IHiddenRegion h = null;
|
||||
if (seg instanceof IHiddenRegion)
|
||||
h = (IHiddenRegion) seg;
|
||||
if (seg instanceof IHiddenRegionPart)
|
||||
h = ((IHiddenRegionPart) seg).getHiddenRegion();
|
||||
if (h != null && (h.getNextSemanticRegion() == null || h.getPreviousSemanticRegion() == null))
|
||||
result.add(seg.replaceWith(""));
|
||||
else
|
||||
result.add(seg.replaceWith(" "));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrite this method to clean up member fields after {@link #format(Object, IFormattableDocument)} has been
|
||||
* called.
|
||||
|
|
|
@ -19,7 +19,6 @@ import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion;
|
|||
import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegionPart;
|
||||
import org.eclipse.xtext.formatting2.regionaccess.ITextSegment;
|
||||
import org.eclipse.xtext.formatting2.regionaccess.IWhitespace;
|
||||
import org.eclipse.xtext.formatting2.regionaccess.internal.TextSegment;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
|
@ -91,12 +90,12 @@ public class HiddenRegionReplacer implements ITextReplacer {
|
|||
public ITextReplacerContext createReplacements(ITextReplacerContext context) {
|
||||
AbstractFormatter2 formatter = context.getFormatter();
|
||||
List<IHiddenRegionPart> hiddens = region.getParts();
|
||||
if (region.isUndefined() || hiddens.isEmpty()) {
|
||||
if (hiddens.isEmpty()) {
|
||||
return formatter.createWhitespaceReplacer(region, formatting).createReplacements(context);
|
||||
} else if ((hiddens.size() == 1 && hiddens.get(0) instanceof IWhitespace)) {
|
||||
return formatter.createWhitespaceReplacer(hiddens.get(0), formatting).createReplacements(context);
|
||||
} else {
|
||||
List<ITextReplacer> replacers = createReplacers(formatter, hiddens);
|
||||
List<ITextReplacer> replacers = createReplacers(formatter);
|
||||
applyHiddenRegionFormatting(replacers);
|
||||
ITextReplacerContext current = context;
|
||||
current.setNextReplacerIsChild();
|
||||
|
@ -106,38 +105,16 @@ public class HiddenRegionReplacer implements ITextReplacer {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a list that starts with whitespace, ends with whitespace and contains a sequence of strictly alternating
|
||||
* whitespace- and comment-regions.
|
||||
*/
|
||||
protected List<ITextReplacer> createReplacers(AbstractFormatter2 formatter, List<IHiddenRegionPart> parts) {
|
||||
ITextSegment last = null;
|
||||
List<ITextReplacer> result = Lists.newArrayList();
|
||||
for (IHiddenRegionPart part : parts) {
|
||||
if (part instanceof IWhitespace) {
|
||||
if (last == null || last instanceof IComment) {
|
||||
result.add(formatter.createWhitespaceReplacer(part, formatter.createHiddenRegionFormatting()));
|
||||
} else {
|
||||
int mergedLength = last.getLength() + part.getLength();
|
||||
TextSegment merged = new TextSegment(part.getTextRegionAccess(), last.getOffset(), mergedLength);
|
||||
IHiddenRegionFormatting formatting2 = formatter.createHiddenRegionFormatting();
|
||||
result.set(result.size() - 1, formatter.createWhitespaceReplacer(merged, formatting2));
|
||||
}
|
||||
}
|
||||
if (part instanceof IComment) {
|
||||
if (last == null || last instanceof IComment) {
|
||||
TextSegment region = new TextSegment(part.getTextRegionAccess(), part.getOffset(), 0);
|
||||
result.add(formatter.createWhitespaceReplacer(region, formatter.createHiddenRegionFormatting()));
|
||||
}
|
||||
result.add(formatter.createCommentReplacer((IComment) part));
|
||||
}
|
||||
last = part;
|
||||
protected List<ITextReplacer> createReplacers(AbstractFormatter2 formatter) {
|
||||
List<ITextSegment> regions = region.getAlternatingMergedSpaceAndComments();
|
||||
List<ITextReplacer> replacers = Lists.newArrayListWithCapacity(regions.size());
|
||||
for (ITextSegment region : regions) {
|
||||
if (region instanceof IComment)
|
||||
replacers.add(formatter.createCommentReplacer((IComment) region));
|
||||
else
|
||||
replacers.add(formatter.createWhitespaceReplacer(region, formatter.createHiddenRegionFormatting()));
|
||||
}
|
||||
if (last instanceof IComment) {
|
||||
TextSegment region = new TextSegment(last.getTextRegionAccess(), last.getOffset() + last.getLength(), 0);
|
||||
result.add(formatter.createWhitespaceReplacer(region, formatter.createHiddenRegionFormatting()));
|
||||
}
|
||||
return result;
|
||||
return replacers;
|
||||
}
|
||||
|
||||
protected WhitespaceReplacer findWhitespaceThatSeparatesSemanticRegions(List<ITextReplacer> replacers) {
|
||||
|
|
|
@ -10,8 +10,9 @@ package org.eclipse.xtext.formatting2.regionaccess;
|
|||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>Represents and groups all {@link IWhitespace} and {@link IComment comments} between two {@link ISemanticRegion semantic regions}. May be
|
||||
* empty.
|
||||
* <p>
|
||||
* Represents and groups all {@link IWhitespace} and {@link IComment comments} between two {@link ISemanticRegion
|
||||
* semantic regions}. May be empty.
|
||||
*
|
||||
* @author Moritz Eysholdt - Initial contribution and API
|
||||
*
|
||||
|
@ -30,7 +31,8 @@ public interface IHiddenRegion extends ISequentialRegion {
|
|||
boolean containsComment();
|
||||
|
||||
/**
|
||||
* @return all {@link IWhitespace white spaces} and {@link IComment comments} that belong to this {@link IHiddenRegion}.
|
||||
* @return all {@link IWhitespace white spaces} and {@link IComment comments} that belong to this
|
||||
* {@link IHiddenRegion}.
|
||||
*/
|
||||
List<IHiddenRegionPart> getParts();
|
||||
|
||||
|
@ -40,4 +42,12 @@ public interface IHiddenRegion extends ISequentialRegion {
|
|||
* semantic model has been constructed or modified programmatically.
|
||||
*/
|
||||
boolean isUndefined();
|
||||
|
||||
/**
|
||||
* @return returns a list that starts with whitespace, ends with whitespace and contains a sequence of strictly
|
||||
* alternating whitespace- and comment-regions.
|
||||
*/
|
||||
List<ITextSegment> getAlternatingMergedSpaceAndComments();
|
||||
|
||||
List<ITextSegment> getMergedSpaces();
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
*******************************************************************************/
|
||||
package org.eclipse.xtext.formatting2.regionaccess.internal;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.xtext.formatting2.debug.TextRegionAccessToString;
|
||||
|
@ -15,6 +16,8 @@ import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion;
|
|||
import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegionPart;
|
||||
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion;
|
||||
import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccess;
|
||||
import org.eclipse.xtext.formatting2.regionaccess.ITextSegment;
|
||||
import org.eclipse.xtext.formatting2.regionaccess.IWhitespace;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
|
@ -23,10 +26,10 @@ import com.google.common.collect.Lists;
|
|||
* @author Moritz Eysholdt - Initial contribution and API
|
||||
*/
|
||||
public abstract class AbstractHiddenRegion extends AbstractTextSegment implements IHiddenRegion {
|
||||
private final ITextRegionAccess access;
|
||||
private final List<IHiddenRegionPart> hiddens = Lists.newArrayList();
|
||||
private ISemanticRegion next;
|
||||
private ISemanticRegion previous;
|
||||
private final ITextRegionAccess access;
|
||||
|
||||
protected AbstractHiddenRegion(ITextRegionAccess access) {
|
||||
super();
|
||||
|
@ -37,6 +40,37 @@ public abstract class AbstractHiddenRegion extends AbstractTextSegment implement
|
|||
this.hiddens.add(part);
|
||||
}
|
||||
|
||||
protected List<ITextSegment> collectAlternatingSpaceAndComments(boolean includeComments) {
|
||||
List<IHiddenRegionPart> parts = getParts();
|
||||
if (parts.isEmpty()) {
|
||||
return Collections.<ITextSegment> singletonList(this);
|
||||
} else {
|
||||
ITextSegment last = null;
|
||||
List<ITextSegment> result = Lists.newArrayList();
|
||||
for (IHiddenRegionPart part : parts) {
|
||||
if (part instanceof IWhitespace) {
|
||||
if (last == null || last instanceof IComment) {
|
||||
result.add(part);
|
||||
} else {
|
||||
int mergedLength = last.getLength() + part.getLength();
|
||||
result.add(new TextSegment(part.getTextRegionAccess(), last.getOffset(), mergedLength));
|
||||
}
|
||||
} else if (part instanceof IComment) {
|
||||
if (last == null || last instanceof IComment) {
|
||||
result.add(new TextSegment(part.getTextRegionAccess(), part.getOffset(), 0));
|
||||
}
|
||||
if (includeComments)
|
||||
result.add(part);
|
||||
}
|
||||
last = part;
|
||||
}
|
||||
if (last instanceof IComment) {
|
||||
result.add(new TextSegment(last.getTextRegionAccess(), last.getOffset() + last.getLength(), 0));
|
||||
}
|
||||
return ImmutableList.copyOf(result);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsComment() {
|
||||
for (IHiddenRegionPart hidden : hiddens)
|
||||
|
@ -45,6 +79,11 @@ public abstract class AbstractHiddenRegion extends AbstractTextSegment implement
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ITextSegment> getAlternatingMergedSpaceAndComments() {
|
||||
return collectAlternatingSpaceAndComments(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
if (hiddens.isEmpty())
|
||||
|
@ -90,6 +129,11 @@ public abstract class AbstractHiddenRegion extends AbstractTextSegment implement
|
|||
return previous;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ITextSegment> getMergedSpaces() {
|
||||
return collectAlternatingSpaceAndComments(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextRegionAccess getTextRegionAccess() {
|
||||
return access;
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.formatting2.regionaccess.internal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.xtext.util.ITextRegion;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
/**
|
||||
* @author Moritz Eysholdt - Initial contribution and API
|
||||
* @since 2.9
|
||||
*/
|
||||
public class TextRegions {
|
||||
|
||||
private static enum Comp implements Comparator<ITextRegion> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public int compare(ITextRegion o1, ITextRegion o2) {
|
||||
int cmp1 = o1.getOffset() - o2.getOffset();
|
||||
if (cmp1 != 0)
|
||||
return cmp1;
|
||||
int cmp2 = o1.getLength() - o2.getLength();
|
||||
if (cmp2 != 0)
|
||||
return cmp2;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T extends ITextRegion> List<T> difference(Iterable<T> r1, Iterable<? extends ITextRegion> r2) {
|
||||
ArrayList<T> regions1 = Lists.newArrayList(r1);
|
||||
ArrayList<ITextRegion> regions2 = Lists.newArrayList(r2);
|
||||
Collections.sort(regions1, Comp.INSTANCE);
|
||||
Collections.sort(regions2, Comp.INSTANCE);
|
||||
List<T> missing = Lists.newArrayList();
|
||||
int i1 = 0, i2 = 0;
|
||||
while (i1 < regions1.size() && i2 < regions2.size()) {
|
||||
T t1 = regions1.get(i1);
|
||||
ITextRegion t2 = regions2.get(i2);
|
||||
int compareTo = Comp.INSTANCE.compare(t1, t2);
|
||||
if (compareTo == 0) {
|
||||
i1++;
|
||||
i2++;
|
||||
} else if (compareTo < 1) {
|
||||
missing.add(t1);
|
||||
i1++;
|
||||
} else {
|
||||
i2++;
|
||||
}
|
||||
}
|
||||
while (i1 < regions1.size()) {
|
||||
missing.add(regions1.get(i1));
|
||||
i1++;
|
||||
}
|
||||
return ImmutableList.copyOf(missing);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue