RegionDiffFormatter may drop essential changes at the boundary of a

change region #1559

Signed-off-by: Stephan Herrmann <stephan.herrmann@berlin.de>
This commit is contained in:
Stephan Herrmann 2020-08-26 14:16:54 +02:00 committed by Christian Dietrich
parent 0afc98c151
commit 45142a25fc
5 changed files with 125 additions and 3 deletions

View file

@ -504,4 +504,32 @@ class ChangeSerializerTest {
0 3 "#23" -> "#23 subs A A2"
'''
}
@Test
def void testAddElementsWithEmptyLine() {
val uri = "inmemory:/file-add.pstl"
val fs = new InMemoryURIHandler()
fs += uri -> '''
#1 {
N1;
}
'''
val rs = fs.createResourceSet
val model = rs.contents(uri, Node)
val serializer = newChangeSerializer()
serializer.addModification(model.eResource) [
model.children += createNode => [name = "N2"]
]
serializer.endRecordChangesToTextDocuments === '''
--------------- inmemory:/file-add.pstl (syntax: <offset|text>) ----------------
#1 {
N1;
<10:0|
N2; >}
--------------------------------------------------------------------------------
10 0 "" -> "\nN2; "
'''
}
}

View file

@ -16,6 +16,9 @@ class PartialSerializationTestLanguageFormatter extends AbstractFormatter2 {
// @Inject extension PartialSerializationTestLanguageGrammarAccess
dispatch def format(Node obj, extension IFormattableDocument document) {
if (obj.name == "N2") {
obj.prepend[newLines=2]
}
for (r : obj.regionFor.keywords(";")) {
r.prepend[noSpace].append[oneSpace]
}

View file

@ -837,4 +837,51 @@ public class ChangeSerializerTest {
_builder_1.newLine();
this._changeSerializerTestHelper.operator_tripleEquals(_endRecordChangesToTextDocuments, _builder_1);
}
@Test
public void testAddElementsWithEmptyLine() {
final String uri = "inmemory:/file-add.pstl";
final InMemoryURIHandler fs = new InMemoryURIHandler();
StringConcatenation _builder = new StringConcatenation();
_builder.append("#1 {");
_builder.newLine();
_builder.append("\t");
_builder.append("N1;");
_builder.newLine();
_builder.append("}");
_builder.newLine();
Pair<String, String> _mappedTo = Pair.<String, String>of(uri, _builder.toString());
this._changeSerializerTestHelper.operator_add(fs, _mappedTo);
final ResourceSet rs = this._changeSerializerTestHelper.createResourceSet(fs);
final Node model = this._changeSerializerTestHelper.<Node>contents(rs, uri, Node.class);
final IChangeSerializer serializer = this._changeSerializerTestHelper.newChangeSerializer();
final IChangeSerializer.IModification<Resource> _function = (Resource it) -> {
EList<Node> _children = model.getChildren();
Node _createNode = this.fac.createNode();
final Procedure1<Node> _function_1 = (Node it_1) -> {
it_1.setName("N2");
};
Node _doubleArrow = ObjectExtensions.<Node>operator_doubleArrow(_createNode, _function_1);
_children.add(_doubleArrow);
};
serializer.<Resource>addModification(model.eResource(), _function);
Collection<IEmfResourceChange> _endRecordChangesToTextDocuments = this._changeSerializerTestHelper.endRecordChangesToTextDocuments(serializer);
StringConcatenation _builder_1 = new StringConcatenation();
_builder_1.append("--------------- inmemory:/file-add.pstl (syntax: <offset|text>) ----------------");
_builder_1.newLine();
_builder_1.append("#1 {");
_builder_1.newLine();
_builder_1.append("\t");
_builder_1.append("N1;");
_builder_1.newLine();
_builder_1.append("<10:0|");
_builder_1.newLine();
_builder_1.append("N2; >}");
_builder_1.newLine();
_builder_1.append("--------------------------------------------------------------------------------");
_builder_1.newLine();
_builder_1.append("10 0 \"\" -> \"\\nN2; \"");
_builder_1.newLine();
this._changeSerializerTestHelper.operator_tripleEquals(_endRecordChangesToTextDocuments, _builder_1);
}
}

View file

@ -8,6 +8,7 @@
*/
package org.eclipse.xtext.ide.tests.testlanguage.formatting2;
import com.google.common.base.Objects;
import java.util.Arrays;
import java.util.List;
import org.eclipse.emf.common.util.EList;
@ -24,15 +25,23 @@ import org.eclipse.xtext.xbase.lib.Procedures.Procedure1;
@SuppressWarnings("all")
public class PartialSerializationTestLanguageFormatter extends AbstractFormatter2 {
protected void _format(final Node obj, @Extension final IFormattableDocument document) {
String _name = obj.getName();
boolean _equals = Objects.equal(_name, "N2");
if (_equals) {
final Procedure1<IHiddenRegionFormatter> _function = (IHiddenRegionFormatter it) -> {
it.setNewLines(2);
};
document.<Node>prepend(obj, _function);
}
List<ISemanticRegion> _keywords = this.textRegionExtensions.regionFor(obj).keywords(";");
for (final ISemanticRegion r : _keywords) {
final Procedure1<IHiddenRegionFormatter> _function = (IHiddenRegionFormatter it) -> {
final Procedure1<IHiddenRegionFormatter> _function_1 = (IHiddenRegionFormatter it) -> {
it.noSpace();
};
final Procedure1<IHiddenRegionFormatter> _function_1 = (IHiddenRegionFormatter it) -> {
final Procedure1<IHiddenRegionFormatter> _function_2 = (IHiddenRegionFormatter it) -> {
it.oneSpace();
};
document.append(document.prepend(r, _function), _function_1);
document.append(document.prepend(r, _function_1), _function_2);
}
EList<Node> _children = obj.getChildren();
for (final Node child : _children) {

View file

@ -16,6 +16,7 @@ import org.eclipse.xtext.formatting2.FormatterRequest;
import org.eclipse.xtext.formatting2.IFormattableDocument;
import org.eclipse.xtext.formatting2.IFormatter2;
import org.eclipse.xtext.formatting2.regionaccess.ITextSegmentDiff;
import org.eclipse.xtext.formatting2.regionaccess.internal.TextReplacement;
import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccess;
import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccessDiff;
import org.eclipse.xtext.formatting2.regionaccess.ITextRegionRewriter;
@ -105,6 +106,11 @@ public class RegionDiffFormatter {
for (ITextReplacement re : rep) {
if (modified.contains(re)) {
local.add(re);
} else if (hasOverlappingWhitespacePrefix(r, re)) {
// change overlaps with a region boundary, trim it to the part within
TextReplacement newReplacement = trimReplacement(re, re.getText());
if (newReplacement != null)
local.add(newReplacement);
}
}
String text;
@ -119,4 +125,33 @@ public class RegionDiffFormatter {
return result;
}
private boolean hasOverlappingWhitespacePrefix(ITextSegmentDiff diff, ITextReplacement re) {
String text = re.getText();
if (!text.isEmpty()) {
ITextSegment modifiedFirstRegion = diff.getModifiedFirstRegion();
if (modifiedFirstRegion.contains(re.getOffset())
&& modifiedFirstRegion.getText().startsWith(text)) {
for (int i=0; i<text.length(); i++) {
if (!Character.isWhitespace(text.charAt(i)))
return false;
}
return true;
}
}
return false;
}
private TextReplacement trimReplacement(ITextReplacement re, String prefixText) {
TextReplacement newReplacement = null;
int prefixLen = prefixText.length();
String newText = re.getReplacementText();
if (newText.length() > prefixLen) {
newText = newText.substring(prefixLen);
int newOffset = re.getOffset()+prefixLen;
int newLength = re.getLength()-prefixLen;
newReplacement = new TextReplacement(re.getTextRegionAccess(), newOffset, newLength, newText);
}
return newReplacement;
}
}