Merge remote-tracking branch 'origin/master'

This commit is contained in:
Konstantin Bulenkov
2014-11-16 22:29:06 +01:00
25 changed files with 815 additions and 769 deletions
@@ -279,7 +279,7 @@ public class VcsLogUiImpl implements VcsLogUi, Disposable {
}
private void commitNotFound(@NotNull String commitHash) {
if (getFilters().isEmpty()) {
if (myMainFrame.getFilterUi().getFilters().isEmpty()) {
showMessage(MessageType.WARNING, "Commit " + commitHash + " not found");
}
else {
@@ -315,7 +315,7 @@ public class VcsLogUiImpl implements VcsLogUi, Disposable {
}
public void applyFiltersAndUpdateUi() {
myFilterer.onFiltersChange(getFilters());
myFilterer.onFiltersChange(myMainFrame.getFilterUi().getFilters());
}
@NotNull
@@ -323,11 +323,6 @@ public class VcsLogUiImpl implements VcsLogUi, Disposable {
return myFilterer;
}
@NotNull
public VcsLogFilterCollection getFilters() {
return myMainFrame.getFilterUi().getFilters();
}
public VcsLogGraphTable getTable() {
return myMainFrame.getGraphTable();
}
@@ -32,19 +32,33 @@ import java.util.*;
public class BranchFilterPopupComponent extends MultipleValueFilterPopupComponent<VcsLogBranchFilter> {
@NotNull private final VcsLogUiProperties myUiProperties;
@NotNull private VcsLogDataPack myDataPack;
public BranchFilterPopupComponent(@NotNull VcsLogClassicFilterUi filterUi, @NotNull VcsLogDataPack dataPack,
@NotNull VcsLogUiProperties uiProperties) {
super(filterUi, "Branch");
myDataPack = dataPack;
myUiProperties = uiProperties;
public BranchFilterPopupComponent(@NotNull VcsLogUiProperties uiProperties,
@NotNull FilterModel<VcsLogBranchFilter> filterModel) {
super("Branch", uiProperties, filterModel);
}
void updateDataPack(@NotNull VcsLogDataPack dataPack) {
myDataPack = dataPack;
@NotNull
@Override
protected String getText(@NotNull VcsLogBranchFilter filter) {
return displayableText(filter.getBranchNames());
}
@Nullable
@Override
protected String getToolTip(@NotNull VcsLogBranchFilter filter) {
return tooltip(filter.getBranchNames());
}
@NotNull
@Override
protected VcsLogBranchFilter createFilter(@NotNull Collection<String> values) {
return new VcsLogBranchFilterImpl(values);
}
@Override
@NotNull
protected Collection<String> getValues(@Nullable VcsLogBranchFilter filter) {
return filter == null ? Collections.<String>emptySet() : filter.getBranchNames();
}
@Override
@@ -54,7 +68,7 @@ public class BranchFilterPopupComponent extends MultipleValueFilterPopupComponen
actionGroup.add(createAllAction());
actionGroup.add(createSelectMultipleValuesAction());
actionGroup.add(constructActionGroup(myDataPack, createRecentItemsActionGroup(), new Function<String, AnAction>() {
actionGroup.add(constructActionGroup(myFilterModel.getDataPack(), createRecentItemsActionGroup(), new Function<String, AnAction>() {
@Override
public AnAction fun(String name) {
return createPredefinedValueAction(Collections.singleton(name));
@@ -151,15 +165,6 @@ public class BranchFilterPopupComponent extends MultipleValueFilterPopupComponen
}
}
@Nullable
@Override
protected VcsLogBranchFilter getFilter() {
if (getSelectedValues() == null) {
return null;
}
return new VcsLogBranchFilterImpl(getSelectedValues());
}
@NotNull
@Override
protected List<List<String>> getRecentValuesFromSettings() {
@@ -176,7 +181,7 @@ public class BranchFilterPopupComponent extends MultipleValueFilterPopupComponen
@NotNull
@Override
protected List<String> getAllValues() {
return ContainerUtil.map(myDataPack.getRefs().getBranches(), new Function<VcsRef, String>() {
return ContainerUtil.map(myFilterModel.getDataPack().getRefs().getBranches(), new Function<VcsRef, String>() {
@Override
public String fun(VcsRef ref) {
return ref.getName();
@@ -22,6 +22,7 @@ import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.ui.DialogBuilder;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.vcs.versionBrowser.DateFilterComponent;
import com.intellij.util.ObjectUtils;
import com.intellij.util.text.DateFormatUtil;
import com.intellij.vcs.log.VcsLogDateFilter;
import com.intellij.vcs.log.data.VcsLogDateFilterImpl;
@@ -33,11 +34,30 @@ import java.util.Date;
class DateFilterPopupComponent extends FilterPopupComponent<VcsLogDateFilter> {
private Date myAfter;
private Date myBefore;
public DateFilterPopupComponent(FilterModel<VcsLogDateFilter> filterModel) {
super("Date", filterModel);
}
DateFilterPopupComponent(@NotNull VcsLogClassicFilterUi filterUi) {
super(filterUi, "Date");
@NotNull
@Override
protected String getText(@NotNull VcsLogDateFilter filter) {
Date after = filter.getAfter();
Date before = filter.getBefore();
if (after != null && before != null) {
return DateFormatUtil.formatDate(after) + "-" + DateFormatUtil.formatDate(before);
}
else if (after != null) {
return "Since " + DateFormatUtil.formatDate(after);
}
else {
return "Until " + DateFormatUtil.formatDate(ObjectUtils.assertNotNull(before));
}
}
@Nullable
@Override
protected String getToolTip(@NotNull VcsLogDateFilter filter) {
return null;
}
@Override
@@ -49,48 +69,24 @@ class DateFilterPopupComponent extends FilterPopupComponent<VcsLogDateFilter> {
cal.add(Calendar.DAY_OF_YEAR, -6);
Date oneWeekBefore = cal.getTime();
DumbAwareAction allAction = new DumbAwareAction(ALL) {
@Override
public void actionPerformed(AnActionEvent e) {
myAfter = null;
myBefore = null;
applyFilters();
setValue(ALL);
}
};
return new DefaultActionGroup(allAction,
return new DefaultActionGroup(createAllAction(),
new DateAction(oneDayBefore, "Last 24 hours"),
new DateAction(oneWeekBefore, "Last 7 days"),
new SelectAction());
}
@Nullable
@Override
protected VcsLogDateFilter getFilter() {
return myAfter == null && myBefore == null ? null : new VcsLogDateFilterImpl(myAfter, myBefore);
}
private void setOnlyAfter(Date after) {
myAfter = after;
myBefore = null;
}
private class DateAction extends DumbAwareAction {
private final Date mySince;
private final String myText;
@NotNull private final Date mySince;
DateAction(Date since, String text) {
DateAction(@NotNull Date since, @NotNull String text) {
super(text);
mySince = since;
myText = text;
}
@Override
public void actionPerformed(AnActionEvent e) {
setValue(myText);
setOnlyAfter(mySince);
applyFilters();
public void actionPerformed(@NotNull AnActionEvent e) {
myFilterModel.setFilter(new VcsLogDateFilterImpl(mySince, null));
}
}
@@ -101,13 +97,16 @@ class DateFilterPopupComponent extends FilterPopupComponent<VcsLogDateFilter> {
}
@Override
public void actionPerformed(AnActionEvent e) {
public void actionPerformed(@NotNull AnActionEvent e) {
final DateFilterComponent dateComponent = new DateFilterComponent(false, DateFormatUtil.getDateFormat().getDelegate());
if (myBefore != null) {
dateComponent.setBefore(myBefore.getTime());
}
if (myAfter != null) {
dateComponent.setAfter(myAfter.getTime());
VcsLogDateFilter currentFilter = myFilterModel.getFilter();
if (currentFilter != null) {
if (currentFilter.getBefore() != null) {
dateComponent.setBefore(currentFilter.getBefore().getTime());
}
if (currentFilter.getAfter() != null) {
dateComponent.setAfter(currentFilter.getAfter().getTime());
}
}
DialogBuilder db = new DialogBuilder(DateFilterPopupComponent.this);
@@ -118,20 +117,8 @@ class DateFilterPopupComponent extends FilterPopupComponent<VcsLogDateFilter> {
if (DialogWrapper.OK_EXIT_CODE == db.show()) {
long after = dateComponent.getAfter();
long before = dateComponent.getBefore();
myAfter = after > 0 ? new Date(after) : null;
myBefore = before > 0 ? new Date(before) : null;
if (myAfter != null && myBefore != null) {
setValue(DateFormatUtil.formatDate(after) + "-" + DateFormatUtil.formatDate(before));
}
else if (myAfter != null) {
setValue("After " + DateFormatUtil.formatDate(after));
}
else {
setValue("Before " + DateFormatUtil.formatDate(before));
}
applyFilters();
VcsLogDateFilter filter = new VcsLogDateFilterImpl(after > 0 ? new Date(after) : null, before > 0 ? new Date(before) : null);
myFilterModel.setFilter(filter);
}
}
}
@@ -0,0 +1,59 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.vcs.log.ui.filter;
import com.intellij.openapi.util.Computable;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.vcs.log.VcsLogDataPack;
import com.intellij.vcs.log.VcsLogFilter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
class FilterModel<Filter extends VcsLogFilter> {
@NotNull private final Computable<VcsLogDataPack> myDataPackProvider;
@NotNull private final Collection<Runnable> mySetFilterListeners = ContainerUtil.newArrayList();
@Nullable private Filter myFilter;
FilterModel(@NotNull Computable<VcsLogDataPack> provider) {
myDataPackProvider = provider;
}
void setFilter(@Nullable Filter filter) {
myFilter = filter;
for (Runnable listener : mySetFilterListeners) {
listener.run();
}
}
@Nullable
Filter getFilter() {
return myFilter;
}
@NotNull
VcsLogDataPack getDataPack() {
return myDataPackProvider.compute();
}
void addSetFilterListener(@NotNull Runnable runnable) {
mySetFilterListeners.add(runnable);
}
}
@@ -52,12 +52,19 @@ abstract class FilterPopupComponent<Filter extends VcsLogFilter> extends JPanel
@NotNull private final JLabel myFilterNameLabel;
@NotNull private final JLabel myFilterValueLabel;
@NotNull private final VcsLogClassicFilterUi myFilterUi;
@NotNull protected final FilterModel<Filter> myFilterModel;
FilterPopupComponent(@NotNull VcsLogClassicFilterUi filterUi, @NotNull String filterName) {
myFilterUi = filterUi;
FilterPopupComponent(@NotNull String filterName,
@NotNull FilterModel<Filter> filterModel) {
myFilterModel = filterModel;
myFilterNameLabel = new JLabel(filterName + ": ");
myFilterValueLabel = new JLabel(ALL);
myFilterValueLabel = new JLabel() {
@Override
public String getText() {
Filter filter = myFilterModel.getFilter();
return filter == null ? ALL : FilterPopupComponent.this.getText(filter);
}
};
setDefaultForeground();
setFocusable(true);
setBorder(UNFOCUSED_BORDER);
@@ -68,12 +75,35 @@ abstract class FilterPopupComponent<Filter extends VcsLogFilter> extends JPanel
add(Box.createHorizontalStrut(GAP_BEFORE_ARROW));
add(new JLabel(AllIcons.Ide.Statusbar_arrows));
updateLabelOnFilterChange();
showPopupMenuOnClick();
showPopupMenuFromKeyboard();
indicateHovering();
indicateFocusing();
}
private void updateLabelOnFilterChange() {
myFilterModel.addSetFilterListener(new Runnable() {
@Override
public void run() {
myFilterValueLabel.revalidate();
myFilterValueLabel.repaint();
}
});
}
@NotNull
protected abstract String getText(@NotNull Filter filter);
@Nullable
protected abstract String getToolTip(@NotNull Filter filter);
@Override
public String getToolTipText() {
Filter filter = myFilterModel.getFilter();
return filter == null ? null : getToolTip(filter);
}
private static Border createFocusedBorder() {
return BorderFactory.createCompoundBorder(new RoundedLineBorder(UIUtil.getHeaderActiveColor(), 10, BORDER_SIZE),
INNER_MARGIN_BORDER);
@@ -84,52 +114,29 @@ abstract class FilterPopupComponent<Filter extends VcsLogFilter> extends JPanel
INNER_MARGIN_BORDER);
}
/**
* Create popup actions available under this filter.
*/
protected abstract ActionGroup createActionGroup();
/**
* Return the filter currently selected via this component or null if no filters are selected via this component.
*/
@Nullable
protected abstract Filter getFilter();
protected void setValue(@NotNull String newValue) {
setValue(newValue, newValue);
}
protected void setValue(@NotNull String value, @NotNull String tooltip) {
myFilterValueLabel.setText(value);
setToolTipText(tooltip.equals(value) ? null : tooltip);
}
@NotNull
public String getValue() {
return myFilterValueLabel.getText();
}
protected void applyFilters() {
myFilterUi.applyFilters();
}
/**
* Returns the special action that indicates that no filtering is selected in this component.
*/
@NotNull
protected AnAction createAllAction() {
return new AllAction(this);
return new AllAction();
}
private void indicateFocusing() {
addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
public void focusGained(@NotNull FocusEvent e) {
setBorder(FOCUSED_BORDER);
}
@Override
public void focusLost(FocusEvent e) {
public void focusLost(@NotNull FocusEvent e) {
setBorder(UNFOCUSED_BORDER);
}
});
@@ -138,7 +145,7 @@ abstract class FilterPopupComponent<Filter extends VcsLogFilter> extends JPanel
private void showPopupMenuFromKeyboard() {
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
public void keyPressed(@NotNull KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER || e.getKeyCode() == KeyEvent.VK_DOWN) {
showPopupMenu();
}
@@ -159,12 +166,12 @@ abstract class FilterPopupComponent<Filter extends VcsLogFilter> extends JPanel
private void indicateHovering() {
addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
public void mouseEntered(@NotNull MouseEvent e) {
setOnHoverForeground();
}
@Override
public void mouseExited(MouseEvent e) {
public void mouseExited(@NotNull MouseEvent e) {
setDefaultForeground();
}
});
@@ -188,28 +195,15 @@ abstract class FilterPopupComponent<Filter extends VcsLogFilter> extends JPanel
popup.showUnderneathOf(this);
}
private static class AllAction extends SetValueAction {
AllAction(@NotNull FilterPopupComponent component) {
super(ALL, component);
}
}
private class AllAction extends DumbAwareAction {
protected static class SetValueAction extends DumbAwareAction {
private final String myValue;
private final FilterPopupComponent myFilterComponent;
SetValueAction(String value, FilterPopupComponent filterComponent) {
super(value);
myValue = value;
myFilterComponent = filterComponent;
AllAction() {
super(ALL);
}
@Override
public void actionPerformed(AnActionEvent e) {
myFilterComponent.setValue(myValue);
myFilterComponent.applyFilters();
public void actionPerformed(@NotNull AnActionEvent e) {
myFilterModel.setFilter(null);
}
}
}
@@ -84,7 +84,7 @@ class MultilinePopupBuilder {
popup.setMinimumSize(new Dimension(200, 90));
AnAction okAction = new DumbAwareAction() {
@Override
public void actionPerformed(AnActionEvent e) {
public void actionPerformed(@NotNull AnActionEvent e) {
unregisterCustomShortcutSet(popup.getContent());
popup.closeOk(e.getInputEvent());
}
@@ -131,6 +131,7 @@ class MultilinePopupBuilder {
return lastPosition;
}
@SuppressWarnings("StringToUpperCaseOrToLowerCaseWithoutLocale")
@Override
protected void addCompletionVariants(@NotNull String text, int offset, @NotNull String prefix,
@NotNull CompletionResultSet result) {
@@ -26,6 +26,7 @@ import com.intellij.openapi.ui.popup.JBPopupAdapter;
import com.intellij.openapi.ui.popup.LightweightWindowEvent;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.vcs.log.VcsLogFilter;
import com.intellij.vcs.log.data.VcsLogUiProperties;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -36,12 +37,18 @@ abstract class MultipleValueFilterPopupComponent<Filter extends VcsLogFilter> ex
private static final int MAX_FILTER_VALUE_LENGTH = 30;
@Nullable private Collection<String> mySelectedValues;
@NotNull protected final VcsLogUiProperties myUiProperties;
MultipleValueFilterPopupComponent(@NotNull VcsLogClassicFilterUi filterUi, @NotNull String filterName) {
super(filterUi, filterName);
MultipleValueFilterPopupComponent(@NotNull String filterName,
@NotNull VcsLogUiProperties uiProperties,
@NotNull FilterModel<Filter> filterModel) {
super(filterName, filterModel);
myUiProperties = uiProperties;
}
@NotNull
protected abstract Collection<String> getValues(@Nullable Filter filter);
@NotNull
protected abstract List<List<String>> getRecentValuesFromSettings();
@@ -50,10 +57,8 @@ abstract class MultipleValueFilterPopupComponent<Filter extends VcsLogFilter> ex
@NotNull
protected abstract List<String> getAllValues();
@Nullable
protected Collection<String> getSelectedValues() {
return mySelectedValues;
}
@NotNull
protected abstract Filter createFilter(@NotNull Collection<String> values);
@NotNull
protected ActionGroup createRecentItemsActionGroup() {
@@ -69,15 +74,6 @@ abstract class MultipleValueFilterPopupComponent<Filter extends VcsLogFilter> ex
return group;
}
void apply(@Nullable Collection<String> values, @NotNull String text, @NotNull String tooltip) {
mySelectedValues = values;
applyFilters();
setValue(text, tooltip);
if (values != null) {
rememberValuesInSettings(values);
}
}
@NotNull
static String displayableText(@NotNull Collection<String> values) {
if (values.size() == 1) {
@@ -91,17 +87,6 @@ abstract class MultipleValueFilterPopupComponent<Filter extends VcsLogFilter> ex
return StringUtil.join(values, ", ");
}
@Override
@NotNull
protected AnAction createAllAction() {
return new DumbAwareAction(ALL) {
@Override
public void actionPerformed(AnActionEvent e) {
apply(null, ALL, ALL);
}
};
}
@NotNull
protected AnAction createPredefinedValueAction(@NotNull Collection<String> values) {
return new PredefinedValueAction(values);
@@ -122,8 +107,9 @@ abstract class MultipleValueFilterPopupComponent<Filter extends VcsLogFilter> ex
}
@Override
public void actionPerformed(AnActionEvent e) {
apply(myValues, displayableText(myValues), tooltip(myValues));
public void actionPerformed(@NotNull AnActionEvent e) {
myFilterModel.setFilter(createFilter(myValues));
rememberValuesInSettings(myValues);
}
}
@@ -137,13 +123,14 @@ abstract class MultipleValueFilterPopupComponent<Filter extends VcsLogFilter> ex
}
@Override
public void actionPerformed(AnActionEvent e) {
public void actionPerformed(@NotNull AnActionEvent e) {
Project project = e.getProject();
if (project == null) {
return;
}
final MultilinePopupBuilder popupBuilder = new MultilinePopupBuilder(project, myVariants, getPopupText(mySelectedValues));
Filter filter = myFilterModel.getFilter();
final MultilinePopupBuilder popupBuilder = new MultilinePopupBuilder(project, myVariants, getPopupText(getValues(filter)));
JBPopup popup = popupBuilder.createPopup();
popup.addListener(new JBPopupAdapter() {
@Override
@@ -151,10 +138,11 @@ abstract class MultipleValueFilterPopupComponent<Filter extends VcsLogFilter> ex
if (event.isOk()) {
Collection<String> selectedValues = popupBuilder.getSelectedValues();
if (selectedValues.isEmpty()) {
apply(null, ALL, ALL);
myFilterModel.setFilter(null);
}
else {
apply(selectedValues, displayableText(selectedValues), tooltip(selectedValues));
myFilterModel.setFilter(createFilter(selectedValues));
rememberValuesInSettings(selectedValues);
}
}
}
@@ -17,6 +17,7 @@ package com.intellij.vcs.log.ui.filter;
import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
@@ -24,26 +25,52 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.vcs.log.VcsLogDataPack;
import com.intellij.vcs.log.VcsLogStructureFilter;
import com.intellij.vcs.log.data.VcsLogStructureFilterImpl;
import com.intellij.vcs.log.ui.VcsStructureChooser;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.*;
class StructureFilterPopupComponent extends FilterPopupComponent<VcsLogStructureFilter> {
public static final int FILTER_LABEL_LENGTH = 20;
@NotNull private final Collection<VirtualFile> myRoots;
@NotNull private final Collection<VirtualFile> myFiles;
private static final int FILTER_LABEL_LENGTH = 20;
public StructureFilterPopupComponent(@NotNull VcsLogClassicFilterUi filterUi, @NotNull Collection<VirtualFile> roots) {
super(filterUi, "Structure");
myRoots = roots;
myFiles = ContainerUtil.newArrayList();
public StructureFilterPopupComponent(@NotNull FilterModel<VcsLogStructureFilter> filterModel) {
super("Structure", filterModel);
}
@NotNull
@Override
protected String getText(@NotNull VcsLogStructureFilter filter) {
Collection<VirtualFile> files = getAllFiles(myFilterModel.getDataPack(), filter);
if (files.size() == 0) {
return ALL;
}
else if (files.size() == 1) {
VirtualFile file = files.iterator().next();
return StringUtil.shortenPathWithEllipsis(file.getPresentableUrl(), FILTER_LABEL_LENGTH);
}
else {
return files.size() + " items";
}
}
@Nullable
@Override
protected String getToolTip(@NotNull VcsLogStructureFilter filter) {
return getToolTip(getAllFiles(myFilterModel.getDataPack(), filter));
}
@NotNull
private static Collection<VirtualFile> getAllFiles(@NotNull VcsLogDataPack dataPack, @NotNull VcsLogStructureFilter filter) {
Collection<VirtualFile> result = ContainerUtil.newArrayList();
for (VirtualFile root : dataPack.getLogProviders().keySet()) {
result.addAll(filter.getFiles(root));
}
return result;
}
@Override
@@ -51,27 +78,8 @@ class StructureFilterPopupComponent extends FilterPopupComponent<VcsLogStructure
return new DefaultActionGroup(createAllAction(), new SelectAction());
}
@Nullable
@Override
protected VcsLogStructureFilter getFilter() {
return getValue() == ALL || myFiles.isEmpty() ? null : new VcsLogStructureFilterImpl(myFiles, myRoots);
}
private void setValue(@NotNull Collection<VirtualFile> files) {
if (files.size() == 0) {
setValue(ALL);
}
else if (files.size() == 1) {
VirtualFile file = files.iterator().next();
setValue(StringUtil.shortenPathWithEllipsis(file.getPresentableUrl(), FILTER_LABEL_LENGTH), getTooltip(files));
}
else {
setValue(files.size() + " items", getTooltip(files));
}
}
@NotNull
private static String getTooltip(@NotNull Collection<VirtualFile> files) {
private static String getToolTip(@NotNull Collection<VirtualFile> files) {
List<VirtualFile> filesToDisplay = new ArrayList<VirtualFile>(files);
if (files.size() > 10) {
filesToDisplay = filesToDisplay.subList(0, 10);
@@ -94,19 +102,17 @@ class StructureFilterPopupComponent extends FilterPopupComponent<VcsLogStructure
}
@Override
public void actionPerformed(AnActionEvent e) {
Project project = e.getProject();
assert project != null;
VcsStructureChooser chooser = new VcsStructureChooser(project, "Select Files or Folders to Filter", myFiles,
new ArrayList<VirtualFile>(myRoots));
public void actionPerformed(@NotNull AnActionEvent e) {
Project project = e.getRequiredData(CommonDataKeys.PROJECT);
VcsLogDataPack dataPack = myFilterModel.getDataPack();
VcsLogStructureFilter filter = myFilterModel.getFilter();
Collection<VirtualFile> files = filter == null ? Collections.<VirtualFile>emptySet() : getAllFiles(dataPack, filter);
Set<VirtualFile> roots = dataPack.getLogProviders().keySet();
VcsStructureChooser chooser = new VcsStructureChooser(project, "Select Files or Folders to Filter", files,
new ArrayList<VirtualFile>(roots));
if (chooser.showAndGet()) {
myFiles.clear();
myFiles.addAll(chooser.getSelectedFiles());
setValue(myFiles);
applyFilters();
myFilterModel.setFilter(new VcsLogStructureFilterImpl(chooser.getSelectedFiles(), roots));
}
}
}
}
@@ -38,16 +38,29 @@ class UserFilterPopupComponent extends MultipleValueFilterPopupComponent<VcsLogU
private static final String ME = "me";
private final VcsLogDataHolder myDataHolder;
private final VcsLogUiProperties myUiProperties;
@NotNull private final VcsLogDataHolder myDataHolder;
@NotNull private final VcsLogUiProperties myUiProperties;
UserFilterPopupComponent(@NotNull VcsLogClassicFilterUi filterUi, @NotNull VcsLogDataHolder dataHolder,
@NotNull VcsLogUiProperties uiProperties) {
super(filterUi, "User");
UserFilterPopupComponent(@NotNull VcsLogUiProperties uiProperties,
@NotNull VcsLogDataHolder dataHolder,
@NotNull FilterModel<VcsLogUserFilter> filterModel) {
super("User", uiProperties, filterModel);
myDataHolder = dataHolder;
myUiProperties = uiProperties;
}
@NotNull
@Override
protected String getText(@NotNull VcsLogUserFilter filter) {
return displayableText(getValues(filter));
}
@Nullable
@Override
protected String getToolTip(@NotNull VcsLogUserFilter filter) {
return tooltip(getValues(filter));
}
@Override
protected ActionGroup createActionGroup() {
DefaultActionGroup group = new DefaultActionGroup();
@@ -62,17 +75,21 @@ class UserFilterPopupComponent extends MultipleValueFilterPopupComponent<VcsLogU
@NotNull
@Override
protected List<List<String>> getRecentValuesFromSettings() {
return myUiProperties.getRecentlyFilteredUserGroups();
protected Collection<String> getValues(@Nullable VcsLogUserFilter filter) {
if (filter == null) {
return Collections.emptySet();
}
Set<String> result = ContainerUtil.newHashSet();
for (VirtualFile root : myFilterModel.getDataPack().getLogProviders().keySet()) {
result.addAll(filter.getUserNames(root));
}
return result;
}
@Nullable
@NotNull
@Override
protected VcsLogUserFilter getFilter() {
if (getSelectedValues() == null) {
return null;
}
return new VcsLogUserFilterImpl(getSelectedValues(), myDataHolder.getCurrentUser());
protected List<List<String>> getRecentValuesFromSettings() {
return myUiProperties.getRecentlyFilteredUserGroups();
}
@Override
@@ -91,6 +108,12 @@ class UserFilterPopupComponent extends MultipleValueFilterPopupComponent<VcsLogU
});
}
@NotNull
@Override
protected VcsLogUserFilter createFilter(@NotNull Collection<String> values) {
return new VcsLogUserFilterImpl(values, myDataHolder.getCurrentUser());
}
private static class VcsLogUserFilterImpl implements VcsLogUserFilter {
@NotNull private final Collection<String> myUsers;
@@ -119,6 +142,7 @@ class UserFilterPopupComponent extends MultipleValueFilterPopupComponent<VcsLogU
@Override
public boolean matches(@NotNull final VcsCommitMetadata commit) {
return ContainerUtil.exists(getUserNames(commit.getRoot()), new Condition<String>() {
@SuppressWarnings("StringToUpperCaseOrToLowerCaseWithoutLocale")
@Override
public boolean value(String user) {
String lowerUser = user.toLowerCase();
@@ -20,11 +20,13 @@ import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.actionSystem.ex.CustomComponentAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.NotNullComputable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.SearchTextField;
import com.intellij.ui.DocumentAdapter;
import com.intellij.ui.SearchTextFieldWithStoredHistory;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
@@ -35,91 +37,129 @@ import com.intellij.vcs.log.impl.VcsLogFilterCollectionImpl;
import com.intellij.vcs.log.impl.VcsLogHashFilterImpl;
import com.intellij.vcs.log.ui.VcsLogUiImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
*/
public class VcsLogClassicFilterUi implements VcsLogFilterUi {
private static final Logger LOG = Logger.getInstance(VcsLogClassicFilterUi.class);
private static final String HASH_PATTERN = "[a-fA-F0-9]{7,}";
@NotNull private final SearchTextField myTextFilter;
@NotNull private final VcsLogUiImpl myUi;
@NotNull private final DefaultActionGroup myActionGroup;
@NotNull private final BranchFilterPopupComponent myBranchFilterComponent;
@NotNull private final UserFilterPopupComponent myUserFilterComponent;
@NotNull private final DateFilterPopupComponent myDateFilterComponent;
@NotNull private final StructureFilterPopupComponent myStructureFilterComponent;
@NotNull private final VcsLogDataHolder myLogDataHolder;
@NotNull private final VcsLogUiProperties myUiProperties;
public VcsLogClassicFilterUi(@NotNull VcsLogUiImpl ui, @NotNull VcsLogDataHolder logDataHolder, @NotNull VcsLogUiProperties uiProperties,
@NotNull private VcsLogDataPack myDataPack;
@NotNull private final FilterModel<VcsLogBranchFilter> myBranchFilterModel;
@NotNull private final FilterModel<VcsLogUserFilter> myUserFilterModel;
@NotNull private final FilterModel<VcsLogDateFilter> myDateFilterModel;
@NotNull private final FilterModel<VcsLogStructureFilter> myStructureFilterModel;
@NotNull private final FilterModel<VcsLogTextFilter> myTextFilterModel;
public VcsLogClassicFilterUi(@NotNull VcsLogUiImpl ui,
@NotNull VcsLogDataHolder logDataHolder,
@NotNull VcsLogUiProperties uiProperties,
@NotNull VcsLogDataPack initialDataPack) {
myUi = ui;
myLogDataHolder = logDataHolder;
myUiProperties = uiProperties;
myDataPack = initialDataPack;
myTextFilter = new SearchTextFieldWithStoredHistory("Vcs.Log.Text.Filter.History") {
NotNullComputable<VcsLogDataPack> dataPackGetter = new NotNullComputable<VcsLogDataPack>() {
@NotNull
@Override
protected void onFieldCleared() {
applyFilters();
public VcsLogDataPack compute() {
return myDataPack;
}
};
myTextFilter.getTextEditor().addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
applyFilters();
myTextFilter.addCurrentTextToHistory();
}
});
myBranchFilterModel = new FilterModel<VcsLogBranchFilter>(dataPackGetter);
myUserFilterModel = new FilterModel<VcsLogUserFilter>(dataPackGetter);
myDateFilterModel = new FilterModel<VcsLogDateFilter>(dataPackGetter);
myStructureFilterModel = new FilterModel<VcsLogStructureFilter>(dataPackGetter);
myTextFilterModel = new FilterModel<VcsLogTextFilter>(dataPackGetter);
myBranchFilterComponent = new BranchFilterPopupComponent(this, initialDataPack, uiProperties);
myUserFilterComponent = new UserFilterPopupComponent(this, logDataHolder, uiProperties);
myDateFilterComponent = new DateFilterPopupComponent(this);
myStructureFilterComponent = new StructureFilterPopupComponent(this, logDataHolder.getRoots());
updateUiOnFilterChange();
}
myActionGroup = new DefaultActionGroup();
myActionGroup.add(new TextFilterComponent(myTextFilter));
myActionGroup.add(new FilterActionComponent(myBranchFilterComponent));
myActionGroup.add(new FilterActionComponent(myUserFilterComponent));
myActionGroup.add(new FilterActionComponent(myDateFilterComponent));
myActionGroup.add(new FilterActionComponent(myStructureFilterComponent));
private void updateUiOnFilterChange() {
FilterModel[] models = {myBranchFilterModel, myUserFilterModel, myDateFilterModel, myStructureFilterModel, myTextFilterModel};
for (FilterModel<?> model : models) {
model.addSetFilterListener(new Runnable() {
@Override
public void run() {
myUi.applyFiltersAndUpdateUi();
}
});
}
}
public void updateDataPack(@NotNull VcsLogDataPack dataPack) {
myBranchFilterComponent.updateDataPack(dataPack);
myDataPack = dataPack;
}
/**
* Returns filter components which will be added to the Log toolbar.
*/
@NotNull
public ActionGroup getActionGroup() {
return myActionGroup;
}
@NotNull
public List<JComponent> getComponents() {
return Arrays.<JComponent>asList(myTextFilter.getTextEditor(), myBranchFilterComponent, myUserFilterComponent,
myDateFilterComponent, myStructureFilterComponent);
public ActionGroup createActionGroup() {
DefaultActionGroup actionGroup = new DefaultActionGroup();
actionGroup.add(new TextFilterComponent(myTextFilterModel));
actionGroup.add(new FilterActionComponent(new Computable<JComponent>() {
@Override
public JComponent compute() {
return new BranchFilterPopupComponent(myUiProperties, myBranchFilterModel);
}
}));
actionGroup.add(new FilterActionComponent(new Computable<JComponent>() {
@Override
public JComponent compute() {
return new UserFilterPopupComponent(myUiProperties, myLogDataHolder, myUserFilterModel);
}
}));
actionGroup.add(new FilterActionComponent(new Computable<JComponent>() {
@Override
public JComponent compute() {
return new DateFilterPopupComponent(myDateFilterModel);
}
}));
actionGroup.add(new FilterActionComponent(new Computable<JComponent>() {
@Override
public JComponent compute() {
return new StructureFilterPopupComponent(myStructureFilterModel);
}
}));
return actionGroup;
}
@NotNull
@Override
public VcsLogFilterCollection getFilters() {
Pair<VcsLogTextFilter, VcsLogHashFilter> filtersFromText = getFiltersFromTextArea(myTextFilter.getText().trim());
return new VcsLogFilterCollectionImpl(myBranchFilterComponent.getFilter(), myUserFilterComponent.getFilter(),
filtersFromText.second, myDateFilterComponent.getFilter(),
filtersFromText.first, myStructureFilterComponent.getFilter());
ApplicationManager.getApplication().assertIsDispatchThread();
Pair<VcsLogTextFilter, VcsLogHashFilter> filtersFromText = getFiltersFromTextArea(myTextFilterModel.getFilter());
return new VcsLogFilterCollectionImpl(myBranchFilterModel.getFilter(),
myUserFilterModel.getFilter(),
filtersFromText.second,
myDateFilterModel.getFilter(),
filtersFromText.first,
myStructureFilterModel.getFilter());
}
@NotNull
private static Pair<VcsLogTextFilter, VcsLogHashFilter> getFiltersFromTextArea(@NotNull String text) {
if (text.isEmpty()) {
private static Pair<VcsLogTextFilter, VcsLogHashFilter> getFiltersFromTextArea(@Nullable VcsLogTextFilter filter) {
if (filter == null) {
return Pair.empty();
}
String text = filter.getText().trim();
if (StringUtil.isEmptyOrSpaces(text)) {
return Pair.empty();
}
List<String> hashes = ContainerUtil.newArrayList();
@@ -147,23 +187,21 @@ public class VcsLogClassicFilterUi implements VcsLogFilterUi {
@Override
public void setFilter(@NotNull VcsLogFilter filter) {
ApplicationManager.getApplication().assertIsDispatchThread();
if (filter instanceof VcsLogBranchFilter) {
Collection<String> values = ((VcsLogBranchFilter)filter).getBranchNames();
myBranchFilterComponent.apply(values, MultipleValueFilterPopupComponent.displayableText(values),
MultipleValueFilterPopupComponent.tooltip(values));
myBranchFilterModel.setFilter((VcsLogBranchFilter)filter);
JComponent toolbar = myUi.getMainFrame().getToolbar();
toolbar.revalidate();
toolbar.repaint();
}
}
void applyFilters() {
myUi.applyFiltersAndUpdateUi();
}
private static class TextFilterComponent extends DumbAwareAction implements CustomComponentAction {
private final SearchTextField mySearchField;
private final FilterModel<VcsLogTextFilter> myFilterModel;
TextFilterComponent(SearchTextField searchField) {
mySearchField = searchField;
public TextFilterComponent(FilterModel<VcsLogTextFilter> filterModel) {
myFilterModel = filterModel;
}
@Override
@@ -172,30 +210,59 @@ public class VcsLogClassicFilterUi implements VcsLogFilterUi {
JLabel filterCaption = new JLabel("Filter:");
filterCaption.setForeground(UIUtil.isUnderDarcula() ? UIUtil.getLabelForeground() : UIUtil.getInactiveTextColor());
panel.add(filterCaption);
panel.add(mySearchField);
panel.add(createSearchField());
return panel;
}
private Component createSearchField() {
final SearchTextFieldWithStoredHistory textFilter = new SearchTextFieldWithStoredHistory("Vcs.Log.Text.Filter.History") {
@Override
protected void onFieldCleared() {
myFilterModel.setFilter(null);
}
};
textFilter.getTextEditor().addActionListener(new ActionListener() {
@Override
public void actionPerformed(@NotNull ActionEvent e) {
myFilterModel.setFilter(new VcsLogTextFilterImpl(textFilter.getText()));
textFilter.addCurrentTextToHistory();
}
});
resetFilterOnTextClear(textFilter);
return textFilter;
}
private void resetFilterOnTextClear(@NotNull SearchTextFieldWithStoredHistory textFilter) {
textFilter.addDocumentListener(new DocumentAdapter() {
@Override
protected void textChanged(DocumentEvent e) {
if (e.getDocument().getLength() == 0) {
myFilterModel.setFilter(null);
}
}
});
}
@Override
public void actionPerformed(AnActionEvent e) {
public void actionPerformed(@NotNull AnActionEvent e) {
}
}
private static class FilterActionComponent extends DumbAwareAction implements CustomComponentAction {
private final FilterPopupComponent myComponent;
public FilterActionComponent(FilterPopupComponent component) {
myComponent = component;
@NotNull private final Computable<JComponent> myComponentCreator;
public FilterActionComponent(@NotNull Computable<JComponent> componentCreator) {
myComponentCreator = componentCreator;
}
@Override
public JComponent createCustomComponent(Presentation presentation) {
return myComponent;
return myComponentCreator.compute();
}
@Override
public void actionPerformed(AnActionEvent e) {
public void actionPerformed(@NotNull AnActionEvent e) {
}
}
}
@@ -215,7 +215,7 @@ public class MainFrame extends JPanel implements TypeSafeDataProvider {
toolbarGroup.add(ActionManager.getInstance().getAction(VcsLogUiImpl.TOOLBAR_ACTION_GROUP));
DefaultActionGroup mainGroup = new DefaultActionGroup();
mainGroup.add(myFilterUi.getActionGroup());
mainGroup.add(myFilterUi.createActionGroup());
mainGroup.addSeparator();
mainGroup.add(toolbarGroup);
ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.CHANGES_VIEW_TOOLBAR, mainGroup, true);
@@ -264,7 +264,8 @@ public class MainFrame extends JPanel implements TypeSafeDataProvider {
}
}
public Component getToolbar() {
@NotNull
public JComponent getToolbar() {
return myToolbar;
}
@@ -382,8 +383,7 @@ public class MainFrame extends JPanel implements TypeSafeDataProvider {
@NotNull
@Override
protected List<Component> getOrderedComponents() {
return ContainerUtil.concat(Arrays.<Component>asList(myGraphTable, myChangesBrowser.getPreferredFocusedComponent()),
myFilterUi.getComponents());
return Arrays.<Component>asList(myGraphTable, myChangesBrowser.getPreferredFocusedComponent());
}
}
+6 -2
View File
@@ -387,9 +387,13 @@ class BaseInterpreterInterface:
def getArray(self, attr, roffset, coffset, rows, cols, format):
xml = "<xml>"
array = pydevd_vars.evalInContext(attr, self.getNamespace(), self.getNamespace())
name = ".".join(attr.split("\t"))
array = pydevd_vars.evalInContext(name, self.getNamespace(), self.getNamespace())
xml += pydevd_vars.array_to_xml(array, roffset, coffset, rows, cols, format)
if rows == -1 and cols == -1:
xml += pydevd_vars.array_to_meta_xml(array, name, format)
else:
xml += pydevd_vars.array_to_xml(array, roffset, coffset, rows, cols, format)
xml += "</xml>"
+13 -7
View File
@@ -974,27 +974,33 @@ class InternalGetArray(InternalThreadCommand):
self.thread_id = thread_id
self.frame_id = frame_id
self.scope = scope
self.name = attrs[-1]
self.attrs = attrs;
self.name = ".".join(attrs.split("\t"))
self.attrs = attrs
self.roffset = int(roffset)
self.coffset = int(coffset)
self.rows = int(rows)
self.cols = int(cols)
self.format = '\'' + format + '\''
self.format = format
if hasattr(self.format, 'decode'):
self.format = self.format.decode('utf-8')
def doIt(self, dbg):
try:
var = getVariable(self.thread_id, self.frame_id, 'EXPRESSION', self.attrs)
frame = pydevd_vars.findFrame(self.thread_id, self.frame_id)
var = pydevd_vars.evalInContext(self.name, frame.f_globals, frame.f_locals)
xml = "<xml>"
xml += pydevd_vars.array_to_xml(var, self.roffset, self.coffset, self.rows, self.cols, self.format)
# need metadata
if self.rows == -1 and self.cols == -1:
xml += pydevd_vars.array_to_meta_xml(var, self.name, self.format)
else:
xml += pydevd_vars.array_to_xml(var, self.roffset, self.coffset, self.rows, self.cols, self.format)
xml += "</xml>"
cmd = dbg.cmdFactory.makeGetArrayMessage(self.sequence, xml)
dbg.writer.addCommand(cmd)
except:
cmd = dbg.cmdFactory.makeErrorMessage(self.sequence, "Error resolving array " + GetExceptionTracebackStr())
cmd = dbg.cmdFactory.makeErrorMessage(self.sequence, "Error resolving array: " + GetExceptionTracebackStr())
dbg.writer.addCommand(cmd)
#=======================================================================================================================
+80 -19
View File
@@ -388,44 +388,45 @@ def changeAttrExpression(thread_id, frame_id, attr, expression, dbg):
except Exception:
traceback.print_exc()
MAXIMUM_ARRAY_SIZE = 300
MAXIMUM_ARRAY_SIZE = 100
MAX_SLICE_SIZE = 1000
def array_to_xml(array, roffset, coffset, rows, cols, format):
xml = ""
rows = min(rows, MAXIMUM_ARRAY_SIZE)
cols = min(cols, MAXIMUM_ARRAY_SIZE)
if rows == 1 and cols == 1:
rows = 1
cols = 1
elif rows == 1 or cols == 1:
is_row = True if (rows == 1) else False
if is_row:
array = array[roffset:]
else:
array = array[coffset:]
if len(array) == 1:
array = array[0]
if is_row:
cols = min(cols, len(array))
else:
rows = min(rows, len(array))
else:
#there is no obvious rule for slicing (at least 5 choices)
if len(array) == 1 and (rows > 1 or cols > 1):
array = array[0]
if array.size > len(array):
array = array[roffset:, coffset:]
rows = min(rows, len(array))
cols = min(cols, len(array[0]))
xml += "<array rows=\"%s\" cols=\"%s\"/>" % (rows, cols)
if len(array) == 1:
array = array[0]
elif array.size == len(array):
if roffset == 0 and rows == 1:
array = array[coffset:]
cols = min(cols, len(array))
elif coffset == 0 and cols == 1:
array = array[roffset:]
rows = min(rows, len(array))
xml += "<array meta=\"%s\" rows=\"%s\" cols=\"%s\"/>" % (False, rows, cols)
for row in range(rows):
xml += "<row index=\"%s\"/>" % to_string(row)
for col in range(cols):
value = array
if rows == 1 or cols == 1:
if rows == 1 and cols == 1:
value = array
value = array[0]
else:
dim = col if (rows == 1) else row
value = array[dim]
if "ndarray" in str(type(value)):
value = value[0]
else:
value = array[row][col]
value = format % value
@@ -433,6 +434,66 @@ def array_to_xml(array, roffset, coffset, rows, cols, format):
return xml
def array_to_meta_xml(array, name, format):
type = array.dtype.kind
slice = name
l = len(array.shape)
# initial load, compute slice
if format == '%':
if l > 2:
slice += '[0]' * (l - 2)
for r in range(l - 2):
array = array[0]
if type == 'f':
format = '.5f'
elif type == 'i' or type == 'u':
format = 'd'
else:
format = 's'
else:
format = format.replace('%', '')
l = len(array.shape)
reslice = ""
if l > 2:
raise Exception("%s has more than 2 dimensions." % slice)
elif l == 1:
# special case with 1D arrays arr[i, :] - row, but arr[:, i] - column with equal shape and ndim
# http://stackoverflow.com/questions/16837946/numpy-a-2-rows-1-column-file-loadtxt-returns-1row-2-columns
# explanation: http://stackoverflow.com/questions/15165170/how-do-i-maintain-row-column-orientation-of-vectors-in-numpy?rq=1
# we use kind of a hack - get information about memory from C_CONTIGUOUS
is_row = array.flags['C_CONTIGUOUS']
if is_row:
rows = 1
cols = min(len(array), MAX_SLICE_SIZE)
if cols < len(array):
reslice = '[0:%s]' % (cols)
array = array[0:cols]
else:
cols = 1
rows = min(len(array), MAX_SLICE_SIZE)
if rows < len(array):
reslice = '[0:%s]' % (rows)
array = array[0:rows]
elif l == 2:
rows = min(array.shape[-2], MAX_SLICE_SIZE)
cols = min(array.shape[-1], MAX_SLICE_SIZE)
if cols < array.shape[-1] or rows < array.shape[-2]:
reslice = '[0:%s, 0:%s]' % (rows, cols)
array = array[0:rows, 0:cols]
#avoid slice duplication
if not slice.endswith(reslice):
slice += reslice
bounds = (0, 0)
if type in "biufc":
bounds = (array.min(), array.max())
xml = '<array meta=\"%s\" slice=\"%s\" rows=\"%s\" cols=\"%s\" format=\"%s\" type=\"%s\" max=\"%s\" min=\"%s\"/>' % \
(True, slice, rows, cols, format, type, bounds[1], bounds[0])
return xml
@@ -0,0 +1,103 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.debugger;
import com.jetbrains.python.debugger.PyDebugValue;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author amarch
*/
public class ArrayChunk {
private final PyDebugValue myValue;
private final String mySlicePresentation;
private final int myRows;
private final int myColumns;
private final String myMax;
private final String myMin;
private final String myFormat;
private final String myType;
private final Object[][] myData;
public ArrayChunk(@NotNull PyDebugValue value,
String slicePresentation,
int rows,
int columns,
String max,
String min,
String format,
String type,
@Nullable Object[][] data) {
myValue = value;
mySlicePresentation = slicePresentation;
myRows = rows;
myColumns = columns;
myMax = max;
myMin = min;
myFormat = format;
myType = type;
myData = data;
}
public PyDebugValue getValue() {
return myValue;
}
public String getSlicePresentation() {
return mySlicePresentation;
}
public int getRows() {
return myRows;
}
public int getColumns() {
return myColumns;
}
public String getMax() {
return myMax;
}
public String getMin() {
return myMin;
}
public String getFormat() {
return myFormat;
}
public String getType() {
return myType;
}
public Object[][] getData() {
return myData;
}
public boolean metaOnly() {
return myData == null;
}
public boolean containsMeta() {
return !mySlicePresentation.isEmpty() &&
myRows > 0 &&
myColumns > 0 &&
!myFormat.isEmpty() &&
!myType.isEmpty();
}
}
@@ -21,5 +21,5 @@ public interface PyFrameAccessor {
@Nullable
PyReferrersLoader getReferrersLoader();
Object[][] getArrayItems(PyDebugValue var, int rowOffset, int colOffset, int rows, int cols, String format) throws PyDebuggerException;
ArrayChunk getArrayItems(PyDebugValue var, int rowOffset, int colOffset, int rows, int cols, String format) throws PyDebuggerException;
}
@@ -1,5 +1,6 @@
package com.jetbrains.python.debugger.pydev;
import com.jetbrains.python.debugger.ArrayChunk;
import com.jetbrains.python.debugger.PyDebugValue;
import com.jetbrains.python.debugger.PyDebuggerException;
@@ -15,7 +16,7 @@ public class GetArrayCommand extends GetFrameCommand {
private final int myRows;
private final int myColumns;
private final String myFormat;
private Object[][] myArrayItems;
private ArrayChunk myChunk;
public GetArrayCommand(final RemoteDebugger debugger, final String threadId, final String frameId, PyDebugValue var, int rowOffset, int colOffset, int rows, int cols, String format) {
super(debugger, GET_ARRAY, threadId, frameId);
@@ -54,10 +55,10 @@ public class GetArrayCommand extends GetFrameCommand {
if (response.getCommand() >= 900 && response.getCommand() < 1000) {
throw new PyDebuggerException(response.getPayload());
}
myArrayItems = ProtocolParser.parseArrayValues(response.getPayload(), myDebugProcess);
myChunk = ProtocolParser.parseArrayValues(response.getPayload(), myDebugProcess);
}
public Object[][] getArray(){
return myArrayItems;
public ArrayChunk getArray(){
return myChunk;
}
}
@@ -181,7 +181,7 @@ public class MultiProcessDebugger implements ProcessDebugger {
return debugger(threadId).loadVariable(threadId, frameId, var);
}
public Object[][] loadArrayItems(String threadId,
public ArrayChunk loadArrayItems(String threadId,
String frameId,
PyDebugValue var,
int rowOffset,
@@ -2,10 +2,7 @@ package com.jetbrains.python.debugger.pydev;
import com.intellij.xdebugger.frame.XValueChildrenList;
import com.jetbrains.python.console.pydev.PydevCompletionVariant;
import com.jetbrains.python.debugger.PyDebugValue;
import com.jetbrains.python.debugger.PyDebuggerException;
import com.jetbrains.python.debugger.PyReferringObjectsValue;
import com.jetbrains.python.debugger.PyThreadInfo;
import com.jetbrains.python.debugger.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -36,7 +33,7 @@ public interface ProcessDebugger {
// todo: don't generate temp variables for qualified expressions - just split 'em
XValueChildrenList loadVariable(String threadId, String frameId, PyDebugValue var) throws PyDebuggerException;
Object[][] loadArrayItems(String threadId, String frameId, PyDebugValue var, int rowOffset, int colOffset, int rows, int cols, String format) throws PyDebuggerException;
ArrayChunk loadArrayItems(String threadId, String frameId, PyDebugValue var, int rowOffset, int colOffset, int rows, int cols, String format) throws PyDebuggerException;
void loadReferrers(String threadId, String frameId, PyReferringObjectsValue var, PyDebugCallback<XValueChildrenList> callback);
@@ -179,7 +179,7 @@ public class ProtocolParser {
return new PyDebugValue(name, type, value, "True".equals(isContainer), "True".equals(isErrorOnEval), frameAccessor);
}
public static Object[][] parseArrayValues(final String text, final PyFrameAccessor frameAccessor) throws PyDebuggerException {
public static ArrayChunk parseArrayValues(final String text, final PyFrameAccessor frameAccessor) throws PyDebuggerException {
final XppReader reader = openReader(text, false);
int cols = 0;
int rows = 0;
@@ -189,6 +189,17 @@ public class ProtocolParser {
if (!"array".equals(reader.getNodeName())) {
throw new PyDebuggerException("Expected <array> at first node, found " + reader.getNodeName());
}
boolean meta = readString(reader, "meta", null).equals("True");
if (meta) {
String slice = readString(reader, "slice", null);
rows = readInt(reader, "rows", null);
cols = readInt(reader, "cols", null);
String format = "%" + readString(reader, "format", null);
String type = readString(reader, "type", null);
String max = readString(reader, "max", null);
String min = readString(reader, "min", null);
return new ArrayChunk(new PyDebugValue(slice, null, null, false, false, frameAccessor), slice, rows, cols, max, min, format, type, null);
}
rows = readInt(reader, "rows", null);
cols = readInt(reader, "cols", null);
reader.moveUp();
@@ -197,7 +208,7 @@ public class ProtocolParser {
return parseArrayValues(reader, frameAccessor, cols, rows);
}
public static Object[][] parseArrayValues(final XppReader reader, final PyFrameAccessor frameAccessor, final int cols, final int rows) throws PyDebuggerException {
public static ArrayChunk parseArrayValues(final XppReader reader, final PyFrameAccessor frameAccessor, final int cols, final int rows) throws PyDebuggerException {
if (rows <= 0 || cols <= 0) {
throw new PyDebuggerException("Array xml: bad rows or columns number: (" + rows + ", " + cols + ")");
}
@@ -228,7 +239,7 @@ public class ProtocolParser {
reader.moveUp();
}
return values;
return new ArrayChunk(new PyDebugValue("", null, null, false, false, frameAccessor), "", rows, cols, null, null, null, null, values);
}
private static XppReader openReader(final String text, final boolean checkForContent) throws PyDebuggerException {
@@ -155,7 +155,7 @@ public class RemoteDebugger implements ProcessDebugger {
}
@Override
public Object[][] loadArrayItems(String threadId, String frameId, PyDebugValue var, int rowOffset, int colOffset, int rows, int cols, String format) throws PyDebuggerException {
public ArrayChunk loadArrayItems(String threadId, String frameId, PyDebugValue var, int rowOffset, int colOffset, int rows, int cols, String format) throws PyDebuggerException {
final GetArrayCommand command = new GetArrayCommand(this, threadId, frameId, var, rowOffset, colOffset, rows, cols, format);
command.execute();
return command.getArray();
@@ -550,11 +550,18 @@ public class PydevConsoleCommunication extends AbstractConsoleCommunication impl
}
@Override
public Object[][] getArrayItems(PyDebugValue var, int rowOffset, int colOffset, int rows, int cols, String format)
public ArrayChunk getArrayItems(PyDebugValue var, int rowOffset, int colOffset, int rows, int cols, String format)
throws PyDebuggerException {
if (myClient != null) {
try {
Object ret = myClient.execute(GET_ARRAY, new Object[]{var.getName(), rowOffset, colOffset, rows, cols, format});
String fullName = var.getName();
PyDebugValue child = var;
while (child.getParent() != null) {
child = child.getParent();
fullName = child.getName() + "\t" + fullName;
}
Object ret = myClient.execute(GET_ARRAY, new Object[]{fullName, rowOffset, colOffset, rows, cols, format});
if (ret instanceof String) {
return ProtocolParser.parseArrayValues((String)ret, this);
}
@@ -572,7 +572,7 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
}
@Override
public Object[][] getArrayItems(PyDebugValue var, int rowOffset, int colOffset, int rows, int cols, String format)
public ArrayChunk getArrayItems(PyDebugValue var, int rowOffset, int colOffset, int rows, int cols, String format)
throws PyDebuggerException {
final PyStackFrame frame = currentFrame();
return myDebugger.loadArrayItems(frame.getThreadId(), frame.getFrameId(), var, rowOffset, colOffset, rows, cols, format);
@@ -22,6 +22,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListenableFutureTask;
import com.intellij.openapi.util.Pair;
import com.intellij.util.ui.UIUtil;
import com.jetbrains.python.debugger.ArrayChunk;
import com.jetbrains.python.debugger.PyDebugValue;
import javax.swing.table.AbstractTableModel;
@@ -35,26 +36,26 @@ public class AsyncArrayTableModel extends AbstractTableModel {
private static final int CHUNK_ROW_SIZE = 30;
public static final String EMPTY_CELL_VALUE = "";
private final int myRows;
private final int myColumns;
private int myRows;
private int myColumns;
private final NumpyArrayTable myProvider;
private final ExecutorService myExecutorService = Executors.newSingleThreadExecutor();
private LoadingCache<Pair<Integer, Integer>, ListenableFuture<Object[][]>> myChunkCache = CacheBuilder.newBuilder().build(
new CacheLoader<Pair<Integer, Integer>, ListenableFuture<Object[][]>>() {
private LoadingCache<Pair<Integer, Integer>, ListenableFuture<ArrayChunk>> myChunkCache = CacheBuilder.newBuilder().build(
new CacheLoader<Pair<Integer, Integer>, ListenableFuture<ArrayChunk>>() {
@Override
public ListenableFuture<Object[][]> load(final Pair<Integer, Integer> key) throws Exception {
public ListenableFuture<ArrayChunk> load(final Pair<Integer, Integer> key) throws Exception {
final PyDebugValue value = myProvider.getDebugValue();
final PyDebugValue slicedValue =
new PyDebugValue(myProvider.getSliceText(), value.getType(), value.getValue(), value.isContainer(), value.isErrorOnEval(),
value.getFrameAccessor());
ListenableFutureTask<Object[][]> task = ListenableFutureTask.create(new Callable<Object[][]>() {
ListenableFutureTask<ArrayChunk> task = ListenableFutureTask.create(new Callable<ArrayChunk>() {
@Override
public Object[][] call() throws Exception {
public ArrayChunk call() throws Exception {
return value.getFrameAccessor()
.getArrayItems(slicedValue, key.first, key.second, Math.min(CHUNK_ROW_SIZE, getRowCount() - key.first),
Math.min(CHUNK_COL_SIZE, getColumnCount() - key.second),
@@ -91,15 +92,16 @@ public class AsyncArrayTableModel extends AbstractTableModel {
Pair<Integer, Integer> key = itemToChunkKey(row, col);
try {
ListenableFuture<Object[][]> chunk = myChunkCache.get(key);
ListenableFuture<ArrayChunk> chunk = myChunkCache.get(key);
if (chunk.isDone()) {
Object[][] data = chunk.get().getData();
int r = row % CHUNK_ROW_SIZE;
int c = col % CHUNK_COL_SIZE;
if (r < chunk.get().length) {
if (c < chunk.get()[r].length) {
return myProvider.correctStringValue(chunk.get()[r][c]);
if (r < data.length) {
if (c < data[r].length) {
return myProvider.correctStringValue(data[r][c]);
}
}
}
@@ -149,17 +151,21 @@ public class AsyncArrayTableModel extends AbstractTableModel {
}
public void changeValue(int row, int col, Object value) {
Future<Object[][]> chunk = myChunkCache.getIfPresent(itemToChunkKey(row, col));
Future<ArrayChunk> chunk = myChunkCache.getIfPresent(itemToChunkKey(row, col));
if (chunk != null && chunk.isDone()) {
try {
chunk.get()[row - getPageRowStart(row)][col - getPageColStart(col)] = value;
chunk.get().getData()[row - getPageRowStart(row)][col - getPageColStart(col)] = value;
}
catch (Exception e) {
throw new IllegalStateException(e);
}
}
else {
throw new IllegalArgumentException("Forced to change empty cell in " + row + " row and " + col + "column.");
throw new IllegalArgumentException("Forced to change empty cell in " + row + " row and " + col + " column.");
}
}
public void addToCache(ArrayChunk chunk) {
//TODO: add preloaded values
}
}
@@ -15,11 +15,12 @@
*/
package com.jetbrains.python.debugger.array;
import com.google.common.util.concurrent.ListenableFutureTask;
import com.intellij.codeInsight.hint.HintManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
@@ -27,10 +28,7 @@ import com.intellij.util.Consumer;
import com.intellij.util.ui.UIUtil;
import com.intellij.xdebugger.evaluation.XDebuggerEvaluator;
import com.intellij.xdebugger.frame.XValue;
import com.jetbrains.python.debugger.PyDebugValue;
import com.jetbrains.python.debugger.PyDebuggerEvaluator;
import com.jetbrains.python.debugger.PyDebuggerException;
import com.jetbrains.python.debugger.PyFrameAccessor;
import com.jetbrains.python.debugger.*;
import org.jetbrains.annotations.NotNull;
import javax.management.InvalidAttributeValueException;
@@ -40,6 +38,10 @@ import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -54,7 +56,6 @@ public class NumpyArrayTable {
private Project myProject;
private PyDebuggerEvaluator myEvaluator;
private String myDtypeKind;
private int[] myShape;
private ArrayTableCellRenderer myTableCellRenderer;
private AsyncArrayTableModel myPagingModel;
@@ -81,7 +82,7 @@ public class NumpyArrayTable {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
doReslice(getSliceText(), null);
doReslice();
}
}
}, new KeyAdapter() {
@@ -101,57 +102,39 @@ public class NumpyArrayTable {
return myComponent;
}
private AsyncArrayTableModel createTableModel(@NotNull int[] shape) {
final int columns = Math.min(getMaxColumn(shape), COLUMNS_IN_DEFAULT_VIEW);
int rows = Math.min(getMaxRow(shape), ROWS_IN_DEFAULT_VIEW);
if (columns == 0 || rows == 0) {
showError("Slice with zero axis shape.");
}
return new AsyncArrayTableModel(rows, columns, this);
}
private void initComponent() {
//add table renderer
myTableCellRenderer = new ArrayTableCellRenderer(Double.MIN_VALUE, Double.MIN_VALUE, myDtypeKind);
myComponent.getColoredCheckbox().setEnabled(false);
myComponent.getColoredCheckbox().addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
if (e.getSource() == myComponent.getColoredCheckbox()) {
//add color checkbox listener
if (!isNumeric()) {
disableColor();
}
else {
myComponent.getColoredCheckbox().addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
if (e.getSource() == myComponent.getColoredCheckbox()) {
if (myTable.getRowCount() > 0 &&
myTable.getColumnCount() > 0 &&
myTable.getCellRenderer(0, 0) instanceof ArrayTableCellRenderer) {
ArrayTableCellRenderer renderer = (ArrayTableCellRenderer)myTable.getCellRenderer(0, 0);
if (myComponent.getColoredCheckbox().isSelected()) {
renderer.setColored(true);
}
else {
renderer.setColored(false);
}
if (myTable.getRowCount() > 0 &&
myTable.getColumnCount() > 0 &&
myTable.getCellRenderer(0, 0) instanceof ArrayTableCellRenderer) {
ArrayTableCellRenderer renderer = (ArrayTableCellRenderer)myTable.getCellRenderer(0, 0);
if (myComponent.getColoredCheckbox().isSelected()) {
renderer.setColored(true);
}
else {
renderer.setColored(false);
}
myComponent.getScrollPane().repaint();
}
myComponent.getScrollPane().repaint();
}
});
}
}
});
//make value name read-only
myComponent.getSliceTextField().addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
myComponent.getSliceTextField().getDocument().createGuardedBlock(0, getNodeName().length());
myComponent.getSliceTextField().getDocument().createGuardedBlock(0, getNodeFullName().length());
}
@Override
public void focusLost(FocusEvent e) {
RangeMarker block = myComponent.getSliceTextField().getDocument().getRangeGuard(0, getNodeName().length());
RangeMarker block = myComponent.getSliceTextField().getDocument().getRangeGuard(0, getNodeFullName().length());
if (block != null) {
myComponent.getSliceTextField().getDocument().removeGuardedBlock(block);
}
@@ -184,213 +167,130 @@ public class NumpyArrayTable {
}
public void init() {
Runnable returnToFillTable = new Runnable() {
init(myValue.getName(), false);
}
public void init(final String slice, final boolean inPlace) {
initComponent();
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
final ExecutorService myExecutorService = Executors.newSingleThreadExecutor();
@Override
public void run() {
init();
}
};
final PyDebugValue value = getDebugValue();
PyDebugValue parent = value.getParent();
String sl = slice;
if (myDtypeKind == null) {
fillType(returnToFillTable);
return;
}
if (slice.contains(".")) {
sl = slice.substring(slice.lastIndexOf(".") + 1);
}
if (myShape == null) {
fillShape(returnToFillTable);
return;
}
final PyDebugValue slicedValue =
new PyDebugValue(sl, value.getType(), value.getValue(), value.isContainer(), value.isErrorOnEval(),
parent, value.getFrameAccessor());
if (myTableCellRenderer == null) {
initComponent();
}
final String format = getFormat().isEmpty() ? "%" : getFormat();
if (isNumeric() && myTableCellRenderer.getMax() == Double.MIN_VALUE && myTableCellRenderer.getMin() == Double.MIN_VALUE) {
fillColorRange(returnToFillTable);
return;
}
final ListenableFutureTask<ArrayChunk> task = ListenableFutureTask.create(new Callable<ArrayChunk>() {
@Override
public ArrayChunk call() throws Exception {
return value.getFrameAccessor()
.getArrayItems(slicedValue, 0, 0, -1, -1, format);
}
});
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
myComponent.getSliceTextField().setText(getDefaultPresentation());
myComponent.getFormatTextField().setText(getDefaultFormat());
myDialog.setTitle(getTitlePresentation(getDefaultPresentation()));
task.addListener(new Runnable() {
@Override
public void run() {
if (task.isDone()) {
try {
initUi(task.get(), inPlace);
}
catch (Exception e) {
showError(e.getMessage());
}
}
}
}, myExecutorService);
myExecutorService.execute(task);
}
});
initTableModel(false);
}
private void initUi(@NotNull final ArrayChunk chunk, final boolean inPlace) {
if (chunk.containsMeta()) {
myPagingModel = new AsyncArrayTableModel(Math.min(chunk.getRows(), ROWS_IN_DEFAULT_VIEW),
Math.min(chunk.getColumns(), COLUMNS_IN_DEFAULT_VIEW), this);
myPagingModel.addToCache(chunk);
myDtypeKind = chunk.getType();
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
myTable.setModel(myPagingModel);
myComponent.getSliceTextField().setText(chunk.getSlicePresentation());
myComponent.getFormatTextField().setText(chunk.getFormat());
myDialog.setTitle(getTitlePresentation(chunk.getSlicePresentation()));
myTableCellRenderer = new ArrayTableCellRenderer(Double.MIN_VALUE, Double.MIN_VALUE, chunk.getType());
fillColorRange(chunk.getMin(), chunk.getMax());
if (!isNumeric()) {
disableColor();
}
else {
myComponent.getColoredCheckbox().setEnabled(true);
}
if (!inPlace) {
myComponent.getScrollPane().getViewport().setViewPosition(new Point(0, 0));
JBTableWithRowHeaders.RowHeaderTable rowTable = ((JBTableWithRowHeaders)myTable).getRowHeaderTable();
rowTable.setRowShift(0);
}
((AsyncArrayTableModel)myTable.getModel()).fireTableDataChanged();
((AsyncArrayTableModel)myTable.getModel()).fireTableCellUpdated(0, 0);
if (myTable.getColumnCount() > 0) {
myTable.setDefaultRenderer(myTable.getColumnClass(0), myTableCellRenderer);
}
}
});
}
else {
showError("Bad metadata for array " + chunk.getValue());
}
}
private static String getTitlePresentation(String slice) {
return "Array View: " + slice;
}
private void fillColorRange(@NotNull final Runnable returnToMain) {
Consumer<PyDebugValue> callback = new Consumer<PyDebugValue>() {
@Override
public void consume(@NotNull PyDebugValue result) {
String rawValue = result.getValue();
double min;
double max;
String minValue = rawValue.substring(1, rawValue.indexOf(","));
String maxValue = rawValue.substring(rawValue.indexOf(", ") + 2, rawValue.length() - 1);
if ("c".equals(myDtypeKind)) {
min = 0;
max = 1;
myTableCellRenderer.setComplexMin(minValue);
myTableCellRenderer.setComplexMax(maxValue);
}
else if ("b".equals(myDtypeKind)) {
min = minValue.equals("True") ? 1 : 0;
max = maxValue.equals("True") ? 1 : 0;
}
else {
min = Double.parseDouble(minValue);
max = Double.parseDouble(maxValue);
}
myTableCellRenderer.setMin(min);
myTableCellRenderer.setMax(max);
returnToMain.run();
}
};
if (getMaxRow(myShape) * getMaxColumn(myShape) > HUGE_ARRAY_SIZE) {
disableColor();
returnToMain.run();
private void fillColorRange(String minValue, String maxValue) {
double min;
double max;
if ("c".equals(myDtypeKind)) {
min = 0;
max = 1;
myTableCellRenderer.setComplexMin(minValue);
myTableCellRenderer.setComplexMax(maxValue);
}
String evalTypeCommand = "[" + getNodeName() + ".min(), " + getNodeName() + ".max()]";
try {
PyDebugValue value = getEvaluator().evaluate(evalTypeCommand, false, false);
callback.consume(value);
}
catch (PyDebuggerException e) {
showError(e.getMessage());
}
}
public String getDefaultPresentation() {
List<Pair<Integer, Integer>> defaultSlice = getDefaultSlice();
String mySlicePresentation = getNodeName();
for (int index = 0; index < defaultSlice.size() - 2; index++) {
mySlicePresentation += "[" + defaultSlice.get(index).getFirst() + "]";
}
// fill current slice
final int columns = Math.min(getMaxColumn(myShape), COLUMNS_IN_DEFAULT_VIEW);
int rows = Math.min(getMaxRow(myShape), ROWS_IN_DEFAULT_VIEW);
if (rows == 1 && columns == 1) {
return mySlicePresentation;
}
if (rows == 1) {
mySlicePresentation += "[0:" + columns + "]";
}
else if (columns == 1) {
mySlicePresentation += "[0:" + rows + "]";
else if ("b".equals(myDtypeKind)) {
min = minValue.equals("True") ? 1 : 0;
max = maxValue.equals("True") ? 1 : 0;
}
else {
mySlicePresentation += "[0:" + rows + ", 0:" + columns + "]";
min = Double.parseDouble(minValue);
max = Double.parseDouble(maxValue);
}
return mySlicePresentation;
}
private List<Pair<Integer, Integer>> getDefaultSlice() {
return getSlice(COLUMNS_IN_DEFAULT_SLICE, ROWS_IN_DEFAULT_SLICE);
}
private List<Pair<Integer, Integer>> getSlice(int columns, int rows) {
List<Pair<Integer, Integer>> slices = new ArrayList<Pair<Integer, Integer>>();
for (int i = 0; i < myShape.length; i++) {
Pair<Integer, Integer> slice = new Pair<Integer, Integer>(0, 0);
if (i == myShape.length - 1) {
slice = new Pair<Integer, Integer>(0, Math.min(myShape[i], columns));
}
else if (i == myShape.length - 2) {
slice = new Pair<Integer, Integer>(0, Math.min(myShape[i], rows));
}
slices.add(slice);
}
return slices;
myTableCellRenderer.setMin(min);
myTableCellRenderer.setMax(max);
}
public String getSliceText() {
if (myComponent.getSliceTextField().getText().isEmpty()) {
return getDefaultPresentation();
}
return myComponent.getSliceTextField().getText();
}
private void fillType(@NotNull final Runnable returnToMain) {
String evalTypeCommand = getNodeName() + ".dtype.kind";
try {
PyDebugValue value = getEvaluator().evaluate(evalTypeCommand, false, false);
setDtypeKind(value.getValue());
returnToMain.run();
}
catch (PyDebuggerException e) {
showError(e.getMessage());
}
}
private void fillShape(@NotNull final Runnable returnToMain) {
String evalShapeCommand = getEvalShapeCommand(getNodeName());
try {
PyDebugValue value = getEvaluator().evaluate(evalShapeCommand, false, false);
setShape(parseShape(value.getValue()));
returnToMain.run();
}
catch (Exception e) {
showError(e.getMessage());
}
}
private int[] parseShape(String value) throws InvalidAttributeValueException {
int index = value.indexOf('#');
if (index == -1) {
LOG.error("Wrong shape format: " + value);
return new int[]{0, 0};
}
String shape = value.substring(0, index);
if (shape.equals("()")) {
return new int[]{1, 1};
}
String[] dimensions = shape.substring(1, shape.length() - 1).trim().split(",");
if (dimensions.length > 1) {
int[] result = new int[dimensions.length];
for (int i = 0; i < dimensions.length; i++) {
result[i] = Integer.parseInt(dimensions[i].trim());
}
return result;
}
else if (dimensions.length == 1) {
//special case with 1D arrays arr[i, :] - row,
//but arr[:, i] - column with equal shape and ndim
//http://stackoverflow.com/questions/16837946/numpy-a-2-rows-1-column-file-loadtxt-returns-1row-2-columns
//explanation: http://stackoverflow.com/questions/15165170/how-do-i-maintain-row-column-orientation-of-vectors-in-numpy?rq=1
//we use kind of a hack - use information about memory from C_CONTIGUOUS
boolean isRow = value.substring(value.indexOf("#") + 1).equals("True");
int[] result = new int[2];
if (isRow) {
result[0] = 1;
result[1] = Integer.parseInt(dimensions[0].trim());
}
else {
result[1] = 1;
result[0] = Integer.parseInt(dimensions[0].trim());
}
return result;
}
else {
throw new InvalidAttributeValueException("Invalid shape string for " + getNodeName() + ".");
}
}
public boolean isNumeric() {
if (myDtypeKind != null) {
return "biufc".contains(myDtypeKind.substring(0, 1));
@@ -399,7 +299,7 @@ public class NumpyArrayTable {
}
private void initTableModel(final boolean inPlace) {
myPagingModel = createTableModel(myShape);
myPagingModel = new AsyncArrayTableModel(myPagingModel.getRowCount(), myPagingModel.getColumnCount(), this);
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
@@ -419,102 +319,6 @@ public class NumpyArrayTable {
});
}
private TableCellEditor getArrayTableCellEditor() {
return new ArrayTableCellEditor(myProject) {
private String getCellSlice() {
String expression = getSliceText();
if (myTable.getRowCount() == 1) {
expression += "[" + myTable.getSelectedColumn() + "]";
}
else if (myTable.getColumnCount() == 1) {
expression += "[" + myTable.getSelectedRow() + "]";
}
else {
expression += "[" + myTable.getSelectedRow() + "][" + myTable.getSelectedColumn() + "]";
}
if (myTable.getRowCount() == 1 && myTable.getColumnCount() == 1) {
return getSliceText();
}
return expression;
}
private String changeValExpression() {
if (getEditor().getEditor() == null) {
throw new IllegalStateException("Null editor in table cell.");
}
return getCellSlice() + " = " + getEditor().getEditor().getDocument().getText();
}
@Override
public void cancelEditing() {
super.cancelEditing();
clearErrorMessage();
}
@Override
public void doOKAction(final int row, final int col) {
if (getEditor().getEditor() == null) {
return;
}
myEvaluator.evaluate(changeValExpression(), new XDebuggerEvaluator.XEvaluationCallback() {
@Override
public void evaluated(@NotNull XValue result) {
XDebuggerEvaluator.XEvaluationCallback callback = new XDebuggerEvaluator.XEvaluationCallback() {
@Override
public void evaluated(@NotNull XValue value) {
//todo: compute presentation and work with
String text = ((PyDebugValue)value).getValue();
final String corrected;
if (!isNumeric()) {
if (!text.startsWith("\\\'") && !text.startsWith("\\\"")) {
corrected = "\'" + text + "\'";
}
else {
corrected = text;
}
}
else {
corrected = text;
disableColor();
}
new WriteCommandAction(null) {
protected void run(@NotNull Result result) throws Throwable {
if (getEditor().getEditor() != null) {
getEditor().getEditor().getDocument().setText(corrected);
((AsyncArrayTableModel)myTable.getModel()).changeValue(row, col, corrected);
cancelEditing();
}
}
}.execute();
setLastValue(corrected);
}
@Override
public void errorOccurred(@NotNull String errorMessage) {
showError(errorMessage);
}
};
myEvaluator.evaluate(getCellSlice(), callback, null);
}
@Override
public void errorOccurred(@NotNull String errorMessage) {
showError(errorMessage);
}
}, null);
super.doOKAction(row, col);
}
};
}
public String correctStringValue(@NotNull Object value) {
if (value instanceof String) {
String corrected = (String)value;
@@ -535,14 +339,6 @@ public class NumpyArrayTable {
this.myDtypeKind = dtype;
}
public int[] getShape() {
return myShape;
}
public void setShape(int[] shape) {
this.myShape = shape;
}
public void showError(String message) {
myDialog.setError(message);
}
@@ -558,80 +354,15 @@ public class NumpyArrayTable {
});
}
public String getDefaultFormat() {
if (isNumeric()) {
if (myDtypeKind.equals("f")) {
return "%.5f";
}
if (myDtypeKind.equals("i") || myDtypeKind.equals("u")) {
return "%d";
}
if (myDtypeKind.equals("b") || myDtypeKind.equals("c")) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
myComponent.getFormatTextField().getComponent().setEnabled(false);
}
});
return "%s";
}
}
return "%s";
}
public String getFormat() {
if (myComponent.getFormatTextField().getText().isEmpty()) {
return getDefaultFormat();
}
return myComponent.getFormatTextField().getText();
}
private void doReslice(final String newSlice, int[] shape) {
if (shape == null) {
String evalShapeCommand = getEvalShapeCommand(newSlice);
try {
PyDebugValue result = getEvaluator().evaluate(evalShapeCommand, false, false);
shape = parseShape(((PyDebugValue)result).getValue());
if (!is2DShape(shape)) {
showError("Incorrect slice shape " + ((PyDebugValue)result).getValue() + ".");
}
doReslice(newSlice, shape);
}
catch (Exception e) {
showError(e.getMessage());
}
return;
}
myShape = shape;
reset();
}
private static String getEvalShapeCommand(@NotNull String slice) {
//add information about memory, see #parseShape comments
return "repr(" + slice + ".shape)+'#'+repr(" + slice + ".flags['C_CONTIGUOUS'])";
private void doReslice() {
clearErrorMessage();
init(getSliceText(), false);
}
private void clearErrorMessage() {
showError(null);
}
private static boolean is2DShape(int[] shape) {
if (shape.length < 2) {
return false;
}
for (int i = 0; i < shape.length - 2; i++) {
if (shape[i] != 1) {
return false;
}
}
return true;
}
private void doApplyFormat() {
reset();
}
@@ -641,24 +372,6 @@ public class NumpyArrayTable {
initTableModel(true);
}
public String evalTypeFunc(String format) {
return "\'" + format + "\' % l";
}
public int getMaxRow(int[] shape) {
if (shape != null && shape.length >= 2) {
return shape[shape.length - 2];
}
return 0;
}
public int getMaxColumn(int[] shape) {
if (shape != null && shape.length >= 2) {
return shape[shape.length - 1];
}
return 0;
}
public void setBusy(final boolean busy) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
@@ -719,7 +432,18 @@ public class NumpyArrayTable {
}
}
public String getNodeName() {
return (myValue).getName();
public String getNodeFullName() {
String fullName = getDebugValue().getName();
PyDebugValue child = getDebugValue();
while (child.getParent() != null) {
child = child.getParent();
fullName = child.getName() + "." + fullName;
}
return fullName;
}
public String getFormat() {
return myComponent.getFormatTextField().getText();
}
}