From d9355e9ae8951bb4b4e3b6c69e5767201e2abd11 Mon Sep 17 00:00:00 2001 From: Jozef Tomek Date: Tue, 4 Jun 2024 00:28:40 +0200 Subject: [PATCH 1/6] Display quick search dialog result in SourceViewer with line numbers Fixes #2010 --- .../internal/ui/QuickSearchDialog.java | 280 ++++++++++++++---- 1 file changed, 220 insertions(+), 60 deletions(-) diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java index 0cce0ad5b65..0b78da3eb03 100644 --- a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java @@ -46,10 +46,21 @@ import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.preference.PreferenceConverter; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.CursorLinePainter; import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IPaintPositionManager; +import org.eclipse.jface.text.IPainter; import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.source.CompositeRuler; +import org.eclipse.jface.text.source.ISharedTextColors; +import org.eclipse.jface.text.source.LineNumberRulerColumn; +import org.eclipse.jface.text.source.SourceViewer; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.ILazyContentProvider; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.IStructuredSelection; @@ -67,6 +78,9 @@ import org.eclipse.swt.accessibility.ACC; import org.eclipse.swt.accessibility.AccessibleAdapter; import org.eclipse.swt.accessibility.AccessibleEvent; +import org.eclipse.swt.custom.LineBackgroundEvent; +import org.eclipse.swt.custom.LineBackgroundListener; +import org.eclipse.swt.custom.ST; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.custom.StyleRange; import org.eclipse.swt.custom.StyledText; @@ -81,10 +95,13 @@ import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; @@ -111,12 +128,16 @@ import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.dialogs.SelectionStatusDialog; +import org.eclipse.ui.editors.text.EditorsUI; import org.eclipse.ui.handlers.IHandlerActivation; import org.eclipse.ui.handlers.IHandlerService; import org.eclipse.ui.internal.IWorkbenchGraphicConstants; import org.eclipse.ui.internal.WorkbenchImages; import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin; import org.eclipse.ui.progress.UIJob; +import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants; +import org.eclipse.ui.texteditor.AbstractTextEditor; +import org.eclipse.ui.texteditor.SourceViewerDecorationSupport; import org.osgi.framework.FrameworkUtil; /** @@ -359,7 +380,10 @@ public void update(ViewerCell cell) { private QuickTextSearcher searcher; - private StyledText details; + private SourceViewer viewer; + private LineNumberRulerColumn lineNumberColumn; + private FixedLinePainter targetLinePainter; + private final IPropertyChangeListener preferenceChangeListener = this::handlePropertyChangeEvent; private DocumentFetcher documents; @@ -398,6 +422,7 @@ public QuickSearchDialog(IWorkbenchWindow window) { MAX_LINE_LEN = QuickSearchActivator.getDefault().getPreferences().getMaxLineLen(); MAX_RESULTS = QuickSearchActivator.getDefault().getPreferences().getMaxResults(); progressJob.setSystem(true); + EditorsUI.getPreferenceStore().addPropertyChangeListener(preferenceChangeListener); } /* @@ -946,14 +971,24 @@ protected void dispose() { blankImage.dispose(); blankImage = null; } + EditorsUI.getPreferenceStore().removePropertyChangeListener(preferenceChangeListener); } private void createDetailsArea(Composite parent) { - details = new StyledText(parent, SWT.MULTI+SWT.READ_ONLY+SWT.BORDER+SWT.H_SCROLL+SWT.V_SCROLL); - details.setFont(JFaceResources.getFont(TEXT_FONT)); + var viewerParent = new Canvas(parent, SWT.BORDER); + viewerParent.setLayout(new FillLayout()); + + viewer = new SourceViewer(viewerParent, new CompositeRuler(), SWT.H_SCROLL | SWT.V_SCROLL | SWT.READ_ONLY); + viewer.getTextWidget().setFont(JFaceResources.getFont(TEXT_FONT)); + + lineNumberColumn = new LineNumberRulerColumn(); + viewer.addVerticalRulerColumn(lineNumberColumn); + setColors(false); + createLinesHighlightingDecorations(); list.addSelectionChangedListener(event -> refreshDetails()); - details.addControlListener(new ControlAdapter() { + + viewer.getTextWidget().addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { refreshDetails(); @@ -961,16 +996,98 @@ public void controlResized(ControlEvent e) { }); } + private void setColors(boolean refresh) { + RGB background = null; + RGB foreground = null; + var textWidget = viewer.getTextWidget(); + var preferenceStore = EditorsUI.getPreferenceStore(); + ISharedTextColors sharedColors = EditorsUI.getSharedTextColors(); + + var isUsingSystemBackground = preferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT); + if (!isUsingSystemBackground) { + background = getColorFromStore(preferenceStore, AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND); + } + if (background != null) { + var color = sharedColors.getColor(background); + textWidget.setBackground(color); + lineNumberColumn.setBackground(color); + } else { + textWidget.setBackground(null); + lineNumberColumn.setBackground(null); + } + + var isUsingSystemForeground = preferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT); + if (!isUsingSystemForeground) { + foreground = getColorFromStore(preferenceStore, AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND); + } + if (foreground != null) { + textWidget.setForeground(sharedColors.getColor(foreground)); + } else { + textWidget.setForeground(null); + } + + var lineNumbersColor = getColorFromStore(EditorsUI.getPreferenceStore(), AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER_COLOR); + if (lineNumbersColor == null) { + lineNumbersColor = new RGB(0, 0, 0); + } + lineNumberColumn.setForeground(sharedColors.getColor(lineNumbersColor)); + + if (refresh) { + textWidget.redraw(); + lineNumberColumn.redraw(); + } + } + + + private void createLinesHighlightingDecorations() { + var sourceViewerDecorationSupport = new SourceViewerDecorationSupport(viewer, null, null, EditorsUI.getSharedTextColors()); + sourceViewerDecorationSupport.setCursorLinePainterPreferenceKeys(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE_COLOR); + sourceViewerDecorationSupport.install(EditorsUI.getPreferenceStore()); + targetLinePainter = new FixedLinePainter(); + viewer.addPainter(targetLinePainter); + viewer.getTextWidget().addLineBackgroundListener(targetLinePainter); + } + + private void handlePropertyChangeEvent(PropertyChangeEvent event) { + if (viewer == null) { + return; + } + var prop = event.getProperty(); + if (prop.equals(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER_COLOR) + || prop.equals(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT) + || prop.equals(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND) + || prop.equals(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT) + || prop.equals(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND)) { + setColors(true); + } + if (prop.equals(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE) + || prop.equals(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE_COLOR)) { + targetLinePainter.highlightColor = null; + targetLinePainter.cursorLinePainter = null; + viewer.getTextWidget().redraw(); + } + } + + private RGB getColorFromStore(IPreferenceStore store, String key) { + RGB rgb = null; + if (store.contains(key)) { + if (store.isDefault(key)) { + rgb = PreferenceConverter.getDefaultColor(store, key); + } else { + rgb = PreferenceConverter.getColor(store, key); + } + } + return rgb; + } - // Dumber version just using the a 'raw' StyledText widget. private void refreshDetails() { - if (details!=null && list!=null && !list.getTable().isDisposed()) { + if (viewer!=null && list!=null && !list.getTable().isDisposed()) { if (documents==null) { documents = new DocumentFetcher(); } IStructuredSelection sel = (IStructuredSelection) list.getSelection(); if (sel==null || sel.isEmpty()) { - details.setText(EMPTY_STRING); + viewer.setDocument(null); } else { //Not empty selection final int context = 100; // number of lines before and after match to include in preview @@ -983,19 +1100,39 @@ private void refreshDetails() { int line = item.getLineNumber()-1; //in document lines are 0 based. In search 1 based. int contextStartLine = Math.max(line-(numLines-1)/2 - context, 0); int start = document.getLineOffset(contextStartLine); + int displayedEndLine = line + numLines/2; int end = document.getLength(); - try { - IRegion lineInfo = document.getLineInformation(line + numLines/2 + context); - end = lineInfo.getOffset() + lineInfo.getLength(); - } catch (BadLocationException e) { - //Presumably line number is past the end of document. - //ignore. + if (displayedEndLine + context <= document.getNumberOfLines()) { + try { + IRegion lineInfo = document.getLineInformation(displayedEndLine + context); + end = lineInfo.getOffset() + lineInfo.getLength(); + } catch (BadLocationException e) { + //Presumably line number is past the end of document. + //ignore. + } } + int contextLenght = end-start; + + viewer.setDocument(document); + viewer.setVisibleRegion(start, contextLenght); + + targetLinePainter.setTargetLineOffset(item.getOffset() - start); + + // center target line in the displayed area + IRegion rangeEndLineInfo = document.getLineInformation(Math.min(displayedEndLine, document.getNumberOfLines() - 1)); + int rangeStart = document.getLineOffset(Math.max(line - numLines/2, 0)); + int rangeEnd = rangeEndLineInfo.getOffset() + rangeEndLineInfo.getLength(); + viewer.revealRange(rangeStart, rangeEnd - rangeStart); + + var targetLineFirstMatch = getQuery().findFirst(document.get(item.getOffset(), contextLenght - (item.getOffset() - start))); + int targetLineFirstMatchStart = item.getOffset() + targetLineFirstMatch.getOffset(); + // sets caret position (also highlights that line) + viewer.setSelectedRange(targetLineFirstMatchStart, 0); + // does horizontal scrolling if necessary to reveal 1st occurrence in target line + viewer.revealRange(targetLineFirstMatchStart, targetLineFirstMatch.getLength()); - StyledString styledString = highlightMatches(document.get(start, end-start)); - details.setText(styledString.getString()); - details.setStyleRanges(styledString.getStyleRanges()); - details.setTopIndex(Math.max(line - contextStartLine - numLines/2, 0)); + StyledString styledString = highlightMatches(document.get(start, contextLenght)); + viewer.getTextWidget().setStyleRanges(styledString.getStyleRanges()); return; } catch (BadLocationException e) { } @@ -1003,7 +1140,7 @@ private void refreshDetails() { } } //empty selection or some error: - details.setText(EMPTY_STRING); + viewer.setDocument(null); } } @@ -1011,7 +1148,8 @@ private void refreshDetails() { * Computes how many lines of text can be displayed in the details section. */ private int computeLines() { - if (details!=null && !details.isDisposed()) { + StyledText details; + if (viewer!=null && !(details = viewer.getTextWidget()).isDisposed()) { int lineHeight = details.getLineHeight(); int areaHeight = details.getClientArea().height; return (areaHeight + lineHeight - 1) / lineHeight; @@ -1034,47 +1172,6 @@ private StyledString highlightMatches(String visibleText) { return styledText; } -// Version using sourceviewer -// private void refreshDetails() { -// if (details!=null && list!=null && !list.getTable().isDisposed()) { -// if (documents==null) { -// documents = new DocumentFetcher(); -// } -// IStructuredSelection sel = (IStructuredSelection) list.getSelection(); -// if (sel!=null && !sel.isEmpty()) { -// //Not empty selection -// LineItem item = (LineItem) sel.getFirstElement(); -// IDocument document = documents.getDocument(item.getFile()); -// try { -// int line = item.getLineNumber()-1; //in document lines are 0 based. In search 1 based. -// int start = document.getLineOffset(Math.max(line-2, 0)); -// int end = document.getLength(); -// try { -// end = document.getLineOffset(line+3); -// } catch (BadLocationException e) { -// //Presumably line number is past the end of document. -// //ignore. -// } -// details.setDocument(document, start, end-start); -// -// String visibleText = document.get(start, end-start); -// List matches = getQuery().findAll(visibleText); -// Region visibleRegion = new Region(start, end-start); -// TextPresentation presentation = new TextPresentation(visibleRegion, 20); -// presentation.setDefaultStyleRange(new StyleRange(0, document.getLength(), null, null)); -// for (TextRange m : matches) { -// presentation.addStyleRange(new StyleRange(m.start+start, m.len, null, YELLOW)); -// } -// details.changeTextPresentation(presentation, true); -// -// return; -// } catch (BadLocationException e) { -// } -// } -// details.setDocument(null); -// } -// } - /** * Handle selection in the items list by updating labels of selected and * unselected items and refresh the details field using the selection. @@ -1471,4 +1568,67 @@ public QuickTextQuery getQuery() { return searcher.getQuery(); } + /** + * A painter that does 'current line' highlighting (background color) but for single fixed line. + *

