blob: 41399b1a8e2f53a27c6e81db32184c446f0c9b17 [file] [log] [blame]
/*******************************************************************************
* 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();
}
}