diff options
8 files changed, 417 insertions, 50 deletions
diff --git a/org.eclipse.debug.tests/META-INF/MANIFEST.MF b/org.eclipse.debug.tests/META-INF/MANIFEST.MF index 180427110..3b3c6bff9 100644 --- a/org.eclipse.debug.tests/META-INF/MANIFEST.MF +++ b/org.eclipse.debug.tests/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.debug.tests;singleton:=true -Bundle-Version: 3.11.400.qualifier +Bundle-Version: 3.11.500.qualifier Bundle-Activator: org.eclipse.debug.tests.TestsPlugin Bundle-Localization: plugin Require-Bundle: org.eclipse.ui;bundle-version="[3.6.0,4.0.0)", diff --git a/org.eclipse.debug.tests/pom.xml b/org.eclipse.debug.tests/pom.xml index 8f410afe5..47ef925e7 100644 --- a/org.eclipse.debug.tests/pom.xml +++ b/org.eclipse.debug.tests/pom.xml @@ -18,7 +18,7 @@ </parent> <groupId>org.eclipse.debug</groupId> <artifactId>org.eclipse.debug.tests</artifactId> - <version>3.11.400-SNAPSHOT</version> + <version>3.11.500-SNAPSHOT</version> <packaging>eclipse-test-plugin</packaging> <properties> <code.ignoredWarnings>${tests.ignoredWarnings}</code.ignoredWarnings> diff --git a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java index 317599be1..d659beea9 100644 --- a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java +++ b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java @@ -23,6 +23,7 @@ import org.eclipse.debug.tests.console.IOConsoleTests; import org.eclipse.debug.tests.console.ProcessConsoleManagerTests; import org.eclipse.debug.tests.console.ProcessConsoleTests; import org.eclipse.debug.tests.console.StreamsProxyTests; +import org.eclipse.debug.tests.console.TextConsoleViewerTest; import org.eclipse.debug.tests.launching.AcceleratorSubstitutionTests; import org.eclipse.debug.tests.launching.ArgumentParsingTests; import org.eclipse.debug.tests.launching.LaunchConfigurationTests; @@ -119,6 +120,7 @@ public class AutomatedSuite extends TestSuite { addTest(new TestSuite(ProcessConsoleManagerTests.class)); addTest(new TestSuite(ProcessConsoleTests.class)); addTest(new TestSuite(StreamsProxyTests.class)); + addTest(new TestSuite(TextConsoleViewerTest.class)); // Launch Groups addTest(new TestSuite(LaunchGroupTests.class)); diff --git a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/TextConsoleViewerTest.java b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/TextConsoleViewerTest.java new file mode 100644 index 000000000..ed260ba48 --- /dev/null +++ b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/TextConsoleViewerTest.java @@ -0,0 +1,326 @@ +/******************************************************************************* + * Copyright (c) 2019 Paul Pazderski and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Paul Pazderski - initial API and implementation + *******************************************************************************/ +package org.eclipse.debug.tests.console; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.debug.tests.AbstractDebugTest; +import org.eclipse.swt.custom.StyleRange; +import org.eclipse.swt.graphics.Color; +import org.eclipse.ui.console.TextConsoleViewer; + +/** + * Not really a test for {@link TextConsoleViewer} yet since it only test one + * private method of it. + */ +public class TextConsoleViewerTest extends AbstractDebugTest { + + public TextConsoleViewerTest() { + super(TextConsoleViewerTest.class.getSimpleName()); + } + + public TextConsoleViewerTest(String name) { + super(name); + } + + /** + * Test override of existing styles with a new style. Typically used to + * apply link styling on already styled console document. + */ + public void testStyleOverride() throws Throwable { + Color colorR = null; + Color colorG = null; + Color colorB = null; + Color colorK = null; + Color colorW = null; + try { + final Method method = TextConsoleViewer.class.getDeclaredMethod("overrideStyleRange", List.class, StyleRange.class); + method.setAccessible(true); + assertTrue("Required method <" + method + "> is not static.", Modifier.isStatic(method.getModifiers())); + + final List<StyleRange> styles = new ArrayList<>(); + colorR = new Color(null, 255, 0, 0); + colorG = new Color(null, 0, 255, 0); + colorB = new Color(null, 0, 0, 255); + colorK = new Color(null, 0, 0, 0); + colorW = new Color(null, 255, 255, 255); + + // overwrite in empty list + method.invoke(null, styles, new StyleRange(5, 5, colorR, null)); + checkOverlapping(styles); + assertStyle(styles, 2, 5, null); + assertStyle(styles, 5, 10, colorR); + assertStyle(styles, 10, 13, null); + + // overwrite unstyled part before + method.invoke(null, styles, new StyleRange(0, 3, colorG, null)); + checkOverlapping(styles); + assertStyle(styles, 0, 3, colorG); + assertStyle(styles, 3, 5, null); + assertStyle(styles, 5, 10, colorR); + assertStyle(styles, 10, 13, null); + + // overwrite unstyled part after + method.invoke(null, styles, new StyleRange(15, 5, colorB, null)); + checkOverlapping(styles); + assertStyle(styles, 0, 3, colorG); + assertStyle(styles, 3, 5, null); + assertStyle(styles, 5, 10, colorR); + assertStyle(styles, 10, 15, null); + assertStyle(styles, 15, 20, colorB); + assertStyle(styles, 20, 23, null); + + // overwrite existing: start exact, end exact + method.invoke(null, styles, new StyleRange(0, 3, colorK, null)); + checkOverlapping(styles); + assertStyle(styles, 0, 3, colorK); + assertStyle(styles, 3, 5, null); + assertStyle(styles, 5, 10, colorR); + assertStyle(styles, 10, 15, null); + assertStyle(styles, 15, 20, colorB); + assertStyle(styles, 20, 23, null); + + // overwrite existing: start exact, end after + method.invoke(null, styles, new StyleRange(0, 4, colorW, null)); + checkOverlapping(styles); + assertStyle(styles, 0, 4, colorW); + assertStyle(styles, 4, 5, null); + assertStyle(styles, 5, 10, colorR); + assertStyle(styles, 10, 15, null); + assertStyle(styles, 15, 20, colorB); + assertStyle(styles, 20, 23, null); + + // overwrite existing: start exact, end inside + method.invoke(null, styles, new StyleRange(0, 2, colorK, null)); + checkOverlapping(styles); + assertStyle(styles, 0, 2, colorK); + assertStyle(styles, 2, 4, colorW); + assertStyle(styles, 4, 5, null); + assertStyle(styles, 5, 10, colorR); + assertStyle(styles, 10, 15, null); + assertStyle(styles, 15, 20, colorB); + assertStyle(styles, 20, 23, null); + + // overwrite existing: start before, end exact + method.invoke(null, styles, new StyleRange(13, 7, colorW, null)); + checkOverlapping(styles); + assertStyle(styles, 0, 2, colorK); + assertStyle(styles, 2, 4, colorW); + assertStyle(styles, 4, 5, null); + assertStyle(styles, 5, 10, colorR); + assertStyle(styles, 10, 13, null); + assertStyle(styles, 13, 20, colorW); + assertStyle(styles, 20, 23, null); + + // overwrite existing: start inside, end exact + method.invoke(null, styles, new StyleRange(15, 5, colorK, null)); + checkOverlapping(styles); + assertStyle(styles, 0, 2, colorK); + assertStyle(styles, 2, 4, colorW); + assertStyle(styles, 4, 5, null); + assertStyle(styles, 5, 10, colorR); + assertStyle(styles, 10, 13, null); + assertStyle(styles, 13, 15, colorW); + assertStyle(styles, 15, 20, colorK); + assertStyle(styles, 20, 23, null); + + // prepare new existing style + styles.clear(); + method.invoke(null, styles, new StyleRange(10, 10, colorW, null)); + assertStyle(styles, 10, 20, colorW); + method.invoke(null, styles, new StyleRange(10, 10, colorK, null)); + assertStyle(styles, 10, 20, colorK); + assertEquals("Wrong number of styles.", 1, styles.size()); + + // overwrite existing: start before, end after + method.invoke(null, styles, new StyleRange(5, 15, colorR, null)); + checkOverlapping(styles); + assertStyle(styles, 5, 20, colorR); + + // overwrite existing: start before, end inside + method.invoke(null, styles, new StyleRange(0, 10, colorG, null)); + checkOverlapping(styles); + assertStyle(styles, 0, 10, colorG); + assertStyle(styles, 10, 20, colorR); + + // overwrite existing: start inside, end after + method.invoke(null, styles, new StyleRange(15, 10, colorB, null)); + checkOverlapping(styles); + assertStyle(styles, 0, 10, colorG); + assertStyle(styles, 10, 15, colorR); + assertStyle(styles, 15, 20, colorB); + + // overwrite existing: start inside, end inside + method.invoke(null, styles, new StyleRange(6, 1, colorW, null)); + checkOverlapping(styles); + assertStyle(styles, 0, 6, colorG); + assertStyle(styles, 6, 7, colorW); + assertStyle(styles, 7, 10, colorG); + assertStyle(styles, 10, 15, colorR); + assertStyle(styles, 15, 20, colorB); + + // overwrite many styles + method.invoke(null, styles, new StyleRange(0, 25, colorK, null)); + checkOverlapping(styles); + assertStyle(styles, 0, 25, colorK); + assertStyle(styles, 25, 30, null); + + // prepare new existing style + styles.clear(); + styles.add(new StyleRange(0, 10, colorR, null)); + styles.add(new StyleRange(15, 5, colorG, null)); + + // overwrite: start in one style, end in other + method.invoke(null, styles, new StyleRange(7, 11, colorB, null)); + checkOverlapping(styles); + assertStyle(styles, 0, 7, colorR); + assertStyle(styles, 7, 18, colorB); + assertStyle(styles, 18, 20, colorG); + assertStyle(styles, 20, 25, null); + + // prepare new existing style + styles.clear(); + styles.add(new StyleRange(0, 10, colorR, null)); + styles.add(new StyleRange(15, 5, colorG, null)); + + // overwrite: start in one style, end after other + method.invoke(null, styles, new StyleRange(7, 15, colorB, null)); + checkOverlapping(styles); + assertStyle(styles, 0, 7, colorR); + assertStyle(styles, 7, 22, colorB); + assertStyle(styles, 22, 25, null); + + // prepare new existing style + styles.clear(); + styles.add(new StyleRange(5, 5, colorR, null)); + styles.add(new StyleRange(15, 5, colorG, null)); + + // overwrite: start before one style, end in other + method.invoke(null, styles, new StyleRange(2, 15, colorB, null)); + checkOverlapping(styles); + assertStyle(styles, 0, 2, null); + assertStyle(styles, 2, 17, colorB); + assertStyle(styles, 17, 20, colorG); + assertStyle(styles, 20, 25, null); + + } catch (InvocationTargetException e) { + if (e.getTargetException() != null) { + throw e.getTargetException(); + } + throw e; + } catch (Exception e) { + // if this happened the method may have be renamed or moved + throw e; + } + finally { + if (colorR != null) { + colorR.dispose(); + } + if (colorG != null) { + colorG.dispose(); + } + if (colorB != null) { + colorB.dispose(); + } + if (colorK != null) { + colorK.dispose(); + } + if (colorW != null) { + colorW.dispose(); + } + } + } + + /** + * Assert any offset in given range is styled with given foreground color. + * <p> + * Note: this test class only uses foreground color and assumes all other + * style attributes are set to there default values. + * </p> + * + * @param styles list of known style ranges + * @param offset inclusive start offset of range to check + * @param end exclusive end offset of range to check + * @param foregroundColor the expected foreground color for styles in given + * range. May be <code>null</code> to check for unstyled ranges. + */ + private static void assertStyle(List<StyleRange> styles, int offset, int end, Color foregroundColor) { + int o = offset; + while (o < end) { + final StyleRange expected = foregroundColor != null ? new StyleRange(0, 0, foregroundColor, null) : null; + final StyleRange actual = getStyleAtOffset(styles, o); + assertEquals("Got wrong style at offset " + o, generalizeStyle(expected), generalizeStyle(actual)); + final int step = actual != null ? actual.length : 1; + o += Math.min(step, 1); + } + } + + /** + * Get style from list at given offset or <code>null</code>. If offset has + * more then one style it is undefined which one is returned. Does not + * return zero-length styles. + * + * @param styles list of styles + * @param offset offset of interest + * @return style at given offset or <code>null</code> if offset is not + * styled + */ + private static StyleRange getStyleAtOffset(List<StyleRange> styles, int offset) { + if (styles != null) { + for (StyleRange style : styles) { + if (style.start <= offset && style.start + style.length >= offset + 1) { + return style; + } + } + } + return null; + } + + /** + * Set styles start and length to <code>0</code> to compare only the styling + * parts using <code>equals</code>. + * + * @param style the original style. Not modified by this method. + * @return the style without position information. + */ + private static StyleRange generalizeStyle(StyleRange style) { + if (style == null) { + return new StyleRange(); + } + final StyleRange copy = (StyleRange) style.clone(); + copy.start = copy.length = 0; + return copy; + } + + /** + * Check if styles are disjoint and sorted ascending by offset. + * + * @param styles the styles to check + */ + private static void checkOverlapping(List<StyleRange> styles) { + if (styles == null || styles.size() <= 1) { + return; + } + int lastEnd = Integer.MIN_VALUE; + for (StyleRange s : styles) { + assertTrue("Styles overlap or not sorted.", lastEnd <= s.start); + assertTrue("Empty style.", s.length > 0); + lastEnd = s.start + s.length; + } + } +} diff --git a/org.eclipse.ui.console/META-INF/MANIFEST.MF b/org.eclipse.ui.console/META-INF/MANIFEST.MF index 2e5be95a9..e84c1e558 100644 --- a/org.eclipse.ui.console/META-INF/MANIFEST.MF +++ b/org.eclipse.ui.console/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.ui.console; singleton:=true -Bundle-Version: 3.8.600.qualifier +Bundle-Version: 3.8.700.qualifier Bundle-Activator: org.eclipse.ui.console.ConsolePlugin Bundle-Vendor: %providerName Bundle-Localization: plugin diff --git a/org.eclipse.ui.console/pom.xml b/org.eclipse.ui.console/pom.xml index 164593c55..7b76a4f3d 100644 --- a/org.eclipse.ui.console/pom.xml +++ b/org.eclipse.ui.console/pom.xml @@ -18,6 +18,6 @@ </parent> <groupId>org.eclipse.ui</groupId> <artifactId>org.eclipse.ui.console</artifactId> - <version>3.8.600-SNAPSHOT</version> + <version>3.8.700-SNAPSHOT</version> <packaging>eclipse-plugin</packaging> </project> diff --git a/org.eclipse.ui.console/src/org/eclipse/ui/console/TextConsole.java b/org.eclipse.ui.console/src/org/eclipse/ui/console/TextConsole.java index e847c23fa..e0cf26783 100644 --- a/org.eclipse.ui.console/src/org/eclipse/ui/console/TextConsole.java +++ b/org.eclipse.ui.console/src/org/eclipse/ui/console/TextConsole.java @@ -376,10 +376,10 @@ public abstract class TextConsole extends AbstractConsole { } /** - * Returns the hyperlink at the given offset of <code>null</code> if none. + * Returns the hyperlink at the given offset or <code>null</code> if none. * * @param offset offset for which a hyperlink is requested - * @return the hyperlink at the given offset of <code>null</code> if none + * @return the hyperlink at the given offset or <code>null</code> if none */ public IHyperlink getHyperlink(int offset) { try { @@ -399,7 +399,8 @@ public abstract class TextConsole extends AbstractConsole { /** * Binary search for the position at a given offset. * - * @param offset the offset whose position should be found + * @param offset the offset whose position should be found + * @param positions the positions list to search in * @return the position containing the offset, or <code>null</code> */ private Position findPosition(int offset, Position[] positions) { diff --git a/org.eclipse.ui.console/src/org/eclipse/ui/console/TextConsoleViewer.java b/org.eclipse.ui.console/src/org/eclipse/ui/console/TextConsoleViewer.java index 6a94c00bb..c5ffa14bd 100644 --- a/org.eclipse.ui.console/src/org/eclipse/ui/console/TextConsoleViewer.java +++ b/org.eclipse.ui.console/src/org/eclipse/ui/console/TextConsoleViewer.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2018 IBM Corporation and others. + * Copyright (c) 2000, 2019 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -10,6 +10,7 @@ * * Contributors: * IBM Corporation - initial API and implementation + * Paul Pazderski - Bug 550620: reimplementation of style override (to fix existing and future problems with link styling) *******************************************************************************/ package org.eclipse.ui.console; @@ -377,11 +378,9 @@ public class TextConsoleViewer extends SourceViewer implements LineStyleListener int offset = event.lineOffset; int length = event.lineText.length(); - StyleRange[] partitionerStyles = ((IConsoleDocumentPartitioner) document.getDocumentPartitioner()).getStyleRanges(event.lineOffset, event.lineText.length()); + StyleRange[] partitionerStyles = ((IConsoleDocumentPartitioner) document.getDocumentPartitioner()).getStyleRanges(offset, length); if (partitionerStyles != null) { Collections.addAll(ranges, partitionerStyles); - } else { - ranges.add(new StyleRange(offset, length, null, null)); } try { @@ -393,7 +392,7 @@ public class TextConsoleViewer extends SourceViewer implements LineStyleListener Position position = overlap[i]; StyleRange linkRange = new StyleRange(position.offset, position.length, color, null); linkRange.underline = true; - override(ranges, linkRange); + overrideStyleRange(ranges, linkRange); } } } catch (BadPositionCategoryException e) { @@ -405,52 +404,92 @@ public class TextConsoleViewer extends SourceViewer implements LineStyleListener } } - private void override(List<StyleRange> ranges, StyleRange newRange) { - if (ranges.isEmpty()) { - ranges.add(newRange); - return; - } - - int start = newRange.start; - int end = start + newRange.length; - for (int i = 0; i < ranges.size(); i++) { - StyleRange existingRange = ranges.get(i); - int rEnd = existingRange.start + existingRange.length; - if (end <= existingRange.start || start >= rEnd) { - continue; - } - - if (start < existingRange.start && end > existingRange.start) { - start = existingRange.start; + /** + * Apply new style range to list of existing style ranges. If the new style + * range overlaps with any of the existing style ranges the new style overrides + * the existing one in the affected range by splitting/adjusting the existing + * ones. + * + * @param ranges list of existing style ranges (will contain the new style + * range when finished) + * @param newRange new style range which should be combined with the existing + * ranges + */ + private static void overrideStyleRange(List<StyleRange> ranges, StyleRange newRange) { + final int overrideStart = newRange.start; + final int overrideEnd = overrideStart + newRange.length; + int insertIndex = ranges.size(); + for (int i = ranges.size() - 1; i >= 0; i--) { + final StyleRange existingRange = ranges.get(i); + final int existingStart = existingRange.start; + final int existingEnd = existingStart + existingRange.length; + + // Find first position to insert where offset of new range is smaller then all + // offsets before. This way the list is still sorted by offset after insert if + // it was sorted before and it will not fail if list was not sorted. + if (overrideStart <= existingStart) { + insertIndex = i; } - if (start >= existingRange.start && end <= rEnd) { - existingRange.length = start - existingRange.start; - ranges.add(++i, newRange); - if (end != rEnd) { - ranges.add(++i, new StyleRange(end, rEnd - end - 1, existingRange.foreground, existingRange.background)); + // adjust the existing range if required + if (overrideStart <= existingStart) { // new range starts before or with existing + if (overrideEnd < existingStart) { + // new range lies before existing range. No overlapping. + // new range: ++++_________ + // existing : ________===== + // . result : ++++____===== + // nothing to do + } else if (overrideEnd < existingEnd) { + // new range overlaps start of existing. + // new range: ++++++++_____ + // existing : _____======== + // . result : ++++++++===== + final int overlap = overrideEnd - existingStart; + existingRange.start += overlap; + existingRange.length -= overlap; + } else { + // new range completely overlaps existing. + // new range: ___++++++++++ + // existing : ___======____ + // . result : ___++++++++++ + ranges.remove(i); + } + } else { // new range starts inside or after existing + if (existingEnd < overrideStart) { + // new range lies after existing range. No overlapping. + // new range: _________++++ + // existing : =====________ + // . result : =====____++++ + // nothing to do + } else if (overrideEnd >= existingEnd) { + // new range overlaps end of existing. + // new range: _____++++++++ + // existing : ========_____ + // . result : =====++++++++ + existingRange.length -= existingEnd - overrideStart; + } else { + // new range lies inside existing range but not overrides all of it + // (and does not touch first or last offset of existing) + // new range: ____+++++____ + // existing : ============= + // . result : ====+++++==== + final StyleRange clonedRange = (StyleRange) existingRange.clone(); + existingRange.length = overrideStart - existingStart; + clonedRange.start = overrideEnd; + clonedRange.length = existingEnd - overrideEnd; + ranges.add(i + 1, clonedRange); } - return; - } else if (start >= existingRange.start && start < rEnd) { - existingRange.length = start - existingRange.start; - ranges.add(++i, newRange); - } else if (end >= rEnd) { - ranges.remove(i); - } else { - ranges.add(++i, new StyleRange(end + 1, rEnd - end + 1, existingRange.foreground, existingRange.background)); } } + ranges.add(insertIndex, newRange); } /** * Binary search for the positions overlapping the given range * - * @param offset - * the offset of the range - * @param length - * the length of the range - * @param positions - * the positions to search + * @param offset the offset of the range + * @param length the length of the range + * @param positions the positions to search * @return the positions overlapping the given range, or <code>null</code> */ private Position[] findPosition(int offset, int length, Position[] positions) { @@ -635,8 +674,7 @@ public class TextConsoleViewer extends SourceViewer implements LineStyleListener * Returns the hyperlink at the specified offset, or <code>null</code> if * none. * - * @param offset - * offset at which a hyperlink has been requested + * @param offset offset at which a hyperlink has been requested * @return hyperlink at the specified offset, or <code>null</code> if none */ public IHyperlink getHyperlink(int offset) { |