4646import org .eclipse .jface .dialogs .IDialogConstants ;
4747import org .eclipse .jface .dialogs .IDialogSettings ;
4848import org .eclipse .jface .layout .GridDataFactory ;
49+ import org .eclipse .jface .preference .IPreferenceStore ;
50+ import org .eclipse .jface .preference .PreferenceConverter ;
4951import org .eclipse .jface .resource .JFaceResources ;
5052import org .eclipse .jface .text .BadLocationException ;
53+ import org .eclipse .jface .text .CursorLinePainter ;
5154import org .eclipse .jface .text .IDocument ;
55+ import org .eclipse .jface .text .IPaintPositionManager ;
56+ import org .eclipse .jface .text .IPainter ;
5257import 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 ;
5364import org .eclipse .jface .viewers .ILazyContentProvider ;
5465import org .eclipse .jface .viewers .IStructuredContentProvider ;
5566import org .eclipse .jface .viewers .IStructuredSelection ;
6778import org .eclipse .swt .accessibility .ACC ;
6879import org .eclipse .swt .accessibility .AccessibleAdapter ;
6980import 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 ;
7084import org .eclipse .swt .custom .SashForm ;
7185import org .eclipse .swt .custom .StyleRange ;
7286import org .eclipse .swt .custom .StyledText ;
8195import org .eclipse .swt .graphics .Color ;
8296import org .eclipse .swt .graphics .Image ;
8397import org .eclipse .swt .graphics .Point ;
98+ import org .eclipse .swt .graphics .RGB ;
8499import org .eclipse .swt .graphics .Rectangle ;
100+ import org .eclipse .swt .layout .FillLayout ;
85101import org .eclipse .swt .layout .GridData ;
86102import org .eclipse .swt .layout .GridLayout ;
87103import org .eclipse .swt .widgets .Button ;
104+ import org .eclipse .swt .widgets .Canvas ;
88105import org .eclipse .swt .widgets .Combo ;
89106import org .eclipse .swt .widgets .Composite ;
90107import org .eclipse .swt .widgets .Control ;
111128import org .eclipse .ui .PartInitException ;
112129import org .eclipse .ui .PlatformUI ;
113130import org .eclipse .ui .dialogs .SelectionStatusDialog ;
131+ import org .eclipse .ui .editors .text .EditorsUI ;
114132import org .eclipse .ui .handlers .IHandlerActivation ;
115133import org .eclipse .ui .handlers .IHandlerService ;
116134import org .eclipse .ui .internal .IWorkbenchGraphicConstants ;
117135import org .eclipse .ui .internal .WorkbenchImages ;
118136import org .eclipse .ui .internal .ide .IDEWorkbenchPlugin ;
119137import 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 ;
120141import 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