diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/JavaValue.java b/java/debugger/impl/src/com/intellij/debugger/engine/JavaValue.java
index 1603ed928ded..9cfb2bfde84e 100644
--- a/java/debugger/impl/src/com/intellij/debugger/engine/JavaValue.java
+++ b/java/debugger/impl/src/com/intellij/debugger/engine/JavaValue.java
@@ -326,19 +326,36 @@ public class JavaValue extends XNamedValue implements NodeDescriptorProvider, XV
}
@Override
- public void computeSourcePosition(@NotNull XNavigatable navigatable) {
- if (myValueDescriptor instanceof FieldDescriptorImpl) {
- SourcePosition position = ((FieldDescriptorImpl)myValueDescriptor).getSourcePosition(getProject(), getDebuggerContext());
- if (position != null) {
- navigatable.setSourcePosition(DebuggerUtilsEx.toXSourcePosition(position));
+ public void computeSourcePosition(@NotNull final XNavigatable navigatable) {
+ if (myEvaluationContext.getSuspendContext().isResumed()) return;
+ myEvaluationContext.getDebugProcess().getManagerThread().schedule(new SuspendContextCommandImpl(myEvaluationContext.getSuspendContext()) {
+ @Override
+ public Priority getPriority() {
+ return Priority.NORMAL;
}
- }
- if (myValueDescriptor instanceof LocalVariableDescriptorImpl) {
- SourcePosition position = ((LocalVariableDescriptorImpl)myValueDescriptor).getSourcePosition(getProject(), getDebuggerContext());
- if (position != null) {
- navigatable.setSourcePosition(DebuggerUtilsEx.toXSourcePosition(position));
+
+ @Override
+ public void contextAction() throws Exception {
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ @Override
+ public void run() {
+ if (myValueDescriptor instanceof FieldDescriptorImpl) {
+ SourcePosition position = ((FieldDescriptorImpl)myValueDescriptor).getSourcePosition(getProject(), getDebuggerContext());
+ if (position != null) {
+ navigatable.setSourcePosition(DebuggerUtilsEx.toXSourcePosition(position));
+ }
+ }
+ if (myValueDescriptor instanceof LocalVariableDescriptorImpl) {
+ SourcePosition position =
+ ((LocalVariableDescriptorImpl)myValueDescriptor).getSourcePosition(getProject(), getDebuggerContext());
+ if (position != null) {
+ navigatable.setSourcePosition(DebuggerUtilsEx.toXSourcePosition(position));
+ }
+ }
+ }
+ });
}
- }
+ });
}
private DebuggerContextImpl getDebuggerContext() {
diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaGenerateMemberCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaGenerateMemberCompletionContributor.java
index 0deafb45cad9..351eca4857fd 100644
--- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaGenerateMemberCompletionContributor.java
+++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaGenerateMemberCompletionContributor.java
@@ -69,7 +69,7 @@ public class JavaGenerateMemberCompletionContributor {
for (PsiField field : parent.getFields()) {
if (!(field instanceof PsiEnumConstant)) {
Collections.addAll(prototypes, GetterSetterPrototypeProvider.generateGetterSetters(field, true));
- Collections.addAll(prototypes, GetterSetterPrototypeProvider.generateGetterSetters(field, true));
+ Collections.addAll(prototypes, GetterSetterPrototypeProvider.generateGetterSetters(field, false));
}
}
for (final PsiMethod prototype : prototypes) {
diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/OverrideImplementTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/OverrideImplementTest.java
index f9f70d655310..f35df4d389f9 100644
--- a/java/java-tests/testSrc/com/intellij/codeInsight/OverrideImplementTest.java
+++ b/java/java-tests/testSrc/com/intellij/codeInsight/OverrideImplementTest.java
@@ -81,7 +81,7 @@ public class OverrideImplementTest extends LightCodeInsightTestCase {
CodeStyleSettings codeStyleSettings = CodeStyleSettingsManager.getSettings(getProject()).clone();
try {
CommonCodeStyleSettings javaSettings = codeStyleSettings.getCommonSettings(JavaLanguage.INSTANCE);
- codeStyleSettings.RIGHT_MARGIN = 80;
+ javaSettings.RIGHT_MARGIN = 80;
javaSettings.KEEP_LINE_BREAKS = true;
codeStyleSettings.GENERATE_FINAL_PARAMETERS = true;
javaSettings.METHOD_PARAMETERS_WRAP = CommonCodeStyleSettings.WRAP_ON_EVERY_ITEM;
@@ -97,7 +97,7 @@ public class OverrideImplementTest extends LightCodeInsightTestCase {
CodeStyleSettings codeStyleSettings = CodeStyleSettingsManager.getSettings(getProject()).clone();
try {
CommonCodeStyleSettings javaSettings = codeStyleSettings.getCommonSettings(JavaLanguage.INSTANCE);
- codeStyleSettings.RIGHT_MARGIN = 80;
+ javaSettings.RIGHT_MARGIN = 80;
javaSettings.KEEP_LINE_BREAKS = false;
codeStyleSettings.GENERATE_FINAL_PARAMETERS = false;
javaSettings.METHOD_PARAMETERS_WRAP = CommonCodeStyleSettings.WRAP_ON_EVERY_ITEM;
diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/ModuleBuildTarget.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/ModuleBuildTarget.java
index a6ac897a1d2d..05c185862399 100644
--- a/jps/jps-builders/src/org/jetbrains/jps/incremental/ModuleBuildTarget.java
+++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/ModuleBuildTarget.java
@@ -165,6 +165,10 @@ public final class ModuleBuildTarget extends JVMModuleBuildTarget getCommits() {
return myCommits;
}
-
- public boolean hasErrors() {
- return !myErrors.isEmpty();
- }
}
diff --git a/platform/dvcs-api/src/com/intellij/dvcs/push/PushSource.java b/platform/dvcs-api/src/com/intellij/dvcs/push/PushSource.java
index 8e756d8fe76a..7ae7a33dd6e2 100644
--- a/platform/dvcs-api/src/com/intellij/dvcs/push/PushSource.java
+++ b/platform/dvcs-api/src/com/intellij/dvcs/push/PushSource.java
@@ -15,9 +15,12 @@
*/
package com.intellij.dvcs.push;
+import org.jetbrains.annotations.NotNull;
+
/**
* Source to push from. For example, local branch for git or branch/bookmark for mercurial.
*/
public interface PushSource {
+ @NotNull
String getPresentation();
}
diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/PushController.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/PushController.java
index 5d6b839e5912..044dadc81272 100644
--- a/platform/dvcs-impl/src/com/intellij/dvcs/push/PushController.java
+++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/PushController.java
@@ -198,14 +198,15 @@ public class PushController implements Disposable {
@Override
public void onSuccess() {
OutgoingResult outgoing = result.get();
- if (outgoing.hasErrors()) {
+ List errors = outgoing.getErrors();
+ if (!errors.isEmpty()) {
final CommitLoader loader = new CommitLoader() {
@Override
public void reloadCommits() {
loadCommits(model, node, false);
}
};
- myPushLog.setChildren(node, ContainerUtil.map(outgoing.getErrors(), new Function() {
+ myPushLog.setChildren(node, ContainerUtil.map(errors, new Function() {
@Override
public DefaultMutableTreeNode fun(final VcsError error) {
VcsLinkedText errorLinkText = new VcsLinkedText(error.getText(), new VcsLinkListener() {
@@ -347,13 +348,13 @@ public class PushController implements Disposable {
return additionalPanels;
}
- private boolean hasRepoForPushSupport(@NotNull PushSupport support) {
- for (MyRepoModel model : myView2Model.values()) {
- if (support.equals(model.getSupport())) {
- return true;
+ private boolean hasRepoForPushSupport(@NotNull final PushSupport support) {
+ return ContainerUtil.exists(myView2Model.values(), new Condition() {
+ @Override
+ public boolean value(MyRepoModel model) {
+ return support.equals(model.getSupport());
}
- }
- return false;
+ });
}
private static class MyRepoModel {
diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java b/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java
index 3b935745a212..44b4c8c5c3f0 100644
--- a/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java
+++ b/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java
@@ -34,13 +34,10 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
-/**
- * @author Nadya Zabrodina
- */
public class BranchActionGroupPopup extends PopupFactoryImpl.ActionGroupPopup {
public BranchActionGroupPopup(@NotNull String title, @NotNull Project project,
@NotNull Condition preselectActionCondition, @NotNull ActionGroup actions) {
- super(title, actions, SimpleDataContext.getProjectContext(project), false, false, false, false, null, -1,
+ super(title, actions, SimpleDataContext.getProjectContext(project), false, false, true, false, null, -1,
preselectActionCondition, null);
}
diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/ui/NewBranchAction.java b/platform/dvcs-impl/src/com/intellij/dvcs/ui/NewBranchAction.java
index 29481e236c2a..92ba9515b5d6 100644
--- a/platform/dvcs-impl/src/com/intellij/dvcs/ui/NewBranchAction.java
+++ b/platform/dvcs-impl/src/com/intellij/dvcs/ui/NewBranchAction.java
@@ -25,9 +25,6 @@ import org.jetbrains.annotations.NotNull;
import java.util.List;
-/**
- * @author Nadya Zabrodina
- */
public abstract class NewBranchAction extends DumbAwareAction {
protected final List myRepositories;
protected Project myProject;
@@ -43,7 +40,7 @@ public abstract class NewBranchAction extends DumbAwareAct
public void update(AnActionEvent e) {
if (DvcsUtil.anyRepositoryIsFresh(myRepositories)) {
e.getPresentation().setEnabled(false);
- e.getPresentation().setDescription("Checkout of a new branch is not possible before the first commit.");
+ e.getPresentation().setDescription("Checkout of a new branch is not possible before the first commit");
}
}
diff --git a/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/PsiNamesElementSignatureProvider.java b/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/PsiNamesElementSignatureProvider.java
index 17a85304d4a1..3409d3f93fc8 100644
--- a/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/PsiNamesElementSignatureProvider.java
+++ b/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/PsiNamesElementSignatureProvider.java
@@ -89,11 +89,23 @@ public class PsiNamesElementSignatureProvider extends AbstractElementSignaturePr
return candidate instanceof PsiComment ? candidate : null;
}
else if (CODE_BLOCK_MARKER.equals(elementMarker)) {
+ int index = 0;
+ if (tokenizer.hasMoreTokens()) {
+ String indexStr = tokenizer.nextToken();
+ try {
+ index = Integer.parseInt(indexStr);
+ }
+ catch (NumberFormatException e) {
+ if (processingInfoStorage != null) {
+ processingInfoStorage.append("Invalid block index: ").append(indexStr).append("\n");
+ }
+ }
+ }
for (PsiElement child = parent.getFirstChild(); child != null; child = child.getNextSibling()) {
- PsiElement firstChild = child.getFirstChild();
- PsiElement lastChild = child.getLastChild();
- if (firstChild != null && lastChild != null && "{".equals(firstChild.getText()) && "}".equals(lastChild.getText())) {
- return child;
+ if (isBlockElement(child)) {
+ if (--index < 0) {
+ return child;
+ }
}
}
return null;
@@ -212,18 +224,39 @@ public class PsiNamesElementSignatureProvider extends AbstractElementSignaturePr
PsiElement parent = element.getParent();
if (parent instanceof PsiNamedElement && !(parent instanceof PsiFile)) {
- PsiElement firstChild = element.getFirstChild();
- PsiElement lastChild = element.getLastChild();
- if (firstChild != null && "{".equals(firstChild.getText()) && lastChild != null && "}".equals(lastChild.getText())) {
+ if (isBlockElement(element)) {
+ int index = getBlockElementIndex(element);
StringBuilder bufferToUse = buffer;
if (bufferToUse == null) {
bufferToUse = new StringBuilder();
}
bufferToUse.append(TYPE_MARKER).append(ELEMENT_TOKENS_SEPARATOR).append(CODE_BLOCK_MARKER);
+ if (index > 0) {
+ bufferToUse.append(ELEMENT_TOKENS_SEPARATOR).append(index);
+ }
return bufferToUse;
}
}
return null;
}
+
+ private static boolean isBlockElement(@NotNull PsiElement element) {
+ PsiElement firstChild = element.getFirstChild();
+ PsiElement lastChild = element.getLastChild();
+ return firstChild != null && "{".equals(firstChild.getText()) && lastChild != null && "}".equals(lastChild.getText());
+ }
+
+ private static int getBlockElementIndex(@NotNull PsiElement element) {
+ int i = 0;
+ for (PsiElement sibling : element.getParent().getChildren()) {
+ if (element.equals(sibling)) {
+ return i;
+ }
+ if (isBlockElement(sibling)) {
+ i++;
+ }
+ }
+ throw new RuntimeException("Malformed PSI");
+ }
}
diff --git a/platform/platform-impl/src/com/intellij/openapi/options/newEditor/OptionsEditor.java b/platform/platform-impl/src/com/intellij/openapi/options/newEditor/OptionsEditor.java
index 07e5a93670e6..271539f038a0 100644
--- a/platform/platform-impl/src/com/intellij/openapi/options/newEditor/OptionsEditor.java
+++ b/platform/platform-impl/src/com/intellij/openapi/options/newEditor/OptionsEditor.java
@@ -49,6 +49,7 @@ import com.intellij.ui.navigation.History;
import com.intellij.ui.navigation.Place;
import com.intellij.ui.speedSearch.ElementFilter;
import com.intellij.ui.treeStructure.SimpleNode;
+import com.intellij.ui.treeStructure.filtered.FilteringTreeBuilder;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.update.Activatable;
import com.intellij.util.ui.update.MergingUpdateQueue;
@@ -87,6 +88,7 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
private final History myHistory = new History(this);
private final OptionsTree myTree;
+ private final SettingsTreeView myTreeView;
private final MySearchField mySearch;
private final Splitter myMainSplitter;
//[back/forward] JComponent myToolbarComponent;
@@ -126,7 +128,12 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
mySearch = new MySearchField() {
@Override
protected void onTextKeyEvent(final KeyEvent e) {
- myTree.processTextEvent(e);
+ if (myTreeView != null) {
+ myTreeView.myTree.processKeyEvent(e);
+ }
+ else {
+ myTree.processTextEvent(e);
+ }
}
};
@@ -144,12 +151,12 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
}
});
- myTree = new OptionsTree(myProject, groups, getContext()) {
+ final KeyListener listener = new KeyListener() {
@Override
- protected void onTreeKeyEvent(final KeyEvent e) {
+ public void keyTyped(KeyEvent event) {
myFilterDocumentWasChanged = false;
try {
- mySearch.keyEventToTextField(e);
+ mySearch.keyEventToTextField(event);
}
finally {
if (myFilterDocumentWasChanged && !isFilterFieldVisible()) {
@@ -157,10 +164,33 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
}
}
}
- };
- getContext().addColleague(myTree);
- Disposer.register(this, myTree);
+ @Override
+ public void keyPressed(KeyEvent event) {
+ keyTyped(event);
+ }
+
+ @Override
+ public void keyReleased(KeyEvent event) {
+ keyTyped(event);
+ }
+ };
+ if (Registry.is("ide.file.settings.tree.new")) {
+ myTreeView = new SettingsTreeView(listener, getContext(), groups);
+ myTree = null;
+ }
+ else {
+ myTreeView = null;
+ myTree = new OptionsTree(myProject, groups, getContext()) {
+ @Override
+ protected void onTreeKeyEvent(final KeyEvent e) {
+ listener.keyTyped(e);
+ }
+ };
+ }
+
+ getContext().addColleague(myTreeView != null ? myTreeView : myTree);
+ Disposer.register(this, myTreeView != null ? myTreeView : myTree);
mySearch.addDocumentListener(new DocumentAdapter() {
@Override
protected void textChanged(DocumentEvent e) {
@@ -198,7 +228,8 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
@Override
public Dimension getMinimumSize() {
Dimension dimension = super.getMinimumSize();
- dimension.width = Math.max(myTree.getMinimumSize().width, mySearchWrapper.getPreferredSize().width);
+ JComponent component = myTreeView != null ? myTreeView : myTree;
+ dimension.width = Math.max(component.getMinimumSize().width, mySearchWrapper.getPreferredSize().width);
return dimension;
}
};
@@ -211,7 +242,7 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
*/
myLeftSide.add(mySearchWrapper, BorderLayout.NORTH);
- myLeftSide.add(myTree, BorderLayout.CENTER);
+ myLeftSide.add(myTreeView != null ? myTreeView : myTree, BorderLayout.CENTER);
setLayout(new BorderLayout());
@@ -233,9 +264,19 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
mySpotlightUpdate = new MergingUpdateQueue("OptionsSpotlight", 200, false, this, this, this);
if (preselectedConfigurable != null) {
- myTree.select(preselectedConfigurable);
+ if (myTreeView != null) {
+ myTreeView.select(preselectedConfigurable);
+ }
+ else {
+ myTree.select(preselectedConfigurable);
+ }
} else {
- myTree.selectFirst();
+ if (myTreeView != null) {
+ myTreeView.selectFirst();
+ }
+ else {
+ myTree.selectFirst();
+ }
}
Toolkit.getDefaultToolkit().addAWTEventListener(this,
@@ -295,12 +336,16 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
@Deprecated
@Nullable
public T findConfigurable(Class configurableClass) {
- return myTree.findConfigurable(configurableClass);
+ return myTreeView != null
+ ? myTreeView.findConfigurable(configurableClass)
+ : myTree.findConfigurable(configurableClass);
}
@Nullable
public SearchableConfigurable findConfigurableById(@NotNull String configurableId) {
- return myTree.findConfigurableById(configurableId);
+ return myTreeView != null
+ ? myTreeView.findConfigurableById(configurableId)
+ : myTree.findConfigurableById(configurableId);
}
public ActionCallback clearSearchAndSelect(Configurable configurable) {
@@ -318,7 +363,9 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
public ActionCallback select(Configurable configurable, final String text) {
myFilter.refilterFor(text, false, true);
- return myTree.select(configurable);
+ return myTreeView != null
+ ? myTreeView.select(configurable)
+ : myTree.select(configurable);
}
private float readProportion(final float defaultValue, final String propertyName) {
@@ -367,7 +414,10 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
myOwnDetails.setContent(myContentWrapper);
myOwnDetails.setBannerMinHeight(mySearchWrapper.getHeight());
myOwnDetails.setText(getBannerText(configurable));
- if (Registry.is("ide.file.settings.order.new")) {
+ if (myTreeView != null) {
+ myOwnDetails.forProject(myTreeView.findConfigurableProject(configurable));
+ }
+ else if (Registry.is("ide.file.settings.order.new")) {
myOwnDetails.forProject(myTree.getConfigurableProject(configurable));
}
@@ -385,7 +435,8 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
checkModified(oldConfigurable);
checkModified(configurable);
- if (myTree.myBuilder.getSelectedElements().size() == 0) {
+ FilteringTreeBuilder builder = myTreeView != null ? myTreeView.myBuilder : myTree.myBuilder;
+ if (builder.getSelectedElements().size() == 0) {
select(configurable).notify(result);
} else {
result.setDone();
@@ -507,6 +558,9 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
}
private String[] getBannerText(Configurable configurable) {
+ if (myTreeView != null) {
+ return myTreeView.getPathNames(configurable);
+ }
final List list = myTree.getPathToRoot(configurable);
final String[] result = new String[list.size()];
int add = 0;
@@ -795,7 +849,12 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
getContext().fireErrorsChanged(errors, null);
if (!errors.isEmpty()) {
- myTree.select(errors.keySet().iterator().next());
+ if (myTreeView != null) {
+ myTreeView.select(errors.keySet().iterator().next());
+ }
+ else {
+ myTree.select(errors.keySet().iterator().next());
+ }
}
}
@@ -835,7 +894,7 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
return myFiltered.contains(node.getConfigurable()) || isChildOfNameHit(node);
}
- return true;
+ return SettingsTreeView.isFiltered(myFiltered, myHits, value);
}
private boolean isChildOfNameHit(OptionsTree.EditorNode node) {
@@ -929,7 +988,8 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
myLastSelected = current;
}
- final ActionCallback callback = fireUpdate(adjustSelection ? myTree.findNodeFor(toSelect) : null, adjustSelection, now);
+ SimpleNode node = !adjustSelection ? null : myTreeView != null ? myTreeView.findNode(toSelect) : myTree.findNodeFor(toSelect);
+ final ActionCallback callback = fireUpdate(node, adjustSelection, now);
myFilterDocumentWasChanged = true;
@@ -965,7 +1025,12 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
myFilter.refilterFor(filter, false, true).doWhenDone(new Runnable() {
@Override
public void run() {
- myTree.select(config).notifyWhenDone(result);
+ if (myTreeView != null) {
+ myTreeView.select(config).notifyWhenDone(result);
+ }
+ else {
+ myTree.select(config).notifyWhenDone(result);
+ }
}
});
diff --git a/platform/platform-impl/src/com/intellij/openapi/options/newEditor/SettingsTreeView.java b/platform/platform-impl/src/com/intellij/openapi/options/newEditor/SettingsTreeView.java
new file mode 100644
index 000000000000..9269da7ab967
--- /dev/null
+++ b/platform/platform-impl/src/com/intellij/openapi/options/newEditor/SettingsTreeView.java
@@ -0,0 +1,864 @@
+/*
+ * 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.openapi.options.newEditor;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.ui.search.ConfigurableHit;
+import com.intellij.ide.util.treeView.NodeDescriptor;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.options.*;
+import com.intellij.openapi.options.ex.ConfigurableWrapper;
+import com.intellij.openapi.options.ex.NodeConfigurable;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.ActionCallback;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.ui.*;
+import com.intellij.ui.treeStructure.*;
+import com.intellij.ui.treeStructure.filtered.FilteringTreeBuilder;
+import com.intellij.ui.treeStructure.filtered.FilteringTreeStructure;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.ui.GraphicsUtil;
+import com.intellij.util.ui.UIUtil;
+import com.intellij.util.ui.tree.TreeUtil;
+import com.intellij.util.ui.update.MergingUpdateQueue;
+import com.intellij.util.ui.update.Update;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+import java.util.List;
+import javax.swing.*;
+import javax.swing.event.TreeExpansionEvent;
+import javax.swing.event.TreeExpansionListener;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.plaf.TreeUI;
+import javax.swing.plaf.basic.BasicTreeUI;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
+
+/**
+ * @author Sergey.Malenkov
+ */
+final class SettingsTreeView extends JComponent implements Disposable, OptionsEditorColleague {
+ final SimpleTree myTree;
+ final FilteringTreeBuilder myBuilder;
+
+ private final OptionsEditorContext myContext;
+ private final MyRoot myRoot;
+ private final JScrollPane myScroller;
+ private JLabel mySeparator;
+ private final MyRenderer myRenderer = new MyRenderer();
+ private final IdentityHashMap myConfigurableToNodeMap = new IdentityHashMap();
+ private final MergingUpdateQueue myQueue = new MergingUpdateQueue("OptionsTree", 150, false, this, this, this).setRestartTimerOnAdd(true);
+
+ private Configurable myQueuedConfigurable;
+
+ SettingsTreeView(final KeyListener listener, OptionsEditorContext context, ConfigurableGroup... groups) {
+ myContext = context;
+ myRoot = new MyRoot(groups);
+
+ myTree = new MyTree();
+ myTree.getInputMap().clear();
+ TreeUtil.installActions(myTree);
+
+ myTree.setOpaque(true);
+ myTree.setBorder(BorderFactory.createEmptyBorder(0, 1, 0, 0));
+
+ myTree.setRowHeight(-1);
+ myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
+
+ myTree.setCellRenderer(myRenderer);
+ myTree.setRootVisible(false);
+ myTree.setShowsRootHandles(false);
+
+ myScroller = ScrollPaneFactory.createScrollPane(myTree);
+ myScroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
+ add(myScroller);
+
+ myTree.addComponentListener(new ComponentAdapter() {
+ @Override
+ public void componentResized(ComponentEvent e) {
+ myBuilder.revalidateTree();
+ }
+
+ @Override
+ public void componentMoved(ComponentEvent e) {
+ myBuilder.revalidateTree();
+ }
+
+ @Override
+ public void componentShown(ComponentEvent e) {
+ myBuilder.revalidateTree();
+ }
+ });
+
+ myTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
+ public void valueChanged(TreeSelectionEvent event) {
+ MyNode node = extractNode(event.getNewLeadSelectionPath());
+ select(node == null ? null : node.myConfigurable);
+ }
+ });
+
+ myTree.addKeyListener(new KeyListener() {
+ public void keyTyped(KeyEvent event) {
+ if (listener != null && isValid(event)) {
+ listener.keyTyped(event);
+ }
+ }
+
+ public void keyPressed(KeyEvent event) {
+ if (listener != null && isValid(event)) {
+ listener.keyPressed(event);
+ }
+ }
+
+ public void keyReleased(KeyEvent event) {
+ if (listener != null && isValid(event)) {
+ listener.keyReleased(event);
+ }
+ }
+
+ private boolean isValid(KeyEvent event) {
+ return null == myTree.getInputMap().get(KeyStroke.getKeyStrokeForEvent(event));
+ }
+ });
+ myBuilder = new MyBuilder(new SimpleTreeStructure.Impl(myRoot));
+ myBuilder.setFilteringMerge(300, null);
+ Disposer.register(this, myBuilder);
+ }
+
+ @NotNull
+ String[] getPathNames(Configurable configurable) {
+ ArrayDeque path = new ArrayDeque();
+ MyNode node = myConfigurableToNodeMap.get(configurable);
+ while (node != null) {
+ path.push(node.myDisplayName);
+ SimpleNode parent = node.getParent();
+ node = parent instanceof MyNode
+ ? (MyNode)parent
+ : null;
+ }
+ return ArrayUtil.toStringArray(path);
+ }
+
+ @Nullable
+ SimpleNode findNode(Configurable toSelect) {
+ return myConfigurableToNodeMap.get(toSelect);
+ }
+
+ @Nullable
+ SearchableConfigurable findConfigurableById(@NotNull String id) {
+ for (Configurable configurable : myConfigurableToNodeMap.keySet()) {
+ if (configurable instanceof SearchableConfigurable) {
+ SearchableConfigurable searchable = (SearchableConfigurable)configurable;
+ if (id.equals(searchable.getId())) {
+ return searchable;
+ }
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ T findConfigurable(@NotNull Class type) {
+ for (UnnamedConfigurable configurable : myConfigurableToNodeMap.keySet()) {
+ if (configurable instanceof ConfigurableWrapper) {
+ ConfigurableWrapper wrapper = (ConfigurableWrapper)configurable;
+ configurable = wrapper.getConfigurable();
+ }
+ if (type.isInstance(configurable)) {
+ return type.cast(configurable);
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ Project findConfigurableProject(@Nullable Configurable configurable) {
+ if (configurable instanceof ConfigurableWrapper) {
+ ConfigurableWrapper wrapper = (ConfigurableWrapper)configurable;
+ return wrapper.getExtensionPoint().getProject();
+ }
+ return findConfigurableProject(myConfigurableToNodeMap.get(configurable));
+ }
+
+ @Nullable
+ private static Project findConfigurableProject(@Nullable MyNode node) {
+ if (node != null) {
+ Configurable configurable = node.myConfigurable;
+ if (configurable instanceof ConfigurableWrapper) {
+ ConfigurableWrapper wrapper = (ConfigurableWrapper)configurable;
+ return wrapper.getExtensionPoint().getProject();
+ }
+ SimpleNode parent = node.getParent();
+ if (parent instanceof MyNode) {
+ return findConfigurableProject((MyNode)parent);
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private ConfigurableGroup findConfigurableGroupAt(int x, int y) {
+ TreePath path = myTree.getClosestPathForLocation(x - myTree.getX(), y - myTree.getY());
+ while (path != null) {
+ MyNode node = extractNode(path);
+ if (node == null) {
+ return null;
+ }
+ if (node.myComposite instanceof ConfigurableGroup) {
+ return (ConfigurableGroup)node.myComposite;
+ }
+ path = path.getParentPath();
+ }
+ return null;
+ }
+
+ @Nullable
+ private static MyNode extractNode(@Nullable Object object) {
+ if (object instanceof TreePath) {
+ TreePath path = (TreePath)object;
+ object = path.getLastPathComponent();
+ }
+ if (object instanceof DefaultMutableTreeNode) {
+ DefaultMutableTreeNode node = (DefaultMutableTreeNode)object;
+ object = node.getUserObject();
+ }
+ if (object instanceof FilteringTreeStructure.FilteringNode) {
+ FilteringTreeStructure.FilteringNode node = (FilteringTreeStructure.FilteringNode)object;
+ object = node.getDelegate();
+ }
+ return object instanceof MyNode
+ ? (MyNode)object
+ : null;
+ }
+
+ static boolean isFiltered(Set configurables, ConfigurableHit hits, SimpleNode value) {
+ if (value instanceof MyNode && !configurables.contains(((MyNode)value).myConfigurable)) {
+ if (hits != null) {
+ configurables = hits.getNameFullHits();
+ while (value != null) {
+ if (value instanceof MyNode) {
+ if (configurables.contains(((MyNode)value).myConfigurable)) {
+ return true;
+ }
+ }
+ value = value.getParent();
+ }
+ }
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void doLayout() {
+ myScroller.setBounds(0, 0, getWidth(), getHeight());
+ }
+
+ @Override
+ public void paint(Graphics g) {
+ super.paint(g);
+
+ if (mySeparator == null) {
+ mySeparator = new JLabel();
+ mySeparator.setFont(UIUtil.getLabelFont());
+ mySeparator.setFont(getFont().deriveFont(Font.BOLD));
+ }
+ ConfigurableGroup group = findConfigurableGroupAt(0, 5 + mySeparator.getFont().getSize());
+ if (group != null && group == findConfigurableGroupAt(0, -5)) {
+ int offset = UIUtil.isUnderNativeMacLookAndFeel() ? 1 : 3;
+ mySeparator.setBorder(BorderFactory.createEmptyBorder(offset, 18, offset, 3));
+ mySeparator.setText(group.getDisplayName());
+
+ Rectangle bounds = myScroller.getViewport().getBounds();
+ int height = mySeparator.getPreferredSize().height;
+ if (bounds.height > height) {
+ bounds.height = height;
+ }
+ g.setColor(myTree.getBackground());
+ if (g instanceof Graphics2D) {
+ int h = bounds.height / 4;
+ int y = bounds.y + bounds.height - h;
+ g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height - h);
+ ((Graphics2D)g).setPaint(UIUtil.getGradientPaint(
+ 0, y, g.getColor(),
+ 0, y + h, ColorUtil.toAlpha(g.getColor(), 0)));
+ g.fillRect(bounds.x, y, bounds.width, h + h);
+ }
+ else {
+ g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
+ }
+ mySeparator.setSize(bounds.width - 1, bounds.height);
+ mySeparator.paint(g.create(bounds.x + 1, bounds.y, bounds.width - 1, bounds.height));
+ }
+ }
+
+ void selectFirst() {
+ for (ConfigurableGroup eachGroup : myRoot.myGroups) {
+ Configurable[] kids = eachGroup.getConfigurables();
+ if (kids.length > 0) {
+ select(kids[0]);
+ return;
+ }
+ }
+ }
+
+ ActionCallback select(@Nullable final Configurable configurable) {
+ if (myBuilder.isSelectionBeingAdjusted()) {
+ return new ActionCallback.Rejected();
+ }
+ final ActionCallback callback = new ActionCallback();
+ myQueuedConfigurable = configurable;
+ myQueue.queue(new Update(this) {
+ public void run() {
+ if (configurable == myQueuedConfigurable) {
+ if (configurable == null) {
+ fireSelected(null, callback);
+ }
+ else {
+ myBuilder.getReady(this).doWhenDone(new Runnable() {
+ @Override
+ public void run() {
+ if (configurable != myQueuedConfigurable) return;
+
+ MyNode editorNode = myConfigurableToNodeMap.get(configurable);
+ FilteringTreeStructure.FilteringNode editorUiNode = myBuilder.getVisibleNodeFor(editorNode);
+ if (editorUiNode == null) return;
+
+ if (!myBuilder.getSelectedElements().contains(editorUiNode)) {
+ myBuilder.select(editorUiNode, new Runnable() {
+ public void run() {
+ fireSelected(configurable, callback);
+ }
+ });
+ }
+ else {
+ myBuilder.scrollSelectionToVisible(new Runnable() {
+ public void run() {
+ fireSelected(configurable, callback);
+ }
+ }, false);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ @Override
+ public void setRejected() {
+ super.setRejected();
+ callback.setRejected();
+ }
+ });
+ return callback;
+ }
+
+ private void fireSelected(Configurable configurable, ActionCallback callback) {
+ myContext.fireSelected(configurable, this).doWhenProcessed(callback.createSetDoneRunnable());
+ }
+
+ @Override
+ public void dispose() {
+ myQueuedConfigurable = null;
+ }
+
+ @Override
+ public ActionCallback onSelected(@Nullable Configurable configurable, Configurable oldConfigurable) {
+ return select(configurable);
+ }
+
+ @Override
+ public ActionCallback onModifiedAdded(Configurable configurable) {
+ myTree.repaint();
+ return new ActionCallback.Done();
+ }
+
+ @Override
+ public ActionCallback onModifiedRemoved(Configurable configurable) {
+ myTree.repaint();
+ return new ActionCallback.Done();
+ }
+
+ @Override
+ public ActionCallback onErrorsChanged() {
+ return new ActionCallback.Done();
+ }
+
+ private final class MyRoot extends CachingSimpleNode {
+ private final ConfigurableGroup[] myGroups;
+
+ private MyRoot(ConfigurableGroup[] groups) {
+ super(null);
+ myGroups = groups;
+ }
+
+ @Override
+ protected SimpleNode[] buildChildren() {
+ if (myGroups == null || myGroups.length == 0) {
+ return NO_CHILDREN;
+ }
+ SimpleNode[] result = new SimpleNode[myGroups.length];
+ for (int i = 0; i < myGroups.length; i++) {
+ result[i] = new MyNode(this, myGroups[i]);
+ }
+ return result;
+ }
+ }
+
+ private final class MyNode extends CachingSimpleNode {
+ private final Configurable.Composite myComposite;
+ private final Configurable myConfigurable;
+ private final String myDisplayName;
+
+ private MyNode(CachingSimpleNode parent, Configurable configurable) {
+ super(parent);
+ myComposite = configurable instanceof Configurable.Composite ? (Configurable.Composite)configurable : null;
+ myConfigurable = configurable;
+ String name = configurable.getDisplayName();
+ myDisplayName = name != null ? name.replace("\n", " ") : "{ " + configurable.getClass().getSimpleName() + " }";
+
+ myConfigurableToNodeMap.put(configurable, this);
+ }
+
+ private MyNode(CachingSimpleNode parent, ConfigurableGroup group) {
+ super(parent);
+ myComposite = group;
+ myConfigurable = null;
+ String name = group.getDisplayName();
+ myDisplayName = name != null ? name.replace("\n", " ") : "{ " + group.getClass().getSimpleName() + " }";
+ }
+
+ @Override
+ protected SimpleNode[] buildChildren() {
+ if (myComposite == null) {
+ return NO_CHILDREN;
+ }
+ Configurable[] configurables = myComposite.getConfigurables();
+ if (configurables == null || configurables.length == 0) {
+ return NO_CHILDREN;
+ }
+ SimpleNode[] result = new SimpleNode[configurables.length];
+ for (int i = 0; i < configurables.length; i++) {
+ result[i] = new MyNode(this, configurables[i]);
+ if (myConfigurable != null) {
+ myContext.registerKid(myConfigurable, configurables[i]);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public boolean isAlwaysLeaf() {
+ return myComposite == null;
+ }
+
+ @Override
+ public int getWeight() {
+ return WeightBasedComparator.UNDEFINED_WEIGHT;
+ }
+ }
+
+ private final class MyRenderer extends GroupedElementsRenderer.Tree {
+ private JLabel myNodeIcon;
+ private JLabel myProjectIcon;
+
+ protected JComponent createItemComponent() {
+ myTextLabel = new ErrorLabel();
+ return myTextLabel;
+ }
+
+ @Override
+ protected void layout() {
+ myNodeIcon = new JLabel(" ", SwingConstants.RIGHT);
+ myProjectIcon = new JLabel(" ", SwingConstants.LEFT);
+ myProjectIcon.setOpaque(true);
+ myRendererComponent.add(BorderLayout.NORTH, mySeparatorComponent);
+ myRendererComponent.add(BorderLayout.CENTER, myComponent);
+ myRendererComponent.add(BorderLayout.WEST, myNodeIcon);
+ myRendererComponent.add(BorderLayout.EAST, myProjectIcon);
+ }
+
+ public Component getTreeCellRendererComponent(JTree tree,
+ Object value,
+ boolean selected,
+ boolean expanded,
+ boolean leaf,
+ int row,
+ boolean focused) {
+ myTextLabel.setOpaque(selected);
+ myTextLabel.setFont(UIUtil.getLabelFont());
+
+ String text;
+ boolean hasSeparatorAbove = false;
+ int preferredForcedWidth = -1;
+
+ MyNode node = extractNode(value);
+ if (node == null) {
+ text = value.toString();
+ }
+ else {
+ text = node.myDisplayName;
+ // show groups in bold
+ if (myRoot == node.getParent()) {
+ hasSeparatorAbove = node != myRoot.getChildAt(0);
+ myTextLabel.setFont(myTextLabel.getFont().deriveFont(Font.BOLD));
+ }
+ TreePath path = tree.getPathForRow(row);
+ if (path == null) {
+ if (value instanceof DefaultMutableTreeNode) {
+ path = new TreePath(((DefaultMutableTreeNode)value).getPath());
+ }
+ }
+ int forcedWidth = 2000;
+ if (path != null && tree.isVisible()) {
+ Rectangle visibleRect = tree.getVisibleRect();
+
+ int nestingLevel = tree.isRootVisible() ? path.getPathCount() - 1 : path.getPathCount() - 2;
+
+ int left = UIUtil.getTreeLeftChildIndent();
+ int right = UIUtil.getTreeRightChildIndent();
+
+ Insets treeInsets = tree.getInsets();
+
+ int indent = (left + right) * nestingLevel + (treeInsets != null ? treeInsets.left + treeInsets.right : 0);
+
+ forcedWidth = visibleRect.width > 0 ? visibleRect.width - indent : forcedWidth;
+ }
+ preferredForcedWidth = forcedWidth - 4;
+ }
+ Component result = configureComponent(text, null, null, null, selected, hasSeparatorAbove, null, preferredForcedWidth);
+ // update font color for modified configurables
+ if (!selected && node != null) {
+ Configurable configurable = node.myConfigurable;
+ if (configurable != null) {
+ if (myContext.getErrors().containsKey(configurable)) {
+ myTextLabel.setForeground(JBColor.RED);
+ }
+ else if (myContext.getModified().contains(configurable)) {
+ myTextLabel.setForeground(JBColor.BLUE);
+ }
+ }
+ }
+ // configure project icon
+ Project project = null;
+ if (node != null) {
+ SimpleNode parent = node.getParent();
+ if (parent instanceof MyNode) {
+ if (myRoot == parent.getParent()) {
+ project = findConfigurableProject(node); // show icon for top-level nodes
+ if (node.myConfigurable instanceof NodeConfigurable) { // special case for custom subgroups (build.tools)
+ Configurable[] configurables = ((NodeConfigurable)node.myConfigurable).getConfigurables();
+ if (configurables != null) { // assume that all configurables have the same project
+ project = findConfigurableProject(configurables[0]);
+ }
+ }
+ }
+ else if (((MyNode)parent).myConfigurable instanceof NodeConfigurable) {
+ if (((MyNode)node.getParent()).myConfigurable instanceof NodeConfigurable) {
+ project = findConfigurableProject(node); // special case for custom subgroups
+ }
+ }
+ }
+ }
+ if (project != null) {
+ myProjectIcon.setIcon(selected
+ ? AllIcons.General.ProjectConfigurableSelected
+ : AllIcons.General.ProjectConfigurable);
+ myProjectIcon.setToolTipText(OptionsBundle.message(project.isDefault()
+ ? "configurable.default.project.tooltip"
+ : "configurable.current.project.tooltip"));
+ myProjectIcon.setBackground(myTextLabel.getBackground());
+ myProjectIcon.setVisible(true);
+ }
+ else {
+ myProjectIcon.setVisible(false);
+ }
+ // configure node icon
+ if (value instanceof DefaultMutableTreeNode) {
+ DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)value;
+ TreePath treePath = new TreePath(treeNode.getPath());
+ myNodeIcon.setIcon(myTree.getHandleIcon(treeNode, treePath));
+ }
+ else {
+ myNodeIcon.setIcon(null);
+ }
+ return result;
+ }
+
+
+ public boolean isUnderHandle(Point point) {
+ Point handlePoint = SwingUtilities.convertPoint(myRendererComponent, point, myNodeIcon);
+ Rectangle bounds = myNodeIcon.getBounds();
+ return bounds.x < handlePoint.x && bounds.getMaxX() >= handlePoint.x;
+ }
+ }
+
+ private final class MyTree extends SimpleTree {
+ @Override
+ public String getToolTipText(MouseEvent event) {
+ if (event != null) {
+ Component component = getDeepestRendererComponentAt(event.getX(), event.getY());
+ if (component instanceof JLabel) {
+ JLabel label = (JLabel)component;
+ if (label.getIcon() != null) {
+ String text = label.getToolTipText();
+ if (text != null) {
+ return text;
+ }
+ }
+ }
+ }
+ return super.getToolTipText(event);
+ }
+
+ @Override
+ protected boolean paintNodes() {
+ return false;
+ }
+
+ @Override
+ protected boolean highlightSingleNode() {
+ return false;
+ }
+
+ @Override
+ public void setUI(TreeUI ui) {
+ TreeUI actualUI = ui;
+ if (!(ui instanceof MyTreeUi)) {
+ actualUI = new MyTreeUi();
+ }
+ super.setUI(actualUI);
+ }
+
+ @Override
+ protected boolean isCustomUI() {
+ return true;
+ }
+
+ @Override
+ protected void configureUiHelper(TreeUIHelper helper) {
+ }
+
+ @Override
+ public boolean getScrollableTracksViewportWidth() {
+ return true;
+ }
+
+
+ @Override
+ public void processKeyEvent(KeyEvent e) {
+ TreePath path = myTree.getSelectionPath();
+ if (path != null) {
+ if (e.getKeyCode() == KeyEvent.VK_LEFT) {
+ if (isExpanded(path)) {
+ collapsePath(path);
+ return;
+ }
+ }
+ else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
+ if (isCollapsed(path)) {
+ expandPath(path);
+ return;
+ }
+ }
+ }
+ super.processKeyEvent(e);
+ }
+
+ @Override
+ protected void processMouseEvent(MouseEvent e) {
+ MyTreeUi ui = (MyTreeUi)myTree.getUI();
+ boolean toggleNow = MouseEvent.MOUSE_RELEASED == e.getID()
+ && UIUtil.isActionClick(e, MouseEvent.MOUSE_RELEASED)
+ && !ui.isToggleEvent(e);
+
+ if (toggleNow || MouseEvent.MOUSE_PRESSED == e.getID()) {
+ TreePath path = getPathForLocation(e.getX(), e.getY());
+ if (path != null) {
+ Rectangle bounds = getPathBounds(path);
+ if (bounds != null && path.getLastPathComponent() instanceof DefaultMutableTreeNode) {
+ DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
+ boolean selected = isPathSelected(path);
+ boolean expanded = isExpanded(path);
+ Component comp =
+ myRenderer.getTreeCellRendererComponent(this, node, selected, expanded, node.isLeaf(), getRowForPath(path), isFocusOwner());
+
+ comp.setBounds(bounds);
+ comp.validate();
+
+ Point point = new Point(e.getX() - bounds.x, e.getY() - bounds.y);
+ if (myRenderer.isUnderHandle(point)) {
+ if (toggleNow) {
+ ui.toggleExpandState(path);
+ }
+ e.consume();
+ return;
+ }
+ }
+ }
+ }
+
+ super.processMouseEvent(e);
+ }
+
+ private final class MyTreeUi extends BasicTreeUI {
+
+ @Override
+ public void toggleExpandState(TreePath path) {
+ super.toggleExpandState(path);
+ }
+
+ @Override
+ public boolean isToggleEvent(MouseEvent event) {
+ return super.isToggleEvent(event);
+ }
+
+ @Override
+ protected boolean shouldPaintExpandControl(TreePath path,
+ int row,
+ boolean isExpanded,
+ boolean hasBeenExpanded,
+ boolean isLeaf) {
+ return false;
+ }
+
+ @Override
+ protected void paintHorizontalPartOfLeg(Graphics g,
+ Rectangle clipBounds,
+ Insets insets,
+ Rectangle bounds,
+ TreePath path,
+ int row,
+ boolean isExpanded,
+ boolean hasBeenExpanded,
+ boolean isLeaf) {
+
+ }
+
+ @Override
+ protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds, Insets insets, TreePath path) {
+ }
+
+ @Override
+ public void paint(Graphics g, JComponent c) {
+ GraphicsUtil.setupAntialiasing(g);
+ super.paint(g, c);
+ }
+ }
+ }
+
+ private final class MyBuilder extends FilteringTreeBuilder {
+
+ List
\
-
-update.locally.modified.rescan.tooltip=Rescan the repository to check for locally modified files again.
Use this button if you have resolved the problem manually.
-update.locally.modified.rescan=Re&scan
-update.locally.modified.revert=Revert Files
-update.locally.modified.title=Locally modified files are detected
update.options.display.name=Git Update Settings
update.options.no.commit=No &Commit
update.options.save.before.update=Clean working tree before update
diff --git a/plugins/git4idea/src/git4idea/rebase/GitRebaser.java b/plugins/git4idea/src/git4idea/rebase/GitRebaser.java
index aabf913fbf61..d5a0470d1667 100644
--- a/plugins/git4idea/src/git4idea/rebase/GitRebaser.java
+++ b/plugins/git4idea/src/git4idea/rebase/GitRebaser.java
@@ -30,6 +30,7 @@ import git4idea.commands.*;
import git4idea.merge.GitConflictResolver;
import git4idea.update.GitUpdateResult;
import git4idea.util.GitUIUtil;
+import git4idea.util.LocalChangesWouldBeOverwrittenHelper;
import git4idea.util.StringScanner;
import git4idea.util.UntrackedFilesNotifier;
import org.jetbrains.annotations.NotNull;
@@ -43,6 +44,8 @@ import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
+import static git4idea.commands.GitLocalChangesWouldBeOverwrittenDetector.Operation.CHECKOUT;
+
/**
* @author Kirill Likhodedov
*/
@@ -77,7 +80,9 @@ public class GitRebaser {
final GitRebaseProblemDetector rebaseConflictDetector = new GitRebaseProblemDetector();
rebaseHandler.addLineListener(rebaseConflictDetector);
GitUntrackedFilesOverwrittenByOperationDetector untrackedFilesDetector = new GitUntrackedFilesOverwrittenByOperationDetector(root);
+ GitLocalChangesWouldBeOverwrittenDetector localChangesDetector = new GitLocalChangesWouldBeOverwrittenDetector(root, CHECKOUT);
rebaseHandler.addLineListener(untrackedFilesDetector);
+ rebaseHandler.addLineListener(localChangesDetector);
String progressTitle = "Rebasing";
GitTask rebaseTask = new GitTask(myProject, rebaseHandler, progressTitle);
@@ -108,7 +113,7 @@ public class GitRebaser {
});
if (failure.get()) {
- updateResult.set(handleRebaseFailure(rebaseHandler, root, rebaseConflictDetector, untrackedFilesDetector));
+ updateResult.set(handleRebaseFailure(rebaseHandler, root, rebaseConflictDetector, untrackedFilesDetector, localChangesDetector));
}
}
finally {
@@ -333,19 +338,27 @@ public class GitRebaser {
}
@NotNull
- public GitUpdateResult handleRebaseFailure(@NotNull GitLineHandler handler, @NotNull VirtualFile root,
+ public GitUpdateResult handleRebaseFailure(@NotNull GitLineHandler handler,
+ @NotNull VirtualFile root,
@NotNull GitRebaseProblemDetector rebaseConflictDetector,
- @NotNull GitMessageWithFilesDetector untrackedWouldBeOverwrittenDetector) {
+ @NotNull GitMessageWithFilesDetector untrackedWouldBeOverwrittenDetector,
+ @NotNull GitLocalChangesWouldBeOverwrittenDetector localChangesDetector) {
if (rebaseConflictDetector.isMergeConflict()) {
LOG.info("handleRebaseFailure merge conflict");
final boolean allMerged = new GitRebaser.ConflictResolver(myProject, myGit, root, this).merge();
return allMerged ? GitUpdateResult.SUCCESS_WITH_RESOLVED_CONFLICTS : GitUpdateResult.INCOMPLETE;
- } else if (untrackedWouldBeOverwrittenDetector.wasMessageDetected()) {
+ }
+ else if (untrackedWouldBeOverwrittenDetector.wasMessageDetected()) {
LOG.info("handleRebaseFailure: untracked files would be overwritten by checkout");
UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, root,
untrackedWouldBeOverwrittenDetector.getRelativeFilePaths(), "rebase", null);
return GitUpdateResult.ERROR;
- } else {
+ }
+ else if (localChangesDetector.wasMessageDetected()) {
+ LocalChangesWouldBeOverwrittenHelper.showErrorNotification(myProject, root, "rebase", localChangesDetector.getRelativeFilePaths());
+ return GitUpdateResult.ERROR;
+ }
+ else {
LOG.info("handleRebaseFailure error " + handler.errors());
GitUIUtil.notifyImportantError(myProject, "Rebase error", GitUIUtil.stringifyErrors(handler.errors()));
return GitUpdateResult.ERROR;
diff --git a/plugins/git4idea/src/git4idea/ui/GitUnstashDialog.java b/plugins/git4idea/src/git4idea/ui/GitUnstashDialog.java
index eccaa446b0ed..44e4f1fab4b9 100644
--- a/plugins/git4idea/src/git4idea/ui/GitUnstashDialog.java
+++ b/plugins/git4idea/src/git4idea/ui/GitUnstashDialog.java
@@ -51,6 +51,7 @@ import git4idea.merge.GitConflictResolver;
import git4idea.repo.GitRepository;
import git4idea.stash.GitStashUtils;
import git4idea.util.GitUIUtil;
+import git4idea.util.LocalChangesWouldBeOverwrittenHelper;
import git4idea.util.UntrackedFilesNotifier;
import git4idea.validators.GitBranchNameValidator;
import org.jetbrains.annotations.NotNull;
@@ -68,6 +69,8 @@ import java.util.HashSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
+import static git4idea.commands.GitLocalChangesWouldBeOverwrittenDetector.Operation.MERGE;
+
/**
* The unstash dialog
*/
@@ -349,7 +352,9 @@ public class GitUnstashDialog extends DialogWrapper {
}
});
GitUntrackedFilesOverwrittenByOperationDetector untrackedFilesDetector = new GitUntrackedFilesOverwrittenByOperationDetector(root);
+ GitLocalChangesWouldBeOverwrittenDetector localChangesDetector = new GitLocalChangesWouldBeOverwrittenDetector(root, MERGE);
h.addLineListener(untrackedFilesDetector);
+ h.addLineListener(localChangesDetector);
GitUtil.workingTreeChangeStarted(myProject);
try {
@@ -371,6 +376,8 @@ public class GitUnstashDialog extends DialogWrapper {
} else if (untrackedFilesDetector.wasMessageDetected()) {
UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(myProject, root, untrackedFilesDetector.getRelativeFilePaths(),
"unstash", null);
+ } else if (localChangesDetector.wasMessageDetected()) {
+ LocalChangesWouldBeOverwrittenHelper.showErrorDialog(myProject, root, "unstash", localChangesDetector.getRelativeFilePaths());
} else if (!res.success()) {
GitUIUtil.showOperationErrors(myProject, h.errors(), h.printableCommandLine());
}
diff --git a/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopup.java b/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopup.java
index bd9c8fb3a1b3..1cb7cf6a66c9 100644
--- a/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopup.java
+++ b/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopup.java
@@ -45,14 +45,8 @@ import javax.swing.event.HyperlinkEvent;
import java.util.List;
/**
- *
* The popup which allows to quickly switch and control Git branches.
- *
- *
- * Use {@link #asListPopup()} to achieve the {@link ListPopup} itself.
- *
- *
- * @author Kirill Likhodedov
+ *
*/
class GitBranchPopup {
@@ -160,32 +154,28 @@ class GitBranchPopup {
}
private void notifyAboutSyncedBranches() {
- VcsNotifier.getInstance(myProject).notifyImportantInfo("Synchronous branch control enabled",
- "You have several Git roots in the project and they all are checked out at the same branch. " +
- "We've enabled synchronous branch control for the project.
" +
- "If you wish to control branches in different roots separately, you may disable the setting.",
- new NotificationListener() {
- @Override
- public void hyperlinkUpdate(@NotNull Notification notification,
- @NotNull HyperlinkEvent event) {
- if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
- ShowSettingsUtil.getInstance().showSettingsDialog(myProject, myVcs
- .getConfigurable().getDisplayName());
- if (myVcsSettings.getSyncSetting() == GitBranchSyncSetting.DONT) {
- notification.expire();
- }
- }
- }
- }
- );
+ String description = "You have several Git roots in the project and they all are checked out at the same branch. " +
+ "We've enabled synchronous branch control for the project.
" +
+ "If you wish to control branches in different roots separately, " +
+ "you may disable the setting.";
+ NotificationListener listener = new NotificationListener() {
+ @Override
+ public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
+ if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
+ ShowSettingsUtil.getInstance().showSettingsDialog(myProject, myVcs.getConfigurable().getDisplayName());
+ if (myVcsSettings.getSyncSetting() == GitBranchSyncSetting.DONT) {
+ notification.expire();
+ }
+ }
+ }
+ };
+ VcsNotifier.getInstance(myProject).notifyImportantInfo("Synchronous branch control enabled", description, listener);
}
private ActionGroup createActions() {
DefaultActionGroup popupGroup = new DefaultActionGroup(null, false);
-
GitRepositoryManager repositoryManager = myRepositoryManager;
if (repositoryManager.moreThanOneRoot()) {
-
if (userWantsSyncControl()) {
fillWithCommonRepositoryActions(popupGroup, repositoryManager);
}
@@ -196,7 +186,6 @@ class GitBranchPopup {
else {
fillPopupWithCurrentRepositoryActions(popupGroup, null);
}
-
popupGroup.addSeparator();
return popupGroup;
}
diff --git a/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopupActions.java b/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopupActions.java
index dbd3f8c902e5..b3cba24540de 100644
--- a/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopupActions.java
+++ b/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopupActions.java
@@ -36,10 +36,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-/**
- *
- * @author Kirill Likhodedov
- */
class GitBranchPopupActions {
private final Project myProject;
@@ -123,7 +119,7 @@ class GitBranchPopupActions {
public void update(AnActionEvent e) {
if (myRepository.isFresh()) {
e.getPresentation().setEnabled(false);
- e.getPresentation().setDescription("Checkout is not possible before the first commit.");
+ e.getPresentation().setDescription("Checkout is not possible before the first commit");
}
}
}
diff --git a/plugins/git4idea/src/git4idea/update/GitRebaseUpdater.java b/plugins/git4idea/src/git4idea/update/GitRebaseUpdater.java
index 04ed3ac845ca..e22c15076170 100644
--- a/plugins/git4idea/src/git4idea/update/GitRebaseUpdater.java
+++ b/plugins/git4idea/src/git4idea/update/GitRebaseUpdater.java
@@ -18,12 +18,10 @@ package git4idea.update;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.VcsNotifier;
import com.intellij.openapi.vcs.update.UpdatedFiles;
import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.util.ui.UIUtil;
import git4idea.GitBranch;
import git4idea.GitUtil;
import git4idea.branch.GitBranchPair;
@@ -78,42 +76,12 @@ public class GitRebaseUpdater extends GitUpdater {
return dest.getName();
}
- // TODO
- //if (!checkLocallyModified(myRoot)) {
- // cancel();
- // updateSucceeded.set(false);
- //}
-
-
- // TODO: show at any case of update successfullibility, also don't show here but for all roots
- //if (mySkippedCommits.size() > 0) {
- // GitSkippedCommits.showSkipped(myProject, mySkippedCommits);
- //}
-
public void cancel() {
myRebaser.abortRebase(myRoot);
myProgressIndicator.setText2("Refreshing files for the root " + myRoot.getPath());
myRoot.refresh(false, true);
}
- /**
- * Check and process locally modified files
- *
- * @param root the project root
- * @param ex the exception holder
- */
- protected boolean checkLocallyModified(final VirtualFile root) throws VcsException {
- final Ref cancelled = new Ref(false);
- UIUtil.invokeAndWaitIfNeeded(new Runnable() {
- public void run() {
- if (!GitUpdateLocallyModifiedDialog.showIfNeeded(myProject, root)) {
- cancelled.set(true);
- }
- }
- });
- return !cancelled.get();
- }
-
@Override
public String toString() {
return "Rebase updater";
diff --git a/plugins/git4idea/src/git4idea/update/GitUpdateLocallyModifiedDialog.form b/plugins/git4idea/src/git4idea/update/GitUpdateLocallyModifiedDialog.form
deleted file mode 100644
index a97507f24ec9..000000000000
--- a/plugins/git4idea/src/git4idea/update/GitUpdateLocallyModifiedDialog.form
+++ /dev/null
@@ -1,71 +0,0 @@
-
-
diff --git a/plugins/git4idea/src/git4idea/update/GitUpdateLocallyModifiedDialog.java b/plugins/git4idea/src/git4idea/update/GitUpdateLocallyModifiedDialog.java
deleted file mode 100644
index c3384cea20b0..000000000000
--- a/plugins/git4idea/src/git4idea/update/GitUpdateLocallyModifiedDialog.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright 2000-2009 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 git4idea.update;
-
-import com.intellij.openapi.application.ApplicationNamesInfo;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.ui.DialogWrapper;
-import com.intellij.openapi.vcs.FilePath;
-import com.intellij.openapi.vcs.VcsException;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.util.ui.UIUtil;
-import com.intellij.vcsUtil.VcsUtil;
-import git4idea.GitUtil;
-import git4idea.commands.GitCommand;
-import git4idea.commands.GitSimpleHandler;
-import git4idea.util.StringScanner;
-import git4idea.i18n.GitBundle;
-import git4idea.rollback.GitRollbackEnvironment;
-import git4idea.util.GitUIUtil;
-
-import javax.swing.*;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * The dialog that displays locally modified files during update process
- */
-public class GitUpdateLocallyModifiedDialog extends DialogWrapper {
- /**
- * The rescan button
- */
- private JButton myRescanButton;
- /**
- * The list of files to revert
- */
- private JList myFilesList;
-
- private JLabel myDescriptionLabel;
- /**
- * The git root label
- */
- private JLabel myGitRoot;
- /**
- * The root panel
- */
- private JPanel myRootPanel;
- /**
- * The collection with locally modified files
- */
- private final List myLocallyModifiedFiles;
-
- /**
- * The constructor
- *
- * @param project the current project
- * @param root the vcs root
- * @param locallyModifiedFiles the collection of locally modified files to use
- */
- protected GitUpdateLocallyModifiedDialog(final Project project, final VirtualFile root, List locallyModifiedFiles) {
- super(project, true);
- myLocallyModifiedFiles = locallyModifiedFiles;
- setTitle(GitBundle.getString("update.locally.modified.title"));
- myGitRoot.setText(root.getPresentableUrl());
- myFilesList.setModel(new DefaultListModel());
- setOKButtonText(GitBundle.getString("update.locally.modified.revert"));
- syncListModel();
- myRescanButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- myLocallyModifiedFiles.clear();
- try {
- scanFiles(project, root, myLocallyModifiedFiles);
- }
- catch (VcsException ex) {
- GitUIUtil.showOperationError(project, ex, "Checking for locally modified files");
- }
- }
- });
- myDescriptionLabel
- .setText(GitBundle.message("update.locally.modified.message", ApplicationNamesInfo.getInstance().getFullProductName()));
- init();
- }
-
- /**
- * Refresh list model according to the current content of the collection
- */
- private void syncListModel() {
- DefaultListModel listModel = (DefaultListModel)myFilesList.getModel();
- listModel.removeAllElements();
- for (String p : myLocallyModifiedFiles) {
- listModel.addElement(p);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected JComponent createCenterPanel() {
- return myRootPanel;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected String getDimensionServiceKey() {
- return getClass().getName();
- }
-
- /**
- * Scan working tree and detect locally modified files
- *
- * @param project the project to scan
- * @param root the root to scan
- * @param files the collection with files
- * @throws VcsException if there problem with running git or working tree is dirty in unsupported way
- */
- private static void scanFiles(Project project, VirtualFile root, List files) throws VcsException {
- String rootPath = root.getPath();
- GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.DIFF);
- h.addParameters("--name-status");
- h.setSilent(true);
- h.setStdoutSuppressed(true);
- StringScanner s = new StringScanner(h.run());
- while (s.hasMoreData()) {
- if (s.isEol()) {
- s.line();
- continue;
- }
- if (s.tryConsume("M\t")) {
- String path = rootPath + "/" + GitUtil.unescapePath(s.line());
- files.add(path);
- }
- else {
- throw new VcsException("Working tree is dirty in unsupported way: " + s.line());
- }
- }
- }
-
-
- /**
- * Show the dialog if needed
- *
- * @param project the project
- * @param root the vcs root
- * @return true if showing is not needed or operation completed successfully
- */
- public static boolean showIfNeeded(final Project project, final VirtualFile root) {
- final ArrayList files = new ArrayList();
- try {
- scanFiles(project, root, files);
- final AtomicBoolean rc = new AtomicBoolean(true);
- if (!files.isEmpty()) {
- UIUtil.invokeAndWaitIfNeeded(new Runnable() {
- public void run() {
- GitUpdateLocallyModifiedDialog d = new GitUpdateLocallyModifiedDialog(project, root, files);
- d.show();
- rc.set(d.isOK());
- }
- });
- if (rc.get()) {
- if (!files.isEmpty()) {
- revertFiles(project, root, files);
- }
- }
- }
- return rc.get();
- }
- catch (final VcsException e) {
- UIUtil.invokeAndWaitIfNeeded(new Runnable() {
- public void run() {
- GitUIUtil.showOperationError(project, e, "Checking for locally modified files");
- }
- });
- return false;
- }
- }
-
- /**
- * Revert files from the list
- *
- * @param project the project
- * @param root the vcs root
- * @param files the files to revert
- */
- private static void revertFiles(Project project, VirtualFile root, ArrayList files) throws VcsException {
- // TODO consider deleted files
- GitRollbackEnvironment rollback = GitRollbackEnvironment.getInstance(project);
- ArrayList list = new ArrayList(files.size());
- for (String p : files) {
- list.add(VcsUtil.getFilePath(p));
- }
- rollback.revert(root, list);
- }
-}
diff --git a/plugins/git4idea/src/git4idea/util/LocalChangesWouldBeOverwrittenHelper.java b/plugins/git4idea/src/git4idea/util/LocalChangesWouldBeOverwrittenHelper.java
index 021ce824b9a7..7659c50e362a 100644
--- a/plugins/git4idea/src/git4idea/util/LocalChangesWouldBeOverwrittenHelper.java
+++ b/plugins/git4idea/src/git4idea/util/LocalChangesWouldBeOverwrittenHelper.java
@@ -35,12 +35,12 @@ import java.util.List;
public class LocalChangesWouldBeOverwrittenHelper {
@NotNull
- public static String getErrorNotificationDescription() {
+ private static String getErrorNotificationDescription() {
return getErrorDescription(true);
}
@NotNull
- public static String getErrorDialogDescription() {
+ private static String getErrorDialogDescription() {
return getErrorDescription(false);
}
@@ -56,7 +56,7 @@ public class LocalChangesWouldBeOverwrittenHelper {
}
}
- public static void showErrorNotification(@NotNull final Project project, @NotNull VirtualFile root, @NotNull final String operationName,
+ public static void showErrorNotification(@NotNull final Project project, @NotNull final VirtualFile root, @NotNull final String operationName,
@NotNull final Collection relativeFilePaths) {
final Collection absolutePaths = GitUtil.toAbsolute(root, relativeFilePaths);
final List changes = GitUtil.findLocalChangesForPaths(project, root, absolutePaths, false);
@@ -66,20 +66,33 @@ public class LocalChangesWouldBeOverwrittenHelper {
@Override
protected void hyperlinkActivated(@NotNull Notification notification,
@NotNull HyperlinkEvent e) {
- String title = "Local Changes Prevent from " + StringUtil.capitalize(operationName);
- String description = getErrorDialogDescription();
- if (changes.isEmpty()) {
- GitUtil.showPathsInDialog(project, absolutePaths, title, description);
- }
- else {
- DialogBuilder builder = new DialogBuilder(project);
- builder.setNorthPanel(new MultiLineLabel(description));
- builder.setCenterPanel(new ChangesBrowserWithRollback(project, changes));
- builder.addOkAction();
- builder.setTitle(title);
- builder.show();
- }
+ showErrorDialog(project, operationName, changes, absolutePaths);
}
});
}
+
+ public static void showErrorDialog(@NotNull Project project, @NotNull VirtualFile root, @NotNull String operationName,
+ @NotNull Collection relativeFilePaths) {
+ Collection absolutePaths = GitUtil.toAbsolute(root, relativeFilePaths);
+ List changes = GitUtil.findLocalChangesForPaths(project, root, absolutePaths, false);
+ showErrorDialog(project, operationName, changes, absolutePaths);
+ }
+
+ private static void showErrorDialog(@NotNull Project project, @NotNull String operationName, @NotNull List changes,
+ @NotNull Collection absolutePaths) {
+ String title = "Local Changes Prevent from " + StringUtil.capitalize(operationName);
+ String description = getErrorDialogDescription();
+ if (changes.isEmpty()) {
+ GitUtil.showPathsInDialog(project, absolutePaths, title, description);
+ }
+ else {
+ DialogBuilder builder = new DialogBuilder(project);
+ builder.setNorthPanel(new MultiLineLabel(description));
+ builder.setCenterPanel(new ChangesBrowserWithRollback(project, changes));
+ builder.addOkAction();
+ builder.setTitle(title);
+ builder.show();
+ }
+ }
+
}
diff --git a/plugins/github/src/org/jetbrains/plugins/github/GithubRebaseAction.java b/plugins/github/src/org/jetbrains/plugins/github/GithubRebaseAction.java
index bfceb0ceec39..719f8b956432 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/GithubRebaseAction.java
+++ b/plugins/github/src/org/jetbrains/plugins/github/GithubRebaseAction.java
@@ -48,6 +48,7 @@ import org.jetbrains.plugins.github.util.*;
import java.io.IOException;
import java.util.Collections;
+import static git4idea.commands.GitLocalChangesWouldBeOverwrittenDetector.Operation.CHECKOUT;
import static org.jetbrains.plugins.github.util.GithubUtil.setVisibleEnabled;
/**
@@ -256,7 +257,9 @@ public class GithubRebaseAction extends DumbAwareAction {
final GitUntrackedFilesOverwrittenByOperationDetector untrackedFilesDetector =
new GitUntrackedFilesOverwrittenByOperationDetector(root);
+ final GitLocalChangesWouldBeOverwrittenDetector localChangesDetector = new GitLocalChangesWouldBeOverwrittenDetector(root, CHECKOUT);
handler.addLineListener(untrackedFilesDetector);
+ handler.addLineListener(localChangesDetector);
GitTask pullTask = new GitTask(project, handler, "Rebasing from upstream/master");
pullTask.setProgressIndicator(indicator);
@@ -271,7 +274,8 @@ public class GithubRebaseAction extends DumbAwareAction {
@Override
protected void onFailure() {
- GitUpdateResult result = rebaser.handleRebaseFailure(handler, root, rebaseConflictDetector, untrackedFilesDetector);
+ GitUpdateResult result = rebaser.handleRebaseFailure(handler, root, rebaseConflictDetector,
+ untrackedFilesDetector, localChangesDetector);
repositoryManager.updateRepository(root);
if (result == GitUpdateResult.NOTHING_TO_UPDATE ||
result == GitUpdateResult.SUCCESS ||
diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/formatter/WrappingTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/formatter/WrappingTest.groovy
index 79d79edc42de..4d585dbf7f6f 100644
--- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/formatter/WrappingTest.groovy
+++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/formatter/WrappingTest.groovy
@@ -16,6 +16,7 @@
package org.jetbrains.plugins.groovy.lang.formatter
import com.intellij.psi.codeStyle.CommonCodeStyleSettings
+import org.jetbrains.plugins.groovy.GroovyLanguage
/**
* @author Max Medvedev
@@ -24,7 +25,7 @@ class WrappingTest extends GroovyFormatterTestCase {
@Override
protected void setUp() throws Exception {
super.setUp()
- myTempSettings.RIGHT_MARGIN = 10
+ myTempSettings.setRightMargin(GroovyLanguage.INSTANCE, 10);
}
void testWrapChainedMethodCalls() {
diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/HgPusher.java b/plugins/hg4idea/src/org/zmlx/hg4idea/HgPusher.java
deleted file mode 100644
index c8e6075649ce..000000000000
--- a/plugins/hg4idea/src/org/zmlx/hg4idea/HgPusher.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright 2000-2011 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 org.zmlx.hg4idea;
-
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.openapi.vcs.VcsNotifier;
-import com.intellij.openapi.vfs.VirtualFile;
-import org.jetbrains.annotations.Nullable;
-import org.zmlx.hg4idea.action.HgCommandResultNotifier;
-import org.zmlx.hg4idea.command.HgPushCommand;
-import org.zmlx.hg4idea.execution.HgCommandResult;
-import org.zmlx.hg4idea.execution.HgCommandResultHandler;
-import org.zmlx.hg4idea.ui.HgPushDialog;
-
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class HgPusher {
-
- private static final Logger LOG = Logger.getInstance(HgPusher.class);
- private static Pattern PUSH_COMMITS_PATTERN = Pattern.compile(".*added (\\d+) changesets.*");
- // hg push command has definite exit values for some cases:
- // mercurial returns 0 if push was successful, 1 if nothing to push. see hg push --help
- private static int PUSH_SUCCEEDED_EXIT_VALUE = 0;
- private static int NOTHING_TO_PUSH_EXIT_VALUE = 1;
-
- private final Project myProject;
-
- public HgPusher(Project project) {
- myProject = project;
- }
-
- /* public void showDialogAndPush(@NotNull List extends Repository> selectedRepositories) {
-
- if (selectedRepositories.isEmpty()) {
- VcsBalloonProblemNotifier.showOverChangesView(myProject, "No Mercurial repositories in the project", MessageType.ERROR);
- return;
- }
- final AtomicReference pushCommand = new AtomicReference();
- final HgPushDialog dialog = new HgPushDialog(myProject, repositories, selectedRepo);
- dialog.show();
- if (d2.isOK()) {
- pushCommand.set(preparePushCommand(myProject, dialog));
- new Task.Backgroundable(myProject, "Pushing...", false) {
- @Override
- public void run(@NotNull ProgressIndicator indicator) {
- if (pushCommand.get() != null) {
- push(myProject, pushCommand.get());
- }
- }
- }.queue();
- }
- }*/
-
- public static void push(final Project project, HgPushCommand command) {
- final VirtualFile repo = command.getRepo();
- command.execute(new HgCommandResultHandler() {
- @Override
- public void process(@Nullable HgCommandResult result) {
- if (result == null) {
- return;
- }
-
- if (result.getExitValue() == PUSH_SUCCEEDED_EXIT_VALUE) {
- int commitsNum = getNumberOfPushedCommits(result);
- String successTitle = "Pushed successfully";
- String successDescription = String.format("Pushed %d %s [%s]", commitsNum, StringUtil.pluralize("commit", commitsNum),
- repo.getPresentableName());
- VcsNotifier.getInstance(project).notifySuccess(successTitle, successDescription);
- }
- else if (result.getExitValue() == NOTHING_TO_PUSH_EXIT_VALUE) {
- VcsNotifier.getInstance(project).notifySuccess("Nothing to push");
- }
- else {
- new HgCommandResultNotifier(project).notifyError(result, "Push failed",
- "Failed to push to [" + repo.getPresentableName() + "]");
- }
- }
- });
- }
-
- private static HgPushCommand preparePushCommand(Project project, HgPushDialog dialog) {
- final HgPushCommand command = new HgPushCommand(project, dialog.getRepository().getRoot(), dialog.getTarget());
- command.setRevision(dialog.getRevision());
- command.setForce(dialog.isForce());
- command.setBranchName(dialog.getBranch());
- command.setBookmarkName(dialog.getBookmarkName());
- command.setIsNewBranch(dialog.isNewBranch());
- return command;
- }
-
- private static int getNumberOfPushedCommits(HgCommandResult result) {
- int numberOfCommitsInAllSubrepos = 0;
- final List outputLines = result.getOutputLines();
- for (String outputLine : outputLines) {
- outputLine = outputLine.trim();
- final Matcher matcher = PUSH_COMMITS_PATTERN.matcher(outputLine);
- if (matcher.matches()) {
- try {
- numberOfCommitsInAllSubrepos += Integer.parseInt(matcher.group(1));
- }
- catch (NumberFormatException e) {
- LOG.error("getNumberOfPushedCommits ", e);
- return -1;
- }
- }
- }
- return numberOfCommitsInAllSubrepos;
- }
-}
diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgOutgoingCommitsProvider.java b/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgOutgoingCommitsProvider.java
index 00977ee31cb0..70055cafe1a1 100644
--- a/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgOutgoingCommitsProvider.java
+++ b/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgOutgoingCommitsProvider.java
@@ -71,7 +71,7 @@ public class HgOutgoingCommitsProvider extends OutgoingCommitsProvider {
if (HgErrorUtil.isAbortLine(error)) {
if (HgErrorUtil.isAuthorizationError(error)) {
VcsError authorizationError =
- new VcsError(error + "" + LOGIN_AND_REFRESH_LINK + "", new VcsErrorHandler() {
+ new VcsError(error + "" + LOGIN_AND_REFRESH_LINK + "", new VcsErrorHandler() {
public void handleError(@NotNull CommitLoader commitLoader) {
commitLoader.reloadCommits();
}
diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgSource.java b/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgPushSource.java
similarity index 68%
rename from plugins/hg4idea/src/org/zmlx/hg4idea/push/HgSource.java
rename to plugins/hg4idea/src/org/zmlx/hg4idea/push/HgPushSource.java
index 292e2df7244c..babd49c9b31d 100644
--- a/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgSource.java
+++ b/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgPushSource.java
@@ -16,16 +16,23 @@
package org.zmlx.hg4idea.push;
import com.intellij.dvcs.push.PushSource;
+import org.jetbrains.annotations.NotNull;
-public class HgSource implements PushSource {
- String mySource;
+public class HgPushSource implements PushSource {
+ @NotNull private String myBranch;
- public HgSource(String branch) {
- mySource = branch;
+ public HgPushSource(@NotNull String branch) {
+ myBranch = branch;
}
+ @NotNull
@Override
public String getPresentation() {
- return mySource;
+ return myBranch;
+ }
+
+ @NotNull
+ public String getBranch() {
+ return myBranch; // presentation may differ from branch
}
}
diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgPushSupport.java b/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgPushSupport.java
index d2a320297c2f..4bfa134a5e26 100644
--- a/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgPushSupport.java
+++ b/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgPushSupport.java
@@ -80,9 +80,9 @@ public class HgPushSupport extends PushSupport {
@NotNull
@Override
- public HgSource getSource(@NotNull HgRepository repository) {
+ public HgPushSource getSource(@NotNull HgRepository repository) {
String localBranch = HgUtil.getActiveBranchName(repository);
- return new HgSource(localBranch);
+ return new HgPushSource(localBranch);
}
@Override
diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgPusher.java b/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgPusher.java
index dcbfa314d03e..f019035a373f 100644
--- a/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgPusher.java
+++ b/plugins/hg4idea/src/org/zmlx/hg4idea/push/HgPusher.java
@@ -19,16 +19,34 @@ import com.intellij.dvcs.push.PushSpec;
import com.intellij.dvcs.push.Pusher;
import com.intellij.dvcs.push.VcsPushOptionValue;
import com.intellij.dvcs.repo.Repository;
+import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vcs.VcsNotifier;
+import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.zmlx.hg4idea.action.HgCommandResultNotifier;
import org.zmlx.hg4idea.command.HgPushCommand;
+import org.zmlx.hg4idea.execution.HgCommandResult;
+import org.zmlx.hg4idea.execution.HgCommandResultHandler;
import org.zmlx.hg4idea.repo.HgRepository;
+import java.util.List;
import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
public class HgPusher extends Pusher {
+ private static final Logger LOG = Logger.getInstance(HgPusher.class);
+ private static final String ONE = "one";
+ private static Pattern PUSH_COMMITS_PATTERN = Pattern.compile(".*(?:added|pushed) (\\d+|" + ONE + ") changeset.*");
+ // hg push command has definite exit values for some cases:
+ // mercurial returns 0 if push was successful, 1 if nothing to push. see hg push --help
+ private static int PUSH_SUCCEEDED_EXIT_VALUE = 0;
+ private static int NOTHING_TO_PUSH_EXIT_VALUE = 1;
+
@Override
public void push(@NotNull Map pushSpecs, @Nullable VcsPushOptionValue vcsPushOptionValue, boolean force) {
for (Map.Entry entry : pushSpecs.entrySet()) {
@@ -39,23 +57,71 @@ public class HgPusher extends Pusher {
if (destination == null) {
continue;
}
- HgSource source = (HgSource)hgSpec.getSource();
+ HgPushSource source = (HgPushSource)hgSpec.getSource();
Project project = repository.getProject();
final HgPushCommand pushCommand = new HgPushCommand(project, repository.getRoot(), destination.myTarget);
pushCommand.setIsNewBranch(true); // set always true, because it just allow mercurial to create a new one if needed
pushCommand.setForce(force);
- if (source.mySource.equals(hgRepository.getCurrentBookmark())) {
+ String branchName = source.getBranch();
+ if (branchName.equals(hgRepository.getCurrentBookmark())) {
if (vcsPushOptionValue == HgVcsPushOptionValue.Current) {
- pushCommand.setBookmarkName(source.mySource);
+ pushCommand.setBookmarkName(branchName);
}
else {
- pushCommand.setRevision(source.mySource);
+ pushCommand.setRevision(branchName);
}
}
else {
- pushCommand.setBranchName(source.mySource);
+ pushCommand.setBranchName(branchName);
}
- org.zmlx.hg4idea.HgPusher.push(project, pushCommand);
+ push(project, pushCommand);
}
}
+
+ public static void push(@NotNull final Project project, @NotNull HgPushCommand command) {
+ final VirtualFile repo = command.getRepo();
+ command.execute(new HgCommandResultHandler() {
+ @Override
+ public void process(@Nullable HgCommandResult result) {
+ if (result == null) {
+ return;
+ }
+
+ if (result.getExitValue() == PUSH_SUCCEEDED_EXIT_VALUE) {
+ int commitsNum = getNumberOfPushedCommits(result);
+ String successTitle = "Pushed successfully";
+ String successDescription = String.format("Pushed %d %s [%s]", commitsNum, StringUtil.pluralize("commit", commitsNum),
+ repo.getPresentableName());
+ VcsNotifier.getInstance(project).notifySuccess(successTitle, successDescription);
+ }
+ else if (result.getExitValue() == NOTHING_TO_PUSH_EXIT_VALUE) {
+ VcsNotifier.getInstance(project).notifySuccess("Nothing to push");
+ }
+ else {
+ new HgCommandResultNotifier(project).notifyError(result, "Push failed",
+ "Failed to push to [" + repo.getPresentableName() + "]");
+ }
+ }
+ });
+ }
+
+ private static int getNumberOfPushedCommits(@NotNull HgCommandResult result) {
+ int numberOfCommitsInAllSubrepos = 0;
+ final List outputLines = result.getOutputLines();
+ for (String outputLine : outputLines) {
+ outputLine = outputLine.trim();
+ final Matcher matcher = PUSH_COMMITS_PATTERN.matcher(outputLine);
+ if (matcher.matches()) {
+ try {
+ String numberOfCommits = matcher.group(1);
+ numberOfCommitsInAllSubrepos += ONE.equals(numberOfCommits) ? 1 : Integer.parseInt(numberOfCommits);
+ }
+ catch (NumberFormatException e) {
+ LOG.error("getNumberOfPushedCommits ", e);
+ return -1;
+ }
+ }
+ }
+ return numberOfCommitsInAllSubrepos;
+ }
}
diff --git a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/sceneBuilder/SceneBuilderEditor.java b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/sceneBuilder/SceneBuilderEditor.java
index f0c1eccd86f1..a0a90efe9c74 100644
--- a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/sceneBuilder/SceneBuilderEditor.java
+++ b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/sceneBuilder/SceneBuilderEditor.java
@@ -11,11 +11,13 @@ import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.fileEditor.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
-import com.intellij.openapi.ui.VerticalFlowLayout;
import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.openapi.vfs.ReadonlyStatusHandler;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.HyperlinkLabel;
+import com.intellij.ui.ScrollPaneFactory;
+import com.intellij.util.ExceptionUtil;
+import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -42,8 +44,10 @@ public class SceneBuilderEditor extends UserDataHolderBase implements FileEditor
private final CardLayout myLayout = new CardLayout();
private final JPanel myPanel = new JPanel(myLayout);
- private final JPanel myErrorPanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 10, 5, true, false));
+ //private final JPanel myErrorPanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 10, 5, true, false));
+ private final JPanel myErrorPanel = new JPanel(new BorderLayout());
private final HyperlinkLabel myErrorLabel = new HyperlinkLabel();
+ private JTextArea myErrorStack;
private final Document myDocument;
private final ExternalChangeListener myChangeListener;
@@ -72,11 +76,19 @@ public class SceneBuilderEditor extends UserDataHolderBase implements FileEditor
}
});
- myErrorPanel.add(myErrorLabel);
+ myErrorStack = new JTextArea(50, 20);
+ myErrorStack.setEditable(false);
+
+ myErrorPanel.add(myErrorLabel, BorderLayout.NORTH);
+ myErrorPanel.add(ScrollPaneFactory.createScrollPane(myErrorStack), BorderLayout.CENTER);
myPanel.add(myErrorPanel);
}
private void showErrorPage(State state, Throwable e) {
+ if (e != null) {
+ LOG.info(e);
+ }
+
removeSceneBuilder();
if (e == null) {
@@ -93,12 +105,21 @@ public class SceneBuilderEditor extends UserDataHolderBase implements FileEditor
}
myErrorLabel.setIcon(Messages.getWarningIcon());
}
+
+ myErrorStack.setText(null);
+ myErrorStack.setVisible(false);
}
else {
- myErrorLabel.setHyperlinkText("Error: " + e.getMessage(), "", "");
+ String message = e.getMessage();
+ if (message == null) {
+ message = e.getClass().getName();
+ }
+
+ myErrorLabel.setHyperlinkText("Error: " + message, "", "");
myErrorLabel.setIcon(Messages.getErrorIcon());
- LOG.info(e);
+ myErrorStack.setText(ExceptionUtil.getThrowableText(e));
+ myErrorStack.setVisible(true);
}
myLayout.show(myPanel, ERROR_CARD);
}
@@ -139,8 +160,12 @@ public class SceneBuilderEditor extends UserDataHolderBase implements FileEditor
}
@Override
- public void handleError(Throwable e) {
- showErrorPage(null, e);
+ public void handleError(final Throwable e) {
+ UIUtil.invokeLaterIfNeeded(new Runnable() {
+ public void run() {
+ showErrorPage(null, e);
+ }
+ });
}
private void initSceneBuilder(boolean choosePathIfEmpty) {
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyCheckAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyCheckAction.java
index ad3df265ccda..f8e10c9c4521 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyCheckAction.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyCheckAction.java
@@ -11,6 +11,8 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.fileEditor.FileEditor;
+import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
@@ -33,7 +35,8 @@ import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.io.*;
-import java.util.Map;
+import java.util.*;
+import java.util.List;
public class StudyCheckAction extends DumbAwareAction {
@@ -111,13 +114,26 @@ public class StudyCheckAction extends DumbAwareAction {
if (openedFile != null) {
StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
final TaskFile selectedTaskFile = taskManager.getTaskFile(openedFile);
+ List filesToDelete = new ArrayList();
if (selectedTaskFile != null) {
- VirtualFile windowsDescription = StudyUtils.flushWindows(selectedEditor.getDocument(), selectedTaskFile, openedFile);
- FileDocumentManager.getInstance().saveAllDocuments();
final VirtualFile taskDir = openedFile.getParent();
Task currentTask = selectedTaskFile.getTask();
+ StudyStatus oldStatus = currentTask.getStatus();
+ Map taskFiles = selectedTaskFile.getTask().getTaskFiles();
+ for (Map.Entry entry : taskFiles.entrySet()) {
+ String name = entry.getKey();
+ TaskFile taskFile = entry.getValue();
+ VirtualFile virtualFile = taskDir.findChild(name);
+ if (virtualFile == null) {
+ continue;
+ }
+ VirtualFile windowFile = StudyUtils.flushWindows(FileDocumentManager.getInstance().getDocument(virtualFile), taskFile, virtualFile);
+ filesToDelete.add(windowFile);
+ FileDocumentManager.getInstance().saveAllDocuments();
+ }
+
StudyRunAction runAction = (StudyRunAction)ActionManager.getInstance().getAction(StudyRunAction.ACTION_ID);
- if (runAction != null) {
+ if (runAction != null && currentTask.getTaskFiles().size() == 1) {
runAction.run(project);
}
final StudyTestRunner testRunner = new StudyTestRunner(currentTask, taskDir);
@@ -131,36 +147,68 @@ public class StudyCheckAction extends DumbAwareAction {
if (testProcess != null) {
String failedMessage = testRunner.getPassedTests(testProcess);
if (failedMessage.equals(StudyTestRunner.TEST_OK)) {
- currentTask.setStatus(StudyStatus.Solved);
+ currentTask.setStatus(StudyStatus.Solved, oldStatus);
StudyUtils.updateStudyToolWindow(project);
selectedTaskFile.drawAllWindows(selectedEditor);
ProjectView.getInstance(project).refresh();
+ for (VirtualFile file:filesToDelete) {
+ try {
+ file.delete(this);
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
createTestResultPopUp("Congratulations!", JBColor.GREEN, project);
return;
}
-
- final TaskFile taskFileCopy = new TaskFile();
- final VirtualFile copyWithAnswers = getCopyWithAnswers(taskDir, openedFile, selectedTaskFile, taskFileCopy);
- for (final TaskWindow taskWindow : taskFileCopy.getTaskWindows()) {
- if (!taskWindow.isValid(selectedEditor.getDocument())) {
+ for (Map.Entry entry : taskFiles.entrySet()) {
+ String name = entry.getKey();
+ TaskFile taskFile = entry.getValue();
+ TaskFile answerTaskFile = new TaskFile();
+ VirtualFile virtualFile = taskDir.findChild(name);
+ if (virtualFile == null) {
continue;
}
- check(project, taskWindow, copyWithAnswers, taskFileCopy, selectedTaskFile, selectedEditor.getDocument(), testRunner,
- openedFile);
+ VirtualFile answerFile = getCopyWithAnswers(taskDir, virtualFile, taskFile, answerTaskFile);
+ for (TaskWindow taskWindow : answerTaskFile.getTaskWindows()) {
+ Document document = FileDocumentManager.getInstance().getDocument(virtualFile);
+ if (document == null) {
+ continue;
+ }
+ if (!taskWindow.isValid(document)) {
+ continue;
+ }
+ check(project, taskWindow, answerFile, answerTaskFile, taskFile, document, testRunner, virtualFile);
+ }
+ FileEditor fileEditor = FileEditorManager.getInstance(project).getSelectedEditor(virtualFile);
+ Editor editor = null;
+ if (fileEditor instanceof StudyEditor) {
+ StudyEditor studyEditor = (StudyEditor) fileEditor;
+ editor = studyEditor.getEditor();
+ }
+
+ if (editor != null) {
+ taskFile.drawAllWindows(editor);
+ StudyUtils.synchronize();
+ }
+ try {
+ answerFile.delete(this);
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
}
- try {
- copyWithAnswers.delete(this);
+ for (VirtualFile file:filesToDelete) {
+ try {
+ file.delete(this);
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
}
- catch (IOException e) {
- LOG.error(e);
- }
- try {
- windowsDescription.delete(this);
- }
- catch (IOException e) {
- LOG.error("failed to delete windows description", e);
- }
- selectedTaskFile.drawAllWindows(selectedEditor);
+ currentTask.setStatus(StudyStatus.Failed, oldStatus);
+ StudyUtils.updateStudyToolWindow(project);
createTestResultPopUp(failedMessage, JBColor.RED, project);
}
}
@@ -183,7 +231,7 @@ public class StudyCheckAction extends DumbAwareAction {
VirtualFile openedFile) {
try {
- VirtualFile windowCopy = answerFile.copy(this, answerFile.getParent(), "window" + taskWindow.getIndex() + ".py");
+ VirtualFile windowCopy = answerFile.copy(this, answerFile.getParent(), answerFile.getNameWithoutExtension() + "_window" + taskWindow.getIndex() + ".py");
final FileDocumentManager documentManager = FileDocumentManager.getInstance();
final Document windowDocument = documentManager.getDocument(windowCopy);
if (windowDocument != null) {
@@ -216,7 +264,7 @@ public class StudyCheckAction extends DumbAwareAction {
VirtualFile fileWindows = StudyUtils.flushWindows(windowDocument, windowTaskFile, windowCopy);
Process smartTestProcess = testRunner.launchTests(project, windowCopy.getPath());
boolean res = testRunner.getPassedTests(smartTestProcess).equals(StudyTestRunner.TEST_OK);
- userTaskWindow.setStatus(res ? StudyStatus.Solved : StudyStatus.Failed);
+ userTaskWindow.setStatus(res ? StudyStatus.Solved : StudyStatus.Failed, StudyStatus.Unchecked);
windowCopy.delete(this);
fileWindows.delete(this);
if (!resourceFile.delete()) {
@@ -240,7 +288,7 @@ public class StudyCheckAction extends DumbAwareAction {
VirtualFile copy = null;
try {
- copy = file.copy(this, taskDir, "answers.py");
+ copy = file.copy(this, taskDir, file.getNameWithoutExtension() +"_answers.py");
final FileDocumentManager documentManager = FileDocumentManager.getInstance();
final Document document = documentManager.getDocument(copy);
if (document != null) {
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskAction.java
index b245b46f90ef..f8abb0b63365 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskAction.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskAction.java
@@ -81,13 +81,8 @@ public class StudyRefreshTaskAction extends DumbAwareAction {
document.setText(patternText);
StudyStatus oldStatus = currentTask.getStatus();
LessonInfo lessonInfo = currentTask.getLesson().getLessonInfo();
- if (oldStatus == StudyStatus.Failed) {
- lessonInfo.setTaskFailed(lessonInfo.getTaskFailed() - 1);
- }
- if (oldStatus == StudyStatus.Solved) {
- lessonInfo.setTaskSolved(lessonInfo.getTaskSolved() - 1);
- }
- lessonInfo.setTaskUnchecked(lessonInfo.getTaskUnchecked() + 1);
+ lessonInfo.update(oldStatus, -1);
+ lessonInfo.update(StudyStatus.Unchecked, +1);
StudyUtils.updateStudyToolWindow(project);
for (TaskWindow taskWindow : selectedTaskFile.getTaskWindows()) {
taskWindow.reset();
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java
index 2ce200bfc641..b781e7da8849 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java
@@ -67,12 +67,21 @@ abstract public class StudyTaskNavigationAction extends DumbAwareAction {
if (taskDir == null) {
return;
}
- for (String name : nextTaskFiles.keySet()) {
+ VirtualFile shouldBeActive = null;
+ for (Map.Entry entry : nextTaskFiles.entrySet()) {
+ String name = entry.getKey();
+ TaskFile taskFile = entry.getValue();
VirtualFile vf = taskDir.findChild(name);
if (vf != null) {
FileEditorManager.getInstance(project).openFile(vf, true);
+ if (!taskFile.getTaskWindows().isEmpty()) {
+ shouldBeActive = vf;
+ }
}
}
+ if (shouldBeActive != null) {
+ FileEditorManager.getInstance(project).openFile(shouldBeActive, true);
+ }
}
protected abstract JButton getButton(StudyEditor selectedStudyEditor);
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/Lesson.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/Lesson.java
index 84396ea404d4..3879d519957e 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/course/Lesson.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/Lesson.java
@@ -33,9 +33,9 @@ public class Lesson implements Stateful{
}
@Override
- public void setStatus(StudyStatus status) {
+ public void setStatus(StudyStatus status, StudyStatus oldStatus) {
for (Task task : taskList) {
- task.setStatus(status);
+ task.setStatus(status, oldStatus);
}
}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/LessonInfo.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/LessonInfo.java
index 9431632c2edd..85e2eb8be1a9 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/course/LessonInfo.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/LessonInfo.java
@@ -40,4 +40,21 @@ public class LessonInfo {
public void setTaskUnchecked(int taskUnchecked) {
myTaskUnchecked = taskUnchecked;
}
+
+ public void update(StudyStatus status, int delta) {
+ switch (status) {
+ case Solved: {
+ myTaskSolved += delta;
+ break;
+ }
+ case Failed: {
+ myTaskFailed += delta;
+ break;
+ }
+ case Unchecked: {
+ myTaskUnchecked += delta;
+ break;
+ }
+ }
+ }
}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/Stateful.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/Stateful.java
index 10374bd94d9a..3a163622f56d 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/course/Stateful.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/Stateful.java
@@ -2,5 +2,5 @@ package com.jetbrains.python.edu.course;
public interface Stateful {
StudyStatus getStatus();
- void setStatus(StudyStatus status);
+ void setStatus(StudyStatus status, StudyStatus oldStatus);
}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/Task.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/Task.java
index a493b0ebfe8e..2323412f4374 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/course/Task.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/Task.java
@@ -55,21 +55,14 @@ public class Task implements Stateful{
this.name = name;
}
- public void setStatus(@NotNull final StudyStatus status) {
+ public void setStatus(@NotNull final StudyStatus status, @NotNull final StudyStatus oldStatus) {
LessonInfo lessonInfo = myLesson.getLessonInfo();
- StudyStatus oldStatus = getStatus();
if (status != oldStatus) {
- if (status == StudyStatus.Failed) {
- lessonInfo.setTaskFailed(lessonInfo.getTaskFailed() + 1);
- lessonInfo.setTaskUnchecked(lessonInfo.getTaskUnchecked() - 1);
- }
- if (status == StudyStatus.Solved) {
- lessonInfo.setTaskSolved(lessonInfo.getTaskSolved() + 1);
- lessonInfo.setTaskUnchecked(lessonInfo.getTaskUnchecked() - 1);
- }
- for (TaskFile taskFile : taskFiles.values()) {
- taskFile.setStatus(status);
- }
+ lessonInfo.update(oldStatus, -1);
+ lessonInfo.update(status, +1);
+ }
+ for (TaskFile taskFile : taskFiles.values()) {
+ taskFile.setStatus(status, oldStatus);
}
}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskFile.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskFile.java
index 06d5736bd81a..4f17fc0d27f3 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskFile.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskFile.java
@@ -200,9 +200,9 @@ public class TaskFile implements Stateful{
this.taskWindows = taskWindows;
}
- public void setStatus(@NotNull final StudyStatus status) {
+ public void setStatus(@NotNull final StudyStatus status, @NotNull final StudyStatus oldStatus) {
for (TaskWindow taskWindow : taskWindows) {
- taskWindow.setStatus(status);
+ taskWindow.setStatus(status, oldStatus);
}
}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskWindow.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskWindow.java
index 04e65c3abb59..6a5a01c7b9c8 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskWindow.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskWindow.java
@@ -34,7 +34,7 @@ public class TaskWindow implements Comparable, Stateful {
return myStatus;
}
- public void setStatus(StudyStatus status) {
+ public void setStatus(StudyStatus status, StudyStatus oldStatus) {
myStatus = status;
}
diff --git a/xml/relaxng/src/resources/html5-schema/html5-svg-mathml.rnc b/xml/relaxng/src/resources/html5-schema/html5-svg-mathml.rnc
index fadc971d2bb4..fe154b8f57bf 100644
--- a/xml/relaxng/src/resources/html5-schema/html5-svg-mathml.rnc
+++ b/xml/relaxng/src/resources/html5-schema/html5-svg-mathml.rnc
@@ -10,8 +10,6 @@ common.elem.phrasing |= math
SVG.foreignObject.content |=
( math
- | html.elem
- | body.elem
| common.inner.flow
)
diff --git a/xml/relaxng/src/resources/html5-schema/html5/applications.rnc b/xml/relaxng/src/resources/html5-schema/html5/applications.rnc
index ac07294db29c..2a592fbbd781 100755
--- a/xml/relaxng/src/resources/html5-schema/html5/applications.rnc
+++ b/xml/relaxng/src/resources/html5-schema/html5/applications.rnc
@@ -377,6 +377,7 @@ datatypes w = "http://whattf.org/datatype-draft"
| common.attrs.aria.role.combobox
| common.attrs.aria.role.dialog
| common.attrs.aria.role.directory
+ | common.attrs.aria.role.group
| common.attrs.aria.role.heading
| common.attrs.aria.role.img
| common.attrs.aria.role.link
@@ -425,7 +426,16 @@ datatypes w = "http://whattf.org/datatype-draft"
( common.attrs
& ( common.attrs.aria.role.presentation
| common.attrs.aria.role.menuitem
+ | common.attrs.aria.role.button
)?
)
summary.inner =
- ( common.inner.phrasing )
+ ( common.inner.phrasing
+ | h1.elem
+ | h2.elem
+ | h3.elem
+ | h4.elem
+ | h5.elem
+ | h6.elem
+ | hgroup.elem
+ )
diff --git a/xml/relaxng/src/resources/html5-schema/html5/aria.rnc b/xml/relaxng/src/resources/html5-schema/html5/aria.rnc
index b1035a0fa2a6..cf412e5f4ace 100755
--- a/xml/relaxng/src/resources/html5-schema/html5/aria.rnc
+++ b/xml/relaxng/src/resources/html5-schema/html5/aria.rnc
@@ -133,15 +133,7 @@ common.attrs.aria.implicit.toolbar |=
& aria.prop.activedescendant?
)
-common.attrs.aria.implicit.columnheader |=
- ( aria.prop.sort?
- & aria.prop.readonly?
- & aria.prop.required?
- & aria.state.selected?
- & aria.state.expanded?
- )
-
-common.attrs.aria.implicit.rowheader |=
+common.attrs.aria.implicit.column-or-row-header |=
( aria.prop.sort?
& aria.prop.readonly?
& aria.prop.required?
diff --git a/xml/relaxng/src/resources/html5-schema/html5/block.rnc b/xml/relaxng/src/resources/html5-schema/html5/block.rnc
index 2fd2d233c71b..de3b5a54ee2d 100755
--- a/xml/relaxng/src/resources/html5-schema/html5/block.rnc
+++ b/xml/relaxng/src/resources/html5-schema/html5/block.rnc
@@ -112,6 +112,7 @@ datatypes w = "http://whattf.org/datatype-draft"
& ol.attrs.reversed?
& ol.attrs.type?
& ( ( common.attrs.aria.role.directory
+ | common.attrs.aria.role.group
| common.attrs.aria.role.list
| common.attrs.aria.role.listbox
| common.attrs.aria.role.menu
diff --git a/xml/relaxng/src/resources/html5-schema/html5/common.rnc b/xml/relaxng/src/resources/html5-schema/html5/common.rnc
index 9f1e65673bd2..09afb1454233 100755
--- a/xml/relaxng/src/resources/html5-schema/html5/common.rnc
+++ b/xml/relaxng/src/resources/html5-schema/html5/common.rnc
@@ -113,9 +113,7 @@ common.attrs =
)
common.attrs.basic =
- ( ( common.attrs.id
- | common.attrs.xml-id
- )? # REVISIT assuming only either one is allowed
+ ( common.attrs.id?
& common.attrs.class?
& common.attrs.title?
& common.attrs.base?
@@ -125,10 +123,6 @@ common.attrs.basic =
attribute id {
common.data.id
}
- common.attrs.xml-id =
- attribute xml:id {
- xsd:NCName
- } & XMLonly
common.attrs.class =
attribute class {
common.data.tokens
@@ -387,6 +381,10 @@ common.attrs.other =
common.data.keylabellist =
w:keylabellist
+## List of Source Sizes
+ common.data.source.size.list =
+ w:source-size-list
+
## Microdata Properties
common.data.microdata-properties =
list { w:microdata-property+ }
@@ -466,7 +464,7 @@ common.attrs.aria.implicit.article = ( notAllowed )
common.attrs.aria.implicit.banner = ( notAllowed )
common.attrs.aria.implicit.button = ( notAllowed )
common.attrs.aria.implicit.checkbox = ( notAllowed )
-common.attrs.aria.implicit.columnheader = ( notAllowed )
+common.attrs.aria.implicit.column-or-row-header = ( notAllowed )
common.attrs.aria.implicit.combobox = ( notAllowed )
common.attrs.aria.implicit.complementary = ( notAllowed )
common.attrs.aria.implicit.contentinfo = ( notAllowed )
@@ -486,7 +484,6 @@ common.attrs.aria.implicit.option = ( notAllowed )
common.attrs.aria.implicit.progressbar = ( notAllowed )
common.attrs.aria.implicit.radio = ( notAllowed )
common.attrs.aria.implicit.region = ( notAllowed )
-common.attrs.aria.implicit.rowheader = ( notAllowed )
common.attrs.aria.implicit.section = ( notAllowed )
common.attrs.aria.implicit.select = ( notAllowed )
common.attrs.aria.implicit.slider = ( notAllowed )
diff --git a/xml/relaxng/src/resources/html5-schema/html5/core-scripting.rnc b/xml/relaxng/src/resources/html5-schema/html5/core-scripting.rnc
index bef6bf107423..eb08c2ed5bb7 100755
--- a/xml/relaxng/src/resources/html5-schema/html5/core-scripting.rnc
+++ b/xml/relaxng/src/resources/html5-schema/html5/core-scripting.rnc
@@ -15,6 +15,7 @@ datatypes w = "http://whattf.org/datatype-draft"
( common.attrs
& script.attrs.type?
& script.attrs.language? # restricted in Schematron
+ & embedded.content.attrs.crossorigin?
& ( common.attrs.aria.role.presentation
| common.attrs.aria.role.menuitem
)?
@@ -29,6 +30,7 @@ datatypes w = "http://whattf.org/datatype-draft"
& script.attrs.type?
& script.attrs.charset?
& script.attrs.language? # restricted in Schematron
+ & embedded.content.attrs.crossorigin?
& ( common.attrs.aria.role.presentation
| common.attrs.aria.role.menuitem
)?
diff --git a/xml/relaxng/src/resources/html5-schema/html5/embed.rnc b/xml/relaxng/src/resources/html5-schema/html5/embed.rnc
index 51a2f0acb7a6..be4204914fa8 100755
--- a/xml/relaxng/src/resources/html5-schema/html5/embed.rnc
+++ b/xml/relaxng/src/resources/html5-schema/html5/embed.rnc
@@ -15,12 +15,15 @@ namespace local = ""
img.attrs =
( common.attrs
& img.attrs.src
+ & img.attrs.srcset?
+ & img.attrs.sizes?
& img.attrs.alt? # ARIA: if alt empty, only allowed role value is "presentation"; check in assertions
& img.attrs.height?
& img.attrs.width?
& img.attrs.usemap?
& img.attrs.ismap?
& img.attrs.border? # obsolete
+ & embedded.content.attrs.crossorigin?
& ( common.attrs.aria.implicit.img
| common.attrs.aria
)?
@@ -29,6 +32,14 @@ namespace local = ""
attribute src {
common.data.uri.non-empty
}
+ img.attrs.srcset =
+ attribute srcset {
+ string
+ } & v5only
+ img.attrs.sizes =
+ attribute sizes {
+ common.data.source.size.list
+ } & v5only
img.attrs.alt =
attribute alt {
text
@@ -58,6 +69,54 @@ namespace local = ""
common.elem.phrasing |= img.elem
+## Image with multiple sources:
+
+ picture.elem =
+ element picture { picture.inner & picture.attrs }
+ & v5only
+ picture.attrs =
+ ( common.attrs )
+ picture.inner =
+ ( ( source.picture.elem*
+ & common.elem.script-supporting*
+ ),
+ ( img.elem
+ & common.elem.script-supporting*
+ )
+ )
+
+ common.elem.phrasing |= picture.elem
+
+## Picture source:
+
+ source.picture.elem =
+ element source { source.picture.inner & source.picture.attrs }
+ source.picture.attrs =
+ ( common.attrs
+ & source.picture.attrs.media?
+ & source.picture.attrs.srcset
+ & source.picture.attrs.sizes?
+ & source.picture.attrs.type?
+ )
+ source.picture.attrs.media =
+ attribute media {
+ common.data.mediaquery
+ }
+ source.picture.attrs.srcset =
+ attribute srcset {
+ string
+ }
+ source.picture.attrs.sizes =
+ attribute sizes {
+ common.data.source.size.list
+ }
+ source.picture.attrs.type =
+ attribute type {
+ common.data.mimetype
+ }
+ source.picture.inner =
+ ( empty )
+
## Plug-ins: