1 /*******************************************************************************
2  * Copyright (c) 2000, 2018 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Red Hat Inc. - use HighlightedPositionCore
14  *******************************************************************************/
15 
16 package org.eclipse.jdt.internal.ui.javaeditor;
17 
18 import java.util.ArrayList;
19 import java.util.List;
20 
21 import org.eclipse.swt.SWT;
22 import org.eclipse.swt.custom.StyleRange;
23 import org.eclipse.swt.graphics.Color;
24 import org.eclipse.swt.graphics.RGB;
25 
26 import org.eclipse.jface.preference.IPreferenceStore;
27 import org.eclipse.jface.preference.PreferenceConverter;
28 import org.eclipse.jface.resource.StringConverter;
29 import org.eclipse.jface.util.IPropertyChangeListener;
30 import org.eclipse.jface.util.PropertyChangeEvent;
31 
32 import org.eclipse.jface.text.Region;
33 import org.eclipse.jface.text.TextAttribute;
34 
35 import org.eclipse.jdt.ui.text.IColorManager;
36 import org.eclipse.jdt.ui.text.IColorManagerExtension;
37 import org.eclipse.jdt.ui.text.JavaSourceViewerConfiguration;
38 
39 import org.eclipse.jdt.internal.ui.text.JavaPresentationReconciler;
40 
41 /**
42  * Semantic highlighting manager
43  *
44  * @since 3.0
45  */
46 public class SemanticHighlightingManager implements IPropertyChangeListener {
47 
48 	/**
49 	 * Highlighting.
50 	 */
51 	static class Highlighting { // TODO: rename to HighlightingStyle
52 
53 		/** Text attribute */
54 		private TextAttribute fTextAttribute;
55 		/** Enabled state */
56 		private boolean fIsEnabled;
57 
58 		/**
59 		 * Initialize with the given text attribute.
60 		 * @param textAttribute The text attribute
61 		 * @param isEnabled the enabled state
62 		 */
Highlighting(TextAttribute textAttribute, boolean isEnabled)63 		public Highlighting(TextAttribute textAttribute, boolean isEnabled) {
64 			setTextAttribute(textAttribute);
65 			setEnabled(isEnabled);
66 		}
67 
68 		/**
69 		 * @return Returns the text attribute.
70 		 */
getTextAttribute()71 		public TextAttribute getTextAttribute() {
72 			return fTextAttribute;
73 		}
74 
75 		/**
76 		 * @param textAttribute The background to set.
77 		 */
setTextAttribute(TextAttribute textAttribute)78 		public void setTextAttribute(TextAttribute textAttribute) {
79 			fTextAttribute= textAttribute;
80 		}
81 
82 		/**
83 		 * @return the enabled state
84 		 */
isEnabled()85 		public boolean isEnabled() {
86 			return fIsEnabled;
87 		}
88 
89 		/**
90 		 * @param isEnabled the new enabled state
91 		 */
setEnabled(boolean isEnabled)92 		public void setEnabled(boolean isEnabled) {
93 			fIsEnabled= isEnabled;
94 		}
95 	}
96 
97 	/**
98 	 * Highlighted Positions.
99 	 */
100 	static class HighlightedPosition extends HighlightedPositionCore {
101 
102 		/**
103 		 * Initialize the styled positions with the given offset, length and foreground color.
104 		 *
105 		 * @param offset The position offset
106 		 * @param length The position length
107 		 * @param highlighting The position's highlighting
108 		 * @param lock The lock object
109 		 */
HighlightedPosition(int offset, int length, Highlighting highlighting, Object lock)110 		public HighlightedPosition(int offset, int length, Highlighting highlighting, Object lock) {
111 			super(offset, length, highlighting, lock);
112 		}
113 
114 		/**
115 		 * @return Returns a corresponding style range.
116 		 */
createStyleRange()117 		public StyleRange createStyleRange() {
118 			int len= 0;
119 			if (getHighlighting().isEnabled())
120 				len= getLength();
121 
122 			TextAttribute textAttribute= getHighlighting().getTextAttribute();
123 			int style= textAttribute.getStyle();
124 			int fontStyle= style & (SWT.ITALIC | SWT.BOLD | SWT.NORMAL);
125 			StyleRange styleRange= new StyleRange(getOffset(), len, textAttribute.getForeground(), textAttribute.getBackground(), fontStyle);
126 			styleRange.strikeout= (style & TextAttribute.STRIKETHROUGH) != 0;
127 			styleRange.underline= (style & TextAttribute.UNDERLINE) != 0;
128 
129 			return styleRange;
130 		}
131 
132 		/**
133 		 * Uses reference equality for the highlighting.
134 		 *
135 		 * @param off The offset
136 		 * @param len The length
137 		 * @param highlighting The highlighting
138 		 * @return <code>true</code> iff the given offset, length and highlighting are equal to the internal ones.
139 		 */
isEqual(int off, int len, Highlighting highlighting)140 		public boolean isEqual(int off, int len, Highlighting highlighting) {
141 			return super.isEqual(off, len, highlighting);
142 		}
143 
144 		/**
145 		 * Is this position contained in the given range (inclusive)? Synchronizes on position updater.
146 		 *
147 		 * @param off The range offset
148 		 * @param len The range length
149 		 * @return <code>true</code> iff this position is not delete and contained in the given range.
150 		 */
151 		@Override
isContained(int off, int len)152 		public boolean isContained(int off, int len) {
153 			return super.isContained(off, len);
154 		}
155 
156 		@Override
update(int off, int len)157 		public void update(int off, int len) {
158 			super.update(off, len);
159 		}
160 
161 		/*
162 		 * @see org.eclipse.jface.text.Position#setLength(int)
163 		 */
164 		@Override
setLength(int length)165 		public void setLength(int length) {
166 			super.setLength(length);
167 		}
168 
169 		/*
170 		 * @see org.eclipse.jface.text.Position#setOffset(int)
171 		 */
172 		@Override
setOffset(int offset)173 		public void setOffset(int offset) {
174 			super.setOffset(offset);
175 		}
176 
177 		/*
178 		 * @see org.eclipse.jface.text.Position#delete()
179 		 */
180 		@Override
delete()181 		public void delete() {
182 			super.delete();
183 		}
184 
185 		/*
186 		 * @see org.eclipse.jface.text.Position#undelete()
187 		 */
188 		@Override
undelete()189 		public void undelete() {
190 			super.undelete();
191 		}
192 
193 		/**
194 		 * @return Returns the highlighting.
195 		 */
196 		@Override
getHighlighting()197 		public Highlighting getHighlighting() {
198 			return (Highlighting)super.getHighlighting();
199 		}
200 	}
201 
202 	/**
203 	 * Highlighted ranges.
204 	 */
205 	public static class HighlightedRange extends Region {
206 		/** The highlighting key as returned by {@link SemanticHighlighting#getPreferenceKey()}. */
207 		private String fKey;
208 
209 		/**
210 		 * Initialize with the given offset, length and highlighting key.
211 		 *
212 		 * @param offset the offset
213 		 * @param length the length
214 		 * @param key the highlighting key as returned by
215 		 *            {@link SemanticHighlighting#getPreferenceKey()}
216 		 */
HighlightedRange(int offset, int length, String key)217 		public HighlightedRange(int offset, int length, String key) {
218 			super(offset, length);
219 			fKey= key;
220 		}
221 
222 		/**
223 		 * @return the highlighting key as returned by {@link SemanticHighlighting#getPreferenceKey()}
224 		 */
getKey()225 		public String getKey() {
226 			return fKey;
227 		}
228 
229 		/*
230 		 * @see org.eclipse.jface.text.Region#equals(java.lang.Object)
231 		 */
232 		@Override
equals(Object o)233 		public boolean equals(Object o) {
234 			return super.equals(o) && o instanceof HighlightedRange && fKey.equals(((HighlightedRange)o).getKey());
235 		}
236 
237 		/*
238 		 * @see org.eclipse.jface.text.Region#hashCode()
239 		 */
240 		@Override
hashCode()241 		public int hashCode() {
242 			return super.hashCode() | fKey.hashCode();
243 		}
244 	}
245 
246 	/** Semantic highlighting presenter */
247 	private SemanticHighlightingPresenter fPresenter;
248 	/** Semantic highlighting reconciler */
249 	private SemanticHighlightingReconciler fReconciler;
250 
251 	/** Semantic highlightings */
252 	private SemanticHighlighting[] fSemanticHighlightings;
253 	/** Highlightings */
254 	private Highlighting[] fHighlightings;
255 
256 	/** The editor */
257 	private JavaEditor fEditor;
258 	/** The source viewer */
259 	private JavaSourceViewer fSourceViewer;
260 	/** The color manager */
261 	private IColorManager fColorManager;
262 	/** The preference store */
263 	private IPreferenceStore fPreferenceStore;
264 	/** The source viewer configuration */
265 	private JavaSourceViewerConfiguration fConfiguration;
266 	/** The presentation reconciler */
267 	private JavaPresentationReconciler fPresentationReconciler;
268 
269 	/** The hard-coded ranges */
270 	private HighlightedRange[][] fHardcodedRanges;
271 
272 	/**
273 	 * Install the semantic highlighting on the given editor infrastructure
274 	 *
275 	 * @param editor The Java editor
276 	 * @param sourceViewer The source viewer
277 	 * @param colorManager The color manager
278 	 * @param preferenceStore The preference store
279 	 */
install(JavaEditor editor, JavaSourceViewer sourceViewer, IColorManager colorManager, IPreferenceStore preferenceStore)280 	public void install(JavaEditor editor, JavaSourceViewer sourceViewer, IColorManager colorManager, IPreferenceStore preferenceStore) {
281 		fEditor= editor;
282 		fSourceViewer= sourceViewer;
283 		fColorManager= colorManager;
284 		fPreferenceStore= preferenceStore;
285 		if (fEditor != null) {
286 			fConfiguration= editor.createJavaSourceViewerConfiguration();
287 			fPresentationReconciler= (JavaPresentationReconciler) fConfiguration.getPresentationReconciler(sourceViewer);
288 		} else {
289 			fConfiguration= null;
290 			fPresentationReconciler= null;
291 		}
292 
293 		fPreferenceStore.addPropertyChangeListener(this);
294 
295 		if (isEnabled())
296 			enable();
297 	}
298 
299 	/**
300 	 * Install the semantic highlighting on the given source viewer infrastructure. No reconciliation will be performed.
301 	 *
302 	 * @param sourceViewer the source viewer
303 	 * @param colorManager the color manager
304 	 * @param preferenceStore the preference store
305 	 * @param hardcodedRanges the hard-coded ranges to be highlighted
306 	 */
install(JavaSourceViewer sourceViewer, IColorManager colorManager, IPreferenceStore preferenceStore, HighlightedRange[][] hardcodedRanges)307 	public void install(JavaSourceViewer sourceViewer, IColorManager colorManager, IPreferenceStore preferenceStore, HighlightedRange[][] hardcodedRanges) {
308 		fHardcodedRanges= hardcodedRanges;
309 		install(null, sourceViewer, colorManager, preferenceStore);
310 	}
311 
312 	/**
313 	 * Enable semantic highlighting.
314 	 */
enable()315 	private void enable() {
316 		initializeHighlightings();
317 
318 		fPresenter= new SemanticHighlightingPresenter();
319 		fPresenter.install(fSourceViewer, fPresentationReconciler);
320 
321 		if (fEditor != null) {
322 			fReconciler= new SemanticHighlightingReconciler();
323 			fReconciler.install(fEditor, fSourceViewer, fPresenter, fSemanticHighlightings, fHighlightings);
324 		} else {
325 			fPresenter.updatePresentation(null, createHardcodedPositions(), new HighlightedPosition[0]);
326 		}
327 	}
328 
329 	/**
330 	 * Computes the hard-coded positions from the hard-coded ranges
331 	 *
332 	 * @return the hard-coded positions
333 	 */
createHardcodedPositions()334 	private HighlightedPosition[] createHardcodedPositions() {
335 		List<HighlightedPosition> positions= new ArrayList<>();
336 		for (HighlightedRange[] hardcodedRange : fHardcodedRanges) {
337 			HighlightedRange range= null;
338 			Highlighting hl= null;
339 			for (HighlightedRange r : hardcodedRange) {
340 				hl= getHighlighting(r.getKey());
341 				if (hl.isEnabled()) {
342 					range= r;
343 					break;
344 				}
345 			}
346 			if (range != null)
347 				positions.add(fPresenter.createHighlightedPosition(range.getOffset(), range.getLength(), hl));
348 		}
349 		return positions.toArray(new HighlightedPosition[positions.size()]);
350 	}
351 
352 	/**
353 	 * Returns the highlighting corresponding to the given key.
354 	 *
355 	 * @param key the highlighting key as returned by {@link SemanticHighlighting#getPreferenceKey()}
356 	 * @return the corresponding highlighting
357 	 */
getHighlighting(String key)358 	private Highlighting getHighlighting(String key) {
359 		for (int i= 0; i < fSemanticHighlightings.length; i++) {
360 			SemanticHighlighting semanticHighlighting= fSemanticHighlightings[i];
361 			if (key.equals(semanticHighlighting.getPreferenceKey()))
362 				return fHighlightings[i];
363 		}
364 		return null;
365 	}
366 
367 	/**
368 	 * Uninstall the semantic highlighting
369 	 */
uninstall()370 	public void uninstall() {
371 		disable();
372 
373 		if (fPreferenceStore != null) {
374 			fPreferenceStore.removePropertyChangeListener(this);
375 			fPreferenceStore= null;
376 		}
377 
378 		fEditor= null;
379 		fSourceViewer= null;
380 		fColorManager= null;
381 		fConfiguration= null;
382 		fPresentationReconciler= null;
383 		fHardcodedRanges= null;
384 	}
385 
386 	/**
387 	 * Disable semantic highlighting.
388 	 */
disable()389 	private void disable() {
390 		if (fReconciler != null) {
391 			fReconciler.uninstall();
392 			fReconciler= null;
393 		}
394 
395 		if (fPresenter != null) {
396 			fPresenter.uninstall();
397 			fPresenter= null;
398 		}
399 
400 		if (fSemanticHighlightings != null)
401 			disposeHighlightings();
402 	}
403 
404 	/**
405 	 * @return <code>true</code> iff semantic highlighting is enabled in the preferences
406 	 */
isEnabled()407 	private boolean isEnabled() {
408 		return SemanticHighlightings.isEnabled(fPreferenceStore);
409 	}
410 
411 	/**
412 	 * Initialize semantic highlightings.
413 	 */
initializeHighlightings()414 	private void initializeHighlightings() {
415 		fSemanticHighlightings= SemanticHighlightings.getSemanticHighlightings();
416 		fHighlightings= new Highlighting[fSemanticHighlightings.length];
417 
418 		for (int i= 0, n= fSemanticHighlightings.length; i < n; i++) {
419 			SemanticHighlighting semanticHighlighting= fSemanticHighlightings[i];
420 			String colorKey= SemanticHighlightings.getColorPreferenceKey(semanticHighlighting);
421 			addColor(colorKey);
422 
423 			String boldKey= SemanticHighlightings.getBoldPreferenceKey(semanticHighlighting);
424 			int style= fPreferenceStore.getBoolean(boldKey) ? SWT.BOLD : SWT.NORMAL;
425 
426 			String italicKey= SemanticHighlightings.getItalicPreferenceKey(semanticHighlighting);
427 			if (fPreferenceStore.getBoolean(italicKey))
428 				style |= SWT.ITALIC;
429 
430 			String strikethroughKey= SemanticHighlightings.getStrikethroughPreferenceKey(semanticHighlighting);
431 			if (fPreferenceStore.getBoolean(strikethroughKey))
432 				style |= TextAttribute.STRIKETHROUGH;
433 
434 			String underlineKey= SemanticHighlightings.getUnderlinePreferenceKey(semanticHighlighting);
435 			if (fPreferenceStore.getBoolean(underlineKey))
436 				style |= TextAttribute.UNDERLINE;
437 
438 			boolean isEnabled= fPreferenceStore.getBoolean(SemanticHighlightings.getEnabledPreferenceKey(semanticHighlighting));
439 
440 			fHighlightings[i]= new Highlighting(new TextAttribute(fColorManager.getColor(PreferenceConverter.getColor(fPreferenceStore, colorKey)), null, style), isEnabled);
441 		}
442 	}
443 
444 	/**
445 	 * Dispose the semantic highlightings.
446 	 */
disposeHighlightings()447 	private void disposeHighlightings() {
448 		for (SemanticHighlighting fSemanticHighlighting : fSemanticHighlightings)
449 			removeColor(SemanticHighlightings.getColorPreferenceKey(fSemanticHighlighting));
450 
451 		fSemanticHighlightings= null;
452 		fHighlightings= null;
453 	}
454 
455 	/*
456 	 * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
457 	 */
458 	@Override
propertyChange(PropertyChangeEvent event)459 	public void propertyChange(PropertyChangeEvent event) {
460 		handlePropertyChangeEvent(event);
461 	}
462 
463 	/**
464 	 * Handle the given property change event
465 	 *
466 	 * @param event The event
467 	 */
handlePropertyChangeEvent(PropertyChangeEvent event)468 	private void handlePropertyChangeEvent(PropertyChangeEvent event) {
469 		if (fPreferenceStore == null)
470 			return; // Uninstalled during event notification
471 
472 		if (fConfiguration != null)
473 			fConfiguration.handlePropertyChangeEvent(event);
474 
475 		if (SemanticHighlightings.affectsEnablement(fPreferenceStore, event)) {
476 			if (isEnabled())
477 				enable();
478 			else
479 				disable();
480 		}
481 
482 		if (!isEnabled())
483 			return;
484 
485 		boolean refreshNeeded= false;
486 
487 		for (int i= 0, n= fSemanticHighlightings.length; i < n; i++) {
488 			SemanticHighlighting semanticHighlighting= fSemanticHighlightings[i];
489 
490 			String colorKey= SemanticHighlightings.getColorPreferenceKey(semanticHighlighting);
491 			if (colorKey.equals(event.getProperty())) {
492 				adaptToTextForegroundChange(fHighlightings[i], event);
493 				fPresenter.highlightingStyleChanged(fHighlightings[i]);
494 				refreshNeeded= true;
495 				continue;
496 			}
497 
498 			String boldKey= SemanticHighlightings.getBoldPreferenceKey(semanticHighlighting);
499 			if (boldKey.equals(event.getProperty())) {
500 				adaptToTextStyleChange(fHighlightings[i], event, SWT.BOLD);
501 				fPresenter.highlightingStyleChanged(fHighlightings[i]);
502 				refreshNeeded= true;
503 				continue;
504 			}
505 
506 			String italicKey= SemanticHighlightings.getItalicPreferenceKey(semanticHighlighting);
507 			if (italicKey.equals(event.getProperty())) {
508 				adaptToTextStyleChange(fHighlightings[i], event, SWT.ITALIC);
509 				fPresenter.highlightingStyleChanged(fHighlightings[i]);
510 				refreshNeeded= true;
511 				continue;
512 			}
513 
514 			String strikethroughKey= SemanticHighlightings.getStrikethroughPreferenceKey(semanticHighlighting);
515 			if (strikethroughKey.equals(event.getProperty())) {
516 				adaptToTextStyleChange(fHighlightings[i], event, TextAttribute.STRIKETHROUGH);
517 				fPresenter.highlightingStyleChanged(fHighlightings[i]);
518 				refreshNeeded= true;
519 				continue;
520 			}
521 
522 			String underlineKey= SemanticHighlightings.getUnderlinePreferenceKey(semanticHighlighting);
523 			if (underlineKey.equals(event.getProperty())) {
524 				adaptToTextStyleChange(fHighlightings[i], event, TextAttribute.UNDERLINE);
525 				fPresenter.highlightingStyleChanged(fHighlightings[i]);
526 				refreshNeeded= true;
527 				continue;
528 			}
529 
530 			String enabledKey= SemanticHighlightings.getEnabledPreferenceKey(semanticHighlighting);
531 			if (enabledKey.equals(event.getProperty())) {
532 				adaptToEnablementChange(fHighlightings[i], event);
533 				fPresenter.highlightingStyleChanged(fHighlightings[i]);
534 				refreshNeeded= true;
535 				continue;
536 			}
537 		}
538 
539 		if (refreshNeeded && fReconciler != null)
540 			fReconciler.refresh();
541 	}
542 
adaptToEnablementChange(Highlighting highlighting, PropertyChangeEvent event)543 	private void adaptToEnablementChange(Highlighting highlighting, PropertyChangeEvent event) {
544 		Object value= event.getNewValue();
545 		boolean eventValue;
546 		if (value instanceof Boolean)
547 			eventValue= ((Boolean) value).booleanValue();
548 		else if (IPreferenceStore.TRUE.equals(value))
549 			eventValue= true;
550 		else
551 			eventValue= false;
552 		highlighting.setEnabled(eventValue);
553 	}
554 
adaptToTextForegroundChange(Highlighting highlighting, PropertyChangeEvent event)555 	private void adaptToTextForegroundChange(Highlighting highlighting, PropertyChangeEvent event) {
556 		RGB rgb= null;
557 
558 		Object value= event.getNewValue();
559 		if (value instanceof RGB)
560 			rgb= (RGB) value;
561 		else if (value instanceof String)
562 			rgb= StringConverter.asRGB((String) value);
563 
564 		if (rgb != null) {
565 
566 			String property= event.getProperty();
567 			Color color= fColorManager.getColor(property);
568 
569 			if ((color == null || !rgb.equals(color.getRGB())) && fColorManager instanceof IColorManagerExtension) {
570 				IColorManagerExtension ext= (IColorManagerExtension) fColorManager;
571 				ext.unbindColor(property);
572 				ext.bindColor(property, rgb);
573 				color= fColorManager.getColor(property);
574 			}
575 
576 			TextAttribute oldAttr= highlighting.getTextAttribute();
577 			highlighting.setTextAttribute(new TextAttribute(color, oldAttr.getBackground(), oldAttr.getStyle()));
578 		}
579 	}
580 
adaptToTextStyleChange(Highlighting highlighting, PropertyChangeEvent event, int styleAttribute)581 	private void adaptToTextStyleChange(Highlighting highlighting, PropertyChangeEvent event, int styleAttribute) {
582 		boolean eventValue= false;
583 		Object value= event.getNewValue();
584 		if (value instanceof Boolean)
585 			eventValue= ((Boolean) value).booleanValue();
586 		else if (IPreferenceStore.TRUE.equals(value))
587 			eventValue= true;
588 
589 		TextAttribute oldAttr= highlighting.getTextAttribute();
590 		boolean activeValue= (oldAttr.getStyle() & styleAttribute) == styleAttribute;
591 
592 		if (activeValue != eventValue)
593 			highlighting.setTextAttribute(new TextAttribute(oldAttr.getForeground(), oldAttr.getBackground(), eventValue ? oldAttr.getStyle() | styleAttribute : oldAttr.getStyle() & ~styleAttribute));
594 	}
595 
addColor(String colorKey)596 	private void addColor(String colorKey) {
597 		if (fColorManager != null && colorKey != null && fColorManager.getColor(colorKey) == null) {
598 			RGB rgb= PreferenceConverter.getColor(fPreferenceStore, colorKey);
599 			if (fColorManager instanceof IColorManagerExtension) {
600 				IColorManagerExtension ext= (IColorManagerExtension) fColorManager;
601 				ext.unbindColor(colorKey);
602 				ext.bindColor(colorKey, rgb);
603 			}
604 		}
605 	}
606 
removeColor(String colorKey)607 	private void removeColor(String colorKey) {
608 		if (fColorManager instanceof IColorManagerExtension)
609 			((IColorManagerExtension) fColorManager).unbindColor(colorKey);
610 	}
611 
612 	/**
613 	 * Returns this hightlighter's reconciler.
614 	 *
615 	 * @return the semantic highlighter reconciler or <code>null</code> if none
616 	 * @since 3.3
617 	 */
getReconciler()618 	public SemanticHighlightingReconciler getReconciler() {
619 		return fReconciler;
620 	}
621 }
622