+ * This class piggybacks on {@link CursorLinePainter} instance already added to viewer's widget, which knows what color + * to use and does all the necessary repainting. This class only forces the background color to be used also for one + * fixed line in addition to current line = line where caret is currently located (done by CursorLinePainter). + * Background color for this fixed line never changes and so CursorLinePainter's repainting code, although not + * knowing about this fixed line at all, produces correct visual results - target line always highlighted. + * + * @see CursorLinePainter + */ + private class FixedLinePainter implements IPainter, LineBackgroundListener { + + private CursorLinePainter cursorLinePainter; + private int lineOffset; + private Color highlightColor; + + public void setTargetLineOffset(int lineOffset) { + this.lineOffset = lineOffset; + } + + // CursorLinePainter adds itself as line LineBackgroundListener lazily + private boolean cursorLinePainterInstalled() { + if (cursorLinePainter == null) { + cursorLinePainter = viewer.getTextWidget().getTypedListeners(ST.LineGetBackground, CursorLinePainter.class).findFirst().orElse(null); + return cursorLinePainter != null; + } + return true; + } + + @Override + public void lineGetBackground(LineBackgroundEvent event) { + if (cursorLinePainterInstalled()) { + cursorLinePainter.lineGetBackground(event); + if (event.lineBackground != null) { + // piggyback on CursorLinePainter knowing proper color + highlightColor = event.lineBackground; + } else if (lineOffset == event.lineOffset) { // target line + event.lineBackground = highlightColor; + } + } + } + + @Override + public void dispose() { + } + + @Override + public void paint(int reason) { + // no custom painting here, cursorLinePainter (also being registered as IPainter) does all the work + // according to background color set in lineGetBackground() + } + + @Override + public void deactivate(boolean redraw) { + } + + @Override + public void setPositionManager(IPaintPositionManager manager) { + } + + } + } From b7a217d89361a1bb7fdde2dcc4d27b2644ac806c Mon Sep 17 00:00:00 2001 From: Jozef Tomek Date: Sat, 4 Jan 2025 19:50:02 +0100 Subject: [PATCH 2/6] Simplify FixedLinePainter into FixedLineHighlighter --- .../internal/ui/QuickSearchDialog.java | 145 +++++++----------- 1 file changed, 55 insertions(+), 90 deletions(-) diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java index 0b78da3eb03..a8829b211fc 100644 --- a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java @@ -21,6 +21,13 @@ package org.eclipse.text.quicksearch.internal.ui; import static org.eclipse.jface.resource.JFaceResources.TEXT_FONT; +import static org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE; +import static org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE_COLOR; +import static org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER_COLOR; +import static org.eclipse.ui.texteditor.AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND; +import static org.eclipse.ui.texteditor.AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT; +import static org.eclipse.ui.texteditor.AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND; +import static org.eclipse.ui.texteditor.AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT; import java.util.ArrayList; import java.util.Arrays; @@ -46,14 +53,11 @@ import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.layout.GridDataFactory; -import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.preference.PreferenceConverter; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.CursorLinePainter; import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.IPaintPositionManager; -import org.eclipse.jface.text.IPainter; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.source.CompositeRuler; import org.eclipse.jface.text.source.ISharedTextColors; @@ -80,7 +84,6 @@ import org.eclipse.swt.accessibility.AccessibleEvent; import org.eclipse.swt.custom.LineBackgroundEvent; import org.eclipse.swt.custom.LineBackgroundListener; -import org.eclipse.swt.custom.ST; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.custom.StyleRange; import org.eclipse.swt.custom.StyledText; @@ -135,8 +138,6 @@ import org.eclipse.ui.internal.WorkbenchImages; import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin; import org.eclipse.ui.progress.UIJob; -import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants; -import org.eclipse.ui.texteditor.AbstractTextEditor; import org.eclipse.ui.texteditor.SourceViewerDecorationSupport; import org.osgi.framework.FrameworkUtil; @@ -382,7 +383,7 @@ public void update(ViewerCell cell) { private SourceViewer viewer; private LineNumberRulerColumn lineNumberColumn; - private FixedLinePainter targetLinePainter; + private FixedLineHighlighter targetLineHighlighter; private final IPropertyChangeListener preferenceChangeListener = this::handlePropertyChangeEvent; private DocumentFetcher documents; @@ -980,11 +981,7 @@ private void createDetailsArea(Composite parent) { viewer = new SourceViewer(viewerParent, new CompositeRuler(), SWT.H_SCROLL | SWT.V_SCROLL | SWT.READ_ONLY); viewer.getTextWidget().setFont(JFaceResources.getFont(TEXT_FONT)); - - lineNumberColumn = new LineNumberRulerColumn(); - viewer.addVerticalRulerColumn(lineNumberColumn); - setColors(false); - createLinesHighlightingDecorations(); + createViewerDecorations(); list.addSelectionChangedListener(event -> refreshDetails()); @@ -996,16 +993,15 @@ public void controlResized(ControlEvent e) { }); } - private void setColors(boolean refresh) { + private void setColors() { RGB background = null; RGB foreground = null; var textWidget = viewer.getTextWidget(); - var preferenceStore = EditorsUI.getPreferenceStore(); ISharedTextColors sharedColors = EditorsUI.getSharedTextColors(); - var isUsingSystemBackground = preferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT); + var isUsingSystemBackground = EditorsUI.getPreferenceStore().getBoolean(PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT); if (!isUsingSystemBackground) { - background = getColorFromStore(preferenceStore, AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND); + background = getColorFromStore(PREFERENCE_COLOR_BACKGROUND); } if (background != null) { var color = sharedColors.getColor(background); @@ -1016,36 +1012,41 @@ private void setColors(boolean refresh) { lineNumberColumn.setBackground(null); } - var isUsingSystemForeground = preferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT); + var isUsingSystemForeground = EditorsUI.getPreferenceStore().getBoolean(PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT); if (!isUsingSystemForeground) { - foreground = getColorFromStore(preferenceStore, AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND); + foreground = getColorFromStore(PREFERENCE_COLOR_FOREGROUND); } if (foreground != null) { textWidget.setForeground(sharedColors.getColor(foreground)); } else { textWidget.setForeground(null); } + } - var lineNumbersColor = getColorFromStore(EditorsUI.getPreferenceStore(), AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER_COLOR); - if (lineNumbersColor == null) { - lineNumbersColor = new RGB(0, 0, 0); - } - lineNumberColumn.setForeground(sharedColors.getColor(lineNumbersColor)); + private Color getLineNumbersColor() { + var lineNumbersColor = getColorFromStore(EDITOR_LINE_NUMBER_RULER_COLOR); + return EditorsUI.getSharedTextColors().getColor(lineNumbersColor == null ? new RGB(0, 0, 0) : lineNumbersColor); + } - if (refresh) { - textWidget.redraw(); - lineNumberColumn.redraw(); - } + private Color getTargetLineHighlightColor() { + RGB background = getColorFromStore(EDITOR_CURRENT_LINE_COLOR); + ISharedTextColors sharedColors = EditorsUI.getSharedTextColors(); + return sharedColors.getColor(background); } + private void createViewerDecorations() { + lineNumberColumn = new LineNumberRulerColumn(); + lineNumberColumn.setForeground(getLineNumbersColor()); + viewer.addVerticalRulerColumn(lineNumberColumn); - private void createLinesHighlightingDecorations() { var sourceViewerDecorationSupport = new SourceViewerDecorationSupport(viewer, null, null, EditorsUI.getSharedTextColors()); - sourceViewerDecorationSupport.setCursorLinePainterPreferenceKeys(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE_COLOR); + sourceViewerDecorationSupport.setCursorLinePainterPreferenceKeys(EDITOR_CURRENT_LINE, EDITOR_CURRENT_LINE_COLOR); sourceViewerDecorationSupport.install(EditorsUI.getPreferenceStore()); - targetLinePainter = new FixedLinePainter(); - viewer.addPainter(targetLinePainter); - viewer.getTextWidget().addLineBackgroundListener(targetLinePainter); + targetLineHighlighter = new FixedLineHighlighter(); + targetLineHighlighter.highlightColor = getTargetLineHighlightColor(); + viewer.getTextWidget().addLineBackgroundListener(targetLineHighlighter); + + setColors(); } private void handlePropertyChangeEvent(PropertyChangeEvent event) { @@ -1053,22 +1054,23 @@ private void handlePropertyChangeEvent(PropertyChangeEvent event) { return; } var prop = event.getProperty(); - if (prop.equals(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER_COLOR) - || prop.equals(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT) - || prop.equals(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND) - || prop.equals(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT) - || prop.equals(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND)) { - setColors(true); - } - if (prop.equals(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE) - || prop.equals(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE_COLOR)) { - targetLinePainter.highlightColor = null; - targetLinePainter.cursorLinePainter = null; + if (PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT.equals(prop) + || PREFERENCE_COLOR_BACKGROUND.equals(prop) + || PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT.equals(prop) + || PREFERENCE_COLOR_FOREGROUND.equals(prop)) { + setColors(); + viewer.getTextWidget().redraw(); + } else if (EDITOR_LINE_NUMBER_RULER_COLOR.equals(prop)) { + lineNumberColumn.setForeground(getLineNumbersColor()); + lineNumberColumn.redraw(); + } else if (EDITOR_CURRENT_LINE_COLOR.equals(prop)) { + targetLineHighlighter.highlightColor = getTargetLineHighlightColor(); viewer.getTextWidget().redraw(); } } - private RGB getColorFromStore(IPreferenceStore store, String key) { + private RGB getColorFromStore(String key) { + var store = EditorsUI.getPreferenceStore(); RGB rgb = null; if (store.contains(key)) { if (store.isDefault(key)) { @@ -1116,7 +1118,7 @@ private void refreshDetails() { viewer.setDocument(document); viewer.setVisibleRegion(start, contextLenght); - targetLinePainter.setTargetLineOffset(item.getOffset() - start); + targetLineHighlighter.setTargetLineOffset(item.getOffset() - start); // center target line in the displayed area IRegion rangeEndLineInfo = document.getLineInformation(Math.min(displayedEndLine, document.getNumberOfLines() - 1)); @@ -1126,11 +1128,12 @@ private void refreshDetails() { var targetLineFirstMatch = getQuery().findFirst(document.get(item.getOffset(), contextLenght - (item.getOffset() - start))); int targetLineFirstMatchStart = item.getOffset() + targetLineFirstMatch.getOffset(); - // sets caret position (also highlights that line) + // sets caret position viewer.setSelectedRange(targetLineFirstMatchStart, 0); // does horizontal scrolling if necessary to reveal 1st occurrence in target line viewer.revealRange(targetLineFirstMatchStart, targetLineFirstMatch.getLength()); + // above setVisibleRegion() call makes these ranges to be aligned with content of text widget StyledString styledString = highlightMatches(document.get(start, contextLenght)); viewer.getTextWidget().setStyleRanges(styledString.getStyleRanges()); return; @@ -1569,19 +1572,14 @@ public QuickTextQuery getQuery() { } /** - * A painter that does 'current line' highlighting (background color) but for single fixed line. - *

- * This class piggybacks on {@link CursorLinePainter} instance already added to viewer's widget, which knows what color - * to use and does all the necessary repainting. This class only forces the background color to be used also for one - * fixed line in addition to current line = line where caret is currently located (done by CursorLinePainter). - * Background color for this fixed line never changes and so CursorLinePainter's repainting code, although not - * knowing about this fixed line at all, produces correct visual results - target line always highlighted. + * A line background listener that provides the color that is used for current line highlighting (what + * {@link CursorLinePainter} does) but for single fixed line only and does so always regardless of show current + * line highlighting on/off preference. * * @see CursorLinePainter */ - private class FixedLinePainter implements IPainter, LineBackgroundListener { + private static class FixedLineHighlighter implements LineBackgroundListener { - private CursorLinePainter cursorLinePainter; private int lineOffset; private Color highlightColor; @@ -1589,46 +1587,13 @@ public void setTargetLineOffset(int lineOffset) { this.lineOffset = lineOffset; } - // CursorLinePainter adds itself as line LineBackgroundListener lazily - private boolean cursorLinePainterInstalled() { - if (cursorLinePainter == null) { - cursorLinePainter = viewer.getTextWidget().getTypedListeners(ST.LineGetBackground, CursorLinePainter.class).findFirst().orElse(null); - return cursorLinePainter != null; - } - return true; - } - @Override public void lineGetBackground(LineBackgroundEvent event) { - if (cursorLinePainterInstalled()) { - cursorLinePainter.lineGetBackground(event); - if (event.lineBackground != null) { - // piggyback on CursorLinePainter knowing proper color - highlightColor = event.lineBackground; - } else if (lineOffset == event.lineOffset) { // target line - event.lineBackground = highlightColor; - } + if (lineOffset == event.lineOffset) { + event.lineBackground = highlightColor; } } - @Override - public void dispose() { - } - - @Override - public void paint(int reason) { - // no custom painting here, cursorLinePainter (also being registered as IPainter) does all the work - // according to background color set in lineGetBackground() - } - - @Override - public void deactivate(boolean redraw) { - } - - @Override - public void setPositionManager(IPaintPositionManager manager) { - } - } } From 937ad6ef437486f0071e46f322fb556a26d355f7 Mon Sep 17 00:00:00 2001 From: Jozef Tomek Date: Sun, 29 Dec 2024 01:51:07 +0100 Subject: [PATCH 3/6] New extension point for Quick Search result text viewers --- .../META-INF/MANIFEST.MF | 6 +- .../icons/full/obj16/file_obj.png | Bin 0 -> 356 bytes .../icons/full/obj16/file_obj@2x.png | Bin 0 -> 1045 bytes .../plugin.properties | 8 + .../org.eclipse.text.quicksearch/plugin.xml | 15 + .../schema/textViewers.exsd | 236 +++++++++ .../quicksearch/ISourceViewerConfigurer.java | 45 ++ .../text/quicksearch/ITextViewerCreator.java | 108 ++++ .../quicksearch/SourceViewerConfigurer.java | 436 ++++++++++++++++ .../text/quicksearch/SourceViewerHandle.java | 305 +++++++++++ .../internal/core/QuickTextQuery.java | 2 +- .../ui/DefaultSourceViewerCreator.java | 43 ++ .../internal/ui/ExtensionsRegistry.java | 104 ++++ .../internal/ui/IViewerDescriptor.java | 38 ++ .../internal/ui/QuickSearchActivator.java | 207 +++++++- .../internal/ui/QuickSearchDialog.java | 488 ++++++++++-------- .../internal/ui/QuickSearchMessages.java | 34 ++ .../ui/QuickSearchMessages.properties | 2 + .../internal/ui/QuickSearchPluginImages.java | 76 +++ .../internal/ui/ViewerDescriptor.java | 145 ++++++ .../internal/util/DocumentFetcher.java | 42 +- .../META-INF/MANIFEST.MF | 3 +- .../plugin.properties | 1 + .../org.eclipse.ui.genericeditor/plugin.xml | 14 + .../GenericEditorViewerCreator.java | 157 ++++++ 25 files changed, 2286 insertions(+), 229 deletions(-) create mode 100644 bundles/org.eclipse.text.quicksearch/icons/full/obj16/file_obj.png create mode 100644 bundles/org.eclipse.text.quicksearch/icons/full/obj16/file_obj@2x.png create mode 100644 bundles/org.eclipse.text.quicksearch/schema/textViewers.exsd create mode 100644 bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/ISourceViewerConfigurer.java create mode 100644 bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/ITextViewerCreator.java create mode 100644 bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/SourceViewerConfigurer.java create mode 100644 bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/SourceViewerHandle.java create mode 100644 bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/DefaultSourceViewerCreator.java create mode 100644 bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/ExtensionsRegistry.java create mode 100644 bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/IViewerDescriptor.java create mode 100644 bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchMessages.java create mode 100644 bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchMessages.properties create mode 100644 bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchPluginImages.java create mode 100644 bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/ViewerDescriptor.java create mode 100644 bundles/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/quicksearch/GenericEditorViewerCreator.java diff --git a/bundles/org.eclipse.text.quicksearch/META-INF/MANIFEST.MF b/bundles/org.eclipse.text.quicksearch/META-INF/MANIFEST.MF index f158e0beb15..f77a54d5be8 100644 --- a/bundles/org.eclipse.text.quicksearch/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.text.quicksearch/META-INF/MANIFEST.MF @@ -12,12 +12,14 @@ Require-Bundle: org.eclipse.ui;bundle-version="[3.113.0,4.0.0)", org.eclipse.ui.editors;bundle-version="[3.11.0,4.0.0)", org.eclipse.jface;bundle-version="[3.17.0,4.0.0)", org.eclipse.jface.text;bundle-version="[3.15.0,4.0.0)", - org.apache.ant;bundle-version="[1.10.0,2.0.0)" + org.apache.ant;bundle-version="[1.10.0,2.0.0)", + org.eclipse.core.contenttype;bundle-version="[3.9.0,4.0.0)" Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-21 Bundle-Vendor: %providerName Bundle-Localization: plugin -Export-Package: org.eclipse.text.quicksearch.internal.core;x-internal:=true, +Export-Package: org.eclipse.text.quicksearch, + org.eclipse.text.quicksearch.internal.core;x-internal:=true, org.eclipse.text.quicksearch.internal.core.pathmatch;x-internal:=true, org.eclipse.text.quicksearch.internal.core.preferences;x-internal:=true, org.eclipse.text.quicksearch.internal.core.priority;x-friends:="org.eclipse.text.quicksearch.tests", diff --git a/bundles/org.eclipse.text.quicksearch/icons/full/obj16/file_obj.png b/bundles/org.eclipse.text.quicksearch/icons/full/obj16/file_obj.png new file mode 100644 index 0000000000000000000000000000000000000000..723da774b96792f82e99d35d856b300a52253ea1 GIT binary patch literal 356 zcmV-q0h|7bP)sleS|(y(M31ajf;xm%qa_5G-()vRSeB)n-eXE7<3hNUqnO@C5F{V_XmFQ z4!S#TUHHOtbI!xVxp_00Sw6QZU>dzv-g7N}E-`RnZNm>DlDk$Gmv+JLn8N(jfOB5N z*pEmaTn@JmhqhG;98;ttWbpWS&wIrR|4+49B!If*r!rR1{$?8 zzJ9_~m`4T-V;3VYj4vo0^ua4#aOE~ui#C>XXIR*%#e3$Ffx{yO!|xy!=8*wS-^buH zNW6gUnMVdxt%|o%kP7q2fKt`)`r#*D!1l}|1G1{)W$35EJTf3vj`2L0#24h%9@ga! zRzw?1TPN}S59W~panHch`y>_Sk%63C$A6jG5B>$N>hUf5y?w3#0000VP)JMBz6siv{fszJnXP$FmOR*-`rY&j_*Djs5+vExmf zG{%I3ii$@L8xuii-mbNTU2i(GP;!?&^W5$4omajONAKME`25e4ni<3`WK*-s&u&#< zVMc{px?PF6*bt~%{jw#m5}jC%DbSeDK8dP7zfpl?R1NC9cG;56x%;fdVsrqFjAAmb z{i5Xpvk?ub_aw9xZ&Tf*#Lw7BbRb1O37V0JXo~T0izVXII#BP&R@ ze=K3|NW$)61^EN2EWejt>c^XfMu{hPkqQr>k=0s>s5lI&`u>s`asPowV=GWtQ|K8@ z;+}Q}KMscRZC?;yKJdx!Wu6WGSK=xCB!0p?DZ%nWx{@gBKH(}54(UNj?=BdJxDIn* z4SLV-(2OlZX%44}$q7tHR_8QJ+6@`u$c2UYA)1qs_t`xR_87`xf^me_VGaHXvwsZ+&vWP}R-qeTfm&F`kbMyYmU;A>=Fm$H zw0k6hE?pe=)G>KJ9GPHw+yy*d3#b#R-WKAto8@sAnDAIZ9arj1j_f;Es3J>t&Fo9a zP-NHXrrk2$HoVDGC3!w5dRQKJ0jJvz>Zn|2Zh8aF`9R3a@&vEIS3qz%KpmFqjGf$o zh1`IN+`uTgfe~^8T5dQT!dmdJi9SMn9IV`8U_&8F=VUN>xPQAK&)9SE>I z6K?^F-~_dQT4Qc{1I>BYWRT@?7cl+i0=0WmV{Uo_&G~U(h~;q?FgV8s}I!n(K$nJKr{XfD#sEYlKuA>p0eF{sN?MKg75l6__`;6ztX4j%yrg@2+QLx zptVebqCXL33$Dxi*U@7X$T8X$$?yMpw)d{#S+esu{!SO=nd{81D9ht6pf>wK<;ZY5 z7@aYe$6a8^I0foOw%8WUJf%tGE1)z4@j>twP>hP%4?(35e){nr-|Nr6M`+~N_=!u= P00000NkvXXu0mjfC@uLo literal 0 HcmV?d00001 diff --git a/bundles/org.eclipse.text.quicksearch/plugin.properties b/bundles/org.eclipse.text.quicksearch/plugin.properties index 1e5d5466c06..2e2ab285f50 100644 --- a/bundles/org.eclipse.text.quicksearch/plugin.properties +++ b/bundles/org.eclipse.text.quicksearch/plugin.properties @@ -9,6 +9,7 @@ # # Contributors: # Pivotal Inc - initial API and implementation +# Jozef Tomek - text viewers extension ############################################################################### pluginName= Quick Search @@ -18,3 +19,10 @@ quickSearch.label= Quick Search quickSearch.ellipsis= &Quick Search... quickSearch.tooltip=Search for a text pattern in the workspace quickAccess.category.label=File content +textViewers=Text Viewer +defaultSourceViewer=Text Viewer + +QuickSearchActivator.targetIdAttributeMissing=target id attribute ''{0}'' missing +QuickSearchActivator.contentTypeNotFound=content type ''{0}'' not found +QuickSearchActivator.targetNotFound=target ''{0}'' not found +QuickSearchActivator.unexpectedTag=expected tag ''{1}'', got ''{0}'' \ No newline at end of file diff --git a/bundles/org.eclipse.text.quicksearch/plugin.xml b/bundles/org.eclipse.text.quicksearch/plugin.xml index 40f76acfad9..c62697f329d 100644 --- a/bundles/org.eclipse.text.quicksearch/plugin.xml +++ b/bundles/org.eclipse.text.quicksearch/plugin.xml @@ -10,10 +10,13 @@ Contributors: Pivotal Inc - Initial API and implementation + Jozef Tomek - text viewers extension --> + + + + + + + + diff --git a/bundles/org.eclipse.text.quicksearch/schema/textViewers.exsd b/bundles/org.eclipse.text.quicksearch/schema/textViewers.exsd new file mode 100644 index 00000000000..04bf6695ef2 --- /dev/null +++ b/bundles/org.eclipse.text.quicksearch/schema/textViewers.exsd @@ -0,0 +1,236 @@ + + + + + + + + + This extension point allows a plug-in to register text viewers for specific types of content in which quick search matches were found. +Since viewers don't have a default constructor, the extension point must implement the factory interface for viewers <samp>org.eclipse.text.quicksearch.ITextViewerCreator</samp> that then creates viewers wrapped by implementation of <samp>org.eclipse.text.quicksearch.ITextViewerCreator.ITextViewerHandle</samp> providing methods necessary for presenting quick search results. + + + + + + + + + + + + + + + + + + a fully qualified identifier of the target extension point + + + + + + + an optional identifier of the extension instance + + + + + + + an optional name of the extension instance + + + + + + + + + + + + + + + + + + + + a unique identifier that can be used to reference the viewer + + + + + + + a fully qualified name of a class that implements a factory for the +text viewer handle and implements <samp>org.eclipse.text.quicksearch.ITextViewerCreator</samp> + + + + + + + + + + a relative name of the icon that will be used in the UI to present this viewer. + + + + + + + + + + an optional comma separated list of file extensions e.g. "java, html". If not defined, some <code>contentTypeBinding</code> must be defined + + + + + + + a translatable label that will be used in the UI for this viewer. + + + + + + + + + + Editor id to consider while searching for a viewer matching a content type. +If the specified "linked" editor has content type associations, they will be also considered as possible bindings for this viewer. This is useful in cases where viewer supports same content types as "linked" editor (like in generic editor). + + + + + + + + + + + + + + + + A <code>contentTypeBinding</code> binds a text viewer to a content type. If no binding is defined, <code>viewer</code> needs to declare <code>extensions</code> + + + + + + + The id of a content type defined using the <code>org.eclipse.core.contenttype.contentTypes</code> extension point. + + + + + + + + + + The id of a text viewer defined using the <code>viewer</code> element of this extension point (i.e. <code>org.eclipse.text.quicksearch.DefaultSourceViewer</code>) + + + + + + + + + + + + + + + 1.3 + + + + + + + + + The following is an example of a text viewer for text content type: +<p> +<pre> +<extension point="org.eclipse.text.quicksearch.textViewers"> + <viewer + id="org.eclipse.text.quicksearch.DefaultSourceViewer" + class="org.eclipse.text.quicksearch.internal.ui.DefaultSourceViewerCreator"/> + <contentTypeBinding + contentTypeId="org.eclipse.core.runtime.text" + sourceViewerId="org.eclipse.text.quicksearch.DefaultSourceViewer"> + </contentTypeBinding> +</extension> +</pre> +</p> +The following is an example of a text viewer for text content type & content types for which referenced editor is registered: +<p> +<pre> +<extension point="org.eclipse.text.quicksearch.textViewers"> + <viewer + id="org.eclipse.ui.genericeditor.quicksearch.GenericEditorViewer" + class="org.eclipse.ui.internal.genericeditor.quicksearch.GenericEditorViewerCreator" + linkedEditor="org.eclipse.ui.genericeditor.GenericEditor"/> + <contentTypeBinding + contentTypeId="org.eclipse.core.runtime.text" + sourceViewerId="org.eclipse.ui.genericeditor.quicksearch.GenericEditorViewer"> + </contentTypeBinding> +</extension> +</pre> +</p> + + + + + + + + + The contributed class must implement <code>org.eclipse.text.quicksearch.ITextViewerCreator</code> + + + + + + + + + The Quick Search plugin provides <code>org.eclipse.text.quicksearch.internal.ui.DefaultSourceViewerCreator</code> supplying default text viewer with no syntax coloring. + + + + + + + + + Copyright (c) 2025 Contributors to the Eclipse Foundation<br> + +See the NOTICE file(s) distributed with this work for additional +information regarding copyright ownership. + +This program and the accompanying materials are made available under the +terms of the Eclipse Public License 2.0 which is available at +<a href="https://www.eclipse.org/legal/epl-2.0">https://www.eclipse.org/legal/epl-2.0.html</a> + +SPDX-License-Identifier: EPL-2.0 + +Contributors: + Jozef Tomek - initial API and implementation + + + + diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/ISourceViewerConfigurer.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/ISourceViewerConfigurer.java new file mode 100644 index 00000000000..d9f36a8905d --- /dev/null +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/ISourceViewerConfigurer.java @@ -0,0 +1,45 @@ +package org.eclipse.text.quicksearch; + +import org.eclipse.jface.text.source.IChangeRulerColumn; +import org.eclipse.jface.text.source.SourceViewer; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.text.quicksearch.SourceViewerConfigurer.ISourceViewerCreator; +import org.eclipse.text.quicksearch.SourceViewerHandle.FixedLineHighlighter; + +/** + * Factory used by {@link SourceViewerHandle} responsible for creation and necessary setup of source viewers so that + * they provide common aspects of quicksearch text viewers: + *
    + *
  • vertical ruler with line numbers supporting selected match line number highlighting + *
  • selected match line highlighting + *
  • current (caret position) line highlighting + *
  • colors and fonts consistent with text viewers/editors preferences + *
+ * + * Actual source viewer instance creation is delegated to provided {@link ISourceViewerCreator}. + * @since 1.3 + * @see SourceViewerConfigurer + */ +public interface ISourceViewerConfigurer { + + /** + * Creates, configures and returns source viewer that provides common aspects of quicksearch text viewers. Delegates + * source viewer creation to {@link ISourceViewerCreator} provided on initialization. + * @param parent the parent SWT control for the viewer + * @return configured source viewer + * @see ISourceViewerCreator + */ + T getSourceViewer(Composite parent); + + /** + * Returns change ruler column installed to the viewer. + * @return viewer's change ruler column + */ + IChangeRulerColumn getChangeRulerColumn(); + + /** + * Returns fixed line highlighter installed to the viewer. + * @return viewer's fixed line highlighter + */ + FixedLineHighlighter getMatchLineHighlighter(); +} diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/ITextViewerCreator.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/ITextViewerCreator.java new file mode 100644 index 00000000000..4c4cfec1ad9 --- /dev/null +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/ITextViewerCreator.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * https://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Jozef Tomek - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch; + +import org.eclipse.core.resources.IFile; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.swt.custom.StackLayout; +import org.eclipse.swt.custom.StyleRange; +import org.eclipse.swt.widgets.Composite; + +/** + * A factory object for instances of ITextViewerHandle that wraps text viewer used for presenting + * quicksearch matches within content of their containing document. + * + * @see ITextViewerHandle + * @since 1.3 + */ +public interface ITextViewerCreator { + + /** + * Creates a new text viewer under the given SWT parent control and returns handle for it. This method must + * always create new text viewer & handle when called since each opened quick search dialog will call this method + * passing unique parent to display the viewer in the dialog. Dialog then re-uses returned handle + * (viewer) when this ITextViewerCreator is again chosen to present some (possibly other) quicksearch + * match. If other ITextViewerCreator contributor is chosen to present a match in the same dialog, + * this viewer is just made not visible (by means of using {@link StackLayout} in the parent composites hierarchy). + *

+ * It's recommended to use {@link SourceViewerHandle} configured by {@link SourceViewerConfigurer} since they + * implement common aspects expected from quicksearch text viewers. + * + * @param parent the SWT parent control under which to create the viewer's SWT control + * @return a new text viewer handle + */ + ITextViewerHandle createTextViewer(Composite parent); + + /** + * Text viewer handle is a wrapper for a text viewer displayed in quicksearch dialog to present matches. + * + * @see ITextViewerCreator#createTextViewer(Composite) + */ + public interface ITextViewerHandle { + + /** + * Returns number of lines of text the client area of this handle's text viewer would display with its current + * size. Value is used to calculate regions subsequently passed to + * {@link #focusMatch(IRegion, IRegion, int, IRegion) focusMatch()}. + * + * @return number of visible text lines + * @see #focusMatch(IRegion, IRegion, int, IRegion) + */ + int getVisibleLines(); + + /** + * Sets input to the text viewer of this handle. Only if different document is to be presented in this handle's + * viewer, this method is called with new input document. If different match in the already displayed document + * is to be presented, only {@link #focusMatch(IRegion, IRegion, int, IRegion) focusMatch()} is called. + * + * @param document the document to present in the viewer (content of the file) + * @param allMatchesStyles common text styles to apply in order to highlight all found matches in the document + * @param file the file contents of which is the document + * @see #focusMatch(IRegion, IRegion, int, IRegion) + */ + void setViewerInput(IDocument document, StyleRange[] allMatchesStyles, IFile file); + + /** + * Focuses on the specific match (selected amongst quicksearch results) within content of its containing + * document, which was previously set as viewer's input by call to + * {@link #setViewerInput(IDocument, StyleRange[], IFile) setViewerInput()}. This is expected to mean:
+ *

    + *
  • limiting presented content of the input document (reachable by scrolling) to visibleRegion + *
  • presenting revealedRange part of the input document (vertical scrolling) + *
  • highlighting line of the match described by matchLine + *
  • making sure actual match described by matchRange is visible (horizontal scrolling) + *
  • positioning caret to the start of the match described by matchRange + *
+ * + * Passed regions are adjusted to have selected match vertically centered in the viewer of this handle since + * they are derived from the number of lines presented by the viewer (previous call to + * {@link #getVisibleLines()}). + *

+ * All parameters are coordinates within the input document previously set by call to + * {@link #setViewerInput(IDocument, StyleRange[], IFile) setViewerInput()}. + * + * @param visibleRegion bounds of the portion of input document to present in the viewer + * @param revealedRange bounds of the part of input document to reveal (for vertical scroll) + * @param matchLine line number in the input document where match is located + * @param matchRange bounds of the focused match in the input document (for horizontal scroll) + * @see #getVisibleLines() + * @see #setViewerInput(IDocument, StyleRange[], IFile) + */ + void focusMatch(IRegion visibleRegion, IRegion revealedRange, int matchLine, IRegion matchRange); + } + +} diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/SourceViewerConfigurer.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/SourceViewerConfigurer.java new file mode 100644 index 00000000000..ff40fa2ca5a --- /dev/null +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/SourceViewerConfigurer.java @@ -0,0 +1,436 @@ +/******************************************************************************* + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * https://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Jozef Tomek - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch; + +import static org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE; +import static org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE_COLOR; +import static org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER_COLOR; +import static org.eclipse.ui.texteditor.AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND; +import static org.eclipse.ui.texteditor.AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT; +import static org.eclipse.ui.texteditor.AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND; +import static org.eclipse.ui.texteditor.AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT; +import static org.eclipse.ui.texteditor.AbstractTextEditor.PREFERENCE_COLOR_SELECTION_BACKGROUND; +import static org.eclipse.ui.texteditor.AbstractTextEditor.PREFERENCE_COLOR_SELECTION_BACKGROUND_SYSTEM_DEFAULT; +import static org.eclipse.ui.texteditor.AbstractTextEditor.PREFERENCE_COLOR_SELECTION_FOREGROUND; +import static org.eclipse.ui.texteditor.AbstractTextEditor.PREFERENCE_COLOR_SELECTION_FOREGROUND_SYSTEM_DEFAULT; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.internal.text.source.DiffPainter; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.preference.PreferenceConverter; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.text.source.CompositeRuler; +import org.eclipse.jface.text.source.IChangeRulerColumn; +import org.eclipse.jface.text.source.ISharedTextColors; +import org.eclipse.jface.text.source.LineNumberChangeRulerColumn; +import org.eclipse.jface.text.source.SourceViewer; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.text.quicksearch.SourceViewerHandle.FixedLineHighlighter; +import org.eclipse.ui.editors.text.EditorsUI; +import org.eclipse.ui.texteditor.SourceViewerDecorationSupport; + +/** + * Implementation of source viewer factory for {@link SourceViewerHandle} that creates and does necessary setup of source viewer so that + * it provides common aspects of quicksearch text viewers: + *

    + *
  • vertical ruler with line numbers supporting selected match line number highlighting + *
  • selected match line highlighting + *
  • current (caret position) line highlighting + *
  • colors and fonts consistent with text viewers/editors preferences + *
+ * + * Actual source viewer instance creation is delegated to provided {@link ISourceViewerCreator}. + * @since 1.3 + */ +public class SourceViewerConfigurer implements ISourceViewerConfigurer { + + public static final int VIEWER_STYLES = SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.BORDER | SWT.FULL_SELECTION | SWT.READ_ONLY; + private static final String DISABLE_CSS = "org.eclipse.e4.ui.css.disabled"; //$NON-NLS-1$ + + private final ISourceViewerCreator fViewerCreator; + private final IPropertyChangeListener fPropertyChangeListener = this::handlePreferenceStoreChanged; + private final LineNumberChangeRulerColumn fLineNumberRulerColumn = new LineNumberChangeRulerColumn(EditorsUI.getSharedTextColors()); + private final FixedLineHighlighter fMatchLineHighlighter = new FixedLineHighlighter(); + + protected final CompositeRuler fVerticalRuler = new CompositeRuler(); + protected final IPreferenceStore fPreferenceStore; + protected T fSourceViewer; + private Font fFont; + + /** + * Creates new instance that will use viewerCreator as a source viewer factory and common store + * to get preferences from for the viewer configuration. + * @param viewerCreator the factory for actual source viewer + */ + public SourceViewerConfigurer(ISourceViewerCreator viewerCreator) { + this(viewerCreator, EditorsUI.getPreferenceStore()); + } + + /** + * Creates new instance that will use viewerCreator as a source viewer factory and store + * to get preferences from for the viewer configuration. + * @param viewerCreator the factory for actual source viewer + * @param store the preference store to use for configuration + */ + public SourceViewerConfigurer(ISourceViewerCreator viewerCreator, IPreferenceStore store) { + Assert.isNotNull(viewerCreator); + Assert.isNotNull(store); + fViewerCreator = viewerCreator; + fPreferenceStore = store; + } + + @Override + public T getSourceViewer(Composite parent) { + fSourceViewer = fViewerCreator.createSourceViewer(parent, fVerticalRuler, VIEWER_STYLES); + Assert.isNotNull(fSourceViewer); + initialize(); + return fSourceViewer; + } + + @Override + public IChangeRulerColumn getChangeRulerColumn() { + return fLineNumberRulerColumn; + } + + @Override + public FixedLineHighlighter getMatchLineHighlighter() { + return fMatchLineHighlighter; + } + + /** + * Initializes created source viewer to provide common aspects of quicksearch text viewers. + */ + protected void initialize() { + fSourceViewer.getTextWidget().setData(DISABLE_CSS, Boolean.TRUE); + fPreferenceStore.addPropertyChangeListener(fPropertyChangeListener); + + fSourceViewer.addVerticalRulerColumn(fLineNumberRulerColumn); + + initializeColors(); + initializeFont(); + + fSourceViewer.getTextWidget().addLineBackgroundListener(fMatchLineHighlighter); + + var currentLineDecorations = getSourceViewerDecorationSupport(); + currentLineDecorations.install(fPreferenceStore); + + updateContributedRulerColumns((CompositeRuler) fVerticalRuler); + + fSourceViewer.getControl().addDisposeListener(e -> { + currentLineDecorations.uninstall(); + fPreferenceStore.removePropertyChangeListener(fPropertyChangeListener); + }); + } + + /** + * Creates decoration support for the created source viewer. Default implementation returns {@link SourceViewerDecorationSupport}. + * @return decoration support for the source viewer + */ + protected SourceViewerDecorationSupport getSourceViewerDecorationSupport() { + var support = new SourceViewerDecorationSupport(fSourceViewer, null, null, EditorsUI.getSharedTextColors()); + support.setCursorLinePainterPreferenceKeys(EDITOR_CURRENT_LINE, EDITOR_CURRENT_LINE_COLOR); + return support; + } + + /** + * Initializes the fore- and background colors of the created source viewer for both normal and selected text, color + * for line numbers (in vertical ruler) and color for line highlighting. + */ + protected void initializeColors() { + + IPreferenceStore store= getPreferenceStore(); + if (store != null) { + ISharedTextColors sharedColors = EditorsUI.getSharedTextColors(); + var textWidget = fSourceViewer.getTextWidget(); + + // ----------- foreground color -------------------- + Color color= store.getBoolean(PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT) + ? null + : sharedColors.getColor(getColorFromStore(store, PREFERENCE_COLOR_FOREGROUND)); + textWidget.setForeground(color); + + // ---------- background color ---------------------- + color= store.getBoolean(PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT) + ? null + : sharedColors.getColor(getColorFromStore(store, PREFERENCE_COLOR_BACKGROUND)); + textWidget.setBackground(color); + + // ----------- selection foreground color -------------------- + color= store.getBoolean(PREFERENCE_COLOR_SELECTION_FOREGROUND_SYSTEM_DEFAULT) + ? null + : sharedColors.getColor(getColorFromStore(store, PREFERENCE_COLOR_SELECTION_FOREGROUND)); + textWidget.setSelectionForeground(color); + + // ---------- selection background color ---------------------- + color= store.getBoolean(PREFERENCE_COLOR_SELECTION_BACKGROUND_SYSTEM_DEFAULT) + ? null + : sharedColors.getColor(getColorFromStore(store, PREFERENCE_COLOR_SELECTION_BACKGROUND)); + textWidget.setSelectionBackground(color); + + fLineNumberRulerColumn.setBackground(textWidget.getBackground()); + + // ----------- line numbers color -------------------- + var lineNumbersColor = getColorFromStore(store, EDITOR_LINE_NUMBER_RULER_COLOR); + if (lineNumbersColor == null) { + lineNumbersColor = new RGB(0, 0, 0); + } + fLineNumberRulerColumn.setForeground(sharedColors.getColor(lineNumbersColor)); + + // ----------- line highlight (background) color -------------------- + color = sharedColors.getColor(getColorFromStore(store, EDITOR_CURRENT_LINE_COLOR)); + fLineNumberRulerColumn.setChangedColor(sharedColors.getColor(reverseInterpolateDiffPainterColor(textWidget.getBackground(), color))); + if (fMatchLineHighlighter != null) { + fMatchLineHighlighter.setHighlightColor(color); + } + } + } + + /** + * Returns color that when set to {@link DiffPainter} makes it to paint quick diff annotation in a change ruler + * column with background color backgroundColor with color equal to finalColor. + * @param backgroundColor background color of the change ruler column + * @param finalColor final desired color of the diff annotation + * @return color to set to diff painter to make it to draw annotation with desired color + */ + @SuppressWarnings("restriction") + public static RGB reverseInterpolateDiffPainterColor(Color backgroundColor, Color finalColor) { + RGB baseRGB= finalColor.getRGB(); + RGB background= backgroundColor.getRGB(); + + boolean darkBase= isDark(baseRGB); + boolean darkBackground= isDark(background); + if (darkBase && darkBackground) + background= new RGB(255, 255, 255); + else if (!darkBase && !darkBackground) + background= new RGB(0, 0, 0); + + // reverse interpolate + double scale = 0.6; + double scaleInv = 1.0 - scale; + return new RGB((int) ((baseRGB.red - scale * background.red) / scaleInv), (int) ((baseRGB.green - scale * background.green) / scaleInv), (int) ((baseRGB.blue - scale * background.blue) / scaleInv)); + } + + + // copy-paste of org.eclipse.jface.internal.text.source.DiffPainter.isDark(RGB) + private static boolean isDark(RGB rgb) { + return greyLevel(rgb) > 128; + } + + // copy-paste of org.eclipse.jface.internal.text.source.DiffPainter.greyLevel(RGB) + private static double greyLevel(RGB rgb) { + if (rgb.red == rgb.green && rgb.green == rgb.blue) + return rgb.red; + return (0.299 * rgb.red + 0.587 * rgb.green + 0.114 * rgb.blue + 0.5); + } + + /** + * Gets color preference configured in store under key. + * @param store preference store + * @param key color preference key + * @return color preference + */ + protected RGB getColorFromStore(IPreferenceStore store, String key) { + RGB rgb = null; + if (store.contains(key)) { + if (store.isDefault(key)) { + rgb = PreferenceConverter.getDefaultColor(store, key); + } else { + rgb = PreferenceConverter.getColor(store, key); + } + } + return rgb; + } + + private void initializeFont() { + + boolean isSharedFont= true; + Font font= null; + String symbolicFontName= getSymbolicFontName(); + + if (symbolicFontName != null) + font= JFaceResources.getFont(symbolicFontName); + else if (fPreferenceStore != null) { + // Backward compatibility + if (fPreferenceStore.contains(JFaceResources.TEXT_FONT) && !fPreferenceStore.isDefault(JFaceResources.TEXT_FONT)) { + FontData data= PreferenceConverter.getFontData(fPreferenceStore, JFaceResources.TEXT_FONT); + + if (data != null) { + isSharedFont= false; + font= new Font(fSourceViewer.getTextWidget().getDisplay(), data); + } + } + } + if (font == null) + font= JFaceResources.getTextFont(); + + if (!font.equals(fSourceViewer.getTextWidget().getFont())) { + setFont(font); + + disposeFont(); + if (!isSharedFont) + fFont= font; + } else if (!isSharedFont) { + font.dispose(); + } + } + + // Sets the font for the source viewer sustaining selection and scroll position. + private void setFont(Font font) { + if (fSourceViewer.getDocument() != null) { + + ISelectionProvider provider= fSourceViewer.getSelectionProvider(); + ISelection selection= provider.getSelection(); + int topIndex= fSourceViewer.getTopIndex(); + + Control parent= fSourceViewer.getControl(); + parent.setRedraw(false); + + fSourceViewer.getTextWidget().setFont(font); + + fVerticalRuler.setFont(font); + + provider.setSelection(selection); + fSourceViewer.setTopIndex(topIndex); + + if (parent instanceof Composite composite) { + composite.layout(true); + } + + parent.setRedraw(true); + } else { + fSourceViewer.getTextWidget().setFont(font); + fVerticalRuler.setFont(font); + } + } + + private void disposeFont() { + if (fFont != null) { + fFont.dispose(); + fFont= null; + } + } + + /** + * Returns the property preference key for the viewer font. + *

+ * If this configurer provides non-null {@link #getSymbolicFontName() symbolicFontName} then this name + * is returned, otherwise {@link JFaceResources#TEXT_FONT} is returned. + *

+ * + * @return key in the preference store for the font used in the source viewer + */ + private final String getFontPropertyPreferenceKey() { + String symbolicFontName= getSymbolicFontName(); + if (symbolicFontName != null) + return symbolicFontName; + return JFaceResources.TEXT_FONT; + } + + /** + * Returns custom symbolic name for the font to be used in the source viewer. By default returns null + * in which case default text viewer font is used. + * @return custom symbolic font name or null for default font use + */ + protected String getSymbolicFontName() { + return null; + } + + /** + * Returns preference store used for source viewer configuration. + * + * @return this configurer's preference store which may be null + */ + protected final IPreferenceStore getPreferenceStore() { + return fPreferenceStore; + } + + /** + * Adds additional ruler contributions to the vertical ruler. + *

+ * Default implementation does nothing, clients may replace.

+ * + * @param ruler the composite ruler to add contributions to + */ + protected void updateContributedRulerColumns(CompositeRuler ruler) { + // no-op in default implementation + } + + /** + * Handles a property change event describing a change of the preference store and updates the preference related + * source viewer properties. + *

+ * Subclasses may extend. + *

+ * + * @param event the property change event + */ + protected void handlePreferenceStoreChanged(PropertyChangeEvent event) { + String property= event.getProperty(); + + if (getFontPropertyPreferenceKey().equals(property)) { + initializeFont(); + return; + } + + if (property != null) { + switch (property) { + case PREFERENCE_COLOR_FOREGROUND: + case PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT: + case PREFERENCE_COLOR_BACKGROUND: + case PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT: + case PREFERENCE_COLOR_SELECTION_FOREGROUND: + case PREFERENCE_COLOR_SELECTION_FOREGROUND_SYSTEM_DEFAULT: + case PREFERENCE_COLOR_SELECTION_BACKGROUND: + case PREFERENCE_COLOR_SELECTION_BACKGROUND_SYSTEM_DEFAULT: + case EDITOR_LINE_NUMBER_RULER_COLOR: + initializeColors(); + return; + default: + break; + } + } + } + + /** + * Factory creating actual source viewer subsequently configured by {@link SourceViewerConfigurer} to provide + * common aspects of quicksearch text viewers. + * @see SourceViewerConfigurer + * @since 1.3 + */ + public interface ISourceViewerCreator { + + /** + * Creates new source viewer with verticalRuler and styles under passed + * parent. + * @param parent the parent SWT control for the viewer + * @param verticalRuler the vertical ruler to add to the created viewer + * @param styles the SWT styles for the viewer + * @return + */ + T createSourceViewer(Composite parent, CompositeRuler verticalRuler, int styles); + } + +} diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/SourceViewerHandle.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/SourceViewerHandle.java new file mode 100644 index 00000000000..47153921c72 --- /dev/null +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/SourceViewerHandle.java @@ -0,0 +1,305 @@ +/******************************************************************************* + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * https://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Jozef Tomek - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch; + +import java.util.Collections; +import java.util.Iterator; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.CursorLinePainter; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.source.Annotation; +import org.eclipse.jface.text.source.IAnnotationModel; +import org.eclipse.jface.text.source.IAnnotationModelListener; +import org.eclipse.jface.text.source.IChangeRulerColumn; +import org.eclipse.jface.text.source.ILineDiffInfo; +import org.eclipse.jface.text.source.ILineDiffer; +import org.eclipse.jface.text.source.SourceViewer; +import org.eclipse.swt.custom.LineBackgroundEvent; +import org.eclipse.swt.custom.LineBackgroundListener; +import org.eclipse.swt.custom.StyleRange; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.text.quicksearch.ITextViewerCreator.ITextViewerHandle; +import org.eclipse.text.quicksearch.internal.ui.QuickSearchActivator; + +/** + * Implementation of {@link ITextViewerHandle} that provides common aspects expected from quicksearch text viewers: + *
    + *
  • focusing on selected quicksearch match as described in the documentation of + * {@link ITextViewerHandle#focusMatch(IRegion, IRegion, int, IRegion) focusMatch()} + *
  • highlighting all matches in the presented content by applying common text style through + * {@link StyledText#setStyleRange(StyleRange)} on the viewert's text widget. + *
+ *

+ * Highlighting the selected match line is achieved by: + *

    + *
  • highlighting line number in the line numbers vertical ruler column by announcing it as a changed line for the + * quick diff feature (if a viewer provides {@link IChangeRulerColumn}) + *
  • highlighting whole line by using {@link FixedLineHighlighter} (if a viewer provides one) + *
+ *

+ * This class uses {@link SourceViewerConfigurer} that does viewer creation and setup necessary for + * aforementioned aspects. + * + * @since 1.3 + */ +public class SourceViewerHandle implements ITextViewerHandle { + + protected final T fSourceViewer; + protected final IChangeRulerColumn fChangeRulerColumn; + protected final FixedLineChangedAnnotationModel fFixedLineChangeModel; + protected final FixedLineHighlighter fMatchLineHighlighter; + protected StyleRange[] fMatchRanges = null; + + /** + * Creates new instance that will use sourceViewerConfigurer to create viewer under the + * parent. + * + * @param sourceViewerConfigurer the viewer configurer responsible for creation & setup of the viewer + * @param parent the parent SWT control for the viewer + */ + public SourceViewerHandle(ISourceViewerConfigurer sourceViewerConfigurer, Composite parent) { + Assert.isNotNull(sourceViewerConfigurer); + fSourceViewer = sourceViewerConfigurer.getSourceViewer(parent); + Assert.isNotNull(fSourceViewer); + fChangeRulerColumn = sourceViewerConfigurer.getChangeRulerColumn(); + fMatchLineHighlighter = sourceViewerConfigurer.getMatchLineHighlighter(); + if (fChangeRulerColumn != null) { + fFixedLineChangeModel = new FixedLineChangedAnnotationModel(); + fChangeRulerColumn.setModel(fFixedLineChangeModel); + } else { + fFixedLineChangeModel = null; + } + } + + @Override + public int getVisibleLines() { + StyledText details = fSourceViewer.getTextWidget(); + if (details != null && !details.isDisposed()) { + int lineHeight = details.getLineHeight(); + int areaHeight = details.getClientArea().height; + return (areaHeight + lineHeight - 1) / lineHeight; + } + return 0; + } + + @Override + public void setViewerInput(IDocument document, StyleRange[] allMatchesStyles, IFile file) { + fMatchRanges = allMatchesStyles; + fSourceViewer.setInput(document); + } + + @Override + public void focusMatch(IRegion visibleRegion, IRegion revealedRange, int matchLine, IRegion matchRange) { + // limit content of the document that we can scroll to + if (!fSourceViewer.getVisibleRegion().equals(visibleRegion)) { + fSourceViewer.setVisibleRegion(visibleRegion.getOffset(), visibleRegion.getLength()); + } + // scroll to range to be presented + fSourceViewer.revealRange(revealedRange.getOffset(), revealedRange.getLength()); + // sets caret position + fSourceViewer.setSelectedRange(matchRange.getOffset(), 0); + // does horizontal scrolling if necessary to reveal 1st occurrence in target line + fSourceViewer.revealRange(matchRange.getOffset(), matchRange.getLength()); + + if (fMatchLineHighlighter != null) { + try { + fMatchLineHighlighter.setTargetLineOffset(fSourceViewer.getDocument().getLineOffset(matchLine) - visibleRegion.getOffset()); + } catch (BadLocationException e) { + QuickSearchActivator.log(e); + } + } + + if (fFixedLineChangeModel != null) { + fFixedLineChangeModel.selectedMatchLine = matchLine; + fChangeRulerColumn.redraw(); + } + + } + + /** + * Applies all matches highlighting styles previously passed to + * {@link #setViewerInput(IDocument, StyleRange[], IFile) setViewerInput()} method considering projection of + * model (document) ranges to source viewer's text widget ranges. + * + * @see #setViewerInput(IDocument, StyleRange[], IFile) + */ + protected void applyMatchesStyles() { + if (fMatchRanges == null || fMatchRanges.length == 0) { + return; + } + for (StyleRange styleRange : fMatchRanges) { + if (modelRange2WidgetStyleRange(styleRange) instanceof StyleRange range) { + fSourceViewer.getTextWidget().setStyleRange(range); + } + } + } + + private StyleRange modelRange2WidgetStyleRange(StyleRange range) { + IRegion region= fSourceViewer.modelRange2WidgetRange(new Region(range.start, range.length)); + if (region != null) { + StyleRange result= (StyleRange) range.clone(); + result.start= region.getOffset(); + result.length= region.getLength(); + return result; + } + return null; + } + + /** + * Annotation model implementation that announces single configured line as {@link ILineDiffInfo#CHANGED} for + * quick diff feature of the viewer's {@link IChangeRulerColumn}. + */ + public static class FixedLineChangedAnnotationModel implements IAnnotationModel, ILineDiffer { + + protected int selectedMatchLine; + + @Override + public void addAnnotationModelListener(IAnnotationModelListener listener) { + // no-op + } + + @Override + public void removeAnnotationModelListener(IAnnotationModelListener listener) { + // no-op + } + + @Override + public void connect(IDocument document) { + // no-op + } + + @Override + public void disconnect(IDocument document) { + // no-op + } + + @Override + public void addAnnotation(Annotation annotation, Position position) { + // no-op + } + + @Override + public void removeAnnotation(Annotation annotation) { + // no-op + } + + @Override + public Iterator getAnnotationIterator() { + return Collections.emptyIterator(); + } + + @Override + public Position getPosition(Annotation annotation) { + return null; + } + + @Override + public ILineDiffInfo getLineInfo(int line) { + return line == selectedMatchLine ? FixedLineChangedDiffInfo.INSTANCE : null; + } + + @Override + public void revertLine(int line) throws BadLocationException { + // no-op + } + + @Override + public void revertBlock(int line) throws BadLocationException { + // no-op + } + + @Override + public void revertSelection(int line, int nLines) throws BadLocationException { + // no-op + } + + @Override + public int restoreAfterLine(int line) throws BadLocationException { + // no-op + return 0; + } + + } + + private static class FixedLineChangedDiffInfo implements ILineDiffInfo { + + static final FixedLineChangedDiffInfo INSTANCE = new FixedLineChangedDiffInfo(); + + @Override + public int getRemovedLinesBelow() { + return 0; + } + + @Override + public int getRemovedLinesAbove() { + return 0; + } + + @Override + public int getChangeType() { + return CHANGED; + } + + @Override + public boolean hasChanges() { + return true; + } + + @Override + public String[] getOriginalText() { + return new String[0]; + } + + } + + /** + * A line background listener that provides the color that is used for current line highlighting (what + * {@link CursorLinePainter} does) but for single fixed line only and does so always regardless of show current + * line highlighting on/off preference. + * + * @see CursorLinePainter + */ + public static class FixedLineHighlighter implements LineBackgroundListener { + + private int lineOffset = -1; + private Color highlightColor; + + public void setHighlightColor(Color highlightColor) { + this.highlightColor = highlightColor; + } + + public void setTargetLineOffset(int lineOffset) { + this.lineOffset = lineOffset; + } + + @Override + public void lineGetBackground(LineBackgroundEvent event) { + if (lineOffset == event.lineOffset) { + event.lineBackground = highlightColor; + } + } + + } + +} diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/QuickTextQuery.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/QuickTextQuery.java index 552912ab95c..a0ec3f370dd 100644 --- a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/QuickTextQuery.java +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/core/QuickTextQuery.java @@ -165,7 +165,7 @@ public boolean isTrivial() { @Override public String toString() { - return "QTQuery("+orgPattern+", "+(caseInsensitive?"caseSens":"caseInSens")+")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + return "QTQuery("+orgPattern+", "+(caseInsensitive?"caseInSens":"caseSens")+")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ } public List findAll(String text) { diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/DefaultSourceViewerCreator.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/DefaultSourceViewerCreator.java new file mode 100644 index 00000000000..13a87b08fcc --- /dev/null +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/DefaultSourceViewerCreator.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * https://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Jozef Tomek - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.ui; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.source.SourceViewer; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.text.quicksearch.ITextViewerCreator; +import org.eclipse.text.quicksearch.SourceViewerConfigurer; +import org.eclipse.text.quicksearch.SourceViewerHandle; + +/** + * Creates quicksearch text viewer handles that use {@link DefaultSourceViewer}. + * Used as a fallback by Quick Search plugin-in to display text file content if no better viewer is available. + * + * @see ITextViewerCreator + */ +public class DefaultSourceViewerCreator implements ITextViewerCreator { + + @Override + public ITextViewerHandle createTextViewer(Composite parent) { + return new SourceViewerHandle<>(new SourceViewerConfigurer<>(SourceViewer::new), parent) { + @Override + public void focusMatch(IRegion visibleRegion, IRegion revealedRange, int matchLine, IRegion matchRange) { + super.focusMatch(visibleRegion, revealedRange, matchLine, matchRange); + applyMatchesStyles(); + } + }; + } + +} diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/ExtensionsRegistry.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/ExtensionsRegistry.java new file mode 100644 index 00000000000..41c09e3050d --- /dev/null +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/ExtensionsRegistry.java @@ -0,0 +1,104 @@ +package org.eclipse.text.quicksearch.internal.ui; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.StringTokenizer; + +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.content.IContentType; + +class ExtensionsRegistry { + private final static String ID_ATTRIBUTE = "id"; //$NON-NLS-1$ + private final static String EXTENSIONS_ATTRIBUTE = "extensions"; //$NON-NLS-1$ + private final static String CONTENT_TYPE_ID_ATTRIBUTE = "contentTypeId"; //$NON-NLS-1$ + + private HashMap fIdMap; // maps ids to data + private HashMap> fExtensionMap; // multimap: maps extensions to list of data + private HashMap> fContentTypeBindings; // multimap: maps content type bindings to list of data + + void register(IConfigurationElement element, T data) { + String id = element.getAttribute(ID_ATTRIBUTE); + if (id != null) { + if (fIdMap == null) + fIdMap = new HashMap<>(); + fIdMap.put(id, data); + } + + String types = element.getAttribute(EXTENSIONS_ATTRIBUTE); + if (types != null) { + if (fExtensionMap == null) + fExtensionMap = new HashMap<>(); + StringTokenizer tokenizer = new StringTokenizer(types, ","); //$NON-NLS-1$ + while (tokenizer.hasMoreElements()) { + String extension = tokenizer.nextToken().trim(); + List l = fExtensionMap.get(normalizeCase(extension)); + if (l == null) + fExtensionMap.put(normalizeCase(extension), l = new ArrayList<>()); + l.add(data); + } + } + } + + void createBinding(IConfigurationElement element, String idAttributeName) { + String type = element.getAttribute(CONTENT_TYPE_ID_ATTRIBUTE); + String id = element.getAttribute(idAttributeName); + if (id == null) + QuickSearchActivator.log(QuickSearchActivator.getFormattedString("QuickSearchActivator.targetIdAttributeMissing", idAttributeName)); //$NON-NLS-1$ + if (type != null && id != null && fIdMap != null) { + T o = fIdMap.get(id); + if (o != null) { + IContentType ct = QuickSearchActivator.getDefault().getContentType(type); + if (ct != null) { + if (fContentTypeBindings == null) + fContentTypeBindings = new HashMap<>(); + List l = fContentTypeBindings.get(ct); + if (l == null) + fContentTypeBindings.put(ct, l = new ArrayList<>()); + l.add(o); + } else { + QuickSearchActivator.log(QuickSearchActivator.getFormattedString("QuickSearchActivator.contentTypeNotFound", type)); //$NON-NLS-1$ + } + } else { + QuickSearchActivator.log(QuickSearchActivator.getFormattedString("QuickSearchActivator.targetNotFound", id)); //$NON-NLS-1$ + } + } + } + + T search(IContentType type) { + List list = searchAll(type); + return list != null ? list.get(0) : null; + } + + List searchAll(IContentType type) { + if (fContentTypeBindings != null) { + for (; type != null; type = type.getBaseType()) { + List data = fContentTypeBindings.get(type); + if (data != null) + return data; + } + } + return null; + } + + T search(String extension) { + List list = searchAll(extension); + return list != null ? list.get(0) : null; + } + + List searchAll(String extension) { + if (fExtensionMap != null) + return fExtensionMap.get(normalizeCase(extension)); + return null; + } + + Collection getAll() { + return fIdMap == null ? Collections.emptySet() : fIdMap.values(); + } + + private static String normalizeCase(String s) { + return s == null ? null : s.toUpperCase(); + } +} \ No newline at end of file diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/IViewerDescriptor.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/IViewerDescriptor.java new file mode 100644 index 00000000000..090609b7388 --- /dev/null +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/IViewerDescriptor.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * https://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Jozef Tomek - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.ui; + +import org.eclipse.swt.graphics.Image; +import org.eclipse.text.quicksearch.ITextViewerCreator; + +interface IViewerDescriptor { + + /** + * Creates a new viewer creator from this descriptor. + * + * @return a new viewer creator. + */ + ITextViewerCreator getViewerCreator(); + + /** + * Returns label for viewer created by provided viewer creator that will be used in UI. + * + * @return label for provided viewer used in UI + */ + String getLabel(); + + Image getIcon(); +} diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchActivator.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchActivator.java index c862c05edf7..07d5d1f5040 100644 --- a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchActivator.java +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchActivator.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2013-2019 Pivotal Software, Inc. + * Copyright (c) 2013-2025 Pivotal Software, Inc. and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at @@ -9,13 +9,38 @@ * * Contributors: * Pivotal Software, Inc. - initial API and implementation + * Jozef Tomek - text viewers extension *******************************************************************************/ package org.eclipse.text.quicksearch.internal.ui; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.MissingResourceException; +import java.util.Optional; +import java.util.ResourceBundle; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IExtensionRegistry; import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.content.IContentDescription; +import org.eclipse.core.runtime.content.IContentType; +import org.eclipse.core.runtime.content.IContentTypeManager; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.text.quicksearch.internal.core.preferences.QuickSearchPreferences; +import org.eclipse.ui.IEditorDescriptor; +import org.eclipse.ui.IEditorRegistry; +import org.eclipse.ui.PlatformUI; import org.eclipse.ui.plugin.AbstractUIPlugin; import org.osgi.framework.BundleContext; @@ -30,7 +55,20 @@ public class QuickSearchActivator extends AbstractUIPlugin { // The shared instance private static QuickSearchActivator plugin; - private QuickSearchPreferences prefs = null; //Lazy initialized + private static final String TEXT_VIEWERS_EXTENSION_POINT= "textViewers"; //$NON-NLS-1$ + private static final String CONTENT_TYPE_BINDING = "contentTypeBinding"; //$NON-NLS-1$ + private static final String VIEWER_TAG = "viewer"; //$NON-NLS-1$ + private static final String VIEWER_ID_ATTRIBUTE = "viewerId"; //$NON-NLS-1$ + private static final String DEFAULT_CREATOR_CLASS = DefaultSourceViewerCreator.class.getName(); + + private final ExtensionsRegistry fTextViewersRegistry = new ExtensionsRegistry<>(); + + // Lazy initialized + private QuickSearchPreferences prefs = null; + private IContentTypeManager contentTypeManager; + private ResourceBundle resourceBundle; + private boolean registryInitialized; + private IViewerDescriptor defaultViewerDescriptor; /** * The constructor @@ -46,6 +84,7 @@ public QuickSearchActivator() { public void start(BundleContext context) throws Exception { super.start(context); plugin = this; + contentTypeManager = Platform.getContentTypeManager(); } /* @@ -55,6 +94,7 @@ public void start(BundleContext context) throws Exception { @Override public void stop(BundleContext context) throws Exception { plugin = null; + QuickSearchPluginImages.dispose(); super.stop(context); } @@ -82,6 +122,10 @@ public static void log(Throwable exception) { log(createErrorStatus(exception)); } + public static void log(String message) { + log(new Status(IStatus.ERROR, PLUGIN_ID, 0, message, null)); + } + public static void log(IStatus status) { // if (logger == null) { getDefault().getLog().log(status); @@ -102,4 +146,163 @@ public QuickSearchPreferences getPreferences() { return prefs; } + private ResourceBundle getResourceBundle() { + if (resourceBundle == null) + resourceBundle = Platform.getResourceBundle(getBundle()); + return resourceBundle; + } + + static String getFormattedString(String key, String arg) { + try { + return MessageFormat.format(getDefault().getResourceBundle().getString(key), arg); + } catch (MissingResourceException e) { + return "!" + key + "!"; //$NON-NLS-2$ //$NON-NLS-1$ + } + } + + private static String getFormattedString(String key, String arg0, String arg1) { + try { + return MessageFormat.format(getDefault().getResourceBundle().getString(key), arg0, arg1); + } catch (MissingResourceException e) { + return "!" + key + "!";//$NON-NLS-2$ //$NON-NLS-1$ + } + } + + private void initializeExtensionsRegistry() { + if (!registryInitialized) { + registerExtensions(); + Assert.isNotNull(defaultViewerDescriptor); + registryInitialized = true; + } + } + + private void registerExtensions() { + IExtensionRegistry registry = Platform.getExtensionRegistry(); + + // collect all descriptors which define the source viewer extension point + IConfigurationElement[] elements = registry.getConfigurationElementsFor(PLUGIN_ID, TEXT_VIEWERS_EXTENSION_POINT); + for (IConfigurationElement element : elements) { + String name = element.getName(); + if (!CONTENT_TYPE_BINDING.equals(name)) { + if (!VIEWER_TAG.equals(name)) + log(getFormattedString("QuickSearchActivator.unexpectedTag", name, VIEWER_TAG)); //$NON-NLS-1$ + var viewerDescriptor = new ViewerDescriptor(element); + fTextViewersRegistry.register(element, viewerDescriptor); + if (DEFAULT_CREATOR_CLASS.equals(viewerDescriptor.getViewerClass())) { + defaultViewerDescriptor = viewerDescriptor; + } + } + } + for (IConfigurationElement element : elements) { + if (CONTENT_TYPE_BINDING.equals(element.getName())) + fTextViewersRegistry.createBinding(element, VIEWER_ID_ATTRIBUTE); + } + } + + IContentType getContentType(String contentTypeIdentifier) { + return contentTypeManager.getContentType(contentTypeIdentifier); + } + + IViewerDescriptor getDefaultViewer() { + initializeExtensionsRegistry(); + return defaultViewerDescriptor; + } + + List getViewers(IFile file) { + initializeExtensionsRegistry(); + if (file == null) { + return List.of(getDefaultViewer()); + } + return findContentViewerDescriptor(file); + } + + private List findContentViewerDescriptor(IFile input) { + LinkedHashSet result = new LinkedHashSet<>(); + + String name = input.getName(); + + IContentDescription cDescr = null; + try { + cDescr = input.getContentDescription(); + } catch (CoreException e) { + log(e); + } + IContentType ctype = cDescr == null ? null : cDescr.getContentType(); + if (ctype == null) { + ctype = contentTypeManager.findContentTypeFor(name); + } + if (ctype != null) { + List list = fTextViewersRegistry.searchAll(ctype); + if (list != null) { + result.addAll(list); + } + } + + String type = input.getFileExtension(); + if (type != null) { + List list = fTextViewersRegistry.searchAll(type); + if (list != null) + result.addAll(list); + } + + Set editorLinkedDescriptors = findEditorLinkedDescriptors(name, ctype, false); + result.addAll(editorLinkedDescriptors); + + if (result.isEmpty() || result.size() == 1) { + // single candidate should always be the default viewer, but in case it's not, add default viewer as well + result.add(defaultViewerDescriptor); + } else { + // more than 1 candidate, make sure default viewer is the last one + result.remove(defaultViewerDescriptor); + result.add(defaultViewerDescriptor); + } + return new ArrayList<>(result); + } + + /** + * @param fileName file name for content in search match preview panel + * @param contentType possible content type for content in search match preview panel, may be null + * @param firstIsEnough stop searching once first match is found + * @return set of descriptors which could be found for given content type via "linked" editor + */ + private Set findEditorLinkedDescriptors(String fileName, IContentType contentType, + boolean firstIsEnough) { + if (fileName == null && contentType == null) { + return Collections.emptySet(); + } + if (contentType == null) { + contentType = contentTypeManager.findContentTypeFor(fileName); + } + + LinkedHashSet viewers = fTextViewersRegistry.getAll().stream() + .filter(vd -> vd.getLinkedEditorId() != null).collect(Collectors.toCollection(LinkedHashSet::new)); + if (viewers.isEmpty()) { + return Collections.emptySet(); + } + + IEditorRegistry editorReg = PlatformUI.getWorkbench().getEditorRegistry(); + LinkedHashSet result = new LinkedHashSet<>(); + IEditorDescriptor[] editors = editorReg.getEditors(fileName, contentType); + for (IEditorDescriptor ed : editors) { + addLinkedEditorContentTypes(viewers, firstIsEnough, ed.getId(), result); + if (firstIsEnough && !result.isEmpty()) { + return result; + } + } + return result; + } + + private void addLinkedEditorContentTypes(LinkedHashSet viewers, boolean firstIsEnough, + String editorId, Set result) { + Stream stream = viewers.stream().filter(vd -> editorId.equals(vd.getLinkedEditorId())); + if (firstIsEnough) { + Optional first = stream.findFirst(); + if (first.isPresent()) { + result.add(first.get()); + } + } else { + stream.collect(Collectors.toCollection(() -> result)); + } + } + } diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java index a8829b211fc..95af9fa231d 100644 --- a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java @@ -17,76 +17,68 @@ * Simon Muschel - bug 258493 * Kris De Volder Copied and modified from org.eclipse.ui.dialogs.FilteredResourcesSelectionDialog * to create QuickSearchDialog + * Jozef Tomek - text viewers extension *******************************************************************************/ package org.eclipse.text.quicksearch.internal.ui; -import static org.eclipse.jface.resource.JFaceResources.TEXT_FONT; -import static org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE; -import static org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE_COLOR; -import static org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER_COLOR; -import static org.eclipse.ui.texteditor.AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND; -import static org.eclipse.ui.texteditor.AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT; -import static org.eclipse.ui.texteditor.AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND; -import static org.eclipse.ui.texteditor.AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Deque; +import java.util.HashMap; +import java.util.IdentityHashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.core.commands.AbstractHandler; import org.eclipse.core.commands.ExecutionEvent; import org.eclipse.core.commands.IHandler; import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.ICoreRunnable; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.GroupMarker; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.LegacyActionTools; import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.action.Separator; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.layout.GridDataFactory; -import org.eclipse.jface.preference.PreferenceConverter; -import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.jface.layout.RowLayoutFactory; import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.CursorLinePainter; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; -import org.eclipse.jface.text.source.CompositeRuler; -import org.eclipse.jface.text.source.ISharedTextColors; -import org.eclipse.jface.text.source.LineNumberRulerColumn; -import org.eclipse.jface.text.source.SourceViewer; -import org.eclipse.jface.util.IPropertyChangeListener; -import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.text.Region; import org.eclipse.jface.viewers.ILazyContentProvider; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.StyledCellLabelProvider; import org.eclipse.jface.viewers.StyledString; -import org.eclipse.jface.viewers.StyledString.Styler; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerCell; +import org.eclipse.jface.viewers.StyledString.Styler; import org.eclipse.osgi.util.NLS; import org.eclipse.search.internal.ui.text.EditorOpener; import org.eclipse.swt.SWT; import org.eclipse.swt.accessibility.ACC; import org.eclipse.swt.accessibility.AccessibleAdapter; import org.eclipse.swt.accessibility.AccessibleEvent; -import org.eclipse.swt.custom.LineBackgroundEvent; -import org.eclipse.swt.custom.LineBackgroundListener; +import org.eclipse.swt.custom.CLabel; import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.custom.StackLayout; import org.eclipse.swt.custom.StyleRange; -import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.KeyAdapter; @@ -98,13 +90,11 @@ import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; @@ -117,11 +107,12 @@ import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; +import org.eclipse.text.quicksearch.ITextViewerCreator.ITextViewerHandle; import org.eclipse.text.quicksearch.internal.core.LineItem; import org.eclipse.text.quicksearch.internal.core.QuickTextQuery; -import org.eclipse.text.quicksearch.internal.core.QuickTextQuery.TextRange; import org.eclipse.text.quicksearch.internal.core.QuickTextSearchRequestor; import org.eclipse.text.quicksearch.internal.core.QuickTextSearcher; +import org.eclipse.text.quicksearch.internal.core.QuickTextQuery.TextRange; import org.eclipse.text.quicksearch.internal.core.pathmatch.ResourceMatchers; import org.eclipse.text.quicksearch.internal.util.DocumentFetcher; import org.eclipse.ui.ActiveShellExpression; @@ -131,14 +122,12 @@ import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.dialogs.SelectionStatusDialog; -import org.eclipse.ui.editors.text.EditorsUI; import org.eclipse.ui.handlers.IHandlerActivation; import org.eclipse.ui.handlers.IHandlerService; import org.eclipse.ui.internal.IWorkbenchGraphicConstants; import org.eclipse.ui.internal.WorkbenchImages; import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin; import org.eclipse.ui.progress.UIJob; -import org.eclipse.ui.texteditor.SourceViewerDecorationSupport; import org.osgi.framework.FrameworkUtil; /** @@ -354,15 +343,13 @@ public void update(ViewerCell cell) { private TableViewer list; - private MenuManager menuManager; + private MenuManager viewMenuManager; private MenuManager contextMenuManager; private boolean multi; - private ToolBar toolBar; - - private ToolItem toolItem; + private ToolBar viewMenuToolBar; private Label progressLabel; @@ -381,11 +368,6 @@ public void update(ViewerCell cell) { private QuickTextSearcher searcher; - private SourceViewer viewer; - private LineNumberRulerColumn lineNumberColumn; - private FixedLineHighlighter targetLineHighlighter; - private final IPropertyChangeListener preferenceChangeListener = this::handlePropertyChangeEvent; - private DocumentFetcher documents; @@ -404,6 +386,18 @@ public void update(ViewerCell cell) { private Combo searchIn; private Label listLabel; + private IDocument lastDocument; + private Composite viewersParent; + private StackLayout viewersParentLayout; + private final Map viewerWrappers = new IdentityHashMap<>(); + private TextViewerWrapper currentViewerWrapper; + private static final Map SELECTED_VIEWERS = new HashMap<>(); + + private List currentDescriptors = Collections.emptyList(); + private CLabel viewerSelectionLabel; + private MenuManager viewersSelectionMenuManager; + private ToolBar viewersSelectionToolBar; + /** * Creates a new instance of the class. * @@ -423,7 +417,6 @@ public QuickSearchDialog(IWorkbenchWindow window) { MAX_LINE_LEN = QuickSearchActivator.getDefault().getPreferences().getMaxLineLen(); MAX_RESULTS = QuickSearchActivator.getDefault().getPreferences().getMaxResults(); progressJob.setSystem(true); - EditorsUI.getPreferenceStore().addPropertyChangeListener(preferenceChangeListener); } /* @@ -567,10 +560,12 @@ public boolean close() { showViewHandler.getHandler().dispose(); showViewHandler = null; } - if (menuManager != null) - menuManager.dispose(); + if (viewMenuManager != null) + viewMenuManager.dispose(); if (contextMenuManager != null) contextMenuManager.dispose(); + if (viewersSelectionMenuManager != null) + viewersSelectionMenuManager.dispose(); storeDialog(getDialogSettings()); if (searcher!=null) { searcher.cancel(); @@ -690,14 +685,14 @@ private Label createLabels(Composite parent) { } private void createViewMenu(Composite parent) { - toolBar = new ToolBar(parent, SWT.FLAT); - toolItem = new ToolItem(toolBar, SWT.PUSH, 0); + viewMenuToolBar = new ToolBar(parent, SWT.FLAT); + ToolItem toolItem = new ToolItem(viewMenuToolBar, SWT.PUSH, 0); GridData data = new GridData(); data.horizontalAlignment = GridData.END; - toolBar.setLayoutData(data); + viewMenuToolBar.setLayoutData(data); - toolBar.addMouseListener(new MouseAdapter() { + viewMenuToolBar.addMouseListener(new MouseAdapter() { @Override public void mouseDown(MouseEvent e) { showViewMenu(); @@ -715,9 +710,9 @@ public void widgetSelected(SelectionEvent e) { } }); - menuManager = new MenuManager(); + viewMenuManager = new MenuManager(); - fillViewMenu(menuManager); + fillViewMenu(viewMenuManager); IHandlerService service = PlatformUI.getWorkbench() .getService(IHandlerService.class); @@ -748,10 +743,10 @@ protected void fillViewMenu(IMenuManager menuManager) { } private void showViewMenu() { - Menu menu = menuManager.createContextMenu(getShell()); - Rectangle bounds = toolItem.getBounds(); + Menu menu = viewMenuManager.createContextMenu(getShell()); + Rectangle bounds = viewMenuToolBar.getItem(0).getBounds(); Point topLeft = new Point(bounds.x, bounds.y + bounds.height); - topLeft = toolBar.toDisplay(topLeft); + topLeft = viewMenuToolBar.toDisplay(topLeft); menu.setLocation(topLeft.x, topLeft.y); menu.setVisible(true); } @@ -972,192 +967,202 @@ protected void dispose() { blankImage.dispose(); blankImage = null; } - EditorsUI.getPreferenceStore().removePropertyChangeListener(preferenceChangeListener); + if (documents != null) { + documents.destroy(); + } } private void createDetailsArea(Composite parent) { - var viewerParent = new Canvas(parent, SWT.BORDER); - viewerParent.setLayout(new FillLayout()); + Composite detailsComposite = new Composite(parent, SWT.NONE); + detailsComposite.setLayout(GridLayoutFactory.fillDefaults().equalWidth(true).spacing(0, 0).create()); - viewer = new SourceViewer(viewerParent, new CompositeRuler(), SWT.H_SCROLL | SWT.V_SCROLL | SWT.READ_ONLY); - viewer.getTextWidget().setFont(JFaceResources.getFont(TEXT_FONT)); - createViewerDecorations(); + final Composite toolbarComposite = new Composite(detailsComposite, SWT.NONE); + toolbarComposite.setLayoutData(GridDataFactory.fillDefaults().hint(SWT.DEFAULT, 24).grab(true, false).create()); + toolbarComposite.setLayout(RowLayoutFactory.fillDefaults().create()); - list.addSelectionChangedListener(event -> refreshDetails()); + viewerSelectionLabel = new CLabel(toolbarComposite, SWT.NONE); - viewer.getTextWidget().addControlListener(new ControlAdapter() { + viewersSelectionToolBar = new ToolBar(toolbarComposite, SWT.FLAT); + final ToolItem toolItem = new ToolItem(viewersSelectionToolBar, SWT.PUSH, 0); + toolItem.setImage(WorkbenchImages.getImage(IWorkbenchGraphicConstants.IMG_LCL_VIEW_MENU)); + toolItem.setToolTipText(QuickSearchMessages.QuickSearchDialog_switchButtonTooltip); + toolItem.addSelectionListener(new SelectionAdapter() { @Override - public void controlResized(ControlEvent e) { - refreshDetails(); + public void widgetSelected(SelectionEvent e) { + showViewersSelectionMenu(); + } + }); + viewersSelectionToolBar.addMouseListener(new MouseAdapter() { + @Override + public void mouseDown(MouseEvent e) { + showViewersSelectionMenu(); } }); - } - private void setColors() { - RGB background = null; - RGB foreground = null; - var textWidget = viewer.getTextWidget(); - ISharedTextColors sharedColors = EditorsUI.getSharedTextColors(); + viewersSelectionMenuManager = new MenuManager(); - var isUsingSystemBackground = EditorsUI.getPreferenceStore().getBoolean(PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT); - if (!isUsingSystemBackground) { - background = getColorFromStore(PREFERENCE_COLOR_BACKGROUND); - } - if (background != null) { - var color = sharedColors.getColor(background); - textWidget.setBackground(color); - lineNumberColumn.setBackground(color); - } else { - textWidget.setBackground(null); - lineNumberColumn.setBackground(null); - } + viewersParent = new Composite(detailsComposite, SWT.NONE); + viewersParent.setLayoutData(GridDataFactory.fillDefaults().grab(true, true).create()); + viewersParentLayout = new StackLayout(); + viewersParent.setLayout(viewersParentLayout); - var isUsingSystemForeground = EditorsUI.getPreferenceStore().getBoolean(PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT); - if (!isUsingSystemForeground) { - foreground = getColorFromStore(PREFERENCE_COLOR_FOREGROUND); - } - if (foreground != null) { - textWidget.setForeground(sharedColors.getColor(foreground)); - } else { - textWidget.setForeground(null); - } - } + replaceViewer(QuickSearchActivator.getDefault().getDefaultViewer()); + Assert.isNotNull(currentViewerWrapper, "Default source viewer initialization failed"); //$NON-NLS-1$ - private Color getLineNumbersColor() { - var lineNumbersColor = getColorFromStore(EDITOR_LINE_NUMBER_RULER_COLOR); - return EditorsUI.getSharedTextColors().getColor(lineNumbersColor == null ? new RGB(0, 0, 0) : lineNumbersColor); - } - - private Color getTargetLineHighlightColor() { - RGB background = getColorFromStore(EDITOR_CURRENT_LINE_COLOR); - ISharedTextColors sharedColors = EditorsUI.getSharedTextColors(); - return sharedColors.getColor(background); + list.addSelectionChangedListener(event -> refreshDetails()); } - private void createViewerDecorations() { - lineNumberColumn = new LineNumberRulerColumn(); - lineNumberColumn.setForeground(getLineNumbersColor()); - viewer.addVerticalRulerColumn(lineNumberColumn); + private TextViewerWrapper replaceViewer(IViewerDescriptor descriptor) { + if (descriptor == null) { + // should never happen + descriptor = QuickSearchActivator.getDefault().getDefaultViewer(); + } + if (currentViewerWrapper != null && currentViewerWrapper.descriptor == descriptor) { + return currentViewerWrapper; + } + var wrapper = wrapViewer(descriptor); + if (currentViewerWrapper != null && wrapper != null) { + wrapper.viewerParent.setSize(currentViewerWrapper.viewerParent.getSize()); + } + currentViewerWrapper = wrapper; + currentViewerWrapper.showInViewersParent(); - var sourceViewerDecorationSupport = new SourceViewerDecorationSupport(viewer, null, null, EditorsUI.getSharedTextColors()); - sourceViewerDecorationSupport.setCursorLinePainterPreferenceKeys(EDITOR_CURRENT_LINE, EDITOR_CURRENT_LINE_COLOR); - sourceViewerDecorationSupport.install(EditorsUI.getPreferenceStore()); - targetLineHighlighter = new FixedLineHighlighter(); - targetLineHighlighter.highlightColor = getTargetLineHighlightColor(); - viewer.getTextWidget().addLineBackgroundListener(targetLineHighlighter); + viewerSelectionLabel.setText(currentViewerWrapper.descriptor.getLabel()); + viewerSelectionLabel.setImage(currentViewerWrapper.descriptor.getIcon()); + viewerSelectionLabel.getParent().layout(); - setColors(); + return currentViewerWrapper; } - private void handlePropertyChangeEvent(PropertyChangeEvent event) { - if (viewer == null) { - return; - } - var prop = event.getProperty(); - if (PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT.equals(prop) - || PREFERENCE_COLOR_BACKGROUND.equals(prop) - || PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT.equals(prop) - || PREFERENCE_COLOR_FOREGROUND.equals(prop)) { - setColors(); - viewer.getTextWidget().redraw(); - } else if (EDITOR_LINE_NUMBER_RULER_COLOR.equals(prop)) { - lineNumberColumn.setForeground(getLineNumbersColor()); - lineNumberColumn.redraw(); - } else if (EDITOR_CURRENT_LINE_COLOR.equals(prop)) { - targetLineHighlighter.highlightColor = getTargetLineHighlightColor(); - viewer.getTextWidget().redraw(); + private void showViewersSelectionMenu() { + viewersSelectionMenuManager.removeAll(); + + // add default viewer option + LineItem item = (LineItem) ((IStructuredSelection) list.getSelection()).getFirstElement(); + var defaultDescriptor = currentDescriptors.getFirst(); + var defaultViewerItem = new DefaultViewerSelectionAction(defaultDescriptor, item); + viewersSelectionMenuManager.add(defaultViewerItem); + viewersSelectionMenuManager.add(new Separator()); + + // add options for all contributed viewers + viewersSelectionMenuManager.add(new GroupMarker("viewers")); //$NON-NLS-1$ // TODO + for (IViewerDescriptor viewerDescriptor : currentDescriptors) { + viewersSelectionMenuManager.add( + new ViewerSelectionAction(viewerDescriptor.getLabel(), viewerDescriptor, item)); } + + Menu menu = viewersSelectionMenuManager.createContextMenu(getShell()); + + Rectangle bounds = viewersSelectionToolBar.getItem(0).getBounds(); + Point topLeft = new Point(bounds.x, bounds.y + bounds.height); + topLeft = viewersSelectionToolBar.toDisplay(topLeft); + menu.setLocation(topLeft.x, topLeft.y); + menu.setVisible(true); } - private RGB getColorFromStore(String key) { - var store = EditorsUI.getPreferenceStore(); - RGB rgb = null; - if (store.contains(key)) { - if (store.isDefault(key)) { - rgb = PreferenceConverter.getDefaultColor(store, key); - } else { - rgb = PreferenceConverter.getColor(store, key); + private TextViewerWrapper wrapViewer(IViewerDescriptor descriptor) { + TextViewerWrapper wrapper = viewerWrappers.get(descriptor); + if (wrapper == null) { + Composite viewerParent = new Composite(viewersParent, SWT.NONE); + viewerParent.setLayout(new FillLayout()); + // ask descriptor to create extension's creator and creator to provide handle for text viewer + if (descriptor.getViewerCreator().createTextViewer(viewerParent) instanceof ITextViewerHandle handle) { + var wrp = wrapper = new TextViewerWrapper(descriptor, handle, viewerParent); + viewerWrappers.put(descriptor, wrapper); + viewerParent.addControlListener(new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + if (currentViewerWrapper == wrp && !currentViewerWrapper.shouldIgnoreResize.get()) { + LineItem item = (LineItem) ((IStructuredSelection) list.getSelection()).getFirstElement(); + if (item != null) { + showInViewer(item, item.getFile(), lastDocument); + } + } + } + }); + } else { // viewer descriptor failed to provide source viewer creator, we return null + viewerParent.dispose(); } - } - return rgb; + } // else we're re-using viewer that was already initialized + return wrapper != null + ? wrapper + : viewerWrappers.get(QuickSearchActivator.getDefault().getDefaultViewer()); // always expected non NULL } private void refreshDetails() { - if (viewer!=null && list!=null && !list.getTable().isDisposed()) { + if (currentViewerWrapper!=null && list!=null && !list.getTable().isDisposed()) { if (documents==null) { documents = new DocumentFetcher(); } IStructuredSelection sel = (IStructuredSelection) list.getSelection(); - if (sel==null || sel.isEmpty()) { - viewer.setDocument(null); - } else { + if (sel!=null && !sel.isEmpty()) { //Not empty selection - final int context = 100; // number of lines before and after match to include in preview - int numLines = computeLines(); - if (numLines > 0) { - LineItem item = (LineItem) sel.getFirstElement(); - IDocument document = documents.getDocument(item.getFile()); - if (document!=null) { - try { - int line = item.getLineNumber()-1; //in document lines are 0 based. In search 1 based. - int contextStartLine = Math.max(line-(numLines-1)/2 - context, 0); - int start = document.getLineOffset(contextStartLine); - int displayedEndLine = line + numLines/2; - int end = document.getLength(); - if (displayedEndLine + context <= document.getNumberOfLines()) { - try { - IRegion lineInfo = document.getLineInformation(displayedEndLine + context); - end = lineInfo.getOffset() + lineInfo.getLength(); - } catch (BadLocationException e) { - //Presumably line number is past the end of document. - //ignore. - } - } - int contextLenght = end-start; - - viewer.setDocument(document); - viewer.setVisibleRegion(start, contextLenght); - - targetLineHighlighter.setTargetLineOffset(item.getOffset() - start); - - // center target line in the displayed area - IRegion rangeEndLineInfo = document.getLineInformation(Math.min(displayedEndLine, document.getNumberOfLines() - 1)); - int rangeStart = document.getLineOffset(Math.max(line - numLines/2, 0)); - int rangeEnd = rangeEndLineInfo.getOffset() + rangeEndLineInfo.getLength(); - viewer.revealRange(rangeStart, rangeEnd - rangeStart); - - var targetLineFirstMatch = getQuery().findFirst(document.get(item.getOffset(), contextLenght - (item.getOffset() - start))); - int targetLineFirstMatchStart = item.getOffset() + targetLineFirstMatch.getOffset(); - // sets caret position - viewer.setSelectedRange(targetLineFirstMatchStart, 0); - // does horizontal scrolling if necessary to reveal 1st occurrence in target line - viewer.revealRange(targetLineFirstMatchStart, targetLineFirstMatch.getLength()); - - // above setVisibleRegion() call makes these ranges to be aligned with content of text widget - StyledString styledString = highlightMatches(document.get(start, contextLenght)); - viewer.getTextWidget().setStyleRanges(styledString.getStyleRanges()); - return; - } catch (BadLocationException e) { + LineItem item = (LineItem) sel.getFirstElement(); + var file = item.getFile(); + IDocument document = documents.getDocument(file); + if (document!=null) { + viewersSelectionToolBar.setVisible(true); + if (document != lastDocument) { + currentDescriptors = QuickSearchActivator.getDefault().getViewers(file); + var selectedDescr = SELECTED_VIEWERS.get(file); + if (selectedDescr == null || !currentDescriptors.contains(selectedDescr)) { + selectedDescr = currentDescriptors.getFirst(); + SELECTED_VIEWERS.remove(file); } + replaceViewer(selectedDescr); } + showInViewer(item, file, document); + return; } } //empty selection or some error: - viewer.setDocument(null); + replaceViewer(QuickSearchActivator.getDefault().getDefaultViewer()).setInput(null, null, null); + viewersSelectionToolBar.setVisible(false); } } - /** - * Computes how many lines of text can be displayed in the details section. - */ - private int computeLines() { - StyledText details; - if (viewer!=null && !(details = viewer.getTextWidget()).isDisposed()) { - int lineHeight = details.getLineHeight(); - int areaHeight = details.getClientArea().height; - return (areaHeight + lineHeight - 1) / lineHeight; + private void showInViewer(LineItem item, IFile file, IDocument document) { + int numLines = currentViewerWrapper.handle.getVisibleLines(); + try { + final int context = 100; // number of lines before and after match to include in preview + int line = item.getLineNumber()-1; //in document lines are 0 based. In search 1 based. + int contextStartLine = Math.max(line-(numLines-1)/2 - context, 0); + int start = document.getLineOffset(contextStartLine); + int displayedEndLine = line + numLines/2; + int end = document.getLength(); + if (displayedEndLine + context <= document.getNumberOfLines()) { + try { + IRegion lineInfo = document.getLineInformation(displayedEndLine + context); + end = lineInfo.getOffset() + lineInfo.getLength(); + } catch (BadLocationException e) { + //Presumably line number is past the end of document. + //ignore. + } + } + int contextLenght = end-start; + + if (document != lastDocument) { + // some matches may fall out of visible region, but we still prepare & pass ranges for all + StyledString styledString = highlightMatches(document.get()); + var ranges = styledString.getStyleRanges(); + lastDocument = document; + currentViewerWrapper.setInput(document, ranges, file); + } + + var visibleRange = new Region(start, contextLenght); + + IRegion rangeEndLineInfo = document.getLineInformation(Math.min(displayedEndLine, document.getNumberOfLines() - 1)); + int rangeStart = document.getLineOffset(Math.max(line - numLines/2, 0)); + int rangeEnd = rangeEndLineInfo.getOffset() + rangeEndLineInfo.getLength(); + var revealedRange = new Region(rangeStart, rangeEnd - rangeStart); + + var targetLineFirstMatch = getQuery().findFirst(document.get(item.getOffset(), contextLenght - (item.getOffset() - start))); + int targetLineFirstMatchStart = item.getOffset() + targetLineFirstMatch.getOffset(); + var matchRange = new Region(targetLineFirstMatchStart, targetLineFirstMatch.getLength()); + + currentViewerWrapper.handle.focusMatch(visibleRange, revealedRange, line, matchRange); + } catch (BadLocationException e) { } - return 0; } /** @@ -1571,29 +1576,94 @@ public QuickTextQuery getQuery() { return searcher.getQuery(); } - /** - * A line background listener that provides the color that is used for current line highlighting (what - * {@link CursorLinePainter} does) but for single fixed line only and does so always regardless of show current - * line highlighting on/off preference. - * - * @see CursorLinePainter - */ - private static class FixedLineHighlighter implements LineBackgroundListener { + private class TextViewerWrapper { - private int lineOffset; - private Color highlightColor; + final IViewerDescriptor descriptor; + final ITextViewerHandle handle; + final Composite viewerParent; + final AtomicBoolean shouldIgnoreResize = new AtomicBoolean(); - public void setTargetLineOffset(int lineOffset) { - this.lineOffset = lineOffset; + public TextViewerWrapper(IViewerDescriptor descriptor, ITextViewerHandle handle, Composite viewerParent) { + this.descriptor = descriptor; + this.handle = handle; + this.viewerParent = viewerParent; + } + + void setInput(IDocument input, StyleRange[] ranges, IFile file) { + // setting document triggers resize of the text widget that is OK to ignore + var wasInCall = shouldIgnoreResize.getAndSet(true); + try { + handle.setViewerInput(input, ranges, file); + } finally { + if (!wasInCall) { + shouldIgnoreResize.set(false); + } + } + } + + void showInViewersParent() { + // layout() of parent triggers resize of the text widget that is OK to ignore + var wasInCall = shouldIgnoreResize.getAndSet(true); + try { + viewersParentLayout.topControl = viewerParent; + viewersParent.layout(); + } finally { + if (!wasInCall) { + shouldIgnoreResize.set(false); + } + } + } + } + + private class ViewerSelectionAction extends Action { + + final IViewerDescriptor descriptor; + + public ViewerSelectionAction(String text, IViewerDescriptor descriptor, LineItem selectedItem) { + super(text, IAction.AS_RADIO_BUTTON); + this.descriptor = descriptor; + setChecked(createAsChecked(selectedItem)); + } + + boolean createAsChecked(LineItem selectedItem) { + return currentViewerWrapper.descriptor == descriptor + && selectedItem != null + && SELECTED_VIEWERS.get(selectedItem.getFile()) == descriptor; } @Override - public void lineGetBackground(LineBackgroundEvent event) { - if (lineOffset == event.lineOffset) { - event.lineBackground = highlightColor; + public void run() { + if (isChecked()) { + LineItem item = (LineItem) ((IStructuredSelection) list.getSelection()).getFirstElement(); + var file = item.getFile(); + applySelection(file); + + replaceViewer(descriptor); + + var document = lastDocument; + lastDocument = null; // to force setInput() + showInViewer(item, file, document); } } + void applySelection(IFile file) { + SELECTED_VIEWERS.put(file, descriptor); + } } + private class DefaultViewerSelectionAction extends ViewerSelectionAction { + public DefaultViewerSelectionAction(IViewerDescriptor descriptor, LineItem selectedItem) { + super(QuickSearchMessages.QuickSearchDialog_defaultViewer, descriptor, selectedItem); + } + + @Override + boolean createAsChecked(LineItem selectedItem) { + return selectedItem == null || !SELECTED_VIEWERS.containsKey(selectedItem.getFile()); + } + + @Override + void applySelection(IFile file) { + SELECTED_VIEWERS.remove(file); + } + } } diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchMessages.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchMessages.java new file mode 100644 index 00000000000..2f790cf6c3f --- /dev/null +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchMessages.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * https://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Jozef Tomek - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.ui; + +import org.eclipse.osgi.util.NLS; + +public final class QuickSearchMessages extends NLS { + + private static final String BUNDLE_NAME = "org.eclipse.text.quicksearch.internal.ui.QuickSearchMessages";//$NON-NLS-1$ + + private QuickSearchMessages() { + // Do not instantiate + } + + public static String QuickSearchDialog_switchButtonTooltip; + public static String QuickSearchDialog_defaultViewer; + + static { + NLS.initializeMessages(BUNDLE_NAME, QuickSearchMessages.class); + } +} diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchMessages.properties b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchMessages.properties new file mode 100644 index 00000000000..8a6c733f5b8 --- /dev/null +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchMessages.properties @@ -0,0 +1,2 @@ +QuickSearchDialog_switchButtonTooltip=Switch Source Viewer +QuickSearchDialog_defaultViewer=Default Viewer \ No newline at end of file diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchPluginImages.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchPluginImages.java new file mode 100644 index 00000000000..2ba198ad05a --- /dev/null +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchPluginImages.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * https://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Jozef Tomek - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.ui; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.resource.ImageRegistry; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.PlatformUI; + +/** + * The images provided by the quicksearch plugin. + */ +public class QuickSearchPluginImages { + + /** + * The image registry containing Images and the ImageDescriptors. + */ + private static ImageRegistry imageRegistry; + + /** + * Returns the ImageRegistry. + * + * @return the ImageRegistry + */ + static ImageRegistry getImageRegistry() { + if (imageRegistry == null) { + imageRegistry = new ImageRegistry(PlatformUI.getWorkbench().getDisplay()); + } + return imageRegistry; + } + + /** + * Returns the Image identified by the given key, or + * null if it does not exist. + * + * @param key the image's key + * @return the Image identified by the given key, or + * null if it does not exist + */ + static Image getImage(String key) { + return getImageRegistry().get(key); + } + + /** + * Returns the ImageDescriptor identified by the given key, or + * null if it does not exist. + * + * @param key the image's key + * @return the ImageDescriptor identified by the given key, or + * null if it does not exist + */ + static ImageDescriptor getImageDescriptor(String key) { + return getImageRegistry().getDescriptor(key); + } + + static void dispose() { + if (imageRegistry != null) { + imageRegistry.dispose(); + imageRegistry = null; + } + } + +} diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/ViewerDescriptor.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/ViewerDescriptor.java new file mode 100644 index 00000000000..f29381f2a8a --- /dev/null +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/ViewerDescriptor.java @@ -0,0 +1,145 @@ +/******************************************************************************* + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * https://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Jozef Tomek - initial API and implementation + *******************************************************************************/ +package org.eclipse.text.quicksearch.internal.ui; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.FileLocator; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Platform; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.swt.graphics.Image; +import org.eclipse.text.quicksearch.ITextViewerCreator; +import org.osgi.framework.Bundle; + +/** + * Creates ITextViewerCreators from an IConfigurationElement. + * + * @see ITextViewerCreator + */ +public class ViewerDescriptor implements IViewerDescriptor { + private final static String ID_ATTRIBUTE = "id"; //$NON-NLS-1$ + private final static String CLASS_ATTRIBUTE = "class"; //$NON-NLS-1$ + private final static String LINKED_EDITOR_ATTRIBUTE = "linkedEditor"; //$NON-NLS-1$ + private final static String LABEL_ATTRIBUTE = "label"; //$NON-NLS-1$ + private final static String ICON_ATTRIBUTE = "icon"; //$NON-NLS-1$ + + private final IConfigurationElement fConfiguration; + private final String fViewerId; + private final String fLabel; + private final String fLinkedEditorId; + + private ITextViewerCreator fViewerCreator; + private Image fIcon; + + public ViewerDescriptor(IConfigurationElement config) { + fConfiguration = config; + fViewerId = config.getContributor().getName() + config.getAttribute(ID_ATTRIBUTE); + fLabel = fConfiguration.getAttribute(LABEL_ATTRIBUTE); + fLinkedEditorId = fConfiguration.getAttribute(LINKED_EDITOR_ATTRIBUTE); + } + + @Override + public ITextViewerCreator getViewerCreator() { + if (fViewerCreator == null) { + try { + fViewerCreator = (ITextViewerCreator) fConfiguration.createExecutableExtension(CLASS_ATTRIBUTE); + } catch (CoreException e) { + QuickSearchActivator.log(e); + } + } + return fViewerCreator; + } + + @Override + public String getLabel() { + return fLabel; + } + + String getLinkedEditorId() { + return fLinkedEditorId; + } + + String getViewerClass() { + return fConfiguration.getAttribute(CLASS_ATTRIBUTE); + } + + @Override + public Image getIcon() { + if (fIcon == null) { + var icon = getIconImageDescriptor(); + if (icon == null) { + icon = ImageDescriptor.getMissingImageDescriptor(); + } + QuickSearchPluginImages.getImageRegistry().put(fViewerId, icon); + fIcon = QuickSearchPluginImages.getImage(fViewerId); + } + return fIcon; + } + + private ImageDescriptor getIconImageDescriptor() { + String iconPath = fConfiguration.getAttribute(ICON_ATTRIBUTE); + if (iconPath != null) { + Bundle bundle = Platform.getBundle(fConfiguration.getContributor().getName()); + return ImageDescriptor.createFromURLSupplier(true, () -> { + URL iconURL = FileLocator.find(bundle, IPath.fromOSString(iconPath), null); + if (iconURL != null) { + return iconURL; + } else { // try to search as a URL in case it is absolute path + try { + return FileLocator.find(new URI(iconPath).toURL()); + } catch (MalformedURLException | URISyntaxException e) { + // return null + } + } + return null; + }); + } + return null; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("ViewerDescriptor ["); //$NON-NLS-1$ + sb.append("fViewerId="); //$NON-NLS-1$ + sb.append(fViewerId); + sb.append(", "); //$NON-NLS-1$ + if (fViewerCreator != null) { + sb.append("viewerCreator="); //$NON-NLS-1$ + sb.append(fViewerCreator); + sb.append(", "); //$NON-NLS-1$ + } + String viewerClass = getViewerClass(); + if (viewerClass != null) { + sb.append("viewerClass="); //$NON-NLS-1$ + sb.append(viewerClass); + sb.append(", "); //$NON-NLS-1$ + } + if (fConfiguration != null) { + sb.append("configuration="); //$NON-NLS-1$ + sb.append(fConfiguration); + } + sb.append("]"); //$NON-NLS-1$ + return sb.toString(); + } + +} diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/util/DocumentFetcher.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/util/DocumentFetcher.java index 10d9f649d53..b18c5736919 100644 --- a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/util/DocumentFetcher.java +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/util/DocumentFetcher.java @@ -50,14 +50,18 @@ */ public class DocumentFetcher { + private static final ITextFileBufferManager BUFFER_MANAGER = FileBuffers.getTextFileBufferManager(); + private Map dirtyEditors; //Simple cache remembers the last fetched file and document. private IFile lastFile = null; private IDocument lastDocument = null; + private boolean disconnectLastFile = false; IDocumentProvider provider = new TextFileDocumentProvider(); + public DocumentFetcher() { if (PlatformUI.isWorkbenchRunning()) { dirtyEditors = evalNonFileBufferDocuments(); @@ -82,9 +86,10 @@ public DocumentFetcher() { * buffer nor corresponds to an existing file in the workspace. */ public IDocument getDocument(IFile file) { - if (file==lastFile) { + if (lastFile != null && lastFile.equals(file)) { return lastDocument; } + disconnectLastFile(); lastFile = file; lastDocument = dirtyEditors.get(file); if (lastDocument==null) { @@ -97,8 +102,7 @@ public IDocument getDocument(IFile file) { } private IDocument getOpenDocument(IFile file) { - ITextFileBufferManager bufferManager= FileBuffers.getTextFileBufferManager(); - ITextFileBuffer textFileBuffer= bufferManager.getTextFileBuffer(file.getFullPath(), LocationKind.IFILE); + ITextFileBuffer textFileBuffer= BUFFER_MANAGER.getTextFileBuffer(file.getFullPath(), LocationKind.IFILE); if (textFileBuffer != null) { return textFileBuffer.getDocument(); } @@ -106,23 +110,19 @@ private IDocument getOpenDocument(IFile file) { } private IDocument getClosedDocument(IFile file) { - //No in the manager yet. Try to create a temporary buffer then remove it again. - ITextFileBufferManager bufferManager = FileBuffers.getTextFileBufferManager(); + //No in the manager yet. Try to create a temporary buffer - required for some extensions to work + // (will get removed once not used anymore) IPath location = file.getFullPath(); //Must use workspace location, not fs location for API below. ITextFileBuffer buffer = null; try { - bufferManager.connect(location, LocationKind.IFILE, new NullProgressMonitor()); - buffer = bufferManager.getTextFileBuffer(location, LocationKind.IFILE); + BUFFER_MANAGER.connect(location, LocationKind.IFILE, new NullProgressMonitor()); + disconnectLastFile = true; + buffer = BUFFER_MANAGER.getTextFileBuffer(location, LocationKind.IFILE); if (buffer!=null) { return buffer.getDocument(); } } catch (Throwable e) { QuickSearchActivator.log(e); - } finally { - try { - bufferManager.disconnect(location, LocationKind.IFILE, new NullProgressMonitor()); - } catch (CoreException e) { - } } return null; } @@ -151,8 +151,7 @@ private void evaluateTextEditor(Map result, IEditorPart ep) { if (input instanceof IFileEditorInput) { IFile file= ((IFileEditorInput) input).getFile(); if (!result.containsKey(file)) { // take the first editor found - ITextFileBufferManager bufferManager= FileBuffers.getTextFileBufferManager(); - ITextFileBuffer textFileBuffer= bufferManager.getTextFileBuffer(file.getFullPath(), LocationKind.IFILE); + ITextFileBuffer textFileBuffer= BUFFER_MANAGER.getTextFileBuffer(file.getFullPath(), LocationKind.IFILE); if (textFileBuffer != null) { // file buffer has precedence result.put(file, textFileBuffer.getDocument()); @@ -167,4 +166,19 @@ private void evaluateTextEditor(Map result, IEditorPart ep) { } } + private void disconnectLastFile() { + if (disconnectLastFile && lastFile != null) { + disconnectLastFile = false; + try { + BUFFER_MANAGER.disconnect(lastFile.getFullPath(), LocationKind.IFILE, new NullProgressMonitor()); + } catch (CoreException e) { + QuickSearchActivator.log(e); + } + } + } + + public void destroy() { + disconnectLastFile(); + } + } diff --git a/bundles/org.eclipse.ui.genericeditor/META-INF/MANIFEST.MF b/bundles/org.eclipse.ui.genericeditor/META-INF/MANIFEST.MF index 31a9a9c320a..e26154a1225 100644 --- a/bundles/org.eclipse.ui.genericeditor/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.ui.genericeditor/META-INF/MANIFEST.MF @@ -15,7 +15,8 @@ Require-Bundle: org.eclipse.ui.workbench.texteditor;bundle-version="3.10.0", org.eclipse.ui.ide;bundle-version="3.12.0", org.eclipse.core.resources;bundle-version="3.11.0", org.eclipse.core.expressions;bundle-version="3.6.0", - org.eclipse.compare;bundle-version="3.11.0";resolution:=optional + org.eclipse.compare;bundle-version="3.11.0";resolution:=optional, + org.eclipse.text.quicksearch;bundle-version="1.3.0" Export-Package: org.eclipse.ui.internal.genericeditor;x-internal:=true, org.eclipse.ui.internal.genericeditor.hover;x-internal:=true, org.eclipse.ui.internal.genericeditor.markers;x-internal:=true, diff --git a/bundles/org.eclipse.ui.genericeditor/plugin.properties b/bundles/org.eclipse.ui.genericeditor/plugin.properties index 9bbbe849ff2..b6b0a54917d 100644 --- a/bundles/org.eclipse.ui.genericeditor/plugin.properties +++ b/bundles/org.eclipse.ui.genericeditor/plugin.properties @@ -25,6 +25,7 @@ ExtPoint.foldingReconcilers=Folding Reconcilers ExtPoint.characterPairMatchers=Character Pair Matcher Providers ExtPoint.hyperlinkDetectorTarget=Generic Text Editor ExtPoint.iconProviders=Generic Editor Icon Providers +ExtPoint.quicksearchSourceViewer=Generic Code Viewer openDeclarationCommand_name=Open Declaration context_name=in Generic Code Editor context_description=When editing in the Generic Code Editor diff --git a/bundles/org.eclipse.ui.genericeditor/plugin.xml b/bundles/org.eclipse.ui.genericeditor/plugin.xml index 107f075c39e..738faccd759 100644 --- a/bundles/org.eclipse.ui.genericeditor/plugin.xml +++ b/bundles/org.eclipse.ui.genericeditor/plugin.xml @@ -14,6 +14,7 @@ Sopot Cela & Mickael Istria (Red Hat Inc). -initial implementation Lucas Bullen (Red Hat Inc.) - Bug 508829, 521382 Simon Scholz - Bug 527830 + Jozef Tomek - Quick Search text viewers extension --> @@ -285,4 +286,17 @@ name="%PreferencePages.GenericTextEditors"> + + + + + diff --git a/bundles/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/quicksearch/GenericEditorViewerCreator.java b/bundles/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/quicksearch/GenericEditorViewerCreator.java new file mode 100644 index 00000000000..4a45e778b8b --- /dev/null +++ b/bundles/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/quicksearch/GenericEditorViewerCreator.java @@ -0,0 +1,157 @@ +/******************************************************************************* + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * https://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Jozef Tomek - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.genericeditor.quicksearch; + +import java.util.Iterator; + +import org.eclipse.core.resources.IFile; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextPresentationListener; +import org.eclipse.jface.text.TextPresentation; +import org.eclipse.jface.text.source.CompositeRuler; +import org.eclipse.jface.text.source.SourceViewer; +import org.eclipse.swt.custom.StyleRange; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.text.quicksearch.ITextViewerCreator; +import org.eclipse.text.quicksearch.SourceViewerConfigurer; +import org.eclipse.text.quicksearch.SourceViewerHandle; +import org.eclipse.ui.editors.text.EditorsUI; +import org.eclipse.ui.internal.genericeditor.ExtensionBasedTextViewerConfiguration; +import org.eclipse.ui.internal.genericeditor.GenericEditorPlugin; +import org.eclipse.ui.texteditor.ChainedPreferenceStore; + +/** + * Creates quicksearch text viewer handles that use + * {@link GenericEditorViewer2}. + */ +public class GenericEditorViewerCreator implements ITextViewerCreator { + + @Override + public ITextViewerHandle createTextViewer(Composite parent) { + return new GenericEditorSourceViewerHandle(parent); + } + + private class GenericEditorSourceViewerHandle extends SourceViewerHandle + implements ITextPresentationListener { + private boolean fNewDocumentReconciliation; + + // after focusing on other match in the same document, not all reconciliations + // are performed again (e.g. LSP reconciliation is done only after setting new + // input document), so we collect all applied styles to be able to set them + // after other match focus manually + private boolean fDoCollectStyles; + private TextPresentation fMergedStylesPresentation; + + private boolean fScheduleMatchRangesPresentation = true; + + public GenericEditorSourceViewerHandle(Composite parent) { + super(new SourceViewerConfigurer<>(GenericEditorViewer::new), parent); + fSourceViewer.addTextPresentationListener(this); + } + + /* + * triggered variable number of times by a) tm4e code (possibly after setInput() + * and/or focusMatch() -> fSourceViewer.setVisibleRegion() ), b) lsp4e code + * (zero or more times after setInput() only) + */ + @Override + public void applyTextPresentation(TextPresentation textPresentation) { + if (fDoCollectStyles) { + StyleRange[] ranges = new StyleRange[textPresentation.getDenumerableRanges()]; + int i = 0; + for (Iterator iter = textPresentation.getAllStyleRangeIterator(); iter.hasNext();) { + ranges[i++] = iter.next(); + } + mergeStylesToTextPresentation(fMergedStylesPresentation, ranges); + } + if (fScheduleMatchRangesPresentation) { + fScheduleMatchRangesPresentation = false; + fSourceViewer.getTextWidget().getDisplay().asyncExec(() -> applyMatchRangesTextPresentation()); + } + } + + private void mergeStylesToTextPresentation(TextPresentation textPresentation, StyleRange[] styleRanges) { + if (styleRanges != null && styleRanges.length > 0) { + // mergeStyleRanges() modifies passed ranges so we need to clone + var ranges = new StyleRange[styleRanges.length]; + for (int i = 0; i < ranges.length; i++) { + ranges[i] = (StyleRange) styleRanges[i].clone(); + } + textPresentation.mergeStyleRanges(ranges); + } + } + + private void applyMatchRangesTextPresentation() { + applyMatchesStyles(); + fScheduleMatchRangesPresentation = true; + + } + + @Override + public void setViewerInput(IDocument document, StyleRange[] matchRangers, IFile file) { + fNewDocumentReconciliation = true; + fMergedStylesPresentation = new TextPresentation(1024); + super.setViewerInput(document, matchRangers, file); + } + + @Override + public void focusMatch(IRegion visibleRegion, IRegion revealedRange, int matchLine, IRegion matchRange) { + if (fNewDocumentReconciliation) { + fNewDocumentReconciliation = false; + fDoCollectStyles = true; + super.focusMatch(visibleRegion, revealedRange, matchLine, matchRange); + } else { + fDoCollectStyles = false; + fScheduleMatchRangesPresentation = false; // temporary disable scheduling match ranges presentation + super.focusMatch(visibleRegion, revealedRange, matchLine, matchRange); + // now apply collected styles + fSourceViewer.changeTextPresentation(fMergedStylesPresentation, false); + applyMatchRangesTextPresentation(); // also enables scheduling match ranges presentation + fDoCollectStyles = true; + } + } + } + + static class GenericEditorViewer extends SourceViewer { + + public GenericEditorViewer(Composite parent, CompositeRuler verticalRuler, int styles) { + super(parent, verticalRuler, styles); + } + + @Override + public void refresh() { + System.out.println("LALALALALALAL"); //$NON-NLS-1$ + // empty implementation + } + + @Override + public void setInput(Object input) { + unconfigure(); + + if (input instanceof IDocument doc) { + setDocument(doc); + var configuration = new ExtensionBasedTextViewerConfiguration(null, + new ChainedPreferenceStore(new IPreferenceStore[] { EditorsUI.getPreferenceStore(), + GenericEditorPlugin.getDefault().getPreferenceStore() })); + configure(configuration); + } + } + + } + +} From fa5e630365e24bfc78782f4e35ee8b657ced22bf Mon Sep 17 00:00:00 2001 From: Jozef Tomek Date: Mon, 24 Feb 2025 21:32:06 +0100 Subject: [PATCH 4/6] Remove extension icon in favor of presenting file type icon --- .../icons/full/obj16/file_obj.png | Bin 356 -> 0 bytes .../icons/full/obj16/file_obj@2x.png | Bin 1045 -> 0 bytes .../org.eclipse.text.quicksearch/plugin.xml | 1 - .../schema/textViewers.exsd | 10 --- .../internal/ui/IViewerDescriptor.java | 2 - .../internal/ui/QuickSearchActivator.java | 53 +++++++++++- .../internal/ui/QuickSearchDialog.java | 26 +++--- .../internal/ui/QuickSearchPluginImages.java | 76 ------------------ .../internal/ui/ViewerDescriptor.java | 47 ----------- .../org.eclipse.ui.genericeditor/plugin.xml | 1 - 10 files changed, 68 insertions(+), 148 deletions(-) delete mode 100644 bundles/org.eclipse.text.quicksearch/icons/full/obj16/file_obj.png delete mode 100644 bundles/org.eclipse.text.quicksearch/icons/full/obj16/file_obj@2x.png delete mode 100644 bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchPluginImages.java diff --git a/bundles/org.eclipse.text.quicksearch/icons/full/obj16/file_obj.png b/bundles/org.eclipse.text.quicksearch/icons/full/obj16/file_obj.png deleted file mode 100644 index 723da774b96792f82e99d35d856b300a52253ea1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 356 zcmV-q0h|7bP)sleS|(y(M31ajf;xm%qa_5G-()vRSeB)n-eXE7<3hNUqnO@C5F{V_XmFQ z4!S#TUHHOtbI!xVxp_00Sw6QZU>dzv-g7N}E-`RnZNm>DlDk$Gmv+JLn8N(jfOB5N z*pEmaTn@JmhqhG;98;ttWbpWS&wIrR|4+49B!If*r!rR1{$?8 zzJ9_~m`4T-V;3VYj4vo0^ua4#aOE~ui#C>XXIR*%#e3$Ffx{yO!|xy!=8*wS-^buH zNW6gUnMVdxt%|o%kP7q2fKt`)`r#*D!1l}|1G1{)W$35EJTf3vj`2L0#24h%9@ga! zRzw?1TPN}S59W~panHch`y>_Sk%63C$A6jG5B>$N>hUf5y?w3#0000VP)JMBz6siv{fszJnXP$FmOR*-`rY&j_*Djs5+vExmf zG{%I3ii$@L8xuii-mbNTU2i(GP;!?&^W5$4omajONAKME`25e4ni<3`WK*-s&u&#< zVMc{px?PF6*bt~%{jw#m5}jC%DbSeDK8dP7zfpl?R1NC9cG;56x%;fdVsrqFjAAmb z{i5Xpvk?ub_aw9xZ&Tf*#Lw7BbRb1O37V0JXo~T0izVXII#BP&R@ ze=K3|NW$)61^EN2EWejt>c^XfMu{hPkqQr>k=0s>s5lI&`u>s`asPowV=GWtQ|K8@ z;+}Q}KMscRZC?;yKJdx!Wu6WGSK=xCB!0p?DZ%nWx{@gBKH(}54(UNj?=BdJxDIn* z4SLV-(2OlZX%44}$q7tHR_8QJ+6@`u$c2UYA)1qs_t`xR_87`xf^me_VGaHXvwsZ+&vWP}R-qeTfm&F`kbMyYmU;A>=Fm$H zw0k6hE?pe=)G>KJ9GPHw+yy*d3#b#R-WKAto8@sAnDAIZ9arj1j_f;Es3J>t&Fo9a zP-NHXrrk2$HoVDGC3!w5dRQKJ0jJvz>Zn|2Zh8aF`9R3a@&vEIS3qz%KpmFqjGf$o zh1`IN+`uTgfe~^8T5dQT!dmdJi9SMn9IV`8U_&8F=VUN>xPQAK&)9SE>I z6K?^F-~_dQT4Qc{1I>BYWRT@?7cl+i0=0WmV{Uo_&G~U(h~;q?FgV8s}I!n(K$nJKr{XfD#sEYlKuA>p0eF{sN?MKg75l6__`;6ztX4j%yrg@2+QLx zptVebqCXL33$Dxi*U@7X$T8X$$?yMpw)d{#S+esu{!SO=nd{81D9ht6pf>wK<;ZY5 z7@aYe$6a8^I0foOw%8WUJf%tGE1)z4@j>twP>hP%4?(35e){nr-|Nr6M`+~N_=!u= P00000NkvXXu0mjfC@uLo diff --git a/bundles/org.eclipse.text.quicksearch/plugin.xml b/bundles/org.eclipse.text.quicksearch/plugin.xml index c62697f329d..27c4a77e27c 100644 --- a/bundles/org.eclipse.text.quicksearch/plugin.xml +++ b/bundles/org.eclipse.text.quicksearch/plugin.xml @@ -122,7 +122,6 @@ - - - - a relative name of the icon that will be used in the UI to present this viewer. - - - - - - diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/IViewerDescriptor.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/IViewerDescriptor.java index 090609b7388..a49a0a9cf05 100644 --- a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/IViewerDescriptor.java +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/IViewerDescriptor.java @@ -15,7 +15,6 @@ *******************************************************************************/ package org.eclipse.text.quicksearch.internal.ui; -import org.eclipse.swt.graphics.Image; import org.eclipse.text.quicksearch.ITextViewerCreator; interface IViewerDescriptor { @@ -34,5 +33,4 @@ interface IViewerDescriptor { */ String getLabel(); - Image getIcon(); } diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchActivator.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchActivator.java index 07d5d1f5040..4c839bf8ff6 100644 --- a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchActivator.java +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchActivator.java @@ -16,8 +16,10 @@ import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; +import java.util.Hashtable; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.MissingResourceException; import java.util.Optional; import java.util.ResourceBundle; @@ -26,8 +28,10 @@ import java.util.stream.Stream; import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.Adapters; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtensionRegistry; import org.eclipse.core.runtime.IStatus; @@ -37,10 +41,12 @@ import org.eclipse.core.runtime.content.IContentType; import org.eclipse.core.runtime.content.IContentTypeManager; import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.swt.graphics.Image; import org.eclipse.text.quicksearch.internal.core.preferences.QuickSearchPreferences; import org.eclipse.ui.IEditorDescriptor; import org.eclipse.ui.IEditorRegistry; import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.model.IWorkbenchAdapter; import org.eclipse.ui.plugin.AbstractUIPlugin; import org.osgi.framework.BundleContext; @@ -62,6 +68,8 @@ public class QuickSearchActivator extends AbstractUIPlugin { private static final String DEFAULT_CREATOR_CLASS = DefaultSourceViewerCreator.class.getName(); private final ExtensionsRegistry fTextViewersRegistry = new ExtensionsRegistry<>(); + private final Map fgImages= new Hashtable<>(10); + private final List fgDisposeOnShutdownImages= new ArrayList<>(); // Lazy initialized private QuickSearchPreferences prefs = null; @@ -94,7 +102,11 @@ public void start(BundleContext context) throws Exception { @Override public void stop(BundleContext context) throws Exception { plugin = null; - QuickSearchPluginImages.dispose(); + for (Image img : fgDisposeOnShutdownImages) { + if (!img.isDisposed()) { + img.dispose(); + } + } super.stop(context); } @@ -305,4 +317,43 @@ private void addLinkedEditorContentTypes(LinkedHashSet viewers } } + /** + * Returns a shared image for the given adaptable. + * This convenience method queries the given adaptable + * for its IWorkbenchAdapter.getImageDescriptor, which it + * uses to create an image if it does not already have one. + *

+ * Note: Images returned from this method will be automatically disposed + * of when this plug-in shuts down. Callers must not dispose of these + * images themselves. + *

+ * + * @param adaptable the adaptable for which to find an image + * @return an image + */ + public Image getImage(IAdaptable adaptable) { + if (adaptable != null) { + IWorkbenchAdapter o= Adapters.adapt(adaptable, IWorkbenchAdapter.class); + if (o == null) { + return null; + } + ImageDescriptor id= o.getImageDescriptor(adaptable); + if (id != null) { + Image image= fgImages.get(id); + if (image == null) { + image= id.createImage(); + try { + fgImages.put(id, image); + } catch (NullPointerException e) { + // NeedWork + } + fgDisposeOnShutdownImages.add(image); + + } + return image; + } + } + return null; + } + } diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java index 95af9fa231d..d90d18919b7 100644 --- a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java @@ -116,6 +116,7 @@ import org.eclipse.text.quicksearch.internal.core.pathmatch.ResourceMatchers; import org.eclipse.text.quicksearch.internal.util.DocumentFetcher; import org.eclipse.ui.ActiveShellExpression; +import org.eclipse.ui.ISharedImages; import org.eclipse.ui.IWorkbenchCommandConstants; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchWindow; @@ -1006,13 +1007,13 @@ public void mouseDown(MouseEvent e) { viewersParentLayout = new StackLayout(); viewersParent.setLayout(viewersParentLayout); - replaceViewer(QuickSearchActivator.getDefault().getDefaultViewer()); + replaceViewer(QuickSearchActivator.getDefault().getDefaultViewer(), null); Assert.isNotNull(currentViewerWrapper, "Default source viewer initialization failed"); //$NON-NLS-1$ list.addSelectionChangedListener(event -> refreshDetails()); } - private TextViewerWrapper replaceViewer(IViewerDescriptor descriptor) { + private TextViewerWrapper replaceViewer(IViewerDescriptor descriptor, IFile file) { if (descriptor == null) { // should never happen descriptor = QuickSearchActivator.getDefault().getDefaultViewer(); @@ -1028,7 +1029,11 @@ private TextViewerWrapper replaceViewer(IViewerDescriptor descriptor) { currentViewerWrapper.showInViewersParent(); viewerSelectionLabel.setText(currentViewerWrapper.descriptor.getLabel()); - viewerSelectionLabel.setImage(currentViewerWrapper.descriptor.getIcon()); + if (file == null) { + viewerSelectionLabel.setImage(ISharedImages.get().getImage(ISharedImages.IMG_OBJ_FILE)); + } else { + viewerSelectionLabel.setImage(QuickSearchActivator.getDefault().getImage(file)); + } viewerSelectionLabel.getParent().layout(); return currentViewerWrapper; @@ -1090,7 +1095,8 @@ public void controlResized(ControlEvent e) { } private void refreshDetails() { - if (currentViewerWrapper!=null && list!=null && !list.getTable().isDisposed()) { + IFile file = null; + if (list!=null && !list.getTable().isDisposed()) { if (documents==null) { documents = new DocumentFetcher(); } @@ -1098,7 +1104,7 @@ private void refreshDetails() { if (sel!=null && !sel.isEmpty()) { //Not empty selection LineItem item = (LineItem) sel.getFirstElement(); - var file = item.getFile(); + file = item.getFile(); IDocument document = documents.getDocument(file); if (document!=null) { viewersSelectionToolBar.setVisible(true); @@ -1109,16 +1115,16 @@ private void refreshDetails() { selectedDescr = currentDescriptors.getFirst(); SELECTED_VIEWERS.remove(file); } - replaceViewer(selectedDescr); + replaceViewer(selectedDescr, file); } showInViewer(item, file, document); return; } } - //empty selection or some error: - replaceViewer(QuickSearchActivator.getDefault().getDefaultViewer()).setInput(null, null, null); - viewersSelectionToolBar.setVisible(false); } + //empty selection or some error: + replaceViewer(QuickSearchActivator.getDefault().getDefaultViewer(), file).setInput(null, null, null); + viewersSelectionToolBar.setVisible(false); } private void showInViewer(LineItem item, IFile file, IDocument document) { @@ -1638,7 +1644,7 @@ public void run() { var file = item.getFile(); applySelection(file); - replaceViewer(descriptor); + replaceViewer(descriptor, file); var document = lastDocument; lastDocument = null; // to force setInput() diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchPluginImages.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchPluginImages.java deleted file mode 100644 index 2ba198ad05a..00000000000 --- a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchPluginImages.java +++ /dev/null @@ -1,76 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2025 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * https://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Jozef Tomek - initial API and implementation - *******************************************************************************/ -package org.eclipse.text.quicksearch.internal.ui; - -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.jface.resource.ImageRegistry; -import org.eclipse.swt.graphics.Image; -import org.eclipse.ui.PlatformUI; - -/** - * The images provided by the quicksearch plugin. - */ -public class QuickSearchPluginImages { - - /** - * The image registry containing Images and the ImageDescriptors. - */ - private static ImageRegistry imageRegistry; - - /** - * Returns the ImageRegistry. - * - * @return the ImageRegistry - */ - static ImageRegistry getImageRegistry() { - if (imageRegistry == null) { - imageRegistry = new ImageRegistry(PlatformUI.getWorkbench().getDisplay()); - } - return imageRegistry; - } - - /** - * Returns the Image identified by the given key, or - * null if it does not exist. - * - * @param key the image's key - * @return the Image identified by the given key, or - * null if it does not exist - */ - static Image getImage(String key) { - return getImageRegistry().get(key); - } - - /** - * Returns the ImageDescriptor identified by the given key, or - * null if it does not exist. - * - * @param key the image's key - * @return the ImageDescriptor identified by the given key, or - * null if it does not exist - */ - static ImageDescriptor getImageDescriptor(String key) { - return getImageRegistry().getDescriptor(key); - } - - static void dispose() { - if (imageRegistry != null) { - imageRegistry.dispose(); - imageRegistry = null; - } - } - -} diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/ViewerDescriptor.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/ViewerDescriptor.java index f29381f2a8a..4e956f8e523 100644 --- a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/ViewerDescriptor.java +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/ViewerDescriptor.java @@ -15,20 +15,9 @@ *******************************************************************************/ package org.eclipse.text.quicksearch.internal.ui; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; - import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IConfigurationElement; -import org.eclipse.core.runtime.IPath; -import org.eclipse.core.runtime.Platform; -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.swt.graphics.Image; import org.eclipse.text.quicksearch.ITextViewerCreator; -import org.osgi.framework.Bundle; /** * Creates ITextViewerCreators from an IConfigurationElement. @@ -40,7 +29,6 @@ public class ViewerDescriptor implements IViewerDescriptor { private final static String CLASS_ATTRIBUTE = "class"; //$NON-NLS-1$ private final static String LINKED_EDITOR_ATTRIBUTE = "linkedEditor"; //$NON-NLS-1$ private final static String LABEL_ATTRIBUTE = "label"; //$NON-NLS-1$ - private final static String ICON_ATTRIBUTE = "icon"; //$NON-NLS-1$ private final IConfigurationElement fConfiguration; private final String fViewerId; @@ -48,7 +36,6 @@ public class ViewerDescriptor implements IViewerDescriptor { private final String fLinkedEditorId; private ITextViewerCreator fViewerCreator; - private Image fIcon; public ViewerDescriptor(IConfigurationElement config) { fConfiguration = config; @@ -82,40 +69,6 @@ String getViewerClass() { return fConfiguration.getAttribute(CLASS_ATTRIBUTE); } - @Override - public Image getIcon() { - if (fIcon == null) { - var icon = getIconImageDescriptor(); - if (icon == null) { - icon = ImageDescriptor.getMissingImageDescriptor(); - } - QuickSearchPluginImages.getImageRegistry().put(fViewerId, icon); - fIcon = QuickSearchPluginImages.getImage(fViewerId); - } - return fIcon; - } - - private ImageDescriptor getIconImageDescriptor() { - String iconPath = fConfiguration.getAttribute(ICON_ATTRIBUTE); - if (iconPath != null) { - Bundle bundle = Platform.getBundle(fConfiguration.getContributor().getName()); - return ImageDescriptor.createFromURLSupplier(true, () -> { - URL iconURL = FileLocator.find(bundle, IPath.fromOSString(iconPath), null); - if (iconURL != null) { - return iconURL; - } else { // try to search as a URL in case it is absolute path - try { - return FileLocator.find(new URI(iconPath).toURL()); - } catch (MalformedURLException | URISyntaxException e) { - // return null - } - } - return null; - }); - } - return null; - } - @Override public String toString() { StringBuilder sb = new StringBuilder(); diff --git a/bundles/org.eclipse.ui.genericeditor/plugin.xml b/bundles/org.eclipse.ui.genericeditor/plugin.xml index 738faccd759..e9557603bf0 100644 --- a/bundles/org.eclipse.ui.genericeditor/plugin.xml +++ b/bundles/org.eclipse.ui.genericeditor/plugin.xml @@ -290,7 +290,6 @@ point="org.eclipse.text.quicksearch.textViewers"> From fa1d5313237f6206e62a0afbb53aae2d0e7bc03e Mon Sep 17 00:00:00 2001 From: Jozef Tomek Date: Mon, 3 Mar 2025 20:55:34 +0100 Subject: [PATCH 5/6] Add common SourceViewerHandle.applyMatchStyles(TextPresentation) Also bump versions for 4.36 --- .../META-INF/MANIFEST.MF | 2 +- .../text/quicksearch/ITextViewerCreator.java | 2 +- .../quicksearch/SourceViewerConfigurer.java | 4 +- .../text/quicksearch/SourceViewerHandle.java | 74 +++++++++++++++++-- .../ui/DefaultSourceViewerCreator.java | 9 +-- .../internal/ui/QuickSearchDialog.java | 1 + .../GenericEditorViewerCreator.java | 42 ++--------- 7 files changed, 82 insertions(+), 52 deletions(-) diff --git a/bundles/org.eclipse.text.quicksearch/META-INF/MANIFEST.MF b/bundles/org.eclipse.text.quicksearch/META-INF/MANIFEST.MF index f77a54d5be8..cc7a899b4bf 100644 --- a/bundles/org.eclipse.text.quicksearch/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.text.quicksearch/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.text.quicksearch;singleton:=true -Bundle-Version: 1.3.0.qualifier +Bundle-Version: 1.4.0.qualifier Bundle-Activator: org.eclipse.text.quicksearch.internal.ui.QuickSearchActivator Require-Bundle: org.eclipse.ui;bundle-version="[3.113.0,4.0.0)", org.eclipse.core.resources;bundle-version="[3.13.0,4.0.0)", diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/ITextViewerCreator.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/ITextViewerCreator.java index 4c4cfec1ad9..63e7bd1cb26 100644 --- a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/ITextViewerCreator.java +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/ITextViewerCreator.java @@ -27,7 +27,7 @@ * quicksearch matches within content of their containing document. * * @see ITextViewerHandle - * @since 1.3 + * @since 1.4 */ public interface ITextViewerCreator { diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/SourceViewerConfigurer.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/SourceViewerConfigurer.java index ff40fa2ca5a..a0d3dddfa2c 100644 --- a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/SourceViewerConfigurer.java +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/SourceViewerConfigurer.java @@ -53,8 +53,8 @@ import org.eclipse.ui.texteditor.SourceViewerDecorationSupport; /** - * Implementation of source viewer factory for {@link SourceViewerHandle} that creates and does necessary setup of source viewer so that - * it provides common aspects of quicksearch text viewers: + * Implementation of source viewer factory for {@link SourceViewerHandle} that creates and does necessary setup of + * source viewer so that it provides common aspects of quicksearch text viewers: *
    *
  • vertical ruler with line numbers supporting selected match line number highlighting *
  • selected match line highlighting diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/SourceViewerHandle.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/SourceViewerHandle.java index 47153921c72..c159be1ffa0 100644 --- a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/SourceViewerHandle.java +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/SourceViewerHandle.java @@ -26,6 +26,7 @@ import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.TextPresentation; import org.eclipse.jface.text.source.Annotation; import org.eclipse.jface.text.source.IAnnotationModel; import org.eclipse.jface.text.source.IAnnotationModelListener; @@ -58,17 +59,35 @@ *
  • highlighting whole line by using {@link FixedLineHighlighter} (if a viewer provides one) *
*

- * This class uses {@link SourceViewerConfigurer} that does viewer creation and setup necessary for + * This class uses {@link ISourceViewerConfigurer} that does viewer creation and setup necessary for * aforementioned aspects. * - * @since 1.3 + * @since 1.4 */ public class SourceViewerHandle implements ITextViewerHandle { + /** + * Source viewer created by {@link ISourceViewerConfigurer}. + */ protected final T fSourceViewer; + /** + * Optional change ruler column provided by {@link ISourceViewerConfigurer}. + */ protected final IChangeRulerColumn fChangeRulerColumn; + /** + * Optional fixed line change annotation model for {@link #fChangeRulerColumn} in case it is provided. + */ protected final FixedLineChangedAnnotationModel fFixedLineChangeModel; + /** + * Optional fixed line highlighter provided by {@link ISourceViewerConfigurer}. + */ protected final FixedLineHighlighter fMatchLineHighlighter; + /** + * Highlighting styles for all matches in the input document previously passed to + * {@link #setViewerInput(IDocument, StyleRange[], IFile) setViewerInput()}. Styles can be subsequently applied to + * source viewer's text widget using {@link #applyMatchesStyles()} (called from default implementation of + * {@link #focusMatch(IRegion, IRegion, int, IRegion) focusMatch()}) or {@link #applyMatchStyles(TextPresentation)}. + */ protected StyleRange[] fMatchRanges = null; /** @@ -109,6 +128,17 @@ public void setViewerInput(IDocument document, StyleRange[] allMatchesStyles, IF fSourceViewer.setInput(document); } + /** + * Called from {@link #setViewerInput(IDocument, StyleRange[], IFile) setViewerInput()} to remember highlighting + * styles for all matches in the input document. Styles can be subsequently applied to source viewer's text widget + * using {@link #applyMatchesStyles()} (called from default implementation of + * {@link #focusMatch(IRegion, IRegion, int, IRegion) focusMatch()}) or {@link #applyMatchStyles(TextPresentation)}. + * @param allMatchesStyles common text styles to apply in order to highlight all found matches in the document file + * the file contents of which is the document + */ + protected void setMatchesStyles(StyleRange[] allMatchesStyles) { + } + @Override public void focusMatch(IRegion visibleRegion, IRegion revealedRange, int matchLine, IRegion matchRange) { // limit content of the document that we can scroll to @@ -134,13 +164,14 @@ public void focusMatch(IRegion visibleRegion, IRegion revealedRange, int matchLi fFixedLineChangeModel.selectedMatchLine = matchLine; fChangeRulerColumn.redraw(); } - + applyMatchesStyles(); } /** * Applies all matches highlighting styles previously passed to - * {@link #setViewerInput(IDocument, StyleRange[], IFile) setViewerInput()} method considering projection of - * model (document) ranges to source viewer's text widget ranges. + * {@link #setViewerInput(IDocument, StyleRange[], IFile) setViewerInput()} method to source viewer's text widget + * considering projection of model (document) ranges to text widget ranges. Styles are set using + * {@link StyledText#setStyleRange(StyleRange)} thus replacing existing styles in the range. * * @see #setViewerInput(IDocument, StyleRange[], IFile) */ @@ -166,6 +197,39 @@ private StyleRange modelRange2WidgetStyleRange(StyleRange range) { return null; } + /** + * Applies all matches highlighting styles previously passed to + * {@link #setViewerInput(IDocument, StyleRange[], IFile) setViewerInput()} method to presentation + * considering presentation's extent. Styles either replace ({@link TextPresentation#replaceStyleRange(StyleRange)}) + * or are merged ({@link TextPresentation#mergeStyleRange(StyleRange)}) with text presentation's styles in the + * particular ranges depending on mergeStyles parameter. + * + * @param mergeStyles true if the styles should be merged, false if they should replace + * text presentation styles in the same ranges + * @see #setViewerInput(IDocument, StyleRange[], IFile) + */ + protected void applyMatchStyles(TextPresentation presentation, boolean mergeStyles) { + if (fMatchRanges == null || fMatchRanges.length == 0) { + return; + } + var extent = presentation.getExtent(); + int extentStart = extent.getOffset(); + var tmpPresentation = new TextPresentation(fMatchRanges.length); + for (StyleRange styleRange : fMatchRanges) { + tmpPresentation.addStyleRange((StyleRange) styleRange.clone()); + } + tmpPresentation.setResultWindow(extent); + for (Iterator iter = tmpPresentation.getAllStyleRangeIterator(); iter.hasNext();) { + var style = iter.next(); + style.start += extentStart; + if (mergeStyles) { + presentation.mergeStyleRange(style); + } else { + presentation.replaceStyleRange(style); + } + } + } + /** * Annotation model implementation that announces single configured line as {@link ILineDiffInfo#CHANGED} for * quick diff feature of the viewer's {@link IChangeRulerColumn}. diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/DefaultSourceViewerCreator.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/DefaultSourceViewerCreator.java index 13a87b08fcc..062fa0c9c8a 100644 --- a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/DefaultSourceViewerCreator.java +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/DefaultSourceViewerCreator.java @@ -14,7 +14,6 @@ * Jozef Tomek - initial API and implementation *******************************************************************************/ package org.eclipse.text.quicksearch.internal.ui; -import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.source.SourceViewer; import org.eclipse.swt.widgets.Composite; import org.eclipse.text.quicksearch.ITextViewerCreator; @@ -31,13 +30,7 @@ public class DefaultSourceViewerCreator implements ITextViewerCreator { @Override public ITextViewerHandle createTextViewer(Composite parent) { - return new SourceViewerHandle<>(new SourceViewerConfigurer<>(SourceViewer::new), parent) { - @Override - public void focusMatch(IRegion visibleRegion, IRegion revealedRange, int matchLine, IRegion matchRange) { - super.focusMatch(visibleRegion, revealedRange, matchLine, matchRange); - applyMatchesStyles(); - } - }; + return new SourceViewerHandle<>(new SourceViewerConfigurer<>(SourceViewer::new), parent); } } diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java index d90d18919b7..5460028ca51 100644 --- a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java @@ -1123,6 +1123,7 @@ private void refreshDetails() { } } //empty selection or some error: + lastDocument = null; replaceViewer(QuickSearchActivator.getDefault().getDefaultViewer(), file).setInput(null, null, null); viewersSelectionToolBar.setVisible(false); } diff --git a/bundles/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/quicksearch/GenericEditorViewerCreator.java b/bundles/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/quicksearch/GenericEditorViewerCreator.java index 4a45e778b8b..af6511eb857 100644 --- a/bundles/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/quicksearch/GenericEditorViewerCreator.java +++ b/bundles/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/quicksearch/GenericEditorViewerCreator.java @@ -57,11 +57,14 @@ private class GenericEditorSourceViewerHandle extends SourceViewerHandle(GenericEditorViewer::new), parent); fSourceViewer.addTextPresentationListener(this); + parent.addDisposeListener(e -> { + fMatchRanges = null; + fMergedStylesPresentation = null; + fSourceViewer.removeTextPresentationListener(this); + }); } /* @@ -72,36 +75,13 @@ public GenericEditorSourceViewerHandle(Composite parent) { @Override public void applyTextPresentation(TextPresentation textPresentation) { if (fDoCollectStyles) { - StyleRange[] ranges = new StyleRange[textPresentation.getDenumerableRanges()]; - int i = 0; + applyMatchStyles(textPresentation, true); for (Iterator iter = textPresentation.getAllStyleRangeIterator(); iter.hasNext();) { - ranges[i++] = iter.next(); - } - mergeStylesToTextPresentation(fMergedStylesPresentation, ranges); - } - if (fScheduleMatchRangesPresentation) { - fScheduleMatchRangesPresentation = false; - fSourceViewer.getTextWidget().getDisplay().asyncExec(() -> applyMatchRangesTextPresentation()); - } - } - - private void mergeStylesToTextPresentation(TextPresentation textPresentation, StyleRange[] styleRanges) { - if (styleRanges != null && styleRanges.length > 0) { - // mergeStyleRanges() modifies passed ranges so we need to clone - var ranges = new StyleRange[styleRanges.length]; - for (int i = 0; i < ranges.length; i++) { - ranges[i] = (StyleRange) styleRanges[i].clone(); + fMergedStylesPresentation.mergeStyleRange((StyleRange) iter.next().clone()); } - textPresentation.mergeStyleRanges(ranges); } } - private void applyMatchRangesTextPresentation() { - applyMatchesStyles(); - fScheduleMatchRangesPresentation = true; - - } - @Override public void setViewerInput(IDocument document, StyleRange[] matchRangers, IFile file) { fNewDocumentReconciliation = true; @@ -117,11 +97,9 @@ public void focusMatch(IRegion visibleRegion, IRegion revealedRange, int matchLi super.focusMatch(visibleRegion, revealedRange, matchLine, matchRange); } else { fDoCollectStyles = false; - fScheduleMatchRangesPresentation = false; // temporary disable scheduling match ranges presentation super.focusMatch(visibleRegion, revealedRange, matchLine, matchRange); // now apply collected styles fSourceViewer.changeTextPresentation(fMergedStylesPresentation, false); - applyMatchRangesTextPresentation(); // also enables scheduling match ranges presentation fDoCollectStyles = true; } } @@ -133,12 +111,6 @@ public GenericEditorViewer(Composite parent, CompositeRuler verticalRuler, int s super(parent, verticalRuler, styles); } - @Override - public void refresh() { - System.out.println("LALALALALALAL"); //$NON-NLS-1$ - // empty implementation - } - @Override public void setInput(Object input) { unconfigure(); From a1b1d2ab767da8dcd35e8fd29440fa9f7e2b8501 Mon Sep 17 00:00:00 2001 From: Jozef Tomek Date: Sun, 23 Mar 2025 16:10:19 +0100 Subject: [PATCH 6/6] Add ReconcilingAwareSourceViewerHandle Also remove key listener from match results table causing duplicate triggering of selection listener. --- .../ReconcilingAwareSourceViewerHandle.java | 162 ++++++++++++++++++ .../text/quicksearch/SourceViewerHandle.java | 47 ++--- .../internal/ui/QuickSearchDialog.java | 31 ---- .../GenericEditorViewerCreator.java | 68 +------- 4 files changed, 178 insertions(+), 130 deletions(-) create mode 100644 bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/ReconcilingAwareSourceViewerHandle.java diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/ReconcilingAwareSourceViewerHandle.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/ReconcilingAwareSourceViewerHandle.java new file mode 100644 index 00000000000..0e5c8751084 --- /dev/null +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/ReconcilingAwareSourceViewerHandle.java @@ -0,0 +1,162 @@ +package org.eclipse.text.quicksearch; + +import java.util.Iterator; + +import org.eclipse.core.resources.IFile; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextPresentationListener; +import org.eclipse.jface.text.TextPresentation; +import org.eclipse.jface.text.source.SourceViewer; +import org.eclipse.swt.custom.StyleRange; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.widgets.Composite; + +/** + * Extension of {@link SourceViewerHandle} which by means of registering itself as a {@link ITextPresentationListener} + * observes styles applied to source viewer's text from presentation reconcilers and merges quick search matches + * highlighting styles into them. It also collects all these styles to be later able to apply them when the handle + * is asked to focus different match in the document. This is to overcome shortcomings of those source viewers that use + * multiple presentation reconcilers but trigger only some of them on when viewer's visible area is changed on match + * focus. + * + * @since 1.4 + */ +public class ReconcilingAwareSourceViewerHandle extends SourceViewerHandle + implements ITextPresentationListener, DisposeListener{ + + private final boolean fCollectStylesInSetInput; + private final boolean fCollectStylesInFocusMatch; + + private boolean fFirstFocusMatch; + private boolean fDoCollectStyles; + /** + * Helps with applying {@link SourceViewerHandle#fMatchRanges} to some text presentation considering its extent. + */ + private TextPresentation fMatchStylesPresentation; + /** + * Union of styles collected from text presentations previously applied to the viewer. Already have match styles + * merged in. + */ + private TextPresentation fCollectedStylesPresentation; + + /** + * Creates new instance which, depending on collectStylesInSetInput and + * collectStylesInFocusMatch, will also collect applied styles while executing + * {@link #setViewerInput(IDocument, StyleRange[], IFile) setViewerInput()} and + * {@link #focusMatch(IRegion, IRegion, int, IRegion) focusMatch()} respectively. + * + * @param sourceViewerConfigurer the viewer configurer responsible for creation & setup of the viewer + * @param parent the parent SWT control for the viewer + * @param collectStylesInSetInput whether to collect styles also during execution of + * {@link #setViewerInput(IDocument, StyleRange[], IFile) setViewerInput()} method + * @param collectStylesInFocusMatch whether to collect styles also during execution of + * {@link #focusMatch(IRegion, IRegion, int, IRegion) focusMatch()} + */ + public ReconcilingAwareSourceViewerHandle(ISourceViewerConfigurer sourceViewerConfigurer, Composite parent, + boolean collectStylesInSetInput, boolean collectStylesInFocusMatch) { + super(sourceViewerConfigurer, parent); + fCollectStylesInSetInput = collectStylesInSetInput; + fCollectStylesInFocusMatch = collectStylesInFocusMatch; + fSourceViewer.addTextPresentationListener(this); + parent.addDisposeListener(this); + } + + @Override + public void setViewerInput(IDocument document, StyleRange[] matchRangers, IFile file) { + fFirstFocusMatch = true; + fDoCollectStyles = fCollectStylesInSetInput; + if (matchRangers != null && matchRangers.length > 0) { + fMatchStylesPresentation = new TextPresentation(matchRangers.length); + for (StyleRange styleRange : matchRangers) { + fMatchStylesPresentation.addStyleRange((StyleRange) styleRange.clone()); + } + } else { + // should never happen + fMatchStylesPresentation = null; + } + fCollectedStylesPresentation = new TextPresentation(1024); + super.setViewerInput(document, matchRangers, file); + fDoCollectStyles = true; + } + + @Override + public void focusMatch(IRegion visibleRegion, IRegion revealedRange, int matchLine, IRegion matchRange) { + fDoCollectStyles = fCollectStylesInFocusMatch; + try { + super.focusMatch(visibleRegion, revealedRange, matchLine, matchRange); + if (fFirstFocusMatch) { + // on 1st focus we don't need to apply collected styles yet + fFirstFocusMatch = false; + } else if (fCollectedStylesPresentation != null) { // should never be NULL here + fDoCollectStyles = false; + int mergedStyles = fCollectedStylesPresentation.getDenumerableRanges(); + if (mergedStyles > 0) { + var presentation = new TextPresentation(fCollectedStylesPresentation.getCoverage(), mergedStyles); + Iterator iter = fCollectedStylesPresentation.getAllStyleRangeIterator(); + while (iter.hasNext()) { + presentation.addStyleRange((StyleRange) iter.next().clone()); + } + fSourceViewer.changeTextPresentation(presentation, false); + } + } + } finally { + fDoCollectStyles = true; + } + } + + @Override + protected void postFocusMatch() { + // no-op to avoid replacing styles directly on text widget + } + + @Override + public void applyTextPresentation(TextPresentation textPresentation) { + applyMatchStyles(textPresentation, true); // always enrich with match styles + if (fDoCollectStyles && fCollectedStylesPresentation != null) { // should never be NULL here + for (Iterator iter = textPresentation.getAllStyleRangeIterator(); iter.hasNext();) { + // viewer's text widget also replaces style ranges with those arriving last + fCollectedStylesPresentation.replaceStyleRange((StyleRange) iter.next().clone()); + } + } + } + + /** + * Applies all matches highlighting styles previously passed to + * {@link #setViewerInput(IDocument, StyleRange[], IFile) setViewerInput()} method to presentation + * considering presentation's extent. Styles either replace ({@link TextPresentation#replaceStyleRange(StyleRange)}) + * or are merged ({@link TextPresentation#mergeStyleRange(StyleRange)}) with text presentation's styles in the + * particular ranges depending on mergeStyles parameter. + * + * @param mergeStyles true if the styles should be merged, false if they should replace + * text presentation styles in the same ranges + * @see #setViewerInput(IDocument, StyleRange[], IFile) + */ + protected void applyMatchStyles(TextPresentation presentation, boolean mergeStyles) { + if (fMatchStylesPresentation == null) { + // should never happen + return; + } + var extent = presentation.getExtent(); + int extentStart = extent.getOffset(); + fMatchStylesPresentation.setResultWindow(extent); + for (Iterator iter = fMatchStylesPresentation.getAllStyleRangeIterator(); iter.hasNext();) { + var style = iter.next(); + style.start += extentStart; + if (mergeStyles) { + presentation.mergeStyleRange(style); + } else { + presentation.replaceStyleRange(style); + } + } + } + + @Override + public void widgetDisposed(DisposeEvent e) { + fCollectedStylesPresentation = null; + fMatchStylesPresentation = null; + fSourceViewer.removeTextPresentationListener(this); + } + +} diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/SourceViewerHandle.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/SourceViewerHandle.java index c159be1ffa0..ec850e6bde4 100644 --- a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/SourceViewerHandle.java +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/SourceViewerHandle.java @@ -164,7 +164,16 @@ public void focusMatch(IRegion visibleRegion, IRegion revealedRange, int matchLi fFixedLineChangeModel.selectedMatchLine = matchLine; fChangeRulerColumn.redraw(); } + postFocusMatch(); + } + + /** + * Called at the end of {@link #focusMatch(IRegion, IRegion, int, IRegion) focusMatch()} execution. Applies matches styles via + * {@link #applyMatchesStyles()} by default. + */ + protected void postFocusMatch() { applyMatchesStyles(); + } /** @@ -180,8 +189,9 @@ protected void applyMatchesStyles() { return; } for (StyleRange styleRange : fMatchRanges) { - if (modelRange2WidgetStyleRange(styleRange) instanceof StyleRange range) { - fSourceViewer.getTextWidget().setStyleRange(range); + styleRange = modelRange2WidgetStyleRange(styleRange); + if (styleRange != null) { + fSourceViewer.getTextWidget().setStyleRange(styleRange); } } } @@ -197,39 +207,6 @@ private StyleRange modelRange2WidgetStyleRange(StyleRange range) { return null; } - /** - * Applies all matches highlighting styles previously passed to - * {@link #setViewerInput(IDocument, StyleRange[], IFile) setViewerInput()} method to presentation - * considering presentation's extent. Styles either replace ({@link TextPresentation#replaceStyleRange(StyleRange)}) - * or are merged ({@link TextPresentation#mergeStyleRange(StyleRange)}) with text presentation's styles in the - * particular ranges depending on mergeStyles parameter. - * - * @param mergeStyles true if the styles should be merged, false if they should replace - * text presentation styles in the same ranges - * @see #setViewerInput(IDocument, StyleRange[], IFile) - */ - protected void applyMatchStyles(TextPresentation presentation, boolean mergeStyles) { - if (fMatchRanges == null || fMatchRanges.length == 0) { - return; - } - var extent = presentation.getExtent(); - int extentStart = extent.getOffset(); - var tmpPresentation = new TextPresentation(fMatchRanges.length); - for (StyleRange styleRange : fMatchRanges) { - tmpPresentation.addStyleRange((StyleRange) styleRange.clone()); - } - tmpPresentation.setResultWindow(extent); - for (Iterator iter = tmpPresentation.getAllStyleRangeIterator(); iter.hasNext();) { - var style = iter.next(); - style.start += extentStart; - if (mergeStyles) { - presentation.mergeStyleRange(style); - } else { - presentation.replaceStyleRange(style); - } - } - } - /** * Annotation model implementation that announces single configured line as {@link ILineDiffInfo#CHANGED} for * quick diff feature of the viewer's {@link IChangeRulerColumn}. diff --git a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java index 5460028ca51..883304e22d6 100644 --- a/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java +++ b/bundles/org.eclipse.text.quicksearch/src/org/eclipse/text/quicksearch/internal/ui/QuickSearchDialog.java @@ -99,7 +99,6 @@ import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Table; @@ -892,36 +891,6 @@ public void keyPressed(KeyEvent e) { list.addDoubleClickListener(event -> handleDoubleClick()); - list.getTable().addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent e) { - - if (e.keyCode == SWT.ARROW_UP && (e.stateMask & SWT.SHIFT) == 0 - && (e.stateMask & SWT.CTRL) == 0) { - StructuredSelection selection = (StructuredSelection) list - .getSelection(); - - if (selection.size() == 1) { - Object element = selection.getFirstElement(); - if (element.equals(list.getElementAt(0))) { - pattern.setFocus(); - } - list.getTable().notifyListeners(SWT.Selection, - new Event()); - - } - } - - if (e.keyCode == SWT.ARROW_DOWN - && (e.stateMask & SWT.SHIFT) != 0 - && (e.stateMask & SWT.CTRL) != 0) { - - list.getTable().notifyListeners(SWT.Selection, new Event()); - } - - } - }); - createDetailsArea(sashForm); sashForm.setWeights(new int[] {5,2}); diff --git a/bundles/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/quicksearch/GenericEditorViewerCreator.java b/bundles/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/quicksearch/GenericEditorViewerCreator.java index af6511eb857..447d2a1ca57 100644 --- a/bundles/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/quicksearch/GenericEditorViewerCreator.java +++ b/bundles/org.eclipse.ui.genericeditor/src/org/eclipse/ui/internal/genericeditor/quicksearch/GenericEditorViewerCreator.java @@ -15,29 +15,21 @@ *******************************************************************************/ package org.eclipse.ui.internal.genericeditor.quicksearch; -import java.util.Iterator; - -import org.eclipse.core.resources.IFile; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.IRegion; -import org.eclipse.jface.text.ITextPresentationListener; -import org.eclipse.jface.text.TextPresentation; import org.eclipse.jface.text.source.CompositeRuler; import org.eclipse.jface.text.source.SourceViewer; -import org.eclipse.swt.custom.StyleRange; import org.eclipse.swt.widgets.Composite; import org.eclipse.text.quicksearch.ITextViewerCreator; +import org.eclipse.text.quicksearch.ReconcilingAwareSourceViewerHandle; import org.eclipse.text.quicksearch.SourceViewerConfigurer; -import org.eclipse.text.quicksearch.SourceViewerHandle; import org.eclipse.ui.editors.text.EditorsUI; import org.eclipse.ui.internal.genericeditor.ExtensionBasedTextViewerConfiguration; import org.eclipse.ui.internal.genericeditor.GenericEditorPlugin; import org.eclipse.ui.texteditor.ChainedPreferenceStore; /** - * Creates quicksearch text viewer handles that use - * {@link GenericEditorViewer2}. + * Creates quicksearch text viewer handles that use {@link GenericEditorViewer}. */ public class GenericEditorViewerCreator implements ITextViewerCreator { @@ -46,62 +38,10 @@ public ITextViewerHandle createTextViewer(Composite parent) { return new GenericEditorSourceViewerHandle(parent); } - private class GenericEditorSourceViewerHandle extends SourceViewerHandle - implements ITextPresentationListener { - private boolean fNewDocumentReconciliation; - - // after focusing on other match in the same document, not all reconciliations - // are performed again (e.g. LSP reconciliation is done only after setting new - // input document), so we collect all applied styles to be able to set them - // after other match focus manually - private boolean fDoCollectStyles; - private TextPresentation fMergedStylesPresentation; + private class GenericEditorSourceViewerHandle extends ReconcilingAwareSourceViewerHandle { public GenericEditorSourceViewerHandle(Composite parent) { - super(new SourceViewerConfigurer<>(GenericEditorViewer::new), parent); - fSourceViewer.addTextPresentationListener(this); - parent.addDisposeListener(e -> { - fMatchRanges = null; - fMergedStylesPresentation = null; - fSourceViewer.removeTextPresentationListener(this); - }); - } - - /* - * triggered variable number of times by a) tm4e code (possibly after setInput() - * and/or focusMatch() -> fSourceViewer.setVisibleRegion() ), b) lsp4e code - * (zero or more times after setInput() only) - */ - @Override - public void applyTextPresentation(TextPresentation textPresentation) { - if (fDoCollectStyles) { - applyMatchStyles(textPresentation, true); - for (Iterator iter = textPresentation.getAllStyleRangeIterator(); iter.hasNext();) { - fMergedStylesPresentation.mergeStyleRange((StyleRange) iter.next().clone()); - } - } - } - - @Override - public void setViewerInput(IDocument document, StyleRange[] matchRangers, IFile file) { - fNewDocumentReconciliation = true; - fMergedStylesPresentation = new TextPresentation(1024); - super.setViewerInput(document, matchRangers, file); - } - - @Override - public void focusMatch(IRegion visibleRegion, IRegion revealedRange, int matchLine, IRegion matchRange) { - if (fNewDocumentReconciliation) { - fNewDocumentReconciliation = false; - fDoCollectStyles = true; - super.focusMatch(visibleRegion, revealedRange, matchLine, matchRange); - } else { - fDoCollectStyles = false; - super.focusMatch(visibleRegion, revealedRange, matchLine, matchRange); - // now apply collected styles - fSourceViewer.changeTextPresentation(fMergedStylesPresentation, false); - fDoCollectStyles = true; - } + super(new SourceViewerConfigurer<>(GenericEditorViewer::new), parent, false, false); } }