1
/*******************************************************************************
2
* Copyright (c) 2000, 2008 IBM Corporation and others.
3
* All rights reserved. This program and the accompanying materials
4
* are made available under the terms of the Eclipse Public License v1.0
5
* which accompanies this distribution, and is available at
6
* http://www.eclipse.org/legal/epl-v10.html
9
* IBM Corporation - initial API and implementation
10
* Anton Leherbauer (Wind River Systems) - Adapted for CDT
11
*******************************************************************************/
13
package org.eclipse.cdt.internal.ui.editor;
15
import java.util.ArrayList;
16
import java.util.Arrays;
17
import java.util.List;
19
import org.eclipse.jface.text.BadLocationException;
20
import org.eclipse.jface.text.BadPositionCategoryException;
21
import org.eclipse.jface.text.DocumentEvent;
22
import org.eclipse.jface.text.IDocument;
23
import org.eclipse.jface.text.IDocumentListener;
24
import org.eclipse.jface.text.IPositionUpdater;
25
import org.eclipse.jface.text.IRegion;
26
import org.eclipse.jface.text.ISynchronizable;
27
import org.eclipse.jface.text.ITextInputListener;
28
import org.eclipse.jface.text.ITextPresentationListener;
29
import org.eclipse.jface.text.Position;
30
import org.eclipse.jface.text.Region;
31
import org.eclipse.jface.text.TextPresentation;
32
import org.eclipse.swt.custom.StyleRange;
34
import org.eclipse.cdt.ui.CUIPlugin;
36
import org.eclipse.cdt.internal.ui.editor.SemanticHighlightingManager.HighlightedPosition;
37
import org.eclipse.cdt.internal.ui.editor.SemanticHighlightingManager.HighlightingStyle;
38
import org.eclipse.cdt.internal.ui.text.CPresentationReconciler;
42
* Semantic highlighting presenter - UI thread implementation.
47
public class SemanticHighlightingPresenter implements ITextPresentationListener, ITextInputListener, IDocumentListener {
50
* Semantic highlighting position updater.
52
private class HighlightingPositionUpdater implements IPositionUpdater {
54
/** The position category. */
55
private final String fCategory;
58
* Creates a new updater for the given <code>category</code>.
60
* @param category the new category.
62
public HighlightingPositionUpdater(String category) {
67
* @see org.eclipse.jface.text.IPositionUpdater#update(org.eclipse.jface.text.DocumentEvent)
69
public void update(DocumentEvent event) {
71
int eventOffset= event.getOffset();
72
int eventOldLength= event.getLength();
73
int eventEnd= eventOffset + eventOldLength;
76
Position[] positions= event.getDocument().getPositions(fCategory);
78
for (int i= 0; i != positions.length; i++) {
80
HighlightedPosition position= (HighlightedPosition) positions[i];
82
// Also update deleted positions because they get deleted by the background thread and removed/invalidated only in the UI runnable
83
// if (position.isDeleted())
86
int offset= position.getOffset();
87
int length= position.getLength();
88
int end= offset + length;
90
if (offset > eventEnd)
91
updateWithPrecedingEvent(position, event);
92
else if (end < eventOffset)
93
updateWithSucceedingEvent(position, event);
94
else if (offset <= eventOffset && end >= eventEnd)
95
updateWithIncludedEvent(position, event);
96
else if (offset <= eventOffset)
97
updateWithOverEndEvent(position, event);
98
else if (end >= eventEnd)
99
updateWithOverStartEvent(position, event);
101
updateWithIncludingEvent(position, event);
103
} catch (BadPositionCategoryException e) {
109
* Update the given position with the given event. The event precedes the position.
111
* @param position The position
112
* @param event The event
114
private void updateWithPrecedingEvent(HighlightedPosition position, DocumentEvent event) {
115
String newText= event.getText();
116
int eventNewLength= newText != null ? newText.length() : 0;
117
int deltaLength= eventNewLength - event.getLength();
119
position.setOffset(position.getOffset() + deltaLength);
123
* Update the given position with the given event. The event succeeds the position.
125
* @param position The position
126
* @param event The event
128
private void updateWithSucceedingEvent(HighlightedPosition position, DocumentEvent event) {
132
* Update the given position with the given event. The event is included by the position.
134
* @param position The position
135
* @param event The event
137
private void updateWithIncludedEvent(HighlightedPosition position, DocumentEvent event) {
138
int eventOffset= event.getOffset();
139
String newText= event.getText();
141
newText= ""; //$NON-NLS-1$
142
int eventNewLength= newText.length();
144
int deltaLength= eventNewLength - event.getLength();
146
int offset= position.getOffset();
147
int length= position.getLength();
148
int end= offset + length;
150
int includedLength= 0;
151
while (includedLength < eventNewLength && Character.isJavaIdentifierPart(newText.charAt(includedLength)))
153
if (includedLength == eventNewLength)
154
position.setLength(length + deltaLength);
156
int newLeftLength= eventOffset - offset + includedLength;
158
int excludedLength= eventNewLength;
159
while (excludedLength > 0 && Character.isJavaIdentifierPart(newText.charAt(excludedLength - 1)))
161
int newRightOffset= eventOffset + excludedLength;
162
int newRightLength= end + deltaLength - newRightOffset;
164
if (newRightLength == 0) {
165
position.setLength(newLeftLength);
167
if (newLeftLength == 0) {
168
position.update(newRightOffset, newRightLength);
170
position.setLength(newLeftLength);
171
addPositionFromUI(newRightOffset, newRightLength, position.getHighlighting());
178
* Update the given position with the given event. The event overlaps with the end of the position.
180
* @param position The position
181
* @param event The event
183
private void updateWithOverEndEvent(HighlightedPosition position, DocumentEvent event) {
184
String newText= event.getText();
186
newText= ""; //$NON-NLS-1$
187
int eventNewLength= newText.length();
189
int includedLength= 0;
190
while (includedLength < eventNewLength && Character.isJavaIdentifierPart(newText.charAt(includedLength)))
192
position.setLength(event.getOffset() - position.getOffset() + includedLength);
196
* Update the given position with the given event. The event overlaps with the start of the position.
198
* @param position The position
199
* @param event The event
201
private void updateWithOverStartEvent(HighlightedPosition position, DocumentEvent event) {
202
int eventOffset= event.getOffset();
203
int eventEnd= eventOffset + event.getLength();
205
String newText= event.getText();
207
newText= ""; //$NON-NLS-1$
208
int eventNewLength= newText.length();
210
int excludedLength= eventNewLength;
211
while (excludedLength > 0 && Character.isJavaIdentifierPart(newText.charAt(excludedLength - 1)))
213
int deleted= eventEnd - position.getOffset();
214
int inserted= eventNewLength - excludedLength;
215
position.update(eventOffset + excludedLength, position.getLength() - deleted + inserted);
219
* Update the given position with the given event. The event includes the position.
221
* @param position The position
222
* @param event The event
224
private void updateWithIncludingEvent(HighlightedPosition position, DocumentEvent event) {
226
position.update(event.getOffset(), 0);
230
/** Position updater */
231
private IPositionUpdater fPositionUpdater= new HighlightingPositionUpdater(getPositionCategory());
233
/** The source viewer this semantic highlighting reconciler is installed on */
234
private CSourceViewer fSourceViewer;
235
/** The background presentation reconciler */
236
private CPresentationReconciler fPresentationReconciler;
238
/** UI's current highlighted positions - can contain <code>null</code> elements */
239
private List<HighlightedPosition> fPositions= new ArrayList<HighlightedPosition>();
240
/** UI position lock */
241
private Object fPositionLock= new Object();
243
/** <code>true</code> iff the current reconcile is canceled. */
244
private boolean fIsCanceled= false;
247
* Creates and returns a new highlighted position with the given offset, length and highlighting.
249
* NOTE: Also called from background thread.
252
* @param offset The offset
253
* @param length The length
254
* @param highlighting The highlighting
255
* @return The new highlighted position
257
public HighlightedPosition createHighlightedPosition(int offset, int length, HighlightingStyle highlighting) {
258
// TODO: reuse deleted positions
259
return new HighlightedPosition(offset, length, highlighting, fPositionUpdater);
263
* Adds all current positions to the given list.
265
* NOTE: Called from background thread.
268
* @param list The list
270
public void addAllPositions(List<? super HighlightedPosition> list) {
271
synchronized (fPositionLock) {
272
list.addAll(fPositions);
277
* Create a text presentation in the background.
279
* NOTE: Called from background thread.
282
* @param addedPositions the added positions
283
* @param removedPositions the removed positions
284
* @return the text presentation or <code>null</code>, if reconciliation should be canceled
286
public TextPresentation createPresentation(List<? extends Position> addedPositions, List<? extends Position> removedPositions) {
287
CSourceViewer sourceViewer= fSourceViewer;
288
CPresentationReconciler presentationReconciler= fPresentationReconciler;
289
if (sourceViewer == null || presentationReconciler == null)
295
IDocument document= sourceViewer.getDocument();
296
if (document == null)
299
int minStart= Integer.MAX_VALUE;
300
int maxEnd= Integer.MIN_VALUE;
301
for (int i= 0, n= removedPositions.size(); i < n; i++) {
302
Position position= removedPositions.get(i);
303
int offset= position.getOffset();
304
minStart= Math.min(minStart, offset);
305
maxEnd= Math.max(maxEnd, offset + position.getLength());
307
for (int i= 0, n= addedPositions.size(); i < n; i++) {
308
Position position= addedPositions.get(i);
309
int offset= position.getOffset();
310
minStart= Math.min(minStart, offset);
311
maxEnd= Math.max(maxEnd, offset + position.getLength());
314
if (minStart < maxEnd)
316
return presentationReconciler.createRepairDescription(new Region(minStart, maxEnd - minStart), document);
317
} catch (RuntimeException e) {
318
// Assume concurrent modification from UI thread
325
* Create a runnable for updating the presentation.
327
* NOTE: Called from background thread.
329
* @param textPresentation the text presentation
330
* @param addedPositions the added positions
331
* @param removedPositions the removed positions
332
* @return the runnable or <code>null</code>, if reconciliation should be canceled
334
public Runnable createUpdateRunnable(final TextPresentation textPresentation, List<HighlightedPosition> addedPositions, List<HighlightedPosition> removedPositions) {
335
if (fSourceViewer == null || textPresentation == null)
338
// TODO: do clustering of positions and post multiple fast runnables
339
final HighlightedPosition[] added= addedPositions.toArray(new HighlightedPosition[addedPositions.size()]);
340
final HighlightedPosition[] removed= removedPositions.toArray(new HighlightedPosition[removedPositions.size()]);
345
Runnable runnable= new Runnable() {
347
updatePresentation(textPresentation, added, removed);
354
* Invalidate the presentation of the positions based on the given added positions and the existing deleted positions.
355
* Also unregisters the deleted positions from the document and patches the positions of this presenter.
357
* NOTE: Indirectly called from background thread by UI runnable.
359
* @param textPresentation the text presentation or <code>null</code>, if the presentation should computed in the UI thread
360
* @param addedPositions the added positions
361
* @param removedPositions the removed positions
363
public void updatePresentation(TextPresentation textPresentation, HighlightedPosition[] addedPositions, HighlightedPosition[] removedPositions) {
364
if (fSourceViewer == null)
367
// checkOrdering("added positions: ", Arrays.asList(addedPositions)); //$NON-NLS-1$
368
// checkOrdering("removed positions: ", Arrays.asList(removedPositions)); //$NON-NLS-1$
369
// checkOrdering("old positions: ", fPositions); //$NON-NLS-1$
371
// TODO: double-check consistency with document.getPositions(...)
372
// TODO: reuse removed positions
376
IDocument document= fSourceViewer.getDocument();
377
if (document == null)
380
String positionCategory= getPositionCategory();
382
List<HighlightedPosition> removedPositionsList= Arrays.asList(removedPositions);
385
synchronized (fPositionLock) {
386
List<HighlightedPosition> oldPositions= fPositions;
387
int newSize= Math.max(fPositions.size() + addedPositions.length - removedPositions.length, 10);
390
* The following loop is a kind of merge sort: it merges two List<Position>, each
391
* sorted by position.offset, into one new list. The first of the two is the
392
* previous list of positions (oldPositions), from which any deleted positions get
393
* removed on the fly. The second of two is the list of added positions. The result
394
* is stored in newPositions.
396
List<HighlightedPosition> newPositions= new ArrayList<HighlightedPosition>(newSize);
397
HighlightedPosition position= null;
398
HighlightedPosition addedPosition= null;
399
for (int i= 0, j= 0, n= oldPositions.size(), m= addedPositions.length; i < n || position != null || j < m || addedPosition != null;) {
400
// loop variant: i + j < old(i + j)
402
// a) find the next non-deleted Position from the old list
403
while (position == null && i < n) {
404
position= oldPositions.get(i++);
405
if (position.isDeleted() || contain(removedPositionsList, position)) {
406
document.removePosition(positionCategory, position);
411
// b) find the next Position from the added list
412
if (addedPosition == null && j < m) {
413
addedPosition= addedPositions[j++];
414
document.addPosition(positionCategory, addedPosition);
417
// c) merge: add the next of position/addedPosition with the lower offset
418
if (position != null) {
419
if (addedPosition != null)
420
if (position.getOffset() <= addedPosition.getOffset()) {
421
newPositions.add(position);
424
newPositions.add(addedPosition);
428
newPositions.add(position);
431
} else if (addedPosition != null) {
432
newPositions.add(addedPosition);
436
fPositions= newPositions;
438
} catch (BadPositionCategoryException e) {
441
} catch (BadLocationException e) {
445
// checkOrdering("new positions: ", fPositions); //$NON-NLS-1$
447
if (textPresentation != null)
448
fSourceViewer.changeTextPresentation(textPresentation, false);
450
fSourceViewer.invalidateTextPresentation();
453
// private void checkOrdering(String s, List positions) {
454
// Position previous= null;
455
// for (int i= 0, n= positions.size(); i < n; i++) {
456
// Position current= (Position) positions.get(i);
457
// if (previous != null && previous.getOffset() + previous.getLength() > current.getOffset())
463
* Returns <code>true</code> iff the positions contain the position.
464
* @param positions the positions, must be ordered by offset but may overlap
465
* @param position the position
466
* @return <code>true</code> iff the positions contain the position
468
private boolean contain(List<? extends Position> positions, Position position) {
469
return indexOf(positions, position) != -1;
473
* Returns index of the position in the positions, <code>-1</code> if not found.
474
* @param positions the positions, must be ordered by offset but may overlap
475
* @param position the position
478
private int indexOf(List<? extends Position> positions, Position position) {
479
int index= computeIndexAtOffset(positions, position.getOffset());
480
int size= positions.size();
481
while (index < size) {
482
if (positions.get(index) == position)
490
* Insert the given position in <code>fPositions</code>, s.t. the offsets remain in linear order.
492
* @param position The position for insertion
494
private void insertPosition(HighlightedPosition position) {
495
int i= computeIndexAfterOffset(fPositions, position.getOffset());
496
fPositions.add(i, position);
500
* Returns the index of the first position with an offset greater than the given offset.
502
* @param positions the positions, must be ordered by offset and must not overlap
503
* @param offset the offset
504
* @return the index of the last position with an offset greater than the given offset
506
private int computeIndexAfterOffset(List<? extends Position> positions, int offset) {
508
int j= positions.size();
511
Position position= positions.get(k);
512
if (position.getOffset() > offset)
521
* Returns the index of the first position with an offset equal or greater than the given offset.
523
* @param positions the positions, must be ordered by offset and must not overlap
524
* @param offset the offset
525
* @return the index of the last position with an offset equal or greater than the given offset
527
private int computeIndexAtOffset(List<? extends Position> positions, int offset) {
529
int j= positions.size();
532
Position position= positions.get(k);
533
if (position.getOffset() >= offset)
542
* @see org.eclipse.jface.text.ITextPresentationListener#applyTextPresentation(org.eclipse.jface.text.TextPresentation)
544
public void applyTextPresentation(TextPresentation textPresentation) {
545
IRegion region= textPresentation.getExtent();
546
int i= computeIndexAtOffset(fPositions, region.getOffset()), n= computeIndexAtOffset(fPositions, region.getOffset() + region.getLength());
548
List<StyleRange> ranges= new ArrayList<StyleRange>(n - i);
550
HighlightedPosition position= fPositions.get(i);
551
if (!position.isDeleted())
552
ranges.add(position.createStyleRange());
554
StyleRange[] array= new StyleRange[ranges.size()];
555
array= ranges.toArray(array);
556
textPresentation.replaceStyleRanges(array);
559
HighlightedPosition position= fPositions.get(i);
560
if (!position.isDeleted())
561
textPresentation.replaceStyleRange(position.createStyleRange());
567
* @see org.eclipse.jface.text.ITextInputListener#inputDocumentAboutToBeChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument)
569
public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) {
571
releaseDocument(oldInput);
576
* @see org.eclipse.jface.text.ITextInputListener#inputDocumentChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument)
578
public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
579
manageDocument(newInput);
583
* @see org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
585
public void documentAboutToBeChanged(DocumentEvent event) {
590
* @see org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse.jface.text.DocumentEvent)
592
public void documentChanged(DocumentEvent event) {
596
* @return Returns <code>true</code> iff the current reconcile is canceled.
598
* NOTE: Also called from background thread.
601
public boolean isCanceled() {
602
IDocument document= fSourceViewer != null ? fSourceViewer.getDocument() : null;
603
if (document == null)
606
synchronized (getLockObject(document)) {
612
* Set whether or not the current reconcile is canceled.
614
* @param isCanceled <code>true</code> iff the current reconcile is canceled
616
public void setCanceled(boolean isCanceled) {
617
IDocument document= fSourceViewer != null ? fSourceViewer.getDocument() : null;
618
if (document == null) {
619
fIsCanceled= isCanceled;
623
synchronized (getLockObject(document)) {
624
fIsCanceled= isCanceled;
629
* @param document the document
630
* @return the document's lock object
632
private Object getLockObject(IDocument document) {
633
if (document instanceof ISynchronizable) {
634
Object lock= ((ISynchronizable)document).getLockObject();
642
* Install this presenter on the given source viewer and background presentation
645
* @param sourceViewer the source viewer
646
* @param backgroundPresentationReconciler the background presentation reconciler,
647
* can be <code>null</code>, in that case {@link SemanticHighlightingPresenter#createPresentation(List, List)}
648
* should not be called
650
public void install(CSourceViewer sourceViewer, CPresentationReconciler backgroundPresentationReconciler) {
651
fSourceViewer= sourceViewer;
652
fPresentationReconciler= backgroundPresentationReconciler;
654
fSourceViewer.prependTextPresentationListener(this);
655
fSourceViewer.addTextInputListener(this);
656
manageDocument(fSourceViewer.getDocument());
660
* Uninstall this presenter.
662
public void uninstall() {
665
if (fSourceViewer != null) {
666
fSourceViewer.removeTextPresentationListener(this);
667
releaseDocument(fSourceViewer.getDocument());
668
invalidateTextPresentation();
671
fSourceViewer.removeTextInputListener(this);
677
* Invalidate text presentation of positions with the given highlighting.
679
* @param highlighting The highlighting
681
public void highlightingStyleChanged(HighlightingStyle highlighting) {
682
for (int i= 0, n= fPositions.size(); i < n; i++) {
683
HighlightedPosition position= fPositions.get(i);
684
if (position.getHighlighting() == highlighting)
685
fSourceViewer.invalidateTextPresentation(position.getOffset(), position.getLength());
690
* Invalidate text presentation of all positions.
692
private void invalidateTextPresentation() {
693
if (fPositions.size() > 1000) {
694
fSourceViewer.invalidateTextPresentation();
696
for (int i= 0, n= fPositions.size(); i < n; i++) {
697
Position position= fPositions.get(i);
698
fSourceViewer.invalidateTextPresentation(position.getOffset(), position.getLength());
704
* Add a position with the given range and highlighting unconditionally, only from UI thread.
705
* The position will also be registered on the document. The text presentation is not invalidated.
707
* @param offset The range offset
708
* @param length The range length
709
* @param highlighting
711
private void addPositionFromUI(int offset, int length, HighlightingStyle highlighting) {
712
HighlightedPosition position= createHighlightedPosition(offset, length, highlighting);
713
synchronized (fPositionLock) {
714
insertPosition(position);
717
IDocument document= fSourceViewer.getDocument();
718
if (document == null)
720
String positionCategory= getPositionCategory();
722
document.addPosition(positionCategory, position);
723
} catch (BadLocationException e) {
726
} catch (BadPositionCategoryException e) {
733
* Reset to initial state.
735
private void resetState() {
736
synchronized (fPositionLock) {
742
* Start managing the given document.
744
* @param document The document
746
private void manageDocument(IDocument document) {
747
if (document != null) {
748
document.addPositionCategory(getPositionCategory());
749
document.addPositionUpdater(fPositionUpdater);
750
document.addDocumentListener(this);
755
* Stop managing the given document.
757
* @param document The document
759
private void releaseDocument(IDocument document) {
760
if (document != null) {
761
document.removeDocumentListener(this);
762
document.removePositionUpdater(fPositionUpdater);
764
document.removePositionCategory(getPositionCategory());
765
} catch (BadPositionCategoryException e) {
773
* @return The semantic reconciler position's category.
775
private String getPositionCategory() {