diff --git a/org.eclipse.gef.dot.tests/src/org/eclipse/gef/dot/tests/AllUiTests.java b/org.eclipse.gef.dot.tests/src/org/eclipse/gef/dot/tests/AllUiTests.java index a4b61f0955..3db39a8637 100644 --- a/org.eclipse.gef.dot.tests/src/org/eclipse/gef/dot/tests/AllUiTests.java +++ b/org.eclipse.gef.dot.tests/src/org/eclipse/gef/dot/tests/AllUiTests.java @@ -53,8 +53,9 @@ DotHighlightingTest.class, DotHoverTest.class, DotHtmlLabelContentAssistLexerTest.class, DotHtmlLabelContentAssistTest.class, - DotHtmlLabelHighlightingLexerTest.class, DotHtmlLabelQuickfixTest.class, - DotHtmlLabelRenameRefactoringTest.class, + DotHtmlLabelHighlightingLexerTest.class, + DotHtmlLabelIndentationHighlightingTest.class, + DotHtmlLabelQuickfixTest.class, DotHtmlLabelRenameRefactoringTest.class, DotHtmlLabelTokenTypeToPartitionMapperTest.class, DotHyperlinkingTest.class, DotLabelProviderTest.class, DotMarkingOccurrencesTest.class, DotMultiQuickfixTest.class, diff --git a/org.eclipse.gef.dot.tests/src/org/eclipse/gef/dot/tests/DotHtmlLabelIndentationHighlightingTest.xtend b/org.eclipse.gef.dot.tests/src/org/eclipse/gef/dot/tests/DotHtmlLabelIndentationHighlightingTest.xtend new file mode 100644 index 0000000000..c5a6a9cce9 --- /dev/null +++ b/org.eclipse.gef.dot.tests/src/org/eclipse/gef/dot/tests/DotHtmlLabelIndentationHighlightingTest.xtend @@ -0,0 +1,206 @@ +/******************************************************************************* + * Copyright (c) 2020 itemis AG 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 + * + * Contributors: + * Tamas Miklossy (itemis AG) - initial API and implementation + *******************************************************************************/ +package org.eclipse.gef.dot.tests + +import java.util.List +import org.eclipse.core.resources.IFile +import org.eclipse.gef.dot.tests.ui.DotUiInjectorProvider +import org.eclipse.jface.text.IRegion +import org.eclipse.jface.text.Region +import org.eclipse.swt.SWT +import org.eclipse.swt.custom.StyledText +import org.eclipse.swt.graphics.Color +import org.eclipse.swt.widgets.Display +import org.eclipse.xtext.testing.InjectWith +import org.eclipse.xtext.testing.XtextRunner +import org.eclipse.xtext.ui.testing.AbstractHighlightingTest +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(XtextRunner) +@InjectWith(DotUiInjectorProvider) +class DotHtmlLabelIndentationHighlightingTest extends AbstractHighlightingTest { + + // position marker + val c = '''<|>''' + + List highlightingRegions = null + + @Test def test001(){ + ''' + graph { + 1 [label = < + «c» Text + >] + } + '''.testHtmlIndentationHighlighting + } + + @Test def test002(){ + ''' + graph { + 1 [ + label = < + «c» Text + >] + } + '''.testHtmlIndentationHighlighting + } + + @Test def test003(){ + ''' + graph { + 1 [label= + < + «c» Text + >] + } + '''.testHtmlIndentationHighlighting + } + + @Test def test004(){ + ''' + graph { + 1 [label= + < + «c» Text + >] + } + '''.testHtmlIndentationHighlighting + } + + @Test def test005(){ + ''' + graph { + 1 [label= + < + «c» + «c» + «c» + «c» + «c»
Text
+ >] + } + '''.testHtmlIndentationHighlighting + } + + @Test def test006(){ + ''' + graph { + 1 [label= + < + «c» + «c» + «c» + «c» + «c»
Text
+ >] + } + '''.testHtmlIndentationHighlighting + } + + private def testHtmlIndentationHighlighting(CharSequence text) { + text.calculateHighlightingRegions + val content = text.toString.replace(c, "") + testHighlighting(content, null, SWT.NORMAL, 0, 0, 0, 220, 220, 220) + } + + protected override testHighlighting(StyledText styledText, String text, int fontStyle, + int foregroundR, int foregroundG, int foregroundB, + int backgroundR, int backgroundG, int backgroundB) { + + val expectedForegroundColor = new Color(null, foregroundR, foregroundG, foregroundB) + val expectedBackgroundColor = new Color(null, backgroundR, backgroundG, backgroundB) + + for(highlightingRegion : highlightingRegions) { + val offset = highlightingRegion.offset + + for (var i = 0; i < highlightingRegion.length; i++) { + val currentPosition = offset + i + val character = styledText.getTextRange(currentPosition, 1) + val styleRange = styledText.getStyleRangeAtOffset(currentPosition) + if (character.isRelevant) { + styleRange => [ + assertFontStyle(character, fontStyle) + assertForegroundColor(character, expectedForegroundColor) + assertBackgroundColor(character, expectedBackgroundColor) + ] + } else { + Assert.fail("Non relevant character '" + character + "' found!") + } + } + } + } + + override protected isRelevant(String character) { + // consider only whitespace characters + Character.isWhitespace(character.charAt(0)) + } + + private def void calculateHighlightingRegions(CharSequence input) { + highlightingRegions = newArrayList + + val text = input.toString + + var fromIndex = 0 + + while (fromIndex < text.lastIndexOf(c)) { + val first = text.indexOf(c, fromIndex) + + if (first==-1) { + fail('''Can't locate the first position symbol '«c»' in the input text''') + } + + val second = text.getIndexOfFirstNonWhitespaceCharacter(first+c.length) + if (second==-1) { + fail('''Can't locate non-whitespace characters after the position symbol '«c»' in the input text''') + } + + val offset = first - (highlightingRegions.length/*-1*/)*c.length + val length = second - first - c.length + val highlightingRegion = new Region(offset, length) + highlightingRegions.add(highlightingRegion) + + fromIndex = second + c.length + } + } + + private def int getIndexOfFirstNonWhitespaceCharacter(String text, int fromIndex) { + for(var i=fromIndex; i' symbols - String htmlString = node.getText().substring(1, - node.getText().length() - 1); + String htmlText = node.getText(); + + // highlight the html indentations + List htmlTextLines = TextLines.splitString(htmlText); + int htmlTextStartOffset = node.getOffset(); + int indentationLength = getIndentationLength(node, dotText); + /* + * do not hightlight the leading '<' and trailing '>' symbols, skipping + * the first and the last line + */ + for (int i = 1; i < htmlTextLines.size() - 1; i++) { + TextLine htmlTextLine = htmlTextLines.get(i); + int offset = htmlTextStartOffset + htmlTextLine.getRelativeOffset() + + indentationLength; + int length = htmlTextLine.getLeadingWhiteSpace().length() + - indentationLength; + acceptor.addPosition(offset, length, + DotHighlightingConfiguration.HTML_INDENTATION); + } // delegate the highlighting of the the html-label substring to the // corresponding sub-grammar highlighter + String htmlString = htmlText.substring(1, htmlText.length() - 1); DotSubgrammarHighlighter htmlLabelHighlighter = new DotSubgrammarHighlighter( DotActivator.ORG_ECLIPSE_GEF_DOT_INTERNAL_LANGUAGE_DOTHTMLLABEL); htmlLabelHighlighter.provideHightlightingFor(htmlString, node.getOffset() + 1, acceptor); } + + /** + * Get the indentation length of the line containing the leading '<' symbol. + */ + private int getIndentationLength(INode htmlTextNode, String dotText) { + int htmlTextStartLine = htmlTextNode.getStartLine(); + List dotTextLines = TextLines.splitString(dotText); + TextLine lineContainingTheLeadingAngleSymbol = dotTextLines + .get(htmlTextStartLine - 1); + return lineContainingTheLeadingAngleSymbol.getLeadingWhiteSpace() + .length(); + } } diff --git a/org.eclipse.gef.dot.ui/src/org/eclipse/gef/dot/internal/ui/language/highlighting/TextLine.java b/org.eclipse.gef.dot.ui/src/org/eclipse/gef/dot/internal/ui/language/highlighting/TextLine.java new file mode 100644 index 0000000000..aad3c036de --- /dev/null +++ b/org.eclipse.gef.dot.ui/src/org/eclipse/gef/dot/internal/ui/language/highlighting/TextLine.java @@ -0,0 +1,168 @@ +/******************************************************************************* + * Copyright (c) 2020 itemis AG 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 + * + * Contributors: + * Tamas Miklossy (itemis AG) - initial API and implementation + * + *******************************************************************************/ +package org.eclipse.gef.dot.internal.ui.language.highlighting; + +/** + * The implementation of this class is taken from the + * {org.eclipse.xtend.core.richstring.TextLine} class. + */ +public class TextLine implements CharSequence { + + protected static class LeadingWSTextLinePart extends TextLine { + + public LeadingWSTextLinePart(String completeText, int offset, + int length) { + super(completeText, offset, length, 0); + } + + @Override + public CharSequence getLeadingWhiteSpace() { + return this; + } + + @Override + public boolean hasLeadingWhiteSpace() { + return length() > 0; + } + + @Override + public boolean containsOnlyWhitespace() { + return true; + } + } + + private final String completeText; + private final int offset; + private final int length; + private final int delimiterLength; + + public TextLine(String completeText, int offset, int length, + int delimiterLength) { + this.completeText = completeText; + this.offset = offset; + this.length = length; + this.delimiterLength = delimiterLength; + } + + public String getCompleteText() { + return completeText; + } + + public boolean hasLeadingWhiteSpace() { + if (length == 0) + return false; + boolean result = Character.isWhitespace(charAt(0)); + return result; + } + + public boolean containsOnlyWhitespace() { + for (int i = 0; i < length(); i++) { + if (!Character.isWhitespace(charAt(i))) { + return false; + } + } + return true; + } + + public CharSequence getLeadingWhiteSpace() { + for (int i = 0; i < length(); i++) { + if (!Character.isWhitespace(charAt(i))) { + if (i == 0) + return ""; //$NON-NLS-1$ + return new LeadingWSTextLinePart(completeText, offset, i); + } + } + return new LeadingWSTextLinePart(completeText, offset, length); + } + + public boolean hasTrailingLineBreak() { + return delimiterLength > 0; + } + + public int getRelativeOffset() { + return offset; + } + + @Override + public int length() { + return length; + } + + @Override + public char charAt(int index) { + return completeText.charAt(index + offset); + } + + public int getDelimiterLength() { + return delimiterLength; + } + + @Override + public String toString() { + return completeText.substring(offset, offset + length); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + toString().hashCode(); + result = prime * result + delimiterLength; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TextLine other = (TextLine) obj; + if (length != other.length) + return false; + if (delimiterLength != other.delimiterLength) + return false; + if (!completeText.regionMatches(offset, other.completeText, + other.offset, length)) + return false; + return true; + } + + /** + * @throws IndexOutOfBoundsException + * if start or end are negative, if + * end is greater than length(), or if + * start is greater than end + */ + @Override + public CharSequence subSequence(int start, int end) { + if (start < 0 || start > end) { + throwIndexOutOfBounds(start); + } + if (end < 0 || end > length) { + throwIndexOutOfBounds(end); + } + if (start > end) { + throwIndexOutOfBounds(end - start); + } + return completeText.subSequence(start + offset, end + offset); + } + + protected void throwIndexOutOfBounds(int offset) { + throw new IndexOutOfBoundsException(("Index out of range: " + offset)); //$NON-NLS-1$ + } + +} \ No newline at end of file diff --git a/org.eclipse.gef.dot.ui/src/org/eclipse/gef/dot/internal/ui/language/highlighting/TextLines.java b/org.eclipse.gef.dot.ui/src/org/eclipse/gef/dot/internal/ui/language/highlighting/TextLines.java new file mode 100644 index 0000000000..c6368a2ab3 --- /dev/null +++ b/org.eclipse.gef.dot.ui/src/org/eclipse/gef/dot/internal/ui/language/highlighting/TextLines.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2020 itemis AG 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 + * + * Contributors: + * Tamas Miklossy (itemis AG) - initial API and implementation + * + *******************************************************************************/ +package org.eclipse.gef.dot.internal.ui.language.highlighting; + +import java.util.Collections; +import java.util.List; + +import com.google.common.collect.Lists; + +/** + * The implementation of this class is taken from the + * {org.eclipse.xtend.core.richstring.TextLines} class. + */ +public class TextLines { + + public static List splitString(String text) { + List result = Lists.newArrayList(); + appendLines(text, result); + return Collections.unmodifiableList(result); + } + + /** + * adapted from + * org.eclipse.jface.text.DefaultLineTracker.nextDelimiterInfo(String, int) + */ + public static void appendLines(String text, List result) { + if (text == null) + return; + int length = text.length(); + int nextLineOffset = 0; + int idx = 0; + while (idx < length) { + char currentChar = text.charAt(idx); + // check for \r or \r\n + if (currentChar == '\r') { + int delimiterLength = 1; + if (idx + 1 < length && text.charAt(idx + 1) == '\n') { + delimiterLength++; + idx++; + } + int lineLength = idx - delimiterLength - nextLineOffset + 1; + TextLine line = new TextLine(text, nextLineOffset, lineLength, + delimiterLength); + result.add(line); + nextLineOffset = idx + 1; + } else if (currentChar == '\n') { + int lineLength = idx - nextLineOffset; + TextLine line = new TextLine(text, nextLineOffset, lineLength, + 1); + result.add(line); + nextLineOffset = idx + 1; + } + idx++; + } + if (nextLineOffset != length) { + int lineLength = length - nextLineOffset; + TextLine line = new TextLine(text, nextLineOffset, lineLength, 0); + result.add(line); + } + } + +} \ No newline at end of file