Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 696bc6357e01c24eb76dd6b16b653133252be2ed (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
/*******************************************************************************
 * 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

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;
import org.junit.Test;

/**
 * Not really a test for {@link TextConsoleViewer} yet since it only test one
 * private method of it.
 */
public class TextConsoleViewerTest extends AbstractDebugTest {

	/**
	 * Test override of existing styles with a new style. Typically used to
	 * apply link styling on already styled console document.
	 */
	@Test
	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;
		}
	}

	/**
	 * 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;
		}
	}
}

Back to the top