Skip to content

Commit c0ecd50

Browse files
RedeemerSKmickaelistria
authored andcommitted
Display quick search dialog result in SourceViewer with line numbers
Fixes eclipse-platform#2010
1 parent 0bc52a9 commit c0ecd50

File tree

1 file changed

+185
-60
lines changed

1 file changed

+185
-60
lines changed

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

+185-60
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@
2121
package org.eclipse.text.quicksearch.internal.ui;
2222

2323
import static org.eclipse.jface.resource.JFaceResources.TEXT_FONT;
24+
import static org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE;
25+
import static org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE_COLOR;
26+
import static org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER_COLOR;
27+
import static org.eclipse.ui.texteditor.AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND;
28+
import static org.eclipse.ui.texteditor.AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT;
29+
import static org.eclipse.ui.texteditor.AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND;
30+
import static org.eclipse.ui.texteditor.AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT;
2431

2532
import java.util.ArrayList;
2633
import java.util.Arrays;
@@ -46,10 +53,18 @@
4653
import org.eclipse.jface.dialogs.IDialogConstants;
4754
import org.eclipse.jface.dialogs.IDialogSettings;
4855
import org.eclipse.jface.layout.GridDataFactory;
56+
import org.eclipse.jface.preference.PreferenceConverter;
4957
import org.eclipse.jface.resource.JFaceResources;
5058
import org.eclipse.jface.text.BadLocationException;
59+
import org.eclipse.jface.text.CursorLinePainter;
5160
import org.eclipse.jface.text.IDocument;
5261
import org.eclipse.jface.text.IRegion;
62+
import org.eclipse.jface.text.source.CompositeRuler;
63+
import org.eclipse.jface.text.source.ISharedTextColors;
64+
import org.eclipse.jface.text.source.LineNumberRulerColumn;
65+
import org.eclipse.jface.text.source.SourceViewer;
66+
import org.eclipse.jface.util.IPropertyChangeListener;
67+
import org.eclipse.jface.util.PropertyChangeEvent;
5368
import org.eclipse.jface.viewers.ILazyContentProvider;
5469
import org.eclipse.jface.viewers.IStructuredContentProvider;
5570
import org.eclipse.jface.viewers.IStructuredSelection;
@@ -67,6 +82,8 @@
6782
import org.eclipse.swt.accessibility.ACC;
6883
import org.eclipse.swt.accessibility.AccessibleAdapter;
6984
import org.eclipse.swt.accessibility.AccessibleEvent;
85+
import org.eclipse.swt.custom.LineBackgroundEvent;
86+
import org.eclipse.swt.custom.LineBackgroundListener;
7087
import org.eclipse.swt.custom.SashForm;
7188
import org.eclipse.swt.custom.StyleRange;
7289
import org.eclipse.swt.custom.StyledText;
@@ -81,10 +98,13 @@
8198
import org.eclipse.swt.graphics.Color;
8299
import org.eclipse.swt.graphics.Image;
83100
import org.eclipse.swt.graphics.Point;
101+
import org.eclipse.swt.graphics.RGB;
84102
import org.eclipse.swt.graphics.Rectangle;
103+
import org.eclipse.swt.layout.FillLayout;
85104
import org.eclipse.swt.layout.GridData;
86105
import org.eclipse.swt.layout.GridLayout;
87106
import org.eclipse.swt.widgets.Button;
107+
import org.eclipse.swt.widgets.Canvas;
88108
import org.eclipse.swt.widgets.Combo;
89109
import org.eclipse.swt.widgets.Composite;
90110
import org.eclipse.swt.widgets.Control;
@@ -111,12 +131,14 @@
111131
import org.eclipse.ui.PartInitException;
112132
import org.eclipse.ui.PlatformUI;
113133
import org.eclipse.ui.dialogs.SelectionStatusDialog;
134+
import org.eclipse.ui.editors.text.EditorsUI;
114135
import org.eclipse.ui.handlers.IHandlerActivation;
115136
import org.eclipse.ui.handlers.IHandlerService;
116137
import org.eclipse.ui.internal.IWorkbenchGraphicConstants;
117138
import org.eclipse.ui.internal.WorkbenchImages;
118139
import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
119140
import org.eclipse.ui.progress.UIJob;
141+
import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
120142
import org.osgi.framework.FrameworkUtil;
121143

