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