diff --git a/bundles/org.eclipse.e4.ui.workbench.renderers.swt/src/org/eclipse/e4/ui/internal/workbench/renderers/swt/AbstractTableInformationControl.java b/bundles/org.eclipse.e4.ui.workbench.renderers.swt/src/org/eclipse/e4/ui/internal/workbench/renderers/swt/AbstractTableInformationControl.java index f4333185d8f..79391dc04aa 100644 --- a/bundles/org.eclipse.e4.ui.workbench.renderers.swt/src/org/eclipse/e4/ui/internal/workbench/renderers/swt/AbstractTableInformationControl.java +++ b/bundles/org.eclipse.e4.ui.workbench.renderers.swt/src/org/eclipse/e4/ui/internal/workbench/renderers/swt/AbstractTableInformationControl.java @@ -414,7 +414,7 @@ private void setMatcherString(String pattern) { stringMatcherUpdated(); } - private SearchPattern getMatcher() { + protected SearchPattern getMatcher() { return fSearchPattern; } diff --git a/bundles/org.eclipse.e4.ui.workbench.renderers.swt/src/org/eclipse/e4/ui/internal/workbench/renderers/swt/BasicPartList.java b/bundles/org.eclipse.e4.ui.workbench.renderers.swt/src/org/eclipse/e4/ui/internal/workbench/renderers/swt/BasicPartList.java index 83df2a75389..2ca1f2a7cf6 100644 --- a/bundles/org.eclipse.e4.ui.workbench.renderers.swt/src/org/eclipse/e4/ui/internal/workbench/renderers/swt/BasicPartList.java +++ b/bundles/org.eclipse.e4.ui.workbench.renderers.swt/src/org/eclipse/e4/ui/internal/workbench/renderers/swt/BasicPartList.java @@ -28,13 +28,21 @@ import org.eclipse.e4.ui.workbench.modeling.EPartService; import org.eclipse.e4.ui.workbench.renderers.swt.CTabRendering; import org.eclipse.e4.ui.workbench.renderers.swt.StackRenderer; +import org.eclipse.e4.ui.workbench.swt.internal.copy.SearchPattern; +import org.eclipse.e4.ui.workbench.swt.internal.copy.StyledStringHighlighter; import org.eclipse.jface.viewers.ArrayContentProvider; -import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.BoldStylerProvider; import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.ILabelProviderListener; +import org.eclipse.jface.viewers.StyledCellLabelProvider; +import org.eclipse.jface.viewers.StyledString; import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CTabItem; +import org.eclipse.swt.custom.StyleRange; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.Image; @@ -46,9 +54,10 @@ public class BasicPartList extends AbstractTableInformationControl { - private class BasicStackListLabelProvider extends ColumnLabelProvider { + private class BasicStackListLabelProvider extends StyledCellLabelProvider implements ILabelProvider { private final Font boldFont; + private BoldStylerProvider boldStylerProvider; public BasicStackListLabelProvider() { Font font = Display.getDefault().getSystemFont(); @@ -60,14 +69,45 @@ public BasicStackListLabelProvider() { } @Override - public Font getFont(Object element) { + public void update(ViewerCell cell) { + Object element = cell.getElement(); + String text = getText(element); + cell.setText(text); + cell.setImage(getImage(element)); + cell.setFont(isHidden(element) ? boldFont : null); + + SearchPattern matcher = getMatcher(); + if (matcher == null || text == null || text.isEmpty()) { + cell.setStyleRanges(null); + } else { + int prefixOffset = text.startsWith("*") ? 1 : 0; //$NON-NLS-1$ + String unprefixed = text.substring(prefixOffset); + StyledString styled = new StyledStringHighlighter().highlight(unprefixed, matcher.getPattern(), + getBoldStylerProvider().getBoldStyler()); + StyleRange[] ranges = styled.getStyleRanges(); + if (prefixOffset > 0) { + for (StyleRange range : ranges) { + range.start += prefixOffset; + } + } + cell.setStyleRanges(ranges); + } + super.update(cell); + } + + private boolean isHidden(Object element) { if (element instanceof MPart part) { CTabItem item = renderer.findItemForPart(part); - if (item != null && !item.isShowing()) { - return boldFont; - } + return item != null && !item.isShowing(); } - return super.getFont(element); + return false; + } + + private BoldStylerProvider getBoldStylerProvider() { + if (boldStylerProvider == null) { + boldStylerProvider = new BoldStylerProvider(boldFont); + } + return boldStylerProvider; } @Override @@ -81,7 +121,6 @@ public String getText(Object element) { @Override public Image getImage(Object element) { - // Check if icons should be hidden for view tabs if (shouldHideIcons()) { return null; } @@ -98,8 +137,28 @@ public boolean useNativeToolTip(Object object) { return true; } + @Override + public void addListener(ILabelProviderListener listener) { + // no-op + } + + @Override + public void removeListener(ILabelProviderListener listener) { + // no-op + } + + @Override + public boolean isLabelProperty(Object element, String property) { + return false; + } + @Override public void dispose() { + super.dispose(); + if (boldStylerProvider != null) { + boldStylerProvider.dispose(); + boldStylerProvider = null; + } boldFont.dispose(); } } diff --git a/bundles/org.eclipse.e4.ui.workbench.swt/src/org/eclipse/e4/ui/workbench/swt/internal/copy/StyledStringHighlighter.java b/bundles/org.eclipse.e4.ui.workbench.swt/src/org/eclipse/e4/ui/workbench/swt/internal/copy/StyledStringHighlighter.java new file mode 100644 index 00000000000..2567dff554e --- /dev/null +++ b/bundles/org.eclipse.e4.ui.workbench.swt/src/org/eclipse/e4/ui/workbench/swt/internal/copy/StyledStringHighlighter.java @@ -0,0 +1,130 @@ +/******************************************************************************* + * Copyright (c) 2019 Uenal Akkaya and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Uenal Akkaya - initial API and implementation + * Patrik Suzzi - Bug 552144 + *******************************************************************************/ +package org.eclipse.e4.ui.workbench.swt.internal.copy; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.StyledString.Styler; + +/** + * Internal copy of {@code org.eclipse.ui.dialogs.StyledStringHighlighter} so the + * e4 layer can highlight matches without depending on + * {@code org.eclipse.ui.workbench}. + */ +public class StyledStringHighlighter { + + private static final String QUOTE_START = "(\\Q"; //$NON-NLS-1$ + private static final String QUOTE_END = "\\E)"; //$NON-NLS-1$ + private static final String DOT_STAR_LAZY = ".*?"; //$NON-NLS-1$ + private static final String DOT = "."; //$NON-NLS-1$ + private static final String QMARK = "?"; //$NON-NLS-1$ + private static final String STAR = "*"; //$NON-NLS-1$ + private static final char TERMINATOR = '<'; + + public StyledString highlight(String text, String pattern, Styler styler) { + if (text == null || text.isEmpty()) { + return new StyledString(); + } + StyledString styledString = new StyledString(text); + + if (pattern == null || pattern.isEmpty() // + || STAR.equals(pattern) || QMARK.equals(pattern)) { + return styledString; + } + + pattern = transformWildcardToRegex(pattern); + + try { + highlight(text, pattern, styledString, styler); + } catch (Exception e) { + // in case of an exception a highlighting of the text won't take place + } + + return styledString; + } + + private static String transformWildcardToRegex(String pattern) { + char[] chars = pattern.toCharArray(); + int len = chars.length; + StringBuilder sb = new StringBuilder(); + boolean quoting = false; + boolean prevStar = false; + boolean prevChar = false; + for (int i = 0; i < len; i++) { + char c = chars[i]; + boolean isWild = isWildcard(c); + if (isWild) { + if (quoting) { + sb.append(QUOTE_END); + quoting = false; + } + if (c == '*') { + if (prevStar) { + continue; + } + sb.append(DOT_STAR_LAZY); + } else { + sb.append(DOT); + } + if (i < len - 1 && !isWildcard(chars[i + 1])) { + sb.append(QUOTE_START); + quoting = true; + } + } else { + if (!quoting) { + sb.append(QUOTE_START); + quoting = true; + } + if (prevChar && Character.isUpperCase(c)) { + sb.append(QUOTE_END); + sb.append(DOT_STAR_LAZY); + sb.append(QUOTE_START); + } + if (c != TERMINATOR) { + sb.append(c); + } + if (i == len - 1) { + sb.append(QUOTE_END); + quoting = false; + } + } + prevChar = !isWild; + prevStar = c == '*'; + } + return sb.toString(); + } + + private static boolean isWildcard(char c) { + return c == '?' || c == '*'; + } + + private void highlight(String text, String filterPattern, StyledString styledString, Styler boldStyler) { + Pattern pattern = Pattern.compile(filterPattern, Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(text); + + while (matcher.find()) { + int groupCount = matcher.groupCount(); + if (groupCount == 0) { + styledString.setStyle(matcher.start(), matcher.end() - matcher.start(), boldStyler); + } else { + for (int i = 1; i <= groupCount; i++) { + styledString.setStyle(matcher.start(i), matcher.end(i) - matcher.start(i), boldStyler); + } + } + } + } + +}