From aa4768fe77615b457e4eb3aca5f0dd22b1ed7da2 Mon Sep 17 00:00:00 2001 From: Mickael Istria Date: Wed, 6 Sep 2017 22:45:53 +0200 Subject: Bug 521958 - Added generic debug hover And contribute it to Generic Editor Signed-off-by: Mickael Istria Change-Id: Ic7cfd1e231b117a2837e543f6dbb535594179662 --- .../debug/internal/ui/hover/DebugTextHover.java | 131 ++++++ .../hover/ExpressionInformationControlCreator.java | 471 +++++++++++++++++++++ 2 files changed, 602 insertions(+) create mode 100644 org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/hover/DebugTextHover.java create mode 100644 org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/hover/ExpressionInformationControlCreator.java (limited to 'org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui') diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/hover/DebugTextHover.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/hover/DebugTextHover.java new file mode 100644 index 000000000..cd9d44217 --- /dev/null +++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/hover/DebugTextHover.java @@ -0,0 +1,131 @@ +package org.eclipse.debug.internal.ui.hover; + +import org.eclipse.core.runtime.Adapters; +import org.eclipse.debug.core.DebugException; +import org.eclipse.debug.core.model.IVariable; +import org.eclipse.debug.internal.ui.DebugUIPlugin; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IInformationControlCreator; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextHover; +import org.eclipse.jface.text.ITextHoverExtension; +import org.eclipse.jface.text.ITextHoverExtension2; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.TextSelection; + + +public class DebugTextHover implements ITextHover, ITextHoverExtension, ITextHoverExtension2 { + + @Override + public IRegion getHoverRegion(ITextViewer textViewer, int offset) { + IVariable variable = getHoverInfo2(textViewer, new Region(offset, 0)); + if (variable == null) { + return null; + } + // assumes variable.getName() is the name of the variable as used in the document + try { + IDocument document = textViewer.getDocument(); + for (int prefix = Math.min(offset, variable.getName().length()); prefix >= 0 && document.getLength() > offset - prefix + variable.getName().length() ; prefix--) { + if (textViewer.getDocument().get(offset - prefix, variable.getName().length()).equals(variable.getName())) { + return new Region(offset - prefix, variable.getName().length()); + } + } + } catch (DebugException | BadLocationException ex) { + DebugUIPlugin.log(ex); + } + return null; + } + + + @Override + public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) { + if (textViewer == null || hoverRegion == null) { + return null; + } + Object object = getHoverInfo2(textViewer, hoverRegion); + if (object instanceof IVariable) { + IVariable var = (IVariable) object; + return getVariableText(var); + } + return null; + } + + /** + * Returns HTML text for the given variable + */ + private static String getVariableText(IVariable variable) { + try { + return replaceHTMLChars(variable.getValue().getValueString()) + "
" + replaceHTMLChars(variable.getReferenceTypeName()); //$NON-NLS-1$ + } catch (DebugException e) { + DebugUIPlugin.log(e); + return null; + } + } + + /** + * Replaces reserved HTML characters in the given string with + * their escaped equivalents. This is to ensure that variable + * values containing reserved characters are correctly displayed. + */ + private static String replaceHTMLChars(String variableText) { + StringBuffer buffer= new StringBuffer(variableText.length()); + char[] characters = variableText.toCharArray(); + for (int i = 0; i < characters.length; i++) { + char character= characters[i]; + switch (character) { + case '<': + buffer.append("<"); //$NON-NLS-1$ + break; + case '>': + buffer.append(">"); //$NON-NLS-1$ + break; + case '&': + buffer.append("&"); //$NON-NLS-1$ + break; + case '"': + buffer.append("""); //$NON-NLS-1$ + break; + default: + buffer.append(character); + } + } + return buffer.toString(); + } + + /** + * Returns the value of this filters preference (on/off) for the given + * view. + * + * @param part + * @return boolean + */ + public static boolean getBooleanPreferenceValue(String id, String preference) { + String compositeKey = id + "." + preference; //$NON-NLS-1$ + IPreferenceStore store = DebugUIPlugin.getDefault().getPreferenceStore(); + boolean value = false; + if (store.contains(compositeKey)) { + value = store.getBoolean(compositeKey); + } else { + value = store.getBoolean(preference); + } + return value; + } + + @Override + public IInformationControlCreator getHoverControlCreator() { + return new ExpressionInformationControlCreator(); + } + + @Override + public IVariable getHoverInfo2(ITextViewer textViewer, IRegion hoverRegion) { + if (textViewer == null || hoverRegion == null) { + return null; + } + TextSelection sel = new TextSelection(textViewer.getDocument(), hoverRegion.getOffset(), hoverRegion.getLength()); + return Adapters.adapt(sel, IVariable.class); + } + +} diff --git a/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/hover/ExpressionInformationControlCreator.java b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/hover/ExpressionInformationControlCreator.java new file mode 100644 index 000000000..980925a44 --- /dev/null +++ b/org.eclipse.debug.ui/ui/org/eclipse/debug/internal/ui/hover/ExpressionInformationControlCreator.java @@ -0,0 +1,471 @@ +package org.eclipse.debug.internal.ui.hover; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.debug.core.model.IVariable; +import org.eclipse.debug.internal.ui.DebugUIPlugin; +import org.eclipse.debug.internal.ui.SWTFactory; +import org.eclipse.debug.internal.ui.model.elements.ElementContentProvider; +import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate; +import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext; +import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate; +import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdateListener; +import org.eclipse.debug.internal.ui.viewers.model.provisional.PresentationContext; +import org.eclipse.debug.internal.ui.viewers.model.provisional.TreeModelViewer; +import org.eclipse.debug.internal.ui.views.variables.details.DefaultDetailPane; +import org.eclipse.debug.internal.ui.views.variables.details.DetailPaneProxy; +import org.eclipse.debug.internal.ui.views.variables.details.IDetailPaneContainer; +import org.eclipse.debug.ui.AbstractDebugView; +import org.eclipse.debug.ui.IDebugUIConstants; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.text.AbstractInformationControl; +import org.eclipse.jface.text.IInformationControl; +import org.eclipse.jface.text.IInformationControlCreator; +import org.eclipse.jface.text.IInformationControlExtension2; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.StructuredViewer; +import org.eclipse.jface.viewers.TreePath; +import org.eclipse.jface.viewers.TreeSelection; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Layout; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchPartSite; +import org.eclipse.ui.PlatformUI; + +/** + * Creates an information control to display an expression in a hover control. + * + * @noextend This class is not intended to be subclassed by clients. + * + * @since 3.3 + */ +public class ExpressionInformationControlCreator implements IInformationControlCreator { + + class ExpressionInformationControl extends AbstractInformationControl implements IInformationControlExtension2 { + + /** + * Dialog setting key for height + */ + private static final String HEIGHT = "HEIGHT"; //$NON-NLS-1$ + + /** + * Dialog setting key for width. + */ + private static final String WIDTH = "WIDTH"; //$NON-NLS-1$ + + /** + * Dialog setting key for tree sash weight + */ + private static final String SASH_WEIGHT_TREE = "SashWeightTree"; //$NON-NLS-1$ + + /** + * Dialog setting key for details sash weight + */ + private static final String SASH_WEIGHT_DETAILS = "SashWeightDetails"; //$NON-NLS-1$ + + /** + * Variable to display. + */ + private IVariable fVariable; + + private IPresentationContext fContext; + private TreeModelViewer fViewer; + private SashForm fSashForm; + private Composite fDetailPaneComposite; + private DetailPaneProxy fDetailPane; + private Tree fTree; + + /** + * Creates the content for the root element of the tree viewer in the hover + */ + private class TreeRoot extends ElementContentProvider { + /* (non-Javadoc) + * @see org.eclipse.debug.internal.ui.viewers.model.provisional.elements.ElementContentProvider#getChildCount(java.lang.Object, org.eclipse.debug.internal.ui.viewers.provisional.IPresentationContext) + */ + @Override + protected int getChildCount(Object element, IPresentationContext context, IViewerUpdate monitor) throws CoreException { + return 1; + } + /* (non-Javadoc) + * @see org.eclipse.debug.internal.ui.viewers.model.provisional.elements.ElementContentProvider#getChildren(java.lang.Object, int, int, org.eclipse.debug.internal.ui.viewers.provisional.IPresentationContext) + */ + @Override + protected Object[] getChildren(Object parent, int index, int length, IPresentationContext context, IViewerUpdate monitor) throws CoreException { + return new Object[] { fVariable }; + } + + /* (non-Javadoc) + * @see org.eclipse.debug.internal.ui.viewers.model.provisional.elements.ElementContentProvider#supportsContextId(java.lang.String) + */ + @Override + protected boolean supportsContextId(String id) { + return true; + } + } + + /** + * Inner class implementing IDetailPaneContainer methods. Handles changes to detail + * pane and provides limited access to the detail pane proxy. + */ + private class DetailPaneContainer implements IDetailPaneContainer{ + + /* (non-Javadoc) + * @see org.eclipse.debug.internal.ui.views.variables.details.IDetailPaneContainer#getCurrentPaneID() + */ + @Override + public String getCurrentPaneID() { + return fDetailPane.getCurrentPaneID(); + } + + /* (non-Javadoc) + * @see org.eclipse.debug.internal.ui.views.variables.details.IDetailPaneContainer#getCurrentSelection() + */ + @Override + public IStructuredSelection getCurrentSelection() { + return (IStructuredSelection)fViewer.getSelection(); + } + + /* (non-Javadoc) + * @see org.eclipse.debug.internal.ui.views.variables.details.IDetailPaneContainer#refreshDetailPaneContents() + */ + @Override + public void refreshDetailPaneContents() { + fDetailPane.display(getCurrentSelection()); + } + + /* (non-Javadoc) + * @see org.eclipse.debug.internal.ui.views.variables.details.IDetailPaneContainer#getParentComposite() + */ + @Override + public Composite getParentComposite() { + return fDetailPaneComposite; + } + + /* (non-Javadoc) + * @see org.eclipse.debug.internal.ui.views.variables.details.IDetailPaneContainer#getWorkbenchPartSite() + */ + @Override + public IWorkbenchPartSite getWorkbenchPartSite() { + return null; + } + + /* (non-Javadoc) + * @see org.eclipse.debug.internal.ui.views.variables.details.IDetailPaneContainer#paneChanged(java.lang.String) + */ + @Override + public void paneChanged(String newPaneID) { + if (newPaneID.equals(DefaultDetailPane.ID)){ + fDetailPane.getCurrentControl().setForeground(getShell().getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND)); + fDetailPane.getCurrentControl().setBackground(getShell().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); + } + } + + } + + /** + * Constructs a new control in the given shell. + * + * @param parentShell shell + * @param resize whether resize is supported + */ + ExpressionInformationControl(Shell parentShell, boolean resize) { + super(parentShell, resize); + create(); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.AbstractInformationControl#computeSizeHint() + */ + @Override + public Point computeSizeHint() { + IDialogSettings settings = getDialogSettings(false); + if (settings != null) { + int x = getIntSetting(settings, WIDTH); + if (x > 0) { + int y = getIntSetting(settings, HEIGHT); + if (y > 0) { + return new Point(x,y); + } + } + } + return super.computeSizeHint(); + } + + /** + * Returns the dialog settings for this hover or null if none + * + * @param create whether to create the settings + */ + private IDialogSettings getDialogSettings(boolean create) { + IDialogSettings settings = DebugUIPlugin.getDefault().getDialogSettings(); + IDialogSettings section = settings.getSection(this.getClass().getName()); + if (section == null & create) { + section = settings.addNewSection(this.getClass().getName()); + } + return section; + } + + /** + * Returns an integer value in the given dialog settings or -1 if none. + * + * @param settings dialog settings + * @param key key + * @return value or -1 if not present + */ + private int getIntSetting(IDialogSettings settings, String key) { + try { + return settings.getInt(key); + } catch (NumberFormatException e) { + return -1; + } + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.AbstractInformationControl#dispose() + */ + @Override + public void dispose() { + persistSettings(getShell()); + fContext.dispose(); + super.dispose(); + } + + /** + * Persists dialog settings. + * + * @param shell + */ + private void persistSettings(Shell shell) { + if (shell != null && !shell.isDisposed()) { + if (isResizable()) { + IDialogSettings settings = getDialogSettings(true); + Point size = shell.getSize(); + settings.put(WIDTH, size.x); + settings.put(HEIGHT, size.y); + int[] weights = fSashForm.getWeights(); + settings.put(SASH_WEIGHT_TREE, weights[0]); + settings.put(SASH_WEIGHT_DETAILS, weights[1]); + } + } + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.AbstractInformationControl#setVisible(boolean) + */ + @Override + public void setVisible(boolean visible) { + if (!visible) { + persistSettings(getShell()); + } + super.setVisible(visible); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.AbstractInformationControl#createContent(org.eclipse.swt.widgets.Composite) + */ + @Override + protected void createContent(Composite parent) { + + fSashForm = new SashForm(parent, parent.getStyle()); + fSashForm.setOrientation(SWT.VERTICAL); + + // update presentation context + AbstractDebugView view = getViewToEmulate(); + fContext = new PresentationContext(IDebugUIConstants.ID_VARIABLE_VIEW); + if (view != null) { + // copy over properties + IPresentationContext copy = ((TreeModelViewer)view.getViewer()).getPresentationContext(); + String[] properties = copy.getProperties(); + for (int i = 0; i < properties.length; i++) { + String key = properties[i]; + fContext.setProperty(key, copy.getProperty(key)); + } + } + + fViewer = new TreeModelViewer(fSashForm, SWT.NO_TRIM | SWT.MULTI | SWT.VIRTUAL, fContext); + fViewer.setAutoExpandLevel(1); + + if (view != null) { + // copy over filters + StructuredViewer structuredViewer = (StructuredViewer) view.getViewer(); + if (structuredViewer != null) { + ViewerFilter[] filters = structuredViewer.getFilters(); + for (int i = 0; i < filters.length; i++) { + fViewer.addFilter(filters[i]); + } + } + } + + fDetailPaneComposite = SWTFactory.createComposite(fSashForm, 1, 1, GridData.FILL_BOTH); + Layout layout = fDetailPaneComposite.getLayout(); + if (layout instanceof GridLayout) { + GridLayout gl = (GridLayout) layout; + gl.marginHeight = 0; + gl.marginWidth = 0; + } + + fDetailPane = new DetailPaneProxy(new DetailPaneContainer()); + fDetailPane.display(null); // Bring up the default pane so the user doesn't see an empty composite + + fTree = fViewer.getTree(); + fTree.addSelectionListener(new SelectionListener() { + @Override + public void widgetSelected(SelectionEvent e) { + fDetailPane.display((IStructuredSelection)fViewer.getSelection()); + } + @Override + public void widgetDefaultSelected(SelectionEvent e) {} + }); + + initSashWeights(); + + // add update listener to auto-select and display details of root expression + fViewer.addViewerUpdateListener(new IViewerUpdateListener() { + @Override + public void viewerUpdatesComplete() { + } + @Override + public void viewerUpdatesBegin() { + } + @Override + public void updateStarted(IViewerUpdate update) { + } + @Override + public void updateComplete(IViewerUpdate update) { + if (update instanceof IChildrenUpdate) { + TreeSelection selection = new TreeSelection(new TreePath(new Object[]{fVariable})); + fViewer.setSelection(selection); + fDetailPane.display(selection); + fViewer.removeViewerUpdateListener(this); + } + } + }); + + setForegroundColor(getShell().getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND)); + setBackgroundColor(getShell().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); + } + + + /** + * Attempts to find an appropriate view to emulate, this will either be the + * variables view or the expressions view. + * @return a view to emulate or null + */ + private AbstractDebugView getViewToEmulate() { + IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); + AbstractDebugView expressionsView = (AbstractDebugView) page.findView(IDebugUIConstants.ID_EXPRESSION_VIEW); + if (expressionsView != null && expressionsView.isVisible()) { + return expressionsView; + } + AbstractDebugView variablesView = (AbstractDebugView) page.findView(IDebugUIConstants.ID_VARIABLE_VIEW); + if (variablesView != null && variablesView.isVisible()) { + return variablesView; + } + if (expressionsView != null) { + return expressionsView; + } + return variablesView; + } + + /** + * Initializes the sash form weights from the preference store (using default values if + * no sash weights were stored previously). + */ + protected void initSashWeights(){ + IDialogSettings settings = getDialogSettings(false); + if (settings != null) { + int tree = getIntSetting(settings, SASH_WEIGHT_TREE); + if (tree > 0) { + int details = getIntSetting(settings, SASH_WEIGHT_DETAILS); + if (details > 0) { + fSashForm.setWeights(new int[]{tree, details}); + } + } + } + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.AbstractInformationControl#setForegroundColor(org.eclipse.swt.graphics.Color) + */ + @Override + public void setForegroundColor(Color foreground) { + super.setForegroundColor(foreground); + fDetailPaneComposite.setForeground(foreground); + fTree.setForeground(foreground); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.AbstractInformationControl#setBackgroundColor(org.eclipse.swt.graphics.Color) + */ + @Override + public void setBackgroundColor(Color background) { + super.setBackgroundColor(background); + fDetailPaneComposite.setBackground(background); + fTree.setBackground(background); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.AbstractInformationControl#setFocus() + */ + @Override + public void setFocus() { + super.setFocus(); + fTree.setFocus(); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.IInformationControlExtension#hasContents() + */ + @Override + public boolean hasContents() { + return fVariable != null; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.IInformationControlExtension2#setInput(java.lang.Object) + */ + @Override + public void setInput(Object input) { + if (input instanceof IVariable) { + fVariable = (IVariable) input; + fViewer.setInput(new TreeRoot()); + } + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.AbstractInformationControl#getInformationPresenterControlCreator() + */ + @Override + public IInformationControlCreator getInformationPresenterControlCreator() { + return new ExpressionInformationControlCreator() { + /* (non-Javadoc) + * @see org.eclipse.jdt.internal.debug.ui.ExpressionInformationControlCreator#createInformationControl(org.eclipse.swt.widgets.Shell) + */ + @Override + public IInformationControl createInformationControl(Shell shell) { + return new ExpressionInformationControl(shell, true); + } + }; + } + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.IInformationControlCreator#createInformationControl(org.eclipse.swt.widgets.Shell) + */ + @Override + public IInformationControl createInformationControl(Shell parent) { + return new ExpressionInformationControl(parent, false); + } + + +} -- cgit v1.2.3