diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/VcsLogUiImpl.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/VcsLogUiImpl.java index 7be1211ec70c..834d2c62ec55 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/VcsLogUiImpl.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/VcsLogUiImpl.java @@ -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(); } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/BranchFilterPopupComponent.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/BranchFilterPopupComponent.java index c70595bf7d1b..c66a075c3878 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/BranchFilterPopupComponent.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/BranchFilterPopupComponent.java @@ -32,19 +32,33 @@ import java.util.*; public class BranchFilterPopupComponent extends MultipleValueFilterPopupComponent { - @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 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 values) { + return new VcsLogBranchFilterImpl(values); + } + + @Override + @NotNull + protected Collection getValues(@Nullable VcsLogBranchFilter filter) { + return filter == null ? Collections.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() { + actionGroup.add(constructActionGroup(myFilterModel.getDataPack(), createRecentItemsActionGroup(), new Function() { @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> getRecentValuesFromSettings() { @@ -176,7 +181,7 @@ public class BranchFilterPopupComponent extends MultipleValueFilterPopupComponen @NotNull @Override protected List getAllValues() { - return ContainerUtil.map(myDataPack.getRefs().getBranches(), new Function() { + return ContainerUtil.map(myFilterModel.getDataPack().getRefs().getBranches(), new Function() { @Override public String fun(VcsRef ref) { return ref.getName(); diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/DateFilterPopupComponent.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/DateFilterPopupComponent.java index 855b9266f12a..dc6c2c8b042c 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/DateFilterPopupComponent.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/DateFilterPopupComponent.java @@ -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 { - private Date myAfter; - private Date myBefore; + public DateFilterPopupComponent(FilterModel 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 { 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 { } @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 { 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); } } } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/FilterModel.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/FilterModel.java new file mode 100644 index 000000000000..4d32ceaa6897 --- /dev/null +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/FilterModel.java @@ -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 { + + @NotNull private final Computable myDataPackProvider; + @NotNull private final Collection mySetFilterListeners = ContainerUtil.newArrayList(); + + @Nullable private Filter myFilter; + + FilterModel(@NotNull Computable 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); + } + +} diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/FilterPopupComponent.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/FilterPopupComponent.java index c30ebb546af0..00283950b9c2 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/FilterPopupComponent.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/FilterPopupComponent.java @@ -52,12 +52,19 @@ abstract class FilterPopupComponent extends JPanel @NotNull private final JLabel myFilterNameLabel; @NotNull private final JLabel myFilterValueLabel; - @NotNull private final VcsLogClassicFilterUi myFilterUi; + @NotNull protected final FilterModel myFilterModel; - FilterPopupComponent(@NotNull VcsLogClassicFilterUi filterUi, @NotNull String filterName) { - myFilterUi = filterUi; + FilterPopupComponent(@NotNull String filterName, + @NotNull FilterModel 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 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 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 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 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 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); } } - } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/MultilinePopupBuilder.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/MultilinePopupBuilder.java index eab6cbc6f444..24a8abc7c52d 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/MultilinePopupBuilder.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/MultilinePopupBuilder.java @@ -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) { diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/MultipleValueFilterPopupComponent.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/MultipleValueFilterPopupComponent.java index 40bb39d2ec83..2f5b520d698e 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/MultipleValueFilterPopupComponent.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/MultipleValueFilterPopupComponent.java @@ -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 ex private static final int MAX_FILTER_VALUE_LENGTH = 30; - @Nullable private Collection 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 filterModel) { + super(filterName, filterModel); + myUiProperties = uiProperties; } + @NotNull + protected abstract Collection getValues(@Nullable Filter filter); + @NotNull protected abstract List> getRecentValuesFromSettings(); @@ -50,10 +57,8 @@ abstract class MultipleValueFilterPopupComponent ex @NotNull protected abstract List getAllValues(); - @Nullable - protected Collection getSelectedValues() { - return mySelectedValues; - } + @NotNull + protected abstract Filter createFilter(@NotNull Collection values); @NotNull protected ActionGroup createRecentItemsActionGroup() { @@ -69,15 +74,6 @@ abstract class MultipleValueFilterPopupComponent ex return group; } - void apply(@Nullable Collection values, @NotNull String text, @NotNull String tooltip) { - mySelectedValues = values; - applyFilters(); - setValue(text, tooltip); - if (values != null) { - rememberValuesInSettings(values); - } - } - @NotNull static String displayableText(@NotNull Collection values) { if (values.size() == 1) { @@ -91,17 +87,6 @@ abstract class MultipleValueFilterPopupComponent 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 values) { return new PredefinedValueAction(values); @@ -122,8 +107,9 @@ abstract class MultipleValueFilterPopupComponent 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 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 ex if (event.isOk()) { Collection 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); } } } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/StructureFilterPopupComponent.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/StructureFilterPopupComponent.java index 3140d647795f..b4d90e8ca94a 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/StructureFilterPopupComponent.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/StructureFilterPopupComponent.java @@ -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 { - public static final int FILTER_LABEL_LENGTH = 20; - @NotNull private final Collection myRoots; - @NotNull private final Collection myFiles; + private static final int FILTER_LABEL_LENGTH = 20; - public StructureFilterPopupComponent(@NotNull VcsLogClassicFilterUi filterUi, @NotNull Collection roots) { - super(filterUi, "Structure"); - myRoots = roots; - myFiles = ContainerUtil.newArrayList(); + public StructureFilterPopupComponent(@NotNull FilterModel filterModel) { + super("Structure", filterModel); + } + + @NotNull + @Override + protected String getText(@NotNull VcsLogStructureFilter filter) { + Collection 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 getAllFiles(@NotNull VcsLogDataPack dataPack, @NotNull VcsLogStructureFilter filter) { + Collection 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 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 files) { + private static String getToolTip(@NotNull Collection files) { List filesToDisplay = new ArrayList(files); if (files.size() > 10) { filesToDisplay = filesToDisplay.subList(0, 10); @@ -94,19 +102,17 @@ class StructureFilterPopupComponent extends FilterPopupComponent(myRoots)); + public void actionPerformed(@NotNull AnActionEvent e) { + Project project = e.getRequiredData(CommonDataKeys.PROJECT); + VcsLogDataPack dataPack = myFilterModel.getDataPack(); + VcsLogStructureFilter filter = myFilterModel.getFilter(); + Collection files = filter == null ? Collections.emptySet() : getAllFiles(dataPack, filter); + Set roots = dataPack.getLogProviders().keySet(); + VcsStructureChooser chooser = new VcsStructureChooser(project, "Select Files or Folders to Filter", files, + new ArrayList(roots)); if (chooser.showAndGet()) { - myFiles.clear(); - myFiles.addAll(chooser.getSelectedFiles()); - setValue(myFiles); - applyFilters(); + myFilterModel.setFilter(new VcsLogStructureFilterImpl(chooser.getSelectedFiles(), roots)); } } - } - } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/UserFilterPopupComponent.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/UserFilterPopupComponent.java index 368b426efc10..8451e549c7fc 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/UserFilterPopupComponent.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/UserFilterPopupComponent.java @@ -38,16 +38,29 @@ class UserFilterPopupComponent extends MultipleValueFilterPopupComponent 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> getRecentValuesFromSettings() { - return myUiProperties.getRecentlyFilteredUserGroups(); + protected Collection getValues(@Nullable VcsLogUserFilter filter) { + if (filter == null) { + return Collections.emptySet(); + } + Set 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> getRecentValuesFromSettings() { + return myUiProperties.getRecentlyFilteredUserGroups(); } @Override @@ -91,6 +108,12 @@ class UserFilterPopupComponent extends MultipleValueFilterPopupComponent values) { + return new VcsLogUserFilterImpl(values, myDataHolder.getCurrentUser()); + } + private static class VcsLogUserFilterImpl implements VcsLogUserFilter { @NotNull private final Collection myUsers; @@ -119,6 +142,7 @@ class UserFilterPopupComponent extends MultipleValueFilterPopupComponent() { + @SuppressWarnings("StringToUpperCaseOrToLowerCaseWithoutLocale") @Override public boolean value(String user) { String lowerUser = user.toLowerCase(); diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/VcsLogClassicFilterUi.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/VcsLogClassicFilterUi.java index 8bc71682ad70..44a1d2508338 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/VcsLogClassicFilterUi.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/filter/VcsLogClassicFilterUi.java @@ -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 myBranchFilterModel; + @NotNull private final FilterModel myUserFilterModel; + @NotNull private final FilterModel myDateFilterModel; + @NotNull private final FilterModel myStructureFilterModel; + @NotNull private final FilterModel 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 dataPackGetter = new NotNullComputable() { + @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(dataPackGetter); + myUserFilterModel = new FilterModel(dataPackGetter); + myDateFilterModel = new FilterModel(dataPackGetter); + myStructureFilterModel = new FilterModel(dataPackGetter); + myTextFilterModel = new FilterModel(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 getComponents() { - return Arrays.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() { + @Override + public JComponent compute() { + return new BranchFilterPopupComponent(myUiProperties, myBranchFilterModel); + } + })); + actionGroup.add(new FilterActionComponent(new Computable() { + @Override + public JComponent compute() { + return new UserFilterPopupComponent(myUiProperties, myLogDataHolder, myUserFilterModel); + } + })); + actionGroup.add(new FilterActionComponent(new Computable() { + @Override + public JComponent compute() { + return new DateFilterPopupComponent(myDateFilterModel); + } + })); + actionGroup.add(new FilterActionComponent(new Computable() { + @Override + public JComponent compute() { + return new StructureFilterPopupComponent(myStructureFilterModel); + } + })); + return actionGroup; } @NotNull @Override public VcsLogFilterCollection getFilters() { - Pair filtersFromText = getFiltersFromTextArea(myTextFilter.getText().trim()); - return new VcsLogFilterCollectionImpl(myBranchFilterComponent.getFilter(), myUserFilterComponent.getFilter(), - filtersFromText.second, myDateFilterComponent.getFilter(), - filtersFromText.first, myStructureFilterComponent.getFilter()); + ApplicationManager.getApplication().assertIsDispatchThread(); + Pair filtersFromText = getFiltersFromTextArea(myTextFilterModel.getFilter()); + return new VcsLogFilterCollectionImpl(myBranchFilterModel.getFilter(), + myUserFilterModel.getFilter(), + filtersFromText.second, + myDateFilterModel.getFilter(), + filtersFromText.first, + myStructureFilterModel.getFilter()); } @NotNull - private static Pair getFiltersFromTextArea(@NotNull String text) { - if (text.isEmpty()) { + private static Pair getFiltersFromTextArea(@Nullable VcsLogTextFilter filter) { + if (filter == null) { + return Pair.empty(); + } + String text = filter.getText().trim(); + if (StringUtil.isEmptyOrSpaces(text)) { return Pair.empty(); } List 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 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 myFilterModel; - TextFilterComponent(SearchTextField searchField) { - mySearchField = searchField; + public TextFilterComponent(FilterModel 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 myComponentCreator; + + public FilterActionComponent(@NotNull Computable 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) { } } - } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/MainFrame.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/MainFrame.java index 45f05ad6646a..484e7c4b4ad3 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/MainFrame.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/MainFrame.java @@ -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 getOrderedComponents() { - return ContainerUtil.concat(Arrays.asList(myGraphTable, myChangesBrowser.getPreferredFocusedComponent()), - myFilterUi.getComponents()); + return Arrays.asList(myGraphTable, myChangesBrowser.getPreferredFocusedComponent()); } } diff --git a/python/helpers/pydev/pydev_console_utils.py b/python/helpers/pydev/pydev_console_utils.py index 2980873cd323..d12419e19e57 100644 --- a/python/helpers/pydev/pydev_console_utils.py +++ b/python/helpers/pydev/pydev_console_utils.py @@ -387,9 +387,13 @@ class BaseInterpreterInterface: def getArray(self, attr, roffset, coffset, rows, cols, format): 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 += "" diff --git a/python/helpers/pydev/pydevd_comm.py b/python/helpers/pydev/pydevd_comm.py index 5727b2d8f790..8b7596d5818c 100644 --- a/python/helpers/pydev/pydevd_comm.py +++ b/python/helpers/pydev/pydevd_comm.py @@ -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 += 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 += "" 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) #======================================================================================================================= diff --git a/python/helpers/pydev/pydevd_vars.py b/python/helpers/pydev/pydevd_vars.py index db297f991928..b03560345844 100644 --- a/python/helpers/pydev/pydevd_vars.py +++ b/python/helpers/pydev/pydevd_vars.py @@ -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 += "" % (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 += "" % (False, rows, cols) for row in range(rows): xml += "" % 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 = '' % \ + (True, slice, rows, cols, format, type, bounds[1], bounds[0]) + return xml diff --git a/python/pydevSrc/com/jetbrains/python/debugger/ArrayChunk.java b/python/pydevSrc/com/jetbrains/python/debugger/ArrayChunk.java new file mode 100644 index 000000000000..d7e5ade2895a --- /dev/null +++ b/python/pydevSrc/com/jetbrains/python/debugger/ArrayChunk.java @@ -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(); + } +} diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java b/python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java index e4f68b797571..0e88aef89a76 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java @@ -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; } diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetArrayCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetArrayCommand.java index cfb244759eb6..da19bf2381a6 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetArrayCommand.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetArrayCommand.java @@ -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; } } diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java index 06a6864adb63..a84335879fd3 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java @@ -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, diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java index de1296b9a4d5..5c9deabfcc5f 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java @@ -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 callback); diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java index e47f4f70ad00..42f17bd042f0 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java @@ -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 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 { diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java index 2fd0374ae545..11cc0d798e58 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java @@ -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(); diff --git a/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java b/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java index 5e516b4fbf96..6d237301c293 100644 --- a/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java +++ b/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java @@ -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); } diff --git a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java index 8842373ef2a9..c09b2b03e743 100644 --- a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java +++ b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java @@ -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); diff --git a/python/src/com/jetbrains/python/debugger/array/AsyncArrayTableModel.java b/python/src/com/jetbrains/python/debugger/array/AsyncArrayTableModel.java index 278bde5f3eb7..3eb47155d316 100644 --- a/python/src/com/jetbrains/python/debugger/array/AsyncArrayTableModel.java +++ b/python/src/com/jetbrains/python/debugger/array/AsyncArrayTableModel.java @@ -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, ListenableFuture> myChunkCache = CacheBuilder.newBuilder().build( - new CacheLoader, ListenableFuture>() { + private LoadingCache, ListenableFuture> myChunkCache = CacheBuilder.newBuilder().build( + new CacheLoader, ListenableFuture>() { @Override - public ListenableFuture load(final Pair key) throws Exception { + public ListenableFuture load(final Pair 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 task = ListenableFutureTask.create(new Callable() { + ListenableFutureTask task = ListenableFutureTask.create(new Callable() { @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 key = itemToChunkKey(row, col); try { - ListenableFuture chunk = myChunkCache.get(key); + ListenableFuture 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 chunk = myChunkCache.getIfPresent(itemToChunkKey(row, col)); + Future 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 + } } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/debugger/array/NumpyArrayTable.java b/python/src/com/jetbrains/python/debugger/array/NumpyArrayTable.java index dacf3d51a770..6a2122e9b7f1 100644 --- a/python/src/com/jetbrains/python/debugger/array/NumpyArrayTable.java +++ b/python/src/com/jetbrains/python/debugger/array/NumpyArrayTable.java @@ -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 task = ListenableFutureTask.create(new Callable() { + @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 callback = new Consumer() { - @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> 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> getDefaultSlice() { - return getSlice(COLUMNS_IN_DEFAULT_SLICE, ROWS_IN_DEFAULT_SLICE); - } - - private List> getSlice(int columns, int rows) { - List> slices = new ArrayList>(); - for (int i = 0; i < myShape.length; i++) { - Pair slice = new Pair(0, 0); - if (i == myShape.length - 1) { - slice = new Pair(0, Math.min(myShape[i], columns)); - } - else if (i == myShape.length - 2) { - slice = new Pair(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(); } }