Skip to content

Commit d078088

Browse files
committed
Display quick search dialog result in SourceViewer with line numbers
Fixes #2010
1 parent ca298d6 commit d078088

File tree

1 file changed

+220
-60
lines changed

1 file changed

+220
-60
lines changed

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

Lines changed: 220 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,21 @@
4646
import org.eclipse.jface.dialogs.IDialogConstants;
4747
import org.eclipse.jface.dialogs.IDialogSettings;
4848
import org.eclipse.jface.layout.GridDataFactory;
49+
import org.eclipse.jface.preference.IPreferenceStore;
50+
import org.eclipse.jface.preference.PreferenceConverter;
4951
import org.eclipse.jface.resource.JFaceResources;
5052
import org.eclipse.jface.text.BadLocationException;
53+
import org.eclipse.jface.text.CursorLinePainter;
5154
import org.eclipse.jface.text.IDocument;
55+
import org.eclipse.jface.text.IPaintPositionManager;
56+
import org.eclipse.jface.text.IPainter;
5257
import org.eclipse.jface.text.IRegion;
58+
import org.eclipse.jface.text.source.CompositeRuler;
59+
import org.eclipse.jface.text.source.ISharedTextColors;
60+
import org.eclipse.jface.text.source.LineNumberRulerColumn;
61+
import org.eclipse.jface.text.source.SourceViewer;
62+
import org.eclipse.jface.util.IPropertyChangeListener;
63+
import org.eclipse.jface.util.PropertyChangeEvent;
5364
import org.eclipse.jface.viewers.ILazyContentProvider;
5465
import org.eclipse.jface.viewers.IStructuredContentProvider;
5566
import org.eclipse.jface.viewers.IStructuredSelection;
@@ -67,6 +78,9 @@
6778
import org.eclipse.swt.accessibility.ACC;
6879
import org.eclipse.swt.accessibility.AccessibleAdapter;
6980
import org.eclipse.swt.accessibility.AccessibleEvent;
81+
import org.eclipse.swt.custom.LineBackgroundEvent;
82+
import org.eclipse.swt.custom.LineBackgroundListener;
83+
import org.eclipse.swt.custom.ST;
7084
import org.eclipse.swt.custom.SashForm;
7185
import org.eclipse.swt.custom.StyleRange;
7286
import org.eclipse.swt.custom.StyledText;
@@ -81,10 +95,13 @@
8195
import org.eclipse.swt.graphics.Color;
8296
import org.eclipse.swt.graphics.Image;
8397
import org.eclipse.swt.graphics.Point;
98+
import org.eclipse.swt.graphics.RGB;
8499
import org.eclipse.swt.graphics.Rectangle;
100+
import org.eclipse.swt.layout.FillLayout;
85101
import org.eclipse.swt.layout.GridData;
86102
import org.eclipse.swt.layout.GridLayout;
87103
import org.eclipse.swt.widgets.Button;
104+
import org.eclipse.swt.widgets.Canvas;
88105
import org.eclipse.swt.widgets.Combo;
89106
import org.eclipse.swt.widgets.Composite;
90107
import org.eclipse.swt.widgets.Control;
@@ -111,12 +128,16 @@
111128
import org.eclipse.ui.PartInitException;
112129
import org.eclipse.ui.PlatformUI;
113130
import org.eclipse.ui.dialogs.SelectionStatusDialog;
131+
import org.eclipse.ui.editors.text.EditorsUI;
114132
import org.eclipse.ui.handlers.IHandlerActivation;
115133
import org.eclipse.ui.handlers.IHandlerService;
116134
import org.eclipse.ui.internal.IWorkbenchGraphicConstants;
117135
import org.eclipse.ui.internal.WorkbenchImages;
118136
import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
119137
import org.eclipse.ui.progress.UIJob;
138+
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
139+
import org.eclipse.ui.texteditor.AbstractTextEditor;
140+
import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
120141
import org.osgi.framework.FrameworkUtil;
121142

