Skip to content

Commit eaae0b5

Browse files
committed
Display quick search dialog result in SourceViewer with line numbers
Fixes #2010
1 parent 0344b2b commit eaae0b5

File tree

1 file changed

+221
-60
lines changed

1 file changed

+221
-60
lines changed

bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java

Lines changed: 221 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,21 @@
4444
import org.eclipse.jface.dialogs.IDialogConstants;
4545
import org.eclipse.jface.dialogs.IDialogSettings;
4646
import org.eclipse.jface.layout.GridDataFactory;
47+
import org.eclipse.jface.preference.IPreferenceStore;
48+
import org.eclipse.jface.preference.PreferenceConverter;
4749
import org.eclipse.jface.resource.JFaceResources;
4850
import org.eclipse.jface.text.BadLocationException;
51+
import org.eclipse.jface.text.CursorLinePainter;
4952
import org.eclipse.jface.text.IDocument;
53+
import org.eclipse.jface.text.IPaintPositionManager;
54+
import org.eclipse.jface.text.IPainter;
5055
import org.eclipse.jface.text.IRegion;
56+
import org.eclipse.jface.text.source.CompositeRuler;
57+
import org.eclipse.jface.text.source.ISharedTextColors;
58+
import org.eclipse.jface.text.source.LineNumberRulerColumn;
59+
import org.eclipse.jface.text.source.SourceViewer;
60+
import org.eclipse.jface.util.IPropertyChangeListener;
61+
import org.eclipse.jface.util.PropertyChangeEvent;
5162
import org.eclipse.jface.viewers.ILazyContentProvider;
5263
import org.eclipse.jface.viewers.IStructuredContentProvider;
5364
import org.eclipse.jface.viewers.IStructuredSelection;
@@ -65,6 +76,9 @@
6576
import org.eclipse.swt.accessibility.ACC;
6677
import org.eclipse.swt.accessibility.AccessibleAdapter;
6778
import org.eclipse.swt.accessibility.AccessibleEvent;
79+
import org.eclipse.swt.custom.LineBackgroundEvent;
80+
import org.eclipse.swt.custom.LineBackgroundListener;
81+
import org.eclipse.swt.custom.ST;
6882
import org.eclipse.swt.custom.SashForm;
6983
import org.eclipse.swt.custom.StyleRange;
7084
import org.eclipse.swt.custom.StyledText;
@@ -79,10 +93,13 @@
7993
import org.eclipse.swt.graphics.Color;
8094
import org.eclipse.swt.graphics.Image;
8195
import org.eclipse.swt.graphics.Point;
96+
import org.eclipse.swt.graphics.RGB;
8297
import org.eclipse.swt.graphics.Rectangle;
98+
import org.eclipse.swt.layout.FillLayout;
8399
import org.eclipse.swt.layout.GridData;
84100
import org.eclipse.swt.layout.GridLayout;
85101
import org.eclipse.swt.widgets.Button;
102+
import org.eclipse.swt.widgets.Canvas;
86103
import org.eclipse.swt.widgets.Composite;
87104
import org.eclipse.swt.widgets.Control;
88105
import org.eclipse.swt.widgets.Display;
@@ -108,12 +125,16 @@
108125
import org.eclipse.ui.PartInitException;
109126
import org.eclipse.ui.PlatformUI;
110127
import org.eclipse.ui.dialogs.SelectionStatusDialog;
128+
import org.eclipse.ui.editors.text.EditorsUI;
111129
import org.eclipse.ui.handlers.IHandlerActivation;
112130
import org.eclipse.ui.handlers.IHandlerService;
113131
import org.eclipse.ui.internal.IWorkbenchGraphicConstants;
114132
import org.eclipse.ui.internal.WorkbenchImages;
115133
import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
116134
import org.eclipse.ui.progress.UIJob;
135+
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
136+
import org.eclipse.ui.texteditor.AbstractTextEditor;
137+
import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
117138
import org.osgi.framework.FrameworkUtil;
118139