122144
/**
@@ -359,7 +381,10 @@ public void update(ViewerCell cell) {
359381

360382
private QuickTextSearcher searcher;
361383

362-
private StyledText details;
384+
private SourceViewer viewer;
385+
private LineNumberRulerColumn lineNumberColumn;
386+
private FixedLineHighlighter targetLineHighlighter;
387+
private final IPropertyChangeListener preferenceChangeListener = this::handlePropertyChangeEvent;
363388

364389
private DocumentFetcher documents;
365390

@@ -398,6 +423,7 @@ public QuickSearchDialog(IWorkbenchWindow window) {
398423
MAX_LINE_LEN = QuickSearchActivator.getDefault().getPreferences().getMaxLineLen();
399424
MAX_RESULTS = QuickSearchActivator.getDefault().getPreferences().getMaxResults();
400425
progressJob.setSystem(true);
426+
EditorsUI.getPreferenceStore().addPropertyChangeListener(preferenceChangeListener);
401427
}
402428

403429
/*
@@ -946,31 +972,124 @@ protected void dispose() {
946972
blankImage.dispose();
947973
blankImage = null;
948974
}
975+
EditorsUI.getPreferenceStore().removePropertyChangeListener(preferenceChangeListener);
949976
}
950977

951978
private void createDetailsArea(Composite parent) {
952-
details = new StyledText(parent, SWT.MULTI+SWT.READ_ONLY+SWT.BORDER+SWT.H_SCROLL+SWT.V_SCROLL);
953-
details.setFont(JFaceResources.getFont(TEXT_FONT));
979+
var viewerParent = new Canvas(parent, SWT.BORDER);
980+
viewerParent.setLayout(new FillLayout());
981+
982+
viewer = new SourceViewer(viewerParent, new CompositeRuler(), SWT.H_SCROLL | SWT.V_SCROLL | SWT.READ_ONLY);
983+
viewer.getTextWidget().setFont(JFaceResources.getFont(TEXT_FONT));
984+
createViewerDecorations();
954985

955986
list.addSelectionChangedListener(event -> refreshDetails());
956-
details.addControlListener(new ControlAdapter() {
987+
988+
viewer.getTextWidget().addControlListener(new ControlAdapter() {
957989
@Override
958990
public void controlResized(ControlEvent e) {
959991
refreshDetails();
960992
}
961993
});
962994
}
963995

996+
private void setColors() {
997+
RGB background = null;
998+
RGB foreground = null;
999+
var textWidget = viewer.getTextWidget();
1000+
ISharedTextColors sharedColors = EditorsUI.getSharedTextColors();
1001+
1002+
var isUsingSystemBackground = EditorsUI.getPreferenceStore().getBoolean(PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT);
1003+
if (!isUsingSystemBackground) {
1004+
background = getColorFromStore(PREFERENCE_COLOR_BACKGROUND);
1005+
}
1006+
if (background != null) {
1007+
var color = sharedColors.getColor(background);
1008+
textWidget.setBackground(color);
1009+
lineNumberColumn.setBackground(color);
1010+
} else {
1011+
textWidget.setBackground(null);
1012+
lineNumberColumn.setBackground(null);
1013+
}
1014+
1015+
var isUsingSystemForeground = EditorsUI.getPreferenceStore().getBoolean(PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT);
1016+
if (!isUsingSystemForeground) {
1017+
foreground = getColorFromStore(PREFERENCE_COLOR_FOREGROUND);
1018+
}
1019+
if (foreground != null) {
1020+
textWidget.setForeground(sharedColors.getColor(foreground));
1021+
} else {
1022+
textWidget.setForeground(null);
1023+
}
1024+
}
1025+
1026+
private Color getLineNumbersColor() {
1027+
var lineNumbersColor = getColorFromStore(EDITOR_LINE_NUMBER_RULER_COLOR);
1028+
return EditorsUI.getSharedTextColors().getColor(lineNumbersColor == null ? new RGB(0, 0, 0) : lineNumbersColor);
1029+
}
1030+
1031+
private Color getTargetLineHighlightColor() {
1032+
RGB background = getColorFromStore(EDITOR_CURRENT_LINE_COLOR);
1033+
ISharedTextColors sharedColors = EditorsUI.getSharedTextColors();
1034+
return sharedColors.getColor(background);
1035+
}
1036+
1037+
private void createViewerDecorations() {
1038+
lineNumberColumn = new LineNumberRulerColumn();
1039+
lineNumberColumn.setForeground(getLineNumbersColor());
1040+
viewer.addVerticalRulerColumn(lineNumberColumn);
1041+
1042+
var sourceViewerDecorationSupport = new SourceViewerDecorationSupport(viewer, null, null, EditorsUI.getSharedTextColors());
1043+
sourceViewerDecorationSupport.setCursorLinePainterPreferenceKeys(EDITOR_CURRENT_LINE, EDITOR_CURRENT_LINE_COLOR);
1044+
sourceViewerDecorationSupport.install(EditorsUI.getPreferenceStore());
1045+
targetLineHighlighter = new FixedLineHighlighter();
1046+
targetLineHighlighter.highlightColor = getTargetLineHighlightColor();
1047+
viewer.getTextWidget().addLineBackgroundListener(targetLineHighlighter);
1048+
1049+
setColors();
1050+
}
1051+
1052+
private void handlePropertyChangeEvent(PropertyChangeEvent event) {
1053+
if (viewer == null) {
1054+
return;
1055+
}
1056+
var prop = event.getProperty();
1057+
if (PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT.equals(prop)
1058+
|| PREFERENCE_COLOR_BACKGROUND.equals(prop)
1059+
|| PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT.equals(prop)
1060+
|| PREFERENCE_COLOR_FOREGROUND.equals(prop)) {
1061+
setColors();
1062+
viewer.getTextWidget().redraw();
1063+
} else if (EDITOR_LINE_NUMBER_RULER_COLOR.equals(prop)) {
1064+
lineNumberColumn.setForeground(getLineNumbersColor());
1065+
lineNumberColumn.redraw();
1066+
} else if (EDITOR_CURRENT_LINE_COLOR.equals(prop)) {
1067+
targetLineHighlighter.highlightColor = getTargetLineHighlightColor();
1068+
viewer.getTextWidget().redraw();
1069+
}
1070+
}
1071+
1072+
private RGB getColorFromStore(String key) {
1073+
var store = EditorsUI.getPreferenceStore();
1074+
RGB rgb = null;
1075+
if (store.contains(key)) {
1076+
if (store.isDefault(key)) {
1077+
rgb = PreferenceConverter.getDefaultColor(store, key);
1078+
} else {
1079+
rgb = PreferenceConverter.getColor(store, key);
1080+
}
1081+
}
1082+
return rgb;
1083+
}
9641084

965-
// Dumber version just using the a 'raw' StyledText widget.
9661085
private void refreshDetails() {
967-
if (details!=null && list!=null && !list.getTable().isDisposed()) {
1086+
if (viewer!=null && list!=null && !list.getTable().isDisposed()) {
9681087
if (documents==null) {
9691088
documents = new DocumentFetcher();
9701089
}
9711090
IStructuredSelection sel = (IStructuredSelection) list.getSelection();
9721091
if (sel==null || sel.isEmpty()) {
973-
details.setText(EMPTY_STRING);
1092+
viewer.setDocument(null);
9741093
} else {
9751094
//Not empty selection
9761095
final int context = 100; // number of lines before and after match to include in preview
@@ -983,35 +1102,57 @@ private void refreshDetails() {
9831102
int line = item.getLineNumber()-1; //in document lines are 0 based. In search 1 based.
9841103
int contextStartLine = Math.max(line-(numLines-1)/2 - context, 0);
9851104
int start = document.getLineOffset(contextStartLine);
1105+
int displayedEndLine = line + numLines/2;
9861106
int end = document.getLength();
987-
try {
988-
IRegion lineInfo = document.getLineInformation(line + numLines/2 + context);
989-
end = lineInfo.getOffset() + lineInfo.getLength();
990-
} catch (BadLocationException e) {
991-
//Presumably line number is past the end of document.
992-
//ignore.
1107+
if (displayedEndLine + context <= document.getNumberOfLines()) {
1108+
try {
1109+
IRegion lineInfo = document.getLineInformation(displayedEndLine + context);
1110+
end = lineInfo.getOffset() + lineInfo.getLength();
1111+
} catch (BadLocationException e) {
1112+
//Presumably line number is past the end of document.
1113+
//ignore.
1114+
}
9931115
}
1116+
int contextLenght = end-start;
1117+
1118+
viewer.setDocument(document);
1119+
viewer.setVisibleRegion(start, contextLenght);
1120+
1121+
targetLineHighlighter.setTargetLineOffset(item.getOffset() - start);
9941122

995-
StyledString styledString = highlightMatches(document.get(start, end-start));
996-
details.setText(styledString.getString());
997-
details.setStyleRanges(styledString.getStyleRanges());
998-
details.setTopIndex(Math.max(line - contextStartLine - numLines/2, 0));
1123+
// center target line in the displayed area
1124+
IRegion rangeEndLineInfo = document.getLineInformation(Math.min(displayedEndLine, document.getNumberOfLines() - 1));
1125+
int rangeStart = document.getLineOffset(Math.max(line - numLines/2, 0));
1126+
int rangeEnd = rangeEndLineInfo.getOffset() + rangeEndLineInfo.getLength();
1127+
viewer.revealRange(rangeStart, rangeEnd - rangeStart);
1128+
1129+
var targetLineFirstMatch = getQuery().findFirst(document.get(item.getOffset(), contextLenght - (item.getOffset() - start)));
1130+
int targetLineFirstMatchStart = item.getOffset() + targetLineFirstMatch.getOffset();
1131+
// sets caret position
1132+
viewer.setSelectedRange(targetLineFirstMatchStart, 0);
1133+
// does horizontal scrolling if necessary to reveal 1st occurrence in target line
1134+
viewer.revealRange(targetLineFirstMatchStart, targetLineFirstMatch.getLength());
1135+
1136+
// above setVisibleRegion() call makes these ranges to be aligned with content of text widget
1137+
StyledString styledString = highlightMatches(document.get(start, contextLenght));
1138+
viewer.getTextWidget().setStyleRanges(styledString.getStyleRanges());
9991139
return;
10001140
} catch (BadLocationException e) {
10011141
}
10021142
}
10031143
}
10041144
}
10051145
//empty selection or some error:
1006-
details.setText(EMPTY_STRING);
1146+
viewer.setDocument(null);
10071147
}
10081148
}
10091149

10101150
/**
10111151
* Computes how many lines of text can be displayed in the details section.
10121152
*/
10131153
private int computeLines() {
1014-
if (details!=null && !details.isDisposed()) {
1154+
StyledText details;
1155+
if (viewer!=null && !(details = viewer.getTextWidget()).isDisposed()) {
10151156
int lineHeight = details.getLineHeight();
10161157
int areaHeight = details.getClientArea().height;
10171158
return (areaHeight + lineHeight - 1) / lineHeight;
@@ -1034,47 +1175,6 @@ private StyledString highlightMatches(String visibleText) {
10341175
return styledText;
10351176
}
10361177

1037-
// Version using sourceviewer
1038-
// private void refreshDetails() {
1039-
// if (details!=null && list!=null && !list.getTable().isDisposed()) {
1040-
// if (documents==null) {
1041-
// documents = new DocumentFetcher();
1042-
// }
1043-
// IStructuredSelection sel = (IStructuredSelection) list.getSelection();
1044-
// if (sel!=null && !sel.isEmpty()) {
1045-
// //Not empty selection
1046-
// LineItem item = (LineItem) sel.getFirstElement();
1047-
// IDocument document = documents.getDocument(item.getFile());
1048-
// try {
1049-
// int line = item.getLineNumber()-1; //in document lines are 0 based. In search 1 based.
1050-
// int start = document.getLineOffset(Math.max(line-2, 0));
1051-
// int end = document.getLength();
1052-
// try {
1053-
// end = document.getLineOffset(line+3);
1054-
// } catch (BadLocationException e) {
1055-
// //Presumably line number is past the end of document.
1056-
// //ignore.
1057-
// }
1058-
// details.setDocument(document, start, end-start);
1059-
//
1060-
// String visibleText = document.get(start, end-start);
1061-
// List<TextRange> matches = getQuery().findAll(visibleText);
1062-
// Region visibleRegion = new Region(start, end-start);
1063-
// TextPresentation presentation = new TextPresentation(visibleRegion, 20);
1064-
// presentation.setDefaultStyleRange(new StyleRange(0, document.getLength(), null, null));
1065-
// for (TextRange m : matches) {
1066-
// presentation.addStyleRange(new StyleRange(m.start+start, m.len, null, YELLOW));
1067-
// }
1068-
// details.changeTextPresentation(presentation, true);
1069-
//
1070-
// return;
1071-
// } catch (BadLocationException e) {
1072-
// }
1073-
// }
1074-
// details.setDocument(null);
1075-
// }
1076-
// }
1077-
10781178
/**
10791179
* Handle selection in the items list by updating labels of selected and
10801180
* unselected items and refresh the details field using the selection.
@@ -1471,4 +1571,29 @@ public QuickTextQuery getQuery() {
14711571
return searcher.getQuery();
14721572
}
14731573

1574+
/**
1575+
* A line background listener that provides the color that is used for current line highlighting (what
1576+
* {@link CursorLinePainter} does) but for single fixed line only and does so always regardless of show current
1577+
* line highlighting on/off preference.
1578+
*
1579+
* @see CursorLinePainter
1580+
*/
1581+
private static class FixedLineHighlighter implements LineBackgroundListener {
1582+
1583+
private int lineOffset = -1;
1584+
private Color highlightColor;
1585+
1586+
public void setTargetLineOffset(int lineOffset) {
1587+
this.lineOffset = lineOffset;
1588+
}
1589+
1590+
@Override
1591+
public void lineGetBackground(LineBackgroundEvent event) {
1592+
if (lineOffset == event.lineOffset) {
1593+
event.lineBackground = highlightColor;
1594+
}
1595+
}
1596+
1597+
}
1598+
14741599
}

0 commit comments

Comments
 (0)