122143
/**
@@ -359,7 +380,10 @@ public void update(ViewerCell cell) {
359380

360381
private QuickTextSearcher searcher;
361382

362-
private StyledText details;
383+
private SourceViewer viewer;
384+
private LineNumberRulerColumn lineNumberColumn;
385+
private FixedLinePainter targetLinePainter;
386+
private final IPropertyChangeListener preferenceChangeListener = this::handlePropertyChangeEvent;
363387

364388
private DocumentFetcher documents;
365389

@@ -398,6 +422,7 @@ public QuickSearchDialog(IWorkbenchWindow window) {
398422
MAX_LINE_LEN = QuickSearchActivator.getDefault().getPreferences().getMaxLineLen();
399423
MAX_RESULTS = QuickSearchActivator.getDefault().getPreferences().getMaxResults();
400424
progressJob.setSystem(true);
425+
EditorsUI.getPreferenceStore().addPropertyChangeListener(preferenceChangeListener);
401426
}
402427

403428
/*
@@ -942,31 +967,123 @@ protected void dispose() {
942967
blankImage.dispose();
943968
blankImage = null;
944969
}
970+
EditorsUI.getPreferenceStore().removePropertyChangeListener(preferenceChangeListener);
945971
}
946972

947973
private void createDetailsArea(Composite parent) {
948-
details = new StyledText(parent, SWT.MULTI+SWT.READ_ONLY+SWT.BORDER+SWT.H_SCROLL+SWT.V_SCROLL);
949-
details.setFont(JFaceResources.getFont(TEXT_FONT));
974+
var viewerParent = new Canvas(parent, SWT.BORDER);
975+
viewerParent.setLayout(new FillLayout());
976+
977+
viewer = new SourceViewer(viewerParent, new CompositeRuler(), SWT.H_SCROLL | SWT.V_SCROLL | SWT.READ_ONLY);
978+
viewer.getTextWidget().setFont(JFaceResources.getFont(TEXT_FONT));
979+
980+
lineNumberColumn = new LineNumberRulerColumn();
981+
viewer.addVerticalRulerColumn(lineNumberColumn);
982+
setColors(false);
983+
createLinesHighlightingDecorations();
950984

951985
list.addSelectionChangedListener(event -> refreshDetails());
952-
details.addControlListener(new ControlAdapter() {
986+
987+
viewer.getTextWidget().addControlListener(new ControlAdapter() {
953988
@Override
954989
public void controlResized(ControlEvent e) {
955990
refreshDetails();
956991
}
957992
});
958993
}
959994

995+
private void setColors(boolean refresh) {
996+
RGB background = null;
997+
RGB foreground = null;
998+
var textWidget = viewer.getTextWidget();
999+
var preferenceStore = EditorsUI.getPreferenceStore();
1000+
ISharedTextColors sharedColors = EditorsUI.getSharedTextColors();
1001+
1002+
var isUsingSystemBackground = preferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT);
1003+
if (!isUsingSystemBackground) {
1004+
background = getColorFromStore(preferenceStore, AbstractTextEditor.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 = preferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT);
1016+
if (!isUsingSystemForeground) {
1017+
foreground = getColorFromStore(preferenceStore, AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND);
1018+
}
1019+
if (foreground != null) {
1020+
textWidget.setForeground(sharedColors.getColor(foreground));
1021+
} else {
1022+
textWidget.setForeground(null);
1023+
}
1024+
1025+
var lineNumbersColor = getColorFromStore(EditorsUI.getPreferenceStore(), AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER_COLOR);
1026+
if (lineNumbersColor == null) {
1027+
lineNumbersColor = new RGB(0, 0, 0);
1028+
}
1029+
lineNumberColumn.setForeground(sharedColors.getColor(lineNumbersColor));
1030+
1031+
if (refresh) {
1032+
textWidget.redraw();
1033+
lineNumberColumn.redraw();
1034+
}
1035+
}
1036+
1037+
1038+
private void createLinesHighlightingDecorations() {
1039+
var sourceViewerDecorationSupport = new SourceViewerDecorationSupport(viewer, null, null, EditorsUI.getSharedTextColors());
1040+
sourceViewerDecorationSupport.setCursorLinePainterPreferenceKeys(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE_COLOR);
1041+
sourceViewerDecorationSupport.install(EditorsUI.getPreferenceStore());
1042+
targetLinePainter = new FixedLinePainter();
1043+
viewer.addPainter(targetLinePainter);
1044+
viewer.getTextWidget().addLineBackgroundListener(targetLinePainter);
1045+
}
1046+
1047+
private void handlePropertyChangeEvent(PropertyChangeEvent event) {
1048+
if (viewer == null) {
1049+
return;
1050+
}
1051+
var prop = event.getProperty();
1052+
if (prop.equals(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER_COLOR)
1053+
|| prop.equals(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT)
1054+
|| prop.equals(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND)
1055+
|| prop.equals(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT)
1056+
|| prop.equals(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND)) {
1057+
setColors(true);
1058+
}
1059+
if (prop.equals(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE)
1060+
|| prop.equals(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE_COLOR)) {
1061+
targetLinePainter.highlightColor = null;
1062+
targetLinePainter.cursorLinePainter = null;
1063+
viewer.getTextWidget().redraw();
1064+
}
1065+
}
1066+
1067+
private RGB getColorFromStore(IPreferenceStore store, String key) {
1068+
RGB rgb = null;
1069+
if (store.contains(key)) {
1070+
if (store.isDefault(key)) {
1071+
rgb = PreferenceConverter.getDefaultColor(store, key);
1072+
} else {
1073+
rgb = PreferenceConverter.getColor(store, key);
1074+
}
1075+
}
1076+
return rgb;
1077+
}
9601078

961-
// Dumber version just using the a 'raw' StyledText widget.
9621079
private void refreshDetails() {
963-
if (details!=null && list!=null && !list.getTable().isDisposed()) {
1080+
if (viewer!=null && list!=null && !list.getTable().isDisposed()) {
9641081
if (documents==null) {
9651082
documents = new DocumentFetcher();
9661083
}
9671084
IStructuredSelection sel = (IStructuredSelection) list.getSelection();
9681085
if (sel==null || sel.isEmpty()) {
969-
details.setText(EMPTY_STRING);
1086+
viewer.setDocument(null);
9701087
} else {
9711088
//Not empty selection
9721089
final int context = 100; // number of lines before and after match to include in preview
@@ -979,35 +1096,56 @@ private void refreshDetails() {
9791096
int line = item.getLineNumber()-1; //in document lines are 0 based. In search 1 based.
9801097
int contextStartLine = Math.max(line-(numLines-1)/2 - context, 0);
9811098
int start = document.getLineOffset(contextStartLine);
1099+
int displayedEndLine = line + numLines/2;
9821100
int end = document.getLength();
983-
try {
984-
IRegion lineInfo = document.getLineInformation(line + numLines/2 + context);
985-
end = lineInfo.getOffset() + lineInfo.getLength();
986-
} catch (BadLocationException e) {
987-
//Presumably line number is past the end of document.
988-
//ignore.
1101+
if (displayedEndLine + context <= document.getNumberOfLines()) {
1102+
try {
1103+
IRegion lineInfo = document.getLineInformation(displayedEndLine + context);
1104+
end = lineInfo.getOffset() + lineInfo.getLength();
1105+
} catch (BadLocationException e) {
1106+
//Presumably line number is past the end of document.
1107+
//ignore.
1108+
}
9891109
}
1110+
int contextLenght = end-start;
1111+
1112+
viewer.setDocument(document);
1113+
viewer.setVisibleRegion(start, contextLenght);
1114+
1115+
targetLinePainter.setTargetLineOffset(item.getOffset() - start);
1116+
1117+
// center target line in the displayed area
1118+
IRegion rangeEndLineInfo = document.getLineInformation(Math.min(displayedEndLine, document.getNumberOfLines() - 1));
1119+
int rangeStart = document.getLineOffset(Math.max(line - numLines/2, 0));
1120+
int rangeEnd = rangeEndLineInfo.getOffset() + rangeEndLineInfo.getLength();
1121+
viewer.revealRange(rangeStart, rangeEnd - rangeStart);
1122+
1123+
var targetLineFirstMatch = getQuery().findFirst(document.get(item.getOffset(), contextLenght - (item.getOffset() - start)));
1124+
int targetLineFirstMatchStart = item.getOffset() + targetLineFirstMatch.getOffset();
1125+
// sets caret position (also highlights that line)
1126+
viewer.setSelectedRange(targetLineFirstMatchStart, 0);
1127+
// does horizontal scrolling if necessary to reveal 1st occurrence in target line
1128+
viewer.revealRange(targetLineFirstMatchStart, targetLineFirstMatch.getLength());
9901129

991-
StyledString styledString = highlightMatches(document.get(start, end-start));
992-
details.setText(styledString.getString());
993-
details.setStyleRanges(styledString.getStyleRanges());
994-
details.setTopIndex(Math.max(line - contextStartLine - numLines/2, 0));
1130+
StyledString styledString = highlightMatches(document.get(start, contextLenght));
1131+
viewer.getTextWidget().setStyleRanges(styledString.getStyleRanges());
9951132
return;
9961133
} catch (BadLocationException e) {
9971134
}
9981135
}
9991136
}
10001137
}
10011138
//empty selection or some error:
1002-
details.setText(EMPTY_STRING);
1139+
viewer.setDocument(null);
10031140
}
10041141
}
10051142

10061143
/**
10071144
* Computes how many lines of text can be displayed in the details section.
10081145
*/
10091146
private int computeLines() {
1010-
if (details!=null && !details.isDisposed()) {
1147+
StyledText details;
1148+
if (viewer!=null && !(details = viewer.getTextWidget()).isDisposed()) {
10111149
int lineHeight = details.getLineHeight();
10121150
int areaHeight = details.getClientArea().height;
10131151
return (areaHeight + lineHeight - 1) / lineHeight;
@@ -1030,47 +1168,6 @@ private StyledString highlightMatches(String visibleText) {
10301168
return styledText;
10311169
}
10321170

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

1567+
/**
1568+
* A painter that does 'current line' highlighting (background color) but for single fixed line.
1569+
* <br><br>
1570+
* This class piggybacks on {@link CursorLinePainter} instance already added to viewer's widget, which knows what color
1571+
* to use and does all the necessary repainting. This class only forces the background color to be used also for one
1572+
* fixed line in addition to current line = line where caret is currently located (done by <code>CursorLinePainter</code>).
1573+
* Background color for this fixed line never changes and so <code>CursorLinePainter</code>'s repainting code, although not
1574+
* knowing about this fixed line at all, produces correct visual results - target line always highlighted.
1575+
*
1576+
* @see CursorLinePainter
1577+
*/
1578+
private class FixedLinePainter implements IPainter, LineBackgroundListener {
1579+
1580+
private CursorLinePainter cursorLinePainter;
1581+
private int lineOffset;
1582+
private Color highlightColor;
1583+
1584+
public void setTargetLineOffset(int lineOffset) {
1585+
this.lineOffset = lineOffset;
1586+
}
1587+
1588+
// CursorLinePainter adds itself as line LineBackgroundListener lazily
1589+
private boolean cursorLinePainterInstalled() {
1590+
if (cursorLinePainter == null) {
1591+
cursorLinePainter = viewer.getTextWidget().getTypedListeners(ST.LineGetBackground, CursorLinePainter.class).findFirst().orElse(null);
1592+
return cursorLinePainter != null;
1593+
}
1594+
return true;
1595+
}
1596+
1597+
@Override
1598+
public void lineGetBackground(LineBackgroundEvent event) {
1599+
if (cursorLinePainterInstalled()) {
1600+
cursorLinePainter.lineGetBackground(event);
1601+
if (event.lineBackground != null) {
1602+
// piggyback on CursorLinePainter knowing proper color
1603+
highlightColor = event.lineBackground;
1604+
} else if (lineOffset == event.lineOffset) { // target line
1605+
event.lineBackground = highlightColor;
1606+
}
1607+
}
1608+
}
1609+
1610+
@Override
1611+
public void dispose() {
1612+
}
1613+
1614+
@Override
1615+
public void paint(int reason) {
1616+
// no custom painting here, cursorLinePainter (also being registered as IPainter) does all the work
1617+
// according to background color set in lineGetBackground()
1618+
}
1619+
1620+
@Override
1621+
public void deactivate(boolean redraw) {
1622+
}
1623+
1624+
@Override
1625+
public void setPositionManager(IPaintPositionManager manager) {
1626+
}
1627+
1628+
}
1629+
14701630
}

0 commit comments

Comments
 (0)