119140
/**
@@ -352,7 +373,11 @@ public void update(ViewerCell cell) {
352373

353374
private QuickTextSearcher searcher;
354375

355-
private StyledText details;
376+
private SourceViewer viewer;
377+
private LineNumberRulerColumn lineNumberColumn;
378+
private SourceViewerDecorationSupport sourceViewerDecorationSupport;
379+
private FixedLinePainter targetLinePainter;
380+
private final IPropertyChangeListener preferenceChangeListener = this::handlePropertyChangeEvent;
356381

357382
private DocumentFetcher documents;
358383

@@ -391,6 +416,7 @@ public QuickSearchDialog(IWorkbenchWindow window) {
391416
MAX_LINE_LEN = QuickSearchActivator.getDefault().getPreferences().getMaxLineLen();
392417
MAX_RESULTS = QuickSearchActivator.getDefault().getPreferences().getMaxResults();
393418
progressJob.setSystem(true);
419+
EditorsUI.getPreferenceStore().addPropertyChangeListener(preferenceChangeListener);
394420
}
395421

396422
/*
@@ -915,31 +941,123 @@ protected void dispose() {
915941
blankImage.dispose();
916942
blankImage = null;
917943
}
944+
EditorsUI.getPreferenceStore().removePropertyChangeListener(preferenceChangeListener);
918945
}
919946

920947
private void createDetailsArea(Composite parent) {
921-
details = new StyledText(parent, SWT.MULTI+SWT.READ_ONLY+SWT.BORDER+SWT.H_SCROLL+SWT.V_SCROLL);
922-
details.setFont(JFaceResources.getFont(TEXT_FONT));
948+
var viewerParent = new Canvas(parent, SWT.BORDER);
949+
viewerParent.setLayout(new FillLayout());
950+
951+
viewer = new SourceViewer(viewerParent, new CompositeRuler(), SWT.H_SCROLL | SWT.V_SCROLL | SWT.READ_ONLY);
952+
viewer.getTextWidget().setFont(JFaceResources.getFont(TEXT_FONT));
953+
954+
lineNumberColumn = new LineNumberRulerColumn();
955+
viewer.addVerticalRulerColumn(lineNumberColumn);
956+
setColors(false);
957+
createLinesHighlightingDecorations();
923958

924959
list.addSelectionChangedListener(event -> refreshDetails());
925-
details.addControlListener(new ControlAdapter() {
960+
961+
viewer.getTextWidget().addControlListener(new ControlAdapter() {
926962
@Override
927963
public void controlResized(ControlEvent e) {
928964
refreshDetails();
929965
}
930966
});
931967
}
932968

969+
private void setColors(boolean refresh) {
970+
RGB background = null;
971+
RGB foreground = null;
972+
var textWidget = viewer.getTextWidget();
973+
var preferenceStore = EditorsUI.getPreferenceStore();
974+
ISharedTextColors sharedColors = EditorsUI.getSharedTextColors();
975+
976+
var isUsingSystemBackground = preferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT);
977+
if (!isUsingSystemBackground) {
978+
background = getColorFromStore(preferenceStore, AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND);
979+
}
980+
if (background != null) {
981+
var color = sharedColors.getColor(background);
982+
textWidget.setBackground(color);
983+
lineNumberColumn.setBackground(color);
984+
} else {
985+
textWidget.setBackground(null);
986+
lineNumberColumn.setBackground(null);
987+
}
988+
989+
var isUsingSystemForeground = preferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT);
990+
if (!isUsingSystemForeground) {
991+
foreground = getColorFromStore(preferenceStore, AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND);
992+
}
993+
if (foreground != null) {
994+
textWidget.setForeground(sharedColors.getColor(foreground));
995+
} else {
996+
textWidget.setForeground(null);
997+
}
998+
999+
var lineNumbersColor = getColorFromStore(EditorsUI.getPreferenceStore(), AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER_COLOR);
1000+
if (lineNumbersColor == null) {
1001+
lineNumbersColor = new RGB(0, 0, 0);
1002+
}
1003+
lineNumberColumn.setForeground(sharedColors.getColor(lineNumbersColor));
1004+
1005+
if (refresh) {
1006+
textWidget.redraw();
1007+
lineNumberColumn.redraw();
1008+
}
1009+
}
1010+
1011+
1012+
private void createLinesHighlightingDecorations() {
1013+
sourceViewerDecorationSupport = new SourceViewerDecorationSupport(viewer, null, null, EditorsUI.getSharedTextColors());
1014+
sourceViewerDecorationSupport.setCursorLinePainterPreferenceKeys(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE_COLOR);
1015+
sourceViewerDecorationSupport.install(EditorsUI.getPreferenceStore());
1016+
targetLinePainter = new FixedLinePainter();
1017+
viewer.addPainter(targetLinePainter);
1018+
viewer.getTextWidget().addLineBackgroundListener(targetLinePainter);
1019+
}
1020+
1021+
private void handlePropertyChangeEvent(PropertyChangeEvent event) {
1022+
if (viewer == null) {
1023+
return;
1024+
}
1025+
var prop = event.getProperty();
1026+
if (prop.equals(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER_COLOR)
1027+
|| prop.equals(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT)
1028+
|| prop.equals(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND)
1029+
|| prop.equals(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT)
1030+
|| prop.equals(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND)) {
1031+
setColors(true);
1032+
}
1033+
if (prop.equals(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE)
1034+
|| prop.equals(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE_COLOR)) {
1035+
targetLinePainter.highlightColor = null;
1036+
targetLinePainter.cursorLinePainter = null;
1037+
viewer.getTextWidget().redraw();
1038+
}
1039+
}
1040+
1041+
private RGB getColorFromStore(IPreferenceStore store, String key) {
1042+
RGB rgb = null;
1043+
if (store.contains(key)) {
1044+
if (store.isDefault(key)) {
1045+
rgb = PreferenceConverter.getDefaultColor(store, key);
1046+
} else {
1047+
rgb = PreferenceConverter.getColor(store, key);
1048+
}
1049+
}
1050+
return rgb;
1051+
}
9331052

934-
// Dumber version just using the a 'raw' StyledText widget.
9351053
private void refreshDetails() {
936-
if (details!=null && list!=null && !list.getTable().isDisposed()) {
1054+
if (viewer!=null && list!=null && !list.getTable().isDisposed()) {
9371055
if (documents==null) {
9381056
documents = new DocumentFetcher();
9391057
}
9401058
IStructuredSelection sel = (IStructuredSelection) list.getSelection();
9411059
if (sel==null || sel.isEmpty()) {
942-
details.setText(EMPTY_STRING);
1060+
viewer.setDocument(null);
9431061
} else {
9441062
//Not empty selection
9451063
final int context = 100; // number of lines before and after match to include in preview
@@ -952,35 +1070,56 @@ private void refreshDetails() {
9521070
int line = item.getLineNumber()-1; //in document lines are 0 based. In search 1 based.
9531071
int contextStartLine = Math.max(line-(numLines-1)/2 - context, 0);
9541072
int start = document.getLineOffset(contextStartLine);
1073+
int displayedEndLine = line + numLines/2;
9551074
int end = document.getLength();
956-
try {
957-
IRegion lineInfo = document.getLineInformation(line + numLines/2 + context);
958-
end = lineInfo.getOffset() + lineInfo.getLength();
959-
} catch (BadLocationException e) {
960-
//Presumably line number is past the end of document.
961-
//ignore.
1075+
if (displayedEndLine + context <= document.getNumberOfLines()) {
1076+
try {
1077+
IRegion lineInfo = document.getLineInformation(displayedEndLine + context);
1078+
end = lineInfo.getOffset() + lineInfo.getLength();
1079+
} catch (BadLocationException e) {
1080+
//Presumably line number is past the end of document.
1081+
//ignore.
1082+
}
9621083
}
1084+
int contextLenght = end-start;
1085+
1086+
viewer.setDocument(document);
1087+
viewer.setVisibleRegion(start, contextLenght);
1088+
1089+
targetLinePainter.setTargetLineOffset(item.getOffset() - start);
1090+
1091+
// center target line in the displayed area
1092+
IRegion rangeEndLineInfo = document.getLineInformation(Math.min(displayedEndLine, document.getNumberOfLines() - 1));
1093+
int rangeStart = document.getLineOffset(Math.max(line - numLines/2, 0));
1094+
int rangeEnd = rangeEndLineInfo.getOffset() + rangeEndLineInfo.getLength();
1095+
viewer.revealRange(rangeStart, rangeEnd - rangeStart);
1096+
1097+
var targetLineFirstMatch = getQuery().findFirst(document.get(item.getOffset(), contextLenght - (item.getOffset() - start)));
1098+
int targetLineFirstMatchStart = item.getOffset() + targetLineFirstMatch.getOffset();
1099+
// sets caret position (also highlights that line)
1100+
viewer.setSelectedRange(targetLineFirstMatchStart, 0);
1101+
// does horizontal scrolling if necessary to reveal 1st occurrence in target line
1102+
viewer.revealRange(targetLineFirstMatchStart, targetLineFirstMatch.getLength());
9631103

964-
StyledString styledString = highlightMatches(document.get(start, end-start));
965-
details.setText(styledString.getString());
966-
details.setStyleRanges(styledString.getStyleRanges());
967-
details.setTopIndex(Math.max(line - contextStartLine - numLines/2, 0));
1104+
StyledString styledString = highlightMatches(document.get(start, contextLenght));
1105+
viewer.getTextWidget().setStyleRanges(styledString.getStyleRanges());
9681106
return;
9691107
} catch (BadLocationException e) {
9701108
}
9711109
}
9721110
}
9731111
}
9741112
//empty selection or some error:
975-
details.setText(EMPTY_STRING);
1113+
viewer.setDocument(null);
9761114
}
9771115
}
9781116

9791117
/**
9801118
* Computes how many lines of text can be displayed in the details section.
9811119
*/
9821120
private int computeLines() {
983-
if (details!=null && !details.isDisposed()) {
1121+
StyledText details;
1122+
if (viewer!=null && !(details = viewer.getTextWidget()).isDisposed()) {
9841123
int lineHeight = details.getLineHeight();
9851124
int areaHeight = details.getClientArea().height;
9861125
return (areaHeight + lineHeight - 1) / lineHeight;
@@ -1003,47 +1142,6 @@ private StyledString highlightMatches(String visibleText) {
10031142
return styledText;
10041143
}
10051144

1006-
// Version using sourceviewer
1007-
// private void refreshDetails() {
1008-
// if (details!=null && list!=null && !list.getTable().isDisposed()) {
1009-
// if (documents==null) {
1010-
// documents = new DocumentFetcher();
1011-
// }
1012-
// IStructuredSelection sel = (IStructuredSelection) list.getSelection();
1013-
// if (sel!=null && !sel.isEmpty()) {
1014-
// //Not empty selection
1015-
// LineItem item = (LineItem) sel.getFirstElement();
1016-
// IDocument document = documents.getDocument(item.getFile());
1017-
// try {
1018-
// int line = item.getLineNumber()-1; //in document lines are 0 based. In search 1 based.
1019-
// int start = document.getLineOffset(Math.max(line-2, 0));
1020-
// int end = document.getLength();
1021-
// try {
1022-
// end = document.getLineOffset(line+3);
1023-
// } catch (BadLocationException e) {
1024-
// //Presumably line number is past the end of document.
1025-
// //ignore.
1026-
// }
1027-
// details.setDocument(document, start, end-start);
1028-
//
1029-
// String visibleText = document.get(start, end-start);
1030-
// List<TextRange> matches = getQuery().findAll(visibleText);
1031-
// Region visibleRegion = new Region(start, end-start);
1032-
// TextPresentation presentation = new TextPresentation(visibleRegion, 20);
1033-
// presentation.setDefaultStyleRange(new StyleRange(0, document.getLength(), null, null));
1034-
// for (TextRange m : matches) {
1035-
// presentation.addStyleRange(new StyleRange(m.start+start, m.len, null, YELLOW));
1036-
// }
1037-
// details.changeTextPresentation(presentation, true);
1038-
//
1039-
// return;
1040-
// } catch (BadLocationException e) {
1041-
// }
1042-
// }
1043-
// details.setDocument(null);
1044-
// }
1045-
// }
1046-
10471145
/**
10481146
* Handle selection in the items list by updating labels of selected and
10491147
* unselected items and refresh the details field using the selection.
@@ -1440,4 +1538,67 @@ public QuickTextQuery getQuery() {
14401538
return searcher.getQuery();
14411539
}
14421540

1541+
/**
1542+
* A painter that does 'current line' highlighting (background color) but for single fixed line.
1543+
* <br><br>
1544+
* This class piggybacks on {@link CursorLinePainter} instance already added to viewer's widget, which knows what color
1545+
* to use and does all the necessary repainting. This class only forces the background color to be used also for one
1546+
* fixed line in addition to current line = line where caret is currently located (done by <code>CursorLinePainter</code>).
1547+
* Background color for this fixed line never changes and so <code>CursorLinePainter</code>'s repainting code, although not
1548+
* knowing about this fixed line at all, produces correct visual results - target line always highlighted.
1549+
*
1550+
* @see CursorLinePainter
1551+
*/
1552+
private class FixedLinePainter implements IPainter, LineBackgroundListener {
1553+
1554+
private CursorLinePainter cursorLinePainter;
1555+
private int lineOffset;
1556+
private Color highlightColor;
1557+
1558+
public void setTargetLineOffset(int lineOffset) {
1559+
this.lineOffset = lineOffset;
1560+
}
1561+
1562+
// CursorLinePainter adds itself as line LineBackgroundListener lazily
1563+
private boolean cursorLinePainterInstalled() {
1564+
if (cursorLinePainter == null) {
1565+
cursorLinePainter = viewer.getTextWidget().getTypedListeners(ST.LineGetBackground, CursorLinePainter.class).findFirst().orElse(null);
1566+
return cursorLinePainter != null;
1567+
}
1568+
return true;
1569+
}
1570+
1571+
@Override
1572+
public void lineGetBackground(LineBackgroundEvent event) {
1573+
if (cursorLinePainterInstalled()) {
1574+
cursorLinePainter.lineGetBackground(event);
1575+
if (event.lineBackground != null) {
1576+
// piggyback on CursorLinePainter knowing proper color
1577+
highlightColor = event.lineBackground;
1578+
} else if (lineOffset == event.lineOffset) { // target line
1579+
event.lineBackground = highlightColor;
1580+
}
1581+
}
1582+
}
1583+
1584+
@Override
1585+
public void dispose() {
1586+
}
1587+
1588+
@Override
1589+
public void paint(int reason) {
1590+
// no custom painting here, cursorLinePainter (also being registered as IPainter) does all the work
1591+
// according to background color set in lineGetBackground()
1592+
}
1593+
1594+
@Override
1595+
public void deactivate(boolean redraw) {
1596+
}
1597+
1598+
@Override
1599+
public void setPositionManager(IPaintPositionManager manager) {
1600+
}
1601+
1602+
}
1603+
14431604
}

0 commit comments

Comments
 (0)