| /******************************************************************************* |
| * Copyright (c) 2019 IBM Corporation 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: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.debug.ui.variables; |
| |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Stack; |
| import java.util.regex.Pattern; |
| |
| import org.eclipse.debug.internal.ui.SWTFactory; |
| import org.eclipse.debug.internal.ui.views.variables.details.DefaultDetailPane; |
| import org.eclipse.jdt.debug.core.IJavaVariable; |
| import org.eclipse.jdt.internal.debug.ui.ExpressionInformationControlCreator; |
| import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin; |
| import org.eclipse.jdt.internal.debug.ui.propertypages.PropertyPageMessages; |
| import org.eclipse.jface.dialogs.DialogSettings; |
| import org.eclipse.jface.dialogs.IDialogSettings; |
| import org.eclipse.jface.text.source.SourceViewer; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.FocusEvent; |
| import org.eclipse.swt.events.FocusListener; |
| import org.eclipse.swt.events.SelectionAdapter; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.graphics.GC; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.widgets.Combo; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| |
| /** |
| * Java Variable detail pane. |
| * |
| * @since 3.10 |
| */ |
| public class JavaVariablesDetailPane extends DefaultDetailPane { |
| |
| /** |
| * Identifier for this Java Variable detail pane editor |
| */ |
| public static final String JAVA_VARIABLE_DETAIL_PANE_VARIABLES = JDIDebugUIPlugin.getUniqueIdentifier() + ".JAVA_VARIABLE_DETAIL_PANE_VARIABLES"; //$NON-NLS-1$ |
| public static final String NAME = PropertyPageMessages.JavaVariableDetailsPane_name; |
| public static final String DESCRIPTION = PropertyPageMessages.JavaVariableDetailsPane_description; |
| |
| private FocusListener focusListener; |
| private Combo fExpressionHistory; |
| private IDialogSettings fExpressionHistoryDialogSettings; |
| private Map<IJavaVariable, Stack<String>> fLocalExpressionHistory; |
| private int fSeparatorIndex; |
| private static final int MAX_HISTORY_SIZE = 10; |
| private static final String DS_SECTION_EXPRESSION_HISTORY = "expressionHistory"; //$NON-NLS-1$ |
| private static final String DS_KEY_HISTORY_ENTRY_COUNT = "expressionHistoryEntryCount"; //$NON-NLS-1$ |
| private static final String DS_KEY_HISTORY_ENTRY_PREFIX = "expressionHistoryEntry_"; //$NON-NLS-1$ |
| private static final Pattern NEWLINE_PATTERN = Pattern.compile("\r\n|\r|\n"); //$NON-NLS-1$ ; |
| |
| private IJavaVariable fVariable; |
| public JavaVariablesDetailPane() { |
| fExpressionHistoryDialogSettings = DialogSettings.getOrCreateSection(JDIDebugUIPlugin.getDefault().getDialogSettings(), DS_SECTION_EXPRESSION_HISTORY); |
| } |
| |
| @Override |
| public Control createControl(Composite parent) { |
| if (!isInView()) { |
| Control c = super.createControl(parent); |
| c.setBackground(ExpressionInformationControlCreator.getSystemBackgroundColor()); |
| return c; |
| } |
| if (fExpressionHistoryDialogSettings != null) { |
| fLocalExpressionHistory = new HashMap<>(); |
| fExpressionHistory = SWTFactory.createCombo(parent, SWT.DROP_DOWN | SWT.READ_ONLY, 1, null); |
| fExpressionHistory.addSelectionListener(new SelectionAdapter() { |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| int historyIndex = fExpressionHistory.getSelectionIndex() - 1; |
| if (historyIndex >= 0 && historyIndex != fSeparatorIndex && getSourceViewer() != null) { |
| getSourceViewer().getDocument().set(getExpressionHistory()[historyIndex]); |
| } |
| } |
| }); |
| GridData data = new GridData(GridData.FILL_HORIZONTAL); |
| data.widthHint = 10; |
| fExpressionHistory.setLayoutData(data); |
| fExpressionHistory.setEnabled(false); |
| } |
| Control newControl = super.createControl(parent); |
| SourceViewer viewer = getSourceViewer(); |
| focusListener = new FocusListener() { |
| |
| @Override |
| public void focusLost(FocusEvent e) { |
| updateExpressionHistories(); |
| initializeExpressionHistoryDropDown(); |
| } |
| |
| @Override |
| public void focusGained(FocusEvent e) { |
| |
| } |
| }; |
| viewer.getTextWidget().addFocusListener(focusListener); |
| return newControl; |
| } |
| |
| /** |
| * Initializes the Expression history drop-down with values. |
| */ |
| private void initializeExpressionHistoryDropDown() { |
| fExpressionHistory.setItems(getExpressionHistoryLabels()); |
| String userHint = PropertyPageMessages.JavaVariableDetailsPane_choosePreviousExpression; |
| fExpressionHistory.add(userHint, 0); |
| fExpressionHistory.setText(userHint); |
| } |
| |
| /** |
| * Returns the Expression history labels for the current variale. |
| * |
| * @return an array of strings containing the Expression history labels |
| */ |
| private String[] getExpressionHistoryLabels() { |
| String[] expressions = getExpressionHistory(); |
| String[] labels = new String[expressions.length]; |
| for (int i = 0; i < expressions.length; i++) { |
| labels[i] = NEWLINE_PATTERN.matcher(expressions[i]).replaceAll(" "); //$NON-NLS-1$ |
| } |
| return labels; |
| } |
| |
| /** |
| * Returns the Expression history entries for the current variable. |
| * |
| * @return an array of strings containing the history of Expressions |
| */ |
| private String[] getExpressionHistory() { |
| fSeparatorIndex = -1; |
| |
| // Get global history |
| String[] globalItems = readExpressionHistory(fExpressionHistoryDialogSettings); |
| |
| // Get local history |
| Stack<String> localHistory = fLocalExpressionHistory.get(fVariable); |
| if (localHistory == null) { |
| return globalItems; |
| } |
| |
| // Create combined history |
| int localHistorySize = Math.min(localHistory.size(), MAX_HISTORY_SIZE); |
| String[] historyItems = new String[localHistorySize + globalItems.length + 1]; |
| for (int i = 0; i < localHistorySize; i++) { |
| historyItems[i] = localHistory.get(localHistory.size() - i - 1); |
| } |
| fSeparatorIndex = localHistorySize; |
| historyItems[localHistorySize] = getSeparatorLabel(); |
| System.arraycopy(globalItems, 0, historyItems, localHistorySize + 1, globalItems.length); |
| return historyItems; |
| } |
| |
| /** |
| * Updates the local and global Expression histories. |
| */ |
| private void updateExpressionHistories() { |
| String newItem = getSourceViewer().getDocument().get(); |
| if (newItem.length() == 0) { |
| return; |
| } |
| |
| // Update local history |
| Stack<String> localHistory = fLocalExpressionHistory.get(fVariable); |
| if (localHistory == null) { |
| localHistory = new Stack<>(); |
| fLocalExpressionHistory.put(fVariable, localHistory); |
| } |
| |
| localHistory.remove(newItem); |
| localHistory.push(newItem); |
| |
| // Update global history |
| String[] globalItems = readExpressionHistory(fExpressionHistoryDialogSettings); |
| if (globalItems.length > 0 && newItem.equals(globalItems[0])) { |
| return; |
| } |
| |
| if (globalItems.length == 0) { |
| globalItems = new String[1]; |
| } else { |
| String[] tempItems = new String[globalItems.length + 1]; |
| System.arraycopy(globalItems, 0, tempItems, 1, globalItems.length); |
| globalItems = tempItems; |
| } |
| globalItems[0] = newItem; |
| storeExpressionHistory(globalItems, fExpressionHistoryDialogSettings); |
| } |
| |
| /** |
| * Reads the Expression history from the given dialog settings. |
| * |
| * @param dialogSettings |
| * the dialog settings |
| * @return the Expression history |
| */ |
| private static String[] readExpressionHistory(IDialogSettings dialogSettings) { |
| int count = 0; |
| try { |
| count = dialogSettings.getInt(DS_KEY_HISTORY_ENTRY_COUNT); |
| } catch (NumberFormatException ex) { |
| // No history yet |
| } |
| count = Math.min(count, MAX_HISTORY_SIZE); |
| String[] expressions = new String[count]; |
| for (int i = 0; i < count; i++) { |
| expressions[i] = dialogSettings.get(DS_KEY_HISTORY_ENTRY_PREFIX + i); |
| } |
| return expressions; |
| } |
| |
| /** |
| * Writes the given Expressions into the given dialog settings. |
| * |
| * @param expressions |
| * an array of strings containing the Expressions |
| * @param dialogSettings |
| * the dialog settings |
| */ |
| private static void storeExpressionHistory(String[] expressions, IDialogSettings dialogSettings) { |
| int length = Math.min(expressions.length, MAX_HISTORY_SIZE); |
| int count = 0; |
| outer: for (int i = 0; i < length; i++) { |
| for (int j = 0; j < i; j++) { |
| if (expressions[i].equals(expressions[j])) { |
| break outer; |
| } |
| } |
| dialogSettings.put(DS_KEY_HISTORY_ENTRY_PREFIX + count, expressions[i]); |
| count = count + 1; |
| } |
| dialogSettings.put(DS_KEY_HISTORY_ENTRY_COUNT, count); |
| } |
| |
| /** |
| * Returns the label for the history separator. |
| * |
| * @return the label for the history separator |
| */ |
| private String getSeparatorLabel() { |
| int borderWidth = fExpressionHistory.computeTrim(0, 0, 0, 0).width; |
| Rectangle rect = fExpressionHistory.getBounds(); |
| int width = rect.width - borderWidth; |
| |
| GC gc = new GC(fExpressionHistory); |
| gc.setFont(fExpressionHistory.getFont()); |
| |
| int fSeparatorWidth = gc.getAdvanceWidth('-'); |
| String separatorLabel = PropertyPageMessages.JavaVariableDetailsPane_historySeparator; |
| int fMessageLength = gc.textExtent(separatorLabel).x; |
| |
| gc.dispose(); |
| |
| StringBuilder dashes = new StringBuilder(); |
| int chars = (((width - fMessageLength) / fSeparatorWidth) / 2) - 2; |
| for (int i = 0; i < chars; i++) { |
| dashes.append('-'); |
| } |
| |
| StringBuilder result = new StringBuilder(); |
| result.append(dashes); |
| result.append(" " + separatorLabel + " "); //$NON-NLS-1$//$NON-NLS-2$ |
| result.append(dashes); |
| return result.toString().trim(); |
| } |
| |
| @Override |
| public String getDescription() { |
| return DESCRIPTION; |
| } |
| |
| @Override |
| public String getID() { |
| return JAVA_VARIABLE_DETAIL_PANE_VARIABLES; |
| } |
| |
| @Override |
| public String getName() { |
| return NAME; |
| } |
| |
| @Override |
| public void display(IStructuredSelection selection) { |
| if (fExpressionHistory != null && selection != null && selection.getFirstElement() instanceof IJavaVariable) { |
| IJavaVariable variable = (IJavaVariable) (selection.getFirstElement()); |
| if (fVariable == null || !fVariable.equals(variable)) { |
| fVariable = variable; |
| fExpressionHistory.setEnabled(true); |
| initializeExpressionHistoryDropDown(); |
| } |
| } |
| super.display(selection); |
| } |
| |
| /** |
| * Clears the Java variable detail viewer, removes all text. |
| */ |
| @Override |
| protected void clearSourceViewer(){ |
| fVariable = null; |
| if (fExpressionHistory != null) { |
| fExpressionHistory.setEnabled(false); |
| } |
| super.clearSourceViewer(); |
| } |
| |
| @Override |
| public void dispose() { |
| if (fExpressionHistory != null) { |
| fExpressionHistory.dispose(); |
| } |
| if (fLocalExpressionHistory != null) { |
| fLocalExpressionHistory.clear(); |
| } |
| if (focusListener != null && getSourceViewer() != null && getSourceViewer().getTextWidget() != null) { |
| getSourceViewer().getTextWidget().removeFocusListener(focusListener); |
| } |
| super.dispose(); |
| } |
| } |
| |