diff --git a/.idea/artifacts/standalone_jps.xml b/.idea/artifacts/standalone_jps.xml index 8f26221c67fd..21767ac83bb4 100644 --- a/.idea/artifacts/standalone_jps.xml +++ b/.idea/artifacts/standalone_jps.xml @@ -40,6 +40,9 @@ + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 9121e0651434..7d75daf3b055 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -73,7 +73,6 @@ - @@ -118,6 +117,7 @@ + @@ -257,7 +257,7 @@ - + diff --git a/build/conf/nsis/DeleteSettings.ini b/build/conf/nsis/DeleteSettings.ini index ba0bbc2a681d..69b4d2cbbe2b 100644 --- a/build/conf/nsis/DeleteSettings.ini +++ b/build/conf/nsis/DeleteSettings.ini @@ -40,7 +40,7 @@ State=0 [Field 6] Type=checkbox -Text=Uninstall Feedback +Text=Share the uninstall feedback Left=5 Right=-1 Top=95 diff --git a/build/conf/nsis/customInstallActions.nsi b/build/conf/nsis/customInstallActions.nsi index 72db97d6db3c..56277ba6e209 100644 --- a/build/conf/nsis/customInstallActions.nsi +++ b/build/conf/nsis/customInstallActions.nsi @@ -6,4 +6,12 @@ FunctionEnd Function customInstallActions DetailPrint "There are no custom install actions." +FunctionEnd + +Function customPostInstallActions + DetailPrint "There are no custom post-install actions." +FunctionEnd + +Function un.customUninstallActions + DetailPrint "There are no custom uninstall actions." FunctionEnd \ No newline at end of file diff --git a/build/conf/nsis/idea.nsi b/build/conf/nsis/idea.nsi index 0483b0d52b3c..8ea34892ef86 100644 --- a/build/conf/nsis/idea.nsi +++ b/build/conf/nsis/idea.nsi @@ -872,6 +872,7 @@ skip_ipr: File "${PRODUCT_PROPERTIES_FILE}" File "${PRODUCT_VM_OPTIONS_FILE}" + Call customPostInstallActions StrCpy $0 $baseRegKey StrCpy $1 "Software\${MANUFACTURER}\${PRODUCT_REG_VER}" StrCpy $2 "" @@ -1143,6 +1144,7 @@ FunctionEnd Section "Uninstall" + Call un.customUninstallActions StrCpy $0 $baseRegKey StrCpy $1 "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_WITH_VER}" StrCpy $2 "InstallLocation" diff --git a/build/dependencies/gradle.properties b/build/dependencies/gradle.properties index 6bca365e2dd0..8c2a0ca55b7a 100644 --- a/build/dependencies/gradle.properties +++ b/build/dependencies/gradle.properties @@ -1,4 +1,4 @@ # The file might be automatically updated. Comments and empty lines will be removed. -kotlinPluginBuild=1.1.3-release-IJ2017.2-2 +kotlinPluginBuild=1.1.4-eap-54-IJ2017.2-1:EAP-1.1 jetSignBuild=42.30 -jdkBuild=u152b941.1 +jdkBuild=u152b956.1 diff --git a/build/gant.xml b/build/gant.xml index fe1fb017fc4a..facd93371f4d 100644 --- a/build/gant.xml +++ b/build/gant.xml @@ -38,6 +38,15 @@ + + + + + + + + + diff --git a/build/groovy/org/jetbrains/intellij/build/IdeaCommunityProperties.groovy b/build/groovy/org/jetbrains/intellij/build/IdeaCommunityProperties.groovy index 8b495f85c6fd..f2348fdb8dbd 100644 --- a/build/groovy/org/jetbrains/intellij/build/IdeaCommunityProperties.groovy +++ b/build/groovy/org/jetbrains/intellij/build/IdeaCommunityProperties.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -89,6 +89,10 @@ class IdeaCommunityProperties extends BaseIdeaProperties { return new LinuxDistributionCustomizer() { { iconPngPath = "$projectHome/platform/icons/src/icon_CE_128.png" + //snapName = "intellij-idea-community" + //snapDescription = + // "The most intelligent Java IDE. Every aspect of IntelliJ IDEA is specifically designed to maximize developer productivity. " + + // "Together, powerful static code analysis and ergonomic design make development not only productive but also an enjoyable experience." } @Override diff --git a/build/lib/jps/aether-dependency-resolver.jar b/build/lib/jps/aether-dependency-resolver.jar new file mode 100644 index 000000000000..61c5bd72cc2b Binary files /dev/null and b/build/lib/jps/aether-dependency-resolver.jar differ diff --git a/build/lib/jps/antlayout.jar b/build/lib/jps/antlayout.jar index 219fc27401c5..fdaeabdc0eea 100644 Binary files a/build/lib/jps/antlayout.jar and b/build/lib/jps/antlayout.jar differ diff --git a/build/lib/jps/groovy-jps-plugin.jar b/build/lib/jps/groovy-jps-plugin.jar index f087ea57cae1..6bd6ae707006 100644 Binary files a/build/lib/jps/groovy-jps-plugin.jar and b/build/lib/jps/groovy-jps-plugin.jar differ diff --git a/build/lib/jps/groovy-rt-constants.jar b/build/lib/jps/groovy-rt-constants.jar index 2d741ba52760..6a9097f45015 100644 Binary files a/build/lib/jps/groovy-rt-constants.jar and b/build/lib/jps/groovy-rt-constants.jar differ diff --git a/build/lib/jps/groovy_rt.jar b/build/lib/jps/groovy_rt.jar index 43b6e1267702..2bb0bcd46c5e 100644 Binary files a/build/lib/jps/groovy_rt.jar and b/build/lib/jps/groovy_rt.jar differ diff --git a/build/lib/jps/jps-builders-6.jar b/build/lib/jps/jps-builders-6.jar new file mode 100644 index 000000000000..fe33464a39d2 Binary files /dev/null and b/build/lib/jps/jps-builders-6.jar differ diff --git a/build/lib/jps/jps-builders.jar b/build/lib/jps/jps-builders.jar index a5d3b070f41b..34773708ff8d 100644 Binary files a/build/lib/jps/jps-builders.jar and b/build/lib/jps/jps-builders.jar differ diff --git a/build/lib/jps/jps-model.jar b/build/lib/jps/jps-model.jar index bc8647e55a3d..1f2de7d25fbd 100644 Binary files a/build/lib/jps/jps-model.jar and b/build/lib/jps/jps-model.jar differ diff --git a/build/lib/jps/ui-designer-jps-plugin.jar b/build/lib/jps/ui-designer-jps-plugin.jar index cc18c0198844..0af06ba9ffc4 100644 Binary files a/build/lib/jps/ui-designer-jps-plugin.jar and b/build/lib/jps/ui-designer-jps-plugin.jar differ diff --git a/build/lib/jps/util.jar b/build/lib/jps/util.jar index 92482174629f..ccc3470fd0b0 100644 Binary files a/build/lib/jps/util.jar and b/build/lib/jps/util.jar differ diff --git a/build/snap/idea-snap.properties b/build/snap/idea-snap.properties new file mode 100644 index 000000000000..2afae5831d38 --- /dev/null +++ b/build/snap/idea-snap.properties @@ -0,0 +1,6 @@ + +#----------------------------------------------------------------------- +# Snap-specific properties +#----------------------------------------------------------------------- +idea.skip.desktop.entry.step=true +ide.no.platform.update=Snap diff --git a/build/snap/snapcraft-template.yaml b/build/snap/snapcraft-template.yaml new file mode 100644 index 000000000000..9b18cc86b37d --- /dev/null +++ b/build/snap/snapcraft-template.yaml @@ -0,0 +1,25 @@ +# see platform/build-scripts/.../LinuxDistributionBuilder.groovy + +name: $NAME$ +version: '$VERSION$' +icon: $NAME$.png +summary: $SUMMARY$ +description: $DESCRIPTION$ +grade: stable +confinement: classic +architectures: [amd64] + +apps: + $NAME$: + command: $SCRIPT$ + +parts: + common: + plugin: dump + source: dist.all + bins: + plugin: dump + source: dist.unix + jre: + plugin: dump + source: jre \ No newline at end of file diff --git a/community-main.iml b/community-main.iml index d707808b289d..c7114369d60e 100644 --- a/community-main.iml +++ b/community-main.iml @@ -111,7 +111,6 @@ - @@ -125,6 +124,6 @@ - + \ No newline at end of file diff --git a/images/src/org/intellij/images/editor/impl/ImageEditorUI.java b/images/src/org/intellij/images/editor/impl/ImageEditorUI.java index 75feeba250d1..aef68cc14e8c 100644 --- a/images/src/org/intellij/images/editor/impl/ImageEditorUI.java +++ b/images/src/org/intellij/images/editor/impl/ImageEditorUI.java @@ -29,17 +29,17 @@ import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.PopupHandler; import com.intellij.ui.ScrollPaneFactory; import com.intellij.ui.components.JBLayeredPane; import com.intellij.ui.components.Magnificator; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import org.intellij.images.ImagesBundle; import org.intellij.images.editor.ImageDocument; +import org.intellij.images.editor.ImageDocument.ScaledImageProvider; import org.intellij.images.editor.ImageEditor; import org.intellij.images.editor.ImageZoomModel; -import org.intellij.images.editor.ImageDocument.ScaledImageProvider; import org.intellij.images.editor.actionSystem.ImageEditorActions; import org.intellij.images.options.*; import org.intellij.images.thumbnail.actionSystem.ThumbnailViewActions; @@ -176,7 +176,7 @@ final class ImageEditorUI extends JPanel implements DataProvider, CopyProvider, JPanel topPanel = new JPanel(new BorderLayout()); topPanel.add(toolbarPanel, BorderLayout.WEST); infoLabel = new JLabel((String)null, SwingConstants.RIGHT); - infoLabel.setBorder(IdeBorderFactory.createEmptyBorder(0, 0, 0, 2)); + infoLabel.setBorder(JBUI.Borders.emptyRight(2)); topPanel.add(infoLabel, BorderLayout.EAST); add(topPanel, BorderLayout.NORTH); diff --git a/images/src/org/intellij/images/statistics/BackgroundImagesUsageCollector.java b/images/src/org/intellij/images/statistics/BackgroundImagesUsageCollector.java index 27e02287320c..10c1274a1573 100644 --- a/images/src/org/intellij/images/statistics/BackgroundImagesUsageCollector.java +++ b/images/src/org/intellij/images/statistics/BackgroundImagesUsageCollector.java @@ -15,7 +15,7 @@ */ package org.intellij.images.statistics; -import com.intellij.internal.statistic.AbstractApplicationUsagesCollector; +import com.intellij.internal.statistic.AbstractProjectsUsagesCollector; import com.intellij.internal.statistic.CollectUsagesException; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; @@ -32,7 +32,7 @@ import static com.intellij.openapi.wm.impl.IdeBackgroundUtil.*; /** * @author gregsh */ -public class BackgroundImagesUsageCollector extends AbstractApplicationUsagesCollector { +public class BackgroundImagesUsageCollector extends AbstractProjectsUsagesCollector { @NotNull @Override public GroupDescriptor getGroupId() { @@ -41,10 +41,10 @@ public class BackgroundImagesUsageCollector extends AbstractApplicationUsagesCol @NotNull @Override - public Set getUsages() throws CollectUsagesException { + protected Set mergeUsagesPostProcess(@NotNull Set usagesFromAllProjects) { // join usages from all projects HashMap map = ContainerUtil.newHashMap(); - for (UsageDescriptor descriptor : super.getUsages()) { + for (UsageDescriptor descriptor : usagesFromAllProjects) { String key = descriptor.getKey(); int idx = key.indexOf(' '); key = idx > 0 ? key.substring(0, idx) : key; diff --git a/java/compiler/impl/src/com/intellij/compiler/CompilerSettingsUsageCollector.java b/java/compiler/impl/src/com/intellij/compiler/CompilerSettingsUsageCollector.java index 22aea94ed446..962ca75d4a2b 100644 --- a/java/compiler/impl/src/com/intellij/compiler/CompilerSettingsUsageCollector.java +++ b/java/compiler/impl/src/com/intellij/compiler/CompilerSettingsUsageCollector.java @@ -15,7 +15,7 @@ */ package com.intellij.compiler; -import com.intellij.internal.statistic.AbstractApplicationUsagesCollector; +import com.intellij.internal.statistic.AbstractProjectsUsagesCollector; import com.intellij.internal.statistic.CollectUsagesException; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; @@ -30,7 +30,7 @@ import java.util.Set; * @author Eugene Zhuravlev * Date: 4/11/13 */ -public class CompilerSettingsUsageCollector extends AbstractApplicationUsagesCollector{ +public class CompilerSettingsUsageCollector extends AbstractProjectsUsagesCollector { public static final String GROUP_ID = "compiler"; @NotNull diff --git a/java/compiler/impl/src/com/intellij/compiler/actions/GenerateAntBuildDialog.java b/java/compiler/impl/src/com/intellij/compiler/actions/GenerateAntBuildDialog.java index 2616e52e1bf7..8e5ccb53a2c4 100644 --- a/java/compiler/impl/src/com/intellij/compiler/actions/GenerateAntBuildDialog.java +++ b/java/compiler/impl/src/com/intellij/compiler/actions/GenerateAntBuildDialog.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -33,6 +33,7 @@ import com.intellij.ui.ScrollPaneFactory; import com.intellij.util.ArrayUtil; import com.intellij.util.Chunk; import com.intellij.util.ListWithSelection; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.Table; import com.intellij.util.ui.table.ComboBoxTableCellEditor; import org.jetbrains.annotations.NonNls; @@ -175,7 +176,7 @@ public class GenerateAntBuildDialog extends DialogWrapper { true)); JLabel textLabel = new JLabel(CompilerBundle.message("generate.ant.build.dialog.cyclic.modules.table.description")); textLabel.setUI(new MultiLineLabelUI()); - textLabel.setBorder(IdeBorderFactory.createEmptyBorder(4, 4, 6, 4)); + textLabel.setBorder(JBUI.Borders.empty(4, 4, 6, 4)); myChunksPanel.add(textLabel, BorderLayout.NORTH); myTableModel = new MyTableModel(chunks); diff --git a/java/compiler/impl/src/com/intellij/compiler/options/AnnotationProcessorsPanel.java b/java/compiler/impl/src/com/intellij/compiler/options/AnnotationProcessorsPanel.java index a5b97e1fbead..de8484eb32bb 100644 --- a/java/compiler/impl/src/com/intellij/compiler/options/AnnotationProcessorsPanel.java +++ b/java/compiler/impl/src/com/intellij/compiler/options/AnnotationProcessorsPanel.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -31,13 +31,13 @@ import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.AnActionButton; import com.intellij.ui.ColoredTreeCellRenderer; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.ToolbarDecorator; import com.intellij.ui.awt.RelativePoint; import com.intellij.ui.components.JBList; import com.intellij.ui.treeStructure.Tree; import com.intellij.util.SmartList; import com.intellij.util.ui.EditableTreeModel; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.tree.TreeUtil; import org.jetbrains.jps.model.java.compiler.ProcessorConfigProfile; import org.jetbrains.jps.model.java.impl.compiler.ProcessorConfigProfileImpl; @@ -165,7 +165,7 @@ public class AnnotationProcessorsPanel extends JPanel { } }); myProfilePanel = new ProcessorProfilePanel(project); - myProfilePanel.setBorder(IdeBorderFactory.createEmptyBorder(0, 6, 0, 0)); + myProfilePanel.setBorder(JBUI.Borders.emptyLeft(6)); splitter.setSecondComponent(myProfilePanel); } diff --git a/java/compiler/impl/src/com/intellij/compiler/options/TargetOptionsComponent.java b/java/compiler/impl/src/com/intellij/compiler/options/TargetOptionsComponent.java index b320875cb432..eb833cf2f69c 100644 --- a/java/compiler/impl/src/com/intellij/compiler/options/TargetOptionsComponent.java +++ b/java/compiler/impl/src/com/intellij/compiler/options/TargetOptionsComponent.java @@ -25,6 +25,7 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.pom.java.LanguageLevel; import com.intellij.ui.*; import com.intellij.ui.table.JBTable; +import com.intellij.util.ArrayUtil; import com.intellij.util.ui.ItemRemovable; import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.Nullable; @@ -46,8 +47,8 @@ public class TargetOptionsComponent extends JPanel { private static final String[] KNOWN_TARGETS; private static final String COMPILER_DEFAULT = "Same as language level"; - private ComboBox myCbProjectTargetLevel; - private JBTable myTable; + private final ComboBox myCbProjectTargetLevel; + private final JBTable myTable; private final Project myProject; static { @@ -60,7 +61,7 @@ public class TargetOptionsComponent extends JPanel { targets.add(target); } } - KNOWN_TARGETS = targets.toArray(new String[targets.size()]); + KNOWN_TARGETS = ArrayUtil.toStringArray(targets); } public TargetOptionsComponent(Project project) { super(new GridBagLayout()); diff --git a/java/debugger/impl/src/com/intellij/debugger/actions/CustomizeContextViewAction.java b/java/debugger/impl/src/com/intellij/debugger/actions/CustomizeContextViewAction.java index 5350cc191dd8..a9dab4afb6ed 100644 --- a/java/debugger/impl/src/com/intellij/debugger/actions/CustomizeContextViewAction.java +++ b/java/debugger/impl/src/com/intellij/debugger/actions/CustomizeContextViewAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -16,7 +16,7 @@ package com.intellij.debugger.actions; import com.intellij.debugger.DebuggerBundle; -import com.intellij.debugger.engine.JavaDebugProcess; +import com.intellij.debugger.impl.DebuggerUtilsEx; import com.intellij.debugger.settings.JavaDebuggerSettings; import com.intellij.debugger.settings.NodeRendererSettings; import com.intellij.idea.ActionsBundle; @@ -27,8 +27,6 @@ import com.intellij.openapi.options.TabbedConfigurable; import com.intellij.openapi.options.ex.SingleConfigurableEditor; import com.intellij.openapi.project.Project; import com.intellij.util.ui.JBUI; -import com.intellij.xdebugger.XDebugSession; -import com.intellij.xdebugger.XDebuggerManager; import com.intellij.xdebugger.impl.ui.tree.actions.XDebuggerTreeActionBase; import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl; import org.jetbrains.annotations.NotNull; @@ -107,14 +105,6 @@ public class CustomizeContextViewAction extends XDebuggerTreeActionBase { @Override public void update(AnActionEvent e) { e.getPresentation().setText(ActionsBundle.actionText(DebuggerActions.CUSTOMIZE_VIEWS)); - Project project = getEventProject(e); - if (project != null) { - final XDebugSession currentSession = XDebuggerManager.getInstance(project).getCurrentSession(); - if (currentSession != null) { - e.getPresentation().setEnabledAndVisible(currentSession.getDebugProcess() instanceof JavaDebugProcess); - return; - } - } - e.getPresentation().setEnabledAndVisible(false); + e.getPresentation().setEnabledAndVisible(DebuggerUtilsEx.isInJavaSession(e)); } } diff --git a/java/debugger/impl/src/com/intellij/debugger/actions/GotoFrameSourceAction.java b/java/debugger/impl/src/com/intellij/debugger/actions/GotoFrameSourceAction.java index e127596ec3b5..39a867bececd 100644 --- a/java/debugger/impl/src/com/intellij/debugger/actions/GotoFrameSourceAction.java +++ b/java/debugger/impl/src/com/intellij/debugger/actions/GotoFrameSourceAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -15,13 +15,25 @@ */ package com.intellij.debugger.actions; -import com.intellij.debugger.SourcePosition; +import com.intellij.debugger.engine.DebugProcessImpl; +import com.intellij.debugger.engine.JavaExecutionStack; +import com.intellij.debugger.engine.SuspendContextImpl; +import com.intellij.debugger.engine.SuspendManagerUtil; +import com.intellij.debugger.engine.events.SuspendContextCommandImpl; +import com.intellij.debugger.jdi.StackFrameProxyImpl; +import com.intellij.debugger.jdi.ThreadReferenceProxyImpl; import com.intellij.debugger.ui.impl.watch.DebuggerTreeNodeImpl; import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebugSession; +import com.intellij.xdebugger.frame.XStackFrame; +import com.intellij.xdebugger.impl.ui.DebuggerUIUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; /** * @author lex @@ -32,16 +44,26 @@ public abstract class GotoFrameSourceAction extends DebuggerAction{ doAction(dataContext); } - protected static void doAction(DataContext dataContext) { + public static void doAction(DataContext dataContext) { final Project project = CommonDataKeys.PROJECT.getData(dataContext); if(project == null) return; StackFrameDescriptorImpl stackFrameDescriptor = getStackFrameDescriptor(dataContext); - if(stackFrameDescriptor != null) { - //DebuggerContextUtil.setStackFrame(getContextManager(dataContext), stackFrameDescriptor.getFrameProxy()); - SourcePosition sourcePosition = stackFrameDescriptor.getSourcePosition(); - if (sourcePosition != null) { - sourcePosition.navigate(true); - } + XDebugSession session = XDebugSession.DATA_KEY.getData(dataContext); + if (stackFrameDescriptor != null && session != null) { + StackFrameProxyImpl frameProxy = stackFrameDescriptor.getFrameProxy(); + DebugProcessImpl process = (DebugProcessImpl)stackFrameDescriptor.getDebugProcess(); + process.getManagerThread().schedule(new SuspendContextCommandImpl((SuspendContextImpl)session.getSuspendContext()) { + @Override + public void contextAction(@NotNull SuspendContextImpl suspendContext) throws Exception { + ThreadReferenceProxyImpl threadProxy = frameProxy.threadProxy(); + SuspendContextImpl threadSuspendContext = SuspendManagerUtil.findContextByThread(process.getSuspendManager(), threadProxy); + JavaExecutionStack executionStack = + new JavaExecutionStack(threadProxy, process, Objects.equals(threadSuspendContext.getThread(), threadProxy)); + executionStack.initTopFrame(); + XStackFrame frame = executionStack.createStackFrame(frameProxy); + DebuggerUIUtil.invokeLater(() -> session.setCurrentStackFrame(executionStack, frame)); + } + }); } } diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/JavaExecutionStack.java b/java/debugger/impl/src/com/intellij/debugger/engine/JavaExecutionStack.java index d5886c161605..61f2027fa1ae 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/JavaExecutionStack.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/JavaExecutionStack.java @@ -88,7 +88,7 @@ public class JavaExecutionStack extends XExecutionStack { try { StackFrameProxyImpl frame = myThreadProxy.frame(0); if (frame != null) { - myTopFrame = createStackFrame(frame, myTracker); + myTopFrame = createStackFrame(frame); } } catch (EvaluateException e) { @@ -99,8 +99,9 @@ public class JavaExecutionStack extends XExecutionStack { } } - private static XStackFrame createStackFrame(@NotNull StackFrameProxyImpl stackFrameProxy, @NotNull MethodsTracker tracker) { - StackFrameDescriptorImpl descriptor = new StackFrameDescriptorImpl(stackFrameProxy, tracker); + @NotNull + public XStackFrame createStackFrame(@NotNull StackFrameProxyImpl stackFrameProxy) { + StackFrameDescriptorImpl descriptor = new StackFrameDescriptorImpl(stackFrameProxy, myTracker); DebugProcessImpl debugProcess = (DebugProcessImpl)descriptor.getDebugProcess(); Location location = descriptor.getLocation(); if (location != null) { @@ -193,7 +194,7 @@ public class JavaExecutionStack extends XExecutionStack { } else { frameProxy = myStackFramesIterator.next(); - frame = createStackFrame(frameProxy, myTracker); + frame = createStackFrame(frameProxy); if (first && !myTopFrameReady) { myTopFrame = frame; myTopFrameReady = true; diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/JavaStackFrame.java b/java/debugger/impl/src/com/intellij/debugger/engine/JavaStackFrame.java index 83a110020e16..ac7883b227cb 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/JavaStackFrame.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/JavaStackFrame.java @@ -706,4 +706,21 @@ public class JavaStackFrame extends XStackFrame implements JVMStackFrameInfoProv public boolean isInLibraryContent() { return myDescriptor.isInLibraryContent(); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + JavaStackFrame frame = (JavaStackFrame)o; + + if (!myDescriptor.getFrameProxy().equals(frame.myDescriptor.getFrameProxy())) return false; + + return true; + } + + @Override + public int hashCode() { + return myDescriptor.getFrameProxy().hashCode(); + } } diff --git a/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsEx.java b/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsEx.java index fe79337049a5..45cd372eff64 100644 --- a/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsEx.java +++ b/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsEx.java @@ -40,6 +40,7 @@ import com.intellij.execution.ui.ConsoleView; import com.intellij.execution.ui.RunnerLayoutUi; import com.intellij.execution.ui.layout.impl.RunnerContentUi; import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.application.ApplicationManager; @@ -66,6 +67,8 @@ import com.intellij.unscramble.ThreadState; import com.intellij.util.DocumentUtil; import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; +import com.intellij.xdebugger.XDebugSession; +import com.intellij.xdebugger.XDebuggerManager; import com.intellij.xdebugger.XSourcePosition; import com.intellij.xdebugger.frame.XValueNode; import com.intellij.xdebugger.impl.XSourcePositionImpl; @@ -1090,4 +1093,15 @@ public abstract class DebuggerUtilsEx extends DebuggerUtils { } }); } + + public static boolean isInJavaSession(AnActionEvent e) { + XDebugSession session = e.getData(XDebugSession.DATA_KEY); + if (session == null) { + Project project = e.getProject(); + if (project != null) { + session = XDebuggerManager.getInstance(project).getCurrentSession(); + } + } + return session != null && session.getDebugProcess() instanceof JavaDebugProcess; + } } diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/RunHotswapDialog.java b/java/debugger/impl/src/com/intellij/debugger/ui/RunHotswapDialog.java index 8d2a9d3f0d56..6d8f655707c0 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/RunHotswapDialog.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/RunHotswapDialog.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -22,15 +22,19 @@ import com.intellij.debugger.settings.DebuggerSettings; import com.intellij.ide.util.ElementsChooser; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MultiLineLabelUI; -import com.intellij.ui.IdeBorderFactory; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.OptionsDialog; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.awt.*; -import java.util.*; -import java.util.List;public class RunHotswapDialog extends OptionsDialog { +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; + +public class RunHotswapDialog extends OptionsDialog { private final JPanel myPanel; private final ElementsChooser myElementsChooser; private final boolean myDisplayHangWarning; @@ -45,7 +49,7 @@ import java.util.List;public class RunHotswapDialog extends OptionsDialog { } items.sort(Comparator.comparing(debuggerSession -> debuggerSession.getSession().getSessionName())); myElementsChooser = new ElementsChooser<>(items, true); - myPanel.setBorder(IdeBorderFactory.createEmptyBorder(10, 0, 5, 0)); + myPanel.setBorder(JBUI.Borders.empty(10, 0, 5, 0)); //myElementsChooser.setBorder(IdeBorderFactory.createEmptyBorder(5, 0, 0, 0)); if (sessions.size() > 0) { myElementsChooser.selectElements(items.subList(0, 1)); diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/impl/ThreadsPanel.java b/java/debugger/impl/src/com/intellij/debugger/ui/impl/ThreadsPanel.java index b2ea6c8dc193..921123727c4f 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/impl/ThreadsPanel.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/impl/ThreadsPanel.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -17,19 +17,18 @@ package com.intellij.debugger.ui.impl; import com.intellij.debugger.actions.DebuggerAction; import com.intellij.debugger.actions.DebuggerActions; +import com.intellij.debugger.actions.GotoFrameSourceAction; import com.intellij.debugger.engine.DebugProcessImpl; import com.intellij.debugger.engine.events.DebuggerCommandImpl; -import com.intellij.debugger.impl.*; -import com.intellij.debugger.jdi.StackFrameProxyImpl; +import com.intellij.debugger.impl.DebuggerContextImpl; +import com.intellij.debugger.impl.DebuggerContextListener; +import com.intellij.debugger.impl.DebuggerSession; +import com.intellij.debugger.impl.DebuggerStateManager; import com.intellij.debugger.ui.impl.watch.DebuggerTree; import com.intellij.debugger.ui.impl.watch.DebuggerTreeNodeImpl; -import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl; -import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl; +import com.intellij.ide.DataManager; import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.ActionManager; -import com.intellij.openapi.actionSystem.ActionPopupMenu; -import com.intellij.openapi.actionSystem.DefaultActionGroup; -import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; @@ -57,13 +56,7 @@ public class ThreadsPanel extends DebuggerTreePanel{ @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER && getThreadsTree().getSelectionCount() == 1) { - DebuggerTreeNodeImpl node = (DebuggerTreeNodeImpl)getThreadsTree().getLastSelectedPathComponent(); - if (node != null) { - NodeDescriptorImpl descriptor = node.getDescriptor(); - if (descriptor instanceof StackFrameDescriptorImpl) { - selectFrame(node); - } - } + GotoFrameSourceAction.doAction(DataManager.getInstance().getDataContext(getThreadsTree())); } } }); @@ -171,12 +164,6 @@ public class ThreadsPanel extends DebuggerTreePanel{ } return super.getData(dataId); } - - private void selectFrame(DebuggerTreeNodeImpl node) { - StackFrameProxyImpl frame = ((StackFrameDescriptorImpl)node.getDescriptor()).getFrameProxy(); - DebuggerContextUtil.setStackFrame(getContextManager(), frame); - } - public ThreadsDebuggerTree getThreadsTree() { return (ThreadsDebuggerTree) getTree(); } diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/tree/actions/ForceOnDemandRenderersAction.kt b/java/debugger/impl/src/com/intellij/debugger/ui/tree/actions/ForceOnDemandRenderersAction.kt index 2d60e46b69f7..574763614d32 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/tree/actions/ForceOnDemandRenderersAction.kt +++ b/java/debugger/impl/src/com/intellij/debugger/ui/tree/actions/ForceOnDemandRenderersAction.kt @@ -15,6 +15,7 @@ */ package com.intellij.debugger.ui.tree.actions +import com.intellij.debugger.impl.DebuggerUtilsEx import com.intellij.debugger.settings.NodeRendererSettings import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.ToggleAction @@ -38,6 +39,10 @@ class ForceOnDemandRenderersAction : ToggleAction(), DumbAware { NodeRendererSettings.getInstance().fireRenderersChanged() } + override fun update(e: AnActionEvent) { + e.presentation.isEnabledAndVisible = DebuggerUtilsEx.isInJavaSession(e) + } + companion object { private val RENDERERS_ONDEMAND_FORCED = Key.create("RENDERERS_ONDEMAND_FORCED") diff --git a/java/execution/impl/src/com/intellij/execution/TestClassCollector.java b/java/execution/impl/src/com/intellij/execution/TestClassCollector.java index 53ce4fe65bf1..d9deea241951 100644 --- a/java/execution/impl/src/com/intellij/execution/TestClassCollector.java +++ b/java/execution/impl/src/com/intellij/execution/TestClassCollector.java @@ -48,7 +48,7 @@ public class TestClassCollector { List urls = new ArrayList<>(); PathsList pathsList = (module == null ? OrderEnumerator.orderEntries(configuration.getProject()) : OrderEnumerator.orderEntries(module)) - .runtimeOnly().withoutSdk().recursively().getPathsList(); + .runtimeOnly().recursively().getPathsList(); //include jdk to avoid NoClassDefFoundError for classes inside tools.jar for (VirtualFile file : pathsList.getVirtualFiles()) { try { urls.add(VfsUtilCore.virtualToIoFile(file).toURI().toURL()); diff --git a/java/execution/impl/src/com/intellij/execution/testframework/AbstractJavaTestConfigurationProducer.java b/java/execution/impl/src/com/intellij/execution/testframework/AbstractJavaTestConfigurationProducer.java index da5f5cc6460d..a3bb261ede5b 100644 --- a/java/execution/impl/src/com/intellij/execution/testframework/AbstractJavaTestConfigurationProducer.java +++ b/java/execution/impl/src/com/intellij/execution/testframework/AbstractJavaTestConfigurationProducer.java @@ -71,10 +71,12 @@ public abstract class AbstractJavaTestConfigurationProducer frameworks = TestFrameworks.detectApplicableFrameworks(psiClass); + return frameworks.stream().filter(framework -> framework instanceof JavaTestFramework && ((JavaTestFramework)framework).isMyConfigurationType(configurationType)) + .map(framework -> (JavaTestFramework)framework) + .findFirst() + .orElse(null); } return null; } diff --git a/java/execution/impl/src/com/intellij/execution/ui/ConfigurationArgumentsHelpArea.java b/java/execution/impl/src/com/intellij/execution/ui/ConfigurationArgumentsHelpArea.java index 2da95c372219..1065deb2b1c7 100644 --- a/java/execution/impl/src/com/intellij/execution/ui/ConfigurationArgumentsHelpArea.java +++ b/java/execution/impl/src/com/intellij/execution/ui/ConfigurationArgumentsHelpArea.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -19,9 +19,9 @@ import com.intellij.execution.ExecutionBundle; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.ide.CopyPasteManager; import com.intellij.openapi.ui.FixedSizeButton; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.PopupHandler; import com.intellij.util.PlatformIcons; +import com.intellij.util.ui.JBUI; import javax.swing.*; import java.awt.*; @@ -38,7 +38,7 @@ public class ConfigurationArgumentsHelpArea extends JPanel { public ConfigurationArgumentsHelpArea() { super(new BorderLayout()); add(myPanel); - setBorder(IdeBorderFactory.createEmptyBorder(10, 0, 0, 0)); + setBorder(JBUI.Borders.emptyTop(10)); final DefaultActionGroup group = new DefaultActionGroup(); group.add(new MyCopyAction()); diff --git a/java/idea-ui/idea-ui-tests.iml b/java/idea-ui/idea-ui-tests.iml new file mode 100644 index 000000000000..619f3b418b76 --- /dev/null +++ b/java/idea-ui/idea-ui-tests.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/idea-ui/src/com/intellij/ide/projectWizard/ProjectCategoryUsagesCollector.java b/java/idea-ui/src/com/intellij/ide/projectWizard/ProjectCategoryUsagesCollector.java index 2a9affe7172b..c65f6852282b 100644 --- a/java/idea-ui/src/com/intellij/ide/projectWizard/ProjectCategoryUsagesCollector.java +++ b/java/idea-ui/src/com/intellij/ide/projectWizard/ProjectCategoryUsagesCollector.java @@ -20,7 +20,7 @@ import com.intellij.internal.statistic.UsagesCollector; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; import com.intellij.openapi.components.ServiceManager; -import com.intellij.util.containers.FactoryMap; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.hash.HashSet; import org.jetbrains.annotations.NotNull; @@ -32,10 +32,13 @@ import java.util.Set; */ public class ProjectCategoryUsagesCollector extends UsagesCollector { - private final Map myUsageDescriptors = FactoryMap.createMap(key -> new UsageDescriptor(key, 0)); + private final Map myUsageDescriptors = ContainerUtil.newHashMap(); public static void projectTypeUsed(@NotNull String projectTypeId) { - getUsageDescriptors().get("project.category." + projectTypeId).advance(); + String key = "project.category." + projectTypeId; + UsageDescriptor descriptor = getUsageDescriptors().get(key); + + getUsageDescriptors().put(key, new UsageDescriptor(key, descriptor == null? 1: descriptor.getValue()+1)); } @NotNull diff --git a/java/idea-ui/src/com/intellij/ide/projectWizard/ProjectSettingsStep.java b/java/idea-ui/src/com/intellij/ide/projectWizard/ProjectSettingsStep.java index a5f20635d616..fc21df9a86f8 100644 --- a/java/idea-ui/src/com/intellij/ide/projectWizard/ProjectSettingsStep.java +++ b/java/idea-ui/src/com/intellij/ide/projectWizard/ProjectSettingsStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -72,7 +72,7 @@ public class ProjectSettingsStep extends ModuleWizardStep implements SettingsSte myModuleNameLocationComponent.bindModuleSettings(myNamePathComponent); myExpertDecorator = new HideableDecorator(myExpertPlaceholder, "Mor&e Settings", false); - myExpertPanel.setBorder(IdeBorderFactory.createEmptyBorder(0, IdeBorderFactory.TITLED_BORDER_INDENT, 5, 0)); + myExpertPanel.setBorder(JBUI.Borders.empty(0, IdeBorderFactory.TITLED_BORDER_INDENT, 5, 0)); myExpertDecorator.setContentComponent(myExpertPanel); if (myWizardContext.isCreatingNewProject()) { diff --git a/java/idea-ui/src/com/intellij/ide/projectWizard/ProjectTemplateList.java b/java/idea-ui/src/com/intellij/ide/projectWizard/ProjectTemplateList.java index 24ab98e3cc6a..b1dfcceca159 100644 --- a/java/idea-ui/src/com/intellij/ide/projectWizard/ProjectTemplateList.java +++ b/java/idea-ui/src/com/intellij/ide/projectWizard/ProjectTemplateList.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -25,10 +25,10 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.platform.ProjectTemplate; import com.intellij.platform.templates.ArchivedProjectTemplate; import com.intellij.ui.CollectionListModel; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.components.JBList; import com.intellij.ui.popup.list.GroupedItemsListRenderer; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; @@ -77,7 +77,7 @@ public class ProjectTemplateList extends JPanel { myTextLabel.setDisabledIcon(IconLoader.getDisabledIcon(icon)); } myTextLabel.setEnabled(myList.isEnabled()); - myTextLabel.setBorder(IdeBorderFactory.createEmptyBorder(3, 3, 3, 3)); + myTextLabel.setBorder(JBUI.Borders.empty(3, 3, 3, 3)); } }; myList.setCellRenderer(renderer); diff --git a/java/idea-ui/src/com/intellij/ide/projectWizard/ProjectTypeStep.java b/java/idea-ui/src/com/intellij/ide/projectWizard/ProjectTypeStep.java index ad23fb5a8f56..575257caf028 100644 --- a/java/idea-ui/src/com/intellij/ide/projectWizard/ProjectTypeStep.java +++ b/java/idea-ui/src/com/intellij/ide/projectWizard/ProjectTypeStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -48,7 +48,6 @@ import com.intellij.platform.ProjectTemplatesFactory; import com.intellij.platform.templates.*; import com.intellij.psi.impl.DebugUtil; import com.intellij.ui.CollectionListModel; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.ListSpeedSearch; import com.intellij.ui.SingleSelectionModel; import com.intellij.ui.components.JBLabel; @@ -57,6 +56,7 @@ import com.intellij.ui.popup.list.GroupedItemsListRenderer; import com.intellij.util.Function; import com.intellij.util.PlatformUtils; import com.intellij.util.containers.*; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.update.UiNotifyConnector; import gnu.trove.THashMap; @@ -157,7 +157,7 @@ public class ProjectTypeStep extends ModuleWizardStep implements SettingsStep, D @Override protected JComponent createItemComponent() { JComponent component = super.createItemComponent(); - myTextLabel.setBorder(IdeBorderFactory.createEmptyBorder(3)); + myTextLabel.setBorder(JBUI.Borders.empty(3)); return component; } }); @@ -189,7 +189,7 @@ public class ProjectTypeStep extends ModuleWizardStep implements SettingsStep, D Disposer.register(this, myFrameworksPanel); myFrameworksPanelPlaceholder.add(myFrameworksPanel.getMainPanel()); myFrameworksLabel.setLabelFor(myFrameworksPanel.getFrameworksTree()); - myFrameworksLabel.setBorder(IdeBorderFactory.createEmptyBorder(3)); + myFrameworksLabel.setBorder(JBUI.Borders.empty(3)); myConfigurationUpdater = new ModuleBuilder.ModuleConfigurationUpdater() { @Override diff --git a/java/idea-ui/src/com/intellij/ide/util/importProject/RootsDetectionStep.java b/java/idea-ui/src/com/intellij/ide/util/importProject/RootsDetectionStep.java index df376fe6f411..1055e02e3c27 100644 --- a/java/idea-ui/src/com/intellij/ide/util/importProject/RootsDetectionStep.java +++ b/java/idea-ui/src/com/intellij/ide/util/importProject/RootsDetectionStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -24,7 +24,6 @@ import com.intellij.ide.util.projectWizard.importSources.impl.ProjectFromSources import com.intellij.openapi.application.ApplicationNamesInfo; import com.intellij.openapi.ui.MultiLineLabelUI; import com.intellij.openapi.ui.ex.MultiLineLabel; -import com.intellij.ui.IdeBorderFactory; import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.Nullable; @@ -109,7 +108,7 @@ public class RootsDetectionStep extends AbstractStepWithProgress 0; // todo: count tasks on per-project basis? + return ourTasksInProgress.get() > 0; // todo: count tasks on per-project basis? } @Nullable public static NewLibraryConfiguration chooseLibraryAndDownload(final @NotNull Project project, final @Nullable String initialFilter, JComponent parentComponent) { - RepositoryAttachDialog dialog = new RepositoryAttachDialog(project, initialFilter); - dialog.setTitle("Download Library From Maven Repository"); + RepositoryAttachDialog dialog = new RepositoryAttachDialog(project, initialFilter, RepositoryAttachDialog.Mode.DOWNLOAD); dialog.show(); if (dialog.getExitCode() != DialogWrapper.OK_EXIT_CODE) { return null; @@ -367,11 +367,11 @@ public class JarRepositoryManager { @Override public void run(@NotNull ProgressIndicator indicator) { try { - ourTasksInProgress++; + ourTasksInProgress.incrementAndGet(); result.set(job.apply(indicator)); } finally { - ourTasksInProgress--; + ourTasksInProgress.decrementAndGet(); } } }.queue(); @@ -382,7 +382,7 @@ public class JarRepositoryManager { final ModalityState startModality = ModalityState.defaultModalityState(); return JobExecutor.INSTANCE.submit(() -> { try { - ourTasksInProgress++; + ourTasksInProgress.incrementAndGet(); final ProgressIndicator indicator = new EmptyProgressIndicator(startModality); return ProgressManager.getInstance().runProcess(() -> job.apply(indicator), indicator); } @@ -392,7 +392,7 @@ public class JarRepositoryManager { LOG.info(e); } finally { - ourTasksInProgress--; + ourTasksInProgress.decrementAndGet(); } return null; }); diff --git a/java/idea-ui/src/com/intellij/jarRepository/RepositoryAttachDialog.form b/java/idea-ui/src/com/intellij/jarRepository/RepositoryAttachDialog.form index 41987a292be5..4c449aeef6ec 100644 --- a/java/idea-ui/src/com/intellij/jarRepository/RepositoryAttachDialog.form +++ b/java/idea-ui/src/com/intellij/jarRepository/RepositoryAttachDialog.form @@ -13,45 +13,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -94,14 +55,63 @@ - + + - + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/idea-ui/src/com/intellij/jarRepository/RepositoryAttachDialog.java b/java/idea-ui/src/com/intellij/jarRepository/RepositoryAttachDialog.java index 55dc8a4a3b75..8545b6cc9e5a 100644 --- a/java/idea-ui/src/com/intellij/jarRepository/RepositoryAttachDialog.java +++ b/java/idea-ui/src/com/intellij/jarRepository/RepositoryAttachDialog.java @@ -75,7 +75,9 @@ public class RepositoryAttachDialog extends DialogWrapper { @NonNls private static final String PROPERTY_DOWNLOAD_TO_PATH_ENABLED = "Downloaded.Files.Path.Enabled"; @NonNls private static final String PROPERTY_ATTACH_JAVADOC = "Repository.Attach.JavaDocs"; @NonNls private static final String PROPERTY_ATTACH_SOURCES = "Repository.Attach.Sources"; + @NotNull private final Mode myMode; + public enum Mode { SEARCH, DOWNLOAD } private final Project myProject; private JBLabel myInfoLabel; @@ -87,6 +89,7 @@ public class RepositoryAttachDialog extends DialogWrapper { private TextFieldWithBrowseButton myDirectoryField; private JBCheckBox myDownloadToCheckBox; private JBLabel myCaptionLabel; + private JPanel myDownloadOptionsPanel; private final JComboBox myCombobox; @@ -97,8 +100,10 @@ public class RepositoryAttachDialog extends DialogWrapper { private String myFilterString; private boolean myInUpdate; - public RepositoryAttachDialog(@NotNull Project project, final @Nullable String initialFilter) { + public RepositoryAttachDialog(@NotNull Project project, final @Nullable String initialFilter, @NotNull Mode mode) { super(project, true); + myMode = mode; + setTitle(mode == Mode.DOWNLOAD ? "Download Library from Maven Repository" : "Search Library in Maven Repositories"); myProject = project; myProgressIcon.suspend(); myCaptionLabel.setText( @@ -176,6 +181,7 @@ public class RepositoryAttachDialog extends DialogWrapper { descriptor); updateInfoLabel(); setOKActionEnabled(false); + myDownloadOptionsPanel.setVisible(mode == Mode.DOWNLOAD); init(); } @@ -372,7 +378,7 @@ public class RepositoryAttachDialog extends DialogWrapper { @Override protected String getDimensionServiceKey() { - return RepositoryAttachDialog.class.getName(); + return RepositoryAttachDialog.class.getName() + "-" + myMode; } private boolean isValidCoordinateSelected() { diff --git a/java/idea-ui/src/com/intellij/jarRepository/RepositoryLibrarySynchronizer.java b/java/idea-ui/src/com/intellij/jarRepository/RepositoryLibrarySynchronizer.java index 705c132cefad..8316ae729914 100644 --- a/java/idea-ui/src/com/intellij/jarRepository/RepositoryLibrarySynchronizer.java +++ b/java/idea-ui/src/com/intellij/jarRepository/RepositoryLibrarySynchronizer.java @@ -17,7 +17,12 @@ package com.intellij.jarRepository; import com.google.common.base.Predicate; import com.intellij.ProjectTopics; +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationType; +import com.intellij.notification.Notifications; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ApplicationNamesInfo; +import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.DumbAware; @@ -28,18 +33,20 @@ import com.intellij.openapi.roots.ModuleRootListener; import com.intellij.openapi.roots.OrderEnumerator; import com.intellij.openapi.roots.OrderRootType; import com.intellij.openapi.roots.impl.libraries.LibraryEx; +import com.intellij.openapi.roots.impl.libraries.LibraryTableImplUtil; import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable; import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.roots.libraries.LibraryUtil; import com.intellij.openapi.startup.StartupActivity; -import com.intellij.openapi.startup.StartupManager; import com.intellij.util.Alarm; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.idea.maven.utils.library.RepositoryLibraryDescription; import org.jetbrains.idea.maven.utils.library.RepositoryLibraryProperties; import org.jetbrains.idea.maven.utils.library.RepositoryUtils; -import java.util.Collection; +import java.util.*; /** * @author gregsh @@ -66,6 +73,8 @@ public class RepositoryLibrarySynchronizer implements StartupActivity, DumbAware private static Collection collectLibraries(final @NotNull Project project, final @NotNull Predicate predicate) { final HashSet result = new HashSet<>(); ApplicationManager.getApplication().runReadAction(() -> { + if (project.isDisposed()) return; + for (final Module module : ModuleManager.getInstance(project).getModules()) { OrderEnumerator.orderEntries(module).withoutSdk().forEachLibrary(library -> { if (predicate.apply(library)) { @@ -83,9 +92,61 @@ public class RepositoryLibrarySynchronizer implements StartupActivity, DumbAware return result; } + private static void removeDuplicatedUrlsFromRepositoryLibraries(@NotNull Project project) { + Collection libraries = collectLibraries(project, library -> + library instanceof LibraryEx && ((LibraryEx)library).getProperties() instanceof RepositoryLibraryProperties && hasDuplicatedRoots(library) + ); + + if (!libraries.isEmpty()) { + ApplicationManager.getApplication().invokeLater(() -> { + List validLibraries = ContainerUtil.filter(libraries, LibraryTableImplUtil::isValidLibrary); + if (validLibraries.isEmpty()) return; + + WriteAction.run(() -> { + for (Library library : validLibraries) { + Library.ModifiableModel model = library.getModifiableModel(); + for (OrderRootType type : OrderRootType.getAllTypes()) { + String[] urls = model.getUrls(type); + Set uniqueUrls = new LinkedHashSet<>(Arrays.asList(urls)); + if (uniqueUrls.size() != urls.length) { + for (String url : urls) { + model.removeRoot(url, type); + } + for (String url : uniqueUrls) { + model.addRoot(url, type); + } + } + } + model.commit(); + } + }); + String libraryText = validLibraries.size() == 1 + ? "'" + LibraryUtil.getPresentableName(validLibraries.iterator().next()) + "' library" + : validLibraries.size() + " libraries"; + Notifications.Bus.notify(new Notification( + "Repository", "Repository libraries cleanup", "Duplicated URLs were removed from " + libraryText + ". " + + "These duplicated URLs were produced due to a bug in a previous " + + ApplicationNamesInfo.getInstance().getFullProductName() + + " version and might cause performance issues.", + NotificationType.INFORMATION + ), project); + }, project.getDisposed()); + } + } + + private static boolean hasDuplicatedRoots(Library library) { + for (OrderRootType type : OrderRootType.getAllTypes()) { + String[] urls = library.getUrls(type); + if (urls.length != ContainerUtil.set(urls).size()) { + return true; + } + } + return false; + } + @Override public void runActivity(@NotNull final Project project) { - final DumbAwareRunnable syncTask = () -> ApplicationManager.getApplication().invokeLater((DumbAwareRunnable)() -> { + final Runnable syncTask = () -> { final Collection toSync = collectLibraries(project, library -> { if (library instanceof LibraryEx) { final LibraryEx libraryEx = (LibraryEx)library; @@ -94,10 +155,15 @@ public class RepositoryLibrarySynchronizer implements StartupActivity, DumbAware } return false; }); - for (Library library : toSync) { - RepositoryUtils.reloadDependencies(project, (LibraryEx)library); - } - }, project.getDisposed()); + + ApplicationManager.getApplication().invokeLater((DumbAwareRunnable)() -> { + for (Library library : toSync) { + if (LibraryTableImplUtil.isValidLibrary(library)) { + RepositoryUtils.reloadDependencies(project, (LibraryEx)library); + } + } + }, project.getDisposed()); + }; project.getMessageBus().connect().subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() { private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, project); @@ -110,6 +176,9 @@ public class RepositoryLibrarySynchronizer implements StartupActivity, DumbAware } }); - StartupManager.getInstance(project).registerPostStartupActivity(syncTask); + ApplicationManager.getApplication().executeOnPooledThread(() -> { + removeDuplicatedUrlsFromRepositoryLibraries(project); + syncTask.run(); + }); } } diff --git a/java/idea-ui/src/com/intellij/jarRepository/settings/RemoteRepositoriesConfigurable.java b/java/idea-ui/src/com/intellij/jarRepository/settings/RemoteRepositoriesConfigurable.java index 5131e6a2b055..8ef73293c33e 100644 --- a/java/idea-ui/src/com/intellij/jarRepository/settings/RemoteRepositoriesConfigurable.java +++ b/java/idea-ui/src/com/intellij/jarRepository/settings/RemoteRepositoriesConfigurable.java @@ -179,7 +179,7 @@ public class RemoteRepositoriesConfigurable extends BaseConfigurable implements } public String getHelpTopic() { - return ""; // todo + return "reference.jar.repositories"; } @NotNull diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/classpath/ClasspathPanelImpl.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/classpath/ClasspathPanelImpl.java index 6223bc5b0da5..079b111a0b22 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/classpath/ClasspathPanelImpl.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/classpath/ClasspathPanelImpl.java @@ -40,6 +40,7 @@ import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable; import com.intellij.openapi.roots.ui.configuration.dependencyAnalysis.AnalyzeDependenciesDialog; import com.intellij.openapi.roots.ui.configuration.libraries.LibraryEditingUtil; import com.intellij.openapi.roots.ui.configuration.libraryEditor.EditExistingLibraryDialog; +import com.intellij.openapi.roots.ui.configuration.projectRoot.ConvertModuleLibraryToRepositoryLibraryAction; import com.intellij.openapi.roots.ui.configuration.projectRoot.FindUsagesInProjectStructureActionBase; import com.intellij.openapi.roots.ui.configuration.projectRoot.ModuleStructureConfigurable; import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext; @@ -288,6 +289,7 @@ public class ClasspathPanelImpl extends JPanel implements ClasspathPanel { addChangeLibraryLevelAction(actionGroup, LibraryTablesRegistrar.PROJECT_LEVEL); addChangeLibraryLevelAction(actionGroup, LibraryTablesRegistrar.APPLICATION_LEVEL); addChangeLibraryLevelAction(actionGroup, LibraryTableImplUtil.MODULE_LEVEL); + actionGroup.add(new ConvertModuleLibraryToRepositoryLibraryAction(this, getStructureConfigurableContext())); PopupHandler.installPopupHandler(myEntryTable, actionGroup, ActionPlaces.UNKNOWN, ActionManager.getInstance()); } diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/libraries/impl/LibraryUsageCollector.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/libraries/impl/LibraryUsageCollector.java index 1bab61d17e4b..7ed67f592fb9 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/libraries/impl/LibraryUsageCollector.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/libraries/impl/LibraryUsageCollector.java @@ -15,7 +15,7 @@ */ package com.intellij.openapi.roots.ui.configuration.libraries.impl; -import com.intellij.internal.statistic.AbstractApplicationUsagesCollector; +import com.intellij.internal.statistic.AbstractProjectsUsagesCollector; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; import com.intellij.openapi.module.Module; @@ -34,7 +34,7 @@ import java.util.Set; /** * @author nik */ -public class LibraryUsageCollector extends AbstractApplicationUsagesCollector { +public class LibraryUsageCollector extends AbstractProjectsUsagesCollector { @NonNls private static final String GROUP_ID = "libraries"; diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/libraryEditor/LibraryPropertiesEditorBase.form b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/libraryEditor/LibraryPropertiesEditorBase.form index 342f8c15c8d5..b0ccb4442fa0 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/libraryEditor/LibraryPropertiesEditorBase.form +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/libraryEditor/LibraryPropertiesEditorBase.form @@ -1,9 +1,9 @@
- + - + diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ConvertModuleLibraryToRepositoryLibraryAction.kt b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ConvertModuleLibraryToRepositoryLibraryAction.kt new file mode 100644 index 000000000000..cdea9c98d8c8 --- /dev/null +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ConvertModuleLibraryToRepositoryLibraryAction.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2000-2017 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.roots.ui.configuration.projectRoot + +import com.intellij.jarRepository.RepositoryLibraryType +import com.intellij.openapi.roots.LibraryOrderEntry +import com.intellij.openapi.roots.impl.OrderEntryUtil +import com.intellij.openapi.roots.impl.libraries.LibraryEx +import com.intellij.openapi.roots.impl.libraries.LibraryTableImplUtil +import com.intellij.openapi.roots.libraries.Library +import com.intellij.openapi.roots.ui.configuration.classpath.ClasspathPanel +import com.intellij.openapi.roots.ui.configuration.libraryEditor.ExistingLibraryEditor +import com.intellij.openapi.roots.ui.configuration.libraryEditor.LibraryEditorBase + +/** + * @author nik + */ +class ConvertModuleLibraryToRepositoryLibraryAction(private val classpathPanel: ClasspathPanel, + context: StructureConfigurableContext) + : ConvertToRepositoryLibraryActionBase(context) { + + override fun getSelectedLibrary(): LibraryEx? { + val entry = classpathPanel.selectedEntry as? LibraryOrderEntry + if (entry == null || !entry.isModuleLevel) return null + return entry.library as? LibraryEx + } + + override fun replaceLibrary(library: Library, configureNewLibrary: (LibraryEditorBase) -> Unit) { + val name = library.name + val modifiableModel = classpathPanel.getModifiableModelProvider(LibraryTableImplUtil.MODULE_LEVEL).modifiableModel + val newLibrary = modifiableModel.createLibrary(name, RepositoryLibraryType.getInstance().kind, null) + OrderEntryUtil.replaceLibraryEntryByAdded(classpathPanel.rootModel, classpathPanel.rootModel.findLibraryOrderEntry(library)!!) + + val editor = ExistingLibraryEditor(newLibrary, null) + configureNewLibrary(editor) + editor.commit() + } +} \ No newline at end of file diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ConvertProjectLibraryToRepositoryLibraryAction.kt b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ConvertProjectLibraryToRepositoryLibraryAction.kt new file mode 100644 index 000000000000..6c244ab4babc --- /dev/null +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ConvertProjectLibraryToRepositoryLibraryAction.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2000-2017 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.roots.ui.configuration.projectRoot + +import com.intellij.jarRepository.RepositoryLibraryType +import com.intellij.openapi.roots.impl.libraries.LibraryEx +import com.intellij.openapi.roots.libraries.Library +import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable +import com.intellij.openapi.roots.ui.configuration.libraryEditor.LibraryEditorBase +import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.LibraryProjectStructureElement + +/** + * @author nik + */ +class ConvertProjectLibraryToRepositoryLibraryAction(private val librariesConfigurable: BaseLibrariesConfigurable, + context: StructureConfigurableContext) + : ConvertToRepositoryLibraryActionBase(context) { + + override fun getSelectedLibrary() = (librariesConfigurable.selectedElement as? LibraryProjectStructureElement)?.library as? LibraryEx + + override fun replaceLibrary(library: Library, configureNewLibrary: (LibraryEditorBase) -> Unit) { + val name = library.name + val modifiableModel = librariesConfigurable.modelProvider.modifiableModel + + val usages = context.daemonAnalyzer.getUsages(LibraryProjectStructureElement(context, library)) + modifiableModel.removeLibrary(library) + val newLibrary = modifiableModel.createLibrary(name, RepositoryLibraryType.getInstance().kind, null) + usages.forEach { it.replaceElement(LibraryProjectStructureElement(context, newLibrary)) } + + val editor = modifiableModel.getLibraryEditor(newLibrary) + configureNewLibrary(editor) + ProjectStructureConfigurable.getInstance(project).selectProjectOrGlobalLibrary(newLibrary, true) + } +} \ No newline at end of file diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ConvertToRepositoryLibraryActionBase.kt b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ConvertToRepositoryLibraryActionBase.kt new file mode 100644 index 000000000000..ce9d441b3b69 --- /dev/null +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ConvertToRepositoryLibraryActionBase.kt @@ -0,0 +1,259 @@ +/* + * Copyright 2000-2017 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.roots.ui.configuration.projectRoot + +import com.intellij.jarRepository.JarRepositoryManager +import com.intellij.jarRepository.RepositoryAttachDialog +import com.intellij.jarRepository.RepositoryLibraryType +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.Result +import com.intellij.openapi.application.WriteAction +import com.intellij.openapi.diagnostic.debug +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.Task +import com.intellij.openapi.project.DumbAwareAction +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.AnnotationOrderRootType +import com.intellij.openapi.roots.OrderRootType +import com.intellij.openapi.roots.impl.libraries.LibraryEx +import com.intellij.openapi.roots.libraries.Library +import com.intellij.openapi.roots.libraries.LibraryUtil +import com.intellij.openapi.roots.libraries.NewLibraryConfiguration +import com.intellij.openapi.roots.libraries.ui.OrderRoot +import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable +import com.intellij.openapi.roots.ui.configuration.libraryEditor.LibraryEditor +import com.intellij.openapi.roots.ui.configuration.libraryEditor.LibraryEditorBase +import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.LibraryProjectStructureElement +import com.intellij.openapi.ui.Messages +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.openapi.vfs.VfsUtilCore +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.newvfs.RefreshQueue +import gnu.trove.THashSet +import gnu.trove.TObjectHashingStrategy +import org.jetbrains.idea.maven.utils.library.RepositoryLibraryProperties +import org.jetbrains.idea.maven.utils.library.RepositoryUtils +import org.jetbrains.jps.model.library.JpsMavenRepositoryLibraryDescriptor +import java.io.File +import java.io.IOException +import java.util.* + +/** + * @author nik + */ +private val LOG = logger() + +abstract class ConvertToRepositoryLibraryActionBase(protected val context: StructureConfigurableContext) : DumbAwareAction( + "Convert to Repository Library...", + "Convert a regular library to a repository library which additionally stores its Maven coordinates, so the IDE can automatically download the library JARs if they are missing", + null) { + protected val project = context.project + + protected abstract fun getSelectedLibrary(): LibraryEx? + + override fun update(e: AnActionEvent) { + val library = getSelectedLibrary() + e.presentation.isEnabledAndVisible = library != null && library.kind == null + } + + override fun actionPerformed(e: AnActionEvent) { + val library = getSelectedLibrary() ?: return + val mavenCoordinates = detectOrSpecifyMavenCoordinates(library) ?: return + + val libraryProperties = RepositoryLibraryProperties(mavenCoordinates.groupId, mavenCoordinates.artifactId, mavenCoordinates.version) + val hasSources = RepositoryUtils.libraryHasSources(library) + val hasJavadoc = RepositoryUtils.libraryHasJavaDocs(library) + LOG.debug("Resolving $mavenCoordinates") + val roots: Collection = + JarRepositoryManager.loadDependenciesModal(project, libraryProperties, hasSources, hasJavadoc, null, null) + + val downloadedFiles = roots.filter { it.type == OrderRootType.CLASSES }.map { VfsUtilCore.virtualToIoFile(it.file) } + val libraryFiles = library.getFiles(OrderRootType.CLASSES).map { VfsUtilCore.virtualToIoFile(it) } + val task = ComparingJarFilesTask(project, downloadedFiles, libraryFiles) + task.queue() + if (task.cancelled) return + + if (!task.filesAreTheSame) { + val ok = LibraryJarsDiffDialog(task.libraryFileToCompare!!, task.downloadedFileToCompare!!, mavenCoordinates, LibraryUtil.getPresentableName(library), project).showAndGet() + task.deleteTemporaryFiles() + if (!ok) { + return + } + } + ApplicationManager.getApplication().invokeLater { + replaceByLibrary(library, object : NewLibraryConfiguration(library.name ?: "", RepositoryLibraryType.getInstance(), libraryProperties) { + override fun addRoots(editor: LibraryEditor) { + editor.addRoots(roots) + } + }) + } + } + + private fun detectOrSpecifyMavenCoordinates(library: Library): JpsMavenRepositoryLibraryDescriptor? { + val detectedCoordinates = detectMavenCoordinates(library.getFiles(OrderRootType.CLASSES)) + LOG.debug("Maven coordinates for ${LibraryUtil.getPresentableName(library)} JARs: $detectedCoordinates") + if (detectedCoordinates.size == 1) { + return detectedCoordinates[0] + } + val message = if (detectedCoordinates.isEmpty()) "Cannot detect Maven coordinates from the library JARs" else "Multiple Maven coordinates are found in the library JARs" + if (Messages.showYesNoDialog(project, "$message. Do you want to search Maven repositories manually?", "Cannot Detect Maven Coordinates", null) != Messages.YES) { + return null + } + val dialog = RepositoryAttachDialog(project, detectedCoordinates.firstOrNull()?.mavenId, RepositoryAttachDialog.Mode.SEARCH) + if (!dialog.showAndGet()) { + return null + } + + return JpsMavenRepositoryLibraryDescriptor(dialog.coordinateText) + } + + private fun replaceByLibrary(library: Library, configuration: NewLibraryConfiguration) { + val annotationUrls = library.getUrls(AnnotationOrderRootType.getInstance()) + replaceLibrary(library) { editor -> + editor.properties = configuration.properties + editor.removeAllRoots() + configuration.addRoots(editor) + annotationUrls.forEach { editor.addRoot(it, AnnotationOrderRootType.getInstance()) } + } + } + + protected abstract fun replaceLibrary(library: Library, configureNewLibrary: (LibraryEditorBase) -> Unit) + + companion object { + fun detectMavenCoordinates(libraryRoots: Array): List = + libraryRoots.flatMap { root -> + val pomPropertiesFiles = root.findFileByRelativePath("META-INF/maven")?.children?.flatMap { groupDir -> + groupDir.children?.mapNotNull { artifactDir -> + artifactDir.findChild("pom.properties") + } ?: emptyList() + } ?: emptyList() + pomPropertiesFiles.mapNotNull { parsePomProperties(it) } + } + + private fun parsePomProperties(virtualFile: VirtualFile): JpsMavenRepositoryLibraryDescriptor? { + val properties = Properties() + try { + virtualFile.inputStream.use { properties.load(it) } + } + catch(e: IOException) { + return null + } + val groupId = properties.getProperty("groupId") + val artifactId = properties.getProperty("artifactId") + val version = properties.getProperty("version") + return if (groupId != null && artifactId != null && version != null) JpsMavenRepositoryLibraryDescriptor(groupId, artifactId, version) else null + } + } +} + +private class ComparingJarFilesTask(project: Project, private val downloadedFiles: List, + private val libraryFiles: List) : Task.Modal(project, "Comparing JAR Files...", true) { + var cancelled = false + var filesAreTheSame = false + var downloadedFileToCompare: VirtualFile? = null + var libraryFileToCompare: VirtualFile? = null + val filesToDelete = ArrayList() + + override fun run(indicator: ProgressIndicator) { + filesAreTheSame = filesAreTheSame() + + if (!filesAreTheSame) { + val libraryIoFileToCompare: File + val downloadedIoFileToCompare: File + if (libraryFiles.size == 1 && downloadedFiles.size == 1) { + libraryIoFileToCompare = libraryFiles[0] + //ensure that the files have the same name so DirDiffViewer will show differences between them properly + if (downloadedFiles[0].name == libraryFiles[0].name) { + downloadedIoFileToCompare = downloadedFiles[0] + } + else { + val downloadedFilesIoDir = FileUtil.createTempDirectory("downloaded_file", "", true) + downloadedIoFileToCompare = File(downloadedFilesIoDir, libraryFiles[0].name) + FileUtil.copy(downloadedFiles[0], downloadedIoFileToCompare) + filesToDelete += downloadedFilesIoDir + } + } + else { + //probably we can enhance DirDiffViewer to accept set of files to avoid copying files into temporary directory + libraryIoFileToCompare = FileUtil.createTempDirectory("library_files", "", true) + filesToDelete += libraryIoFileToCompare + libraryFiles.forEach { FileUtil.copy(it, File(libraryIoFileToCompare, it.name)) } + + downloadedIoFileToCompare = FileUtil.createTempDirectory("downloaded_files", "", true) + downloadedFiles.forEach { FileUtil.copy(it, File(downloadedIoFileToCompare, it.name)) } + filesToDelete += downloadedIoFileToCompare + } + + object : WriteAction() { + override fun run(result: Result) { + libraryFileToCompare = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(libraryIoFileToCompare)!! + downloadedFileToCompare = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(downloadedIoFileToCompare)!! + } + }.execute() + RefreshQueue.getInstance().refresh(false, true, null, libraryFileToCompare, downloadedFileToCompare) + } + } + + fun deleteTemporaryFiles() { + FileUtil.asyncDelete(filesToDelete) + } + + override fun onCancel() { + cancelled = true + } + + private fun filesAreTheSame(): Boolean { + LOG.debug {"Downloaded files: ${downloadedFiles.joinToString { "${it.name} (${it.length()} bytes)" }}"} + LOG.debug {"Library files: ${libraryFiles.joinToString { "${it.name} (${it.length()} bytes)" }}"} + if (downloadedFiles.size != libraryFiles.size) { + return false + } + val contentHashing = object : TObjectHashingStrategy { + override fun computeHashCode(file: File) = file.length().toInt() + + override fun equals(o1: File, o2: File): Boolean { + val equal = contentEqual(o1, o2) + LOG.debug(" comparing files: ${o1.absolutePath}${if (equal) "==" else "!="}${o2.absolutePath}") + return equal + } + } + return THashSet(downloadedFiles, contentHashing) == THashSet(libraryFiles, contentHashing) + } + + private fun contentEqual(file1: File, file2: File): Boolean { + if (file1.length() != file2.length()) return false + + file1.inputStream().use { input1 -> + file2.inputStream().use { input2 -> + val buffer1 = ByteArray(4096) + val buffer2 = ByteArray(4096) + while (true) { + val len1 = input1.read(buffer1) + val len2 = input2.read(buffer2) + if (len1 != len2) return false + if (len1 <= 0) break + for (i in 0..len1 - 1) { + if (buffer1[i] != buffer2[i]) return false + } + } + return true + } + } + } +} diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/LibraryJarsDiffDialog.kt b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/LibraryJarsDiffDialog.kt new file mode 100644 index 000000000000..04bcca31ed2c --- /dev/null +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/LibraryJarsDiffDialog.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2000-2017 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.roots.ui.configuration.projectRoot + +import com.intellij.diff.DiffManager +import com.intellij.diff.DiffRequestFactory +import com.intellij.diff.DiffRequestPanel +import com.intellij.diff.requests.ContentDiffRequest +import com.intellij.ide.diff.DirDiffSettings +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.ui.components.JBLabel +import com.intellij.xml.util.XmlStringUtil +import org.jetbrains.jps.model.library.JpsMavenRepositoryLibraryDescriptor + +/** + * @author nik + */ +class LibraryJarsDiffDialog(libraryFile: VirtualFile, + downloadedFile: VirtualFile, + private val mavenCoordinates: JpsMavenRepositoryLibraryDescriptor, + private val libraryName: String, + project: Project) : DialogWrapper(project) { + + private val panel: DiffRequestPanel + + init { + title = "Replace Library" + setOKButtonText("Replace") + val request: ContentDiffRequest = DiffRequestFactory.getInstance().createFromFiles(project, libraryFile, downloadedFile) + panel = DiffManager.getInstance().createRequestPanel(project, disposable, window) + panel.putContextHints(DirDiffSettings.KEY, DirDiffSettings().apply { enableChoosers = false }) + panel.setRequest(request) + init() + } + + override fun createNorthPanel() = JBLabel(XmlStringUtil.wrapInHtml("${mavenCoordinates.mavenId} JARs differ from '$libraryName' library JARs.")) + + override fun createCenterPanel() = panel.component + + override fun getPreferredFocusedComponent() = panel.preferredFocusedComponent +} \ No newline at end of file diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ProjectLibrariesConfigurable.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ProjectLibrariesConfigurable.java index 817cebb96fa2..83b8b54125d0 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ProjectLibrariesConfigurable.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ProjectLibrariesConfigurable.java @@ -15,15 +15,20 @@ */ package com.intellij.openapi.roots.ui.configuration.projectRoot; +import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectBundle; import com.intellij.openapi.roots.libraries.LibraryTablePresentation; import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import java.util.Collections; +import java.util.List; + public class ProjectLibrariesConfigurable extends BaseLibrariesConfigurable { public ProjectLibrariesConfigurable(final Project project) { super(project, LibraryTablesRegistrar.PROJECT_LEVEL); @@ -67,6 +72,16 @@ public class ProjectLibrariesConfigurable extends BaseLibrariesConfigurable { return LibraryTablesRegistrar.getInstance().getLibraryTable(myProject).getPresentation(); } + @NotNull + @Override + protected List createCopyActions(boolean fromPopup) { + List actions = super.createCopyActions(fromPopup); + if (fromPopup) { + return ContainerUtil.concat(actions, Collections.singletonList(new ConvertProjectLibraryToRepositoryLibraryAction(this, myContext))); + } + return actions; + } + @Override protected String getAddText() { return ProjectBundle.message("add.new.project.library.text"); diff --git a/java/idea-ui/src/org/jetbrains/idea/maven/utils/library/RepositoryUtils.java b/java/idea-ui/src/org/jetbrains/idea/maven/utils/library/RepositoryUtils.java index 85ede972ddf3..6a8cdbf8ba9e 100644 --- a/java/idea-ui/src/org/jetbrains/idea/maven/utils/library/RepositoryUtils.java +++ b/java/idea-ui/src/org/jetbrains/idea/maven/utils/library/RepositoryUtils.java @@ -24,6 +24,7 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.AnnotationOrderRootType; import com.intellij.openapi.roots.JavadocOrderRootType; import com.intellij.openapi.roots.OrderRootType; import com.intellij.openapi.roots.impl.libraries.LibraryEx; @@ -118,6 +119,7 @@ public class RepositoryUtils { return; } final RepositoryLibraryProperties properties = (RepositoryLibraryProperties)library.getProperties(); + String[] annotationUrls = library.getUrls(AnnotationOrderRootType.getInstance()); JarRepositoryManager.loadDependenciesAsync( project, properties, downloadSources, downloadJavaDocs, null, copyTo, @@ -134,6 +136,9 @@ public class RepositoryUtils { editor.setKeepInvalidUrls(false); editor.removeAllRoots(); editor.addRoots(roots); + for (String url : annotationUrls) { + editor.addRoot(url, AnnotationOrderRootType.getInstance()); + } final Library.ModifiableModel model = library.getModifiableModel(); editor.applyTo((LibraryEx.ModifiableModelEx)model); model.commit(); diff --git a/java/idea-ui/testSrc/com/intellij/openapi/roots/ui/configuration/projectRoot/ConvertToRepositoryLibraryActionTest.kt b/java/idea-ui/testSrc/com/intellij/openapi/roots/ui/configuration/projectRoot/ConvertToRepositoryLibraryActionTest.kt new file mode 100644 index 000000000000..80657c7ef4ad --- /dev/null +++ b/java/idea-ui/testSrc/com/intellij/openapi/roots/ui/configuration/projectRoot/ConvertToRepositoryLibraryActionTest.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2000-2017 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.roots.ui.configuration.projectRoot + +import com.intellij.openapi.application.PathManager +import com.intellij.openapi.vfs.VfsUtil +import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.testFramework.LightPlatformTestCase + +/** + * @author nik + */ +class ConvertToRepositoryLibraryActionTest : LightPlatformTestCase() { + fun `test read Maven coordinates`() { + val jarFile = PathManager.findFileInLibDirectory("commons-codec-1.9.jar") + val file = VirtualFileManager.getInstance().refreshAndFindFileByUrl(VfsUtil.getUrlForLibraryRoot(jarFile)) + assertNotNull(jarFile.absolutePath, file) + val coordinates = assertOneElement(ConvertToRepositoryLibraryActionBase.detectMavenCoordinates(arrayOf(file!!))) + assertEquals("commons-codec", coordinates.groupId) + assertEquals("commons-codec", coordinates.artifactId) + assertEquals("1.9", coordinates.version) + } +} \ No newline at end of file diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java index 7cfe2cb889d4..96ee18fed728 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java @@ -79,8 +79,6 @@ import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static com.intellij.openapi.util.Pair.pair; - /** * @author cdr * @since Jul 30, 2002 @@ -1722,37 +1720,36 @@ public class HighlightUtil extends HighlightUtilBase { return accessProblemTextAndFixes(ref, resolved, result).first; } - private static Pair> accessProblemTextAndFixes(PsiElement reference, - PsiElement resolved, - JavaResolveResult result) { + @NotNull + private static Pair> accessProblemTextAndFixes(@NotNull PsiElement reference, + @NotNull PsiElement resolved, + @NotNull JavaResolveResult result) { assert resolved instanceof PsiModifierListOwner : resolved; PsiModifierListOwner refElement = (PsiModifierListOwner)resolved; String symbolName = HighlightMessageUtil.getSymbolName(refElement, result.getSubstitutor()); if (refElement.hasModifierProperty(PsiModifier.PRIVATE)) { String containerName = getContainerName(refElement, result.getSubstitutor()); - return pair(JavaErrorMessages.message("private.symbol", symbolName, containerName), null); + return Pair.create(JavaErrorMessages.message("private.symbol", symbolName, containerName), null); } - else if (refElement.hasModifierProperty(PsiModifier.PROTECTED)) { + if (refElement.hasModifierProperty(PsiModifier.PROTECTED)) { String containerName = getContainerName(refElement, result.getSubstitutor()); - return pair(JavaErrorMessages.message("protected.symbol", symbolName, containerName), null); + return Pair.create(JavaErrorMessages.message("protected.symbol", symbolName, containerName), null); + } + PsiClass packageLocalClass = getPackageLocalClassInTheMiddle(reference); + if (packageLocalClass != null) { + refElement = packageLocalClass; + symbolName = HighlightMessageUtil.getSymbolName(refElement, result.getSubstitutor()); + } + if (refElement.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) || packageLocalClass != null) { + String containerName = getContainerName(refElement, result.getSubstitutor()); + return Pair.create(JavaErrorMessages.message("package.local.symbol", symbolName, containerName), null); } else { - PsiClass packageLocalClass = getPackageLocalClassInTheMiddle(reference); - if (packageLocalClass != null) { - refElement = packageLocalClass; - symbolName = HighlightMessageUtil.getSymbolName(refElement, result.getSubstitutor()); - } - if (refElement.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) || packageLocalClass != null) { - String containerName = getContainerName(refElement, result.getSubstitutor()); - return pair(JavaErrorMessages.message("package.local.symbol", symbolName, containerName), null); - } - else { - String containerName = getContainerName(refElement, result.getSubstitutor()); - ErrorWithFixes problem = checkModuleAccess(resolved, reference, symbolName, containerName); - if (problem != null) return pair(problem.message, problem.fixes); - return pair(JavaErrorMessages.message("visibility.access.problem", symbolName, containerName), null); - } + String containerName = getContainerName(refElement, result.getSubstitutor()); + ErrorWithFixes problem = checkModuleAccess(resolved, reference, symbolName, containerName); + if (problem != null) return Pair.create(problem.message, problem.fixes); + return Pair.create(JavaErrorMessages.message("visibility.access.problem", symbolName, containerName), null); } } @@ -1775,14 +1772,14 @@ public class HighlightUtil extends HighlightUtilBase { } private static ErrorWithFixes checkAccess(JavaModuleSystemEx system, PsiElement target, PsiElement place) { - if (target instanceof PsiClass) return system.checkAccess(((PsiClass)target), place); - if (target instanceof PsiPackage) return system.checkAccess(((PsiPackage)target), place); + if (target instanceof PsiClass) return system.checkAccess((PsiClass)target, place); + if (target instanceof PsiPackage) return system.checkAccess((PsiPackage)target, place); return null; } private static boolean isAccessible(JavaModuleSystem system, PsiElement target, PsiElement place) { - if (target instanceof PsiClass) return system.isAccessible(((PsiClass)target), place); - if (target instanceof PsiPackage) return system.isAccessible(((PsiPackage)target), place); + if (target instanceof PsiClass) return system.isAccessible((PsiClass)target, place); + if (target instanceof PsiPackage) return system.isAccessible((PsiPackage)target, place); return true; } @@ -2017,7 +2014,7 @@ public class HighlightUtil extends HighlightUtilBase { while (lastChild instanceof PsiComment || lastChild instanceof PsiWhiteSpace) { lastChild = lastChild.getPrevSibling(); } - if (!(PsiUtil.isJavaToken(lastChild, JavaTokenType.COLON))) { + if (!PsiUtil.isJavaToken(lastChild, JavaTokenType.COLON)) { int start = statement.getTextRange().getEndOffset(); int end = statement.getTextRange().getEndOffset() + 1; String description = JavaErrorMessages.message("switch.colon.expected.after.case.label"); diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/ModuleHighlightUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/ModuleHighlightUtil.java index 02c59f4bed9d..268329705651 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/ModuleHighlightUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/ModuleHighlightUtil.java @@ -66,46 +66,50 @@ public class ModuleHighlightUtil { VirtualFile file = fsItem.getVirtualFile(); if (file == null) return null; - VirtualFile root; - Module module; Project project = fsItem.getProject(); ProjectFileIndex index = ProjectFileIndex.SERVICE.getInstance(project); - if ((root = index.getClassRootForFile(file)) != null) { - VirtualFile descriptorFile = root.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE); - if (descriptorFile == null) { - VirtualFile alt = root.findFileByRelativePath("META-INF/versions/9/" + PsiJavaModule.MODULE_INFO_CLS_FILE); - if (alt != null && isMultiReleaseJar(root)) { - descriptorFile = alt; + if (index.isInLibrary(file)) { + VirtualFile root; + if ((root = index.getClassRootForFile(file)) != null) { + VirtualFile descriptorFile = root.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE); + if (descriptorFile == null) { + VirtualFile alt = root.findFileByRelativePath("META-INF/versions/9/" + PsiJavaModule.MODULE_INFO_CLS_FILE); + if (alt != null && isMultiReleaseJar(root)) { + descriptorFile = alt; + } + } + if (descriptorFile != null) { + PsiFile psiFile = PsiManager.getInstance(project).findFile(descriptorFile); + if (psiFile instanceof PsiJavaFile) { + return ((PsiJavaFile)psiFile).getModuleDeclaration(); + } + } + else if (root.getFileSystem() instanceof JarFileSystem && "jar".equalsIgnoreCase(root.getExtension())) { + return LightJavaModule.getModule(PsiManager.getInstance(project), root); } } - if (descriptorFile != null) { - PsiFile psiFile = PsiManager.getInstance(project).findFile(descriptorFile); - if (psiFile instanceof PsiJavaFile) { - return ((PsiJavaFile)psiFile).getModuleDeclaration(); - } - } - else if (root.getFileSystem() instanceof JarFileSystem && "jar".equalsIgnoreCase(root.getExtension())) { - return LightJavaModule.getModule(PsiManager.getInstance(project), root); - } - } - else if ((root = index.getSourceRootForFile(file)) != null) { - VirtualFile descriptorFile = root.findChild(MODULE_INFO_FILE); - if (descriptorFile != null) { - PsiFile psiFile = PsiManager.getInstance(project).findFile(descriptorFile); - if (psiFile instanceof PsiJavaFile) { - return ((PsiJavaFile)psiFile).getModuleDeclaration(); + else if ((root = index.getSourceRootForFile(file)) != null) { + VirtualFile descriptorFile = root.findChild(MODULE_INFO_FILE); + if (descriptorFile != null) { + PsiFile psiFile = PsiManager.getInstance(project).findFile(descriptorFile); + if (psiFile instanceof PsiJavaFile) { + return ((PsiJavaFile)psiFile).getModuleDeclaration(); + } } } } - else if ((module = index.getModuleForFile(file)) != null) { - boolean isTest = index.isInTestSourceContent(file); - List files = FilenameIndex.getVirtualFilesByName(project, MODULE_INFO_FILE, module.getModuleScope()).stream() - .filter(f -> index.isInTestSourceContent(f) == isTest) - .collect(Collectors.toList()); - if (files.size() == 1) { - PsiFile psiFile = PsiManager.getInstance(project).findFile(files.get(0)); - if (psiFile instanceof PsiJavaFile) { - return ((PsiJavaFile)psiFile).getModuleDeclaration(); + else { + Module module = index.getModuleForFile(file); + if (module != null) { + boolean isTest = index.isInTestSourceContent(file); + List files = FilenameIndex.getVirtualFilesByName(project, MODULE_INFO_FILE, module.getModuleScope()).stream() + .filter(f -> index.isInTestSourceContent(f) == isTest) + .collect(Collectors.toList()); + if (files.size() == 1) { + PsiFile psiFile = PsiManager.getInstance(project).findFile(files.get(0)); + if (psiFile instanceof PsiJavaFile) { + return ((PsiJavaFile)psiFile).getModuleDeclaration(); + } } } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/ReplaceWithTernaryOperatorFix.java b/java/java-analysis-impl/src/com/intellij/codeInspection/ReplaceWithTernaryOperatorFix.java index 071268b1e720..75bc6aeef3d0 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/ReplaceWithTernaryOperatorFix.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/ReplaceWithTernaryOperatorFix.java @@ -22,6 +22,7 @@ import com.intellij.psi.*; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.util.PsiTypesUtil; import com.intellij.psi.util.PsiUtil; +import com.siyeh.ig.psiutils.ParenthesesUtils; import org.jetbrains.annotations.NotNull; /** @@ -37,7 +38,7 @@ public class ReplaceWithTernaryOperatorFix implements LocalQuickFix { } public ReplaceWithTernaryOperatorFix(@NotNull PsiExpression expressionToAssert) { - myText = expressionToAssert.getText(); + myText = ParenthesesUtils.getText(expressionToAssert, ParenthesesUtils.BINARY_AND_PRECEDENCE); } @NotNull diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CFGBuilder.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CFGBuilder.java new file mode 100644 index 000000000000..30e276dc5367 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CFGBuilder.java @@ -0,0 +1,602 @@ +/* + * Copyright 2000-2017 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.codeInspection.dataFlow; + +import com.intellij.codeInspection.dataFlow.inliner.CallInliner; +import com.intellij.codeInspection.dataFlow.instructions.*; +import com.intellij.codeInspection.dataFlow.value.DfaUnknownValue; +import com.intellij.codeInspection.dataFlow.value.DfaValue; +import com.intellij.codeInspection.dataFlow.value.DfaValueFactory; +import com.intellij.psi.*; +import com.intellij.psi.impl.light.LightVariableBuilder; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.ObjectUtils; +import one.util.streamex.StreamEx; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +/** + * A facade for building control flow graph used by {@link CallInliner} implementations + */ +@SuppressWarnings("UnusedReturnValue") +public class CFGBuilder { + private final ControlFlowAnalyzer myAnalyzer; + private final Deque myBranches = new ArrayDeque<>(); + private final Map myMethodRefQualifiers = new HashMap<>(); + + CFGBuilder(ControlFlowAnalyzer analyzer) { + myAnalyzer = analyzer; + } + + /** + * Generate instructions to push unknown DfaValue on stack. + *

+ * Stack before: ... + *

+ * Stack after: ... unknown + * + * @return this builder + */ + public CFGBuilder pushUnknown() { + myAnalyzer.pushUnknown(); + return this; + } + + /** + * Generate instructions to push null DfaValue on stack. + *

+ * Stack before: ... + *

+ * Stack after: ... null + * + * @return this builder + */ + public CFGBuilder pushNull() { + myAnalyzer.addInstruction(new PushInstruction(getFactory().getConstFactory().getNull(), null)); + return this; + } + + /** + * Generate instructions to evaluate given expression and push its result on stack. + *

+ * Stack before: ... + *

+ * Stack after: ... expression_result + * + * @param expression expression to evaluate + * @return this builder + */ + public CFGBuilder pushExpression(PsiExpression expression) { + expression.accept(myAnalyzer); + return this; + } + + /** + * Generate instructions to push given variable on stack for subsequent write. + *

+ * Stack before: ... + *

+ * Stack after: ... variable + * + * @param variable to push + * @return this builder + */ + public CFGBuilder pushVariable(PsiVariable variable) { + myAnalyzer.addInstruction( + new PushInstruction(getFactory().getVarFactory().createVariableValue(variable, false), null, true)); + return this; + } + + /** + * Generate instructions to push given DfaValue on stack. + *

+ * Stack before: ... + *

+ * Stack after: ... value + * + * @param value value to push + * @return this builder + */ + public CFGBuilder push(DfaValue value) { + myAnalyzer.addInstruction(new PushInstruction(value, null)); + return this; + } + + /** + * Generate instructions to pop single DfaValue from stack + *

+ * Stack before: ... value + *

+ * Stack after: ... + * + * @return this builder + */ + public CFGBuilder pop() { + myAnalyzer.addInstruction(new PopInstruction()); + return this; + } + + /** + * Generate instructions to duplicate top stack value + *

+ * Stack before: ... value + *

+ * Stack after: ... value value + * + * @return this builder + */ + public CFGBuilder dup() { + myAnalyzer.addInstruction(new DupInstruction()); + return this; + } + + /** + * Generate instructions to pop given number of stack values, then push some or all of popped values referred by indices, + * possibly duplicating them + *

+ * E.g. {@code splice(2, 0, 1, 0)} will change "... val1 val2" stack to "... val2 val1 val2". + * Stack depth is increased by {@code replacement.length - count}. + * + * @param count number of values to pop + * @param replacement replacement indices from 0 to {@code count-1}. Index 0 = top stack value, index 1 = next value and so on. + * @return this builder + */ + public CFGBuilder splice(int count, int... replacement) { + myAnalyzer.addInstruction(new SpliceInstruction(count, replacement)); + return this; + } + + /** + * Generate instructions to swap two top stack values + *

+ * Stack before: ... val1 val2 + *

+ * Stack after: ... val2 val1 + * + * @return this builder + */ + public CFGBuilder swap() { + myAnalyzer.addInstruction(new SwapInstruction()); + return this; + } + + /** + * Generate instructions to invoke the method associated with given method call assuming that method arguments and qualifier + * are already on stack. If vararg call is specified, vararg arguments should be placed as is, without packing into array, + * so number of arguments may differ from number of method parameters. + *

+ * Stack before: ... qualifier arg1 arg2 ... argN + *

+ * Stack after: ... return value + *

+ * Note that qualifier must be present even if method is static (use {@link #pushUnknown()}). Similarly, return value will be pushed + * on stack always, even if method is void. + * + * @param call a method call to generate invocation upon + * @return this builder + */ + public CFGBuilder invoke(PsiMethodCallExpression call) { + myAnalyzer.addBareCall(call, call.getMethodExpression()); + return this; + } + + /** + * Generate instructions to compare two values on top of stack with given relation operation (e.g. {@link JavaTokenType#GT}). + *

+ * Stack before: ... val1 val2 + *

+ * Stack after: ... result_of_val1_relation_val2 + * + * @param relation relation to use for comparison + * @return this builder + */ + private CFGBuilder compare(IElementType relation) { + myAnalyzer.addInstruction(new BinopInstruction(relation, null, myAnalyzer.getContext().getProject())); + return this; + } + + /** + * Generate instructions to start a conditional block based on stack top value, consuming this value + *

+ * Stack before: ... condition + *

+ * Stack after: ... + *

+ * The conditional block must end with {@link #endIf()} and may contain one {@link #elseBranch()} inside. + * Nested conditional blocks are acceptable. + * + * @param value a value condition must have to visit conditional block + * @return this builder + */ + public CFGBuilder ifConditionIs(boolean value) { + ConditionalGotoInstruction gotoInstruction = new ConditionalGotoInstruction(null, value, null); + myBranches.add(gotoInstruction); + myAnalyzer.addInstruction(gotoInstruction); + return this; + } + + /** + * Generate instructions to start a conditional block based on result of comparison of + * two stack values with given relation (e.g. {@link JavaTokenType#GT}), consuming these values. + *

+ * Stack before: ... val1 val2 + *

+ * Stack after: ... + *

+ * The conditional block must end with {@link #endIf()} and may contain one {@link #elseBranch()} inside. + * Nested conditional blocks are acceptable. + * + * @param relation a relation to use to compare two stack values. Conditional block will be executed if "val1 relation val2" is true. + * @return this builder + */ + public CFGBuilder ifCondition(IElementType relation) { + return compare(relation).ifConditionIs(true); + } + + /** + * Generate instructions to start a conditional block which is executed if top stack value is not null. + *

+ * Stack before: ... value + *

+ * Stack after: ... + *

+ * The conditional block must end with {@link #endIf()} and may contain one {@link #elseBranch()} inside. + * Nested conditional blocks are acceptable. + * + * @return this builder + */ + public CFGBuilder ifNotNull() { + return pushNull().ifCondition(JavaTokenType.NE); + } + + /** + * Generate instructions to start a conditional block which is executed if top stack value is null. + *

+ * Stack before: ... value + *

+ * Stack after: ... + *

+ * The conditional block must end with {@link #endIf()} and may contain one {@link #elseBranch()} inside. + * Nested conditional blocks are acceptable. + * + * @return this builder + */ + public CFGBuilder ifNull() { + return pushNull().ifCondition(JavaTokenType.EQEQ); + } + + /** + * Generate instructions to finish a conditional block started with {@link #ifCondition(IElementType)}, {@link #ifConditionIs(boolean)}, + * {@link #ifNull()} or {@link #ifNotNull()}. Stack is unchanged. + * + * @return this builder + */ + public CFGBuilder endIf() { + myBranches.removeLast().setOffset(myAnalyzer.getInstructionCount()); + return this; + } + + /** + * Generate instructions to finish a "then-branch" and start an "else-branch" of a conditional block started + * with {@link #ifCondition(IElementType)}, {@link #ifConditionIs(boolean)}, {@link #ifNull()} or {@link #ifNotNull()}. + * Stack is unchanged. + * + * @return this builder + */ + public CFGBuilder elseBranch() { + GotoInstruction gotoInstruction = new GotoInstruction(null); + myAnalyzer.addInstruction(gotoInstruction); + endIf(); + myBranches.add(gotoInstruction); + return this; + } + + /** + * Generate instructions to start a loop. Stack is unchanged. Loop must be terminated via {@link #endWhileUnknown()}. + * Nested loops are acceptable. + * + * @return this builder + */ + public CFGBuilder doWhile() { + ConditionalGotoInstruction jump = new ConditionalGotoInstruction(null, false, null); + jump.setOffset(myAnalyzer.getInstructionCount()); + myBranches.add(jump); + return this; + } + + /** + * Generate instructions to end a loop started via {@link #doWhile()} by unknown condition. Stack is unchanged. + * + * @return this builder + */ + public CFGBuilder endWhileUnknown() { + pushUnknown(); + myAnalyzer.addInstruction((ConditionalGotoInstruction)myBranches.removeLast()); + return this; + } + + /** + * Generate instructions to box or unbox stack top value if necessary to satisfy the specified expected type. + *

+ * Stack before: ... value + *

+ * Stack after: ... boxed_or_unboxed_value + * + * @param expression an expression which result is placed on the top of stack + * @param expectedType an expected type + * + * @return this builder + */ + public CFGBuilder boxUnbox(PsiExpression expression, PsiType expectedType) { + myAnalyzer.generateBoxingUnboxingInstructionFor(expression, expectedType); + return this; + } + + /** + * Generate instructions to box or unbox stack top value if necessary to satisfy the specified expected type. + *

+ * Stack before: ... value + *

+ * Stack after: ... boxed_or_unboxed_value + * + * @param expression an expression which is used to anchor instructions so issued warnings can point to this expression + * @param expressionType an actual type of the expression on top of stack + * @param expectedType an expected type + * + * @return this builder + */ + public CFGBuilder boxUnbox(PsiExpression expression, PsiType expressionType, PsiType expectedType) { + myAnalyzer.generateBoxingUnboxingInstructionFor(expression, expressionType, expectedType); + return this; + } + + /** + * Generate instructions to flush known values of non-final fields of mutable classes. + * + * @return this builder + */ + public CFGBuilder flushFields() { + myAnalyzer.addInstruction(new FlushVariableInstruction(null)); + return this; + } + + /** + * Generate instructions to check that stack top value is not null issuing a warning like "argument is nullable" if + * this is not satisfied. Stack is unchanged. + * + * @param expression an anchor expression to bind a warning to + * @param problem a type of nullability problem to report if value is nullable + * @return this builder + */ + public CFGBuilder checkNotNull(PsiExpression expression, NullabilityProblem problem) { + myAnalyzer.addInstruction(new CheckNotNullInstruction(expression, problem)); + return this; + } + + /** + * Generate instructions to assign top stack value to the second stack value + * (usually pushed via {@link #pushVariable(PsiVariable)}). + *

+ * Stack before: ... variable_for_write value + *

+ * Stack after: ... variable + * + * @return this builder + */ + public CFGBuilder assign() { + myAnalyzer.addInstruction(new AssignInstruction(null, null)); + return this; + } + + /** + * Generate instructions to assign top stack value to the specified variable + *

+ * Stack before: ... value + *

+ * Stack after: ... variable + * + * @return this builder + */ + public CFGBuilder assignTo(PsiVariable var) { + return pushVariable(var).swap().assign(); + } + + /** + * Returns a {@link DfaValueFactory} associated with current control flow. + * + * @return a {@link DfaValueFactory} associated with current control flow. + */ + public DfaValueFactory getFactory() { + return myAnalyzer.getFactory(); + } + + /** + * Generate instructions to evaluate functional expression (but not invoke the function itself + * -- see {@link #invokeFunction(int, PsiExpression)}). Stack is unchanged. + * + * @param functionalExpression a functional expression to evaluate + * @return this builder + */ + public CFGBuilder evaluateFunction(@Nullable PsiExpression functionalExpression) { + PsiExpression stripped = PsiUtil.deparenthesizeExpression(functionalExpression); + if (stripped == null || stripped instanceof PsiLambdaExpression) { + return this; + } + if (stripped instanceof PsiMethodReferenceExpression) { + PsiMethodReferenceExpression methodRef = (PsiMethodReferenceExpression)stripped; + PsiExpression qualifier = methodRef.getQualifierExpression(); + if (qualifier != null && !PsiMethodReferenceUtil.isStaticallyReferenced(methodRef)) { + PsiVariable qualifierBinding = createTempVariable(qualifier.getType()); + pushVariable(qualifierBinding) + .pushExpression(qualifier) + .dup(); + myAnalyzer.addInstruction(new FieldReferenceInstruction(qualifier, ControlFlowAnalyzer.METHOD_REFERENCE_QUALIFIER_SYNTHETIC_FIELD)); + assign().pop(); + myMethodRefQualifiers.put(methodRef, qualifierBinding); + } else { + pushExpression(methodRef).pop(); + } + return this; + } + return pushExpression(functionalExpression) + .checkNotNull(functionalExpression, NullabilityProblem.passingNullableToNotNullParameter) + .pop(); + } + + /** + * Generates instructions to invoke functional expression (inlining it if possible) which + * consumes given amount of stack arguments, assuming that it was previously evaluated + * (see {@link #evaluateFunction(PsiExpression)}). + * + * @param argCount number of stack arguments to consume + * @param functionalExpression a functional expression to invoke + * @return this builder + */ + public CFGBuilder invokeFunction(int argCount, @Nullable PsiExpression functionalExpression) { + return invokeFunction(argCount, functionalExpression, false); + } + + /** + * Generates instructions to invoke functional expression (inlining it if possible) which + * consumes given amount of stack arguments, assuming that it was previously evaluated + * (see {@link #evaluateFunction(PsiExpression)}). + * + * @param argCount number of stack arguments to consume + * @param functionalExpression a functional expression to invoke + * @param forceNotNullResult if true, function result will be forced to not-null (possibly issuing a warning) + * @return this builder + */ + public CFGBuilder invokeFunction(int argCount, @Nullable PsiExpression functionalExpression, boolean forceNotNullResult) { + PsiExpression stripped = PsiUtil.deparenthesizeExpression(functionalExpression); + if (stripped instanceof PsiLambdaExpression) { + PsiLambdaExpression lambda = (PsiLambdaExpression)stripped; + PsiParameter[] parameters = lambda.getParameterList().getParameters(); + if (parameters.length == argCount && lambda.getBody() != null) { + StreamEx.ofReversed(parameters).forEach(p -> assignTo(p).pop()); + return inlineLambda(lambda, forceNotNullResult); + } + } + if (stripped instanceof PsiMethodReferenceExpression) { + PsiMethodReferenceExpression methodRef = (PsiMethodReferenceExpression)stripped; + JavaResolveResult resolveResult = methodRef.advancedResolve(false); + PsiMethod method = ObjectUtils.tryCast(resolveResult.getElement(), PsiMethod.class); + if (method != null && !method.isVarArgs()) { + int expectedArgCount = method.getParameterList().getParametersCount(); + boolean pushQualifier = true; + if (!method.hasModifierProperty(PsiModifier.STATIC) && !method.isConstructor()) { + pushQualifier = !PsiMethodReferenceUtil.isStaticallyReferenced(methodRef); + if (!pushQualifier) { + expectedArgCount++; // qualifier is already on stack for statically referenced method ref + } + } + if (argCount == expectedArgCount) { + if (pushQualifier) { + PsiVariable qualifierVar = myMethodRefQualifiers.remove(methodRef); + DfaValue qualifierValue = qualifierVar == null ? DfaUnknownValue.getInstance() : + getFactory().getVarFactory().createVariableValue(qualifierVar, false); + push(qualifierValue); + moveTopValue(argCount); + } + myAnalyzer.addBareCall(null, methodRef); + myAnalyzer.generateBoxingUnboxingInstructionFor(methodRef, resolveResult.getSubstitutor().substitute(method.getReturnType()), + LambdaUtil.getFunctionalInterfaceReturnType(methodRef)); + if (forceNotNullResult) { + checkNotNull(methodRef, NullabilityProblem.nullableFunctionReturn); + } + return this; + } + } + PsiElement qualifier = methodRef.getQualifier(); + if(qualifier instanceof PsiTypeElement && ((PsiTypeElement)qualifier).getType() instanceof PsiArrayType) { + // like String[]::new + splice(argCount) + .push(getFactory().createTypeValue(((PsiTypeElement)qualifier).getType(), Nullness.NOT_NULL)); + return this; + } + } + splice(argCount); + if (functionalExpression == null) { + pushUnknown(); + return this; + } + // Unknown function + flushFields(); + PsiType returnType = LambdaUtil.getFunctionalInterfaceReturnType(functionalExpression.getType()); + if (returnType != null) { + push(getFactory().createTypeValue(returnType, DfaPsiUtil.getTypeNullability(returnType))); + } + else { + pushUnknown(); + } + return this; + } + + /** + * Generate instructions to move top stack value to the specified depth + *

+ * Stack before: ... val#1 val#2 ... val#depth topValue + *

+ * Stack after: ... topValue val#1 val#2 ... val#depth + * + * @param depth a desired depth for the top stack value + */ + private void moveTopValue(int depth) { + if (depth > 0) { + int[] permutation = new int[depth + 1]; + for (int i = 1; i < permutation.length; i++) { + permutation[i] = depth + 1 - i; + } + splice(depth + 1, permutation); + } + } + + public CFGBuilder inlineLambda(PsiLambdaExpression lambda, boolean forceNotNullResult) { + myAnalyzer.inlineLambda(lambda, forceNotNullResult); + return this; + } + + /** + * Create a temporary {@link PsiVariable} (not declared in the original code) to be used within this control flow. + * + * @param type a type of variable to create + * @return newly created variable + */ + @NotNull + public PsiVariable createTempVariable(@Nullable PsiType type) { + if(type == null) { + type = PsiType.VOID; + } + return new LightVariableBuilder<>("tmp$" + myAnalyzer.getInstructionCount(), type, myAnalyzer.getContext()); + } + + /** + * A convenient method to chain specific builder operation + * + * @param operation to execute on this builder + * @return this builder + */ + public CFGBuilder chain(Consumer operation) { + operation.accept(this); + return this; + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java index 4b03ac9c8202..b45115b90ea2 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java @@ -17,6 +17,7 @@ package com.intellij.codeInspection.dataFlow; import com.intellij.codeInsight.AnnotationUtil; import com.intellij.codeInsight.ExceptionUtil; +import com.intellij.codeInspection.dataFlow.inliner.*; import com.intellij.codeInspection.dataFlow.instructions.*; import com.intellij.codeInspection.dataFlow.value.*; import com.intellij.codeInspection.dataFlow.value.DfaRelationValue.RelationType; @@ -46,8 +47,10 @@ import static com.intellij.psi.CommonClassNames.*; public class ControlFlowAnalyzer extends JavaElementVisitor { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.ControlFlowAnalyzer"); public static final String ORG_JETBRAINS_ANNOTATIONS_CONTRACT = Contract.class.getName(); + static final String METHOD_REFERENCE_QUALIFIER_SYNTHETIC_FIELD = "Method reference qualifier"; private final PsiElement myCodeFragment; - private boolean myIgnoreAssertions; + private final boolean myIgnoreAssertions; + private final boolean myInlining; private final Project myProject; private static class CannotAnalyzeException extends RuntimeException { } @@ -58,8 +61,11 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { private final ExceptionTransfer myRuntimeException; private final ExceptionTransfer myError; private final PsiType myAssertionError; + private PsiLambdaExpression myLambdaExpression = null; + private boolean myForceNotNullLambdaResult = false; - ControlFlowAnalyzer(final DfaValueFactory valueFactory, @NotNull PsiElement codeFragment, boolean ignoreAssertions) { + ControlFlowAnalyzer(final DfaValueFactory valueFactory, @NotNull PsiElement codeFragment, boolean ignoreAssertions, boolean inlining) { + myInlining = inlining; myFactory = valueFactory; myCodeFragment = codeFragment; myProject = codeFragment.getProject(); @@ -96,6 +102,13 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { return myCurrentFlow; } + DfaValueFactory getFactory() { + return myFactory; + } + + PsiElement getContext() { + return myCodeFragment; + } private PsiClassType createClassType(GlobalSearchScope scope, String fqn) { PsiClass aClass = JavaPsiFacade.getInstance(myProject).findClass(fqn, scope); @@ -103,11 +116,15 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { return JavaPsiFacade.getElementFactory(myProject).createTypeByFQClassName(fqn, scope); } - private T addInstruction(T i) { + T addInstruction(T i) { myCurrentFlow.addInstruction(i); return i; } + int getInstructionCount() { + return myCurrentFlow.getInstructionCount(); + } + private ControlFlow.ControlFlowOffset getEndOffset(PsiElement element) { return myCurrentFlow.getEndOffset(element); } @@ -644,10 +661,22 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { generateBoxingUnboxingInstructionFor(returnValue, LambdaUtil.getFunctionalInterfaceReturnType(lambdaExpression)); } } - addInstruction(new CheckReturnValueInstruction(returnValue)); } - addInstruction(new ReturnInstruction(myFactory.controlTransfer(ReturnTransfer.INSTANCE, myTrapStack), statement)); + if (myLambdaExpression == null) { + if (returnValue != null) { + addInstruction(new CheckReturnValueInstruction(returnValue)); + } + addInstruction(new ReturnInstruction(myFactory.controlTransfer(ReturnTransfer.INSTANCE, myTrapStack), statement)); + } + else { + if (returnValue == null) { + pushUnknown(); + } else if (myForceNotNullLambdaResult) { + addInstruction(new CheckNotNullInstruction(returnValue, NullabilityProblem.nullableFunctionReturn)); + } + controlTransfer(new InstructionTransfer(getEndOffset(myLambdaExpression), getVariablesInside(myLambdaExpression)), myTrapStack); + } finishElement(statement); } @@ -746,7 +775,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { PsiExpression qualifier = expression.getQualifierExpression(); if (qualifier != null) { qualifier.accept(this); - addInstruction(new FieldReferenceInstruction(qualifier, "Method reference qualifier")); + addInstruction(new FieldReferenceInstruction(qualifier, METHOD_REFERENCE_QUALIFIER_SYNTHETIC_FIELD)); } addInstruction(new PushInstruction(myFactory.createTypeValue(expression.getFunctionalInterfaceType(), Nullness.NOT_NULL), expression)); @@ -825,6 +854,11 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { } return DfaInstructionState.EMPTY_ARRAY; } + + @Override + public String toString() { + return "APPLY NOT NULL"; + } } @Override @@ -1115,24 +1149,26 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { } } - private void generateBoxingUnboxingInstructionFor(@NotNull PsiExpression expression, PsiType expectedType) { + void generateBoxingUnboxingInstructionFor(@NotNull PsiExpression expression, PsiType expectedType) { + generateBoxingUnboxingInstructionFor(expression, expression.getType(), expectedType); + } + + void generateBoxingUnboxingInstructionFor(@NotNull PsiExpression context, PsiType actualType, PsiType expectedType) { if (PsiType.VOID.equals(expectedType)) return; - PsiType exprType = expression.getType(); - - if (TypeConversionUtil.isPrimitiveAndNotNull(expectedType) && TypeConversionUtil.isPrimitiveWrapper(exprType)) { - addInstruction(new MethodCallInstruction(expression, MethodCallInstruction.MethodType.UNBOXING, expectedType)); + if (TypeConversionUtil.isPrimitiveAndNotNull(expectedType) && TypeConversionUtil.isPrimitiveWrapper(actualType)) { + addInstruction(new MethodCallInstruction(context, MethodCallInstruction.MethodType.UNBOXING, expectedType)); } - else if (TypeConversionUtil.isPrimitiveAndNotNull(exprType) && TypeConversionUtil.isAssignableFromPrimitiveWrapper(expectedType)) { + else if (TypeConversionUtil.isPrimitiveAndNotNull(actualType) && TypeConversionUtil.isAssignableFromPrimitiveWrapper(expectedType)) { addConditionalRuntimeThrow(); - addInstruction(new MethodCallInstruction(expression, MethodCallInstruction.MethodType.BOXING, expectedType)); + addInstruction(new MethodCallInstruction(context, MethodCallInstruction.MethodType.BOXING, expectedType)); } - else if (exprType != expectedType && - TypeConversionUtil.isPrimitiveAndNotNull(exprType) && + else if (actualType != expectedType && + TypeConversionUtil.isPrimitiveAndNotNull(actualType) && TypeConversionUtil.isPrimitiveAndNotNull(expectedType) && - TypeConversionUtil.isNumericType(exprType) && + TypeConversionUtil.isNumericType(actualType) && TypeConversionUtil.isNumericType(expectedType)) { - addInstruction(new MethodCallInstruction(expression, MethodCallInstruction.MethodType.CAST, expectedType) { + addInstruction(new MethodCallInstruction(context, MethodCallInstruction.MethodType.CAST, expectedType) { @Override public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState stateBefore, InstructionVisitor visitor) { return visitor.visitCast(this, runner, stateBefore); @@ -1281,7 +1317,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { finishElement(expression); } - private void pushUnknown() { + void pushUnknown() { addInstruction(new PushInstruction(DfaUnknownValue.getInstance(), null)); } @@ -1333,6 +1369,15 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { @Override public void visitMethodCallExpression(PsiMethodCallExpression expression) { startElement(expression); + if (myInlining) { + for (CallInliner inliner : INLINERS) { + if (inliner.tryInlineCall(new CFGBuilder(this), expression)) { + finishElement(expression); + return; + } + } + } + PsiReferenceExpression methodExpression = expression.getMethodExpression(); PsiExpression qualifierExpression = methodExpression.getQualifierExpression(); @@ -1360,40 +1405,17 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { } if (i == 0 && isEqualsCall) { // stack: .., qualifier, arg1 - addInstruction(new SwapInstruction()); - // stack: .., arg1, qualifier - addInstruction(new DupInstruction(2, 1)); - // stack: .., arg1, qualifier, arg1, qualifier - addInstruction(new PopInstruction()); + addInstruction(new SpliceInstruction(2, 0, 1, 0)); // stack: .., arg1, qualifier, arg1 } } - addConditionalRuntimeThrow(); - List contracts = - method instanceof PsiMethod ? getMethodCallContracts((PsiMethod)method, expression) : Collections.emptyList(); - addInstruction(new MethodCallInstruction(expression, myFactory.createValue(expression), contracts)); - if (contracts.stream().anyMatch(c -> c.getReturnValue() == MethodContract.ValueConstraint.THROW_EXCEPTION)) { - // if a contract resulted in 'fail', handle it - addInstruction(new DupInstruction()); - addInstruction(new PushInstruction(myFactory.getConstFactory().getContractFail(), null)); - addInstruction(new BinopInstruction(JavaTokenType.EQEQ, null, myProject)); - ConditionalGotoInstruction ifNotFail = new ConditionalGotoInstruction(null, true, null); - addInstruction(ifNotFail); - addInstruction(new EmptyStackInstruction()); - addInstruction(new ReturnInstruction(myFactory.controlTransfer(new ExceptionTransfer(DfaUnknownValue.getInstance()), myTrapStack), expression)); - - ifNotFail.setOffset(myCurrentFlow.getInstructionCount()); - } - - if (!myTrapStack.isEmpty()) { - addMethodThrows(expression.resolveMethod(), expression); - } + addBareCall(expression, expression.getMethodExpression()); if (isEqualsCall) { // assume equals argument must be not-null if the result is true // don't assume the call result to be false if arg1==null - + // stack: .., arg1, call-result ConditionalGotoInstruction ifFalse = addInstruction(new ConditionalGotoInstruction(null, true, null)); @@ -1408,6 +1430,41 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { finishElement(expression); } + void addBareCall(@Nullable PsiMethodCallExpression expression, @NotNull PsiReferenceExpression reference) { + addConditionalRuntimeThrow(); + PsiMethod method = ObjectUtils.tryCast(reference.resolve(), PsiMethod.class); + List contracts = method == null ? Collections.emptyList() : getMethodCallContracts(method, expression); + MethodCallInstruction instruction; + PsiExpression anchor; + if (expression == null) { + assert reference instanceof PsiMethodReferenceExpression; + instruction = new MethodCallInstruction((PsiMethodReferenceExpression)reference, contracts); + anchor = reference; + } + else { + instruction = new MethodCallInstruction(expression, myFactory.createValue(expression), contracts); + anchor = expression; + } + addInstruction(instruction); + if (contracts.stream().anyMatch(c -> c.getReturnValue() == MethodContract.ValueConstraint.THROW_EXCEPTION)) { + // if a contract resulted in 'fail', handle it + addInstruction(new DupInstruction()); + addInstruction(new PushInstruction(myFactory.getConstFactory().getContractFail(), null)); + addInstruction(new BinopInstruction(JavaTokenType.EQEQ, null, myProject)); + ConditionalGotoInstruction ifNotFail = new ConditionalGotoInstruction(null, true, null); + addInstruction(ifNotFail); + addInstruction(new EmptyStackInstruction()); + addInstruction( + new ReturnInstruction(myFactory.controlTransfer(new ExceptionTransfer(DfaUnknownValue.getInstance()), myTrapStack), anchor)); + + ifNotFail.setOffset(myCurrentFlow.getInstructionCount()); + } + + if (!myTrapStack.isEmpty()) { + addMethodThrows(method, anchor); + } + } + public static List getMethodCallContracts(@NotNull final PsiMethod method, @Nullable PsiMethodCallExpression call) { List contracts = HardcodedContracts.getHardcodedContracts(method, call); @@ -1650,5 +1707,38 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { @Override public void visitClass(PsiClass aClass) { } + void inlineLambda(PsiLambdaExpression lambda, boolean forceNotNullResult) { + PsiLambdaExpression oldLambda = myLambdaExpression; + boolean oldForceNotNullLambdaResult = myForceNotNullLambdaResult; + // Transfer value is pushed to avoid emptying stack beyond this point + addInstruction(new PushInstruction(myFactory.controlTransfer(ReturnTransfer.INSTANCE, this.myTrapStack), null)); + myLambdaExpression = lambda; + myForceNotNullLambdaResult = forceNotNullResult; + startElement(lambda); + try { + PsiElement body = lambda.getBody(); + Objects.requireNonNull(body).accept(this); + if (body instanceof PsiCodeBlock) { + // return value for void or incomplete lambda + pushUnknown(); + } + else if (body instanceof PsiExpression) { + generateBoxingUnboxingInstructionFor((PsiExpression)body, LambdaUtil.getFunctionalInterfaceReturnType(lambda)); + if (myForceNotNullLambdaResult) { + addInstruction(new CheckNotNullInstruction((PsiExpression)body, NullabilityProblem.nullableFunctionReturn)); + } + } + } + finally { + finishElement(lambda); + myLambdaExpression = oldLambda; + myForceNotNullLambdaResult = oldForceNotNullLambdaResult; + // Pop transfer value (which is second value in stack now) + addInstruction(new SpliceInstruction(2, 0)); + } + } + + static final CallInliner[] INLINERS = {new OptionalChainInliner(), new LambdaInliner(), new CollectionFactoryInliner(), + new StreamChainInliner()}; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java index 57c88dd26b04..de9a9dcf6f7d 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java @@ -24,10 +24,7 @@ import com.intellij.codeInsight.daemon.impl.quickfix.SimplifyBooleanExpressionFi import com.intellij.codeInsight.intention.impl.AddNotNullAnnotationFix; import com.intellij.codeInsight.intention.impl.AddNullableAnnotationFix; import com.intellij.codeInspection.*; -import com.intellij.codeInspection.dataFlow.fix.RedundantInstanceofFix; -import com.intellij.codeInspection.dataFlow.fix.ReplaceWithConstantValueFix; -import com.intellij.codeInspection.dataFlow.fix.ReplaceWithObjectsEqualsFix; -import com.intellij.codeInspection.dataFlow.fix.SimplifyToAssignmentFix; +import com.intellij.codeInspection.dataFlow.fix.*; import com.intellij.codeInspection.dataFlow.instructions.*; import com.intellij.codeInspection.dataFlow.value.DfaConstValue; import com.intellij.codeInspection.dataFlow.value.DfaUnknownValue; @@ -51,6 +48,7 @@ import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import com.siyeh.ig.psiutils.ComparisonUtils; import com.siyeh.ig.psiutils.ExpressionUtils; +import com.siyeh.ig.psiutils.SideEffectChecker; import com.siyeh.ig.psiutils.TypeUtils; import one.util.streamex.StreamEx; import org.jdom.Element; @@ -270,7 +268,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { if (isVolatileFieldReference(qualifier)) { ContainerUtil.addIfNotNull(fixes, createIntroduceVariableFix(qualifier)); } - else if (!isNullLiteral(qualifier) && !(qualifier instanceof PsiMethodCallExpression)) { + else if (!isNullLiteral(qualifier) && !SideEffectChecker.mayHaveSideEffects(qualifier)) { if (PsiUtil.getLanguageLevel(qualifier).isAtLeast(LanguageLevel.JDK_1_4)) { final Project project = qualifier.getProject(); final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(project).getElementFactory(); @@ -286,6 +284,10 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { } } + if (!isNullLiteral(qualifier) && PsiUtil.isLanguageLevel7OrHigher(qualifier)) { + fixes.add(new SurroundWithRequireNonNullFix(qualifier)); + } + ContainerUtil.addIfNotNull(fixes, DfaOptionalSupport.registerReplaceOptionalOfWithOfNullableFix(qualifier)); } catch (IncorrectOperationException e) { @@ -333,7 +335,12 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { HashSet reportedAnchors = new HashSet<>(); for (PsiElement element : visitor.getProblems(NullabilityProblem.callNPE)) { if (reportedAnchors.add(element)) { - reportCallMayProduceNpe(holder, (PsiMethodCallExpression)element, holder.isOnTheFly()); + if (element instanceof PsiMethodReferenceExpression) { + holder.registerProblem(element, InspectionsBundle.message("dataflow.message.npe.methodref.invocation")); + } + else { + reportCallMayProduceNpe(holder, (PsiMethodCallExpression)element, holder.isOnTheFly()); + } } } for (PsiElement element : visitor.getProblems(NullabilityProblem.fieldAccessNPE)) { @@ -358,6 +365,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { reportConstantPushes(runner, holder, visitor, reportedAnchors); + reportNullableFunctions(visitor, holder, reportedAnchors); reportNullableArguments(visitor, holder, reportedAnchors); reportNullableAssignments(visitor, holder, reportedAnchors); reportUnboxedNullables(visitor, holder, reportedAnchors); @@ -502,20 +510,20 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { private static void reportOptionalOfNullableImprovements(ProblemsHolder holder, Set reportedAnchors, Instruction[] instructions) { for (Instruction instruction : instructions) { if (instruction instanceof MethodCallInstruction) { - final PsiExpression[] args = ((MethodCallInstruction)instruction).getArgs(); - if (args.length != 1) continue; + MethodCallInstruction methodCall = (MethodCallInstruction)instruction; + if (methodCall.getArgCount() != 1) continue; - final PsiExpression expr = args[0]; + final PsiElement arg = methodCall.getArgumentAnchor(0); - if (((MethodCallInstruction)instruction).isOptionalAlwaysNullProblem()) { - if (!reportedAnchors.add(expr)) continue; - holder.registerProblem(expr, "Passing null argument to Optional", - DfaOptionalSupport.createReplaceOptionalOfNullableWithEmptyFix(expr)); + if (methodCall.isOptionalAlwaysNullProblem()) { + if (!reportedAnchors.add(arg)) continue; + holder.registerProblem(arg, "Passing null argument to Optional", + DfaOptionalSupport.createReplaceOptionalOfNullableWithEmptyFix(arg)); } - else if (((MethodCallInstruction)instruction).isOptionalAlwaysNotNullProblem()) { - if (!reportedAnchors.add(expr)) continue; - holder.registerProblem(expr, "Passing a non-null argument to Optional", - DfaOptionalSupport.createReplaceOptionalOfNullableWithOfFix()); + else if (methodCall.isOptionalAlwaysNotNullProblem()) { + if (!reportedAnchors.add(arg)) continue; + holder.registerProblem(arg, "Passing a non-null argument to Optional", + DfaOptionalSupport.createReplaceOptionalOfNullableWithOfFix(arg)); } } @@ -546,6 +554,11 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { for (PsiElement expr : visitor.getProblems(NullabilityProblem.passingNullableArgumentToNonAnnotatedParameter)) { if (reportedAnchors.contains(expr)) continue; + if (expr.getParent() instanceof PsiMethodReferenceExpression) { + holder.registerProblem(expr.getParent(), "Method reference argument might be null but passed to non annotated parameter"); + continue; + } + final String text = isNullLiteralExpression(expr) ? "Passing null argument to non annotated parameter" : "Argument #ref #loc might be null but passed to non annotated parameter"; @@ -687,15 +700,27 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { visitor.silenceConstantCondition(psiAnchor); } + private static void reportNullableFunctions(DataFlowInstructionVisitor visitor, ProblemsHolder holder, Set reportedAnchors) { + for (PsiElement expr : visitor.getProblems(NullabilityProblem.nullableFunctionReturn)) { + if (!reportedAnchors.add(expr)) continue; + holder.registerProblem(expr, InspectionsBundle.message("dataflow.message.return.nullable.from.notnull.function")); + } + } + private void reportNullableArguments(DataFlowInstructionVisitor visitor, ProblemsHolder holder, Set reportedAnchors) { for (PsiElement expr : visitor.getProblems(NullabilityProblem.passingNullableToNotNullParameter)) { if (!reportedAnchors.add(expr)) continue; - final String text = isNullLiteralExpression(expr) - ? InspectionsBundle.message("dataflow.message.passing.null.argument") - : InspectionsBundle.message("dataflow.message.passing.nullable.argument"); - List fixes = createNPEFixes((PsiExpression)expr, (PsiExpression)expr, holder.isOnTheFly()); - holder.registerProblem(expr, text, fixes.toArray(LocalQuickFix.EMPTY_ARRAY)); + if (expr.getParent() instanceof PsiMethodReferenceExpression) { + holder.registerProblem(expr.getParent(), InspectionsBundle.message("dataflow.message.passing.nullable.argument.methodref")); + } + else { + final String text = isNullLiteralExpression(expr) + ? InspectionsBundle.message("dataflow.message.passing.null.argument") + : InspectionsBundle.message("dataflow.message.passing.nullable.argument"); + List fixes = createNPEFixes((PsiExpression)expr, (PsiExpression)expr, holder.isOnTheFly()); + holder.registerProblem(expr, text, fixes.toArray(LocalQuickFix.EMPTY_ARRAY)); + } } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java index 4ce5270446e1..7922667774c2 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowRunner.java @@ -33,7 +33,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; -import java.util.function.Predicate; public class DataFlowRunner { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.DataFlowRunner"); @@ -41,11 +40,9 @@ public class DataFlowRunner { private Instruction[] myInstructions; private final MultiMap myNestedClosures = new MultiMap<>(); - // Closures which were registered for previous instruction and can be queried by visitor - // to adjust them somehow - private final Map myStackTopClosures = new HashMap<>(); @NotNull private final DfaValueFactory myValueFactory; + private boolean myInlining = true; // Maximum allowed attempts to process instruction. Fail as too complex to process if certain instruction // is executed more than this limit times. static final int MAX_STATES_PER_BRANCH = 300; @@ -69,7 +66,14 @@ public class DataFlowRunner { if (container != null && (!(container instanceof PsiClass) || PsiUtil.isLocalOrAnonymousClass((PsiClass)container))) { PsiElement block = DfaPsiUtil.getTopmostBlockInSameClass(container.getParent()); if (block != null) { - final RunnerResult result = analyzeMethod(block, visitor); + final RunnerResult result; + try { + myInlining = false; + result = analyzeMethod(block, visitor); + } + finally { + myInlining = true; + } if (result == RunnerResult.OK) { final Collection closureStates = myNestedClosures.get(DfaPsiUtil.getTopmostBlockInSameClass(psiBlock)); if (!closureStates.isEmpty()) { @@ -83,14 +87,6 @@ public class DataFlowRunner { return Collections.singletonList(createMemoryState()); } - void updateStackTopClosures(Predicate updater) { - myStackTopClosures.forEach((state, element) -> { - if(!updater.test(state)) { - myNestedClosures.remove(element, state); - } - }); - } - @NotNull public final RunnerResult analyzeMethod(@NotNull PsiElement psiBlock, @NotNull InstructionVisitor visitor) { Collection initialStates = createInitialStates(psiBlock, visitor); @@ -103,15 +99,14 @@ public class DataFlowRunner { boolean ignoreAssertions, @NotNull Collection initialStates) { try { - final ControlFlow flow = new ControlFlowAnalyzer(myValueFactory, psiBlock, ignoreAssertions).buildControlFlow(); + final ControlFlow flow = new ControlFlowAnalyzer(myValueFactory, psiBlock, ignoreAssertions, myInlining).buildControlFlow(); if (flow == null) return RunnerResult.NOT_APPLICABLE; int[] loopNumber = LoopAnalyzer.calcInLoop(flow); int endOffset = flow.getInstructionCount(); myInstructions = flow.getInstructions(); myNestedClosures.clear(); - myStackTopClosures.clear(); - + Set joinInstructions = ContainerUtil.newHashSet(); for (int index = 0; index < myInstructions.length; index++) { Instruction instruction = myInstructions[index]; @@ -282,7 +277,6 @@ public class DataFlowRunner { Instruction instruction = instructionState.getInstruction(); DfaInstructionState[] states = instruction.accept(this, instructionState.getMemoryState(), visitor); - myStackTopClosures.clear(); PsiElement closure = DfaUtil.getClosureInside(instruction); if (closure instanceof PsiClass) { registerNestedClosures(instructionState, (PsiClass)closure); @@ -318,9 +312,7 @@ public class DataFlowRunner { } private void createClosureState(PsiElement anchor, DfaMemoryState state) { - DfaMemoryState closureState = state.createClosureState(); - myStackTopClosures.put(closureState, anchor); - myNestedClosures.putValue(anchor, closureState); + myNestedClosures.putValue(anchor, state.createClosureState()); } @NotNull diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java index 7b584c4db565..a074d58d29ed 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java @@ -627,9 +627,9 @@ public class DfaMemoryStateImpl implements DfaMemoryState { } DfaConstValue dfaNull = myFactory.getConstFactory().getNull(); - int c1Index = getEqClassIndex(dfaVar); + Integer c1Index = getOrCreateEqClassIndex(dfaVar); int c2Index = getEqClassIndex(dfaNull); - if (c1Index < 0 || c2Index < 0) { + if (c1Index == null || c2Index < 0) { return false; } @@ -1032,7 +1032,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState { } } if (value instanceof DfaVariableValue) { - DfaVariableState state = myVariableStates.get((DfaVariableValue)value); + DfaVariableState state = findVariableState((DfaVariableValue)value); if (state != null) { T fact = state.getFact(factType); if (fact != null) { @@ -1073,9 +1073,31 @@ public class DfaMemoryStateImpl implements DfaMemoryState { } myCachedHash = null; } + + private DfaVariableState findVariableState(DfaVariableValue var) { + DfaVariableState state = myVariableStates.get(var); + if (state != null) { + return state; + } + DfaVariableValue qualifier = var.getQualifier(); + if (qualifier == null) return null; + int qualifierIndex = getEqClassIndex(qualifier); + if (qualifierIndex == -1) return null; + for (DfaValue eqQualifier : myEqClasses.get(qualifierIndex).getMemberValues()) { + if (eqQualifier != qualifier && eqQualifier instanceof DfaVariableValue) { + DfaVariableValue eqValue = getFactory().getVarFactory() + .createVariableValue(var.getPsiVariable(), var.getVariableType(), var.isNegated(), (DfaVariableValue)eqQualifier); + state = myVariableStates.get(eqValue); + if (state != null) { + return state; + } + } + } + return null; + } DfaVariableState getVariableState(DfaVariableValue dfaVar) { - DfaVariableState state = myVariableStates.get(dfaVar); + DfaVariableState state = findVariableState(dfaVar); if (state == null) { state = myDefaultVariableStates.get(dfaVar); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaOptionalSupport.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaOptionalSupport.java index 380673d953f0..b13b1a33a61b 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaOptionalSupport.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaOptionalSupport.java @@ -22,6 +22,8 @@ import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; +import com.siyeh.ig.callMatcher.CallMatcher; +import com.siyeh.ig.psiutils.ExpressionUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -30,7 +32,11 @@ import org.jetbrains.annotations.Nullable; */ public class DfaOptionalSupport { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.DfaOptionalSupport"); - private static final String GUAVA_OPTIONAL = "com.google.common.base.Optional"; + public static final String GUAVA_OPTIONAL = "com.google.common.base.Optional"; + + public static final CallMatcher JDK_OPTIONAL_OF_NULLABLE = CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "ofNullable").parameterCount(1); + public static final CallMatcher GUAVA_OPTIONAL_FROM_NULLABLE = CallMatcher.staticCall(GUAVA_OPTIONAL, "fromNullable").parameterCount(1); + public static final CallMatcher OPTIONAL_OF_NULLABLE = CallMatcher.anyOf(JDK_OPTIONAL_OF_NULLABLE, GUAVA_OPTIONAL_FROM_NULLABLE); @Nullable static LocalQuickFix registerReplaceOptionalOfWithOfNullableFix(@NotNull PsiExpression qualifier) { @@ -59,34 +65,20 @@ public class DfaOptionalSupport { } return null; } - private static boolean isJdkOptional(@NotNull PsiElement anchor) { - final PsiElement parent = findCallExpression(anchor); - PsiMethod method = parent == null ? null : resolveOfNullable(findCallExpression(anchor)); - return method != null && "ofNullable".equals(method.getName()); - } - @NotNull + @Nullable static LocalQuickFix createReplaceOptionalOfNullableWithEmptyFix(@NotNull PsiElement anchor) { - return new ReplaceOptionalCallFix(isJdkOptional(anchor) ? "empty" : "absent", true); - } - - @NotNull - static LocalQuickFix createReplaceOptionalOfNullableWithOfFix() { - return new ReplaceOptionalCallFix("of", false); + final PsiMethodCallExpression parent = findCallExpression(anchor); + if (parent == null) return null; + boolean jdkOptional = JDK_OPTIONAL_OF_NULLABLE.test(parent); + return new ReplaceOptionalCallFix(jdkOptional ? "empty" : "absent", true); } @Nullable - public static PsiMethod resolveOfNullable(@NotNull PsiMethodCallExpression expression) { - String name = expression.getMethodExpression().getReferenceName(); - if ("ofNullable".equals(name) || "fromNullable".equals(name)) { - PsiMethod method = expression.resolveMethod(); - PsiClass psiClass = method == null ? null : method.getContainingClass(); - String qname = psiClass == null ? null : psiClass.getQualifiedName(); - if (CommonClassNames.JAVA_UTIL_OPTIONAL.equals(qname) || GUAVA_OPTIONAL.equals(qname)) { - return method; - } - } - return null; + static LocalQuickFix createReplaceOptionalOfNullableWithOfFix(@NotNull PsiElement anchor) { + final PsiMethodCallExpression parent = findCallExpression(anchor); + if (parent == null) return null; + return new ReplaceOptionalCallFix("of", false); } static boolean isOptionalGetMethodName(String name) { @@ -113,15 +105,7 @@ public class DfaOptionalSupport { final PsiMethodCallExpression methodCallExpression = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiMethodCallExpression.class); if (methodCallExpression != null) { - final PsiElement ofNullableExprName = - ((PsiMethodCallExpression)JavaPsiFacade.getElementFactory(project) - .createExpressionFromText("Optional." + myTargetMethodName + "(null)", null)).getMethodExpression(); - final PsiElement referenceNameElement = methodCallExpression.getMethodExpression().getReferenceNameElement(); - if (referenceNameElement != null) { - final PsiElement ofNullableNameElement = ((PsiReferenceExpression)ofNullableExprName).getReferenceNameElement(); - LOG.assertTrue(ofNullableNameElement != null); - referenceNameElement.replace(ofNullableNameElement); - } + ExpressionUtils.bindCallTo(methodCallExpression, myTargetMethodName); if (myClearArguments) { PsiExpressionList argList = methodCallExpression.getArgumentList(); PsiExpression[] args = argList.getExpressions(); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java index 8c1777873ec2..e44381b3120e 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaPsiUtil.java @@ -117,8 +117,8 @@ public class DfaPsiUtil { @NotNull public static Nullness getTypeNullability(@Nullable PsiType type) { - if (type == null) return Nullness.UNKNOWN; - + if (type == null || type instanceof PsiPrimitiveType) return Nullness.UNKNOWN; + Ref result = Ref.create(Nullness.UNKNOWN); InheritanceUtil.processSuperTypes(type, true, eachType -> { result.set(getTypeOwnNullability(result, eachType)); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InstructionVisitor.java index e02f15c29870..edfb55b59f67 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InstructionVisitor.java @@ -19,7 +19,6 @@ import com.intellij.codeInspection.dataFlow.instructions.*; import com.intellij.codeInspection.dataFlow.value.DfaUnknownValue; import com.intellij.codeInspection.dataFlow.value.DfaValue; import com.intellij.codeInspection.dataFlow.value.DfaVariableValue; -import com.intellij.psi.PsiExpression; import java.util.ArrayList; @@ -34,6 +33,10 @@ public abstract class InstructionVisitor { return nextInstruction(instruction, runner, memState); } + public DfaInstructionState[] visitCheckNotNull(CheckNotNullInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { + return nextInstruction(instruction, runner, memState); + } + protected static DfaInstructionState[] nextInstruction(Instruction instruction, DataFlowRunner runner, DfaMemoryState memState) { return new DfaInstructionState[]{new DfaInstructionState(runner.getInstruction(instruction.getIndex() + 1), memState)}; } @@ -138,8 +141,7 @@ public abstract class InstructionVisitor { } public DfaInstructionState[] visitMethodCall(MethodCallInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { - //noinspection UnusedDeclaration - for (PsiExpression arg : instruction.getArgs()) { + for(int i = instruction.getArgCount(); i > 0; i--) { memState.pop(); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblem.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblem.java index 9240f5d3383e..b5c62a649954 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblem.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblem.java @@ -9,6 +9,7 @@ public enum NullabilityProblem { unboxingNullable, assigningToNotNull, nullableReturn, + nullableFunctionReturn, passingNullableToNotNullParameter, passingNullableArgumentToNonAnnotatedParameter } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullnessUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullnessUtil.java index 1359dd4d8bb1..25c1454415f6 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullnessUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullnessUtil.java @@ -148,6 +148,13 @@ public class NullnessUtil { PsiElement target = ((PsiReferenceExpression)expression).resolve(); return DfaPsiUtil.getElementNullability(expression.getType(), (PsiModifierListOwner)target); } + if (expression instanceof PsiAssignmentExpression) { + PsiAssignmentExpression assignment = (PsiAssignmentExpression)expression; + if(assignment.getOperationTokenType().equals(JavaTokenType.EQ)) { + return getExpressionNullness(assignment.getRExpression()); + } + return Nullness.NOT_NULL; + } if (expression instanceof PsiMethodCallExpression) { PsiMethod method = ((PsiMethodCallExpression)expression).resolveMethod(); return method != null ? DfaPsiUtil.getElementNullability(expression.getType(), method) : Nullness.UNKNOWN; diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java index bd85b23b2828..f51ec9dcdffc 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java @@ -29,11 +29,9 @@ import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.TypeConversionUtil; import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.FactoryMap; import com.intellij.util.containers.MultiMap; import com.siyeh.ig.callMatcher.CallMapper; import com.siyeh.ig.callMatcher.CallMatcher; -import com.siyeh.ig.psiutils.TypeUtils; import gnu.trove.THashSet; import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; @@ -49,9 +47,6 @@ public class StandardInstructionVisitor extends InstructionVisitor { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.StandardInstructionVisitor"); private static final Object ANY_VALUE = new Object(); - private static final Set OPTIONAL_METHOD_NAMES = ContainerUtil - .set("of", "ofNullable", "fromNullable", "empty", "absent", "or", "orElse", "orElseGet", "ifPresent", "map", "flatMap", "filter", - "transform"); private static final CallMapper KNOWN_METHOD_RANGES = new CallMapper() .register(CallMatcher.instanceCall("java.time.LocalDateTime", "getHour"), LongRangeSet.range(0, 23)) .register(CallMatcher.instanceCall("java.time.LocalDateTime", "getMinute", "getSecond"), LongRangeSet.range(0, 59)) @@ -65,16 +60,6 @@ public class StandardInstructionVisitor extends InstructionVisitor { private final MultiMap myPossibleVariableValues = MultiMap.createSet(); private final Set myNotToReportReachability = new THashSet<>(); private final Set myUsefulInstanceofs = new THashSet<>(); - @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") - private final Map myReturnTypeNullability = FactoryMap.createMap(key-> { - final PsiCall callExpression = key.getCallExpression(); - if (callExpression instanceof PsiNewExpression) { - return Nullness.NOT_NULL; - } - - return callExpression != null ? DfaPsiUtil.getElementNullability(key.getResultType(), callExpression.resolveMethod()) : null; - } - ); @Override public DfaInstructionState[] visitAssign(AssignInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { @@ -308,7 +293,6 @@ public class StandardInstructionVisitor extends InstructionVisitor { @Override public DfaInstructionState[] visitMethodCall(final MethodCallInstruction instruction, final DataFlowRunner runner, final DfaMemoryState memState) { Set finalStates = ContainerUtil.newLinkedHashSet(); - finalStates.addAll(handleOptionalMethods(instruction, runner, memState)); finalStates.addAll(handleKnownMethods(instruction, runner, memState)); if (finalStates.isEmpty()) { @@ -363,65 +347,6 @@ public class StandardInstructionVisitor extends InstructionVisitor { return states; } - @NotNull - private List handleOptionalMethods(MethodCallInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { - PsiMethodCallExpression call = ObjectUtils.tryCast(instruction.getCallExpression(), PsiMethodCallExpression.class); - if (call == null) return Collections.emptyList(); - String methodName = call.getMethodExpression().getReferenceName(); - if (methodName == null || !OPTIONAL_METHOD_NAMES.contains(methodName)) return Collections.emptyList(); - PsiMethod method = call.resolveMethod(); - if (method == null || !TypeUtils.isOptional(method.getContainingClass())) return Collections.emptyList(); - DfaCallArguments arguments = popCall(instruction, runner, memState, false); - DfaValue[] argValues = arguments.myArguments; - DfaValue result = null; - DfaValueFactory factory = runner.getFactory(); - switch (methodName) { - case "of": - case "ofNullable": - case "fromNullable": - if ("of".equals(methodName) || (argValues != null && argValues.length == 1 && memState.isNotNull(argValues[0]))) { - result = factory.getOptionalFactory().getOptional(true); - } - break; - case "empty": - case "absent": - result = factory.getOptionalFactory().getOptional(false); - break; - case "orElse": - if (argValues != null && argValues.length == 1) { - DfaMemoryState falseState = memState.createCopy(); - DfaOptionalValue optional = factory.getOptionalFactory().getOptional(true); - DfaValue relation = factory.createCondition(arguments.myQualifier, RelationType.IS, optional); - List states = new ArrayList<>(2); - if (memState.applyCondition(relation)) { - memState.push(factory.createTypeValue(instruction.getResultType(), Nullness.NOT_NULL)); - states.add(memState); - } - if (falseState.applyCondition(relation.createNegated())) { - falseState.push(argValues[0]); - states.add(falseState); - } - return states; - } - break; - case "filter": - case "flatMap": - case "ifPresent": - case "map": - case "or": - case "orElseGet": - case "transform": { - DfaOptionalValue optional = factory.getOptionalFactory().getOptional(!methodName.startsWith("or")); - DfaValue relation = factory.createCondition(arguments.myQualifier, RelationType.IS, optional); - runner.updateStackTopClosures(state -> state.applyCondition(relation)); - break; - } - default: - } - memState.push(result == null ? getMethodResultValue(instruction, arguments.myQualifier, factory) : result); - return Collections.singletonList(memState); - } - @NotNull private DfaCallArguments popCall(MethodCallInstruction instruction, DataFlowRunner runner, @@ -437,7 +362,7 @@ public class StandardInstructionVisitor extends InstructionVisitor { DataFlowRunner runner, DfaMemoryState memState, boolean contractOnly) { - final PsiExpression[] args = instruction.getArgs(); + final int argCount = instruction.getArgCount(); PsiMethod method = instruction.getTargetMethod(); boolean varargCall = instruction.isVarArgCall(); @@ -447,7 +372,7 @@ public class StandardInstructionVisitor extends InstructionVisitor { } else { PsiParameterList paramList = method.getParameterList(); int paramCount = paramList.getParametersCount(); - if (paramCount == args.length || method.isVarArgs() && args.length >= paramCount - 1) { + if (paramCount == argCount || method.isVarArgs() && argCount >= paramCount - 1) { argValues = new DfaValue[paramCount]; if (varargCall) { argValues[paramCount - 1] = runner.getFactory().createTypeValue(paramList.getParameters()[paramCount - 1].getType(), Nullness.NOT_NULL); @@ -457,22 +382,22 @@ public class StandardInstructionVisitor extends InstructionVisitor { } } - for (int i = 0; i < args.length; i++) { + for (int i = 0; i < argCount; i++) { final DfaValue arg = memState.pop(); - int paramIndex = args.length - i - 1; + int paramIndex = argCount - i - 1; if (argValues != null && (paramIndex < argValues.length - 1 || !varargCall)) { argValues[paramIndex] = arg; } - PsiExpression expr = args[paramIndex]; - Nullness requiredNullability = instruction.getArgRequiredNullability(expr); + PsiElement anchor = instruction.getArgumentAnchor(paramIndex); + Nullness requiredNullability = instruction.getArgRequiredNullability(paramIndex); if (requiredNullability == Nullness.NOT_NULL) { - if (!checkNotNullable(memState, arg, NullabilityProblem.passingNullableToNotNullParameter, expr)) { + if (!checkNotNullable(memState, arg, NullabilityProblem.passingNullableToNotNullParameter, anchor)) { forceNotNull(runner, memState, arg); } } else if (!instruction.updateOfNullable(memState, arg) && requiredNullability == Nullness.UNKNOWN) { - checkNotNullable(memState, arg, NullabilityProblem.passingNullableArgumentToNonAnnotatedParameter, expr); + checkNotNullable(memState, arg, NullabilityProblem.passingNullableArgumentToNonAnnotatedParameter, anchor); } } return argValues; @@ -482,7 +407,7 @@ public class StandardInstructionVisitor extends InstructionVisitor { @NotNull final DfaValue qualifier = memState.pop(); boolean unboxing = instruction.getMethodType() == MethodCallInstruction.MethodType.UNBOXING; NullabilityProblem problem = unboxing ? NullabilityProblem.unboxingNullable : NullabilityProblem.callNPE; - PsiElement anchor = unboxing ? instruction.getContext() : instruction.getCallExpression(); + PsiElement anchor = instruction.getContext(); if (!checkNotNullable(memState, qualifier, problem, anchor)) { forceNotNull(runner, memState, qualifier); } @@ -545,7 +470,9 @@ public class StandardInstructionVisitor extends InstructionVisitor { } @NotNull - private DfaValue getMethodResultValue(MethodCallInstruction instruction, @Nullable DfaValue qualifierValue, DfaValueFactory factory) { + private static DfaValue getMethodResultValue(MethodCallInstruction instruction, + @Nullable DfaValue qualifierValue, + DfaValueFactory factory) { DfaValue precalculated = instruction.getPrecalculatedReturnValue(); if (precalculated != null) { return precalculated; @@ -573,7 +500,7 @@ public class StandardInstructionVisitor extends InstructionVisitor { } if (type != null && !(type instanceof PsiPrimitiveType)) { - Nullness nullability = myReturnTypeNullability.get(instruction); + Nullness nullability = instruction.getReturnNullability(); PsiMethod targetMethod = instruction.getTargetMethod(); if (nullability == Nullness.UNKNOWN && targetMethod != null) { nullability = factory.suggestNullabilityForNonAnnotatedMember(targetMethod); @@ -612,6 +539,24 @@ public class StandardInstructionVisitor extends InstructionVisitor { return notNullable; } + @Override + public DfaInstructionState[] visitCheckNotNull(CheckNotNullInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { + if (!checkNotNullable(memState, memState.peek(), instruction.getProblem(), instruction.getExpression())) { + DfaValue arg = memState.peek(); + if (arg instanceof DfaVariableValue) { + DfaVariableValue var = (DfaVariableValue)arg; + memState.setVarValue(var, runner.getFactory().createTypeValue(var.getVariableType(), Nullness.NOT_NULL)); + } else if (arg instanceof DfaTypeValue) { + memState.pop(); + memState.push(((DfaTypeValue)arg).withNullness(Nullness.NOT_NULL)); + } else if (memState.isNull(arg) && instruction.getProblem() == NullabilityProblem.nullableFunctionReturn) { + memState.pop(); + memState.push(runner.getFactory().createTypeValue(PsiType.VOID, Nullness.NOT_NULL)); + } + } + return super.visitCheckNotNull(instruction, runner, memState); + } + @Override public DfaInstructionState[] visitBinop(BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { myReachable.add(instruction); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/controlTransfer.kt b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/controlTransfer.kt index 243f6832dd17..45320f93beef 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/controlTransfer.kt +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/controlTransfer.kt @@ -38,7 +38,9 @@ class DfaControlTransferValue(factory: DfaValueFactory, interface TransferTarget data class ExceptionTransfer(val throwable: DfaValue) : TransferTarget data class InstructionTransfer(val offset: ControlFlow.ControlFlowOffset, val toFlush: List) : TransferTarget -object ReturnTransfer : TransferTarget +object ReturnTransfer : TransferTarget { + override fun toString(): String = "ReturnTransfer" +} open class ControlTransferInstruction(val transfer: DfaControlTransferValue?) : Instruction() { override fun accept(runner: DataFlowRunner, state: DfaMemoryState, visitor: InstructionVisitor): Array { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/fix/SurroundWithRequireNonNullFix.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/fix/SurroundWithRequireNonNullFix.java new file mode 100644 index 000000000000..460bf941806e --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/fix/SurroundWithRequireNonNullFix.java @@ -0,0 +1,62 @@ +/* + * Copyright 2000-2017 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.codeInspection.dataFlow.fix; + +import com.intellij.codeInspection.InspectionsBundle; +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.JavaPsiFacade; +import com.intellij.psi.PsiExpression; +import com.intellij.psi.SmartPointerManager; +import com.intellij.psi.SmartPsiElementPointer; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +public class SurroundWithRequireNonNullFix implements LocalQuickFix { + private final String myText; + private final SmartPsiElementPointer myQualifierPointer; + + public SurroundWithRequireNonNullFix(@NotNull PsiExpression expressionToSurround) { + myText = expressionToSurround.getText(); + myQualifierPointer = + SmartPointerManager.getInstance(expressionToSurround.getProject()).createSmartPsiElementPointer(expressionToSurround); + } + + @Nls + @NotNull + @Override + public String getName() { + return InspectionsBundle.message("inspection.surround.requirenonnull.quickfix", myText); + } + + @Nls + @NotNull + @Override + public String getFamilyName() { + return InspectionsBundle.message("inspection.surround.requirenonnull.quickfix", ""); + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PsiExpression qualifier = myQualifierPointer.getElement(); + if (qualifier == null) return; + PsiExpression replacement = JavaPsiFacade.getElementFactory(project) + .createExpressionFromText("java.util.Objects.requireNonNull(" + qualifier.getText() + ")", qualifier); + JavaCodeStyleManager.getInstance(project).shortenClassReferences(qualifier.replace(replacement)); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/CallInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/CallInliner.java new file mode 100644 index 000000000000..d31ff96315b0 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/CallInliner.java @@ -0,0 +1,36 @@ +/* + * Copyright 2000-2017 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.codeInspection.dataFlow.inliner; + +import com.intellij.codeInspection.dataFlow.CFGBuilder; +import com.intellij.psi.PsiMethodCallExpression; +import org.jetbrains.annotations.NotNull; + +/** + * A CallInliner can recognize specific method calls and inline their implementation into current CFG + */ +public interface CallInliner { + /** + * Try to inline the supplied call + * + * @param builder a builder to use for inlining. Current state is before given method call (call arguments and qualifier are not + * handled yet). + * @param call a call to inline + * @return true if inlining is successful. In this case subsequent inliners are skipped and default processing is omitted. + * If false is returned, inliner must not emit any instructions via builder. + */ + boolean tryInlineCall(@NotNull CFGBuilder builder, @NotNull PsiMethodCallExpression call); +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/CollectionFactoryInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/CollectionFactoryInliner.java new file mode 100644 index 000000000000..5644994dd80d --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/CollectionFactoryInliner.java @@ -0,0 +1,72 @@ +/* + * Copyright 2000-2017 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.codeInspection.dataFlow.inliner; + +import com.intellij.codeInspection.dataFlow.CFGBuilder; +import com.intellij.codeInspection.dataFlow.Nullness; +import com.intellij.codeInspection.dataFlow.SpecialField; +import com.intellij.codeInspection.dataFlow.value.DfaValueFactory; +import com.intellij.codeInspection.dataFlow.value.DfaVariableValue; +import com.intellij.psi.PsiExpression; +import com.intellij.psi.PsiMethodCallExpression; +import com.intellij.psi.PsiType; +import com.intellij.psi.PsiVariable; +import com.siyeh.ig.callMatcher.CallMapper; +import org.jetbrains.annotations.NotNull; + +import static com.intellij.codeInspection.dataFlow.SpecialField.COLLECTION_SIZE; +import static com.intellij.codeInspection.dataFlow.SpecialField.MAP_SIZE; +import static com.intellij.psi.CommonClassNames.JAVA_UTIL_COLLECTIONS; +import static com.siyeh.ig.callMatcher.CallMatcher.staticCall; + +public class CollectionFactoryInliner implements CallInliner { + static final class FactoryInfo { + int mySize; + SpecialField mySizeField; + + public FactoryInfo(int size, SpecialField sizeField) { + mySize = size; + mySizeField = sizeField; + } + } + + private static final CallMapper STATIC_FACTORIES = new CallMapper() + .register(staticCall(JAVA_UTIL_COLLECTIONS, "emptyList", "emptySet").parameterCount(0), new FactoryInfo(0, COLLECTION_SIZE)) + .register(staticCall(JAVA_UTIL_COLLECTIONS, "singletonList", "singleton").parameterCount(1), new FactoryInfo(1, COLLECTION_SIZE)) + .register(staticCall(JAVA_UTIL_COLLECTIONS, "emptyMap").parameterCount(0), new FactoryInfo(0, MAP_SIZE)) + .register(staticCall(JAVA_UTIL_COLLECTIONS, "singletonMap").parameterCount(2), new FactoryInfo(1, MAP_SIZE)); + + @Override + public boolean tryInlineCall(@NotNull CFGBuilder builder, @NotNull PsiMethodCallExpression call) { + FactoryInfo factoryInfo = STATIC_FACTORIES.mapFirst(call); + if (factoryInfo == null) return false; + PsiExpression[] args = call.getArgumentList().getExpressions(); + for (PsiExpression arg : args) { + builder.pushExpression(arg).pop(); + } + PsiVariable variable = builder.createTempVariable(call.getType()); + DfaValueFactory factory = builder.getFactory(); + DfaVariableValue variableValue = factory.getVarFactory().createVariableValue(variable, false); + builder.pushVariable(variable) // tmpVar = + .push(factory.createTypeValue(call.getType(), Nullness.NOT_NULL)) + .assign() // leave tmpVar on stack: it's result of method call + .push(factoryInfo.mySizeField.createValue(factory, variableValue)) // tmpVar.size = + .push(factory.getConstFactory().createFromValue(factoryInfo.mySize, PsiType.INT, null)) + .assign() + .pop(); + return true; + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/LambdaInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/LambdaInliner.java new file mode 100644 index 000000000000..f83563e264ff --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/LambdaInliner.java @@ -0,0 +1,53 @@ +/* + * Copyright 2000-2017 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.codeInspection.dataFlow.inliner; + +import com.intellij.codeInspection.dataFlow.CFGBuilder; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.ObjectUtils; +import one.util.streamex.EntryStream; +import org.jetbrains.annotations.NotNull; + +/** + * An inliner which is capable to inline a call like ((IntSupplier)(() -> 5)).getAsInt() to the lambda body. + * Works even if lambda body is complex, has several returns, etc. + */ +public class LambdaInliner implements CallInliner { + @Override + public boolean tryInlineCall(@NotNull CFGBuilder builder, @NotNull PsiMethodCallExpression call) { + PsiMethod method = call.resolveMethod(); + if (method == null || method != LambdaUtil.getFunctionalInterfaceMethod(method.getContainingClass())) return false; + PsiTypeCastExpression typeCastExpression = ObjectUtils + .tryCast(PsiUtil.skipParenthesizedExprDown(call.getMethodExpression().getQualifierExpression()), PsiTypeCastExpression.class); + if (typeCastExpression == null) return false; + PsiLambdaExpression lambda = + ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(typeCastExpression.getOperand()), PsiLambdaExpression.class); + if (lambda == null || lambda.getBody() == null) return false; + if (method.isVarArgs()) return false; // TODO: support varargs + PsiExpression[] args = call.getArgumentList().getExpressions(); + PsiParameter[] parameters = lambda.getParameterList().getParameters(); + if (args.length != parameters.length) return false; + EntryStream.zip(args, parameters).forKeyValue((arg, parameter) -> + builder.pushVariable(parameter) + .pushExpression(arg) + .boxUnbox(arg, parameter.getType()) + .assign() + .pop()); + builder.inlineLambda(lambda, false); + return true; + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/OptionalChainInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/OptionalChainInliner.java new file mode 100644 index 000000000000..976219597c18 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/OptionalChainInliner.java @@ -0,0 +1,261 @@ +/* + * Copyright 2000-2017 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.codeInspection.dataFlow.inliner; + +import com.intellij.codeInspection.dataFlow.CFGBuilder; +import com.intellij.codeInspection.dataFlow.NullabilityProblem; +import com.intellij.codeInspection.dataFlow.Nullness; +import com.intellij.codeInspection.dataFlow.value.DfaOptionalValue; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.ArrayUtil; +import com.intellij.util.ObjectUtils; +import com.siyeh.ig.callMatcher.CallMapper; +import com.siyeh.ig.callMatcher.CallMatcher; +import one.util.streamex.StreamEx; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.function.BiConsumer; + +import static com.intellij.codeInspection.dataFlow.DfaOptionalSupport.GUAVA_OPTIONAL; +import static com.intellij.psi.CommonClassNames.JAVA_UTIL_OPTIONAL; +import static com.siyeh.ig.callMatcher.CallMatcher.*; + +/** + * An inliner which is capable to inline some Optional chains like + * {@code Optional.of(xyz).map(lambda).filter(lambda).flatMap(lambda).orElseGet(lambda)} + *

+ * TODO support primitive Optionals + */ +public class OptionalChainInliner implements CallInliner { + private static final CallMatcher OPTIONAL_OR_ELSE = anyOf( + instanceCall(JAVA_UTIL_OPTIONAL, "orElse").parameterCount(1), + instanceCall(GUAVA_OPTIONAL, "or").parameterTypes("T")); + private static final CallMatcher OPTIONAL_OR_NULL = instanceCall(GUAVA_OPTIONAL, "orNull").parameterCount(0); + private static final CallMatcher OPTIONAL_OR_ELSE_GET = anyOf( + instanceCall(JAVA_UTIL_OPTIONAL, "orElseGet").parameterCount(1), + instanceCall(GUAVA_OPTIONAL, "or").parameterTypes("com.google.common.base.Supplier")); + private static final CallMatcher OPTIONAL_OR = instanceCall(JAVA_UTIL_OPTIONAL, "or").parameterCount(1); // Java 9 + private static final CallMatcher OPTIONAL_IF_PRESENT = instanceCall(JAVA_UTIL_OPTIONAL, "ifPresent").parameterCount(1); + private static final CallMatcher OPTIONAL_FILTER = instanceCall(JAVA_UTIL_OPTIONAL, "filter").parameterCount(1); + private static final CallMatcher OPTIONAL_MAP = instanceCall(JAVA_UTIL_OPTIONAL, "map").parameterCount(1); + // Guava transform() throws if function returns null, so handled separately + private static final CallMatcher GUAVA_TRANSFORM = instanceCall(GUAVA_OPTIONAL, "transform").parameterCount(1); + private static final CallMatcher OPTIONAL_FLAT_MAP = instanceCall(JAVA_UTIL_OPTIONAL, "flatMap").parameterCount(1); + private static final CallMatcher OPTIONAL_OF = anyOf( + staticCall(JAVA_UTIL_OPTIONAL, "of", "ofNullable").parameterCount(1), + staticCall(GUAVA_OPTIONAL, "of", "fromNullable").parameterCount(1)); + private static final CallMatcher OPTIONAL_EMPTY = anyOf( + staticCall(JAVA_UTIL_OPTIONAL, "empty").parameterCount(0), + staticCall(GUAVA_OPTIONAL, "absent").parameterCount(0)); + private static final CallMatcher GUAVA_TO_JAVA = + instanceCall(GUAVA_OPTIONAL, "toJavaUtil").parameterCount(0); + + private static final CallMapper> TERMINAL_MAPPER = + new CallMapper>() + .register(OPTIONAL_OR_ELSE, (builder, call) -> { + PsiExpression argument = call.getArgumentList().getExpressions()[0]; + builder.pushExpression(argument) // stack: .. optValue, elseValue + .boxUnbox(argument, call.getType()) + .splice(2, 0, 1, 1) // stack: .. elseValue, optValue, optValue + .ifNotNull() + .swap() // stack: .. optValue, elseValue + .endIf() + .pop(); + }) + .register(OPTIONAL_OR_NULL, (builder, call) -> { + // no op! + }) + .register(OPTIONAL_OR_ELSE_GET, (builder, call) -> { + PsiExpression fn = call.getArgumentList().getExpressions()[0]; + builder + .evaluateFunction(fn) + .dup() + .ifNull() + .pop() + .invokeFunction(0, fn) + .endIf(); + }) + .register(OPTIONAL_IF_PRESENT, (builder, call) -> { + PsiExpression fn = call.getArgumentList().getExpressions()[0]; + builder + .evaluateFunction(fn) + .dup() + .ifNotNull() + .invokeFunction(0, fn) + .elseBranch() + .pop() + .pushUnknown() + .endIf(); + }); + + private static final CallMapper> INTERMEDIATE_MAPPER = + new CallMapper>() + .register(OPTIONAL_MAP, (builder, function) -> inlineMap(builder, function, false)) + .register(GUAVA_TRANSFORM, (builder, function) -> inlineMap(builder, function, true)) + .register(OPTIONAL_FILTER, (builder, function) -> builder + .evaluateFunction(function) + .dup() + .ifNotNull() + .dup() + .invokeFunction(1, function) + .ifConditionIs(false) + .pop() + .pushNull() + .endIf() + .endIf()) + .register(OPTIONAL_FLAT_MAP, (builder, function) -> builder + .dup() + .ifNotNull() + .chain(b -> invokeAndUnwrapOptional(b, 1, function)) + .endIf()) + .register(OPTIONAL_OR, (builder, function) -> builder + .dup() + .ifNull() + .pop() + .chain(b -> invokeAndUnwrapOptional(b, 0, function)) + .endIf()) + .register(GUAVA_TO_JAVA, (builder, stub) -> {/* no op */}); + + @Override + public boolean tryInlineCall(@NotNull CFGBuilder builder, @NotNull PsiMethodCallExpression call) { + BiConsumer terminalInliner = TERMINAL_MAPPER.mapFirst(call); + if (terminalInliner != null) { + PsiExpression qualifierExpression = call.getMethodExpression().getQualifierExpression(); + if (!pushOptionalValue(builder, PsiUtil.skipParenthesizedExprDown(qualifierExpression), call, NullabilityProblem.callNPE)) { + return false; + } + terminalInliner.accept(builder, call); + return true; + } + DfaOptionalValue.Factory optionalFactory = builder.getFactory().getOptionalFactory(); + if (pushIntermediateOperationValue(builder, call)) { + builder.ifNotNull() + .push(optionalFactory.getOptional(true)) + .elseBranch() + .push(optionalFactory.getOptional(false)) + .endIf(); + return true; + } + if (OPTIONAL_EMPTY.test(call)) { + builder.push(optionalFactory.getOptional(false)); + return true; + } + return false; + } + + @Contract("null -> null") + private static PsiType getOptionalElementType(PsiExpression expression) { + if (expression == null) return null; + PsiClassType type = ObjectUtils.tryCast(expression.getType(), PsiClassType.class); + if (type == null) return null; + String rawName = type.rawType().getCanonicalText(); + if (!rawName.equals(JAVA_UTIL_OPTIONAL) && !rawName.equals(GUAVA_OPTIONAL)) return null; + PsiType[] parameters = type.getParameters(); + if (parameters.length != 1) return null; + return parameters[0]; + } + + private static boolean pushOptionalValue(CFGBuilder builder, PsiExpression expression, + PsiExpression dereferenceContext, NullabilityProblem problem) { + PsiType optionalElementType = getOptionalElementType(expression); + if (optionalElementType == null) return false; + if (expression instanceof PsiMethodCallExpression) { + PsiMethodCallExpression qualifierCall = (PsiMethodCallExpression)expression; + if (OPTIONAL_EMPTY.test(qualifierCall)) { + builder.pushNull(); + return true; + } + if (pushIntermediateOperationValue(builder, qualifierCall)) { + builder.assignTo(builder.createTempVariable(optionalElementType)); + return true; + } + } + DfaOptionalValue presentOptional = builder.getFactory().getOptionalFactory().getOptional(true); + builder + .pushExpression(expression) + .checkNotNull(dereferenceContext, problem) + .push(presentOptional) + .ifCondition(JavaTokenType.INSTANCEOF_KEYWORD) + .push(builder.getFactory().createTypeValue(optionalElementType, Nullness.NOT_NULL)) + .elseBranch() + .pushNull() + .endIf() + .assignTo(builder.createTempVariable(optionalElementType)); + return true; + } + + private static boolean pushIntermediateOperationValue(CFGBuilder builder, PsiMethodCallExpression call) { + if (OPTIONAL_OF.test(call)) { + PsiType optionalElementType = getOptionalElementType(call); + inlineOf(builder, optionalElementType, call); + return true; + } + BiConsumer intermediateInliner = INTERMEDIATE_MAPPER.mapFirst(call); + if (intermediateInliner == null) return false; + PsiExpression argument = ArrayUtil.getFirstElement(call.getArgumentList().getExpressions()); + PsiExpression qualifierExpression = call.getMethodExpression().getQualifierExpression(); + if (!pushOptionalValue(builder, PsiUtil.skipParenthesizedExprDown(qualifierExpression), call, NullabilityProblem.callNPE)) return false; + intermediateInliner.accept(builder, argument); + return true; + } + + private static void invokeAndUnwrapOptional(CFGBuilder builder, + int argCount, + PsiExpression function) { + PsiLambdaExpression lambda = ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(function), PsiLambdaExpression.class); + if (lambda != null) { + PsiParameter[] parameters = lambda.getParameterList().getParameters(); + PsiExpression lambdaBody = LambdaUtil.extractSingleExpressionFromBody(lambda.getBody()); + if (parameters.length == argCount && lambdaBody != null) { + StreamEx.ofReversed(parameters).forEach(p -> builder.assignTo(p).pop()); + if (pushOptionalValue(builder, lambdaBody, lambdaBody, NullabilityProblem.nullableFunctionReturn)) { + return; + } + // Restore stack for common invokeFunction + StreamEx.of(parameters).forEach(p -> builder.push(builder.getFactory().getVarFactory().createVariableValue(p, false))); + } + } + builder + .evaluateFunction(function) + .invokeFunction(argCount, function, true) + .pop() + .pushUnknown(); + } + + private static void inlineMap(CFGBuilder builder, PsiExpression function, boolean forceNotNullResult) { + builder + .evaluateFunction(function) + .dup() + .ifNotNull() + .invokeFunction(1, function, forceNotNullResult) + .endIf(); + } + + private static void inlineOf(CFGBuilder builder, PsiType optionalElementType, PsiMethodCallExpression qualifierCall) { + PsiExpression argument = qualifierCall.getArgumentList().getExpressions()[0]; + builder.pushExpression(argument) + .boxUnbox(argument, optionalElementType) + .pushUnknown() // ... arg, ? + .splice(2, 1, 0, 1) // ... arg, ?, arg + .invoke(qualifierCall) // ... arg, opt -- keep original call in CFG so some warnings like "ofNullable for null" can work + .pop(); // ... arg + if ("of".equals(qualifierCall.getMethodExpression().getReferenceName())) { + builder.checkNotNull(argument, NullabilityProblem.passingNullableToNotNullParameter); + } + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java new file mode 100644 index 000000000000..4f753a861118 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/inliner/StreamChainInliner.java @@ -0,0 +1,351 @@ +/* + * Copyright 2000-2017 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.codeInspection.dataFlow.inliner; + +import com.intellij.codeInspection.dataFlow.CFGBuilder; +import com.intellij.codeInspection.dataFlow.DfaPsiUtil; +import com.intellij.codeInspection.dataFlow.NullabilityProblem; +import com.intellij.codeInspection.dataFlow.Nullness; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.ArrayUtil; +import com.intellij.util.ObjectUtils; +import com.siyeh.ig.callMatcher.CallMapper; +import com.siyeh.ig.callMatcher.CallMatcher; +import com.siyeh.ig.psiutils.MethodCallUtils; +import com.siyeh.ig.psiutils.StreamApiUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.function.UnaryOperator; + +import static com.intellij.psi.CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM; +import static com.intellij.psi.CommonClassNames.JAVA_UTIL_STREAM_STREAM; +import static com.siyeh.ig.callMatcher.CallMatcher.anyOf; +import static com.siyeh.ig.callMatcher.CallMatcher.instanceCall; + +public class StreamChainInliner implements CallInliner { + private static final String[] TERMINALS = + {"count", "sum", "summaryStatistics", "reduce", "collect", "findFirst", "findAny", "anyMatch", "allMatch", "noneMatch", "toArray", + "average", "forEach", "forEachOrdered", "min", "max", "toList", "toSet"}; + private static final CallMatcher TERMINAL_CALL = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, TERMINALS); + + private static final CallMatcher LAMBDA_TERMINAL = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "anyMatch", "allMatch", + "noneMatch", "forEach", "forEachOrdered").parameterCount(1); + + private static final CallMatcher SKIP_STEP = + instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "unordered", "parallel", "sequential", "sorted").parameterCount(0); + private static final CallMatcher SORTED = instanceCall(JAVA_UTIL_STREAM_STREAM, "sorted").parameterCount(1); + private static final CallMatcher FILTER = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "filter").parameterCount(1); + private static final CallMatcher STATE_FILTER = anyOf(instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "distinct").parameterCount(0), + instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "skip", "limit").parameterCount(1)); + private static final CallMatcher BOXED = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "boxed").parameterCount(0); + private static final CallMatcher MAP = + instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "map", "mapToInt", "mapToLong", "mapToDouble", "mapToObj").parameterCount(1); + private static final CallMatcher FLAT_MAP = + instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "flatMap", "flatMapToInt", "flatMapToLong", "flatMapToDouble").parameterCount(1); + private static final CallMatcher PEEK = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "peek").parameterCount(1); + + private static final CallMapper> INTERMEDIATE_STEP_MAPPER = new CallMapper>() + .register(FILTER, (PsiMethodCallExpression call) -> (Step next) -> new FilterStep(call, next)) + .register(MAP, (PsiMethodCallExpression call) -> (Step next) -> new MapStep(call, next)) + .register(FLAT_MAP, (PsiMethodCallExpression call) -> (Step next) -> new FlatMapStep(call, next)) + .register(PEEK, (PsiMethodCallExpression call) -> (Step next) -> new PeekStep(call, next)) + .register(SORTED, (PsiMethodCallExpression call) -> (Step next) -> new SortedStep(call, next)) + .register(BOXED, (PsiMethodCallExpression call) -> (Step next) -> new BoxedStep(call, next)) + .register(STATE_FILTER, (PsiMethodCallExpression call) -> (Step next) -> new StateFilterStep(call, next)); + + static abstract class Step { + final Step myNext; + final @NotNull PsiMethodCallExpression myCall; + final PsiExpression myFunction; + + Step(@NotNull PsiMethodCallExpression call, Step next, PsiExpression function) { + myNext = next; + myCall = call; + myFunction = function; + } + + void before(CFGBuilder builder) { + if (myFunction != null) { + builder.evaluateFunction(myFunction); + } + if (myNext != null) { + myNext.before(builder); + } + } + + abstract void iteration(CFGBuilder builder); + } + + static class UnknownTerminalStep extends Step { + UnknownTerminalStep(PsiMethodCallExpression call) { + super(call, null, null); + } + + @Override + void before(CFGBuilder builder) { + for (PsiExpression arg : myCall.getArgumentList().getExpressions()) { + builder.pushExpression(arg).pop(); + } + super.before(builder); + } + + @Override + void iteration(CFGBuilder builder) { + // Stream variable is on stack + builder.pop().flushFields(); + } + } + + static class LambdaTerminalStep extends Step { + LambdaTerminalStep(@NotNull PsiMethodCallExpression call) { + super(call, null, call.getArgumentList().getExpressions()[0]); + } + + @Override + void iteration(CFGBuilder builder) { + builder.invokeFunction(1, myFunction).pop(); + } + } + + static class FilterStep extends Step { + FilterStep(@NotNull PsiMethodCallExpression call, Step next) { + super(call, next, call.getArgumentList().getExpressions()[0]); + } + + @Override + void iteration(CFGBuilder builder) { + builder + .dup() + .invokeFunction(1, myFunction) + .ifConditionIs(true) + .chain(myNext::iteration) + .elseBranch() + .pop() + .endIf(); + } + } + + static class MapStep extends Step { + MapStep(@NotNull PsiMethodCallExpression call, Step next) { + super(call, next, call.getArgumentList().getExpressions()[0]); + } + + @Override + void iteration(CFGBuilder builder) { + builder + .invokeFunction(1, myFunction) + .assignTo(builder.createTempVariable(StreamApiUtil.getStreamElementType(myCall.getType()))) + .chain(myNext::iteration); + } + } + + static class FlatMapStep extends Step { + private final Step myChain; + private final PsiParameter myParameter; + private final PsiExpression myStreamSource; + + FlatMapStep(@NotNull PsiMethodCallExpression call, Step next) { + super(call, next, null); + // Try to inline smoothly .flatMap(x -> stream().call().chain()) + PsiLambdaExpression lambda = + ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(myCall.getArgumentList().getExpressions()[0]), PsiLambdaExpression.class); + Step chain = null; + PsiParameter parameter = null; + PsiExpression streamSource = null; + if (lambda != null) { + parameter = ArrayUtil.getFirstElement(lambda.getParameterList().getParameters()); + if (parameter != null) { + PsiExpression body = PsiUtil.skipParenthesizedExprDown(LambdaUtil.extractSingleExpressionFromBody(lambda.getBody())); + if (body != null) { + streamSource = body; + chain = next; + if (body instanceof PsiMethodCallExpression) { + chain = buildChain((PsiMethodCallExpression)body, next); + if (chain != next) { + streamSource = chain.myCall.getMethodExpression().getQualifierExpression(); + } + } + } + } + } + myStreamSource = streamSource; + myChain = chain; + myParameter = parameter; + } + + @Override + void before(CFGBuilder builder) { + if (myStreamSource == null) { + PsiExpression arg = myCall.getArgumentList().getExpressions()[0]; + builder.pushExpression(arg).checkNotNull(arg, NullabilityProblem.passingNullableToNotNullParameter).pop(); + } + super.before(builder); + } + + @Override + void iteration(CFGBuilder builder) { + if (myStreamSource != null) { + builder.assignTo(myParameter).pop(); + buildStreamCFG(builder, myChain, myStreamSource); + } else { + PsiType outType = StreamApiUtil.getStreamElementType(myCall.getType()); + builder.pop() + .doWhile() + .push(builder.getFactory().createTypeValue(outType, Nullness.UNKNOWN)) + .chain(myNext::iteration) + .endWhileUnknown(); + } + } + } + + static class PeekStep extends Step { + PeekStep(@NotNull PsiMethodCallExpression call, Step next) { + super(call, next, call.getArgumentList().getExpressions()[0]); + } + + @Override + void iteration(CFGBuilder builder) { + builder + .dup() + .invokeFunction(1, myFunction) + .pop() + .chain(myNext::iteration); + } + } + + static class StateFilterStep extends Step { + StateFilterStep(@NotNull PsiMethodCallExpression call, Step next) { + super(call, next, null); + } + + @Override + void before(CFGBuilder builder) { + for (PsiExpression arg : myCall.getArgumentList().getExpressions()) { + builder.pushExpression(arg).pop(); + } + super.before(builder); + } + + @Override + void iteration(CFGBuilder builder) { + builder + .pushUnknown() + .ifConditionIs(true) + .chain(myNext::iteration) + .elseBranch() + .pop() + .endIf(); + } + } + + // Currently sorted is just a no-op as DFA results does not depend on sort order. + // In future we could check the comparator implementation + // (e.g. warn if stream can contain nulls, but comparator is not null-friendly) + static class SortedStep extends Step { + SortedStep(@NotNull PsiMethodCallExpression call, Step next) { + super(call, next, null); + } + + @Override + void before(CFGBuilder builder) { + builder.pushExpression(myCall.getArgumentList().getExpressions()[0]).pop(); + super.before(builder); + } + + @Override + void iteration(CFGBuilder builder) { + myNext.iteration(builder); + } + } + + static class BoxedStep extends Step { + BoxedStep(@NotNull PsiMethodCallExpression call, Step next) { + super(call, next, null); + } + + @Override + void iteration(CFGBuilder builder) { + PsiType outType = StreamApiUtil.getStreamElementType(myCall.getType()); + PsiPrimitiveType primitiveType = PsiPrimitiveType.getUnboxedType(outType); + if (primitiveType != null) { + builder.boxUnbox(myCall, primitiveType, outType).assignTo(builder.createTempVariable(outType)); + } + myNext.iteration(builder); + } + } + + @Override + public boolean tryInlineCall(@NotNull CFGBuilder builder, @NotNull PsiMethodCallExpression call) { + if (!TERMINAL_CALL.test(call)) { + return false; + } + PsiMethodCallExpression qualifierCall = MethodCallUtils.getQualifierMethodCall(call); + Step terminalStep = createTerminalStep(call); + Step firstStep = buildChain(qualifierCall, terminalStep); + if (firstStep == terminalStep) { + // Do not handle specially case when only terminal operation is known: at least one intermediate op should be known as well + return false; + } + PsiExpression originalQualifier = firstStep.myCall.getMethodExpression().getQualifierExpression(); + if (originalQualifier == null) return false; + buildStreamCFG(builder, firstStep, originalQualifier); + builder.push( + builder.getFactory().createTypeValue(call.getType(), DfaPsiUtil.getElementNullability(call.getType(), call.resolveMethod()))); + return true; + } + + static void buildStreamCFG(CFGBuilder builder, Step firstStep, PsiExpression originalQualifier) { + PsiType inType = StreamApiUtil.getStreamElementType(originalQualifier.getType()); + builder + .pushExpression(originalQualifier) + .checkNotNull(firstStep.myCall, NullabilityProblem.callNPE) + .pop() + .chain(firstStep::before) + .doWhile() + .pushVariable(builder.createTempVariable(inType)) + .push(builder.getFactory().createTypeValue(inType, DfaPsiUtil.getTypeNullability(inType))) + .assign() + .chain(firstStep::iteration) + .endWhileUnknown(); + } + + static Step buildChain(PsiMethodCallExpression qualifierCall, Step terminalStep) { + Step curStep = terminalStep; + while (qualifierCall != null) { + if (!SKIP_STEP.test(qualifierCall)) { + Step nextStep = createIntermediateStep(curStep, qualifierCall); + if (nextStep == null) break; + curStep = nextStep; + } + qualifierCall = MethodCallUtils.getQualifierMethodCall(qualifierCall); + } + return curStep; + } + + private static Step createIntermediateStep(Step nextStep, PsiMethodCallExpression call) { + UnaryOperator stepFactory = INTERMEDIATE_STEP_MAPPER.mapFirst(call); + if (stepFactory == null) return null; + return stepFactory.apply(nextStep); + } + + private static Step createTerminalStep(PsiMethodCallExpression call) { + if (LAMBDA_TERMINAL.test(call)) { + return new LambdaTerminalStep(call); + } + return new UnknownTerminalStep(call); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/CheckNotNullInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/CheckNotNullInstruction.java new file mode 100644 index 000000000000..50c9ca23bffc --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/CheckNotNullInstruction.java @@ -0,0 +1,47 @@ +/* + * Copyright 2000-2017 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.codeInspection.dataFlow.instructions; + +import com.intellij.codeInspection.dataFlow.*; +import com.intellij.psi.PsiExpression; + +public class CheckNotNullInstruction extends Instruction { + private final PsiExpression myExpression; + private final NullabilityProblem myProblem; + + public CheckNotNullInstruction(PsiExpression expression, NullabilityProblem problem) { + myExpression = expression; + myProblem = problem; + } + + public PsiExpression getExpression() { + return myExpression; + } + + public NullabilityProblem getProblem() { + return myProblem; + } + + @Override + public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState stateBefore, InstructionVisitor visitor) { + return visitor.visitCheckNotNull(this, runner, stateBefore); + } + + @Override + public String toString() { + return "CHECK_NOT_NULL ["+myProblem+"] "+myExpression.getText(); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/ConditionalGotoInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/ConditionalGotoInstruction.java index 287b86b89d29..1caa02e3265f 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/ConditionalGotoInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/ConditionalGotoInstruction.java @@ -20,7 +20,7 @@ package com.intellij.codeInspection.dataFlow.instructions; import com.intellij.codeInspection.dataFlow.*; import com.intellij.psi.PsiElement; -public class ConditionalGotoInstruction extends BranchingInstruction { +public class ConditionalGotoInstruction extends BranchingInstruction implements JumpInstruction { private ControlFlow.ControlFlowOffset myOffset; private final boolean myIsNegated; @@ -43,10 +43,12 @@ public class ConditionalGotoInstruction extends BranchingInstruction { return (isNegated() ? "!":"") + "cond?_goto " + getOffset(); } + @Override public int getOffset() { return myOffset.getInstructionOffset(); } + @Override public void setOffset(final int offset) { myOffset = new ControlFlow.ControlFlowOffset() { @Override diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/DupInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/DupInstruction.java index 213a64b512e1..081f4f965442 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/DupInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/DupInstruction.java @@ -19,47 +19,19 @@ import com.intellij.codeInspection.dataFlow.DataFlowRunner; import com.intellij.codeInspection.dataFlow.DfaInstructionState; import com.intellij.codeInspection.dataFlow.DfaMemoryState; import com.intellij.codeInspection.dataFlow.InstructionVisitor; -import com.intellij.codeInspection.dataFlow.value.DfaValue; - -import java.util.ArrayList; -import java.util.List; /** * @author max */ public class DupInstruction extends Instruction { - private final int myValueCount; - private final int myDuplicationCount; - - public DupInstruction() { - this(1, 1); - } - - public DupInstruction(int valueCount, int duplicationCount) { - myValueCount = valueCount; - myDuplicationCount = duplicationCount; - } - @Override public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState memState, InstructionVisitor visitor) { - if (myDuplicationCount == 1 && myValueCount == 1) { - memState.push(memState.peek()); - } else { - List values = new ArrayList<>(myValueCount); - for (int i = 0; i < myValueCount; i++) { - values.add(memState.pop()); - } - for (int j = 0; j < myDuplicationCount + 1; j++) { - for (int i = values.size() - 1; i >= 0; i--) { - memState.push(values.get(i)); - } - } - } + memState.push(memState.peek()); Instruction nextInstruction = runner.getInstruction(getIndex() + 1); return new DfaInstructionState[]{new DfaInstructionState(nextInstruction, memState)}; } public String toString() { - return "DUP(" + myValueCount + " top stack values, " + myDuplicationCount + " times)"; + return "DUP"; } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/GotoInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/GotoInstruction.java index b4d2eafd1c61..b09862ef7162 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/GotoInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/GotoInstruction.java @@ -19,13 +19,14 @@ package com.intellij.codeInspection.dataFlow.instructions; import com.intellij.codeInspection.dataFlow.*; -public class GotoInstruction extends Instruction { +public class GotoInstruction extends Instruction implements JumpInstruction { private ControlFlow.ControlFlowOffset myOffset; public GotoInstruction(ControlFlow.ControlFlowOffset myOffset) { this.myOffset = myOffset; } + @Override public int getOffset() { return myOffset.getInstructionOffset(); } @@ -40,6 +41,7 @@ public class GotoInstruction extends Instruction { return "GOTO: " + getOffset(); } + @Override public void setOffset(final int offset) { myOffset = new ControlFlow.ControlFlowOffset() { @Override diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/JumpInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/JumpInstruction.java new file mode 100644 index 000000000000..dd44eb1c3f37 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/JumpInstruction.java @@ -0,0 +1,22 @@ +/* + * Copyright 2000-2017 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.codeInspection.dataFlow.instructions; + +public interface JumpInstruction { + int getOffset(); + + void setOffset(final int offset); +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/MethodCallInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/MethodCallInstruction.java index 4de5bb65051f..99106fe42836 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/MethodCallInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/MethodCallInstruction.java @@ -19,20 +19,21 @@ package com.intellij.codeInspection.dataFlow.instructions; import com.intellij.codeInspection.dataFlow.*; import com.intellij.codeInspection.dataFlow.value.DfaValue; import com.intellij.psi.*; -import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ObjectUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Map; public class MethodCallInstruction extends Instruction { + private static final Nullness[] EMPTY_NULLNESS_ARRAY = new Nullness[0]; + @Nullable private final PsiCall myCall; @Nullable private final PsiType myType; - @NotNull private final PsiExpression[] myArgs; + private final int myArgCount; private final boolean myShouldFlushFields; @NotNull private final PsiElement myContext; @Nullable private final PsiMethod myTargetMethod; @@ -41,12 +42,13 @@ public class MethodCallInstruction extends Instruction { @Nullable private final DfaValue myPrecalculatedReturnValue; private final boolean myOfNullable; private final boolean myVarArgCall; - private final Map myArgRequiredNullability; + private final Nullness[] myArgRequiredNullability; private boolean myOnlyNullArgs = true; private boolean myOnlyNotNullArgs = true; + private final Nullness myReturnNullability; public enum MethodType { - BOXING, UNBOXING, REGULAR_METHOD_CALL, CAST + BOXING, UNBOXING, REGULAR_METHOD_CALL, METHOD_REFERENCE_CALL, CAST } public MethodCallInstruction(@NotNull PsiExpression context, MethodType methodType, @Nullable PsiType resultType) { @@ -54,14 +56,49 @@ public class MethodCallInstruction extends Instruction { myContracts = Collections.emptyList(); myMethodType = methodType; myCall = null; - myArgs = PsiExpression.EMPTY_ARRAY; + myArgCount = 0; myType = resultType; myShouldFlushFields = false; myPrecalculatedReturnValue = null; myTargetMethod = null; myVarArgCall = false; myOfNullable = false; - myArgRequiredNullability = Collections.emptyMap(); + myArgRequiredNullability = EMPTY_NULLNESS_ARRAY; + myReturnNullability = Nullness.UNKNOWN; + } + + public MethodCallInstruction(@NotNull PsiMethodReferenceExpression reference, @NotNull List contracts) { + myContext = reference; + myMethodType = MethodType.METHOD_REFERENCE_CALL; + JavaResolveResult resolveResult = reference.advancedResolve(false); + myTargetMethod = ObjectUtils.tryCast(resolveResult.getElement(), PsiMethod.class); + myCall = null; + myContracts = Collections.unmodifiableList(contracts); + myArgCount = myTargetMethod == null ? 0 : myTargetMethod.getParameterList().getParametersCount(); + if (myTargetMethod == null) { + myType = null; + myReturnNullability = Nullness.UNKNOWN; + } + else { + if (myTargetMethod.isConstructor()) { + PsiClass containingClass = myTargetMethod.getContainingClass(); + myType = containingClass == null ? null : JavaPsiFacade.getElementFactory(myTargetMethod.getProject()) + .createType(containingClass, resolveResult.getSubstitutor()); + myReturnNullability = Nullness.NOT_NULL; + } + else { + myType = resolveResult.getSubstitutor().substitute(myTargetMethod.getReturnType()); + myReturnNullability = DfaPsiUtil.getElementNullability(myType, myTargetMethod); + } + } + myVarArgCall = false; // vararg method reference calls are not supported now + myPrecalculatedReturnValue = null; + myOfNullable = DfaOptionalSupport.OPTIONAL_OF_NULLABLE.methodReferenceMatches(reference); + myArgRequiredNullability = myTargetMethod == null + ? EMPTY_NULLNESS_ARRAY + : calcArgRequiredNullability(resolveResult.getSubstitutor(), + myTargetMethod.getParameterList().getParameters()); + myShouldFlushFields = !isPureCall(); } public MethodCallInstruction(@NotNull PsiCall call, @Nullable DfaValue precalculatedReturnValue, List contracts) { @@ -70,7 +107,8 @@ public class MethodCallInstruction extends Instruction { myMethodType = MethodType.REGULAR_METHOD_CALL; myCall = call; final PsiExpressionList argList = call.getArgumentList(); - myArgs = argList != null ? argList.getExpressions() : PsiExpression.EMPTY_ARRAY; + PsiExpression[] args = argList != null ? argList.getExpressions() : PsiExpression.EMPTY_ARRAY; + myArgCount = args.length; myType = myCall instanceof PsiCallExpression ? ((PsiCallExpression)myCall).getType() : null; JavaResolveResult result = call.resolveMethodGenerics(); @@ -79,35 +117,57 @@ public class MethodCallInstruction extends Instruction { PsiSubstitutor substitutor = result.getSubstitutor(); if (argList != null && myTargetMethod != null) { PsiParameter[] parameters = myTargetMethod.getParameterList().getParameters(); - myVarArgCall = isVarArgCall(myTargetMethod, substitutor, myArgs, parameters); + myVarArgCall = isVarArgCall(myTargetMethod, substitutor, args, parameters); myArgRequiredNullability = calcArgRequiredNullability(substitutor, parameters); } else { myVarArgCall = false; - myArgRequiredNullability = Collections.emptyMap(); + myArgRequiredNullability = EMPTY_NULLNESS_ARRAY; } myShouldFlushFields = !(call instanceof PsiNewExpression && myType != null && myType.getArrayDimensions() > 0) && !isPureCall(); myPrecalculatedReturnValue = precalculatedReturnValue; - myOfNullable = call instanceof PsiMethodCallExpression && DfaOptionalSupport.resolveOfNullable((PsiMethodCallExpression)call) != null; + myOfNullable = call instanceof PsiMethodCallExpression && DfaOptionalSupport.OPTIONAL_OF_NULLABLE.test((PsiMethodCallExpression)call); + myReturnNullability = call instanceof PsiNewExpression ? Nullness.NOT_NULL : DfaPsiUtil.getElementNullability(myType, myTargetMethod); } - private Map calcArgRequiredNullability(PsiSubstitutor substitutor, PsiParameter[] parameters) { - int checkedCount = Math.min(myArgs.length, parameters.length) - (myVarArgCall ? 1 : 0); + /** + * Returns a PsiElement which at best represents an argument with given index + * + * @param index an argument index, must be from 0 to {@link #getArgCount()}-1. + * @return a PsiElement. Either argument expression or method reference if call is described by method reference + */ + public PsiElement getArgumentAnchor(int index) { + if (myCall != null) { + PsiExpressionList argumentList = myCall.getArgumentList(); + if (argumentList != null) { + return argumentList.getExpressions()[index]; + } + } + if (myContext instanceof PsiMethodReferenceExpression) { + return ((PsiMethodReferenceExpression)myContext).getReferenceNameElement(); + } + return myContext; + } - Map map = ContainerUtil.newHashMap(); + private Nullness[] calcArgRequiredNullability(PsiSubstitutor substitutor, PsiParameter[] parameters) { + if (myArgCount == 0) { + return EMPTY_NULLNESS_ARRAY; + } + + int checkedCount = Math.min(myArgCount, parameters.length) - (myVarArgCall ? 1 : 0); + + Nullness[] nullness = new Nullness[myArgCount]; for (int i = 0; i < checkedCount; i++) { - map.put(myArgs[i], DfaPsiUtil.getElementNullability(substitutor.substitute(parameters[i].getType()), parameters[i])); + nullness[i] = DfaPsiUtil.getElementNullability(substitutor.substitute(parameters[i].getType()), parameters[i]); } if (myVarArgCall) { PsiType lastParamType = substitutor.substitute(parameters[parameters.length - 1].getType()); if (isEllipsisWithNotNullElements(lastParamType)) { - for (int i = parameters.length - 1; i < myArgs.length; i++) { - map.put(myArgs[i], Nullness.NOT_NULL); - } + Arrays.fill(nullness, parameters.length - 1, myArgCount, Nullness.NOT_NULL); } } - return map; + return nullness; } private static boolean isEllipsisWithNotNullElements(PsiType lastParamType) { @@ -146,9 +206,8 @@ public class MethodCallInstruction extends Instruction { return myType; } - @NotNull - public PsiExpression[] getArgs() { - return myArgs; + public int getArgCount() { + return myArgCount; } public MethodType getMethodType() { @@ -168,9 +227,9 @@ public class MethodCallInstruction extends Instruction { return myVarArgCall; } - @Nullable - public Nullness getArgRequiredNullability(@NotNull PsiExpression arg) { - return myArgRequiredNullability.get(arg); + @Nullable + public Nullness getArgRequiredNullability(int index) { + return index >= myArgRequiredNullability.length ? null : myArgRequiredNullability[index]; } public List getContracts() { @@ -197,12 +256,26 @@ public class MethodCallInstruction extends Instruction { return myPrecalculatedReturnValue; } + @NotNull + public Nullness getReturnNullability() { + return myReturnNullability; + } + public String toString() { - return myMethodType == MethodType.UNBOXING - ? "UNBOX" - : myMethodType == MethodType.BOXING - ? "BOX" : - "CALL_METHOD: " + (myCall == null ? "null" : myCall.getText()); + switch (myMethodType) { + case UNBOXING: + return "UNBOX"; + case BOXING: + return "BOX"; + case CAST: + return "CAST TO " + myType; + case METHOD_REFERENCE_CALL: + return "CALL_METHOD_REFERENCE: " + myContext.getText(); + case REGULAR_METHOD_CALL: + return "CALL_METHOD: " + (myCall == null ? "null" : myCall.getText()); + default: + throw new IllegalStateException("Unexpected method type: " + myMethodType); + } } public boolean updateOfNullable(DfaMemoryState memState, DfaValue arg) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/SpliceInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/SpliceInstruction.java new file mode 100644 index 000000000000..3f84a41446dc --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/SpliceInstruction.java @@ -0,0 +1,51 @@ +/* + * Copyright 2000-2017 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.codeInspection.dataFlow.instructions; + +import com.intellij.codeInspection.dataFlow.DataFlowRunner; +import com.intellij.codeInspection.dataFlow.DfaInstructionState; +import com.intellij.codeInspection.dataFlow.DfaMemoryState; +import com.intellij.codeInspection.dataFlow.InstructionVisitor; +import com.intellij.codeInspection.dataFlow.value.DfaValue; +import one.util.streamex.IntStreamEx; + +import java.util.Arrays; +import java.util.List; + +/** + * Pop several elements from the stack and replace them with some of them (possibly duplicating, swapping, removing some, etc.) + */ +public class SpliceInstruction extends Instruction { + private final int myCount; + private final int[] myReplacement; + + public SpliceInstruction(int count, int... replacement) { + myCount = count; + myReplacement = replacement; + } + + @Override + public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState stateBefore, InstructionVisitor visitor) { + List removed = IntStreamEx.range(myCount).mapToObj(idx -> stateBefore.pop()).toList(); + IntStreamEx.of(myReplacement).elements(removed).forEach(stateBefore::push); + Instruction nextInstruction = runner.getInstruction(getIndex() + 1); + return new DfaInstructionState[]{new DfaInstructionState(nextInstruction, stateBefore)}; + } + + public String toString() { + return "SPLICE [" + myCount + "] -> " + Arrays.toString(myReplacement); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java index 135b63ce7c71..1d31756d3656 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaExpressionFactory.java @@ -19,7 +19,6 @@ import com.intellij.codeInsight.AnnotationUtil; import com.intellij.codeInspection.dataFlow.DfaPsiUtil; import com.intellij.codeInspection.dataFlow.Nullness; import com.intellij.codeInspection.dataFlow.SpecialField; -import com.intellij.ide.highlighter.JavaFileType; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Conditions; @@ -27,6 +26,7 @@ import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.impl.JavaConstantExpressionEvaluator; +import com.intellij.psi.impl.light.LightVariableBuilder; import com.intellij.psi.util.PropertyUtil; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; @@ -202,14 +202,8 @@ public class DfaExpressionFactory { private PsiVariable getArrayIndexVariable(@Nullable PsiExpression indexExpression) { Object constant = JavaConstantExpressionEvaluator.computeConstantExpression(indexExpression, false); if (constant instanceof Integer && ((Integer)constant).intValue() >= 0) { - PsiVariable mockVar = myMockIndices.get(constant); - if (mockVar == null) { - PsiJavaFile file = (PsiJavaFile)PsiFileFactory.getInstance(indexExpression.getProject()) - .createFileFromText("ArrayIndex.java", JavaFileType.INSTANCE, "class _Index_ { int $array$index$" + constant + ";}"); - mockVar = file.getClasses()[0].getFields()[0]; - myMockIndices.put((Integer)constant, mockVar); - } - return mockVar; + return myMockIndices + .computeIfAbsent((Integer)constant, k -> new LightVariableBuilder<>("$array$index$" + k, PsiType.INT, indexExpression)); } return null; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/util/SpecialAnnotationsUtilBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/util/SpecialAnnotationsUtilBase.java index 78130a783c0f..32c003c3d5cf 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/util/SpecialAnnotationsUtilBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/util/SpecialAnnotationsUtilBase.java @@ -51,6 +51,11 @@ public class SpecialAnnotationsUtilBase { return family; } + @Override + public boolean startInWriteAction() { + return false; + } + @Override public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) { doQuickFixInternal(project, targetList, qualifiedName); diff --git a/java/java-impl/src/com/intellij/application/options/CodeStyleGenerationConfigurable.java b/java/java-impl/src/com/intellij/application/options/CodeStyleGenerationConfigurable.java index b3e77fa63781..a85fdc186367 100644 --- a/java/java-impl/src/com/intellij/application/options/CodeStyleGenerationConfigurable.java +++ b/java/java-impl/src/com/intellij/application/options/CodeStyleGenerationConfigurable.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -30,10 +30,10 @@ import com.intellij.psi.codeStyle.CodeStyleConfigurable; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.refactoring.RefactoringBundle; import com.intellij.refactoring.ui.JavaVisibilityPanel; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.SortedListModel; import com.intellij.ui.components.JBCheckBox; import com.intellij.util.ui.JBInsets; +import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -73,7 +73,7 @@ public class CodeStyleGenerationConfigurable implements CodeStyleConfigurable { public CodeStyleGenerationConfigurable(CodeStyleSettings settings) { mySettings = settings; - myPanel.setBorder(IdeBorderFactory.createEmptyBorder(2, 2, 2, 2)); + myPanel.setBorder(JBUI.Borders.empty(2, 2, 2, 2)); myJavaVisibilityPanel = new JavaVisibilityPanel(false, true, RefactoringBundle.message("default.visibility.border.title")); } diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JShellCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/completion/JShellCompletionContributor.java new file mode 100644 index 000000000000..48eadf690b1a --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JShellCompletionContributor.java @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2017 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.codeInsight.completion; + +import com.intellij.psi.PsiJShellSyntheticElement; +import org.jetbrains.annotations.NotNull; + +/** + * @author Eugene Zhuravlev + * Date: 26-Jul-17 + */ +public class JShellCompletionContributor extends CompletionContributor { + @Override + public void fillCompletionVariants(@NotNull final CompletionParameters parameters, @NotNull final CompletionResultSet resultSet) { + resultSet.runRemainingContributors(parameters, r -> { + if (!(r.getLookupElement().getPsiElement() instanceof PsiJShellSyntheticElement)) { + resultSet.passResult(r); + } + }); + } + +} diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java index b5f751b33b57..74f10b38b543 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java @@ -789,6 +789,11 @@ public class JavaCompletionContributor extends CompletionContributor { } if (context.getCompletionType() == CompletionType.BASIC) { + if (PsiTreeUtil.findElementOfClassAtOffset(file, context.getStartOffset() - 1, PsiReferenceParameterList.class, false) != null) { + context.setDummyIdentifier(CompletionInitializationContext.DUMMY_IDENTIFIER_TRIMMED); + return; + } + if (semicolonNeeded(context.getEditor(), file, context.getStartOffset())) { context.setDummyIdentifier(CompletionInitializationContext.DUMMY_IDENTIFIER.trim() + ";"); return; @@ -818,7 +823,7 @@ public class JavaCompletionContributor extends CompletionContributor { } } - public static boolean semicolonNeeded(final Editor editor, PsiFile file, final int startOffset) { + public static boolean semicolonNeeded(Editor editor, PsiFile file, int startOffset) { PsiJavaCodeReferenceElement ref = PsiTreeUtil.findElementOfClassAtOffset(file, startOffset, PsiJavaCodeReferenceElement.class, false); if (ref != null && !(ref instanceof PsiReferenceExpression)) { if (ref.getParent() instanceof PsiTypeElement) { diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaKeywordCompletion.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaKeywordCompletion.java index d5bb1ed359bd..889549d66e3d 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaKeywordCompletion.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaKeywordCompletion.java @@ -38,7 +38,6 @@ import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.util.Consumer; import com.intellij.util.ObjectUtils; -import com.intellij.util.ProcessingContext; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.JBIterable; import org.jetbrains.annotations.NotNull; @@ -311,8 +310,6 @@ public class JavaKeywordCompletion { addClassLiteral(); - addUnfinishedMethodTypeParameters(); - addExtendsImplements(); } @@ -516,8 +513,12 @@ public class JavaKeywordCompletion { addKeyword(new OverridableSpace(createKeyword(s), TailType.HUMBLE_SPACE_BEFORE_WORD)); } - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.CLASS), TailType.HUMBLE_SPACE_BEFORE_WORD)); - if (PsiTreeUtil.getParentOfType(myPosition, PsiCodeBlock.class, true, PsiMember.class) == null) { + PsiExpression expression = PsiTreeUtil.getParentOfType(myPosition, PsiExpression.class, true, PsiMember.class); + if (expression != null && expression.getParent() instanceof PsiExpressionStatement) { + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.CLASS), TailType.HUMBLE_SPACE_BEFORE_WORD)); + } + if (expression == null && PsiTreeUtil.getParentOfType(myPosition, PsiCodeBlock.class, true, PsiMember.class) == null) { + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.CLASS), TailType.HUMBLE_SPACE_BEFORE_WORD)); addKeyword(new OverridableSpace(createKeyword(PsiKeyword.INTERFACE), TailType.HUMBLE_SPACE_BEFORE_WORD)); if (PsiUtil.isLanguageLevel5OrHigher(myPosition)) { addKeyword(new OverridableSpace(createKeyword(PsiKeyword.ENUM), TailType.INSERT_SPACE)); @@ -614,25 +615,6 @@ public class JavaKeywordCompletion { return END_OF_BLOCK.getValue().isAcceptable(position, position); } - private void addUnfinishedMethodTypeParameters() { - final ProcessingContext context = new ProcessingContext(); - if (psiElement().inside( - psiElement(PsiTypeElement.class).afterLeaf( - psiElement().withText(">").withParent( - psiElement(PsiTypeParameterList.class).withParent(PsiErrorElement.class).save("typeParameterList")))).accepts(myPosition, context)) { - final PsiTypeParameterList list = (PsiTypeParameterList)context.get("typeParameterList"); - PsiElement current = list.getParent().getParent(); - if (current instanceof PsiField) { - current = current.getParent(); - } - if (current instanceof PsiClass) { - for (PsiTypeParameter typeParameter : list.getTypeParameters()) { - addKeyword(new JavaPsiClassReferenceElement(typeParameter)); - } - } - } - } - static boolean isAfterPrimitiveOrArrayType(PsiElement element) { return psiElement().withParent( psiReferenceExpression().withFirstChild( diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallElement.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallElement.java index 5f106d2680c5..99b68892d938 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallElement.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallElement.java @@ -20,9 +20,6 @@ import com.intellij.codeInsight.completion.util.MethodParenthesesHandler; import com.intellij.codeInsight.hint.ParameterInfoController; import com.intellij.codeInsight.hint.ShowParameterInfoContext; import com.intellij.codeInsight.hint.api.impls.MethodParameterInfoHandler; -import com.intellij.codeInsight.hints.HintInfo; -import com.intellij.codeInsight.hints.JavaInlayParameterHintsProvider; -import com.intellij.codeInsight.hints.MethodInfoBlacklistFilter; import com.intellij.codeInsight.hints.ParameterHintsPass; import com.intellij.codeInsight.lookup.*; import com.intellij.codeInsight.lookup.impl.JavaElementLookupRenderer; @@ -332,10 +329,10 @@ public class JavaMethodCallElement extends LookupItem implements Type return; } - methodCall.putUserData(COMPLETION_HINTS, Boolean.TRUE); + setCompletionMode(methodCall, true); ParameterInfoController controller = new ParameterInfoController(project, editor, braceOffset, infoContext.getItemsToShow(), null, - parameterOwner, handler, false, false); - Disposable hintsDisposal = () -> methodCall.putUserData(COMPLETION_HINTS, null); + methodCall.getArgumentList(), handler, false, false); + Disposable hintsDisposal = () -> setCompletionMode(methodCall, false); if (Disposer.isDisposed(controller)) { Disposer.dispose(hintsDisposal); } @@ -345,7 +342,11 @@ public class JavaMethodCallElement extends LookupItem implements Type } } - public static boolean showCompletionHints(@NotNull PsiCallExpression expression) { + public static void setCompletionMode(@NotNull PsiCall expression, boolean value) { + expression.putUserData(COMPLETION_HINTS, value ? Boolean.TRUE : null); + } + + public static boolean isCompletionMode(@NotNull PsiCall expression) { return expression.getUserData(COMPLETION_HINTS) != null; } diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/ConvertSwitchToIfIntention.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/ConvertSwitchToIfIntention.java index da28d516564d..0f65ab658d7d 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/ConvertSwitchToIfIntention.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/ConvertSwitchToIfIntention.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -35,8 +35,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import static com.intellij.psi.CommonClassNames.JAVA_LANG_STRING; - public class ConvertSwitchToIfIntention implements IntentionAction { private final PsiSwitchStatement mySwitchExpression; @@ -87,8 +85,7 @@ public class ConvertSwitchToIfIntention implements IntentionAction { if (switchExpressionType == null) { return; } - final boolean isSwitchOnString = - switchExpressionType.equalsToText(JAVA_LANG_STRING); + final boolean isSwitchOnString = switchExpressionType.equalsToText(CommonClassNames.JAVA_LANG_STRING); boolean useEquals = isSwitchOnString; if (!useEquals) { final PsiClass aClass = PsiUtil.resolveClassInType(switchExpressionType); @@ -101,16 +98,13 @@ public class ConvertSwitchToIfIntention implements IntentionAction { if (RemoveUnusedVariableUtil.checkSideEffects(switchExpression, null, new ArrayList<>())) { hadSideEffects = true; - final JavaCodeStyleManager javaCodeStyleManager = - JavaCodeStyleManager.getInstance(project); + final JavaCodeStyleManager javaCodeStyleManager = JavaCodeStyleManager.getInstance(project); final String variableName; if (isSwitchOnString) { - variableName = javaCodeStyleManager.suggestUniqueVariableName( - "s", switchExpression, true); + variableName = javaCodeStyleManager.suggestUniqueVariableName("s", switchExpression, true); } else { - variableName = javaCodeStyleManager.suggestUniqueVariableName( - "i", switchExpression, true); + variableName = javaCodeStyleManager.suggestUniqueVariableName("i", switchExpression, true); } expressionText = variableName; declarationString = switchExpressionType.getCanonicalText() + ' ' + variableName + " = " + switchExpression.getText() + ';'; @@ -126,19 +120,15 @@ public class ConvertSwitchToIfIntention implements IntentionAction { if (body == null) { return; } - final List openBranches = - new ArrayList<>(); - final Set declaredVariables = - new HashSet<>(); - final List allBranches = - new ArrayList<>(); + final List openBranches = new ArrayList<>(); + final Set declaredVariables = new HashSet<>(); + final List allBranches = new ArrayList<>(); SwitchStatementBranch currentBranch = null; final PsiElement[] children = body.getChildren(); for (int i = 1; i < children.length - 1; i++) { final PsiElement statement = children[i]; if (statement instanceof PsiSwitchLabelStatement) { - final PsiSwitchLabelStatement label = - (PsiSwitchLabelStatement)statement; + final PsiSwitchLabelStatement label = (PsiSwitchLabelStatement)statement; if (currentBranch == null) { openBranches.clear(); currentBranch = new SwitchStatementBranch(); @@ -163,13 +153,10 @@ public class ConvertSwitchToIfIntention implements IntentionAction { else { if (statement instanceof PsiStatement) { if (statement instanceof PsiDeclarationStatement) { - final PsiDeclarationStatement declarationStatement = - (PsiDeclarationStatement)statement; - final PsiElement[] elements = - declarationStatement.getDeclaredElements(); + final PsiDeclarationStatement declarationStatement = (PsiDeclarationStatement)statement; + final PsiElement[] elements = declarationStatement.getDeclaredElements(); for (PsiElement varElement : elements) { - final PsiLocalVariable variable = - (PsiLocalVariable)varElement; + final PsiLocalVariable variable = (PsiLocalVariable)varElement; declaredVariables.add(variable); } } @@ -177,8 +164,8 @@ public class ConvertSwitchToIfIntention implements IntentionAction { branch.addStatement(statement); } try { - ControlFlow controlFlow = ControlFlowFactory - .getInstance(project).getControlFlow(statement, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance()); + ControlFlow controlFlow = + ControlFlowFactory.getInstance(project).getControlFlow(statement, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance()); int startOffset = controlFlow.getStartOffset(statement); int endOffset = controlFlow.getEndOffset(statement); if (startOffset != -1 && endOffset != -1 && !ControlFlowUtil.canCompleteNormally(controlFlow, startOffset, endOffset)) { @@ -209,62 +196,34 @@ public class ConvertSwitchToIfIntention implements IntentionAction { defaultBranch = branch; } else { - final List caseValues = branch.getCaseValues(); - final List bodyElements = branch.getBodyElements(); - final Set pendingVariableDeclarations = - branch.getPendingVariableDeclarations(); - dumpBranch(expressionText, caseValues, bodyElements, - pendingVariableDeclarations, firstBranch, - useEquals, ifStatementText); + dumpBranch(branch, expressionText, firstBranch, useEquals, ifStatementText); firstBranch = false; } } if (defaultBranch != null) { - final List bodyElements = - defaultBranch.getBodyElements(); - final Set pendingVariableDeclarations = - defaultBranch.getPendingVariableDeclarations(); - dumpDefaultBranch(bodyElements, pendingVariableDeclarations, - firstBranch, ifStatementText); + dumpDefaultBranch(defaultBranch, firstBranch, ifStatementText); } - if (ifStatementText.length() == 0) return; - final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); - final PsiElementFactory factory = psiFacade.getElementFactory(); + if (ifStatementText.length() == 0) { + return; + } + final PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); if (hadSideEffects) { - final PsiStatement declarationStatement = - factory.createStatementFromText(declarationString, - switchStatement); - final PsiStatement ifStatement = - factory.createStatementFromText(ifStatementText.toString(), - switchStatement); - final PsiElement parent = switchStatement.getParent(); - parent.addBefore(declarationStatement, switchStatement); - switchStatement.replace(ifStatement); - } - else { - final PsiStatement newStatement = - factory.createStatementFromText(ifStatementText.toString(), - switchStatement); - switchStatement.replace(newStatement); + final PsiStatement declarationStatement = factory.createStatementFromText(declarationString, switchStatement); + switchStatement.getParent().addBefore(declarationStatement, switchStatement); } + final PsiStatement ifStatement = factory.createStatementFromText(ifStatementText.toString(), switchStatement); + switchStatement.replace(ifStatement); } private static String getCaseValueText(PsiExpression value) { + value = PsiUtil.skipParenthesizedExprDown(value); if (value == null) { return ""; } - if (value instanceof PsiParenthesizedExpression) { - final PsiParenthesizedExpression parenthesizedExpression = - (PsiParenthesizedExpression)value; - final PsiExpression expression = - parenthesizedExpression.getExpression(); - return getCaseValueText(expression); - } if (!(value instanceof PsiReferenceExpression)) { return value.getText(); } - final PsiReferenceExpression referenceExpression = - (PsiReferenceExpression)value; + final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)value; final PsiElement target = referenceExpression.resolve(); final String text = referenceExpression.getText(); if (!(target instanceof PsiEnumConstant)) { @@ -279,96 +238,74 @@ public class ConvertSwitchToIfIntention implements IntentionAction { return name + '.' + text; } - private static void dumpBranch( - String expressionText, List caseValues, - List bodyStatements, - Set variables, boolean firstBranch, - boolean useEquals, - @NonNls StringBuilder ifStatementString) { + private static void dumpBranch(SwitchStatementBranch branch, + String expressionText, + boolean firstBranch, + boolean useEquals, + @NonNls StringBuilder out) { if (!firstBranch) { - ifStatementString.append("else "); + out.append("else "); } - dumpCaseValues(expressionText, caseValues, useEquals, - ifStatementString); - dumpBody(bodyStatements, variables, ifStatementString); + dumpCaseValues(expressionText, branch.getCaseValues(), useEquals, out); + dumpBody(branch, out); } - private static void dumpDefaultBranch( - List bodyStatements, - Set variables, boolean firstBranch, - @NonNls StringBuilder ifStatementString) { + private static void dumpDefaultBranch(SwitchStatementBranch defaultBranch, boolean firstBranch, @NonNls StringBuilder out) { if (!firstBranch) { - ifStatementString.append("else "); + out.append("else "); } - dumpBody(bodyStatements, variables, ifStatementString); + dumpBody(defaultBranch, out); } - private static void dumpCaseValues( - String expressionText, List caseValues, boolean useEquals, - @NonNls StringBuilder ifStatementString) { - ifStatementString.append("if("); + private static void dumpCaseValues(String expressionText, List caseValues, boolean useEquals, @NonNls StringBuilder out) { + out.append("if("); boolean firstCaseValue = true; for (String caseValue : caseValues) { if (!firstCaseValue) { - ifStatementString.append("||"); + out.append("||"); } firstCaseValue = false; - ifStatementString.append(expressionText); if (useEquals) { - ifStatementString.append(".equals("); - ifStatementString.append(caseValue); - ifStatementString.append(')'); + out.append(caseValue).append(".equals(").append(expressionText).append(')'); } else { - ifStatementString.append("=="); - ifStatementString.append(caseValue); + out.append(expressionText).append("==").append(caseValue); } } - ifStatementString.append(')'); + out.append(')'); } - private static void dumpBody(List bodyStatements, - Set variables, - @NonNls StringBuilder ifStatementString) { - ifStatementString.append('{'); - for (PsiLocalVariable variable : variables) { - if (ReferencesSearch.search(variable, new LocalSearchScope(bodyStatements.toArray(new PsiElement[bodyStatements.size()]))).findFirst() != null) { - final PsiType varType = variable.getType(); - ifStatementString.append(varType.getCanonicalText()); - ifStatementString.append(' '); - ifStatementString.append(variable.getName()); - ifStatementString.append(';'); + private static void dumpBody(SwitchStatementBranch branch, @NonNls StringBuilder out) { + final List bodyStatements = branch.getBodyElements(); + out.append('{'); + for (PsiLocalVariable variable : branch.getPendingVariableDeclarations()) { + if (ReferencesSearch.search(variable, new LocalSearchScope(bodyStatements.toArray(PsiElement.EMPTY_ARRAY))).findFirst() != null) { + out.append(variable.getType().getCanonicalText()).append(' ').append(variable.getName()).append(';'); } } for (PsiElement bodyStatement : bodyStatements) { if (bodyStatement instanceof PsiBlockStatement) { - final PsiBlockStatement blockStatement = - (PsiBlockStatement)bodyStatement; + final PsiBlockStatement blockStatement = (PsiBlockStatement)bodyStatement; final PsiCodeBlock codeBlock = blockStatement.getCodeBlock(); - final PsiStatement[] statements = codeBlock.getStatements(); - for (PsiStatement statement : statements) { - appendElement(statement, ifStatementString); + for (PsiStatement statement : codeBlock.getStatements()) { + appendElement(statement, out); } } else { - appendElement(bodyStatement, ifStatementString); + appendElement(bodyStatement, out); } } - ifStatementString.append("\n}"); + out.append("\n}"); } - private static void appendElement( - PsiElement element, @NonNls StringBuilder ifStatementString) { + private static void appendElement(PsiElement element, @NonNls StringBuilder out) { if (element instanceof PsiBreakStatement) { - final PsiBreakStatement breakStatement = - (PsiBreakStatement)element; - final PsiIdentifier identifier = - breakStatement.getLabelIdentifier(); + final PsiBreakStatement breakStatement = (PsiBreakStatement)element; + final PsiIdentifier identifier = breakStatement.getLabelIdentifier(); if (identifier == null) { return; } } - final String text = element.getText(); - ifStatementString.append(text); + out.append(element.getText()); } } diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateLocalVarFromInstanceofAction.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateLocalVarFromInstanceofAction.java index 0fb4d3f8b166..1c640d78f82a 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateLocalVarFromInstanceofAction.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateLocalVarFromInstanceofAction.java @@ -30,15 +30,18 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.actions.EnterAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.codeStyle.SuggestedNameInfo; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.TypeConversionUtil; import com.intellij.refactoring.JavaRefactoringSettings; import com.intellij.refactoring.introduceVariable.IntroduceVariableBase; import com.intellij.util.ArrayUtil; import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -363,19 +366,45 @@ public class CreateLocalVarFromInstanceofAction extends BaseIntentionAction { anchorAfter = ((PsiBlockStatement)whileStatement.getBody()).getCodeBlock().getLBrace(); } } + if (anchorAfter == null) { return null; } - PsiElement nextSibling = PsiTreeUtil.skipWhitespacesForward(anchorAfter); - anchorAfter = nextSibling instanceof PsiComment ? PsiTreeUtil.skipSiblingsForward(nextSibling, PsiComment.class) : anchorAfter; - nextSibling = PsiTreeUtil.getNextSiblingOfType(anchorAfter, PsiStatement.class); - while (nextSibling instanceof PsiDeclarationStatement) { + PsiElement nextSibling = anchorAfter.getNextSibling(); + while (nextSibling != null) { + if (nextSibling instanceof PsiWhiteSpace) { + final String text = nextSibling.getText(); + if (StringUtil.countNewLines(text) > 1) { + final PsiElement newWhitespace = PsiParserFacade.SERVICE.getInstance(nextSibling.getProject()) + .createWhiteSpaceFromText(text.substring(0, text.lastIndexOf('\n'))); + nextSibling.replace(newWhitespace); + break; + } + nextSibling = nextSibling.getNextSibling(); + continue; + } + else if (!isValidDeclarationStatement(nextSibling) && !(nextSibling instanceof PsiComment)) { + break; + } anchorAfter = nextSibling; - nextSibling = PsiTreeUtil.getNextSiblingOfType(anchorAfter, PsiStatement.class); + nextSibling = anchorAfter.getNextSibling(); } return anchorAfter.getParent().addAfter(toInsert, anchorAfter); } + private static boolean isValidDeclarationStatement(PsiElement nextSibling) { + if (!(nextSibling instanceof PsiDeclarationStatement)) { + return false; + } + final PsiDeclarationStatement declarationStatement = (PsiDeclarationStatement)nextSibling; + final PsiElement[] elements = declarationStatement.getDeclaredElements(); + if (elements.length == 0) { + return false; + } + final PsiElement lastElement = elements[elements.length - 1]; + return !(lastElement instanceof PsiClass) && PsiUtil.isJavaToken(lastElement.getLastChild(), JavaTokenType.SEMICOLON); + } + private static void reformatNewCodeBlockBraces(final PsiElement start, final PsiBlockStatement end) throws IncorrectOperationException { CodeStyleManager.getInstance(end.getProject()).reformatRange(end.getContainingFile(), diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/InsertNewFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/InsertNewFix.java index 660775834b50..0eab0117d989 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/InsertNewFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/InsertNewFix.java @@ -47,7 +47,7 @@ public class InsertNewFix implements IntentionAction { @Override public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { - return myMethodCall.isValid() && myMethodCall.getManager().isInProject(myMethodCall); + return myMethodCall.isValid() && myMethodCall.getManager().isInProject(myMethodCall) && !(myMethodCall.getNextSibling() instanceof PsiErrorElement); } @NotNull diff --git a/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaMethodOverloadSwitchHandler.java b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaMethodOverloadSwitchHandler.java index 4f8de02b85a6..eb0147675d99 100644 --- a/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaMethodOverloadSwitchHandler.java +++ b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaMethodOverloadSwitchHandler.java @@ -25,6 +25,7 @@ import com.intellij.openapi.editor.Caret; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Key; import com.intellij.psi.*; import com.intellij.psi.infos.CandidateInfo; @@ -129,10 +130,14 @@ public class JavaMethodOverloadSwitchHandler extends EditorWriteActionHandler { } if (targetCaretPosition == -1) targetCaretPosition = offset; caret.moveToLogicalPosition(editor.offsetToLogicalPosition(targetCaretPosition).leanForward(true)); - call.putUserData(JavaMethodCallElement.COMPLETION_HINTS, Boolean.TRUE); + PsiCall methodCall = (PsiCall)call; + if (!JavaMethodCallElement.isCompletionMode(methodCall)) { + JavaMethodCallElement.setCompletionMode(methodCall, true); + Disposer.register(controller, () -> JavaMethodCallElement.setCompletionMode(methodCall, false)); + } PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument()); - CompletionMemory.registerChosenMethod(targetMethod, (PsiCall)call); + CompletionMemory.registerChosenMethod(targetMethod, methodCall); controller.resetHighlighted(); controller.updateComponent(); // update popup immediately (otherwise, it will be updated only after delay) ParameterHintsPass.syncUpdate(call, editor); diff --git a/java/java-impl/src/com/intellij/codeInsight/generation/GenerationInfoBase.java b/java/java-impl/src/com/intellij/codeInsight/generation/GenerationInfoBase.java index ca474ff2370d..cfe0e7915e53 100644 --- a/java/java-impl/src/com/intellij/codeInsight/generation/GenerationInfoBase.java +++ b/java/java-impl/src/com/intellij/codeInsight/generation/GenerationInfoBase.java @@ -20,6 +20,7 @@ import com.intellij.psi.JavaTokenType; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiMember; +import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -52,8 +53,7 @@ public abstract class GenerationInfoBase implements GenerationInfo { if (!GenerateMembersUtil.isChildInRange(element, lBrace.getNextSibling(), rBrace)) { return null; } - PsiElement prev = leaf.getPrevSibling(); - if (prev != null && prev.getNode() != null && prev.getNode().getElementType() == JavaTokenType.END_OF_LINE_COMMENT) { + if (leaf.getParent() == aClass && PsiUtilCore.getElementType(leaf.getPrevSibling()) == JavaTokenType.END_OF_LINE_COMMENT) { element = leaf.getNextSibling(); } return element; diff --git a/java/java-impl/src/com/intellij/codeInsight/hint/api/impls/MethodParameterInfoHandler.java b/java/java-impl/src/com/intellij/codeInsight/hint/api/impls/MethodParameterInfoHandler.java index 6819a9b4a276..1a9c4ad23e1b 100644 --- a/java/java-impl/src/com/intellij/codeInsight/hint/api/impls/MethodParameterInfoHandler.java +++ b/java/java-impl/src/com/intellij/codeInsight/hint/api/impls/MethodParameterInfoHandler.java @@ -23,12 +23,17 @@ import com.intellij.codeInsight.completion.CompletionMemory; import com.intellij.codeInsight.completion.JavaCompletionUtil; import com.intellij.codeInsight.completion.JavaMethodCallElement; import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; +import com.intellij.codeInsight.daemon.impl.ParameterHintsPresentationManager; +import com.intellij.codeInsight.hints.ParameterHintsPass; import com.intellij.codeInsight.javadoc.JavaDocInfoGenerator; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.lang.parameterInfo.*; import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.Inlay; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; import com.intellij.psi.impl.PsiImplUtil; import com.intellij.psi.impl.source.resolve.CompletionParameterTypeInferencePolicy; @@ -59,6 +64,8 @@ public class MethodParameterInfoHandler implements ParameterInfoHandlerWithTabAc PsiMethodCallExpression.class, PsiNewExpression.class, PsiAnonymousClass.class, PsiEnumConstant.class); private static final Set ourStopSearch = Collections.singleton(PsiMethod.class); + + private Inlay myHighlightedHint; @Override public Object[] getParametersForLookup(LookupElement item, ParameterInfoContext context) { @@ -125,38 +132,58 @@ public class MethodParameterInfoHandler implements ParameterInfoHandlerWithTabAc @Override public PsiExpressionList findElementForUpdatingParameterInfo(@NotNull final UpdateParameterInfoContext context) { PsiExpressionList expressionList = findArgumentList(context.getFile(), context.getOffset(), context.getParameterListStart()); - if (expressionList == null) return null; - Object[] candidates = context.getObjectsToView(); - if (candidates == null || candidates.length == 0) return null; - Object currentMethodInfo = context.getHighlightedParameter(); - if (currentMethodInfo == null) currentMethodInfo = candidates[0]; - if (!(currentMethodInfo instanceof CandidateInfo)) return null; - PsiElement element = ((CandidateInfo)currentMethodInfo).getElement(); - if (!(element instanceof PsiMethod)) return null; - - PsiMethod method = (PsiMethod)element; - PsiElement parent = expressionList.getParent(); - int currentNumberOfParameters = expressionList.getExpressions().length; - PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(context.getProject()); - Document document = psiDocumentManager.getCachedDocument(context.getFile()); - if ((context.getHighlightedParameter() != null || candidates.length == 1) && parent != null && - document != null && psiDocumentManager.isCommitted(document) && - isIncompatibleParameterCount(method, currentNumberOfParameters)) { - parent.putUserData(JavaMethodCallElement.COMPLETION_HINTS, null); + if (expressionList != null) { + Object[] candidates = context.getObjectsToView(); + if (candidates != null && candidates.length != 0) { + Object currentMethodInfo = context.getHighlightedParameter(); + if (currentMethodInfo == null) currentMethodInfo = candidates[0]; + if ((currentMethodInfo instanceof CandidateInfo)) { + PsiElement element = ((CandidateInfo)currentMethodInfo).getElement(); + if ((element instanceof PsiMethod)) { + PsiMethod method = (PsiMethod)element; + PsiElement parent = expressionList.getParent(); + + String originalMethodName = method.getName(); + PsiQualifiedReference currentMethodReference = null; + if (parent instanceof PsiMethodCallExpression && !method.isConstructor()) { + currentMethodReference = ((PsiMethodCallExpression)parent).getMethodExpression(); + } + else if (parent instanceof PsiNewExpression) { + currentMethodReference = ((PsiNewExpression)parent).getClassReference(); + } + else if (parent instanceof PsiAnonymousClass) { + currentMethodReference = ((PsiAnonymousClass)parent).getBaseClassReference(); + } + if (currentMethodReference == null || originalMethodName.equals(currentMethodReference.getReferenceName())) { + + int currentNumberOfParameters = expressionList.getExpressions().length; + PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(context.getProject()); + Document document = psiDocumentManager.getCachedDocument(context.getFile()); + if (parent instanceof PsiCallExpression && JavaMethodCallElement.isCompletionMode((PsiCall)parent)) { + PsiMethod chosenMethod = CompletionMemory.getChosenMethod((PsiCall)parent); + if ((context.getHighlightedParameter() != null || candidates.length == 1) && chosenMethod != null && + document != null && psiDocumentManager.isCommitted(document) && + isIncompatibleParameterCount(chosenMethod, currentNumberOfParameters)) { + JavaMethodCallElement.setCompletionMode((PsiCall)parent, false); + highlightHints(context.getEditor(), null, -1); + } + else { + int index = ParameterInfoUtils.getCurrentParameterIndex(expressionList.getNode(), + context.getOffset(), JavaTokenType.COMMA); + TextRange textRange = expressionList.getTextRange(); + if (context.getOffset() <= textRange.getStartOffset() || context.getOffset() >= textRange.getEndOffset()) index = -1; + highlightHints(context.getEditor(), expressionList, index); + } + } + + return expressionList; + } + } + } + } } - - String originalMethodName = method.getName(); - PsiQualifiedReference currentMethodReference = null; - if (parent instanceof PsiMethodCallExpression && !method.isConstructor()) { - currentMethodReference = ((PsiMethodCallExpression)parent).getMethodExpression(); - } - else if (parent instanceof PsiNewExpression) { - currentMethodReference = ((PsiNewExpression)parent).getClassReference(); - } - else if (parent instanceof PsiAnonymousClass) { - currentMethodReference = ((PsiAnonymousClass)parent).getBaseClassReference(); - } - return (currentMethodReference == null || originalMethodName.equals(currentMethodReference.getReferenceName())) ? expressionList : null; + highlightHints(context.getEditor(), null, -1); + return null; } private static boolean isIncompatibleParameterCount(@NotNull PsiMethod method, int numberOfParameters) { @@ -269,6 +296,56 @@ public class MethodParameterInfoHandler implements ParameterInfoHandlerWithTabAc } } + private void highlightHints(@NotNull Editor editor, @Nullable PsiExpressionList expressionList, int currentHintIndex) { + if (editor.isDisposed()) return; + ParameterHintsPresentationManager presentationManager = ParameterHintsPresentationManager.getInstance(); + Inlay hint = null; + if (expressionList != null && expressionList.isValid() && + currentHintIndex >= 0 && (currentHintIndex < expressionList.getExpressions().length || + currentHintIndex == 0 && expressionList.getExpressions().length == 0)) { + PsiElement prevDelimiter, nextDelimiter; + if (currentHintIndex < expressionList.getExpressions().length) { + PsiExpression expression = expressionList.getExpressions()[currentHintIndex]; + //noinspection StatementWithEmptyBody + for (prevDelimiter = expression; + prevDelimiter != null && !(prevDelimiter instanceof PsiJavaToken); + prevDelimiter = prevDelimiter.getPrevSibling()) + ; + //noinspection StatementWithEmptyBody + for (nextDelimiter = expression; + nextDelimiter != null && !(nextDelimiter instanceof PsiJavaToken); + nextDelimiter = nextDelimiter.getNextSibling()) + ; + } + else { + prevDelimiter = expressionList.getFirstChild(); // left parenthesis + nextDelimiter = expressionList.getLastChild(); // right parenthesis + } + if (prevDelimiter != null && nextDelimiter != null) { + ParameterHintsPass.syncUpdate(expressionList.getParent(), editor); + for (Inlay inlay : editor.getInlayModel().getInlineElementsInRange(prevDelimiter.getTextRange().getEndOffset(), + nextDelimiter.getTextRange().getStartOffset())) { + if (presentationManager.isParameterHint(inlay)) { + hint = inlay; + break; + } + } + } + } + if (hint == myHighlightedHint) return; + if (myHighlightedHint != null && myHighlightedHint.isValid()) presentationManager.setHighlighted(myHighlightedHint, false); + myHighlightedHint = hint; + if (myHighlightedHint != null && myHighlightedHint.isValid()) presentationManager.setHighlighted(myHighlightedHint, true); + } + + @Override + public void dispose() { + if (myHighlightedHint != null) { + if (myHighlightedHint.isValid()) ParameterHintsPresentationManager.getInstance().setHighlighted(myHighlightedHint, false); + myHighlightedHint = null; + } + } + private static PsiSubstitutor getCandidateInfoSubstitutor(CandidateInfo candidate) { return candidate instanceof MethodCandidateInfo && ((MethodCandidateInfo)candidate).isInferencePossible() ? ((MethodCandidateInfo)candidate).inferTypeArguments(CompletionParameterTypeInferencePolicy.INSTANCE, true) diff --git a/java/java-impl/src/com/intellij/codeInsight/hints/JavaHintUtils.kt b/java/java-impl/src/com/intellij/codeInsight/hints/JavaHintUtils.kt index 3ed6ea0dd58c..7fbb2f97859c 100644 --- a/java/java-impl/src/com/intellij/codeInsight/hints/JavaHintUtils.kt +++ b/java/java-impl/src/com/intellij/codeInsight/hints/JavaHintUtils.kt @@ -23,12 +23,13 @@ import com.intellij.psi.impl.source.resolve.graphInference.PsiPolyExpressionUtil import com.intellij.psi.impl.source.tree.java.PsiMethodCallExpressionImpl import com.intellij.psi.impl.source.tree.java.PsiNewExpressionImpl import com.intellij.psi.util.TypeConversionUtil +import com.intellij.util.IncorrectOperationException object JavaInlayHintsProvider { fun hints(callExpression: PsiCallExpression): Set { - if (JavaMethodCallElement.showCompletionHints(callExpression)) { + if (JavaMethodCallElement.isCompletionMode(callExpression)) { val method = CompletionMemory.getChosenMethod(callExpression)?:return emptySet() val params = method.parameterList.parameters @@ -46,7 +47,7 @@ object JavaInlayHintsProvider { }.toSet() } - if (!EditorSettingsExternalizable.getInstance().isShowParameterNameHints()) return emptySet() + if (!EditorSettingsExternalizable.getInstance().isShowParameterNameHints) return emptySet() val resolveResult = callExpression.resolveMethodGenerics() val hints = methodHints(callExpression, resolveResult) @@ -88,12 +89,55 @@ object JavaInlayHintsProvider { if (element is PsiMethod && isMethodToShow(element, callExpression)) { val info = callInfo(callExpression, element) - return hintSet(info, substitutor) + if (isCallInfoToShow(info)) { + return hintSet(info, substitutor) + } } return emptySet() } + private fun isCallInfoToShow(info: CallInfo): Boolean { + val hintsProvider = JavaInlayParameterHintsProvider.getInstance() + if (hintsProvider.ignoreOneCharOneDigitHints.get() && info.allParamsSequential()) { + return false + } + return true + } + + private fun String.decomposeOrderedParams(): Pair? { + val firstDigit = indexOfFirst { it.isDigit() } + if (firstDigit < 0) return null + + val prefix = substring(0, firstDigit) + try { + val number = substring(firstDigit, length).toInt() + return prefix to number + } + catch (e: NumberFormatException) { + return null + } + } + + private fun CallInfo.allParamsSequential(): Boolean { + val paramNames = regularArgs + .map { it.parameter.name?.decomposeOrderedParams() } + .filterNotNull() + + if (paramNames.size > 1 && paramNames.size == regularArgs.size) { + val prefixes = paramNames.map { it.first } + if (prefixes.toSet().size != 1) return false + + val numbers = paramNames.map { it.second } + val first = numbers.first() + if (first == 0 || first == 1) { + return numbers.areSequential() + } + } + + return false + } + private fun hintSet(info: CallInfo, substitutor: PsiSubstitutor): Set { val resultSet = mutableSetOf() @@ -107,7 +151,7 @@ object JavaInlayHintsProvider { } resultSet.addAll(info.unclearInlays(substitutor)) - + return resultSet } @@ -169,6 +213,15 @@ object JavaInlayHintsProvider { } +private fun List.areSequential(): Boolean { + if (size == 0) throw IncorrectOperationException("List is empty") + val ordered = (first()..first() + size - 1).toList() + if (ordered.size == size) { + return zip(ordered).all { it.first == it.second } + } + return false +} + private fun inlayInfo(info: CallArgumentInfo, showOnlyIfExistedBefore: Boolean = false): InlayInfo? { return inlayInfo(info.argument, info.parameter, showOnlyIfExistedBefore) diff --git a/java/java-impl/src/com/intellij/codeInsight/hints/JavaInlayParameterHintsProvider.kt b/java/java-impl/src/com/intellij/codeInsight/hints/JavaInlayParameterHintsProvider.kt index 1e74940b4623..ee2eb66894b3 100644 --- a/java/java-impl/src/com/intellij/codeInsight/hints/JavaInlayParameterHintsProvider.kt +++ b/java/java-impl/src/com/intellij/codeInsight/hints/JavaInlayParameterHintsProvider.kt @@ -119,12 +119,18 @@ class JavaInlayParameterHintsProvider : InlayParameterHintsProvider { val isDoNotShowForBuilderLikeMethods = Option("java.build.like.method", "Do not show for builder-like methods", true) - + + + val ignoreOneCharOneDigitHints = Option("java.simple.sequentially.numbered", + "Do not show for methods with same-named numbered parameters", + true) + override fun getSupportedOptions(): List

+ * JVM element represents a compiled element from perspective of the JVM. + */ +public interface JvmElement extends PomTarget { + + /** + * @return corresponsing source element or {@code null} if no source element is available + */ + @Nullable + PsiElement getSourceElement(); +} diff --git a/java/java-psi-api/src/com/intellij/lang/jvm/JvmField.java b/java/java-psi-api/src/com/intellij/lang/jvm/JvmField.java new file mode 100644 index 000000000000..ca4e8b6fc5cf --- /dev/null +++ b/java/java-psi-api/src/com/intellij/lang/jvm/JvmField.java @@ -0,0 +1,41 @@ +/* + * Copyright 2000-2017 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.lang.jvm; + +import com.intellij.lang.jvm.types.JvmType; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a field. + * + * @see java.lang.reflect.Field + */ +public interface JvmField extends JvmMember { + + /** + * @see java.lang.reflect.Field#getName + */ + @NotNull + @Override + String getName(); + + /** + * @see java.lang.reflect.Field#getGenericType + * @see java.lang.reflect.Field#getAnnotatedType + */ + @NotNull + JvmType getType(); +} diff --git a/java/java-psi-api/src/com/intellij/lang/jvm/JvmMember.java b/java/java-psi-api/src/com/intellij/lang/jvm/JvmMember.java new file mode 100644 index 000000000000..c6aeec8352d5 --- /dev/null +++ b/java/java-psi-api/src/com/intellij/lang/jvm/JvmMember.java @@ -0,0 +1,30 @@ +/* + * Copyright 2000-2017 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.lang.jvm; + +import org.jetbrains.annotations.Nullable; + +/** + * @see java.lang.reflect.Member + */ +public interface JvmMember extends JvmModifiersOwner, JvmNamedElement { + + /** + * @see java.lang.reflect.Member#getDeclaringClass + */ + @Nullable + JvmClass getContainingClass(); +} diff --git a/java/java-psi-api/src/com/intellij/lang/jvm/JvmMethod.java b/java/java-psi-api/src/com/intellij/lang/jvm/JvmMethod.java new file mode 100644 index 000000000000..a05e43a400c4 --- /dev/null +++ b/java/java-psi-api/src/com/intellij/lang/jvm/JvmMethod.java @@ -0,0 +1,69 @@ +/* + * Copyright 2000-2017 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.lang.jvm; + +import com.intellij.lang.jvm.types.JvmReferenceType; +import com.intellij.lang.jvm.types.JvmType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a method or a constructor. + * + * @see java.lang.reflect.Method + * @see java.lang.reflect.Constructor + * @see java.lang.reflect.Executable + */ +public interface JvmMethod extends JvmTypeParametersOwner { + + /** + * @return {@code true} if this method is a constructor + */ + boolean isConstructor(); + + /** + * @see java.lang.reflect.Executable#getName + */ + @NotNull + @Override + String getName(); + + /** + * @return return type of a method or {@code null} if this {@code JvmMethod} represents a constructor + * @see java.lang.reflect.Method#getGenericReturnType + * @see java.lang.reflect.Method#getAnnotatedReturnType + */ + @Nullable + JvmType getReturnType(); + + /** + * @see java.lang.reflect.Executable#getParameters + */ + @NotNull + JvmParameter[] getParameters(); + + /** + * @see java.lang.reflect.Executable#isVarArgs + */ + boolean isVarArgs(); + + /** + * @see java.lang.reflect.Method#getGenericExceptionTypes + * @see java.lang.reflect.Method#getAnnotatedExceptionTypes + */ + @NotNull + JvmReferenceType[] getThrowsTypes(); +} diff --git a/java/java-psi-api/src/com/intellij/lang/jvm/JvmModifier.java b/java/java-psi-api/src/com/intellij/lang/jvm/JvmModifier.java new file mode 100644 index 000000000000..945d0eb1c9fb --- /dev/null +++ b/java/java-psi-api/src/com/intellij/lang/jvm/JvmModifier.java @@ -0,0 +1,37 @@ +/* + * Copyright 2000-2017 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.lang.jvm; + +public enum JvmModifier { + PUBLIC, + PROTECTED, + PRIVATE, + PACKAGE_LOCAL, + + STATIC, + ABSTRACT, + FINAL, + DEFAULT, + + NATIVE, + SYNCHRONIZED, + STRICTFP, + TRANSIENT, + VOLATILE, + TRANSITIVE; + + public static final JvmModifier[] EMPTY_ARRAY = new JvmModifier[0]; +} diff --git a/java/java-psi-api/src/com/intellij/lang/jvm/JvmModifiersOwner.java b/java/java-psi-api/src/com/intellij/lang/jvm/JvmModifiersOwner.java new file mode 100644 index 000000000000..cabeea893062 --- /dev/null +++ b/java/java-psi-api/src/com/intellij/lang/jvm/JvmModifiersOwner.java @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2017 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.lang.jvm; + +import org.jetbrains.annotations.NotNull; + +import static com.intellij.util.ArrayUtil.contains; + +/** + * Represents an element which has modifiers and annotations. + */ +public interface JvmModifiersOwner extends JvmAnnotatedElement { + + @NotNull + JvmModifier[] getModifiers(); + + /** + * Checks if the element effectively has the specified modifier. + * + * @param modifier the modifier to check + * @return true if the element has the modifier, false otherwise + */ + default boolean hasModifier(@NotNull JvmModifier modifier) { + return contains(getModifiers(), modifier); + } +} diff --git a/java/java-psi-api/src/com/intellij/lang/jvm/JvmNamedElement.java b/java/java-psi-api/src/com/intellij/lang/jvm/JvmNamedElement.java new file mode 100644 index 000000000000..de0358c2d86c --- /dev/null +++ b/java/java-psi-api/src/com/intellij/lang/jvm/JvmNamedElement.java @@ -0,0 +1,26 @@ +/* + * Copyright 2000-2017 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.lang.jvm; + +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; + +public interface JvmNamedElement extends JvmElement { + + @NonNls + @Nullable + String getName(); +} diff --git a/java/java-psi-api/src/com/intellij/lang/jvm/JvmParameter.java b/java/java-psi-api/src/com/intellij/lang/jvm/JvmParameter.java new file mode 100644 index 000000000000..e5b3ac0c17b9 --- /dev/null +++ b/java/java-psi-api/src/com/intellij/lang/jvm/JvmParameter.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2017 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.lang.jvm; + +import com.intellij.lang.jvm.types.JvmType; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a parameter of a method or a constructor. + * + * @see java.lang.reflect.Parameter + */ +public interface JvmParameter extends JvmNamedElement, JvmModifiersOwner { + + /** + * @see java.lang.reflect.Parameter#getParameterizedType + * @see java.lang.reflect.Parameter#getAnnotatedType + */ + @NotNull + JvmType getType(); +} diff --git a/java/java-psi-api/src/com/intellij/lang/jvm/JvmTypeDeclaration.java b/java/java-psi-api/src/com/intellij/lang/jvm/JvmTypeDeclaration.java new file mode 100644 index 000000000000..0974d06b885f --- /dev/null +++ b/java/java-psi-api/src/com/intellij/lang/jvm/JvmTypeDeclaration.java @@ -0,0 +1,22 @@ +/* + * Copyright 2000-2017 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.lang.jvm; + +/** + * Represents an element which declares a type, such as class or type parameter. + */ +public interface JvmTypeDeclaration extends JvmNamedElement, JvmAnnotatedElement { +} diff --git a/java/java-psi-api/src/com/intellij/lang/jvm/JvmTypeParameter.java b/java/java-psi-api/src/com/intellij/lang/jvm/JvmTypeParameter.java new file mode 100644 index 000000000000..d819942dc5cd --- /dev/null +++ b/java/java-psi-api/src/com/intellij/lang/jvm/JvmTypeParameter.java @@ -0,0 +1,43 @@ +/* + * Copyright 2000-2017 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.lang.jvm; + +import com.intellij.lang.jvm.types.JvmReferenceType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a type parameter. + * + * @see java.lang.reflect.TypeVariable + */ +public interface JvmTypeParameter extends JvmTypeDeclaration { + + /** + * @return bounds of this type parameter + * @see java.lang.reflect.TypeVariable#getBounds + * @see java.lang.reflect.TypeVariable#getAnnotatedBounds + */ + @NotNull + JvmReferenceType[] getBounds(); + + /** + * @return the element which is parameterized by this type parameter + * @see java.lang.reflect.TypeVariable#getGenericDeclaration + */ + @Nullable + JvmTypeParametersOwner getOwner(); +} diff --git a/java/java-psi-api/src/com/intellij/lang/jvm/JvmTypeParametersOwner.java b/java/java-psi-api/src/com/intellij/lang/jvm/JvmTypeParametersOwner.java new file mode 100644 index 000000000000..b6231f80ceb2 --- /dev/null +++ b/java/java-psi-api/src/com/intellij/lang/jvm/JvmTypeParametersOwner.java @@ -0,0 +1,32 @@ +/* + * Copyright 2000-2017 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.lang.jvm; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents an element which declares type parameters. + * + * @see java.lang.reflect.GenericDeclaration + */ +public interface JvmTypeParametersOwner extends JvmMember { + + /** + * @see java.lang.reflect.GenericDeclaration#getTypeParameters + */ + @NotNull + JvmTypeParameter[] getTypeParameters(); +} diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/ideSettings/IdeSettingsDescriptor.java b/java/java-psi-api/src/com/intellij/lang/jvm/facade/JvmElementProvider.java similarity index 51% rename from platform/platform-impl/src/com/intellij/internal/statistic/ideSettings/IdeSettingsDescriptor.java rename to java/java-psi-api/src/com/intellij/lang/jvm/facade/JvmElementProvider.java index 3e53c331e427..9e150ab600e5 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/ideSettings/IdeSettingsDescriptor.java +++ b/java/java-psi-api/src/com/intellij/lang/jvm/facade/JvmElementProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -13,26 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.internal.statistic.ideSettings; +package com.intellij.lang.jvm.facade; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.util.xmlb.annotations.Attribute; -import com.intellij.util.xmlb.annotations.Tag; +import com.intellij.lang.jvm.JvmClass; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.psi.search.GlobalSearchScope; import org.jetbrains.annotations.NotNull; -import java.util.Collections; import java.util.List; -@Tag("setting") -public class IdeSettingsDescriptor { - @Attribute("persistent-component") - public String myProviderName; +public interface JvmElementProvider { - @Attribute("properties") - public String myPropertyNames; + ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.jvm.elementProvider"); @NotNull - public List getPropertyNames() { - return myPropertyNames == null ? Collections.emptyList() : StringUtil.split(myPropertyNames, ","); - } + List getClasses(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope); } diff --git a/java/java-psi-api/src/com/intellij/lang/jvm/facade/JvmFacade.java b/java/java-psi-api/src/com/intellij/lang/jvm/facade/JvmFacade.java new file mode 100644 index 000000000000..d0d7dae8937d --- /dev/null +++ b/java/java-psi-api/src/com/intellij/lang/jvm/facade/JvmFacade.java @@ -0,0 +1,62 @@ +/* + * Copyright 2000-2017 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.lang.jvm.facade; + +import com.intellij.lang.jvm.JvmClass; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.psi.search.GlobalSearchScope; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +import static com.intellij.util.containers.ContainerUtil.getFirstItem; + +public interface JvmFacade { + + @NotNull + static JvmFacade getInstance(@NotNull Project project) { + return ServiceManager.getService(project, JvmFacade.class); + } + + /** + * Searches the specified scope within the project for a class with the specified full-qualified + * name and returns one if it is found. + * + * @param qualifiedName the full-qualified name of the class to find. + * @param scope the scope to search. + * @return the PSI class, or null if no class with such name is found. + */ + @Nullable + default JvmClass findClass(@NonNls @NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { + ProgressManager.checkCanceled(); + return getFirstItem(findClasses(qualifiedName, scope)); + } + + /** + * Searches the specified scope within the project for classes with the specified full-qualified + * name and returns all found classes. + * + * @param qualifiedName the full-qualified name of the class to find. + * @param scope the scope to search. + * @return the array of found classes, or an empty array if no classes are found. + */ + @NotNull + List findClasses(@NonNls @NotNull String qualifiedName, @NotNull GlobalSearchScope scope); +} diff --git a/java/java-psi-api/src/com/intellij/lang/jvm/package-info.java b/java/java-psi-api/src/com/intellij/lang/jvm/package-info.java new file mode 100644 index 000000000000..dfb7acc3de18 --- /dev/null +++ b/java/java-psi-api/src/com/intellij/lang/jvm/package-info.java @@ -0,0 +1,30 @@ +/* + * Copyright 2000-2017 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. + */ +/** + * Classes from this package represent elements of JVM class-files + * which will be produced from source code of a JVM-based language after compilation. + * They can be used to implement features for all JVM-based languages at once + * if these features depend on structure of produced class-files rather than on source code constructions. + *

+ * E.g. if a framework provides an annotation and requires that signatures of methods annotated by it must satisfy some requirements + * it's possible to write a single inspection which will check this for all JVM-based languages which supports these interfaces. + * + * @see com.intellij.lang.jvm.JvmElement + */ +@Experimental +package com.intellij.lang.jvm; + +import org.jetbrains.annotations.ApiStatus.Experimental; \ No newline at end of file diff --git a/java/java-psi-api/src/com/intellij/lang/jvm/types/JvmArrayType.java b/java/java-psi-api/src/com/intellij/lang/jvm/types/JvmArrayType.java new file mode 100644 index 000000000000..3cbcea9d8c19 --- /dev/null +++ b/java/java-psi-api/src/com/intellij/lang/jvm/types/JvmArrayType.java @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2017 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.lang.jvm.types; + +import org.jetbrains.annotations.NotNull; + +/** + * @see Class#isArray + */ +public interface JvmArrayType extends JvmType { + + /** + * @return component type of an array. That is: + *

    + *
  • for {@code int[]}: {@code int} + *
  • for {@code String[][]}: {@code String[]} + *
+ * @see Class#getComponentType + */ + @NotNull + JvmType getComponentType(); +} diff --git a/java/java-psi-api/src/com/intellij/lang/jvm/types/JvmPrimitiveType.java b/java/java-psi-api/src/com/intellij/lang/jvm/types/JvmPrimitiveType.java new file mode 100644 index 000000000000..9ed556baeba9 --- /dev/null +++ b/java/java-psi-api/src/com/intellij/lang/jvm/types/JvmPrimitiveType.java @@ -0,0 +1,24 @@ +/* + * Copyright 2000-2017 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.lang.jvm.types; + +/** + * Represents a primitive type of JVM. + * + * @see Class#isPrimitive + */ +public interface JvmPrimitiveType extends JvmType { +} diff --git a/java/java-psi-api/src/com/intellij/lang/jvm/types/JvmReferenceType.java b/java/java-psi-api/src/com/intellij/lang/jvm/types/JvmReferenceType.java new file mode 100644 index 000000000000..527802de6350 --- /dev/null +++ b/java/java-psi-api/src/com/intellij/lang/jvm/types/JvmReferenceType.java @@ -0,0 +1,61 @@ +/* + * Copyright 2000-2017 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.lang.jvm.types; + +import com.intellij.lang.jvm.JvmTypeDeclaration; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a type which could be resolved into a class or a type parameter and optionally has type arguments. + *
+ * Such type appears in throws, bounds, extends, implements. + *

+ * While {@link Class} and {@link java.lang.reflect.TypeVariable TypeVariable} + * are {@link java.lang.reflect.Type types} because they are resolved at runtime, + * this interface defines the contract for delaying actual resolution of the type declaration. + * + * @see java.lang.reflect.ParameterizedType + */ +public interface JvmReferenceType extends JvmType { + + JvmReferenceType[] EMPTY_ARRAY = new JvmReferenceType[0]; + + @NotNull + String getName(); + + /** + * @return declaration that declares this type or {@code null} if it cannot be resolved + */ + @Nullable + default JvmTypeDeclaration resolve() { + JvmTypeResolveResult result = resolveType(); + return result == null ? null : result.getDeclaration(); + } + + /** + * @return resolve result or {@code null} if it cannot be resolved + */ + @Nullable + JvmTypeResolveResult resolveType(); + + /** + * @return type arguments or empty iterable if this type is not a parameterized type + * @see java.lang.reflect.ParameterizedType#getActualTypeArguments + */ + @NotNull + Iterable typeArguments(); +} diff --git a/java/java-psi-api/src/com/intellij/lang/jvm/types/JvmSubstitutor.java b/java/java-psi-api/src/com/intellij/lang/jvm/types/JvmSubstitutor.java new file mode 100644 index 000000000000..73fd43e1da3e --- /dev/null +++ b/java/java-psi-api/src/com/intellij/lang/jvm/types/JvmSubstitutor.java @@ -0,0 +1,26 @@ +/* + * Copyright 2000-2017 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.lang.jvm.types; + +import com.intellij.lang.jvm.JvmTypeParameter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface JvmSubstitutor { + + @Nullable + JvmType substitute(@NotNull JvmTypeParameter typeParameter); +} diff --git a/java/java-psi-api/src/com/intellij/lang/jvm/types/JvmType.java b/java/java-psi-api/src/com/intellij/lang/jvm/types/JvmType.java new file mode 100644 index 000000000000..5f1064535a49 --- /dev/null +++ b/java/java-psi-api/src/com/intellij/lang/jvm/types/JvmType.java @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2017 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.lang.jvm.types; + +import com.intellij.lang.jvm.JvmAnnotation; +import org.jetbrains.annotations.NotNull; + +/** + * Represents an type which is supported by JVM. + * + * @see java.lang.reflect.Type + * @see java.lang.reflect.AnnotatedType + */ +public interface JvmType { + + /** + * @return type annotations + * @see java.lang.reflect.AnnotatedType#getAnnotations + */ + @NotNull + JvmAnnotation[] getAnnotations(); +} diff --git a/java/java-psi-api/src/com/intellij/lang/jvm/types/JvmTypeResolveResult.java b/java/java-psi-api/src/com/intellij/lang/jvm/types/JvmTypeResolveResult.java new file mode 100644 index 000000000000..1a69370bd940 --- /dev/null +++ b/java/java-psi-api/src/com/intellij/lang/jvm/types/JvmTypeResolveResult.java @@ -0,0 +1,31 @@ +/* + * Copyright 2000-2017 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.lang.jvm.types; + +import com.intellij.lang.jvm.JvmTypeDeclaration; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a result of resolving a {@link JvmReferenceType reference type}. + */ +public interface JvmTypeResolveResult { + + @NotNull + JvmTypeDeclaration getDeclaration(); + + @NotNull + JvmSubstitutor getSubstitutor(); +} diff --git a/java/java-psi-api/src/com/intellij/lang/jvm/types/JvmWildcardType.java b/java/java-psi-api/src/com/intellij/lang/jvm/types/JvmWildcardType.java new file mode 100644 index 000000000000..b0661d974736 --- /dev/null +++ b/java/java-psi-api/src/com/intellij/lang/jvm/types/JvmWildcardType.java @@ -0,0 +1,57 @@ +/* + * Copyright 2000-2017 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.lang.jvm.types; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents wildcard type, for example {@code ? extends Number} in {@code List}. + * + * @see java.lang.reflect.WildcardType + */ +public interface JvmWildcardType extends JvmType { + + /** + * An upper bound that this wildcard imposes on type parameter value.
+ * That is: + *

    + *
  • for {@code ? extends XXX}: {@code XXX} + *
  • for {@code ? super XXX}: {@code java.lang.Object} + *
  • for {@code ?}: {@code java.lang.Object} + *
+ *

+ * + * @return an upper bound + * @see java.lang.reflect.WildcardType#getUpperBounds + */ + @NotNull + JvmType upperBound(); + + /** + * A lower bound that this wildcard imposes on type parameter value.
+ * That is: + *

    + *
  • for {@code ? extends XXX}: empty iterable + *
  • for {@code ? super XXX}: {@code XXX} + *
  • for {@code ?}: null type + *
+ * + * @return a lower bound + * @see java.lang.reflect.WildcardType#getLowerBounds() + */ + @NotNull + JvmType lowerBound(); +} diff --git a/java/java-psi-api/src/com/intellij/lang/jvm/types/package-info.java b/java/java-psi-api/src/com/intellij/lang/jvm/types/package-info.java new file mode 100644 index 000000000000..c92b958e5d99 --- /dev/null +++ b/java/java-psi-api/src/com/intellij/lang/jvm/types/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2000-2017 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. + */ +@Experimental +package com.intellij.lang.jvm.types; + +import org.jetbrains.annotations.ApiStatus.Experimental; \ No newline at end of file diff --git a/java/java-psi-api/src/com/intellij/psi/PsiAnnotation.java b/java/java-psi-api/src/com/intellij/psi/PsiAnnotation.java index c84f5080631b..319bf53b4047 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiAnnotation.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiAnnotation.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -15,6 +15,7 @@ */ package com.intellij.psi; +import com.intellij.lang.jvm.JvmAnnotation; import com.intellij.psi.meta.PsiMetaOwner; import com.intellij.util.ArrayFactory; import org.jetbrains.annotations.NonNls; @@ -26,7 +27,7 @@ import org.jetbrains.annotations.Nullable; * * @author ven */ -public interface PsiAnnotation extends PsiAnnotationMemberValue, PsiMetaOwner { +public interface PsiAnnotation extends PsiAnnotationMemberValue, PsiMetaOwner, JvmAnnotation { /** * The empty array of PSI annotations which can be reused to avoid unnecessary allocations. */ @@ -110,4 +111,10 @@ public interface PsiAnnotation extends PsiAnnotationMemberValue, PsiMetaOwner { */ @Nullable PsiAnnotationOwner getOwner(); + + @NotNull + @Override + default PsiElement getSourceElement() { + return this; + } } diff --git a/java/java-psi-api/src/com/intellij/psi/PsiArrayType.java b/java/java-psi-api/src/com/intellij/psi/PsiArrayType.java index cce3516e408c..6c950ad5e293 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiArrayType.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiArrayType.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -15,6 +15,7 @@ */ package com.intellij.psi; +import com.intellij.lang.jvm.types.JvmArrayType; import com.intellij.psi.search.GlobalSearchScope; import org.jetbrains.annotations.NotNull; @@ -23,7 +24,7 @@ import org.jetbrains.annotations.NotNull; * * @author max */ -public class PsiArrayType extends PsiType.Stub { +public class PsiArrayType extends PsiType.Stub implements JvmArrayType { private final PsiType myComponentType; public PsiArrayType(@NotNull PsiType componentType) { @@ -112,6 +113,7 @@ public class PsiArrayType extends PsiType.Stub { * @return the component type instance. */ @NotNull + @Override public PsiType getComponentType() { return myComponentType; } diff --git a/java/java-psi-api/src/com/intellij/psi/PsiClass.java b/java/java-psi-api/src/com/intellij/psi/PsiClass.java index 5565fafd8499..175aec11f667 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiClass.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiClass.java @@ -15,10 +15,13 @@ */ package com.intellij.psi; +import com.intellij.lang.jvm.JvmClass; +import com.intellij.lang.jvm.JvmClassKind; +import com.intellij.lang.jvm.types.JvmReferenceType; import com.intellij.openapi.util.Pair; +import com.intellij.pom.PomRenameableTarget; import com.intellij.util.ArrayFactory; import com.intellij.util.IncorrectOperationException; -import com.intellij.pom.PomRenameableTarget; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -26,6 +29,8 @@ import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.List; +import static com.intellij.psi.PsiJvmConversionHelper.*; + /** * Represents a Java class or interface. * @@ -33,7 +38,7 @@ import java.util.List; */ public interface PsiClass extends PsiNameIdentifierOwner, PsiModifierListOwner, PsiDocCommentOwner, PsiTypeParameterListOwner, - PsiQualifiedNamedElement, PsiTarget, PomRenameableTarget { + PsiQualifiedNamedElement, PsiTarget, PomRenameableTarget, JvmClass { /** * The empty array of PSI classes which can be reused to avoid unnecessary allocations. */ @@ -353,4 +358,22 @@ public interface PsiClass @Override PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException; + + @NotNull + @Override + default JvmClassKind getClassKind() { + return getJvmClassKind(this); + } + + @Nullable + @Override + default JvmReferenceType getSuperClassType() { + return getClassSuperType(this); + } + + @NotNull + @Override + default JvmReferenceType[] getInterfaceTypes() { + return getClassInterfaces(this); + } } \ No newline at end of file diff --git a/java/java-psi-api/src/com/intellij/psi/PsiClassType.java b/java/java-psi-api/src/com/intellij/psi/PsiClassType.java index ccd277dee6c8..0fab704ae82a 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiClassType.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiClassType.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -15,6 +15,11 @@ */ package com.intellij.psi; +import com.intellij.lang.jvm.JvmTypeDeclaration; +import com.intellij.lang.jvm.types.JvmReferenceType; +import com.intellij.lang.jvm.types.JvmSubstitutor; +import com.intellij.lang.jvm.types.JvmType; +import com.intellij.lang.jvm.types.JvmTypeResolveResult; import com.intellij.openapi.util.Comparing; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.search.GlobalSearchScope; @@ -24,12 +29,14 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Arrays; + /** * Represents a class type. * * @author max */ -public abstract class PsiClassType extends PsiType { +public abstract class PsiClassType extends PsiType implements JvmReferenceType { public static final PsiClassType[] EMPTY_ARRAY = new PsiClassType[0]; public static final ArrayFactory ARRAY_FACTORY = count -> count == 0 ? EMPTY_ARRAY : new PsiClassType[count]; @@ -243,6 +250,41 @@ public abstract class PsiClassType extends PsiType { @Contract(pure = true) public abstract PsiClassType setLanguageLevel(@NotNull LanguageLevel languageLevel); + @NotNull + @Override + public String getName() { + return getClassName(); + } + + @Nullable + @Override + public JvmTypeResolveResult resolveType() { + ClassResolveResult resolveResult = resolveGenerics(); + PsiClass clazz = resolveResult.getElement(); + return clazz == null ? null : new JvmTypeResolveResult() { + + private final JvmSubstitutor mySubstitutor = new PsiJvmConversionHelper.PsiJvmSubstitutor(resolveResult.getSubstitutor()); + + @NotNull + @Override + public JvmTypeDeclaration getDeclaration() { + return clazz; + } + + @NotNull + @Override + public JvmSubstitutor getSubstitutor() { + return mySubstitutor; + } + }; + } + + @NotNull + @Override + public Iterable typeArguments() { + return Arrays.asList(getParameters()); + } + /** * Represents the result of resolving a reference to a Java class. */ diff --git a/java/java-psi-api/src/com/intellij/psi/PsiField.java b/java/java-psi-api/src/com/intellij/psi/PsiField.java index 7a40d47408fd..68beef3cd491 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiField.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiField.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -15,6 +15,7 @@ */ package com.intellij.psi; +import com.intellij.lang.jvm.JvmField; import com.intellij.util.ArrayFactory; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NotNull; @@ -23,7 +24,7 @@ import org.jetbrains.annotations.Nullable; /** * Represents a Java field or enum constant. */ -public interface PsiField extends PsiMember, PsiVariable, PsiDocCommentOwner { +public interface PsiField extends PsiMember, PsiVariable, PsiDocCommentOwner, JvmField { /** * The empty array of PSI fields which can be reused to avoid unnecessary allocations. */ @@ -43,4 +44,10 @@ public interface PsiField extends PsiMember, PsiVariable, PsiDocCommentOwner { @Override @NotNull PsiIdentifier getNameIdentifier(); + + /* This explicit declaration is required to force javac generate bridge method 'JvmType getType()'; without it calling + JvmField#getType() method on instances which weren't recompiled against the new API will cause AbstractMethodError. */ + @NotNull + @Override + PsiType getType(); } diff --git a/java/java-psi-api/src/com/intellij/psi/PsiJvmConversionHelper.java b/java/java-psi-api/src/com/intellij/psi/PsiJvmConversionHelper.java new file mode 100644 index 000000000000..d5d5894a81c8 --- /dev/null +++ b/java/java-psi-api/src/com/intellij/psi/PsiJvmConversionHelper.java @@ -0,0 +1,124 @@ +/* + * Copyright 2000-2017 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.psi; + +import com.intellij.lang.jvm.JvmClassKind; +import com.intellij.lang.jvm.JvmModifier; +import com.intellij.lang.jvm.JvmTypeParameter; +import com.intellij.lang.jvm.types.JvmReferenceType; +import com.intellij.lang.jvm.types.JvmSubstitutor; +import com.intellij.lang.jvm.types.JvmType; +import com.intellij.openapi.diagnostic.Logger; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.EnumSet; +import java.util.Set; + +import static com.intellij.psi.PsiType.getJavaLangObject; +import static com.intellij.psi.PsiType.getTypeByName; + +class PsiJvmConversionHelper { + + private static final Logger LOG = Logger.getInstance(PsiJvmConversionHelper.class); + + @NotNull + static PsiAnnotation[] getListAnnotations(@NotNull PsiModifierListOwner modifierListOwner) { + PsiModifierList list = modifierListOwner.getModifierList(); + return list == null ? PsiAnnotation.EMPTY_ARRAY : list.getAnnotations(); + } + + @NotNull + static JvmModifier[] getListModifiers(@NotNull PsiModifierListOwner modifierListOwner) { + final Set result = EnumSet.noneOf(JvmModifier.class); + for (@NonNls String modifier : PsiModifier.MODIFIERS) { + if (modifierListOwner.hasModifierProperty(modifier)) { + String jvmName = modifier.toUpperCase(); + JvmModifier jvmModifier = JvmModifier.valueOf(jvmName); + result.add(jvmModifier); + } + } + if (modifierListOwner.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) { + result.add(JvmModifier.PACKAGE_LOCAL); + } + return result.toArray(JvmModifier.EMPTY_ARRAY); + } + + @NotNull + static JvmClassKind getJvmClassKind(@NotNull PsiClass psiClass) { + if (psiClass.isAnnotationType()) return JvmClassKind.ANNOTATION; + if (psiClass.isInterface()) return JvmClassKind.INTERFACE; + if (psiClass.isEnum()) return JvmClassKind.ENUM; + return JvmClassKind.CLASS; + } + + @Nullable + static JvmReferenceType getClassSuperType(@NotNull PsiClass psiClass) { + if (psiClass.isInterface()) return null; + if (psiClass.isEnum()) return getTypeByName(CommonClassNames.JAVA_LANG_ENUM, psiClass.getProject(), psiClass.getResolveScope()); + if (psiClass instanceof PsiAnonymousClass) { + PsiClassType baseClassType = ((PsiAnonymousClass)psiClass).getBaseClassType(); + PsiClass baseClass = baseClassType.resolve(); + if (baseClass == null || !baseClass.isInterface()) { + return baseClassType; + } + else { + return getJavaLangObject(psiClass.getManager(), psiClass.getResolveScope()); + } + } + if (CommonClassNames.JAVA_LANG_OBJECT.equals(psiClass.getQualifiedName())) return null; + + PsiClassType[] extendsTypes = psiClass.getExtendsListTypes(); + if (extendsTypes.length != 1) return getJavaLangObject(psiClass.getManager(), psiClass.getResolveScope()); + return extendsTypes[0]; + } + + @NotNull + static JvmReferenceType[] getClassInterfaces(@NotNull PsiClass psiClass) { + if (psiClass instanceof PsiAnonymousClass) { + PsiClassType baseClassType = ((PsiAnonymousClass)psiClass).getBaseClassType(); + PsiClass baseClass = baseClassType.resolve(); + if (baseClass != null && baseClass.isInterface()) { + return new JvmReferenceType[]{baseClassType}; + } + else { + return JvmReferenceType.EMPTY_ARRAY; + } + } + + PsiReferenceList referenceList = psiClass.isInterface() ? psiClass.getExtendsList() : psiClass.getImplementsList(); + if (referenceList == null) return JvmReferenceType.EMPTY_ARRAY; + return referenceList.getReferencedTypes(); + } + + static class PsiJvmSubstitutor implements JvmSubstitutor { + + private final @NotNull PsiSubstitutor mySubstitutor; + + PsiJvmSubstitutor(@NotNull PsiSubstitutor substitutor) { + mySubstitutor = substitutor; + } + + @Nullable + @Override + public JvmType substitute(@NotNull JvmTypeParameter typeParameter) { + if (!(typeParameter instanceof PsiTypeParameter)) return null; + PsiTypeParameter psiTypeParameter = ((PsiTypeParameter)typeParameter); + return mySubstitutor.substitute(psiTypeParameter); + } + } +} diff --git a/java/java-psi-api/src/com/intellij/psi/PsiMember.java b/java/java-psi-api/src/com/intellij/psi/PsiMember.java index c0acdeae428e..0f76df031b0e 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiMember.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiMember.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -15,12 +15,13 @@ */ package com.intellij.psi; +import com.intellij.lang.jvm.JvmMember; import org.jetbrains.annotations.Nullable; /** * Represents a member of a Java class (for example, a field or a method). */ -public interface PsiMember extends PsiModifierListOwner, NavigatablePsiElement { +public interface PsiMember extends PsiModifierListOwner, NavigatablePsiElement, JvmMember { /** * The empty array of PSI members which can be reused to avoid unnecessary allocations. */ diff --git a/java/java-psi-api/src/com/intellij/psi/PsiMethod.java b/java/java-psi-api/src/com/intellij/psi/PsiMethod.java index adcdab5849a0..56ec84bcd84b 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiMethod.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -15,6 +15,9 @@ */ package com.intellij.psi; +import com.intellij.lang.jvm.JvmMethod; +import com.intellij.lang.jvm.JvmParameter; +import com.intellij.lang.jvm.types.JvmReferenceType; import com.intellij.pom.PomRenameableTarget; import com.intellij.psi.util.MethodSignature; import com.intellij.psi.util.MethodSignatureBackedByPsiMethod; @@ -32,7 +35,7 @@ import java.util.List; * @see PsiClass#getMethods() */ public interface PsiMethod extends PsiMember, PsiNameIdentifierOwner, PsiModifierListOwner, PsiDocCommentOwner, PsiTypeParameterListOwner, - PomRenameableTarget, PsiTarget, PsiParameterListOwner { + PomRenameableTarget, PsiTarget, PsiParameterListOwner, JvmMethod { /** * The empty array of PSI methods which can be reused to avoid unnecessary allocations. */ @@ -196,4 +199,16 @@ public interface PsiMethod extends PsiMember, PsiNameIdentifierOwner, PsiModifie @NotNull HierarchicalMethodSignature getHierarchicalMethodSignature(); + + @NotNull + @Override + default JvmParameter[] getParameters() { + return getParameterList().getParameters(); + } + + @NotNull + @Override + default JvmReferenceType[] getThrowsTypes() { + return getThrowsList().getReferencedTypes(); + } } diff --git a/java/java-psi-api/src/com/intellij/psi/PsiModifierListOwner.java b/java/java-psi-api/src/com/intellij/psi/PsiModifierListOwner.java index 6d10d14a8aeb..fa7d7da037e7 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiModifierListOwner.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiModifierListOwner.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -15,15 +15,20 @@ */ package com.intellij.psi; +import com.intellij.lang.jvm.JvmModifier; +import com.intellij.lang.jvm.JvmModifiersOwner; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import static com.intellij.psi.PsiJvmConversionHelper.getListAnnotations; +import static com.intellij.psi.PsiJvmConversionHelper.getListModifiers; + /** * Represents a PSI element which has a list of modifiers (public/private/protected/etc.) * and annotations. */ -public interface PsiModifierListOwner extends PsiElement { +public interface PsiModifierListOwner extends PsiElement, JvmModifiersOwner { /** * Returns the list of modifiers for the element. * @@ -41,4 +46,22 @@ public interface PsiModifierListOwner extends PsiElement { * @return true if the element has the modifier, false otherwise */ boolean hasModifierProperty(@PsiModifier.ModifierConstant @NonNls @NotNull String name); + + @NotNull + @Override + default PsiAnnotation[] getAnnotations() { + return getListAnnotations(this); + } + + @NotNull + @Override + default JvmModifier[] getModifiers() { + return getListModifiers(this); + } + + @NotNull + @Override + default PsiElement getSourceElement() { + return this; + } } diff --git a/java/java-psi-api/src/com/intellij/psi/PsiPackageAccessibilityStatement.java b/java/java-psi-api/src/com/intellij/psi/PsiPackageAccessibilityStatement.java index 0a99af80ecd5..6d2c0a834fff 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiPackageAccessibilityStatement.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiPackageAccessibilityStatement.java @@ -21,7 +21,7 @@ import org.jetbrains.annotations.Nullable; import java.util.List; /** - * Represents a package access control statement ({@code exports} or {@code opens}) of a Java module declaration. + * Represents a package access control directive ({@code exports} or {@code opens}) of a Java module declaration. * * @since 2017.1 */ diff --git a/java/java-psi-api/src/com/intellij/psi/PsiParameter.java b/java/java-psi-api/src/com/intellij/psi/PsiParameter.java index a9c623e1d97f..39b78eece59b 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiParameter.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiParameter.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -15,6 +15,7 @@ */ package com.intellij.psi; +import com.intellij.lang.jvm.JvmParameter; import com.intellij.util.ArrayFactory; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -22,7 +23,7 @@ import org.jetbrains.annotations.Nullable; /** * Represents the parameter of a Java method, foreach (enhanced for) statement or catch block. */ -public interface PsiParameter extends PsiVariable { +public interface PsiParameter extends PsiVariable, JvmParameter { /** * The empty array of PSI parameters which can be reused to avoid unnecessary allocations. */ @@ -52,4 +53,16 @@ public interface PsiParameter extends PsiVariable { @Override @Nullable PsiTypeElement getTypeElement(); + + /* This explicit declaration is required to force javac generate bridge method 'JvmType getType()'; without it calling + JvmParameter#getType() method on instances which weren't recompiled against the new API will cause AbstractMethodError. */ + @NotNull + @Override + PsiType getType(); + + @NotNull + @Override + default PsiAnnotation[] getAnnotations() { + return PsiVariable.super.getAnnotations(); + } } diff --git a/java/java-psi-api/src/com/intellij/psi/PsiPrimitiveType.java b/java/java-psi-api/src/com/intellij/psi/PsiPrimitiveType.java index d450a5c53b39..54ed4fd1e4a0 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiPrimitiveType.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiPrimitiveType.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -15,6 +15,7 @@ */ package com.intellij.psi; +import com.intellij.lang.jvm.types.JvmPrimitiveType; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiUtil; @@ -29,7 +30,8 @@ import java.util.Map; /** * Represents primitive types of Java language. */ -public class PsiPrimitiveType extends PsiType.Stub { +public class PsiPrimitiveType extends PsiType.Stub implements JvmPrimitiveType { + private static final Map ourQNameToUnboxed = new THashMap<>(); private static final Map ourUnboxedToQName = new THashMap<>(); diff --git a/java/java-psi-api/src/com/intellij/psi/PsiProvidesStatement.java b/java/java-psi-api/src/com/intellij/psi/PsiProvidesStatement.java index b11cf9c4b22d..aac8753affeb 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiProvidesStatement.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiProvidesStatement.java @@ -18,7 +18,7 @@ package com.intellij.psi; import org.jetbrains.annotations.Nullable; /** - * Represents a {@code provides} statement of a Java module declaration. + * Represents a {@code provides} directive of a Java module declaration. * * @since 2016.3 */ diff --git a/java/java-psi-api/src/com/intellij/psi/PsiRequiresStatement.java b/java/java-psi-api/src/com/intellij/psi/PsiRequiresStatement.java index 70f8ca1910c6..804c808fef4b 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiRequiresStatement.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiRequiresStatement.java @@ -18,7 +18,7 @@ package com.intellij.psi; import org.jetbrains.annotations.Nullable; /** - * Represents a {@code requires} statement of a Java module declaration. + * Represents a {@code requires} directive of a Java module declaration. * * @since 2016.3 */ diff --git a/java/java-psi-api/src/com/intellij/psi/PsiType.java b/java/java-psi-api/src/com/intellij/psi/PsiType.java index ad35e2086d74..339bfcd8894a 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiType.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiType.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -15,6 +15,7 @@ */ package com.intellij.psi; +import com.intellij.lang.jvm.types.JvmType; import com.intellij.openapi.project.Project; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.TypeConversionUtil; @@ -25,7 +26,7 @@ import org.jetbrains.annotations.Nullable; /** * Representation of Java type (primitive type, array or class type). */ -public abstract class PsiType implements PsiAnnotationOwner, Cloneable { +public abstract class PsiType implements PsiAnnotationOwner, Cloneable, JvmType { @SuppressWarnings("StaticInitializerReferencesSubClass") public static final PsiPrimitiveType BYTE = new PsiPrimitiveType("byte", "java.lang.Byte"); @SuppressWarnings("StaticInitializerReferencesSubClass") public static final PsiPrimitiveType CHAR = new PsiPrimitiveType("char", "java.lang.Character"); @SuppressWarnings("StaticInitializerReferencesSubClass") public static final PsiPrimitiveType DOUBLE = new PsiPrimitiveType("double", "java.lang.Double"); diff --git a/java/java-psi-api/src/com/intellij/psi/PsiTypeParameter.java b/java/java-psi-api/src/com/intellij/psi/PsiTypeParameter.java index f89b4184757b..fbfde1663722 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiTypeParameter.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiTypeParameter.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -15,6 +15,8 @@ */ package com.intellij.psi; +import com.intellij.lang.jvm.JvmTypeParameter; +import com.intellij.lang.jvm.types.JvmReferenceType; import com.intellij.util.ArrayFactory; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -24,7 +26,7 @@ import org.jetbrains.annotations.Nullable; * * @author dsl */ -public interface PsiTypeParameter extends PsiClass, PsiAnnotationOwner { +public interface PsiTypeParameter extends PsiClass, PsiAnnotationOwner, JvmTypeParameter { /** * The empty array of PSI type parameters which can be reused to avoid unnecessary allocations. */ @@ -47,6 +49,7 @@ public interface PsiTypeParameter extends PsiClass, PsiAnnotationOwner { * @return the type parameter owner instance. */ @Nullable + @Override PsiTypeParameterListOwner getOwner(); /** @@ -55,4 +58,16 @@ public interface PsiTypeParameter extends PsiClass, PsiAnnotationOwner { * @return the type parameter position. */ int getIndex(); + + @NotNull + @Override + default PsiAnnotation[] getAnnotations() { + return PsiClass.super.getAnnotations(); + } + + @NotNull + @Override + default JvmReferenceType[] getBounds() { + return getExtendsList().getReferencedTypes(); + } } diff --git a/java/java-psi-api/src/com/intellij/psi/PsiTypeParameterListOwner.java b/java/java-psi-api/src/com/intellij/psi/PsiTypeParameterListOwner.java index aaa154f7bf5e..256ac118f3fc 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiTypeParameterListOwner.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiTypeParameterListOwner.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -15,6 +15,7 @@ */ package com.intellij.psi; +import com.intellij.lang.jvm.JvmTypeParametersOwner; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -24,7 +25,7 @@ import org.jetbrains.annotations.Nullable; * * @author dsl */ -public interface PsiTypeParameterListOwner extends PsiMember { +public interface PsiTypeParameterListOwner extends PsiMember, JvmTypeParametersOwner { /** * Checks if the element has any type parameters. * @@ -46,5 +47,6 @@ public interface PsiTypeParameterListOwner extends PsiMember { * @return the array of type parameters, or an empty array if the element has no type parameters. */ @NotNull + @Override PsiTypeParameter[] getTypeParameters(); } diff --git a/java/java-psi-api/src/com/intellij/psi/PsiUsesStatement.java b/java/java-psi-api/src/com/intellij/psi/PsiUsesStatement.java index b8e38657ab4c..8e2567a70dc4 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiUsesStatement.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiUsesStatement.java @@ -18,7 +18,7 @@ package com.intellij.psi; import org.jetbrains.annotations.Nullable; /** - * Represents a {@code uses} statement of a Java module declaration. + * Represents a {@code uses} directive of a Java module declaration. * * @since 2016.3 */ diff --git a/java/java-psi-api/src/com/intellij/psi/PsiWildcardType.java b/java/java-psi-api/src/com/intellij/psi/PsiWildcardType.java index 94112d2edc17..74c5b8ac7caf 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiWildcardType.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiWildcardType.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -15,6 +15,8 @@ */ package com.intellij.psi; +import com.intellij.lang.jvm.types.JvmType; +import com.intellij.lang.jvm.types.JvmWildcardType; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Key; @@ -27,7 +29,7 @@ import org.jetbrains.annotations.Nullable; * * @author dsl */ -public class PsiWildcardType extends PsiType.Stub { +public class PsiWildcardType extends PsiType.Stub implements JvmWildcardType { public static final String EXTENDS_PREFIX = "? extends "; public static final String SUPER_PREFIX = "? super "; @@ -75,7 +77,9 @@ public class PsiWildcardType extends PsiType.Stub { return new PsiWildcardType(manager, false, bound); } - /** @deprecated use {@link #annotate(TypeAnnotationProvider)} (to be removed in IDEA 18) */ + /** + * @deprecated use {@link #annotate(TypeAnnotationProvider)} (to be removed in IDEA 18) + */ public PsiWildcardType annotate(@NotNull final PsiAnnotation[] annotations) { return annotations.length == 0 ? this : new PsiWildcardType(this, TypeAnnotationProvider.Static.create(annotations)); } @@ -193,7 +197,7 @@ public class PsiWildcardType extends PsiType.Stub { * Returns whether this is a lower bound ({@code ? extends XXX}). * * @return {@code true} for {@code extends} wildcards, {@code false} for {@code super} - * and unbounded wildcards. + * and unbounded wildcards. */ public boolean isExtends() { return myBound != null && myIsExtending; @@ -203,7 +207,7 @@ public class PsiWildcardType extends PsiType.Stub { * Returns whether this is an upper bound ({@code ? super XXX}). * * @return {@code true} for {@code super} wildcards, {@code false} for {@code extends} - * and unbounded wildcards. + * and unbounded wildcards. */ public boolean isSuper() { return myBound != null && !myIsExtending; @@ -250,4 +254,16 @@ public class PsiWildcardType extends PsiType.Stub { public PsiType getSuperBound() { return myBound == null || myIsExtending ? NULL : myBound; } + + @NotNull + @Override + public JvmType upperBound() { + return getExtendsBound(); + } + + @NotNull + @Override + public JvmType lowerBound() { + return getSuperBound(); + } } \ No newline at end of file diff --git a/java/java-psi-api/src/com/intellij/psi/util/PsiClassUtil.java b/java/java-psi-api/src/com/intellij/psi/util/PsiClassUtil.java index a9979577a155..82c700871bab 100644 --- a/java/java-psi-api/src/com/intellij/psi/util/PsiClassUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/util/PsiClassUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. diff --git a/java/java-psi-impl/src/com/intellij/lang/jvm/facade/JvmFacadeImpl.java b/java/java-psi-impl/src/com/intellij/lang/jvm/facade/JvmFacadeImpl.java new file mode 100644 index 000000000000..e719daa9330d --- /dev/null +++ b/java/java-psi-impl/src/com/intellij/lang/jvm/facade/JvmFacadeImpl.java @@ -0,0 +1,123 @@ +/* + * Copyright 2000-2017 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.lang.jvm.facade; + +import com.intellij.lang.jvm.JvmClass; +import com.intellij.lang.jvm.util.JvmClassUtil; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.project.Project; +import com.intellij.psi.JavaPsiFacade; +import com.intellij.psi.PsiClass; +import com.intellij.psi.impl.JavaPsiFacadeImpl; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.util.PsiModificationTracker; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.messages.MessageBus; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.intellij.util.containers.ContainerUtil.createConcurrentWeakKeySoftValueMap; + +public class JvmFacadeImpl implements JvmFacade { + + private static final Logger LOG = Logger.getInstance(JvmFacadeImpl.class); + + private final DumbService myDumbService; + private final JavaPsiFacadeImpl myJavaPsiFacade; + private final JvmElementProvider[] myElementProviders; + private final Map>> myClassCache = createConcurrentWeakKeySoftValueMap(); + + public JvmFacadeImpl(@NotNull Project project, MessageBus bus) { + myDumbService = DumbService.getInstance(project); + myJavaPsiFacade = (JavaPsiFacadeImpl)JavaPsiFacade.getInstance(project); + myElementProviders = JvmElementProvider.EP_NAME.getExtensions(project); + if (bus != null) { + bus.connect().subscribe(PsiModificationTracker.TOPIC, () -> myClassCache.clear()); + } + } + + @Override + @NotNull + public List findClasses(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { + Map> map = myClassCache.computeIfAbsent(scope, s -> ContainerUtil.createConcurrentWeakValueMap()); + return map.computeIfAbsent(qualifiedName, fqn -> doFindClassesWithJavaFacade(fqn, scope)); + } + + private List doFindClassesWithJavaFacade(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { + return sortByScope(findClassesWithJavaFacade(qualifiedName, scope), scope); + } + + private List findClassesWithJavaFacade(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { + List result = null; + + final List ownClasses = findClassesWithoutJavaFacade(qualifiedName, scope); + if (!ownClasses.isEmpty()) { + result = new ArrayList<>(ownClasses); + } + + final List javaClasses = myJavaPsiFacade.findClassesWithoutJvmFacade(qualifiedName, scope); + if (!javaClasses.isEmpty()) { + if (result == null) { + result = new ArrayList<>(javaClasses); + } + else { + result.addAll(javaClasses); + } + } + + return result == null ? Collections.emptyList() : result; + } + + @NotNull + public List findClassesWithoutJavaFacade(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { + List result = null; + for (JvmElementProvider provider : filteredProviders()) { + List providedClasses = provider.getClasses(qualifiedName, scope); + if (providedClasses.isEmpty()) continue; + assertNotNullClasses(provider, providedClasses); + if (result == null) { + result = new ArrayList<>(providedClasses); + } + else { + result.addAll(providedClasses); + } + } + return result == null ? Collections.emptyList() : result; + } + + @NotNull + private static List sortByScope(@NotNull List classes, @NotNull GlobalSearchScope scope) { + if (classes.size() == 1) return classes; + classes.sort(JvmClassUtil.createScopeComparator(scope)); + return classes; + } + + private static void assertNotNullClasses(@NotNull JvmElementProvider provider, @NotNull List classes) { + for (JvmClass jvmClass : classes) { + LOG.assertTrue(jvmClass != null, "Provider " + provider + "returned null JvmClass"); + } + } + + @NotNull + private List filteredProviders() { + return myDumbService.filterByDumbAwareness(myElementProviders); + } +} diff --git a/java/java-psi-impl/src/com/intellij/lang/jvm/util/JvmClassUtil.java b/java/java-psi-impl/src/com/intellij/lang/jvm/util/JvmClassUtil.java new file mode 100644 index 000000000000..cb563a82e3b2 --- /dev/null +++ b/java/java-psi-impl/src/com/intellij/lang/jvm/util/JvmClassUtil.java @@ -0,0 +1,40 @@ +/* + * Copyright 2000-2017 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.lang.jvm.util; + +import com.intellij.lang.jvm.JvmClass; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.util.PsiUtilCore; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.Comparator; + +public class JvmClassUtil { + + @Contract(pure = true) + @NotNull + public static Comparator createScopeComparator(@NotNull GlobalSearchScope scope) { + return (c1, c2) -> { + VirtualFile file1 = PsiUtilCore.getVirtualFile(c1.getSourceElement()); + VirtualFile file2 = PsiUtilCore.getVirtualFile(c2.getSourceElement()); + if (file1 == null) return file2 == null ? 0 : -1; + if (file2 == null) return 1; + return scope.compare(file2, file1); + }; + } +} diff --git a/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowUtil.java b/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowUtil.java index 7d757cbfe931..576f231b4a1d 100644 --- a/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowUtil.java +++ b/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -1056,35 +1056,9 @@ public class ControlFlowUtil { if (nextOffset > flow.getSize()) nextOffset = flow.getSize(); if (offset > endOffset) return; int throwToOffset = instruction.offset; - boolean isNormal = false; + boolean isNormal; if (throwToOffset == nextOffset) { - - if (nextOffset == endOffset) { - int lastOffset = endOffset - 1; - Instruction lastInstruction = flow.getInstructions().get(lastOffset); - while (lastInstruction instanceof GoToInstruction && - ((GoToInstruction)lastInstruction).role == BranchingInstruction.Role.END && - !((GoToInstruction)lastInstruction).isReturn) { - if (((GoToInstruction)lastInstruction).offset == startOffset) { - lastOffset = -1; - break; - } - else { - lastOffset--; - if (lastOffset < 0) { - break; - } - lastInstruction = flow.getInstructions().get(lastOffset); - } - } - - if (lastOffset >= 0) { - isNormal = !(lastInstruction instanceof GoToInstruction && ((GoToInstruction)lastInstruction).isReturn) && - !(lastInstruction instanceof ThrowToInstruction); - } - } - - isNormal |= throwToOffset <= endOffset && !isLeaf(nextOffset) && canCompleteNormally[nextOffset]; + isNormal = throwToOffset <= endOffset && !isLeaf(nextOffset) && canCompleteNormally[nextOffset]; } else { isNormal = canCompleteNormally[nextOffset]; diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/JavaPsiFacadeImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/JavaPsiFacadeImpl.java index 2883a0736bd7..04ea0f05b58a 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/JavaPsiFacadeImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/JavaPsiFacadeImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -15,6 +15,9 @@ */ package com.intellij.psi.impl; +import com.intellij.lang.jvm.JvmClass; +import com.intellij.lang.jvm.facade.JvmFacade; +import com.intellij.lang.jvm.facade.JvmFacadeImpl; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.ExtensionPoint; import com.intellij.openapi.extensions.Extensions; @@ -22,12 +25,14 @@ import com.intellij.openapi.extensions.SimpleSmartExtensionPoint; import com.intellij.openapi.progress.ProgressIndicatorProvider; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.AtomicNotNullLazyValue; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Conditions; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.impl.file.impl.JavaFileManager; +import com.intellij.psi.impl.jvm2psi.JvmPsiConversionHelper; import com.intellij.psi.impl.source.DummyHolderFactory; import com.intellij.psi.impl.source.JavaDummyHolder; import com.intellij.psi.impl.source.JavaDummyHolderFactory; @@ -57,6 +62,8 @@ public class JavaPsiFacadeImpl extends JavaPsiFacadeEx { private final ConcurrentMap> myClassCache = ContainerUtil.createConcurrentWeakKeySoftValueMap(); private final Project myProject; private final JavaFileManager myFileManager; + private final AtomicNotNullLazyValue myJvmFacade; + private final JvmPsiConversionHelper myConversionHelper; public JavaPsiFacadeImpl(Project project, PsiManager psiManager, @@ -65,6 +72,8 @@ public class JavaPsiFacadeImpl extends JavaPsiFacadeEx { myProject = project; myFileManager = javaFileManager; myConstantEvaluationHelper = new PsiConstantEvaluationHelperImpl(); + myJvmFacade = AtomicNotNullLazyValue.createValue(() -> (JvmFacadeImpl)JvmFacade.getInstance(project)); + myConversionHelper = new JvmPsiConversionHelper(psiManager); final PsiModificationTracker modificationTracker = psiManager.getModificationTracker(); @@ -161,10 +170,38 @@ public class JavaPsiFacadeImpl extends JavaPsiFacadeEx { @Override @NotNull public PsiClass[] findClasses(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { - if (shouldUseSlowResolve()) { - return findClassesInDumbMode(qualifiedName, scope); + List allClasses = findClassesWithJvmFacade(qualifiedName, scope); + return allClasses.isEmpty() ? PsiClass.EMPTY_ARRAY : allClasses.toArray(PsiClass.EMPTY_ARRAY); + } + + @NotNull + private List findClassesWithJvmFacade(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { + List result = null; + + final List ownClasses = findClassesWithoutJvmFacade(qualifiedName, scope); + if (!ownClasses.isEmpty()) { + result = new ArrayList<>(ownClasses); } + final List jvmClasses = myJvmFacade.getValue().findClassesWithoutJavaFacade(qualifiedName, scope); + if (!jvmClasses.isEmpty()) { + final List jvmPsiClasses = ContainerUtil.map(jvmClasses, it -> myConversionHelper.toPsiClass(it)); + if (result == null) { + result = new ArrayList<>(jvmPsiClasses); + } + else { + result.addAll(jvmPsiClasses); + } + } + + return result == null ? Collections.emptyList() : result; + } + + @NotNull + public List findClassesWithoutJvmFacade(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { + if (shouldUseSlowResolve()) { + return Arrays.asList(findClassesInDumbMode(qualifiedName, scope)); + } List finders = finders(); Condition classesFilter = getFilterFromFinders(scope, finders); @@ -177,7 +214,7 @@ public class JavaPsiFacadeImpl extends JavaPsiFacadeEx { } } - return result == null || result.isEmpty() ? PsiClass.EMPTY_ARRAY : result.toArray(new PsiClass[result.size()]); + return result == null ? Collections.emptyList() : result; } private static Condition getFilterFromFinders(@NotNull GlobalSearchScope scope, @NotNull List finders) { diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/jvm2psi/JvmPsiConversionHelper.java b/java/java-psi-impl/src/com/intellij/psi/impl/jvm2psi/JvmPsiConversionHelper.java new file mode 100644 index 000000000000..603386beac98 --- /dev/null +++ b/java/java-psi-impl/src/com/intellij/psi/impl/jvm2psi/JvmPsiConversionHelper.java @@ -0,0 +1,44 @@ +/* + * Copyright 2000-2017 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.psi.impl.jvm2psi; + +import com.intellij.lang.jvm.JvmClass; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiManager; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class JvmPsiConversionHelper { + + private final @NotNull PsiManager myManager; + + public JvmPsiConversionHelper(@NotNull PsiManager manager) { + myManager = manager; + } + + @NotNull + public PsiManager getManager() { + return myManager; + } + + @Contract("null -> null; !null -> !null") + @Nullable + public PsiClass toPsiClass(@Nullable JvmClass jvmClass) { + // TODO + throw new RuntimeException("TODO"); + } +} diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/JavaLightStubBuilder.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/JavaLightStubBuilder.java index 3470ad3a1ca8..1b5602406110 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/JavaLightStubBuilder.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/JavaLightStubBuilder.java @@ -102,8 +102,8 @@ public class JavaLightStubBuilder extends LightStubBuilder { private static class CodeBlockVisitor extends RecursiveTreeElementWalkingVisitor implements LighterLazyParseableNode.Visitor { private static final TokenSet BLOCK_ELEMENTS = TokenSet.create( - JavaElementType.ANNOTATION, JavaElementType.CLASS, JavaElementType.ANONYMOUS_CLASS, - JavaElementType.LAMBDA_EXPRESSION, JavaElementType.METHOD_REF_EXPRESSION); + JavaElementType.CLASS, JavaElementType.ANONYMOUS_CLASS, + JavaTokenType.ARROW, JavaTokenType.DOUBLE_COLON, JavaTokenType.AT); private boolean result = true; @@ -117,6 +117,7 @@ public class JavaLightStubBuilder extends LightStubBuilder { super.visitNode(element); } + private IElementType preLast; private IElementType last; private boolean seenNew; @@ -142,10 +143,13 @@ public class JavaLightStubBuilder extends LightStubBuilder { return (result = false); } // local classes - else if (type == JavaTokenType.CLASS_KEYWORD && last != JavaTokenType.DOT) { + else if (type == JavaTokenType.CLASS_KEYWORD && (last != JavaTokenType.DOT || preLast != JavaTokenType.IDENTIFIER) + || type == JavaTokenType.ENUM_KEYWORD + || type == JavaTokenType.INTERFACE_KEYWORD) { return (result = false); } + preLast = last; last = type; return true; } diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/PsiJavaCodeReferenceElementImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/PsiJavaCodeReferenceElementImpl.java index bc3c24d1d33d..4b73582e1992 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/PsiJavaCodeReferenceElementImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/PsiJavaCodeReferenceElementImpl.java @@ -47,6 +47,7 @@ import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.IncorrectOperationException; +import com.intellij.util.ProcessingContext; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -54,6 +55,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import static com.intellij.patterns.PsiJavaPatterns.psiElement; + public class PsiJavaCodeReferenceElementImpl extends CompositePsiElement implements PsiAnnotatedJavaCodeReferenceElement, SourceJavaCodeReference { private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.PsiJavaCodeReferenceElementImpl"); @@ -218,7 +221,11 @@ public class PsiJavaCodeReferenceElementImpl extends CompositePsiElement impleme else { getParent().addRangeBefore(getFirstChild(), lastChild, this); } - deleteChildRange(getFirstChild(), lastChild); + // during previous operations, formatter support could have altered the children (if they're whitespace), + // so we retrieve and check them again + if (ref != getFirstChild()) { + deleteChildRange(getFirstChild(), ref.getPsi().getPrevSibling()); + } } } else if (child.getElementType() == JavaElementType.REFERENCE_PARAMETER_LIST) { @@ -859,9 +866,34 @@ public class PsiJavaCodeReferenceElementImpl extends CompositePsiElement impleme OrFilter filter = new OrFilter(filters.toArray(ElementFilter.EMPTY_ARRAY)); FilterScopeProcessor proc = new FilterScopeProcessor(filter, processor); + + for (PsiTypeParameter typeParameter : getUnfinishedMethodTypeParameters()) { + if (!proc.execute(typeParameter, ResolveState.initial())) { + return; + } + } + PsiScopesUtil.resolveAndWalk(proc, this, null, true); } + private PsiTypeParameter[] getUnfinishedMethodTypeParameters() { + ProcessingContext context = new ProcessingContext(); + if (psiElement().inside( + psiElement(PsiTypeElement.class).afterLeaf( + psiElement().withText(">").withParent( + psiElement(PsiTypeParameterList.class).withParent(PsiErrorElement.class).save("typeParameterList")))).accepts(this, context)) { + PsiTypeParameterList list = (PsiTypeParameterList)context.get("typeParameterList"); + PsiElement current = list.getParent().getParent(); + if (current instanceof PsiField) { + current = current.getParent(); + } + if (current instanceof PsiClass) { + return list.getTypeParameters(); + } + } + return PsiTypeParameter.EMPTY_ARRAY; + } + private boolean isInCode() { if (isCodeFragmentType(getTreeParent().getElementType()) || getParent() instanceof PsiAnnotation) { return false; diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/JavaElementType.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/JavaElementType.java index f1bc9bcaf277..48537ada61fd 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/JavaElementType.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/JavaElementType.java @@ -180,24 +180,7 @@ public interface JavaElementType { @Override public int getErrorsCount(final CharSequence seq, Language fileLanguage, final Project project) { Lexer lexer = JavaParserDefinition.createLexer(LanguageLevel.HIGHEST); - - lexer.start(seq); - if (lexer.getTokenType() != JavaTokenType.LBRACE) return IErrorCounterReparseableElementType.FATAL_ERROR; - lexer.advance(); - int balance = 1; - while (true) { - IElementType type = lexer.getTokenType(); - if (type == null) break; - if (balance == 0) return IErrorCounterReparseableElementType.FATAL_ERROR; - if (type == JavaTokenType.LBRACE) { - balance++; - } - else if (type == JavaTokenType.RBRACE) { - balance--; - } - lexer.advance(); - } - return balance; + return PsiBuilderUtil.hasProperBraceBalance(seq, lexer, JavaTokenType.LBRACE, JavaTokenType.RBRACE) ? NO_ERRORS : FATAL_ERROR; } } ILazyParseableElementType CODE_BLOCK = new ICodeBlockElementType(); diff --git a/java/java-psi-impl/src/messages/JavaErrorMessages.properties b/java/java-psi-impl/src/messages/JavaErrorMessages.properties index c38621cde6b5..4e17654c03f6 100644 --- a/java/java-psi-impl/src/messages/JavaErrorMessages.properties +++ b/java/java-psi-impl/src/messages/JavaErrorMessages.properties @@ -373,7 +373,7 @@ expected.catch.or.finally='catch' or 'finally' expected expected.boolean.expression=Boolean expression expected expected.with='with' expected expected.module.declaration=Module declaration expected -expected.module.statement=Module statement expected +expected.module.statement=Module directive expected error.cannot.resolve.class=Cannot resolve class ''{0}'' error.cannot.resolve.class.or.package=Cannot resolve class or package ''{0}'' diff --git a/java/java-tests/java-tests.iml b/java/java-tests/java-tests.iml index acb59b97af89..47ccdb4f49db 100644 --- a/java/java-tests/java-tests.iml +++ b/java/java-tests/java-tests.iml @@ -57,6 +57,5 @@ - \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/keywords/noClassKeywordsInFieldArrayInitializer.java b/java/java-tests/testData/codeInsight/completion/keywords/noClassKeywordsInFieldArrayInitializer.java new file mode 100644 index 000000000000..ff09cf01aaee --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/keywords/noClassKeywordsInFieldArrayInitializer.java @@ -0,0 +1,8 @@ +class MyTest { + + String[] a = new String[]{ + "a", + "b"}; + +} + diff --git a/java/java-tests/testData/codeInsight/completion/keywords/noClassKeywordsInLocalArrayInitializer.java b/java/java-tests/testData/codeInsight/completion/keywords/noClassKeywordsInLocalArrayInitializer.java new file mode 100644 index 000000000000..dd8f9c14b8f5 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/keywords/noClassKeywordsInLocalArrayInitializer.java @@ -0,0 +1,9 @@ +class MyTest { + void foo() { + String[] a = new String[]{ + "a", + "b"}; + } + +} + diff --git a/java/java-tests/testData/codeInsight/completion/normal/ShadowedTypeParameter.java b/java/java-tests/testData/codeInsight/completion/normal/ShadowedTypeParameter.java new file mode 100644 index 000000000000..77feb9e92c42 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/normal/ShadowedTypeParameter.java @@ -0,0 +1,5 @@ +class Foo { + MyParam foo(MyParam p) { + return p; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/normal/TypeArgumentInCast.java b/java/java-tests/testData/codeInsight/completion/normal/TypeArgumentInCast.java new file mode 100644 index 000000000000..a69579bd2797 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/normal/TypeArgumentInCast.java @@ -0,0 +1,5 @@ +class Z { + { + Object o = ((Mapxing>) o1).get(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/smartType/StaticBuilderWithInterfaceAndGenerics-out.java b/java/java-tests/testData/codeInsight/completion/smartType/StaticBuilderWithInterfaceAndGenerics-out.java new file mode 100644 index 000000000000..a7fa402b315d --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/smartType/StaticBuilderWithInterfaceAndGenerics-out.java @@ -0,0 +1,23 @@ +public class S { + + { + Map m = Map.build().get(); + } + +} + +class Map { + static MapBuilder build() {} + + interface BaseBuilder { + T get(); + } + + interface MapBuilder extends BaseBuilder { + + } + + static class Builder implements MapBuilder { + Map get() {} + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/smartType/StaticBuilderWithInterfaceAndGenerics.java b/java/java-tests/testData/codeInsight/completion/smartType/StaticBuilderWithInterfaceAndGenerics.java new file mode 100644 index 000000000000..4d08bcf4b1b6 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/smartType/StaticBuilderWithInterfaceAndGenerics.java @@ -0,0 +1,23 @@ +public class S { + + { + Map m = bui + } + +} + +class Map { + static MapBuilder build() {} + + interface BaseBuilder { + T get(); + } + + interface MapBuilder extends BaseBuilder { + + } + + static class Builder implements MapBuilder { + Map get() {} + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addOnDemandStaticImport/beforeIncompleteReference.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addOnDemandStaticImport/beforeIncompleteReference.java new file mode 100644 index 000000000000..1ba8b800e195 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addOnDemandStaticImport/beforeIncompleteReference.java @@ -0,0 +1,6 @@ +// "Add on demand static import for 'test.Foo'" "false" +class Foo { + { + final String. s = toString(); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertSwitchToIf/after5.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertSwitchToIf/after5.java index 65cd31bfe4e4..7b2a4c2d62a3 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertSwitchToIf/after5.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertSwitchToIf/after5.java @@ -4,8 +4,8 @@ abstract class Test { void foo() { Class i = getObject().getClass(); - if (i.equals(RuntimeException.class)) { - } else if (i.equals(IOException.class)) { + if (RuntimeException.class.equals(i)) { + } else if (IOException.class.equals(i)) { } } } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertSwitchToIf/afterNotCompletesNormally.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertSwitchToIf/afterNotCompletesNormally.java new file mode 100644 index 000000000000..9c2e48605c53 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertSwitchToIf/afterNotCompletesNormally.java @@ -0,0 +1,14 @@ +// "Replace 'switch' with 'if'" "true" +class X { + + void m(String s, int a) throws IOException { + if ("a".equals(s)) { + a(); + } else { + d(); + } + } + + void a() throws IOException { } + void d() throws IOException { } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertSwitchToIf/afterNotCompletesNormally2.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertSwitchToIf/afterNotCompletesNormally2.java new file mode 100644 index 000000000000..0e80684a1153 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertSwitchToIf/afterNotCompletesNormally2.java @@ -0,0 +1,17 @@ +// "Replace 'switch' with 'if'" "true" +class X { + void test4() throws IOException { + String variable = "abc"; + if ("abc".equals(variable)) { + String s1 = "abcd"; + } else if ("def".equals(variable)) { + String s1 = "abcd"; + myFunction(s1); + } else { + throw new IllegalArgumentException(); + } + } + + public void myFunction(Object o1) throws IOException { + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertSwitchToIf/afterString.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertSwitchToIf/afterString.java new file mode 100644 index 000000000000..7515ac710698 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertSwitchToIf/afterString.java @@ -0,0 +1,9 @@ +// "Replace 'switch' with 'if'" "true" +class X { + public void doSomething( String value) { + if ("case1".equals(value)) { + } else if ("case2".equals(value)) { + } else { + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertSwitchToIf/beforeNotCompletesNormally.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertSwitchToIf/beforeNotCompletesNormally.java new file mode 100644 index 000000000000..c660a18be354 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertSwitchToIf/beforeNotCompletesNormally.java @@ -0,0 +1,18 @@ +// "Replace 'switch' with 'if'" "true" +class X { + + void m(String s, int a) throws IOException { + switch (s) { + case "a": { + a(); + break; + } + default: { + d(); + } + } + } + + void a() throws IOException { } + void d() throws IOException { } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertSwitchToIf/beforeNotCompletesNormally2.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertSwitchToIf/beforeNotCompletesNormally2.java new file mode 100644 index 000000000000..c6553e746193 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertSwitchToIf/beforeNotCompletesNormally2.java @@ -0,0 +1,22 @@ +// "Replace 'switch' with 'if'" "true" +class X { + void test4() throws IOException { + String variable = "abc"; + switch (variable) { + case "abc": { + String s1 = "abcd"; + break; + } + case "def": { + String s1 = "abcd"; + myFunction(s1); + break; + } + default: + throw new IllegalArgumentException(); + } + } + + public void myFunction(Object o1) throws IOException { + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertSwitchToIf/beforeString.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertSwitchToIf/beforeString.java new file mode 100644 index 000000000000..392f563f93a9 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/convertSwitchToIf/beforeString.java @@ -0,0 +1,13 @@ +// "Replace 'switch' with 'if'" "true" +class X { + public void doSomething( String value) { + switch ( value ) { + case "case1": + break; + case "case2": + break; + default: + break; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/after7.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/after7.java index 7ff37832e410..943fa1881ea8 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/after7.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/after7.java @@ -5,7 +5,6 @@ class C { if (s instanceof String) { String s1 = (String) s; - } } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/afterBrokenCode.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/afterBrokenCode.java new file mode 100644 index 000000000000..70ed92c84641 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/afterBrokenCode.java @@ -0,0 +1,10 @@ +// "Insert '(String)o' declaration" "true" +class X { + void foo(Object o) { + if (o instanceof String) { + String o1 = (String) o; + + String substring = o.(); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/afterFormatting.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/afterFormatting.java index b6b03de16ebb..316614ce5f2e 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/afterFormatting.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/afterFormatting.java @@ -5,8 +5,7 @@ class C { void f(Object o) { if (o instanceof IOException) { IOException o1 = (IOException) o; - - + } } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/afterInsertInEmptyLine.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/afterInsertInEmptyLine.java new file mode 100644 index 000000000000..01151e51479f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/afterInsertInEmptyLine.java @@ -0,0 +1,13 @@ +// "Insert '(String)o' declaration" "true" +class X { + void foo(Object o) { + if (o instanceof String) { + String a; + String b; + String o1 = (String) o; + + + String c; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/afterIntersection.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/afterIntersection.java index 41b78666ddec..8a125441c98c 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/afterIntersection.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/afterIntersection.java @@ -4,7 +4,6 @@ class C { if (o instanceof String && f == null) { String o1 = (String) o; - } } } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/afterLineEndComment.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/afterLineEndComment.java index d768f17e4e56..913734c870ff 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/afterLineEndComment.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/afterLineEndComment.java @@ -2,7 +2,6 @@ class C { void f(Object o, Object f) { if (o instanceof String) {//todo comment - String o1 = (String) o; } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/beforeBrokenCode.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/beforeBrokenCode.java new file mode 100644 index 000000000000..869ac81e5f2d --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/beforeBrokenCode.java @@ -0,0 +1,8 @@ +// "Insert '(String)o' declaration" "true" +class X { + void foo(Object o) { + if (o instanceof String) { + String substring = o.(); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/beforeInsertInEmptyLine.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/beforeInsertInEmptyLine.java new file mode 100644 index 000000000000..a02e33ac1717 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createLocalVarFromInstanceof/beforeInsertInEmptyLine.java @@ -0,0 +1,12 @@ +// "Insert '(String)o' declaration" "true" +class X { + void foo(Object o) { + if (o instanceof String) { + String a; + String b; + + + String c; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/insertNew/beforeInvalidCode.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/insertNew/beforeInvalidCode.java new file mode 100644 index 000000000000..a29a07cf2276 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/insertNew/beforeInvalidCode.java @@ -0,0 +1,9 @@ +// "Insert new" "false" +class a { + void f() { + if (RuntimeException() { + + } + } +} + diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/optionalIsPresent/afterConsumerField.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/optionalIsPresent/afterConsumerField.java index 57bb5a8769a4..3c9c84aabbe2 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/optionalIsPresent/afterConsumerField.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/optionalIsPresent/afterConsumerField.java @@ -1,4 +1,4 @@ -// "Replace Optional.isPresent() condition with functional style expression" "true" +// "Replace Optional.isPresent() condition with functional style expression" "GENERIC_ERROR_OR_WARNING" import java.util.*; @@ -6,6 +6,6 @@ public class Main { String myString; public void testOptional(Optional str) { - str.ifPresent(s -> myString = s); + str.ifPresent(s -> myString = s.isEmpty() ? null : s); } } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/optionalIsPresent/beforeConsumerField.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/optionalIsPresent/beforeConsumerField.java index cecea1887d42..da123fca7c58 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/optionalIsPresent/beforeConsumerField.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/optionalIsPresent/beforeConsumerField.java @@ -1,4 +1,4 @@ -// "Replace Optional.isPresent() condition with functional style expression" "true" +// "Replace Optional.isPresent() condition with functional style expression" "GENERIC_ERROR_OR_WARNING" import java.util.*; @@ -7,7 +7,7 @@ public class Main { public void testOptional(Optional str) { if (str.isPresent()) { - myString = str.get(); + myString = str.get().isEmpty() ? null : str.get(); } } } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceWithTernaryOperator/afterTernary.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceWithTernaryOperator/afterTernary.java new file mode 100644 index 000000000000..e2f7e3e2b49a --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceWithTernaryOperator/afterTernary.java @@ -0,0 +1,8 @@ +// "Replace with '(b ? null : "foo") != null ?:'" "true" +class A { + void bar(String s) {} + + void foo(boolean b){ + bar((b ? null : "foo") != null ? b ? null : "foo" : null); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceWithTernaryOperator/beforeTernary.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceWithTernaryOperator/beforeTernary.java new file mode 100644 index 000000000000..413b3d1b3f95 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceWithTernaryOperator/beforeTernary.java @@ -0,0 +1,8 @@ +// "Replace with '(b ? null : "foo") != null ?:'" "true" +class A { + void bar(String s) {} + + void foo(boolean b){ + bar(b ? null : "foo"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithIf/afterTernary.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithIf/afterTernary.java new file mode 100644 index 000000000000..862486a4b834 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithIf/afterTernary.java @@ -0,0 +1,10 @@ +// "Surround with 'if ((b ? null : "foo") != null)'" "true" +class A { + void bar(String s) {} + + void foo(boolean b){ + if ((b ? null : "foo") != null) { + bar(b ? null : "foo"); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithIf/beforeSideEffect.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithIf/beforeSideEffect.java new file mode 100644 index 000000000000..5c0f7390a211 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithIf/beforeSideEffect.java @@ -0,0 +1,8 @@ +// "Surround with 'if ((Math.random() > 0.5 ? null : "foo") != null)'" "false" +class A { + void bar(String s) {} + + void foo(boolean b){ + bar(Math.random() > 0.5 ? null : "foo"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithIf/beforeTernary.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithIf/beforeTernary.java new file mode 100644 index 000000000000..6e7bc5706bf5 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithIf/beforeTernary.java @@ -0,0 +1,8 @@ +// "Surround with 'if ((b ? null : "foo") != null)'" "true" +class A { + void bar(String s) {} + + void foo(boolean b){ + bar(b ? null : "foo"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterArrayAccess.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterArrayAccess.java new file mode 100644 index 000000000000..833a69757ae7 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterArrayAccess.java @@ -0,0 +1,10 @@ +// "Replace with 'Objects.requireNonNull(arr)'" "true" + +import java.util.Objects; + +class MyClass { + void test() { + int[] arr = Math.random() > 0.5 ? null : new int[10]; + System.out.println(Objects.requireNonNull(arr)[1]); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterFieldDereference.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterFieldDereference.java new file mode 100644 index 000000000000..6f981f5475fc --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterFieldDereference.java @@ -0,0 +1,14 @@ +// "Replace with 'Objects.requireNonNull(getObject())'" "true" + +import java.util.Objects; + +class MyClass { + int a; + + static MyClass getObject() { + return Math.random() > 0.5 ? new MyClass() : null; + } + void test() { + Objects.requireNonNull(getObject()).a = 5; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterMethodArgument.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterMethodArgument.java new file mode 100644 index 000000000000..0b8833741ab4 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterMethodArgument.java @@ -0,0 +1,12 @@ +// "Replace with 'Objects.requireNonNull(arr)'" "true" + +import java.util.Objects; + +class MyClass { + void foo(String[] arr) {} + + void test() { + String[] arr = Math.random() > 0.5 ? null : new String[10]; + foo(Objects.requireNonNull(arr)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterMethodArgument2.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterMethodArgument2.java new file mode 100644 index 000000000000..a460146b68ec --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterMethodArgument2.java @@ -0,0 +1,12 @@ +// "Replace with 'Objects.requireNonNull(Math.random() > 0.5 ? null : "bar")'" "true" + +import java.util.List; +import java.util.Objects; + +class MyClass { + void foo(String str) {} + + void test() { + foo(Objects.requireNonNull(Math.random() > 0.5 ? null : "bar")); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterMethodCall.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterMethodCall.java new file mode 100644 index 000000000000..cd097f2f859b --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterMethodCall.java @@ -0,0 +1,11 @@ +// "Replace with 'Objects.requireNonNull(s1)'" "true" +import java.util.List; +import java.util.Objects; + +class MyClass { + void test(List list) { + list.stream().map(s -> s.isEmpty() ? null : s) + .map(s1 -> Objects.requireNonNull(s1).trim()) + .forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeArrayAccess.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeArrayAccess.java new file mode 100644 index 000000000000..97dc30bbaebd --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeArrayAccess.java @@ -0,0 +1,8 @@ +// "Replace with 'Objects.requireNonNull(arr)'" "true" + +class MyClass { + void test() { + int[] arr = Math.random() > 0.5 ? null : new int[10]; + System.out.println(arr[1]); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeFieldDereference.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeFieldDereference.java new file mode 100644 index 000000000000..7ae951894d71 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeFieldDereference.java @@ -0,0 +1,12 @@ +// "Replace with 'Objects.requireNonNull(getObject())'" "true" + +class MyClass { + int a; + + static MyClass getObject() { + return Math.random() > 0.5 ? new MyClass() : null; + } + void test() { + getObject().a = 5; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeMethodArgument.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeMethodArgument.java new file mode 100644 index 000000000000..4b17793dfc51 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeMethodArgument.java @@ -0,0 +1,10 @@ +// "Replace with 'Objects.requireNonNull(arr)'" "true" + +class MyClass { + void foo(String[] arr) {} + + void test() { + String[] arr = Math.random() > 0.5 ? null : new String[10]; + foo(arr); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeMethodArgument2.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeMethodArgument2.java new file mode 100644 index 000000000000..b17be30ea19d --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeMethodArgument2.java @@ -0,0 +1,11 @@ +// "Replace with 'Objects.requireNonNull(Math.random() > 0.5 ? null : "bar")'" "true" + +import java.util.List; + +class MyClass { + void foo(String str) {} + + void test() { + foo(Math.random() > 0.5 ? null : "bar"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeMethodCall.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeMethodCall.java new file mode 100644 index 000000000000..fe0f437804e8 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeMethodCall.java @@ -0,0 +1,10 @@ +// "Replace with 'Objects.requireNonNull(s1)'" "true" +import java.util.List; + +class MyClass { + void test(List list) { + list.stream().map(s -> s.isEmpty() ? null : s) + .map(s1 -> s1.trim()) + .forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInspection/redundantLambdaParameterType/NotApplicableDueToChainedCall.java b/java/java-tests/testData/codeInspection/redundantLambdaParameterType/NotApplicableDueToChainedCall.java new file mode 100644 index 000000000000..da3c6fe0ad73 --- /dev/null +++ b/java/java-tests/testData/codeInspection/redundantLambdaParameterType/NotApplicableDueToChainedCall.java @@ -0,0 +1,16 @@ +import java.util.ArrayList; +import java.util.Comparator; + +public final class Main { + public static void main(final String[] args) { + new ArrayList().sort(Comparator.comparingInt((Node node) -> node.value).reversed()); + } + + static class Node { + private final int value; + + Node(final int value) { + this.value = value; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/EmptySingletonMap.java b/java/java-tests/testData/inspection/dataFlow/fixture/EmptySingletonMap.java new file mode 100644 index 000000000000..bd316c1bf5ec --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/EmptySingletonMap.java @@ -0,0 +1,42 @@ +import java.util.*; + +public class EmptySingletonMap { + void testEmpty() { + List objects = Collections.emptyList(); + for (Object o : objects) { + System.out.println("hello"); + } + } + + void testMixed(int x) { + Collection strings; + if(x > 10) { + strings = Collections.singleton("foo"); + } else if(x > 6) { + strings = Collections.emptyList(); + } else if(x > 2) { + strings = Collections.singletonList("bar"); + } else { + strings = Arrays.asList("a", "b", "c"); + } + if(strings.size() >= 2 && x > 3) { + System.out.println("never"); + } + } + + void testMap(boolean b) { + Map map; + if (b) { + map = Collections.emptyMap(); + } + else { + map = Collections.singletonMap("a", "b"); + } + if(b && map.isEmpty()) { + System.out.println("true"); + } + if(!b && map.size() == 1) { + System.out.println("??"); + } + } +} diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/IgnoreNullabilityOnPrimitiveCast.java b/java/java-tests/testData/inspection/dataFlow/fixture/IgnoreNullabilityOnPrimitiveCast.java new file mode 100644 index 000000000000..0fa48439939c --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/IgnoreNullabilityOnPrimitiveCast.java @@ -0,0 +1,7 @@ +import foo.*; + +class IgnoreNullabilityOnPrimitiveCast { + private static int map(@Nullable int[] mapping, int idx) { + return mapping[idx]; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/LambdaInlining.java b/java/java-tests/testData/inspection/dataFlow/fixture/LambdaInlining.java new file mode 100644 index 000000000000..4bc09bca8205 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/LambdaInlining.java @@ -0,0 +1,30 @@ +import java.util.function.IntSupplier; + +public class LambdaInlining { + void testLambdaInline() { + int x = ((IntSupplier) (() -> { + if (Math.random() > 0.5) { + return 4; + } + return 5; + })).getAsInt(); + if (x == 6) { + System.out.println("oops"); + } + } + + void testLambdaTryCatch() { + int x = ((IntSupplier)(() -> { + try { + return Math.random() > 0.5 ? 2 : 3; + } + catch (Exception ex) { + + } + return 1; + })).getAsInt(); + if(x == 0) { + System.out.println("oops"); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/OptionalGetWithoutIsPresent.java b/java/java-tests/testData/inspection/dataFlow/fixture/OptionalGetWithoutIsPresent.java index 36299e7c0a6d..f8e87467cc92 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/OptionalGetWithoutIsPresent.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/OptionalGetWithoutIsPresent.java @@ -324,7 +324,7 @@ class OptionalWithoutIsPresent { } } - void guavaTest(com.google.common.base.Optional opt, String s) { + void guavaTest(com.google.common.base.Optional opt, String s, String s1) { System.out.println(opt.get()); if(opt.isPresent()) { System.out.println(opt.get()); @@ -333,7 +333,8 @@ class OptionalWithoutIsPresent { if(opt.isPresent()) { System.out.println(opt.get()); } - opt = com.google.common.base.Optional.of(s); + opt = com.google.common.base.Optional.of(s); + opt = com.google.common.base.Optional.of(s1); if(opt.isPresent()) { System.out.println(opt.get()); } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/OptionalInlining.java b/java/java-tests/testData/inspection/dataFlow/fixture/OptionalInlining.java new file mode 100644 index 000000000000..e99427140f83 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/OptionalInlining.java @@ -0,0 +1,231 @@ +import org.jetbrains.annotations.Nullable; +import java.util.Optional; + +public class OptionalInlining { + void testOrElse() { + String s = Optional.ofNullable("foo").orElse("bar"); + if (s.equals("bar")) { + System.out.println("Never"); + } + String s2 = Optional.ofNullable(null).orElse("bar"); + if (s2.equals("bar")) { + System.out.println("Always"); + } + String s3 = Optional.of(Math.random() > 0.5 ? "foo" : "baz").orElse("bar"); + if (s3.equals("foo") || s3.equals("baz")) { + System.out.println("Always"); + } + if (s3.equals("bar")) { + System.out.println("Never"); + } + } + + void testGuavaOr() { + String res = com.google.common.base.Optional.of("xyz").or("foo"); + if(res.equals("xyz")) { + System.out.println("Always"); + } + if(res.equals("foo")) { + System.out.println("Never"); + } + String res2 = com.google.common.base.Optional.absent().or(() -> "foo"); + if(res2.equals("xyz")) { + System.out.println("Never"); + } + if(res2.equals("foo")) { + System.out.println("Always"); + } + } + + void testGuavaOrNull() { + String s = com.google.common.base.Optional.fromNullable("foo").orNull(); + if (s == null) { + System.out.println("Never"); + } + if (s.equals("foo")) { + System.out.println("Always"); + } + } + + + void testIsPresent(Optional opt) { + if (!opt.isPresent() && opt.orElse("foo").equals("bar")) { + + } + } + + void testDeref(Optional opt) { + if (opt == null) { + System.out.println(opt.orElse("qq")); + } + } + + void testOrElseGet(Optional opt) { + opt.orElseGet(null); + String s = opt.orElseGet(() -> { + if (Math.random() > 0.5) { + return "foo"; + } + return "baz"; + }); + if (s.equals("bar") && !opt.isPresent()) { + System.out.println("Impossible"); + } + } + + void testFilter(Optional opt, Optional intOpt) { + opt.filter(null); + Integer integer = intOpt.filter(x -> x > 5).filter(x -> x == 5).orElse(0); + String s1 = opt.filter(s -> false).filter(s -> s.equals("barr")).orElse("baz"); + if (s1.equals("xz")) { + System.out.println("never"); + } + String abc = opt.filter(s -> s == "xyz").orElse("abc"); // s.equals("xyz") does not work yet :( + if (abc.equals("123")) { + System.out.println("never"); + } + if (abc.equals("xyz") && opt.isPresent()) { + System.out.println("always"); + } + opt.filter(x -> x.length() > 5).filter(x -> x.isEmpty()).ifPresent(x -> System.out.println(x)); + } + + @Nullable + String nullableMethod() { + if(Math.random() > 0.5) { + return null; + } + return ""; + } + + @Nullable + Optional nullableOptionalMethod(String x) { + return x.isEmpty() ? null : Optional.of(x); + } + + @Nullable + Object getObj(String s) { + return new Object(); + } + + void testMap(Optional opt) { + opt.map(null); + String res = opt.map(s -> null).orElse("abc"); + if (!res.equals("abc")) { + System.out.println("Never"); + } + String trimmed = Optional.ofNullable(nullableMethod()).map(xx -> xx.trim()).orElse(""); + if(trimmed == null) { + System.out.println("impossible"); + } + String xyz = nullableMethod(); + Object n = Optional.ofNullable(xyz).map(String::trim).map(this::getObj).orElse(null); + if(n instanceof Integer) { + // n instanceof Integer -> n is not null -> xyz was not null -> safe to dereference + System.out.println(xyz.trim()); + } + xyz.trim(); + //opt.map(x -> x.isEmpty() ? "foo" : "bar").filter(x -> x.isEmpty()).ifPresent(x -> System.out.println(x)); + } + + void testGuavaTransform(com.google.common.base.Optional opt) { + String trimmed = com.google.common.base.Optional.fromNullable(nullableMethod()).transform(xx -> xx.trim()).or(""); + if(trimmed == null) { + System.out.println("impossible"); + } + if(opt.isPresent()) { + if(opt.transform(x -> x.isEmpty() ? null : x).toJavaUtil().isPresent()) { + System.out.println("Always"); + } + if(opt.toJavaUtil().map(x -> x.isEmpty() ? null : x).isPresent()) { + System.out.println("Sometimes"); + } + } + } + + void testToJavaUtil() { + String xyz = nullableMethod(); + Object n = com.google.common.base.Optional.fromNullable(xyz).transform(String::trim).toJavaUtil().map(this::getObj).orElse(null); + if(n instanceof Integer) { + // n instanceof Integer -> n is not null -> xyz was not null -> safe to dereference + System.out.println(xyz.trim()); + } + xyz.trim(); + } + + void testFlatMap(Optional opt) { + opt.flatMap(null); + opt.flatMap(x -> null); + opt.flatMap(this::nullableOptionalMethod); + opt.flatMap(x -> x.isEmpty() ? null : Optional.of(x)); + opt.flatMap(x -> { + if (x.isEmpty()) { + return null; + } + else { + return Optional.of(x); + } + }); + String s = opt.flatMap(str -> Optional.of(str.length() > 10 ? "foo" : "bar")).orElse("baz"); + if (s.equals("qux")) { + System.out.println("Never"); + } + boolean res = opt.filter(x -> x.isEmpty()).flatMap(x -> x.length() <= 2 ? Optional.empty() : Optional.of("foo")) + .isPresent(); + } + + void testIfPresent(Optional opt) { + opt.ifPresent(null); + opt.map(s -> s.isEmpty() ? 5 : 6).ifPresent(val -> { + if (val == 7) { + System.out.println("oops"); + } + }); + } + + void testIntermediate(Optional opt) { + if ( x == \"bar\").isPresent()' is always 'false'">opt.filter(x -> x == "foo").filter(x -> x == "bar").isPresent()) { + System.out.println("never"); + } + } + + // IDEA-174759 + void testTwoOptionalInteraction(Optional a, Optional b) { + if (a.isPresent() || b.isPresent()) { + // prefer a over b + Integer result = a.map(s -> s + "0").map(s -> Integer.parseInt(s)) + .orElseGet(() -> Integer.parseInt(b.get())); // <-- no more warning for b.get() + System.out.println(result); + } + } + + void testTwoOptionalInteractionMethodRef(Optional a, Optional b) { + if (a.isPresent() || b.isPresent()) { + // prefer a over b + Integer result = a.map(s -> s + "0").map(Integer::parseInt) + .orElseGet(() -> Integer.parseInt(b.get())); // <-- no more warning for b.get() + System.out.println(result); + } + } + + static class Holder { + int x; + String s; + } + + void testFilterChain(Optional opt) { + boolean present = opt + .filter(h -> h.x < 5) + .filter(h -> h.x > 6) + .map(h -> h.x).isPresent(); + } + + void testFilterMap(Optional opt) { + boolean present = opt + .filter(h -> h.s == null) + .map(h -> h.s) + .isPresent(); + + opt.filter(h -> h.s == null).map(h -> h.s.trim()).ifPresent(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/StreamInlining.java b/java/java-tests/testData/inspection/dataFlow/fixture/StreamInlining.java new file mode 100644 index 000000000000..21b1558f12be --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/StreamInlining.java @@ -0,0 +1,121 @@ +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +public class StreamInlining { + void testNulls(List list) { + list.stream().filter(null).forEach(System.out::println); + list.stream().map(null).forEach(System.out::println); + list.stream().flatMap(null).forEach(System.out::println); + list.stream().filter(x -> x != null).forEach(null); + Stream stream = null; + stream.filter(x -> x != null).forEach(System.out::println); + } + + void testMethodRef(List list, int[] data) { + if(list.stream().map(String::new).anyMatch(x -> x == null)) { + System.out.println("never"); + } + if(Arrays.stream(data).mapToObj(int[]::new).anyMatch(x -> x == null)) { + System.out.println("never"); + } + list.stream().filter(Objects::isNull).map(String::trim).forEach(System.out::println); + if(list.stream().filter(Objects::nonNull).anyMatch(x -> x == null)) { + System.out.println("never"); + } + } + + void filterDistinctLimitSkipFilter(List list) { + list.stream().filter(x -> x == null).distinct().limit(10).skip(1).filter(x -> x != null).forEach(System.out::println); + } + + class Holder { + Object obj; + } + + int hash(List holders) { + return holders.stream().filter(h -> h.obj == null).mapToInt(h -> h.obj.hashCode()).sum(); + } + + void test2(int[] array) { + IntStream.of(array).filter(x -> x < 5) + .filter(x -> x > 7) // TODO: find variable state for non-qualified value + .forEach(value -> System.out.println(value)); + } + + // IDEA-152871 + static class A { + + void improved(String[] arr) { + Arrays.stream(arr).map(s -> s.isEmpty() ? s : null) + .map(s1 -> some(s1)) + .forEach(s -> System.out.println(s)); + } + + void improvedMethodRef(String[] arr) { + Arrays.stream(arr).map(s -> s.isEmpty() ? s : null) + .map(A::some) + .forEach(s -> System.out.println(s)); + } + + public static String some(@NotNull String s) { + return s; + } + } + + // IDEA-169280 + void f(List l) { + if(l.stream().filter(s -> s != null).allMatch(s -> s != null && s.startsWith("A"))) { + System.out.println("ok"); + } + } + + void boxed(IntStream is) { + is.boxed().filter(x -> x != null).forEach(s -> System.out.println(s)); + } + + boolean flatMap(List list) { + return list.stream().map(s -> s.isEmpty() ? null : s) + .flatMap(s -> Stream.of(s, s.trim()).filter(r -> r != null)) + .anyMatch(x -> x == null); + } + + String blockLambda(List list) { + return list == null ? "" : list.stream().map(s -> { + return s.equals("abc") ? null : s; + }).findFirst().orElse(""); + } + + // IDEA-164262 + static class MyClass { + @Nullable + static String nullableFunction(String s) { + return s; + } + + static String functionThatDoesNotAcceptNull(@NotNull String s) { + return s; + } + + void test(List list) { + list.stream() + .map(MyClass::nullableFunction) + .map(s -> functionThatDoesNotAcceptNull(s)) // Exception will be thrown here + .forEach(System.out::println); + } + + void testMr(List list) { + list.stream() + .map(MyClass::nullableFunction) + .map(MyClass::functionThatDoesNotAcceptNull) + .forEach(System.out::println); + } + } + + Optional testOptionalOfNullable(List list) { + return list.stream().filter(Objects::isNull).map(Optional::ofNullable).findFirst().orElse(Optional.empty()); + } +} diff --git a/java/java-tests/testData/inspection/redundantRequiresStatement/noImportsInSources/expected.xml b/java/java-tests/testData/inspection/redundantRequiresStatement/noImportsInSources/expected.xml index 4abff300b770..bcc925f3cb98 100644 --- a/java/java-tests/testData/inspection/redundantRequiresStatement/noImportsInSources/expected.xml +++ b/java/java-tests/testData/inspection/redundantRequiresStatement/noImportsInSources/expected.xml @@ -4,7 +4,7 @@ module-info.java 1 - Redundant 'requires' statement in module-info - Redundant statement 'requires M2' + Redundant 'requires' directive in module-info + Redundant directive 'requires M2' \ No newline at end of file diff --git a/java/java-tests/testData/inspection/redundantRequiresStatement/noSourcesAtAll/expected.xml b/java/java-tests/testData/inspection/redundantRequiresStatement/noSourcesAtAll/expected.xml index 4abff300b770..bcc925f3cb98 100644 --- a/java/java-tests/testData/inspection/redundantRequiresStatement/noSourcesAtAll/expected.xml +++ b/java/java-tests/testData/inspection/redundantRequiresStatement/noSourcesAtAll/expected.xml @@ -4,7 +4,7 @@ module-info.java 1 - Redundant 'requires' statement in module-info - Redundant statement 'requires M2' + Redundant 'requires' directive in module-info + Redundant directive 'requires M2' \ No newline at end of file diff --git a/java/java-tests/testData/inspection/redundantRequiresStatement/nonexistentMethodImported/expected.xml b/java/java-tests/testData/inspection/redundantRequiresStatement/nonexistentMethodImported/expected.xml index 4abff300b770..bcc925f3cb98 100644 --- a/java/java-tests/testData/inspection/redundantRequiresStatement/nonexistentMethodImported/expected.xml +++ b/java/java-tests/testData/inspection/redundantRequiresStatement/nonexistentMethodImported/expected.xml @@ -4,7 +4,7 @@ module-info.java 1 - Redundant 'requires' statement in module-info - Redundant statement 'requires M2' + Redundant 'requires' directive in module-info + Redundant directive 'requires M2' \ No newline at end of file diff --git a/java/java-tests/testData/inspection/redundantRequiresStatement/reexportedPackageImported/expected.xml b/java/java-tests/testData/inspection/redundantRequiresStatement/reexportedPackageImported/expected.xml index e6c1a320f3bd..17846e504251 100644 --- a/java/java-tests/testData/inspection/redundantRequiresStatement/reexportedPackageImported/expected.xml +++ b/java/java-tests/testData/inspection/redundantRequiresStatement/reexportedPackageImported/expected.xml @@ -4,7 +4,7 @@ module-info.java 1 - Redundant 'requires' statement in module-info - Redundant statement 'requires M6' + Redundant 'requires' directive in module-info + Redundant directive 'requires M6' \ No newline at end of file diff --git a/java/java-tests/testData/inspection/redundantRequiresStatement/requiresJavaBase/expected.xml b/java/java-tests/testData/inspection/redundantRequiresStatement/requiresJavaBase/expected.xml index d8d91d0ed3f7..c8217f852052 100644 --- a/java/java-tests/testData/inspection/redundantRequiresStatement/requiresJavaBase/expected.xml +++ b/java/java-tests/testData/inspection/redundantRequiresStatement/requiresJavaBase/expected.xml @@ -4,7 +4,7 @@ module-info.java 1 - Redundant 'requires' statement in module-info - Redundant statement 'requires java.base' + Redundant 'requires' directive in module-info + Redundant directive 'requires java.base' \ No newline at end of file diff --git a/java/java-tests/testData/inspection/redundantRequiresStatement/requiresManyModulesFewPackagesImported/expected.xml b/java/java-tests/testData/inspection/redundantRequiresStatement/requiresManyModulesFewPackagesImported/expected.xml index 2e2c01e3cf50..a8c73d013d0d 100644 --- a/java/java-tests/testData/inspection/redundantRequiresStatement/requiresManyModulesFewPackagesImported/expected.xml +++ b/java/java-tests/testData/inspection/redundantRequiresStatement/requiresManyModulesFewPackagesImported/expected.xml @@ -4,14 +4,14 @@ module-info.java 1 - Redundant 'requires' statement in module-info - Redundant statement 'requires M7' + Redundant 'requires' directive in module-info + Redundant directive 'requires M7' module-info.java 1 - Redundant 'requires' statement in module-info - Redundant statement 'requires M4' + Redundant 'requires' directive in module-info + Redundant directive 'requires M4' \ No newline at end of file diff --git a/java/java-tests/testData/inspection/streamApiCallChains/afterCollectorSummingDouble.java b/java/java-tests/testData/inspection/streamApiCallChains/afterCollectorSummingDouble.java new file mode 100644 index 000000000000..d2daa9a1f66e --- /dev/null +++ b/java/java-tests/testData/inspection/streamApiCallChains/afterCollectorSummingDouble.java @@ -0,0 +1,9 @@ +// "Replace 'collect(summingDouble())' with 'mapToDouble().sum()'" "true" + +import java.util.stream.*; + +public class Main { + void from(Stream stream) { + stream.mapToDouble(Double::doubleValue).sum(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/streamApiCallChains/beforeCollectorSummingDouble.java b/java/java-tests/testData/inspection/streamApiCallChains/beforeCollectorSummingDouble.java new file mode 100644 index 000000000000..620385cc18c4 --- /dev/null +++ b/java/java-tests/testData/inspection/streamApiCallChains/beforeCollectorSummingDouble.java @@ -0,0 +1,9 @@ +// "Replace 'collect(summingDouble())' with 'mapToDouble().sum()'" "true" + +import java.util.stream.*; + +public class Main { + void from(Stream stream) { + stream.collect(Collectors.summingDouble(Double::doubleValue)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/psi/parser-partial/modules/BadContent.txt b/java/java-tests/testData/psi/parser-partial/modules/BadContent.txt index 9b9e5d91b51d..108d32a11a9b 100644 --- a/java/java-tests/testData/psi/parser-partial/modules/BadContent.txt +++ b/java/java-tests/testData/psi/parser-partial/modules/BadContent.txt @@ -11,7 +11,7 @@ PsiJavaFile:BadContent.java PsiWhiteSpace(' ') PsiJavaToken:LBRACE('{') PsiWhiteSpace('\n ') - PsiErrorElement:Module statement expected + PsiErrorElement:Module directive expected PsiIdentifier:some('some') PsiWhiteSpace(' ') PsiIdentifier:junk('junk') diff --git a/java/java-tests/testData/psi/parser-partial/modules/Incomplete4.txt b/java/java-tests/testData/psi/parser-partial/modules/Incomplete4.txt index f5b2dd4e6682..b84928f628cd 100644 --- a/java/java-tests/testData/psi/parser-partial/modules/Incomplete4.txt +++ b/java/java-tests/testData/psi/parser-partial/modules/Incomplete4.txt @@ -11,5 +11,5 @@ PsiJavaFile:Incomplete4.java PsiWhiteSpace(' ') PsiJavaToken:LBRACE('{') PsiWhiteSpace(' ') - PsiErrorElement:Module statement expected + PsiErrorElement:Module directive expected PsiIdentifier:junk('junk') \ No newline at end of file diff --git a/java/java-tests/testData/psi/parser-partial/modules/Requires4.txt b/java/java-tests/testData/psi/parser-partial/modules/Requires4.txt index 8546b19090bf..3e1abeeafd71 100644 --- a/java/java-tests/testData/psi/parser-partial/modules/Requires4.txt +++ b/java/java-tests/testData/psi/parser-partial/modules/Requires4.txt @@ -20,7 +20,7 @@ PsiJavaFile:Requires4.java PsiIdentifier:A('A') PsiErrorElement:';' expected - PsiErrorElement:Module statement expected + PsiErrorElement:Module directive expected PsiJavaToken:COMMA(',') PsiWhiteSpace(' ') PsiIdentifier:B('B') diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/GenerateGetterSetterTest.groovy b/java/java-tests/testSrc/com/intellij/java/codeInsight/GenerateGetterSetterTest.groovy index a2aa12ab5b46..1908afb1f3c4 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/GenerateGetterSetterTest.groovy +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/GenerateGetterSetterTest.groovy @@ -219,7 +219,29 @@ class Foo { } ''' } - + + void "test invoke between comment and method"() { + myFixture.enableInspections(UnqualifiedFieldAccessInspection.class) + myFixture.configureByText 'a.java', ''' +class Foo { + int a; + //comment + void foo() {} +}''' + generateGetter() + myFixture.checkResult ''' +class Foo { + int a; + + public int getA() { + return this.a; + } + + //comment + void foo() {} +}''' + } + private void generateSetter() { new GenerateSetterHandler() { @Override diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/CompletionHintsTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/CompletionHintsTest.java index 672545c585f8..9fa46c82c9f7 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/CompletionHintsTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/CompletionHintsTest.java @@ -61,24 +61,26 @@ public class CompletionHintsTest extends LightFixtureCompletionTestCase { // check hints appearance on completion configureJava("class C { void m() { System.setPro } }"); complete("setProperty"); - checkResultWithInlays("class C { void m() { System.setProperty(, ) } }"); + checkResultWithInlays("class C { void m() { System.setProperty(, ) } }"); // check that hints don't disappear after daemon highlighting passes waitForAllAsyncStuff(); - checkResultWithInlays("class C { void m() { System.setProperty(, ) } }"); + checkResultWithInlays("class C { void m() { System.setProperty(, ) } }"); // test Tab/Shift+Tab navigation next(); - checkResultWithInlays("class C { void m() { System.setProperty(, ) } }"); + waitForAllAsyncStuff(); + checkResultWithInlays("class C { void m() { System.setProperty(, ) } }"); prev(); - checkResultWithInlays("class C { void m() { System.setProperty(, ) } }"); + waitForAllAsyncStuff(); + checkResultWithInlays("class C { void m() { System.setProperty(, ) } }"); // test hints remain shown while entering parameter values myFixture.type("\"a"); next(); myFixture.type("\"b"); waitForAllAsyncStuff(); - checkResultWithInlays("class C { void m() { System.setProperty(\"a\", \"b\") } }"); + checkResultWithInlays("class C { void m() { System.setProperty(\"a\", \"b\") } }"); // test hints disappearance when caret moves out of parameter list right(); @@ -93,24 +95,26 @@ public class CompletionHintsTest extends LightFixtureCompletionTestCase { // check hints appearance on completion configureJava("class C { void m() { Character.for } }"); complete("forDigit"); - checkResultWithInlays("class C { void m() { Character.forDigit(, ) } }"); + checkResultWithInlays("class C { void m() { Character.forDigit(, ) } }"); // check that hints don't disappear after daemon highlighting passes waitForAllAsyncStuff(); - checkResultWithInlays("class C { void m() { Character.forDigit(, ) } }"); + checkResultWithInlays("class C { void m() { Character.forDigit(, ) } }"); // test Tab/Shift+Tab navigation next(); - checkResultWithInlays("class C { void m() { Character.forDigit(, ) } }"); + waitForAllAsyncStuff(); + checkResultWithInlays("class C { void m() { Character.forDigit(, ) } }"); prev(); - checkResultWithInlays("class C { void m() { Character.forDigit(, ) } }"); + waitForAllAsyncStuff(); + checkResultWithInlays("class C { void m() { Character.forDigit(, ) } }"); // test hints remain shown while entering parameter values myFixture.type("1"); next(); myFixture.type("2"); waitForAllAsyncStuff(); - checkResultWithInlays("class C { void m() { Character.forDigit(1, 2) } }"); + checkResultWithInlays("class C { void m() { Character.forDigit(1, 2) } }"); // test hints don't disappear when caret moves out of parameter list right(); @@ -129,10 +133,10 @@ public class CompletionHintsTest extends LightFixtureCompletionTestCase { configureJava("class C { void m() { Character.for } }"); complete("forDigit"); - checkResultWithInlays("class C { void m() { Character.forDigit(, ) } }"); + checkResultWithInlays("class C { void m() { Character.forDigit(, ) } }"); waitForAllAsyncStuff(); - checkResultWithInlays("class C { void m() { Character.forDigit(, ) } }"); + checkResultWithInlays("class C { void m() { Character.forDigit(, ) } }"); } finally { option.set(savedValue); @@ -142,21 +146,21 @@ public class CompletionHintsTest extends LightFixtureCompletionTestCase { public void testSwitchingOverloads() { configureJava("class C { void m() { Character.to } }"); complete("toChars(int codePoint)"); - checkResultWithInlays("class C { void m() { Character.toChars() } }"); + checkResultWithInlays("class C { void m() { Character.toChars() } }"); showParameterInfo(); myFixture.performEditorAction("MethodOverloadSwitchDown"); checkResultWithInlays( - "class C { void m() { Character.toChars(, , ) } }"); + "class C { void m() { Character.toChars(, , ) } }"); } public void testSwitchingOverloadsWithParameterFilled() { configureJava("class C { void m() { Character.to } }"); complete("toChars(int codePoint)"); type("123"); - checkResultWithInlays("class C { void m() { Character.toChars(123) } }"); + checkResultWithInlays("class C { void m() { Character.toChars(123) } }"); showParameterInfo(); myFixture.performEditorAction("MethodOverloadSwitchDown"); - checkResultWithInlays("class C { void m() { Character.toChars(123, , ) } }"); + checkResultWithInlays("class C { void m() { Character.toChars(123, , ) } }"); } public void testSwitchingOverloadsForMethodWithDisabledHints() throws Exception { @@ -169,7 +173,7 @@ public class CompletionHintsTest extends LightFixtureCompletionTestCase { checkResultWithInlays("class C {\n" + " int some(int from, int to) { return 0; }\n" + " int some(int from, int to, int other) { return 0; }\n" + - " void m() { some(, , ) }\n" + + " void m() { some(, , ) }\n" + "}"); showParameterInfo(); myFixture.performEditorAction("MethodOverloadSwitchDown"); @@ -177,7 +181,7 @@ public class CompletionHintsTest extends LightFixtureCompletionTestCase { checkResultWithInlays("class C {\n" + " int some(int from, int to) { return 0; }\n" + " int some(int from, int to, int other) { return 0; }\n" + - " void m() { some(, ) }\n" + + " void m() { some(, ) }\n" + "}"); } @@ -201,11 +205,11 @@ public class CompletionHintsTest extends LightFixtureCompletionTestCase { configureJava("class C { void m() { System.setPro } }"); complete("setProperty"); waitForAllAsyncStuff(); - checkResultWithInlays("class C { void m() { System.setProperty(, ) } }"); + checkResultWithInlays("class C { void m() { System.setProperty(, ) } }"); myFixture.type("System.getPro"); complete("getProperty(String key, String def)"); waitForAllAsyncStuff(); - checkResultWithInlays("class C { void m() { System.setProperty(System.getProperty(, ), ) } }"); + checkResultWithInlays("class C { void m() { System.setProperty(System.getProperty(, ), ) } }"); } public void testTabWithNestedCompletion() throws Exception { @@ -215,14 +219,18 @@ public class CompletionHintsTest extends LightFixtureCompletionTestCase { myFixture.type("System.getPro"); complete("getProperty(String key, String def)"); waitForAllAsyncStuff(); - checkResultWithInlays("class C { void m() { System.setProperty(System.getProperty(, ), ) } }"); + checkResultWithInlays("class C { void m() { System.setProperty(System.getProperty(, ), ) } }"); next(); - checkResultWithInlays("class C { void m() { System.setProperty(System.getProperty(, ), ) } }"); + waitForAllAsyncStuff(); + checkResultWithInlays("class C { void m() { System.setProperty(System.getProperty(, ), ) } }"); next(); - checkResultWithInlays("class C { void m() { System.setProperty(System.getProperty(, ), ) } }"); + waitForAllAsyncStuff(); + checkResultWithInlays("class C { void m() { System.setProperty(System.getProperty(, ), ) } }"); next(); - checkResultWithInlays("class C { void m() { System.setProperty(System.getProperty(, ), ) } }"); + waitForAllAsyncStuff(); + checkResultWithInlays("class C { void m() { System.setProperty(System.getProperty(, ), ) } }"); next(); + waitForAllAsyncStuff(); checkResultWithInlays("class C { void m() { System.setProperty(System.getProperty(, ), ) } }"); } @@ -235,7 +243,7 @@ public class CompletionHintsTest extends LightFixtureCompletionTestCase { public void testHintsDisappearWhenNumberOfParametersIsChangedDirectly() throws Exception { configureJava("class C { void m() { System.getPro } }"); complete("getProperty(String key, String def)"); - checkResultWithInlays("class C { void m() { System.getProperty(, ) } }"); + checkResultWithInlays("class C { void m() { System.getProperty(, ) } }"); myFixture.performEditorAction(IdeActions.ACTION_EDITOR_DELETE); waitForAllAsyncStuff(); checkResultWithInlays("class C { void m() { System.getProperty( ) } }"); @@ -244,7 +252,7 @@ public class CompletionHintsTest extends LightFixtureCompletionTestCase { public void testHintsDisappearWhenNumberOfParametersIsChangedDirectlyWithNoOverloads() throws Exception { configureJava("class C { void m() { Character.for } }"); complete("forDigit"); - checkResultWithInlays("class C { void m() { Character.forDigit(, ) } }"); + checkResultWithInlays("class C { void m() { Character.forDigit(, ) } }"); myFixture.performEditorAction(IdeActions.ACTION_EDITOR_DELETE); waitForAllAsyncStuff(); checkResultWithInlays("class C { void m() { Character.forDigit( ) } }"); @@ -255,7 +263,8 @@ public class CompletionHintsTest extends LightFixtureCompletionTestCase { complete("setProperty"); type("new String().trim"); myFixture.complete(CompletionType.SMART); - checkResultWithInlays("class C { void m() { System.setProperty(new String().trim(), ) } }"); + waitForAllAsyncStuff(); + checkResultWithInlays("class C { void m() { System.setProperty(new String().trim(), ) } }"); } public void testNoHintsDuplicationWhenTypingToTheLeftOfHint() throws Exception { @@ -266,7 +275,7 @@ public class CompletionHintsTest extends LightFixtureCompletionTestCase { right(); type("2"); waitForAllAsyncStuff(); - checkResultWithInlays("class C { void m() { Character.forDigit(1,2 ) } }"); + checkResultWithInlays("class C { void m() { Character.forDigit(1,2 ) } }"); myFixture.performEditorAction(IdeActions.ACTION_EDITOR_MOVE_LINE_END); waitForAllAsyncStuff(); checkResultWithInlays("class C { void m() { Character.forDigit(1,2 ) } }"); @@ -318,7 +327,8 @@ public class CompletionHintsTest extends LightFixtureCompletionTestCase { next(); next(); prev(); - checkResultWithInlays("class C { void m() { System.getProperty(, ) } }"); + waitForAllAsyncStuff(); + checkResultWithInlays("class C { void m() { System.getProperty(, ) } }"); } public void testPrevParameterFromOutsideWhenParametersAreNotEmpty() throws Exception { @@ -328,64 +338,77 @@ public class CompletionHintsTest extends LightFixtureCompletionTestCase { next(); type("\"b"); next(); - waitForAllAsyncStuff(); prev(); - checkResultWithInlays("class C { void m() { System.getProperty(\"a\", \"b\") } }"); + waitForAllAsyncStuff(); + checkResultWithInlays("class C { void m() { System.getProperty(\"a\", \"b\") } }"); } public void testVararg() throws Exception { configureJava("class C { void m() { String.for } }"); complete(); - checkResultWithInlays("class C { void m() { String.format() } }"); + checkResultWithInlays("class C { void m() { String.format() } }"); type("\"a"); next(); - checkResultWithInlays("class C { void m() { String.format(\"a\", ) } }"); + waitForAllAsyncStuff(); + checkResultWithInlays("class C { void m() { String.format(\"a\", ) } }"); next(); + waitForAllAsyncStuff(); checkResultWithInlays("class C { void m() { String.format(\"a\") } }"); prev(); - checkResultWithInlays("class C { void m() { String.format(\"a\", ) } }"); + waitForAllAsyncStuff(); + checkResultWithInlays("class C { void m() { String.format(\"a\", ) } }"); prev(); - checkResultWithInlays("class C { void m() { String.format(\"a\") } }"); + waitForAllAsyncStuff(); + checkResultWithInlays("class C { void m() { String.format(\"a\") } }"); } public void testVarargWithNoMandatoryArguments() throws Exception { configureJava("class C { int vararg(int... args){ return 0; } void m() { varar } }"); complete(); - checkResultWithInlays("class C { int vararg(int... args){ return 0; } void m() { vararg() } }"); + checkResultWithInlays("class C { int vararg(int... args){ return 0; } void m() { vararg() } }"); type("1"); next(); + waitForAllAsyncStuff(); checkResultWithInlays("class C { int vararg(int... args){ return 0; } void m() { vararg(1, ) } }"); next(); + waitForAllAsyncStuff(); checkResultWithInlays("class C { int vararg(int... args){ return 0; } void m() { vararg(1) } }"); prev(); + waitForAllAsyncStuff(); checkResultWithInlays("class C { int vararg(int... args){ return 0; } void m() { vararg(1, ) } }"); prev(); - checkResultWithInlays("class C { int vararg(int... args){ return 0; } void m() { vararg(1) } }"); + waitForAllAsyncStuff(); + checkResultWithInlays("class C { int vararg(int... args){ return 0; } void m() { vararg(1) } }"); } public void testVarargWithTwoMandatoryArguments() throws Exception { configureJava("class C { int vararg(int a, int b, int... args){ return 0; } void m() { varar } }"); complete(); - checkResultWithInlays("class C { int vararg(int a, int b, int... args){ return 0; } void m() { vararg(, ) } }"); + checkResultWithInlays("class C { int vararg(int a, int b, int... args){ return 0; } void m() { vararg(, ) } }"); type("1"); next(); - checkResultWithInlays("class C { int vararg(int a, int b, int... args){ return 0; } void m() { vararg(1, ) } }"); + waitForAllAsyncStuff(); + checkResultWithInlays("class C { int vararg(int a, int b, int... args){ return 0; } void m() { vararg(1, ) } }"); type("2"); next(); - checkResultWithInlays("class C { int vararg(int a, int b, int... args){ return 0; } void m() { vararg(1, 2, ) } }"); + waitForAllAsyncStuff(); + checkResultWithInlays("class C { int vararg(int a, int b, int... args){ return 0; } void m() { vararg(1, 2, ) } }"); next(); + waitForAllAsyncStuff(); checkResultWithInlays("class C { int vararg(int a, int b, int... args){ return 0; } void m() { vararg(1, 2) } }"); prev(); - checkResultWithInlays("class C { int vararg(int a, int b, int... args){ return 0; } void m() { vararg(1, 2, ) } }"); + waitForAllAsyncStuff(); + checkResultWithInlays("class C { int vararg(int a, int b, int... args){ return 0; } void m() { vararg(1, 2, ) } }"); prev(); - checkResultWithInlays("class C { int vararg(int a, int b, int... args){ return 0; } void m() { vararg(1, 2) } }"); + waitForAllAsyncStuff(); + checkResultWithInlays("class C { int vararg(int a, int b, int... args){ return 0; } void m() { vararg(1, 2) } }"); } public void testHintsDontDisappearWhenNavigatingAwayFromUncompletedInvocation() throws Exception { configureJava("class C { void m() { System.setPro } }"); complete("setProperty"); waitForAllAsyncStuff(); - checkResultWithInlays("class C { void m() { System.setProperty(, ) } }"); + checkResultWithInlays("class C { void m() { System.setProperty(, ) } }"); myFixture.getEditor().getCaretModel().moveToOffset(0); type(' '); waitForAllAsyncStuff(); @@ -407,7 +430,7 @@ public class CompletionHintsTest extends LightFixtureCompletionTestCase { "}\n" + "class C extends P {\n" + " void method(int a, int b) {\n" + - " super.method(, );\n" + + " super.method(, );\n" + " }\n" + "}"); complete("a, b"); @@ -431,24 +454,26 @@ public class CompletionHintsTest extends LightFixtureCompletionTestCase { // check hints appearance on completion configureJava("class C { void m() { Character.for } }"); complete("forDigit"); - checkResultWithInlays("class C { void m() { Character.forDigit(, ) } }"); + checkResultWithInlays("class C { void m() { Character.forDigit(, ) } }"); // check that hints don't disappear after daemon highlighting passes waitForAllAsyncStuff(); - checkResultWithInlays("class C { void m() { Character.forDigit(, ) } }"); + checkResultWithInlays("class C { void m() { Character.forDigit(, ) } }"); // test Tab/Shift+Tab navigation next(); - checkResultWithInlays("class C { void m() { Character.forDigit(, ) } }"); + waitForAllAsyncStuff(); + checkResultWithInlays("class C { void m() { Character.forDigit(, ) } }"); prev(); - checkResultWithInlays("class C { void m() { Character.forDigit(, ) } }"); + waitForAllAsyncStuff(); + checkResultWithInlays("class C { void m() { Character.forDigit(, ) } }"); // test hints remain shown while entering parameter values myFixture.type("1"); next(); myFixture.type("2"); waitForAllAsyncStuff(); - checkResultWithInlays("class C { void m() { Character.forDigit(1, 2) } }"); + checkResultWithInlays("class C { void m() { Character.forDigit(1, 2) } }"); // test hints disappear when caret moves out of parameter list right(); @@ -463,6 +488,18 @@ public class CompletionHintsTest extends LightFixtureCompletionTestCase { } } + public void testLargeNumberOfParameters() throws Exception { + configureJava("class C {\n" + + " void mmm(int a, int b, int c, int d, int e, int f) {}\n" + + " void m2() { mm }\n" + + "}"); + complete(); + checkResultWithInlays("class C {\n" + + " void mmm(int a, int b, int c, int d, int e, int f) {}\n" + + " void m2() { mmm(, , , , , ); }\n" + + "}"); + } + private void checkResult(String text) { myFixture.checkResult(text); } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/KeywordCompletionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/KeywordCompletionTest.java index cda69c211976..e4696d341a88 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/KeywordCompletionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/KeywordCompletionTest.java @@ -125,6 +125,9 @@ public class KeywordCompletionTest extends LightCompletionTestCase { public void testPrimitivesInClassAnnotationAttribute() { doTest(3, "true", "int", "boolean"); } public void testPrimitivesInMethodReturningArray() { doTest(2, "true", "byte", "boolean"); } + public void testNoClassKeywordsInLocalArrayInitializer() { doTest(0, "class", "interface", "enum"); } + public void testNoClassKeywordsInFieldArrayInitializer() { doTest(0, "class", "interface", "enum"); } + public void testImportStatic() { doTest(1, "static"); } public void testAbstractInInterface() { doTest(1, "abstract"); } public void testCharInAnnotatedParameter() { doTest(1, "char"); } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalCompletionTest.groovy b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalCompletionTest.groovy index 28d7d52cdcbd..2e83d48bcd4c 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalCompletionTest.groovy +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/NormalCompletionTest.groovy @@ -688,6 +688,11 @@ public class ListUtils { assert myFixture.lookupElementStrings == ['char'] } + void testShadowedTypeParameter() { + configure() + assert myFixture.lookupElementStrings == ['MyParam'] + } + void testMethodReturnType() throws Throwable { doTest() } @@ -1794,5 +1799,10 @@ class Bar { configure() assert myFixture.lookupElements.collect { LookupElementPresentation.renderElement(it).itemText } == ['Bar.valueOf', 'Foo.valueOf', 'Enum.valueOf'] } + + void testTypeArgumentInCast() { + configure() + myFixture.assertPreferredCompletionItems 0, 'String' + } } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/SmartTypeCompletionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/SmartTypeCompletionTest.java index 886fd3441442..907207497a9d 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/SmartTypeCompletionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/SmartTypeCompletionTest.java @@ -1249,6 +1249,7 @@ public class SmartTypeCompletionTest extends LightFixtureCompletionTestCase { public void testStaticBuilder() { doTest(); } public void testStaticBuilderWithArguments() { doTest(); } + public void testStaticBuilderWithInterfaceAndGenerics() { doTest(); } public void testStaticBuilderWithGenerics() { configureByTestName(); diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/impl/DaemonRespondToChangesTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/impl/DaemonRespondToChangesTest.java index 32347d53f58c..6c055fc8bfa5 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/impl/DaemonRespondToChangesTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/impl/DaemonRespondToChangesTest.java @@ -443,11 +443,14 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { try { while (true) { try { - myDaemonCodeAnalyzer.runPasses(getFile(), getDocument(getFile()), textEditor, new int[0], true, () -> { + int[] toIgnore = new int[0]; + Runnable callbackWhileWaiting = () -> { if (!run.getAndSet(true)) { action.run(); } - }); + }; + myDaemonCodeAnalyzer + .runPasses(getFile(), getDocument(getFile()), Collections.singletonList(textEditor), toIgnore, true, callbackWhileWaiting); break; } catch (ProcessCanceledException ignored) { } @@ -570,7 +573,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { assertTrue(tool.visited.toString(), visitedCount > 0); tool.visited.clear(); - Document otherDocument = PsiDocumentManager.getInstance(getProject()).getDocument(otherFile); + Document otherDocument = ObjectUtils.assertNotNull(PsiDocumentManager.getInstance(getProject()).getDocument(otherFile)); WriteCommandAction.runWriteCommandAction(getProject(), () -> otherDocument.setText("zzz")); infos = doHighlighting(HighlightSeverity.WARNING); @@ -650,7 +653,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { break; } } - delete(schemaEditor); + delete(ObjectUtils.assertNotNull(schemaEditor)); errors = highlightErrors(); assertFalse(errors.isEmpty()); @@ -904,7 +907,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { Disposer.register(getTestRootDisposable(), () -> LineMarkerProviders.INSTANCE.removeExplicitExtension(JavaLanguage.INSTANCE, provider)); myDaemonCodeAnalyzer.restart(); try { - TextRange range = FileStatusMap.getDirtyTextRange(getEditor(), Pass.UPDATE_ALL); + TextRange range = ObjectUtils.assertNotNull(FileStatusMap.getDirtyTextRange(getEditor(), Pass.UPDATE_ALL)); log.append("FileStatusMap.getDirtyTextRange: " + range+"\n"); List elements = CollectHighlightsUtil.getElementsInRange(getFile(), range.getStartOffset(), range.getEndOffset()); log.append("CollectHighlightsUtil.getElementsInRange" + range + ": " + elements.size() +" elements : "+ elements+"\n"); @@ -1524,7 +1527,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { }.execute().throwException().getResultObject(); FileEditorManager fe = FileEditorManager.getInstance(alienProject); - final Editor alienEditor = fe.openTextEditor(alienDescriptor, false); + final Editor alienEditor = ObjectUtils.assertNotNull(fe.openTextEditor(alienDescriptor, false)); ((EditorImpl)alienEditor).setCaretActive(); PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); PsiDocumentManager.getInstance(alienProject).commitAllDocuments(); @@ -1533,11 +1536,12 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(getEditor()); DaemonCodeAnalyzerImpl di = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject()); final boolean[] checked = {false}; - di.runPasses(getFile(), getEditor().getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, true, () -> { + Runnable callbackWhileWaiting = () -> { if (checked[0]) return; checked[0] = true; typeInAlienEditor(alienEditor, 'x'); - }); + }; + di.runPasses(getFile(), getEditor().getDocument(), Collections.singletonList(textEditor), ArrayUtil.EMPTY_INT_ARRAY, true, callbackWhileWaiting); } catch (ProcessCanceledException ignored) { //DaemonProgressIndicator.setDebug(true); @@ -1636,7 +1640,8 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { throw new ProcessCanceledException(); }; long hiStart = System.currentTimeMillis(); - codeAnalyzer.runPasses(file, editor.getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, false, interrupt); + codeAnalyzer + .runPasses(file, editor.getDocument(), Collections.singletonList(textEditor), ArrayUtil.EMPTY_INT_ARRAY, false, interrupt); long hiEnd = System.currentTimeMillis(); DaemonProgressIndicator progress = codeAnalyzer.getUpdateProgress(); String message = "Should have been interrupted: " + progress + "; Elapsed: " + (hiEnd - hiStart) + "ms"; @@ -1724,7 +1729,8 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { CodeInsightTestFixtureImpl.ensureIndexesUpToDate(project); TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(editor); PsiDocumentManager.getInstance(myProject).commitAllDocuments(); - codeAnalyzer.runPasses(file, editor.getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, false, interrupt); + codeAnalyzer + .runPasses(file, editor.getDocument(), Collections.singletonList(textEditor), ArrayUtil.EMPTY_INT_ARRAY, false, interrupt); throw new RuntimeException("should have been interrupted"); } @@ -1750,179 +1756,10 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { assertTrue(String.valueOf(mean), mean < 10); } - @NotNull - private static Collection getInspectionsFor(@NotNull com.intellij.lang.Language language) { - List allInspections = InspectionToolRegistrar.getInstance().createTools(); - Collection filtered = allInspections.stream(). - map(wrapper -> wrapper instanceof LocalInspectionToolWrapper ? (LocalInspectionToolWrapper)wrapper : - //wrapper instanceof GlobalInspectionToolWrapper - //? ((GlobalInspectionToolWrapper)wrapper).getSharedLocalInspectionToolWrapper() : - null) - .filter(wrapper -> wrapper != null && wrapper.isApplicable(language)) - .map(InspectionToolWrapper::getTool) - .collect(Collectors.toSet()); - System.out.println("All inspections: " + allInspections.size() + "; filtered for " + language+": "+filtered.size()); - return filtered; - } - - /* - private void inspect(LocalInspectionToolWrapper wrapper) { - PsiFile file = getFile(); - TextRange range = file.getTextRange(); - - List allDivided = new ArrayList<>(); - Divider.divideInsideAndOutsideAllRoots(file, range, range, Conditions.alwaysTrue(), new CommonProcessors.CollectProcessor<>(allDivided)); - - List elements = ContainerUtil.concat( - (List>)ContainerUtil.map(allDivided, d -> ContainerUtil.concat(d.inside, d.outside, d.parents))); - - PsiErrorElement errorElement = ContainerUtil.findInstance(elements, PsiErrorElement.class); - assertNull(file.getText(), errorElement); - - Set elementDialectIds = InspectionEngine.calcElementDialectIds(elements); - - //List errors = DaemonAnalyzerTestCase.filter(doHighlighting(), HighlightSeverity.ERROR);// warmup - //assertEmpty(errors); - - timeInspection(wrapper, elements, elementDialectIds); // warmup - } - - private int timeInspection(@NotNull LocalInspectionToolWrapper wrapper, @NotNull List elements, @NotNull Set elementDialectIds) { - long start = System.currentTimeMillis(); - InspectionEngine.inspectElements(Collections.singletonList(wrapper), getFile(), InspectionManager.getInstance(getProject()), true, true, new EmptyProgressIndicator(), elements, - elementDialectIds); - return (int)(System.currentTimeMillis() - start); - } - - public void testTypingLatencyForAllInspectionsPerformance() throws Throwable { - @NonNls String filePath = "/psi/resolve/ThinletBig.java"; - - //Collection tools = getInspectionsFor(JavaLanguage.INSTANCE); - //enableInspectionTools(tools.toArray(new InspectionProfileEntry[tools.size()])); - - configureByFile(filePath); - - //type(' '); - //CompletionContributor.forLanguage(getFile().getLanguage()); - //long s = System.currentTimeMillis(); - //highlightErrors(); - //long e = System.currentTimeMillis(); - //System.out.println("Hi elapsed: "+(e-s)); - - final DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject()); - int N = Math.max(5, Timings.adjustAccordingToMySpeed(80, false)); - System.out.println("N = " + N); - final long[] interruptTimes = new long[N]; - LossyEncodingInspection encodingInspection = new LossyEncodingInspection(); - LocalInspectionToolWrapper wrapper = new LocalInspectionToolWrapper(encodingInspection); - codeAnalyzer.setUpdateByTimerEnabled(false); - ((PsiDocumentManagerBase)PsiDocumentManager.getInstance(getProject())).disableBackgroundCommit(getTestRootDisposable()); - - // position caret away from line start to disable smart backspace heuristics which does commit document - int offset = getEditor().getDocument().getText().indexOf("{ ")+1; - getEditor().getCaretModel().moveToOffset(offset); - - for (int i = 0; i < N; i++) { - type(' '); - - System.out.println("i = " + i); - //codeAnalyzer.restart(); - //final int finalI = i; - //final long start = System.currentTimeMillis(); - try { - //PsiFile file = getFile(); - //Editor editor = getEditor(); - //Project project = file.getProject(); - //CodeInsightTestFixtureImpl.ensureIndexesUpToDate(project); - //TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(editor); - //PsiDocumentManager.getInstance(myProject).commitAllDocuments(); - - inspect(wrapper); - } - catch (ProcessCanceledException ignored) { - } - backspace(); - } - - //long mean = ArrayUtil.averageAmongMedians(interruptTimes, 3); - //long avg = Arrays.stream(interruptTimes).sum() / interruptTimes.length; - //long max = Arrays.stream(interruptTimes).max().getAsLong(); - //long min = Arrays.stream(interruptTimes).min().getAsLong(); - //System.out.println("Average among the N/3 median times: " + mean + "ms; max: "+max+"; min:"+min+"; avg: "+avg); - //assertTrue(mean < 10); - } - - public void testResolveAllAfterTypingLatencyPerformance() throws Throwable { - @NonNls String filePath = "/psi/resolve/ThinletBig.java"; - - //Collection tools = getInspectionsFor(JavaLanguage.INSTANCE); - //enableInspectionTools(tools.toArray(new InspectionProfileEntry[tools.size()])); - - configureByFile(filePath); - - //type(' '); - //CompletionContributor.forLanguage(getFile().getLanguage()); - //long s = System.currentTimeMillis(); - //highlightErrors(); - //long e = System.currentTimeMillis(); - //System.out.println("Hi elapsed: "+(e-s)); - - final DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject()); - int N = Math.max(5, Timings.adjustAccordingToMySpeed(80, false)); - System.out.println("N = " + N); - final long[] interruptTimes = new long[N]; - LossyEncodingInspection encodingInspection = new LossyEncodingInspection(); - LocalInspectionToolWrapper wrapper = new LocalInspectionToolWrapper(encodingInspection); - codeAnalyzer.setUpdateByTimerEnabled(false); - ((PsiDocumentManagerBase)PsiDocumentManager.getInstance(getProject())).disableBackgroundCommit(getTestRootDisposable()); - - // position caret away from line start to disable smart backspace heuristics which does commit document - int offset = getEditor().getDocument().getText().indexOf("{ ")+1; - getEditor().getCaretModel().moveToOffset(offset); - - for (int i = 0; i < N; i++) { - type(' '); - - System.out.println("i = " + i); - //codeAnalyzer.restart(); - //final int finalI = i; - //final long start = System.currentTimeMillis(); - try { - //PsiFile file = getFile(); - //Editor editor = getEditor(); - //Project project = file.getProject(); - //CodeInsightTestFixtureImpl.ensureIndexesUpToDate(project); - //TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(editor); - //PsiDocumentManager.getInstance(myProject).commitAllDocuments(); - PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); - //getFile().accept(new PsiRecursiveElementWalkingVisitor() { - // @Override - // public void visitElement(PsiElement element) { - // for (PsiReference reference : element.getReferences()) { - // reference.resolve(); - // } - // super.visitElement(element); - // } - //}); - } - catch (ProcessCanceledException ignored) { - } - backspace(); - } - - //long mean = ArrayUtil.averageAmongMedians(interruptTimes, 3); - //long avg = Arrays.stream(interruptTimes).sum() / interruptTimes.length; - //long max = Arrays.stream(interruptTimes).max().getAsLong(); - //long min = Arrays.stream(interruptTimes).min().getAsLong(); - //System.out.println("Average among the N/3 median times: " + mean + "ms; max: "+max+"; min:"+min+"; avg: "+avg); - //assertTrue(mean < 10); - } - */ - private static void startCPUProfiling() { try { Class aClass = Class.forName("com.intellij.util.ProfilingUtil"); - Method method = ReflectionUtil.getDeclaredMethod(aClass, "startCPUProfiling"); + Method method = ObjectUtils.assertNotNull(ReflectionUtil.getDeclaredMethod(aClass, "startCPUProfiling")); method.invoke(null); } catch (Exception e) { @@ -1932,7 +1769,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { private static void stopCPUProfiling() { try { Class aClass = Class.forName("com.intellij.util.ProfilingUtil"); - Method method = ReflectionUtil.getDeclaredMethod(aClass, "stopCPUProfiling"); + Method method = ObjectUtils.assertNotNull(ReflectionUtil.getDeclaredMethod(aClass, "stopCPUProfiling")); method.invoke(null); } catch (Exception e) { @@ -1943,7 +1780,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { private static String captureCPUSnapshot() { try { Class aClass = Class.forName("com.intellij.util.ProfilingUtil"); - Method method = ReflectionUtil.getDeclaredMethod(aClass, "captureCPUSnapshot"); + Method method = ObjectUtils.assertNotNull(ReflectionUtil.getDeclaredMethod(aClass, "captureCPUSnapshot")); return (String)method.invoke(null); } catch (Exception e) { @@ -2079,7 +1916,9 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { Project project = file.getProject(); CodeInsightTestFixtureImpl.ensureIndexesUpToDate(project); TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(editor); - codeAnalyzer.runPasses(file, editor.getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, true, () -> type(' ')); + Runnable callbackWhileWaiting = () -> type(' '); + codeAnalyzer + .runPasses(file, editor.getDocument(), Collections.singletonList(textEditor), ArrayUtil.EMPTY_INT_ARRAY, true, callbackWhileWaiting); } catch (ProcessCanceledException ignored) { return; @@ -2105,7 +1944,8 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { Project project = file.getProject(); CodeInsightTestFixtureImpl.ensureIndexesUpToDate(project); TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(editor); - codeAnalyzer.runPasses(file, editor.getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, true, () -> type(' ')); + Runnable callbackWhileWaiting = () -> type(' '); + codeAnalyzer.runPasses(file, editor.getDocument(), Collections.singletonList(textEditor), ArrayUtil.EMPTY_INT_ARRAY, true, callbackWhileWaiting); } catch (ProcessCanceledException ignored) { codeAnalyzer.waitForTermination(); @@ -2250,17 +2090,19 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(getEditor()); final DaemonCodeAnalyzerImpl di = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject()); final AtomicReference indicator = new AtomicReference<>(); + Runnable callbackWhileWaiting = () -> { + if (indicator.get() == null) { + indicator.set(di.getUpdateProgress()); + } + assertSame(indicator.get(), di.getUpdateProgress()); + caretRight(); + if (getEditor().getCaretModel().getOffset() == getEditor().getDocument().getTextLength()-1) { + getEditor().getCaretModel().moveToOffset(0); + } + }; final List errors = filter( - di.runPasses(getFile(), getEditor().getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, false, () -> { - if (indicator.get() == null) { - indicator.set(di.getUpdateProgress()); - } - assertSame(indicator.get(), di.getUpdateProgress()); - caretRight(); - if (getEditor().getCaretModel().getOffset() == getEditor().getDocument().getTextLength()-1) { - getEditor().getCaretModel().moveToOffset(0); - } - }), HighlightSeverity.ERROR); + di.runPasses(getFile(), getEditor().getDocument(), Collections.singletonList(textEditor), ArrayUtil.EMPTY_INT_ARRAY, false, + callbackWhileWaiting), HighlightSeverity.ERROR); assertEquals(1, errors.size()); assertEquals("Incompatible types. Found: 'java.lang.String', required: 'int'", errors.get(0).getDescription()); @@ -2272,13 +2114,11 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { final AtomicBoolean run = new AtomicBoolean(); final int SLEEP = 20000; ExternalAnnotator annotator = new ExternalAnnotator() { - @Nullable @Override public Integer collectInformation(@NotNull PsiFile file) { return 0; } - @Nullable @Override public Integer doAnnotate(final Integer collectedInfo) { TimeoutUtil.sleep(SLEEP); @@ -2334,7 +2174,8 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { application.doNotSave(false); try { application.saveAll(); - final PsiFile excluded = PsiManager.getInstance(getProject()).findFile(getProject().getWorkspaceFile()); + VirtualFile workspaceFile = ObjectUtils.assertNotNull(getProject().getWorkspaceFile()); + PsiFile excluded = ObjectUtils.assertNotNull(PsiManager.getInstance(getProject()).findFile(workspaceFile)); List errors = highlightErrors(); assertEmpty(errors); @@ -2343,7 +2184,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { assertNull(scope); WriteCommandAction.runWriteCommandAction(getProject(), () -> { - Document document = PsiDocumentManager.getInstance(getProject()).getDocument(excluded); + Document document = ObjectUtils.assertNotNull(PsiDocumentManager.getInstance(getProject()).getDocument(excluded)); document.insertString(0, ""); PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); }); @@ -2519,7 +2360,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { PsiExpressionCodeFragment fragment = JavaCodeFragmentFactory.getInstance(getProject()).createExpressionCodeFragment("+ \"a\"", null, PsiType.getJavaLangObject(getPsiManager(), GlobalSearchScope.allScope(getProject())), true); myFile = fragment; - Document document = PsiDocumentManager.getInstance(getProject()).getDocument(fragment); + Document document = ObjectUtils.assertNotNull(PsiDocumentManager.getInstance(getProject()).getDocument(fragment)); myEditor = EditorFactory.getInstance().createEditor(document, getProject(), StdFileTypes.JAVA, false); ProperTextRange visibleRange = makeEditorWindowVisible(new Point(0, 0), myEditor); @@ -2686,7 +2527,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { public void visitIfStatement(PsiIfStatement statement) { if (statement.getElseBranch() != null) { PsiKeyword keyw = (PsiKeyword)statement.getChildren()[0]; - holder.registerProblem(keyw, "dododo", new StupidQuickFixWhichDoesntCheckItsOwnApplicability()); + holder.registerProblem(keyw, "Dododo", new StupidQuickFixWhichDoesntCheckItsOwnApplicability()); } } }; diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/inlays/JavaParameterNameHintsTest.kt b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/inlays/JavaParameterNameHintsTest.kt index c2f3f8d22524..9c7b87d85563 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/inlays/JavaParameterNameHintsTest.kt +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/inlays/JavaParameterNameHintsTest.kt @@ -320,13 +320,13 @@ class QList { } class QCmp { - void cmpre(E o1, E o2) {} + void cmpre(E oe1, E oq2) {} } public class Test { public void main(QCmp c, QList l) { - c.cmpre(0, /** ddd */3); + c.cmpre(0, /** ddd */3); l.add(1, "uuu"); } } @@ -895,7 +895,120 @@ class Test { """) } - + fun `test if resolved but no hints just return no hints`() { + myFixture.configureByText(JavaFileType.INSTANCE, """ +public class Test { + + public void main() { + foo(1); + } + + void foo(int a) {} + void foo() {} +} +""") + + myFixture.doHighlighting() + + var inlays = getHints() + assert(inlays.size == 1) + + + myFixture.type('\b') + + myFixture.performEditorAction("EditorLeft") + myFixture.doHighlighting() + + inlays = getHints() + assert(inlays.isEmpty()) + } + + fun `test one-char one-digit hints enabled`() { + JavaInlayParameterHintsProvider.getInstance().ignoreOneCharOneDigitHints.set(false) + check(""" +class Test { + void main() { + timeoutExecution(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + void timeoutExecution(int t1, int t2, int t3, int t4, int t5, int t6, int t7, int t8, int t9, int t10) {} +} +""") + } + + fun `test ordered sequential`() { + JavaInlayParameterHintsProvider.getInstance().ignoreOneCharOneDigitHints.set(true) + check(""" +class Test { + void main() { + rect(1, 2, 3, 4); + fromZero(1, 2); + fromOne(1, 2); + fromX(1, 2); + fromX(1); + } + void rect(int x1, int y1, int x2, int y2) {} + void fromZero(int x0, int x1) {} + void fromOne(int x1, int x2) {} + void fromX(int x86, int x87) {} + void fromX(int x86) {} +} +""") + } + + fun `test unordered sequential`() { + JavaInlayParameterHintsProvider.getInstance().ignoreOneCharOneDigitHints.set(true) + check(""" +class Test { + void test() { + unordered(100, 200); + unordered2(100, 200); + } + void unordered(int x1, int x3) {} + void unordered2(int x0, int x3) {} +} +""") + } + + fun `test ordered with varargs`() { + JavaInlayParameterHintsProvider.getInstance().ignoreOneCharOneDigitHints.set(true) + check(""" +class Test { + void test() { + ord(100, 200); + ord2(100, 200); + } + void ord(int x1, int x2, int... others) {} + void ord2(int x0, int x1, int... others) {} +} +""") + } + + fun `test one-char one-digit hints disabled`() { + JavaInlayParameterHintsProvider.getInstance().ignoreOneCharOneDigitHints.set(true) + check(""" +class Test { + void main() { + timeoutExecution(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + void timeoutExecution(int t1, int t2, int t3, int t4, int t5, int t6, int t7, int t8, int t9, int t10) {} +} +""") + } + + fun `test just some unparsable parameter name`() { + JavaInlayParameterHintsProvider.getInstance().ignoreOneCharOneDigitHints.set(true) + check(""" +class Test { + void main() { + check(100); + www(100); + } + void check(int main2toMain) {} + void www(int x86) {} +} +""") + } + fun getHints(): List { val document = myFixture.getDocument(myFixture.file) val manager = ParameterHintsPresentationManager.getInstance() diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/inlays/ToggleInlineHintsActionTest.kt b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/inlays/ToggleInlineHintsActionTest.kt deleted file mode 100644 index 50ce949d23c2..000000000000 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/inlays/ToggleInlineHintsActionTest.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2000-2017 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.java.codeInsight.daemon.inlays - -import com.intellij.codeInsight.hints.isPossibleHintNearOffset -import com.intellij.openapi.editor.ex.EditorSettingsExternalizable -import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase -import org.assertj.core.api.Assertions.assertThat - -class ToggleInlineHintsActionTest : LightCodeInsightFixtureTestCase() { - - private var before: Boolean = false - - override fun setUp() { - super.setUp() - before = EditorSettingsExternalizable.getInstance().isShowParameterNameHints - EditorSettingsExternalizable.getInstance().isShowParameterNameHints = false - } - - override fun tearDown() { - EditorSettingsExternalizable.getInstance().isShowParameterNameHints = before - super.tearDown() - } - - fun `test is enabled near method with possible hints`() { - myFixture.configureByText("A.java", """" -class Test { - Test(int time) {} - void initialize(int loadTime) {} - static void s_test() { - Test test = new Test(10); - test.initialize(10000); - } -} -""") - - val file = myFixture.file - val caretModel = myFixture.editor.caretModel - - assertThat(caretModel.caretCount).isEqualTo(4) - - editor.caretModel.allCarets.forEach { - val hintCanBeAtCaret = isPossibleHintNearOffset(file, it.offset) - assertThat(hintCanBeAtCaret).isTrue() - } - } - - fun `test is disabled in random places`() { - myFixture.configureByText("A.java", """" -class Test { - static void s_test() { - int a = 2; - List list = null; - } -} -""") - - val file = myFixture.file - val caretModel = myFixture.editor.caretModel - - assertThat(caretModel.caretCount).isEqualTo(3) - - editor.caretModel.allCarets.forEach { - val hintCanBeAtCaret = isPossibleHintNearOffset(file, it.offset) - assertThat(hintCanBeAtCaret).isFalse() - } - } - -} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/CreateMethodFromUsageTest.groovy b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/CreateMethodFromUsageTest.groovy index 4e71ece1a90c..d13c386533d8 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/CreateMethodFromUsageTest.groovy +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/CreateMethodFromUsageTest.groovy @@ -26,6 +26,8 @@ import com.intellij.openapi.editor.actionSystem.EditorActionManager import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiMethod import com.intellij.psi.PsiModifier +import com.intellij.psi.codeStyle.CodeStyleSettings +import com.intellij.psi.codeStyle.CodeStyleSettingsManager import com.intellij.psi.util.PsiTreeUtil /** * @author ven @@ -173,6 +175,53 @@ class A { } + void "test use fully qualified names with conflicting imports"() { + final CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(project); + def fqClassNames = settings.useFqClassNames + try { + settings.setUseFqClassNames(true) + configureFromFileText "a.java", """ + import java.awt.List; + class A { + void m(java.util.List list){ + foo(list); + } + } + """ + TemplateManagerImpl.setTemplateTesting(project, testRootDisposable) + doAction("Create method 'foo'") + def state = TemplateManagerImpl.getTemplateState(getEditor()) + + def document = getEditor().getDocument() + def offset = getEditor().getCaretModel().getOffset() + + ApplicationManager.application.runWriteAction { + def method = PsiTreeUtil.getParentOfType(getFile().findElementAt(offset), PsiMethod.class) + method.getModifierList().setModifierProperty(PsiModifier.STATIC, false) + PsiDocumentManager.getInstance(getFile().project).commitDocument(document) + } + + state.gotoEnd(false) + + checkResultByText """ + import java.awt.List; + class A { + void m(java.util.List list){ + foo(list); + } + + private void foo(java.util.List list) { + + } + } + """ + } + finally { + settings.setUseFqClassNames(fqClassNames) + } + + } + @Override protected String getBasePath() { return "/codeInsight/daemonCodeAnalyzer/quickFix/createMethodFromUsage" diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/ReplaceWithTernaryOperatorTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/ReplaceWithTernaryOperatorTest.java index 1b1f25fa3658..5083b69884b8 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/ReplaceWithTernaryOperatorTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/ReplaceWithTernaryOperatorTest.java @@ -26,7 +26,9 @@ public class ReplaceWithTernaryOperatorTest extends LightQuickFixParameterizedTe @NotNull @Override protected LocalInspectionTool[] configureLocalInspectionTools() { - return new LocalInspectionTool[]{new DataFlowInspection(), new NullableStuffInspection()}; + DataFlowInspection dataFlowInspection = new DataFlowInspection(); + dataFlowInspection.SUGGEST_NULLABLE_ANNOTATIONS = true; + return new LocalInspectionTool[]{dataFlowInspection, new NullableStuffInspection()}; } public void test() throws Exception { diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/SurroundWithIfFixTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/SurroundWithIfFixTest.java index dc1954bf982c..4cc2f5eff237 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/SurroundWithIfFixTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/SurroundWithIfFixTest.java @@ -25,7 +25,9 @@ public class SurroundWithIfFixTest extends LightQuickFixParameterizedTestCase { @NotNull @Override protected LocalInspectionTool[] configureLocalInspectionTools() { - return new LocalInspectionTool[]{new DataFlowInspection()}; + DataFlowInspection inspection = new DataFlowInspection(); + inspection.SUGGEST_NULLABLE_ANNOTATIONS = true; + return new LocalInspectionTool[]{inspection}; } public void test() throws Exception { diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/SurroundWithRequireNonNullFixTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/SurroundWithRequireNonNullFixTest.java new file mode 100644 index 000000000000..733b7908e318 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/SurroundWithRequireNonNullFixTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 2000-2017 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.java.codeInsight.daemon.quickFix; + +import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase; +import com.intellij.codeInspection.LocalInspectionTool; +import com.intellij.codeInspection.dataFlow.DataFlowInspection; +import org.jetbrains.annotations.NotNull; + +public class SurroundWithRequireNonNullFixTest extends LightQuickFixParameterizedTestCase { + @NotNull + @Override + protected LocalInspectionTool[] configureLocalInspectionTools() { + DataFlowInspection inspection = new DataFlowInspection(); + inspection.SUGGEST_NULLABLE_ANNOTATIONS = true; + return new LocalInspectionTool[]{inspection}; + } + + public void test() throws Exception { + doAllTests(); + } + + @Override + protected String getBasePath() { + return "/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull"; + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java index 44249b44e796..34781a8755d9 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection8Test.java @@ -74,6 +74,17 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase { "public class Assert {" + " public static void assertTrue(boolean b) {}" + "}"); + addGuava(); + doTest(); + } + + private void addGuava() { + myFixture.addClass("package com.google.common.base;\n" + + "\n" + + "public interface Supplier { T get();}\n"); + myFixture.addClass("package com.google.common.base;\n" + + "\n" + + "public interface Function { T apply(F input);}\n"); myFixture.addClass("package com.google.common.base;\n" + "\n" + "public abstract class Optional {\n" + @@ -82,8 +93,12 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase { " public static Optional fromNullable(T ref) {}\n" + " public abstract T get();\n" + " public abstract boolean isPresent();\n" + + " public abstract T orNull();\n" + + " public abstract T or(Supplier supplier);\n" + + " public abstract Optional transform(Function fn);\n" + + " public abstract T or(T val);\n" + + " public abstract java.util.Optional toJavaUtil();\n" + "}"); - doTest(); } public void testPrimitiveInVoidLambda() { doTest(); } @@ -154,12 +169,21 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase { public void testCapturedWildcardNotNull() { doTest(); } public void testVarargNotNull() { doTestWithCustomAnnotations(); } + public void testIgnoreNullabilityOnPrimitiveCast() { doTestWithCustomAnnotations();} public void testArrayComponentAndMethodAnnotationConflict() { setupCustomAnnotations("withTypeUse", "{ElementType.METHOD, ElementType.TYPE_USE}", myFixture); doTest(); } + public void testLambdaInlining() { doTest(); } + + public void testOptionalInlining() { + addGuava(); + doTest(); + } + public void testStreamInlining() { doTest(); } + public void testMethodVsExpressionTypeAnnotationConflict() { setupCustomAnnotations("withTypeUse", "{ElementType.METHOD, ElementType.TYPE_USE}", myFixture); doTest(); diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java index 219195c736d5..728294cbe954 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java @@ -532,4 +532,5 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase { assertEmpty(ContainerUtil.findAll(myFixture.getAvailableIntentions(), i -> i.getText().contains("null"))); } + public void testEmptySingletonMap() {doTest();} } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/Java9AccessCanBeTightenedTest.kt b/java/java-tests/testSrc/com/intellij/java/codeInspection/Java9AccessCanBeTightenedTest.kt index 360aa8ad7988..e347629a422c 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/Java9AccessCanBeTightenedTest.kt +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/Java9AccessCanBeTightenedTest.kt @@ -66,8 +66,7 @@ class Java9AccessCanBeTightenedTest : LightCodeInsightFixtureTestCase() { val sourceFile = FileUtil.findFirstThatExist("$path/module-info.java") val text = String(FileUtil.loadFileText(sourceFile!!)) - val moduleInfo = myFixture.configureByText("module-info.java", text) - myFixture.allowTreeAccessForFile(moduleInfo.virtualFile) + myFixture.configureByText("module-info.java", text) } private fun createGlobalTool() = VisibilityInspection().apply { diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/Java9RedundantRequiresStatementTest.kt b/java/java-tests/testSrc/com/intellij/java/codeInspection/Java9RedundantRequiresStatementTest.kt index 85b1fb96f31e..5bbde9bbb288 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/Java9RedundantRequiresStatementTest.kt +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/Java9RedundantRequiresStatementTest.kt @@ -18,14 +18,13 @@ package com.intellij.java.codeInspection import com.intellij.analysis.AnalysisScope import com.intellij.codeInspection.ex.GlobalInspectionToolWrapper import com.intellij.codeInspection.java19modules.Java9RedundantRequiresStatementInspection +import com.intellij.java.testFramework.fixtures.LightJava9ModulesCodeInsightFixtureTestCase +import com.intellij.java.testFramework.fixtures.MultiModuleJava9ProjectDescriptor.ModuleDescriptor import com.intellij.openapi.application.ex.PathManagerEx import com.intellij.openapi.vfs.VirtualFile import com.intellij.testFramework.InspectionTestUtil import com.intellij.testFramework.createGlobalContextForTool -import com.intellij.java.testFramework.fixtures.LightJava9ModulesCodeInsightFixtureTestCase -import com.intellij.java.testFramework.fixtures.MultiModuleJava9ProjectDescriptor.ModuleDescriptor import org.intellij.lang.annotations.Language -import org.jetbrains.annotations.NonNls /** * @author Pavel.Dolgov @@ -74,7 +73,6 @@ class Java9RedundantRequiresStatementTest : LightJava9ModulesCodeInsightFixtureT mainModule("module MAIN { requires M2; }") } - fun testRequiresManyModulesAllPackagesImported() { mainClass("org.example.m2.*", "org.example.m4.*", "org.example.m6.*", "org.example.m7.*") mainModule("module MAIN { requires M2; requires M4; requires M6; requires M7; }") @@ -100,7 +98,7 @@ class Java9RedundantRequiresStatementTest : LightJava9ModulesCodeInsightFixtureT mainModule("module MAIN { requires java.base; }") } - private fun mainModule(@Language("JAVA") @NonNls text: String) { + private fun mainModule(@Language("JAVA") text: String) { addFile("module-info.java", text, ModuleDescriptor.MAIN) val toolWrapper = GlobalInspectionToolWrapper(Java9RedundantRequiresStatementInspection()) @@ -113,35 +111,28 @@ class Java9RedundantRequiresStatementTest : LightJava9ModulesCodeInsightFixtureT myFixture.checkHighlighting() // make sure the imports work } - private fun add(@NonNls packageName: String, - @NonNls className: String, - module: ModuleDescriptor, - @NonNls body: String = "", - vararg @NonNls imports: String) { - val importsText = imports.map { "import ${it};" }.joinToString("\n") + private fun add(packageName: String, className: String, module: ModuleDescriptor, body: String = "", vararg imports: String) { + val importsText = imports.joinToString("\n") { "import ${it};" } addFile("${packageName.replace('.', '/')}/${className}.java", """ -package ${packageName}; -${importsText} -public class ${className} -{ - ${body} -}""", module = module) + package ${packageName}; + ${importsText} + public class ${className} { + ${body} + }""".trimIndent(), module = module) } private var myMainClassFile: VirtualFile? = null - private fun mainClass(vararg @NonNls imports: String, - @NonNls staticImports: List = emptyList()) { - val importsText = imports.map { "import ${it};" }.joinToString("\n") - val staticImportsText = staticImports.map { "import static ${it};" }.joinToString("\n") + private fun mainClass(vararg imports: String, staticImports: List = emptyList()) { + val importsText = imports.joinToString("\n") { "import ${it};" } + val staticImportsText = staticImports.joinToString("\n") { "import static ${it};" } myMainClassFile = addFile("org.example.main/Main.java", """ -package org.example.main; -${importsText} -${staticImportsText} -public class Main { - void main() {} -} -""", module = ModuleDescriptor.MAIN) + package org.example.main; + ${importsText} + ${staticImportsText} + public class Main { + void main() {} + }""".trimIndent(), module = ModuleDescriptor.MAIN) } override fun getTestDataPath() = PathManagerEx.getTestDataPath() + "/inspection/redundantRequiresStatement/" diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/RedundantLambdaParameterTypeInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/RedundantLambdaParameterTypeInspectionTest.java index 1121070a6c02..82b3b3955500 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/RedundantLambdaParameterTypeInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/RedundantLambdaParameterTypeInspectionTest.java @@ -26,7 +26,7 @@ import java.util.List; public class RedundantLambdaParameterTypeInspectionTest extends LightCodeInsightFixtureTestCase { private RedundantLambdaParameterTypeInspection myInspection = new RedundantLambdaParameterTypeInspection(); - private final String myIntentionName = "Remove redundant parameter types"; + private static final String ourIntentionName = "Remove redundant parameter types"; @Override protected String getBasePath() { @@ -91,20 +91,24 @@ public class RedundantLambdaParameterTypeInspectionTest extends LightCodeInsight assertIntentionNotAvailable(); } - public void _testInChain() { // disabled till the functionality is available + public void testInChain() { // disabled till the functionality is available doTest(); } + public void testNotApplicableDueToChainedCall() throws Exception { + assertIntentionNotAvailable(); + } + private void doTest() { myFixture.configureByFiles(getTestName(false) + ".java"); - final IntentionAction singleIntention = myFixture.findSingleIntention(myIntentionName); + final IntentionAction singleIntention = myFixture.findSingleIntention(ourIntentionName); myFixture.launchAction(singleIntention); myFixture.checkResultByFile(getTestName(false) + ".java", getTestName(false) + "_after.java", true); } private void assertIntentionNotAvailable() { myFixture.configureByFiles(getTestName(false) + ".java"); - final List intentionActions = myFixture.filterAvailableIntentions(myIntentionName); - assertEmpty(myIntentionName + " is not expected", intentionActions); + final List intentionActions = myFixture.filterAvailableIntentions(ourIntentionName); + assertEmpty(ourIntentionName + " is not expected", intentionActions); } } \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/compiler/CompilerReferencesFindUsagesTest.java b/java/java-tests/testSrc/com/intellij/java/compiler/CompilerReferencesFindUsagesTest.java index 8c23a529c31c..b43f5cede9ed 100644 --- a/java/java-tests/testSrc/com/intellij/java/compiler/CompilerReferencesFindUsagesTest.java +++ b/java/java-tests/testSrc/com/intellij/java/compiler/CompilerReferencesFindUsagesTest.java @@ -28,6 +28,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; import com.intellij.psi.impl.source.tree.injected.MyTestInjector; +import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.searches.FunctionalExpressionSearch; import com.intellij.psi.search.searches.MethodReferencesSearch; import com.intellij.psi.search.searches.ReferencesSearch; @@ -38,6 +39,7 @@ import com.intellij.util.Consumer; import org.jetbrains.annotations.NotNull; import java.util.Arrays; +import java.util.Collection; @SkipSlowTestLocally public class CompilerReferencesFindUsagesTest extends DaemonAnalyzerTestCase { @@ -79,54 +81,54 @@ public class CompilerReferencesFindUsagesTest extends DaemonAnalyzerTestCase { public void testMethodUsageOnGetter() throws Exception { configureByFiles(getName(), getName() + "/Foo.java", getName() + "/FooFactory.java", getName() + "/Bar.java"); PsiMethod methodToSearch = findClass("Foo").findMethodsByName("someMethod", false)[0]; - assertOneElement(MethodReferencesSearch.search(methodToSearch).findAll()); + assertOneElement(searchMethodReferences(methodToSearch)); myCompilerTester.rebuild(); - assertOneElement(MethodReferencesSearch.search(methodToSearch).findAll()); + assertOneElement(searchMethodReferences(methodToSearch)); } public void testMethodUsageInClassHierarchy() throws Exception { configureByFiles(getName(), getName() + "/Bar.java", getName() + "/Foo.java"); PsiMethod methodToSearch = findClass("Foo").findMethodsByName("someMethod", false)[0]; - assertOneElement(MethodReferencesSearch.search(methodToSearch).findAll()); + assertOneElement(searchMethodReferences(methodToSearch)); myCompilerTester.rebuild(); - assertOneElement(MethodReferencesSearch.search(methodToSearch).findAll()); + assertOneElement(searchMethodReferences(methodToSearch)); } public void testMethodUsageInClassHierarchy2() throws Exception { configureByFiles(getName(), getName() + "/Bar.java", getName() + "/Foo.java"); PsiMethod methodToSearch = myJavaFacade.findClass(CommonClassNames.JAVA_LANG_RUNNABLE).findMethodsByName("run", false)[0]; - assertOneElement(MethodReferencesSearch.search(methodToSearch).findAll()); + assertOneElement(searchMethodReferences(methodToSearch)); myCompilerTester.rebuild(); - assertOneElement(MethodReferencesSearch.search(methodToSearch).findAll()); + assertOneElement(searchMethodReferences(methodToSearch)); } public void testLibMethodUsage() throws Exception { configureByFile(getName() + "/Foo.java"); myCompilerTester.rebuild(); PsiMethod methodToSearch = myJavaFacade.findClass(CommonClassNames.JAVA_UTIL_COLLECTIONS).findMethodsByName("emptyList", false)[0]; - assertOneElement(MethodReferencesSearch.search(methodToSearch).findAll()); + assertOneElement(searchMethodReferences(methodToSearch)); } public void testLibClassUsage() throws Exception { configureByFile(getName() + "/Foo.java"); myCompilerTester.rebuild(); PsiClass classForSearch = myJavaFacade.findClass("java.lang.System"); - assertOneElement(ReferencesSearch.search(classForSearch).findAll()); + assertOneElement(searchReferences(classForSearch)); } public void testLibClassInJavaDocUsage() throws Exception { configureByFile(getName() + "/Foo.java"); myCompilerTester.rebuild(); PsiClass classForSearch = myJavaFacade.findClass("java.lang.System"); - assertOneElement(ReferencesSearch.search(classForSearch).findAll()); + assertOneElement(searchReferences(classForSearch)); } public void testCompileTimeConstFindUsages() throws Exception { configureByFiles(getName(), getName() + "/Bar.java", getName() + "/Foo.java"); PsiField classForSearch = findClass("Foo").findFieldByName("CONST", false); - PsiElement referenceBefore = assertOneElement(ReferencesSearch.search(classForSearch).findAll()).getElement(); + PsiElement referenceBefore = assertOneElement(searchReferences(classForSearch)).getElement(); myCompilerTester.rebuild(); - PsiElement referenceAfter = assertOneElement(ReferencesSearch.search(classForSearch).findAll()).getElement(); + PsiElement referenceAfter = assertOneElement(searchReferences(classForSearch)).getElement(); assertTrue(referenceBefore == referenceAfter); } @@ -135,7 +137,7 @@ public class CompilerReferencesFindUsagesTest extends DaemonAnalyzerTestCase { configureByFile(getName() + "/Foo.java"); myCompilerTester.rebuild(); PsiClass classForSearch = myJavaFacade.findClass("java.lang.System"); - PsiReference reference = assertOneElement(ReferencesSearch.search(classForSearch).findAll()); + PsiReference reference = assertOneElement(searchReferences(classForSearch)); assertTrue(InjectedLanguageManager.getInstance(getProject()).isInjectedFragment(reference.getElement().getContainingFile())); } @@ -163,9 +165,9 @@ public class CompilerReferencesFindUsagesTest extends DaemonAnalyzerTestCase { public void testOverloadedMethods() throws Exception { configureByFiles(getName(), getName() + "/Foo.java", getName() + "/A.java", getName() + "/B.java"); PsiMethod[] methodsToSearch = findClass("Foo").findMethodsByName("bar", false); - Arrays.stream(methodsToSearch).forEach((m) -> assertSize(2, MethodReferencesSearch.search(m, false).findAll())); + Arrays.stream(methodsToSearch).forEach((m) -> assertSize(2, searchMethodReferences(m))); myCompilerTester.rebuild(); - Arrays.stream(methodsToSearch).forEach((m) -> assertSize(2, MethodReferencesSearch.search(m, false).findAll())); + Arrays.stream(methodsToSearch).forEach((m) -> assertSize(2, searchMethodReferences(m))); } private void doTestRunnableFindUsagesWithExcludesConfiguration(@NotNull Consumer excludesConfigurationPatcher, @@ -182,4 +184,12 @@ public class CompilerReferencesFindUsagesTest extends DaemonAnalyzerTestCase { excludesConfiguration.removeAllExcludeEntryDescriptions(); } } + + private Collection searchMethodReferences(@NotNull PsiMethod method) { + return MethodReferencesSearch.search(method, GlobalSearchScope.projectScope(getProject()), false).findAll(); + } + + private Collection searchReferences(@NotNull PsiElement element) { + return ReferencesSearch.search(element, GlobalSearchScope.projectScope(getProject())).findAll(); + } } diff --git a/java/java-tests/testSrc/com/intellij/java/find/impl/FindManagerTest.java b/java/java-tests/testSrc/com/intellij/java/find/impl/FindManagerTest.java index 62a4bfd07567..72aabdd6b84a 100644 --- a/java/java-tests/testSrc/com/intellij/java/find/impl/FindManagerTest.java +++ b/java/java-tests/testSrc/com/intellij/java/find/impl/FindManagerTest.java @@ -23,6 +23,8 @@ import com.intellij.find.impl.FindResultImpl; import com.intellij.find.replaceInProject.ReplaceInProjectManager; import com.intellij.lang.properties.IProperty; import com.intellij.lang.properties.psi.PropertiesFile; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.LangDataKeys; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.Result; import com.intellij.openapi.application.ex.PathManagerEx; @@ -31,10 +33,13 @@ import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileTypes.FileTypes; import com.intellij.openapi.fileTypes.PlainTextFileType; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.util.ProgressIndicatorBase; import com.intellij.openapi.project.DumbServiceImpl; +import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.ProperTextRange; @@ -49,14 +54,12 @@ import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.GlobalSearchScopesCore; import com.intellij.psi.search.LocalSearchScope; +import com.intellij.psi.search.SearchScope; import com.intellij.psi.search.scope.packageSet.NamedScope; import com.intellij.psi.search.scope.packageSet.PackageSet; import com.intellij.psi.search.scope.packageSet.PackageSetFactory; import com.intellij.psi.search.scope.packageSet.ParsingException; -import com.intellij.testFramework.IdeaTestUtil; -import com.intellij.testFramework.LightVirtualFile; -import com.intellij.testFramework.PlatformTestUtil; -import com.intellij.testFramework.PsiTestUtil; +import com.intellij.testFramework.*; import com.intellij.testFramework.fixtures.TempDirTestFixture; import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl; import com.intellij.testFramework.fixtures.impl.TempDirTestFixtureImpl; @@ -1041,4 +1044,41 @@ public class FindManagerTest extends DaemonAnalyzerTestCase { assertTrue(FindUtil.replace(myProject, myEditor, 0, model)); assertEquals(expectedResult, myEditor.getDocument().getText()); } + + public void testDataContextProcessing() { + initProject("findInPath", "src"); + VirtualFile directory = mySourceDirs[0].findFileByRelativePath("x"); + VirtualFile file = directory.findChild("dd.properties"); + assertNotNull(file); + SearchScope scope = GlobalSearchScope.filesScope(myProject, ContainerUtil.list(directory)); + Module module = ModuleManager.getInstance(myProject).getModules()[0]; + String moduleName = module.getName(); + String dirName = directory.getPresentableUrl(); + + FindModel model = new FindModel(); + model.setDirectoryName("initialDirectoryName"); + model.setModuleName("initialModuleName"); + model.setProjectScope(false); + model.setCustomScopeName("initialScopeName"); + + checkContext(model, myProject, directory, module, false, null, moduleName, false); + checkContext(model, myProject, directory, null, false, dirName, moduleName, false);//prev module state + checkContext(model, myProject, null, null, true, dirName, moduleName, false);//prev module and dir state + model.setCustomScope(scope); + model.setCustomScope(true); + checkContext(model, myProject, null, null, false, dirName, moduleName, true);//prev module and dir state + } + + private static void checkContext(FindModel model, Project project, VirtualFile directory, Module module, + boolean shouldBeProjectScope, String expectedDirectoryName, String expectedModuleName, boolean shouldBeCustomScope) { + MapDataContext dataContext = new MapDataContext(); + dataContext.put(CommonDataKeys.PROJECT, project); + dataContext.put(CommonDataKeys.VIRTUAL_FILE, directory); + dataContext.put(LangDataKeys.MODULE_CONTEXT, module); + FindInProjectUtil.setDirectoryName(model, dataContext); + assertEquals(shouldBeProjectScope, model.isProjectScope()); + assertEquals(expectedDirectoryName, model.getDirectoryName()); + assertEquals(expectedModuleName, model.getModuleName()); + assertEquals(shouldBeCustomScope, model.isCustomScope()); + } } diff --git a/java/java-tests/testSrc/com/intellij/java/propertyBased/AbstractApplyAndRevertTestCase.java b/java/java-tests/testSrc/com/intellij/java/propertyBased/AbstractApplyAndRevertTestCase.java index 548fb0035073..af1189c66b6c 100644 --- a/java/java-tests/testSrc/com/intellij/java/propertyBased/AbstractApplyAndRevertTestCase.java +++ b/java/java-tests/testSrc/com/intellij/java/propertyBased/AbstractApplyAndRevertTestCase.java @@ -55,7 +55,7 @@ import com.intellij.psi.search.GlobalSearchScope; import com.intellij.testFramework.*; import com.intellij.util.ThrowableRunnable; import com.intellij.util.containers.ContainerUtil; -import slowCheck.Generator; +import jetCheck.Generator; import java.io.File; import java.util.ArrayList; diff --git a/java/java-tests/testSrc/com/intellij/java/propertyBased/CompletionConsistencyTest.java b/java/java-tests/testSrc/com/intellij/java/propertyBased/CompletionConsistencyTest.java deleted file mode 100644 index 29cbfd7d3978..000000000000 --- a/java/java-tests/testSrc/com/intellij/java/propertyBased/CompletionConsistencyTest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2000-2017 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.java.propertyBased; - -import com.intellij.testFramework.SkipSlowTestLocally; -import com.intellij.testFramework.propertyBased.InvokeCompletion; -import com.intellij.testFramework.propertyBased.MadTestingAction; -import com.intellij.testFramework.propertyBased.MadTestingUtil; -import com.intellij.util.SystemProperties; -import slowCheck.CheckerSettings; -import slowCheck.Generator; -import slowCheck.PropertyChecker; - -/** - * @author peter - */ -@SkipSlowTestLocally -public class CompletionConsistencyTest extends AbstractApplyAndRevertTestCase { - - public void testCompletionConsistency() { - CheckerSettings settings = CheckerSettings.DEFAULT_SETTINGS; - PropertyChecker.forAll(settings.withIterationCount(20), psiJavaFiles(), file -> { - System.out.println("for file: " + file.getVirtualFile().getPresentableUrl()); - PropertyChecker.forAll(settings.withIterationCount(10), Generator.listsOf(InvokeCompletion.completions(file, new JavaCompletionPolicy())), list -> { - MadTestingUtil.changeAndRevert(myProject, () -> MadTestingAction.runActions(list)); - return true; - }); - return true; - }); - } - - @Override - protected String getTestDataPath() { - return SystemProperties.getUserHome() + "/IdeaProjects/univocity-parsers"; - } -} diff --git a/java/java-tests/testSrc/com/intellij/java/propertyBased/JavaCodeInsightSanityTest.java b/java/java-tests/testSrc/com/intellij/java/propertyBased/JavaCodeInsightSanityTest.java index 4d597eca0bf4..9173a715c099 100644 --- a/java/java-tests/testSrc/com/intellij/java/propertyBased/JavaCodeInsightSanityTest.java +++ b/java/java-tests/testSrc/com/intellij/java/propertyBased/JavaCodeInsightSanityTest.java @@ -18,10 +18,11 @@ package com.intellij.java.propertyBased; import com.intellij.openapi.application.PathManager; import com.intellij.psi.PsiFile; import com.intellij.testFramework.SkipSlowTestLocally; -import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase; +import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; import com.intellij.testFramework.propertyBased.*; import org.jetbrains.annotations.NotNull; -import slowCheck.*; +import jetCheck.Generator; +import jetCheck.PropertyChecker; import java.util.function.Function; @@ -29,15 +30,16 @@ import java.util.function.Function; * @author peter */ @SkipSlowTestLocally -public class JavaCodeInsightSanityTest extends LightPlatformCodeInsightFixtureTestCase { +public class JavaCodeInsightSanityTest extends LightCodeInsightFixtureTestCase { public void testRandomActivity() { MadTestingUtil.enableAllInspections(getProject(), getTestRootDisposable()); Function> fileActions = file -> Generator.anyOf(InvokeIntention.randomIntentions(file, new JavaIntentionPolicy()), InvokeCompletion.completions(file, new JavaCompletionPolicy()), + Generator.constant(new StripTestDataMarkup(file)), DeleteRange.psiRangeDeletions(file)); - PropertyChecker.forAll(actionsOnJavaFiles(fileActions), FileWithActions::runActions); + PropertyChecker.forAll(actionsOnJavaFiles(fileActions)).shouldHold(FileWithActions::runActions); } @NotNull @@ -46,6 +48,6 @@ public class JavaCodeInsightSanityTest extends LightPlatformCodeInsightFixtureTe } public void testReparse() { - PropertyChecker.forAll(actionsOnJavaFiles(MadTestingUtil::randomEditsWithReparseChecks), FileWithActions::runActions); + PropertyChecker.forAll(actionsOnJavaFiles(MadTestingUtil::randomEditsWithReparseChecks)).shouldHold(FileWithActions::runActions); } } diff --git a/java/java-tests/testSrc/com/intellij/java/propertyBased/JavaCompletionPolicy.java b/java/java-tests/testSrc/com/intellij/java/propertyBased/JavaCompletionPolicy.java index a1e546a7bc2e..43437fd70b02 100644 --- a/java/java-tests/testSrc/com/intellij/java/propertyBased/JavaCompletionPolicy.java +++ b/java/java-tests/testSrc/com/intellij/java/propertyBased/JavaCompletionPolicy.java @@ -15,6 +15,7 @@ */ package com.intellij.java.propertyBased; +import com.intellij.lang.jvm.JvmModifier; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; @@ -26,26 +27,49 @@ import org.jetbrains.annotations.NotNull; */ class JavaCompletionPolicy extends CompletionPolicy { @Override - protected boolean shouldSuggestReferenceText(@NotNull PsiReference ref) { - if (ref instanceof PsiJavaCodeReferenceElement && !shouldSuggestJavaTarget((PsiJavaCodeReferenceElement)ref)) { + protected boolean shouldSuggestReferenceText(@NotNull PsiReference ref, @NotNull PsiElement target) { + PsiElement refElement = ref.getElement(); + if (refElement instanceof PsiJavaCodeReferenceElement && + !shouldSuggestJavaTarget((PsiJavaCodeReferenceElement)refElement, target)) { return false; } return true; } - private static boolean shouldSuggestJavaTarget(PsiJavaCodeReferenceElement ref) { - if (PsiTreeUtil.getParentOfType(ref, PsiPackageStatement.class) == null) return false; + private static boolean shouldSuggestJavaTarget(PsiJavaCodeReferenceElement ref, @NotNull PsiElement target) { + if (PsiTreeUtil.getParentOfType(ref, PsiPackageStatement.class) != null) return false; - PsiElement target = ref.resolve(); if (!ref.isQualified() && target instanceof PsiPackage) return false; + if (target instanceof PsiClass && PsiTreeUtil.isAncestor(target, ref, true)) { + PsiElement lBrace = ((PsiClass)target).getLBrace(); + if (lBrace == null || ref.getTextRange().getStartOffset() < lBrace.getTextRange().getStartOffset()) { + return false; + } + } + if (target instanceof PsiField && + SyntaxTraverser.psiApi().parents(ref).find(e -> e instanceof PsiMethod && ((PsiMethod)e).isConstructor()) != null) { + // https://youtrack.jetbrains.com/issue/IDEA-174744 on red code + return false; + } + if (isStaticWithInstanceQualifier(ref, target)) { + return false; + } return target != null; } + private static boolean isStaticWithInstanceQualifier(PsiJavaCodeReferenceElement ref, @NotNull PsiElement target) { + PsiElement qualifier = ref.getQualifier(); + return target instanceof PsiModifierListOwner && + ((PsiModifierListOwner)target).hasModifier(JvmModifier.STATIC) && + qualifier instanceof PsiJavaCodeReferenceElement && + !(((PsiJavaCodeReferenceElement)qualifier).resolve() instanceof PsiClass); + } + @Override protected boolean shouldSuggestNonReferenceLeafText(@NotNull PsiElement leaf) { if (leaf instanceof PsiKeyword) { if (leaf.getParent() instanceof PsiClassObjectAccessExpression && - PsiUtil.resolveClassInType(((PsiClassObjectAccessExpression)leaf.getParent()).getType()) == null) { + PsiUtil.resolveClassInType(((PsiClassObjectAccessExpression)leaf.getParent()).getOperand().getType()) == null) { return false; } } diff --git a/java/java-tests/testSrc/com/intellij/java/propertyBased/JavaIntentionPolicy.java b/java/java-tests/testSrc/com/intellij/java/propertyBased/JavaIntentionPolicy.java index f8cffbff7538..90813b04b3c0 100644 --- a/java/java-tests/testSrc/com/intellij/java/propertyBased/JavaIntentionPolicy.java +++ b/java/java-tests/testSrc/com/intellij/java/propertyBased/JavaIntentionPolicy.java @@ -25,15 +25,12 @@ public class JavaIntentionPolicy extends IntentionPolicy { @Override protected boolean shouldSkipIntention(@NotNull String actionText) { return actionText.startsWith("Flip") || + actionText.startsWith("Generate empty 'private' constructor") || // displays a dialog actionText.startsWith("Attach annotations") || // changes project model actionText.startsWith("Convert to string literal") || // can produce uncompilable code by design actionText.startsWith("Optimize imports") || // https://youtrack.jetbrains.com/issue/IDEA-173801 actionText.startsWith("Make method default") || actionText.startsWith("Convert to project line separators") || // changes VFS, not document - actionText.contains("to custom tags") || // changes only inspection settings - actionText.contains("to 'Ignore if annotated by' list") || // changes only inspection settings - actionText.startsWith("Allow these suppressions") || // changes only inspection settings - actionText.startsWith("Typo: Change to...") || // doesn't change file text (starts live template) actionText.startsWith("Change class type parameter") || // doesn't change file text (starts live template) actionText.startsWith("Rename reference") || // doesn't change file text (starts live template) actionText.startsWith("Detail exceptions") || // can produce uncompilable code if 'catch' section contains 'instanceof's @@ -42,6 +39,7 @@ public class JavaIntentionPolicy extends IntentionPolicy { actionText.startsWith("Cast to ") || // produces uncompilable code by design actionText.startsWith("Unwrap 'else' branch (changes semantics)") || // might produce code with final variables are initialized several times actionText.startsWith("Create missing 'switch' branches") || // if all existing branches do 'return something', we don't automatically generate compilable code for new branches - actionText.startsWith("Unimplement"); + actionText.startsWith("Unimplement") || + super.shouldSkipIntention(actionText); } } diff --git a/java/java-tests/testSrc/com/intellij/java/propertyBased/PsiIndexConsistencyTest.kt b/java/java-tests/testSrc/com/intellij/java/propertyBased/PsiIndexConsistencyTest.kt index f1ef3d197ad9..e3e1d264dc9b 100644 --- a/java/java-tests/testSrc/com/intellij/java/propertyBased/PsiIndexConsistencyTest.kt +++ b/java/java-tests/testSrc/com/intellij/java/propertyBased/PsiIndexConsistencyTest.kt @@ -32,9 +32,8 @@ import com.intellij.testFramework.SkipSlowTestLocally import com.intellij.testFramework.fixtures.CodeInsightTestFixture import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase import com.intellij.util.FileContentUtilCore -import slowCheck.CheckerSettings -import slowCheck.Generator -import slowCheck.PropertyChecker +import jetCheck.Generator +import jetCheck.PropertyChecker /** * @author peter @@ -62,7 +61,7 @@ class PsiIndexConsistencyTest: LightCodeInsightFixtureTestCase() { Generator.booleans().generateValue(data), Generator.booleans().generateValue(data)) } )) - PropertyChecker.forAll(CheckerSettings.DEFAULT_SETTINGS.withIterationCount(20), Generator.listsOf(genAction)) { actions -> + PropertyChecker.forAll(Generator.listsOf(genAction)).withIterationCount(20).shouldHold { actions -> runActions(*actions.toTypedArray()) true } diff --git a/java/java-tests/testSrc/com/intellij/java/propertyBased/ApplyRandomIntentionsTest.java b/java/java-tests/testSrc/com/intellij/java/propertyBased/UnivocityTest.java similarity index 82% rename from java/java-tests/testSrc/com/intellij/java/propertyBased/ApplyRandomIntentionsTest.java rename to java/java-tests/testSrc/com/intellij/java/propertyBased/UnivocityTest.java index d83a3e94ed93..06d6eec2172f 100644 --- a/java/java-tests/testSrc/com/intellij/java/propertyBased/ApplyRandomIntentionsTest.java +++ b/java/java-tests/testSrc/com/intellij/java/propertyBased/UnivocityTest.java @@ -23,16 +23,15 @@ import com.intellij.testFramework.SkipSlowTestLocally; import com.intellij.testFramework.propertyBased.*; import com.intellij.util.SystemProperties; import com.intellij.util.containers.ContainerUtil; -import slowCheck.CheckerSettings; -import slowCheck.Generator; -import slowCheck.IntDistribution; -import slowCheck.PropertyChecker; +import jetCheck.Generator; +import jetCheck.IntDistribution; +import jetCheck.PropertyChecker; import java.util.List; import java.util.concurrent.atomic.AtomicLong; @SkipSlowTestLocally -public class ApplyRandomIntentionsTest extends AbstractApplyAndRevertTestCase { +public class UnivocityTest extends AbstractApplyAndRevertTestCase { @Override public void setUp() throws Exception { @@ -41,15 +40,14 @@ public class ApplyRandomIntentionsTest extends AbstractApplyAndRevertTestCase { MadTestingUtil.enableAllInspections(myProject, myProject); } - public void testIntentionsInDifferentFiles() throws Throwable { + public void testCompilabilityAfterIntentions() { initCompiler(); PsiModificationTracker tracker = PsiManager.getInstance(myProject).getModificationTracker(); AtomicLong rebuildStamp = new AtomicLong(); - CheckerSettings settings = CheckerSettings.DEFAULT_SETTINGS.withIterationCount(30); Generator genIntention = psiJavaFiles().flatMap(file -> InvokeIntention.randomIntentions(file, new JavaIntentionPolicy())); - PropertyChecker.forAll(settings, Generator.listsOf(genIntention.noShrink()), list -> { + PropertyChecker.forAll(Generator.listsOf(genIntention.noShrink())).withIterationCount(30).shouldHold(list -> { long startModCount = tracker.getModificationCount(); if (rebuildStamp.getAndSet(startModCount) != startModCount) { checkCompiles(myCompilerTester.rebuild()); @@ -66,14 +64,14 @@ public class ApplyRandomIntentionsTest extends AbstractApplyAndRevertTestCase { }); } - public void testIntentionsAndModificationsInDifferentFiles() throws Throwable { - CheckerSettings settings = CheckerSettings.DEFAULT_SETTINGS.withIterationCount(50); + public void testRandomActivity() { Generator> genActionGroup = psiJavaFiles().flatMap( file -> { Generator mutation = Generator.anyOf(DeleteRange.psiRangeDeletions(file), Generator.constant(new AddNullArgument(file)), Generator.constant(new DeleteForeachInitializers(file)), Generator.constant(new DeleteSecondArgument(file)), + InvokeCompletion.completions(file, new JavaCompletionPolicy()), Generator.constant(new MakeAllMethodsVoid(file))); Generator allActions = Generator.frequency(2, InvokeIntention.randomIntentions(file, new JavaIntentionPolicy()), 1, Generator.constant(new RehighlightAllEditors(myProject)), @@ -81,7 +79,7 @@ public class ApplyRandomIntentionsTest extends AbstractApplyAndRevertTestCase { return Generator.listsOf(IntDistribution.uniform(0, 5), allActions.noShrink()); }); - PropertyChecker.forAll(settings, Generator.listsOf(genActionGroup).map(ContainerUtil::flatten), list -> { + PropertyChecker.forAll(Generator.listsOf(genActionGroup).map(ContainerUtil::flatten)).withIterationCount(50).shouldHold(list -> { MadTestingUtil.changeAndRevert(myProject, () -> { //System.out.println(list); MadTestingAction.runActions(list); diff --git a/java/java-tests/testSrc/com/intellij/java/psi/JavaStubsTest.groovy b/java/java-tests/testSrc/com/intellij/java/psi/JavaStubsTest.groovy index 604ba9ef1394..eca6c5a2915c 100644 --- a/java/java-tests/testSrc/com/intellij/java/psi/JavaStubsTest.groovy +++ b/java/java-tests/testSrc/com/intellij/java/psi/JavaStubsTest.groovy @@ -221,4 +221,42 @@ class Foo { PsiTestUtil.checkStubsMatchText(psiFile) } + void "test inserting enum keyword"() { + String text = "class Foo { void foo() { return; } }" + PsiFile psiFile = myFixture.addFileToProject("a.java", text) + Document document = psiFile.getViewProvider().getDocument() + + WriteCommandAction.runWriteCommandAction(project) { + document.insertString(text.indexOf("return"), "enum Foo") + } + PsiDocumentManager.getInstance(getProject()).commitAllDocuments() + PsiTestUtil.checkStubsMatchText(psiFile) + } + + void "test type arguments without type in a method"() { + String text = "class Foo { { final Collection contexts; f instanceof -> } }" + PsiFile psiFile = myFixture.addFileToProject("a.java", text) + + WriteCommandAction.runWriteCommandAction(project) { deleteString(psiFile, "Collection") } + PsiDocumentManager.getInstance(getProject()).commitAllDocuments() + PsiTestUtil.checkStubsMatchText(psiFile) + } + + private static void deleteString(PsiFile file, String fragment) { + def document = file.viewProvider.document + def index = document.text.indexOf(fragment) + document.deleteString(index, index + fragment.size()) + } + + void "test remove class literal qualifier"() { + String text = "class Foo { { foo(String.class); } }" + PsiFile psiFile = myFixture.addFileToProject("a.java", text) + WriteCommandAction.runWriteCommandAction(project) { + psiFile.viewProvider.document.insertString(text.indexOf(');'), ' x') + WriteCommandAction.runWriteCommandAction(project) { deleteString(psiFile, "String") } + } + PsiDocumentManager.getInstance(getProject()).commitAllDocuments() + PsiTestUtil.checkStubsMatchText(psiFile) + } + } \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/psi/codeStyle/IndentOptionsCacheDropTest.kt b/java/java-tests/testSrc/com/intellij/java/psi/codeStyle/IndentOptionsCacheDropTest.kt index 5a352d6e4975..7ed4c4f3d033 100644 --- a/java/java-tests/testSrc/com/intellij/java/psi/codeStyle/IndentOptionsCacheDropTest.kt +++ b/java/java-tests/testSrc/com/intellij/java/psi/codeStyle/IndentOptionsCacheDropTest.kt @@ -18,10 +18,15 @@ package com.intellij.java.psi.codeStyle import com.intellij.ide.highlighter.JavaFileType import com.intellij.lang.java.JavaLanguage +import com.intellij.openapi.editor.Document import com.intellij.openapi.editor.impl.EditorImpl +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiFileFactory import com.intellij.psi.codeStyle.CodeStyleSettingsManager import com.intellij.psi.codeStyle.CommonCodeStyleSettings.IndentOptions import com.intellij.psi.codeStyle.DetectableIndentOptionsProvider +import com.intellij.psi.codeStyle.TimeStampedIndentOptions import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase import org.assertj.core.api.Assertions.assertThat @@ -47,7 +52,25 @@ class Test { } } """ - + + fun `test store valid timestamped options in document when detecting indents`() { + val file = PsiFileFactory.getInstance(project).createFileFromText("Test.java", JavaFileType.INSTANCE, code, 0, true) + val detectableOptionsProvider = object : DetectableIndentOptionsProvider() { + override fun scheduleDetectionInBackground(project: Project, document: Document, indentOptions: TimeStampedIndentOptions) { + //just do nothing, so default indent options will be kept (same as very long indent detection calculation) + } + } + detectableOptionsProvider.setEnabledInTest(true) + + val settings = CodeStyleSettingsManager.getSettings(project) + val options = detectableOptionsProvider.getIndentOptions(settings, file) + + val document = PsiDocumentManager.getInstance(project).getDocument(file)!! + val indentOptions = detectableOptionsProvider.getValidCachedIndentOptions(file, document)!! + + assert(options == indentOptions && options === indentOptions) + } + fun testDropIndentOptions_WhenTabSizeChanged() { val current = CodeStyleSettingsManager.getInstance(project).currentSettings val options = current.getCommonSettings(JavaLanguage.INSTANCE).indentOptions!! diff --git a/java/java-tests/testSrc/com/intellij/java/psi/formatter/java/JavadocFormatterTest.java b/java/java-tests/testSrc/com/intellij/java/psi/formatter/java/JavadocFormatterTest.java index 36b34e7b6c83..8dcaec2402b5 100644 --- a/java/java-tests/testSrc/com/intellij/java/psi/formatter/java/JavadocFormatterTest.java +++ b/java/java-tests/testSrc/com/intellij/java/psi/formatter/java/JavadocFormatterTest.java @@ -1003,5 +1003,18 @@ public class JavadocFormatterTest extends AbstractJavaFormatterTest { ); } - + public void testMultipleSince() { + doTextTest("/**\n" + + " * @since 1.7\n" + + " * @since 2.0\n" + + " */\n" + + "public class C {\n" + + "}", + "/**\n" + + " * @since 1.7\n" + + " * @since 2.0\n" + + " */\n" + + "public class C {\n" + + "}"); + } } \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/psi/impl/smartPointers/SmartPsiElementPointersTest.java b/java/java-tests/testSrc/com/intellij/java/psi/impl/smartPointers/SmartPsiElementPointersTest.java index 367d5528ca18..223202d62f36 100644 --- a/java/java-tests/testSrc/com/intellij/java/psi/impl/smartPointers/SmartPsiElementPointersTest.java +++ b/java/java-tests/testSrc/com/intellij/java/psi/impl/smartPointers/SmartPsiElementPointersTest.java @@ -1034,4 +1034,13 @@ public class SmartPsiElementPointersTest extends CodeInsightTestCase { assertEquals(TextRange.from(3, 1), TextRange.create(pointer.getRange())); } + public void testFileRangeWithUnicode() throws Exception { + PsiFile file = createFile("a.java", "Ы"); + assertEquals(1, file.getTextLength()); + SmartPsiFileRange pointer = getPointerManager().createSmartPsiFileRangePointer(file, TextRange.from(0, file.getTextLength())); + assertEquals(TextRange.from(0, 1), TextRange.create(pointer.getPsiRange())); + assertEquals(TextRange.from(0, 1), TextRange.create(pointer.getRange())); + + } + } diff --git a/java/java-tests/testSrc/com/intellij/java/psi/search/JavaOverrideMethodsSearchTest.java b/java/java-tests/testSrc/com/intellij/java/psi/search/JavaOverrideMethodsSearchTest.java index 1dc5a204b219..b797f6c07ab0 100644 --- a/java/java-tests/testSrc/com/intellij/java/psi/search/JavaOverrideMethodsSearchTest.java +++ b/java/java-tests/testSrc/com/intellij/java/psi/search/JavaOverrideMethodsSearchTest.java @@ -27,7 +27,7 @@ import org.jetbrains.annotations.NotNull; import java.util.Collection; public class JavaOverrideMethodsSearchTest extends LightCodeInsightFixtureTestCase { - public void testSearchInLocalScope() throws Exception { + public void testSearchInLocalScopePerformance() throws Exception { myFixture.addClass("public interface I {void m();}"); for (int i = 0; i < 1000; i++) { final int dirIdx = i % 100; diff --git a/java/java-tests/testSrc/com/intellij/roots/libraries/LibraryTest.java b/java/java-tests/testSrc/com/intellij/roots/libraries/LibraryTest.java index 9b563c5ce054..b0924da99042 100644 --- a/java/java-tests/testSrc/com/intellij/roots/libraries/LibraryTest.java +++ b/java/java-tests/testSrc/com/intellij/roots/libraries/LibraryTest.java @@ -18,13 +18,12 @@ package com.intellij.roots.libraries; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.Result; import com.intellij.openapi.application.WriteAction; -import com.intellij.openapi.roots.ModifiableRootModel; -import com.intellij.openapi.roots.ModuleRootManager; -import com.intellij.openapi.roots.NativeLibraryOrderRootType; -import com.intellij.openapi.roots.OrderRootType; +import com.intellij.openapi.roots.*; import com.intellij.openapi.roots.impl.ModuleRootManagerComponent; +import com.intellij.openapi.roots.impl.OrderEntryUtil; import com.intellij.openapi.roots.impl.libraries.LibraryEx; import com.intellij.openapi.roots.impl.libraries.LibraryTableBase; +import com.intellij.openapi.roots.impl.libraries.LibraryTableImplUtil; import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable; import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.roots.libraries.LibraryTable; @@ -64,7 +63,19 @@ public class LibraryTest extends ModuleRootManagerTestCase { commit(model2); assertFalse(listenerNotifiedOnChange[0]); + assertTrue(LibraryTableImplUtil.isValidLibrary(library)); ApplicationManager.getApplication().runWriteAction(() -> libraryTable.removeLibrary(library)); + assertFalse(LibraryTableImplUtil.isValidLibrary(library)); + } + + public void testAddRemoveModuleLibrary() { + ModuleRootModificationUtil.addModuleLibrary(myModule, getJDomJar().getUrl()); + Library library = assertOneElement(OrderEntryUtil.getModuleLibraries(ModuleRootManager.getInstance(myModule))); + assertTrue(LibraryTableImplUtil.isValidLibrary(library)); + ModuleRootModificationUtil.updateModel(myModule, (model) -> { + model.getModuleLibraryTable().removeLibrary(library); + }); + assertFalse(LibraryTableImplUtil.isValidLibrary(library)); } public void testLibrarySerialization() { diff --git a/java/manifest/src/org/jetbrains/lang/manifest/highlighting/MisspelledHeaderInspection.java b/java/manifest/src/org/jetbrains/lang/manifest/highlighting/MisspelledHeaderInspection.java index 0697a695fed2..3b9d56188f7a 100644 --- a/java/manifest/src/org/jetbrains/lang/manifest/highlighting/MisspelledHeaderInspection.java +++ b/java/manifest/src/org/jetbrains/lang/manifest/highlighting/MisspelledHeaderInspection.java @@ -188,5 +188,10 @@ public class MisspelledHeaderInspection extends LocalInspectionTool { ProjectInspectionProfileManager.getInstance(project).fireProfileChanged(); } + + @Override + public boolean startInWriteAction() { + return false; + } } } diff --git a/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaReplaceHandler.java b/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaReplaceHandler.java index 3d52d262b98d..5c92f293577d 100644 --- a/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaReplaceHandler.java +++ b/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaReplaceHandler.java @@ -315,14 +315,34 @@ public class JavaReplaceHandler extends StructuralReplaceHandler { copy.setModifierProperty(modifier, false); } } - final PsiElement anchor = copy.getFirstChild(); + for (PsiAnnotation copyAnnotation : copy.getAnnotations()) { + for (PsiAnnotation queryAnnotation : query.getAnnotations()) { + if (matches(queryAnnotation, copyAnnotation)) { + copyAnnotation.delete(); + } + } + } for (PsiAnnotation annotation : replacementModifierList.getAnnotations()) { - copy.addBefore(annotation, anchor); + copy.addBefore(annotation, copy.getFirstChild()); } replacementModifierList.replace(copy); } } + private static boolean matches(PsiAnnotation queryAnnotation, PsiAnnotation originalAnnotation) { + final PsiJavaCodeReferenceElement queryReferenceElement = queryAnnotation.getNameReferenceElement(); + if (queryReferenceElement == null) { + return false; + } + final PsiJavaCodeReferenceElement originalReferenceElement = originalAnnotation.getNameReferenceElement(); + if (originalReferenceElement == null) { + return false; + } + final String queryQualifiedName = queryReferenceElement.getQualifiedName(); + return queryQualifiedName.equals(originalReferenceElement.getQualifiedName()) || + queryQualifiedName.equals(originalReferenceElement.getReferenceName()); + } + private PsiElement handleSymbolReplacement(PsiElement replacement, final PsiElement el) throws IncorrectOperationException { PsiNamedElement nameElement = getSymbolReplacementTarget(el); if (nameElement != null) { diff --git a/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaStructuralSearchProfile.java b/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaStructuralSearchProfile.java index c284d2f49fb0..ea08662a51c7 100644 --- a/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaStructuralSearchProfile.java +++ b/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaStructuralSearchProfile.java @@ -480,6 +480,7 @@ public class JavaStructuralSearchProfile extends StructuralSearchProfile { private PsiElement myCurrent; @Override public void visitAnnotation(PsiAnnotation annotation) { + super.visitAnnotation(annotation); final PsiJavaCodeReferenceElement nameReferenceElement = annotation.getNameReferenceElement(); if (nameReferenceElement == null || diff --git a/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/JavaMatchingVisitor.java b/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/JavaMatchingVisitor.java index d4885c38265e..89539ae3bbd6 100644 --- a/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/JavaMatchingVisitor.java +++ b/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/JavaMatchingVisitor.java @@ -301,23 +301,28 @@ public class JavaMatchingVisitor extends JavaElementVisitor { myMatchingVisitor.setResult(myMatchingVisitor.match(value, elementNameValuePair.getValue())); if (myMatchingVisitor.getResult()) { final PsiIdentifier nameIdentifier = pair.getNameIdentifier(); - final PsiIdentifier otherIdentifier = elementNameValuePair.getNameIdentifier(); - if (nameIdentifier == null) { - myMatchingVisitor.setResult(otherIdentifier == null || - otherIdentifier.getText().equals(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME)); - } - else { + if (nameIdentifier != null) { + final PsiIdentifier otherIdentifier = elementNameValuePair.getNameIdentifier(); final MatchingHandler handler = myMatchingVisitor.getMatchContext().getPattern().getHandler(nameIdentifier); if (handler instanceof SubstitutionHandler) { myMatchingVisitor.setResult(((SubstitutionHandler)handler).handle(otherIdentifier, myMatchingVisitor.getMatchContext())); } else { - myMatchingVisitor.setResult(myMatchingVisitor.match(nameIdentifier, otherIdentifier)); + myMatchingVisitor.setResult( + (PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME.equals(nameIdentifier.getText()) && otherIdentifier == null) || + myMatchingVisitor.match(nameIdentifier, otherIdentifier)); } } } } + @Override + public void visitAnnotationArrayInitializer(PsiArrayInitializerMemberValue initializer) { + final PsiArrayInitializerMemberValue otherInitializer = (PsiArrayInitializerMemberValue)myMatchingVisitor.getElement(); + final PsiAnnotationMemberValue[] initializers = initializer.getInitializers(); + myMatchingVisitor.setResult(myMatchingVisitor.matchSequentially(initializers, otherInitializer.getInitializers())); + } + private boolean checkHierarchy(PsiMember element, PsiMember patternElement) { final MatchingHandler handler = myMatchingVisitor.getMatchContext().getPattern().getHandler(patternElement); if (handler instanceof SubstitutionHandler) { diff --git a/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/filters/ExpressionFilter.java b/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/filters/ExpressionFilter.java index 616e9cdef336..813cdbc18514 100644 --- a/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/filters/ExpressionFilter.java +++ b/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/filters/ExpressionFilter.java @@ -18,6 +18,7 @@ package com.intellij.structuralsearch.impl.matcher.filters; import com.intellij.dupLocator.util.NodeFilter; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiExpression; +import com.intellij.psi.PsiNameValuePair; /** * Filters expression nodes @@ -34,6 +35,6 @@ public class ExpressionFilter implements NodeFilter { @Override public boolean accepts(PsiElement element) { - return element instanceof PsiExpression; + return element instanceof PsiExpression || element instanceof PsiNameValuePair; } } diff --git a/java/testFramework/src/com/intellij/testFramework/fixtures/impl/JavaCodeInsightTestFixtureImpl.java b/java/testFramework/src/com/intellij/testFramework/fixtures/impl/JavaCodeInsightTestFixtureImpl.java index 73d4d73cf225..c892ffc864c1 100644 --- a/java/testFramework/src/com/intellij/testFramework/fixtures/impl/JavaCodeInsightTestFixtureImpl.java +++ b/java/testFramework/src/com/intellij/testFramework/fixtures/impl/JavaCodeInsightTestFixtureImpl.java @@ -17,6 +17,7 @@ package com.intellij.testFramework.fixtures.impl; import com.intellij.ide.highlighter.JavaFileType; import com.intellij.openapi.application.ReadAction; +import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.impl.JavaPsiFacadeEx; @@ -24,6 +25,7 @@ import com.intellij.psi.search.ProjectScope; import com.intellij.testFramework.fixtures.IdeaProjectTestFixture; import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture; import com.intellij.testFramework.fixtures.TempDirTestFixture; +import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.junit.Assert; @@ -46,7 +48,22 @@ public class JavaCodeInsightTestFixtureImpl extends CodeInsightTestFixtureImpl i @Override public PsiClass addClass(@NotNull @NonNls final String classText) { assertInitialized(); - final PsiClass psiClass = addClass(getTempDirPath(), classText); + + String rootPath = getTempDirPath(); + + // Make sure rootPath belongs to the module: + final ModuleRootManager rootManager = ModuleRootManager.getInstance(getModule()); + if (rootManager != null) { + VirtualFile[] allSourceRoots = rootManager.getSourceRoots(true); + VirtualFile[] productionSourceRoots = rootManager.getSourceRoots(false); + VirtualFile rootVirtualFile = getTempDirFixture().getFile(""); // should be equivalent to rootPath. + if (!ArrayUtil.contains(rootVirtualFile, allSourceRoots) && !ArrayUtil.isEmpty(productionSourceRoots)) { + // The temp directory is not a source root, so there's little point adding the class there. Pick a production source root instead. + rootPath = productionSourceRoots[0].getPath(); + } + } + + final PsiClass psiClass = addClass(rootPath, classText); final VirtualFile file = psiClass.getContainingFile().getVirtualFile(); allowTreeAccessForFile(file); return psiClass; diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/BaseGuavaTypeConversionRule.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/BaseGuavaTypeConversionRule.java index c87da269ba11..c11956f56dbf 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/BaseGuavaTypeConversionRule.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/BaseGuavaTypeConversionRule.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -59,9 +59,7 @@ public abstract class BaseGuavaTypeConversionRule extends TypeConversionRule { } @Nullable - protected TypeConversionDescriptorBase findConversionForVariableReference(@NotNull PsiReferenceExpression referenceExpression, - @NotNull PsiVariable psiVariable, - @Nullable PsiExpression context) { + protected TypeConversionDescriptorBase findConversionForVariableReference(@Nullable PsiExpression context) { return null; } @@ -96,7 +94,8 @@ public abstract class BaseGuavaTypeConversionRule extends TypeConversionRule { return descriptor; } } - return findConversionForMethod(from, to, method, methodName, context, labeler); + TypeConversionDescriptorBase conversionForMethod = findConversionForMethod(from, to, method, methodName, context, labeler); + return conversionForMethod != null ? conversionForMethod : getUnknownMethodConversion(); } else if (context instanceof PsiNewExpression) { final PsiAnonymousClass anonymousClass = ((PsiNewExpression)context).getAnonymousClass(); return anonymousClass == null ? null : findConversionForAnonymous(anonymousClass, labeler.getSettings(GuavaConversionSettings.class)); @@ -111,13 +110,15 @@ public abstract class BaseGuavaTypeConversionRule extends TypeConversionRule { if (context instanceof PsiReferenceExpression) { final PsiElement resolvedElement = ((PsiReferenceExpression)context).resolve(); if (resolvedElement instanceof PsiVariable) { - return findConversionForVariableReference((PsiReferenceExpression)context, (PsiVariable)resolvedElement, context); + return findConversionForVariableReference(context); } } } return null; } + protected abstract TypeConversionDescriptorBase getUnknownMethodConversion(); + @Nullable protected TypeConversionDescriptorBase findConversionForAnonymous(@NotNull PsiAnonymousClass anonymousClass, @Nullable GuavaConversionSettings settings) { diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/FluentIterableConversionUtil.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/FluentIterableConversionUtil.java index 2cc9126a2bb5..6bf575166335 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/FluentIterableConversionUtil.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/FluentIterableConversionUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -140,7 +140,7 @@ public class FluentIterableConversionUtil { return new GuavaFilterInstanceOfConversionDescriptor(filterClassName); } else if (GuavaLambda.PREDICATE.getClassQName().equals(resolvedClass.getQualifiedName())) { - return new GuavaTypeConversionDescriptor("$it$.filter($p$)", "$it$." + StreamApiConstants.FILTER + "($p$)"); + return new GuavaTypeConversionDescriptor("$it$.filter($p$)", "$it$." + StreamApiConstants.FILTER + "($p$)", context); } return null; } @@ -157,8 +157,8 @@ public class FluentIterableConversionUtil { } static class TransformAndConcatConversionRule extends GuavaTypeConversionDescriptor { - public TransformAndConcatConversionRule() { - super("$q$.transformAndConcat($params$)", "$q$.flatMap($params$)"); + public TransformAndConcatConversionRule(PsiExpression context) { + super("$q$.transformAndConcat($params$)", "$q$.flatMap($params$)", context); } @Override @@ -272,7 +272,7 @@ public class FluentIterableConversionUtil { final String returnType; if ("toMap".equals(methodName) || "uniqueIndex".equals(methodName)) { final GuavaTypeConversionDescriptor descriptor = new GuavaTypeConversionDescriptor("$it$.$methodName$($f$)", - "$it$.collect(java.util.stream.Collectors.toMap(java.util.function.Function.identity(), $f$))"); + "$it$.collect(java.util.stream.Collectors.toMap(java.util.function.Function.identity(), $f$))", context); return descriptor.withConversionType(GuavaConversionUtil.addTypeParameters(CommonClassNames.JAVA_UTIL_MAP, context.getType(), context)); } else if ("toList".equals(methodName)) { diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaFluentIterableConversionRule.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaFluentIterableConversionRule.java index 9f3345158f0e..fff0276c2529 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaFluentIterableConversionRule.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaFluentIterableConversionRule.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -78,8 +78,8 @@ public class GuavaFluentIterableConversionRule extends BaseGuavaTypeConversionRu myFluentIterableReturnType = fluentIterableReturnType; } - public TypeConversionDescriptor create() { - GuavaTypeConversionDescriptor descriptor = new GuavaTypeConversionDescriptor(myStringToReplace, myReplaceByString); + public TypeConversionDescriptor create(PsiExpression context) { + GuavaTypeConversionDescriptor descriptor = new GuavaTypeConversionDescriptor(myStringToReplace, myReplaceByString, context); if (!myWithLambdaParameter) { descriptor = descriptor.setConvertParameterAsLambda(false); } @@ -125,14 +125,13 @@ public class GuavaFluentIterableConversionRule extends BaseGuavaTypeConversionRu return buildCompoundDescriptor((PsiMethodCallExpression)context, to, labeler); } - return getOneMethodDescriptor(methodName, method, from, to, context); + return getOneMethodDescriptor(methodName, method, from, context); } @Nullable private static TypeConversionDescriptorBase getOneMethodDescriptor(@NotNull String methodName, @NotNull PsiMethod method, @NotNull PsiType from, - @Nullable PsiType to, @Nullable PsiExpression context) { TypeConversionDescriptor descriptorBase = null; PsiType conversionType = null; @@ -176,7 +175,7 @@ public class GuavaFluentIterableConversionRule extends BaseGuavaTypeConversionRu needSpecifyType = false; } else if (methodName.equals("transformAndConcat")) { - descriptorBase = new FluentIterableConversionUtil.TransformAndConcatConversionRule(); + descriptorBase = new FluentIterableConversionUtil.TransformAndConcatConversionRule(context); } else if (methodName.equals("toArray")) { descriptorBase = FluentIterableConversionUtil.getToArrayDescriptor(from, context); needSpecifyType = false; @@ -250,7 +249,7 @@ public class GuavaFluentIterableConversionRule extends BaseGuavaTypeConversionRu else { final TypeConversionDescriptorFactory base = DESCRIPTORS_MAP.get(methodName); if (base != null) { - final TypeConversionDescriptor descriptor = base.create(); + final TypeConversionDescriptor descriptor = base.create(context); needSpecifyType = base.isChainedMethod(); if (needSpecifyType && !base.isFluentIterableReturnType()) { conversionType = GuavaConversionUtil.addTypeParameters(GuavaOptionalConversionRule.JAVA_OPTIONAL, context.getType(), context); @@ -327,7 +326,7 @@ public class GuavaFluentIterableConversionRule extends BaseGuavaTypeConversionRu } TypeConversionDescriptorBase descriptor = null; if (FLUENT_ITERABLE.equals(containingClass.getQualifiedName())) { - descriptor = getOneMethodDescriptor(methodName, method, current.getType(), null, current); + descriptor = getOneMethodDescriptor(methodName, method, current.getType(), current); if (descriptor == null) { return null; } @@ -436,4 +435,9 @@ public class GuavaFluentIterableConversionRule extends BaseGuavaTypeConversionRu public String ruleToClass() { return StreamApiConstants.JAVA_UTIL_STREAM_STREAM; } + + @Override + protected TypeConversionDescriptorBase getUnknownMethodConversion() { + return null; + } } diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaLambdaConversionRule.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaLambdaConversionRule.java index 366acf0d9820..1e1d32eed832 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaLambdaConversionRule.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaLambdaConversionRule.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -15,8 +15,11 @@ */ package com.intellij.refactoring.typeMigration.rules.guava; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.psi.*; +import com.intellij.psi.PsiAnonymousClass; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiExpression; +import com.intellij.psi.PsiNewExpression; +import com.intellij.refactoring.typeMigration.TypeConversionDescriptor; import com.intellij.refactoring.typeMigration.TypeConversionDescriptorBase; import com.intellij.refactoring.typeMigration.TypeEvaluator; import com.intellij.refactoring.typeMigration.inspections.GuavaConversionSettings; @@ -30,7 +33,6 @@ import java.util.Map; * @author Dmitry Batkovich */ public class GuavaLambdaConversionRule extends BaseGuavaTypeConversionRule { - private final static Logger LOG = Logger.getInstance(GuavaLambdaConversionRule.class); private final GuavaLambda myLambda; protected GuavaLambdaConversionRule(GuavaLambda lambda) { @@ -44,8 +46,7 @@ public class GuavaLambdaConversionRule extends BaseGuavaTypeConversionRule { @Nullable @Override - protected TypeConversionDescriptorBase findConversionForVariableReference(@NotNull PsiReferenceExpression referenceExpression, - @NotNull PsiVariable psiVariable, PsiExpression context) { + protected TypeConversionDescriptorBase findConversionForVariableReference(PsiExpression context) { return new FunctionalInterfaceTypeConversionDescriptor(myLambda.getSamName(), myLambda.getJavaAnalogueSamName(), myLambda.getJavaAnalogueClassQName()); } @@ -61,6 +62,11 @@ public class GuavaLambdaConversionRule extends BaseGuavaTypeConversionRule { return myLambda.getJavaAnalogueClassQName(); } + @Override + protected TypeConversionDescriptorBase getUnknownMethodConversion() { + return new TypeConversionDescriptor("$q$.$m$($args$)", "$q$.$m$($args$)::" + myLambda.getJavaAnalogueSamName()); + } + @Nullable @Override protected TypeConversionDescriptorBase findConversionForAnonymous(@NotNull PsiAnonymousClass anonymousClass, diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaOptionalConversionRule.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaOptionalConversionRule.java index e6ccc20cf915..7447115681a3 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaOptionalConversionRule.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaOptionalConversionRule.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -74,7 +74,7 @@ public class GuavaOptionalConversionRule extends BaseGuavaTypeConversionRule { return descriptor; } return GuavaLambda.SUPPLIER.getClassQName().equals(qName) - ? new GuavaTypeConversionDescriptor("$val$.or($other$)", "$val$.orElseGet($other$)") + ? new GuavaTypeConversionDescriptor("$val$.or($other$)", "$val$.orElseGet($other$)", context) : new TypeConversionDescriptor("$val$.or($other$)", "$val$.orElse($other$)"); } return null; @@ -86,7 +86,7 @@ public class GuavaOptionalConversionRule extends BaseGuavaTypeConversionRule { return null; } final PsiExpression functionArgument = arguments[0]; - final TypeConversionDescriptor descriptor = new GuavaTypeConversionDescriptor("$val$.transform($fun$)", "$val$.map($fun$)"); + final TypeConversionDescriptor descriptor = new GuavaTypeConversionDescriptor("$val$.transform($fun$)", "$val$.map($fun$)", context); final PsiType typeParameter = GuavaConversionUtil.getFunctionReturnType(functionArgument); if (typeParameter == null) { return descriptor; @@ -112,9 +112,7 @@ public class GuavaOptionalConversionRule extends BaseGuavaTypeConversionRule { @Nullable @Override - protected TypeConversionDescriptorBase findConversionForVariableReference(@NotNull PsiReferenceExpression referenceExpression, - @NotNull PsiVariable psiVariable, - @Nullable PsiExpression context) { + protected TypeConversionDescriptorBase findConversionForVariableReference(@Nullable PsiExpression context) { if (GuavaOptionalConversionUtil.isOptionalOrContext(context)) { return new TypeConversionDescriptor("$o$", "com.google.common.base." + OPTIONAL_CONVERTOR_PATTERN); } @@ -171,4 +169,9 @@ public class GuavaOptionalConversionRule extends BaseGuavaTypeConversionRule { public String ruleToClass() { return JAVA_OPTIONAL; } + + @Override + protected TypeConversionDescriptorBase getUnknownMethodConversion() { + return null; + } } diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaTypeConversionDescriptor.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaTypeConversionDescriptor.java index 7c82412c193c..fb20e3ff99d8 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaTypeConversionDescriptor.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaTypeConversionDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -32,11 +32,15 @@ import org.jetbrains.annotations.Nullable; public class GuavaTypeConversionDescriptor extends TypeConversionDescriptor { private static final Logger LOG = Logger.getInstance(GuavaTypeConversionDescriptor.class); private final String myReplaceByStringSource; + private final boolean myIterable; private boolean myConvertParameterAsLambda = true; - GuavaTypeConversionDescriptor(@NonNls String stringToReplace, @NonNls String replaceByString) { + GuavaTypeConversionDescriptor(@NonNls String stringToReplace, + @NonNls String replaceByString, + @NotNull PsiExpression expression) { super(stringToReplace, replaceByString); myReplaceByStringSource = replaceByString; + myIterable = isIterable(expression); } public GuavaTypeConversionDescriptor setConvertParameterAsLambda(boolean convertParameterAsLambda) { @@ -46,7 +50,7 @@ public class GuavaTypeConversionDescriptor extends TypeConversionDescriptor { @Override public PsiExpression replace(PsiExpression expression, @NotNull TypeEvaluator evaluator) throws IncorrectOperationException { - setReplaceByString(myReplaceByStringSource + (isIterable(expression) ? ".collect(java.util.stream.Collectors.toList())" : "")); + setReplaceByString(myReplaceByStringSource + (myIterable ? ".collect(java.util.stream.Collectors.toList())" : "")); if (myConvertParameterAsLambda) { LOG.assertTrue(expression instanceof PsiMethodCallExpression); final PsiExpression[] arguments = ((PsiMethodCallExpression)expression).getArgumentList().getExpressions(); @@ -82,11 +86,14 @@ public class GuavaTypeConversionDescriptor extends TypeConversionDescriptor { } } } + else if (parent instanceof PsiMethodCallExpression) { + return isIterable((PsiExpression)parent); + } return false; } private static boolean isIterable(@Nullable PsiType type) { - PsiClass aClass; - return (aClass = PsiTypesUtil.getPsiClass(type)) != null && CommonClassNames.JAVA_LANG_ITERABLE.equals(aClass.getQualifiedName()); + PsiClass aClass = PsiTypesUtil.getPsiClass(type); + return aClass != null && CommonClassNames.JAVA_LANG_ITERABLE.equals(aClass.getQualifiedName()); } } diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/IterableStreamConversionRule.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/IterableStreamConversionRule.java index c87402aa14ac..c4875b31f6f0 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/IterableStreamConversionRule.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/IterableStreamConversionRule.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -37,7 +37,7 @@ public class IterableStreamConversionRule extends TypeConversionRule { to, CommonClassNames.JAVA_LANG_ITERABLE, StreamApiConstants.JAVA_UTIL_STREAM_STREAM)) { - return new GuavaTypeConversionDescriptor("$it$", "$it$").setConvertParameterAsLambda(false); + return new GuavaTypeConversionDescriptor("$it$", "$it$", context).setConvertParameterAsLambda(false); } return null; } diff --git a/java/typeMigration/test/com/intellij/codeInsight/inspections/GuavaInspectionTest.java b/java/typeMigration/test/com/intellij/codeInsight/inspections/GuavaInspectionTest.java index addc796dbd3e..f83021ba9d4f 100644 --- a/java/typeMigration/test/com/intellij/codeInsight/inspections/GuavaInspectionTest.java +++ b/java/typeMigration/test/com/intellij/codeInsight/inspections/GuavaInspectionTest.java @@ -178,6 +178,10 @@ public class GuavaInspectionTest extends JavaCodeInsightFixtureTestCase { doTest(); } + public void testConvertFluentIterableAsIterableParameter2() { + doTest(); + } + public void testConvertFunctionAsParameter() { doTest(); } diff --git a/java/typeMigration/testData/inspections/guava/convertFluentIterableAsIterableParameter2.java b/java/typeMigration/testData/inspections/guava/convertFluentIterableAsIterableParameter2.java new file mode 100644 index 000000000000..dfb72fc42c01 --- /dev/null +++ b/java/typeMigration/testData/inspections/guava/convertFluentIterableAsIterableParameter2.java @@ -0,0 +1,11 @@ +import com.google.common.collect.FluentIterable; +import com.google.common.collect.Iterables; + +import java.util.ArrayList; + +public class Main16 { + void m() { + //read-only method with Iterable parameter + Iterables.cycle(FluentIterable.from(new ArrayList()).transform(s -> s)); + } +} diff --git a/java/typeMigration/testData/inspections/guava/convertFluentIterableAsIterableParameter2_after.java b/java/typeMigration/testData/inspections/guava/convertFluentIterableAsIterableParameter2_after.java new file mode 100644 index 000000000000..3acb9c34a713 --- /dev/null +++ b/java/typeMigration/testData/inspections/guava/convertFluentIterableAsIterableParameter2_after.java @@ -0,0 +1,11 @@ +import com.google.common.collect.Iterables; + +import java.util.ArrayList; +import java.util.stream.Collectors; + +public class Main16 { + void m() { + //read-only method with Iterable parameter + Iterables.cycle(new ArrayList().stream().map(s -> s).collect(Collectors.toList())); + } +} diff --git a/slowCheck/slowCheck.iml b/jetCheck/jetCheck.iml similarity index 100% rename from slowCheck/slowCheck.iml rename to jetCheck/jetCheck.iml diff --git a/slowCheck/src/slowCheck/AbstractDataStructure.java b/jetCheck/src/jetCheck/AbstractDataStructure.java similarity index 95% rename from slowCheck/src/slowCheck/AbstractDataStructure.java rename to jetCheck/src/jetCheck/AbstractDataStructure.java index 5f0eb4c55d78..665e011c6713 100644 --- a/slowCheck/src/slowCheck/AbstractDataStructure.java +++ b/jetCheck/src/jetCheck/AbstractDataStructure.java @@ -1,4 +1,4 @@ -package slowCheck; +package jetCheck; /** * @author peter diff --git a/slowCheck/src/slowCheck/BoundedIntDistribution.java b/jetCheck/src/jetCheck/BoundedIntDistribution.java similarity index 98% rename from slowCheck/src/slowCheck/BoundedIntDistribution.java rename to jetCheck/src/jetCheck/BoundedIntDistribution.java index 0bc9df4b967f..5f93a6695a38 100644 --- a/slowCheck/src/slowCheck/BoundedIntDistribution.java +++ b/jetCheck/src/jetCheck/BoundedIntDistribution.java @@ -1,4 +1,4 @@ -package slowCheck; +package jetCheck; import java.util.Random; import java.util.function.ToIntFunction; diff --git a/slowCheck/src/slowCheck/CannotRestoreValue.java b/jetCheck/src/jetCheck/CannotRestoreValue.java similarity index 80% rename from slowCheck/src/slowCheck/CannotRestoreValue.java rename to jetCheck/src/jetCheck/CannotRestoreValue.java index d3ebb4ca3e4a..99410fdd15db 100644 --- a/slowCheck/src/slowCheck/CannotRestoreValue.java +++ b/jetCheck/src/jetCheck/CannotRestoreValue.java @@ -1,4 +1,4 @@ -package slowCheck; +package jetCheck; /** * @author peter diff --git a/slowCheck/src/slowCheck/CannotSatisfyCondition.java b/jetCheck/src/jetCheck/CannotSatisfyCondition.java similarity index 96% rename from slowCheck/src/slowCheck/CannotSatisfyCondition.java rename to jetCheck/src/jetCheck/CannotSatisfyCondition.java index cb394c0e7676..c2fd344e853c 100644 --- a/slowCheck/src/slowCheck/CannotSatisfyCondition.java +++ b/jetCheck/src/jetCheck/CannotSatisfyCondition.java @@ -1,4 +1,4 @@ -package slowCheck; +package jetCheck; import org.jetbrains.annotations.NotNull; diff --git a/jetCheck/src/jetCheck/CounterExampleImpl.java b/jetCheck/src/jetCheck/CounterExampleImpl.java new file mode 100644 index 000000000000..bb1285278776 --- /dev/null +++ b/jetCheck/src/jetCheck/CounterExampleImpl.java @@ -0,0 +1,41 @@ +package jetCheck; + +import org.jetbrains.annotations.Nullable; + +import java.util.function.Predicate; + +class CounterExampleImpl implements PropertyFailure.CounterExample { + final StructureNode data; + private final T value; + @Nullable private final Throwable exception; + + private CounterExampleImpl(StructureNode data, T value, @Nullable Throwable exception) { + this.data = data; + this.value = value; + this.exception = exception; + } + + @Override + public T getExampleValue() { + return value; + } + + @Nullable + @Override + public Throwable getExceptionCause() { + return exception; + } + + static CounterExampleImpl checkProperty(Predicate property, T value, StructureNode node) { + try { + if (!property.test(value)) { + return new CounterExampleImpl<>(node, value, null); + } + } + catch (Throwable e) { + return new CounterExampleImpl<>(node, value, e); + } + return null; + } + +} \ No newline at end of file diff --git a/slowCheck/src/slowCheck/DataStructure.java b/jetCheck/src/jetCheck/DataStructure.java similarity index 58% rename from slowCheck/src/slowCheck/DataStructure.java rename to jetCheck/src/jetCheck/DataStructure.java index 22568f204285..9ee1c75d9ec8 100644 --- a/slowCheck/src/slowCheck/DataStructure.java +++ b/jetCheck/src/jetCheck/DataStructure.java @@ -1,4 +1,4 @@ -package slowCheck; +package jetCheck; import org.jetbrains.annotations.NotNull; @@ -14,7 +14,12 @@ public interface DataStructure { } int drawInt(@NotNull IntDistribution distribution); - + + /** + * @return a non-negative number used by various generators to guide the sizes of structures (e.g. collections) they create. + * The sizes need not be exactly equal to this hint, but in average bigger hints should in average correspond to bigger structures. When generators invoke other generators using {@link #subStructure}, the size hint of the sub-structure is + * generally less than the parent's one. + */ int getSizeHint(); default int suggestCollectionSize() { diff --git a/slowCheck/src/slowCheck/GenerativeDataStructure.java b/jetCheck/src/jetCheck/GenerativeDataStructure.java similarity index 90% rename from slowCheck/src/slowCheck/GenerativeDataStructure.java rename to jetCheck/src/jetCheck/GenerativeDataStructure.java index 98f4fb47639f..7819017b2456 100644 --- a/slowCheck/src/slowCheck/GenerativeDataStructure.java +++ b/jetCheck/src/jetCheck/GenerativeDataStructure.java @@ -1,4 +1,4 @@ -package slowCheck; +package jetCheck; import org.jetbrains.annotations.NotNull; @@ -34,14 +34,14 @@ class GenerativeDataStructure extends AbstractDataStructure { public T generateNonShrinkable(@NotNull Generator generator) { GenerativeDataStructure data = subStructure(); data.node.shrinkProhibited = true; - return generator.generateUnstructured(data); + return generator.getGeneratorFunction().apply(data); } @Override public T generateConditional(@NotNull Generator generator, @NotNull Predicate condition) { for (int i = 0; i < 100; i++) { GenerativeDataStructure structure = new GenerativeDataStructure(random, node.subStructure(), childSizeHint()); - T value = generator.generateUnstructured(structure); + T value = generator.getGeneratorFunction().apply(structure); if (condition.test(value)) return value; node.removeLastChild(structure.node); diff --git a/slowCheck/src/slowCheck/Generator.java b/jetCheck/src/jetCheck/Generator.java similarity index 61% rename from slowCheck/src/slowCheck/Generator.java rename to jetCheck/src/jetCheck/Generator.java index 48671d7808f2..59881a141793 100644 --- a/slowCheck/src/slowCheck/Generator.java +++ b/jetCheck/src/jetCheck/Generator.java @@ -1,4 +1,4 @@ -package slowCheck; +package jetCheck; import org.jetbrains.annotations.NotNull; @@ -10,9 +10,10 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; /** - * A generator for objects based on random data from {@link DataStructure}. - * - * @author peter + * A generator for objects based on random data from {@link DataStructure}.

+ * + * Generators for standard types can be obtained using static methods of this class (e.g. {@link #integers}, {@link #listsOf} etc). + * Generators for custom types can be created by deriving them from standard types (e.g. via {@link #map}, {@link #flatMap}, {@link #zipWith}) or by writing your own from scratch ({@link #from(Function)}). */ public final class Generator { private final Function myFunction; @@ -21,23 +22,46 @@ public final class Generator { myFunction = function; } + /** + * Creates a generator from a custom function, that creates objects of the given type based on the data from {@link DataStructure}. + * The generator may call {@link DataStructure#drawInt} methods directly (and interpret those ints in any way it wishes), + * or invoke other generators using {@link #generateValue(DataStructure)}.

+ * + * When a property is falsified, the DataStructure is attempted to be minimized, and the generator will be run on + * ever "smaller" versions of it, this enables automatic minimization on all kinds of generated types.

+ * + * To ensure test reproducibility during re-run or minimization phase, generators must not have any internal state. + * Their result should only depend on the DataStructure. + */ @NotNull public static Generator from(@NotNull Function function) { return new Generator<>(function); } + /** + * Generates a value inside the given data structure. + */ public T generateValue(@NotNull DataStructure data) { return myFunction.apply(data.subStructure()); } - public T generateUnstructured(@NotNull DataStructure data) { - return myFunction.apply(data); + Function getGeneratorFunction() { + return myFunction; } - + + /** + * Invokes "this" generator, and then applies the given function to transform the generated value in any way. + * The function should not depend on anything besides its argument. + */ public Generator map(@NotNull Function fun) { return from(data -> fun.apply(myFunction.apply(data))); } - + + /** + * Invokes "this" generator, and then applies the given function to provide a new generator that additionally + * depends on the generated value. + * The function should not depend on anything besides its argument. + */ public Generator flatMap(@NotNull Function> fun) { return from(data -> { T value = generateValue(data); @@ -46,11 +70,21 @@ public final class Generator { return result.generateValue(data); }); } - + + /** + * Turns off automatic minimization for the data produced by this generator (and its components, if any). + * This can be useful to speed up minimization by not wasting time on shrinking objects where it makes no sense. + */ public Generator noShrink() { return from(data -> data.generateNonShrinkable(this)); } + /** + * Skips all generated data that doesn't satisfy the given condition. Useful to avoid infrequent corner cases. + * If the condition fails too often, data generation is stopped prematurely due to inability to produce the data.

+ * + * To eliminate large portions of search space, consider changing the generator instead of using {@code suchThat}. + */ public Generator suchThat(@NotNull Predicate condition) { return from(data -> data.generateConditional(this, condition)); } @@ -59,24 +93,29 @@ public final class Generator { // common generators // ------------------------------------ + /** A generator that always returns the same value */ public static Generator constant(T value) { return from(data -> value); } + /** A generator that returns one of the given values with equal probability */ @SafeVarargs public static Generator sampledFrom(T... values) { return sampledFrom(Arrays.asList(values)); } + /** A generator that returns one of the given values with equal probability */ public static Generator sampledFrom(List values) { return anyOf(values.stream().map(Generator::constant).collect(Collectors.toList())); } + /** Delegates to one of the given generators with equal probability */ @SafeVarargs public static Generator anyOf(Generator... alternatives) { return anyOf(Arrays.asList(alternatives)); } + /** Delegates to one of the given generators with equal probability */ public static Generator anyOf(List> alternatives) { if (alternatives.isEmpty()) throw new IllegalArgumentException("No alternatives to choose from"); return from(data -> { @@ -85,6 +124,7 @@ public final class Generator { }); } + /** Delegates one of the two generators with probability corresponding to their weights */ public static Generator frequency(int weight1, Generator alternative1, int weight2, Generator alternative2) { Map> alternatives = new HashMap<>(); @@ -93,6 +133,7 @@ public final class Generator { return frequency(alternatives); } + /** Delegates one of the three generators with probability corresponding to their weights */ public static Generator frequency(int weight1, Generator alternative1, int weight2, Generator alternative2, int weight3, Generator alternative3) { @@ -102,54 +143,65 @@ public final class Generator { alternatives.put(weight3, alternative3); return frequency(alternatives); } - + + /** Delegates one of the generators from the map, with probability corresponding to the map keys */ public static Generator frequency(Map> alternatives) { List weights = new ArrayList<>(alternatives.keySet()); IntDistribution distribution = IntDistribution.frequencyDistribution(weights); return from(data -> alternatives.get(weights.get(data.drawInt(distribution))).generateValue(data)); } + /** Gets the data from two generators and invokes the given function to produce a result based on the two generated values. */ public static Generator zipWith(Generator gen1, Generator gen2, BiFunction zip) { return from(data -> zip.apply(gen1.generateValue(data), gen2.generateValue(data))); } + /** A generator that returns 'true' or 'false' */ public static Generator booleans() { return integers(0, 1).map(i -> i == 1); } // char generators + /** Generates characters in the given range (both ends inclusive) */ public static Generator charsInRange(char min, char max) { return integers(min, max).map(i -> (char)i.intValue()).noShrink(); } + /** Generates ASCII characters excluding the system ones (lower than 32) */ public static Generator asciiPrintableChars() { return charsInRange((char)32, (char)126); } + /** Generates uppercase latin letters */ public static Generator asciiUppercaseChars() { return charsInRange('A', 'Z'); } + /** Generates lowercase latin letters */ public static Generator asciiLowercaseChars() { return charsInRange('a', 'z'); } + /** Generates latin letters, with a preference for lowercase ones */ public static Generator asciiLetters() { return frequency(9, asciiLowercaseChars(), 1, asciiUppercaseChars()).noShrink(); } + /** Generates decimal digits */ public static Generator digits() { return charsInRange('0', '9'); } // strings + /** Generates (possibly empty) random strings consisting of the given characters (provided as a string) */ public static Generator stringsOf(@NotNull String possibleChars) { List chars = IntStream.range(0, possibleChars.length()).mapToObj(possibleChars::charAt).collect(Collectors.toList()); return stringsOf(sampledFrom(chars)); } + /** Generates (possibly empty) random strings consisting of characters provided by the given generator */ public static Generator stringsOf(@NotNull Generator charGen) { return listsOf(charGen).map(chars -> { StringBuilder sb = new StringBuilder(); @@ -158,6 +210,7 @@ public final class Generator { }); } + /** Generates random strings consisting ASCII letters and digit and starting with a letter */ public static Generator asciiIdentifiers() { return stringsOf(frequency(50, asciiLetters(), 5, digits(), @@ -168,15 +221,18 @@ public final class Generator { // numbers + /** Generates any integers */ public static Generator integers() { return from(data -> data.drawInt()); } + /** Generates integers in the given range (both ends inclusive) */ public static Generator integers(int min, int max) { IntDistribution distribution = IntDistribution.uniform(min, max); return from(data -> data.drawInt(distribution)); } + /** Generates any doubles, including infinities and NaN */ public static Generator doubles() { return from(data -> { long i1 = data.drawInt(); @@ -187,14 +243,17 @@ public final class Generator { // lists + /** Generates (possibly empty) lists of values produced by the given generator */ public static Generator> listsOf(Generator itemGenerator) { return from(data -> generateList(itemGenerator, data, data.suggestCollectionSize())); } + /** Generates non-empty lists of values produced by the given generator */ public static Generator> nonEmptyLists(Generator itemGenerator) { return listsOf(itemGenerator).suchThat(l -> !l.isEmpty()); } + /** Generates lists of values produced by the given generator. The list length is determined by the given distribution. */ public static Generator> listsOf(IntDistribution length, Generator itemGenerator) { return from(data -> generateList(itemGenerator, data, data.drawInt(length))); } diff --git a/jetCheck/src/jetCheck/GeneratorException.java b/jetCheck/src/jetCheck/GeneratorException.java new file mode 100644 index 000000000000..0862d8256b41 --- /dev/null +++ b/jetCheck/src/jetCheck/GeneratorException.java @@ -0,0 +1,11 @@ +package jetCheck; + +/** + * @author peter + */ +public class GeneratorException extends RuntimeException { + + GeneratorException(Iteration iteration, Throwable cause) { + super("Exception while generating data, " + iteration.printSeeds(), cause); + } +} diff --git a/slowCheck/src/slowCheck/IntDistribution.java b/jetCheck/src/jetCheck/IntDistribution.java similarity index 75% rename from slowCheck/src/slowCheck/IntDistribution.java rename to jetCheck/src/jetCheck/IntDistribution.java index 23e8abd83e32..41ae1b56ec88 100644 --- a/slowCheck/src/slowCheck/IntDistribution.java +++ b/jetCheck/src/jetCheck/IntDistribution.java @@ -1,14 +1,16 @@ -package slowCheck; +package jetCheck; import java.util.List; import java.util.Random; /** - * @author peter + * Used for generating random int values with custom distribution, and ensuring that minimized integer values don't violate that distribution. */ public interface IntDistribution { + /** Returns an int value distributed in the needed fashion using the given Random */ int generateInt(Random random); + /** @return true if the given value is valid for this distribution */ boolean isValidValue(int i); /** @@ -42,6 +44,10 @@ public interface IntDistribution { }; } + /** + * The distribution for numbers in {@code [0, ..., weights.size()-1]} range, where the probability of {@code i} + * is equal to {@code weights.get(i)/sum(weights)}. + */ static IntDistribution frequencyDistribution(List weights) { if (weights.isEmpty()) throw new IllegalArgumentException("No alternatives to choose from"); diff --git a/jetCheck/src/jetCheck/Iteration.java b/jetCheck/src/jetCheck/Iteration.java new file mode 100644 index 000000000000..058db22eb140 --- /dev/null +++ b/jetCheck/src/jetCheck/Iteration.java @@ -0,0 +1,110 @@ +package jetCheck; + +import org.jetbrains.annotations.Nullable; + +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +import java.util.function.IntUnaryOperator; +import java.util.function.Predicate; + +class Iteration { + + private static final Predicate DATA_IS_DIFFERENT = new Predicate() { + @Override + public boolean test(Object o) { + return false; + } + + @Override + public String toString() { + return ": cannot generate enough sufficiently different values"; + } + }; + + final CheckSession session; + final long iterationSeed; + final int sizeHint; + final int iterationNumber; + + Iteration(CheckSession session, long iterationSeed, int iterationNumber) { + this.session = session; + this.iterationSeed = iterationSeed; + this.sizeHint = session.sizeHintFun.applyAsInt(iterationNumber); + this.iterationNumber = iterationNumber; + if (sizeHint < 0) { + throw new IllegalArgumentException("Size hint should be non-negative, found " + sizeHint); + } + } + + @Nullable + private CounterExampleImpl findCounterExample(Random random) { + for (int i = 0; i < 100; i++) { + StructureNode node = new StructureNode(); + T value; + try { + value = session.generator.getGeneratorFunction().apply(new GenerativeDataStructure(random, node, sizeHint)); + } + catch (Throwable e) { + throw new GeneratorException(this, e); + } + if (!session.generatedHashes.add(node.hashCode())) continue; + + return CounterExampleImpl.checkProperty(session.property, value, node); + } + throw new CannotSatisfyCondition(DATA_IS_DIFFERENT); + } + + String printToReproduce() { + return "To reproduce the last iteration, run PropertyChecker.forAll(...).rechecking(" + iterationSeed + "L, " + sizeHint + ").shouldHold(...)\n" + + "Global seed: " + session.globalSeed + "L"; + } + + String printSeeds() { + return "iteration seed=" + iterationSeed + "L, " + + "size hint=" + sizeHint + ", " + + "global seed=" + session.globalSeed + "L"; + } + + @Nullable + Iteration performIteration() { + session.notifier.iterationStarted(iterationNumber); + + Random random = new Random(iterationSeed); + CounterExampleImpl example = findCounterExample(random); + if (example != null) { + session.notifier.counterExampleFound(this); + PropertyFailureImpl failure = new PropertyFailureImpl<>(example, this); + throw new PropertyFalsified(failure, () -> new ReplayDataStructure(failure.getMinimalCounterexample().data, sizeHint)); + } + + if (iterationNumber >= session.iterationCount) { + return null; + } + + return new Iteration<>(session, random.nextLong(), iterationNumber + 1); + } +} + +class CheckSession { + final Generator generator; + final Predicate property; + final long globalSeed; + final Set generatedHashes = new HashSet<>(); + final StatusNotifier notifier; + final int iterationCount; + final IntUnaryOperator sizeHintFun; + + CheckSession(Generator generator, Predicate property, long globalSeed, int iterationCount, IntUnaryOperator sizeHintFun) { + this.generator = generator; + this.property = property; + this.globalSeed = globalSeed; + this.iterationCount = iterationCount; + this.sizeHintFun = sizeHintFun; + notifier = new StatusNotifier(iterationCount); + } + + Iteration firstIteration() { + return new Iteration<>(this, globalSeed, 1); + } +} diff --git a/jetCheck/src/jetCheck/PropertyChecker.java b/jetCheck/src/jetCheck/PropertyChecker.java new file mode 100644 index 000000000000..1703f93582f4 --- /dev/null +++ b/jetCheck/src/jetCheck/PropertyChecker.java @@ -0,0 +1,81 @@ +package jetCheck; + +import org.jetbrains.annotations.NotNull; + +import java.util.Random; +import java.util.function.IntUnaryOperator; +import java.util.function.Predicate; + +/** + * An entry point to property-based testing. The main usage pattern: {@code PropertyChecker.forAll(generator).shouldHold(property)}. + */ +public class PropertyChecker { + private final Generator generator; + private long globalSeed = new Random().nextLong(); + private IntUnaryOperator sizeHintFun = iteration -> (iteration - 1) % 100 + 1; + private int iterationCount = 100; + + private PropertyChecker(Generator generator) { + this.generator = generator; + } + + /** + * Creates a property checker for the given generator. It can be further customized using {@code with*}-methods, + * and should finally used for property to check via {@link #shouldHold(Predicate)} call. + */ + public static PropertyChecker forAll(Generator generator) { + return new PropertyChecker<>(generator); + } + + /** + * This function allows to start the test with a fixed random seed. It's useful to reproduce some previous test run and debug it. + * @param seed A random seed to use for the first iteration. + * The following iterations will use other, pseudo-random seeds, but still derived from this one. + * @return this PropertyChecker + */ + public PropertyChecker withSeed(long seed) { + globalSeed = seed; + return this; + } + + /** + * @param iterationCount the number of iterations to try. By default it's 100. + * @return this PropertyChecker + */ + public PropertyChecker withIterationCount(int iterationCount) { + this.iterationCount = iterationCount; + return this; + } + + /** + * @param sizeHintFun a function determining how size hint should be distributed depending on the iteration number. + * By default the size hint will be 1 in the first iteration, 2 in the second one, and so on until 100, + * then again 1,...,100,1,...,100, etc. + * @return this PropertyChecker + * @see DataStructure#getSizeHint() + */ + public PropertyChecker withSizeHint(@NotNull IntUnaryOperator sizeHintFun) { + this.sizeHintFun = sizeHintFun; + return this; + } + + /** + * Checks the property within a single iteration by using specified seed and size hint. Useful to debug the test after it's failed. + */ + public PropertyChecker rechecking(long seed, int sizeHint) { + return withSeed(seed).withSizeHint(whatever -> sizeHint).withIterationCount(1); + } + + /** + * Checks that the given property returns {@code true} and doesn't throw exceptions by running the generator and the property + * given number of times (see {@link #withIterationCount(int)}). + */ + public void shouldHold(@NotNull Predicate property) { + Iteration iteration = new CheckSession<>(generator, property, globalSeed, iterationCount, sizeHintFun).firstIteration(); + while (iteration != null) { + iteration = iteration.performIteration(); + } + } + +} + diff --git a/slowCheck/src/slowCheck/PropertyFailure.java b/jetCheck/src/jetCheck/PropertyFailure.java similarity index 79% rename from slowCheck/src/slowCheck/PropertyFailure.java rename to jetCheck/src/jetCheck/PropertyFailure.java index 60f215c26cab..ce0acd690597 100644 --- a/slowCheck/src/slowCheck/PropertyFailure.java +++ b/jetCheck/src/jetCheck/PropertyFailure.java @@ -1,4 +1,4 @@ -package slowCheck; +package jetCheck; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -19,6 +19,14 @@ public interface PropertyFailure { int getTotalMinimizationExampleCount(); int getMinimizationStageCount(); + + int getIterationNumber(); + + long getIterationSeed(); + + long getGlobalSeed(); + + int getSizeHint(); interface CounterExample { T getExampleValue(); diff --git a/jetCheck/src/jetCheck/PropertyFailureImpl.java b/jetCheck/src/jetCheck/PropertyFailureImpl.java new file mode 100644 index 000000000000..b15356a68ff8 --- /dev/null +++ b/jetCheck/src/jetCheck/PropertyFailureImpl.java @@ -0,0 +1,97 @@ +package jetCheck; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +class PropertyFailureImpl implements PropertyFailure { + private final CounterExampleImpl initial; + private CounterExampleImpl minimized; + private int totalSteps; + private int successfulSteps; + final Iteration iteration; + private Throwable stoppingReason; + + PropertyFailureImpl(@NotNull CounterExampleImpl initial, Iteration iteration) { + this.initial = initial; + this.minimized = initial; + this.iteration = iteration; + try { + shrink(); + } + catch (Throwable e) { + stoppingReason = e; + } + } + + @NotNull + @Override + public CounterExampleImpl getFirstCounterExample() { + return initial; + } + + @NotNull + @Override + public CounterExampleImpl getMinimalCounterexample() { + return minimized; + } + + @Nullable + @Override + public Throwable getStoppingReason() { + return stoppingReason; + } + + @Override + public int getTotalMinimizationExampleCount() { + return totalSteps; + } + + @Override + public int getMinimizationStageCount() { + return successfulSteps; + } + + @Override + public int getIterationNumber() { + return iteration.iterationNumber; + } + + @Override + public long getIterationSeed() { + return iteration.iterationSeed; + } + + @Override + public long getGlobalSeed() { + return iteration.session.globalSeed; + } + + @Override + public int getSizeHint() { + return iteration.sizeHint; + } + + private void shrink() { + minimized.data.shrink(node -> { + if (!iteration.session.generatedHashes.add(node.hashCode())) return false; + + iteration.session.notifier.shrinkAttempt(this, iteration); + + try { + T value = iteration.session.generator.getGeneratorFunction().apply(new ReplayDataStructure((StructureNode)node, iteration.sizeHint)); + totalSteps++; + CounterExampleImpl example = CounterExampleImpl.checkProperty(iteration.session.property, value, (StructureNode)node); + if (example != null) { + minimized = example; + successfulSteps++; + return true; + } + return false; + } + catch (CannotRestoreValue e) { + return false; + } + + }); + } +} diff --git a/slowCheck/src/slowCheck/PropertyFalsified.java b/jetCheck/src/jetCheck/PropertyFalsified.java similarity index 90% rename from slowCheck/src/slowCheck/PropertyFalsified.java rename to jetCheck/src/jetCheck/PropertyFalsified.java index ec8803ed2bad..abb0edd35518 100644 --- a/slowCheck/src/slowCheck/PropertyFalsified.java +++ b/jetCheck/src/jetCheck/PropertyFalsified.java @@ -1,4 +1,4 @@ -package slowCheck; +package jetCheck; import java.util.ArrayList; import java.util.List; @@ -11,13 +11,11 @@ import java.util.function.Supplier; public class PropertyFalsified extends RuntimeException { static final String FAILURE_REASON_HAS_CHANGED_DURING_MINIMIZATION = "!!! FAILURE REASON HAS CHANGED DURING MINIMIZATION !!!"; private static final String SEPARATOR = "\n==========================\n"; - private final long seed; - private final PropertyFailure failure; + private final PropertyFailureImpl failure; private final Supplier data; - PropertyFalsified(long seed, PropertyFailure failure, Supplier data) { + PropertyFalsified(PropertyFailureImpl failure, Supplier data) { super(failure.getMinimalCounterexample().getExceptionCause()); - this.seed = seed; this.failure = failure; this.data = data; } @@ -26,7 +24,7 @@ public class PropertyFalsified extends RuntimeException { public String getMessage() { String msg = "Falsified on " + failure.getMinimalCounterexample().getExampleValue() + "\n" + getMinimizationStats() + - "Seed=" + seed; + failure.iteration.printToReproduce(); if (failure.getStoppingReason() != null) { msg += "\n Shrinking stopped because of " + StatusNotifier.printStackTrace(failure.getStoppingReason()); @@ -71,7 +69,7 @@ public class PropertyFalsified extends RuntimeException { List result = new ArrayList<>(); for (StackTraceElement element : e.getStackTrace()) { String s = element.toString(); - if (s.startsWith("slowCheck.CounterExampleImpl.checkProperty")) { + if (s.startsWith("jetCheck.CounterExampleImpl.checkProperty")) { break; } result.add(s); diff --git a/slowCheck/src/slowCheck/ReplayDataStructure.java b/jetCheck/src/jetCheck/ReplayDataStructure.java similarity index 98% rename from slowCheck/src/slowCheck/ReplayDataStructure.java rename to jetCheck/src/jetCheck/ReplayDataStructure.java index 5f1f647599ad..46bbf014fe5c 100644 --- a/slowCheck/src/slowCheck/ReplayDataStructure.java +++ b/jetCheck/src/jetCheck/ReplayDataStructure.java @@ -1,4 +1,4 @@ -package slowCheck; +package jetCheck; import org.jetbrains.annotations.NotNull; diff --git a/slowCheck/src/slowCheck/StatusNotifier.java b/jetCheck/src/jetCheck/StatusNotifier.java similarity index 86% rename from slowCheck/src/slowCheck/StatusNotifier.java rename to jetCheck/src/jetCheck/StatusNotifier.java index d9122682aa47..420beb6bdb58 100644 --- a/slowCheck/src/slowCheck/StatusNotifier.java +++ b/jetCheck/src/jetCheck/StatusNotifier.java @@ -1,4 +1,4 @@ -package slowCheck; +package jetCheck; import org.jetbrains.annotations.NotNull; @@ -15,13 +15,11 @@ import java.util.Locale; @SuppressWarnings("UseOfSystemOutOrSystemErr") class StatusNotifier { private final int iterationCount; - private final long seed; private int currentIteration; private long lastPrinted = System.currentTimeMillis(); - StatusNotifier(int iterationCount, long seed) { + StatusNotifier(int iterationCount) { this.iterationCount = iterationCount; - this.seed = seed; } void iterationStarted(int iteration) { @@ -31,9 +29,9 @@ class StatusNotifier { } } - void counterExampleFound() { + void counterExampleFound(Iteration iteration) { lastPrinted = System.currentTimeMillis(); - System.err.println(formatCurrentTime() + ": failed on iteration " + currentIteration + " (seed=" + seed + "), shrinking..."); + System.err.println(formatCurrentTime() + ": failed on iteration " + currentIteration + " (" + iteration.printSeeds() + "), shrinking..."); } private boolean shouldPrint() { @@ -46,11 +44,11 @@ class StatusNotifier { private int lastReportedStage = -1; private String lastReportedTrace = null; - void shrinkAttempt(PropertyFailure failure) { + void shrinkAttempt(PropertyFailure failure, Iteration iteration) { if (shouldPrint()) { int stage = failure.getMinimizationStageCount(); - System.err.println(formatCurrentTime() + ": still shrinking (seed=" + seed + "). " + - "Examples tried: " + failure.getTotalMinimizationExampleCount() + + System.out.println(formatCurrentTime() + ": still shrinking (" + iteration.printSeeds() + "). " + + "Examples tried: " + failure.getTotalMinimizationExampleCount() + ", successful minimizations: " + stage); if (lastReportedStage != stage) { lastReportedStage = stage; diff --git a/jetCheck/src/jetCheck/StructureNode.java b/jetCheck/src/jetCheck/StructureNode.java new file mode 100644 index 000000000000..0b6709ad1a0e --- /dev/null +++ b/jetCheck/src/jetCheck/StructureNode.java @@ -0,0 +1,174 @@ +package jetCheck; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * @author peter + */ +interface StructureElement { + void shrink(Predicate suitable); +} + +class StructureNode implements StructureElement { + final List children; + boolean shrinkProhibited; + + StructureNode() { + this(new ArrayList<>()); + } + + StructureNode(List children) { + this.children = children; + } + + Iterator childrenIterator() { + return children.iterator(); + } + + void addChild(StructureElement child) { + children.add(child); + } + + StructureNode subStructure() { + StructureNode e = new StructureNode(); + addChild(e); + return e; + } + + void removeLastChild(StructureNode node) { + if (children.isEmpty() || children.get(children.size() - 1) != node) { + throw new IllegalStateException("Last sub-structure changed"); + } + children.remove(children.size() - 1); + } + + @Override + public void shrink(Predicate suitable) { + if (shrinkProhibited) return; + + List children = this.children; + boolean isList = isList(); + if (isList) { + children = shrinkList(suitable, children); + } + + for (int i = isList ? 1 : 0; i < children.size(); i++) { + children = shrinkChild(suitable, children, i); + } + } + + private static List shrinkChild(Predicate suitable, List children, int index) { + List result = new ArrayList<>(children); + children.get(index).shrink(less -> { + List copy = new ArrayList<>(result); + copy.set(index, less); + if (suitable.test(new StructureNode(copy))) { + result.set(index, less); + return true; + } + return false; + }); + return result; + } + + private static List shrinkList(Predicate suitable, List listChildren) { + int start = 1; + int length = 1; + int limit = listChildren.size(); + while (limit > 0) { + int lastSuccessfulRemove = -1; + while (start < limit && start < listChildren.size()) { + StructureNode less = removeRange(listChildren, start, length); + if (suitable.test(less)) { + listChildren = less.children; + length = Math.min(length * 2, listChildren.size() - start); + lastSuccessfulRemove = start; + } else { + if (length > 1) { + length /= 2; + } else { + start++; + } + } + } + limit = lastSuccessfulRemove; + } + return listChildren; + } + + @NotNull + private static StructureNode removeRange(List listChildren, int start, int length) { + int newSize = listChildren.size() - length - 1; + List lessItems = new ArrayList<>(newSize + 1); + lessItems.add(new IntData(newSize, IntDistribution.uniform(0, newSize))); + lessItems.addAll(listChildren.subList(1, start)); + lessItems.addAll(listChildren.subList(start + length, listChildren.size())); + return new StructureNode(lessItems); + } + + private boolean isList() { + if (!children.isEmpty() && + children.get(0) instanceof IntData && ((IntData)children.get(0)).value == children.size() - 1) { + for (int i = 1; i < children.size(); i++) { + if (!(children.get(i) instanceof StructureNode)) return false; + } + return true; + } + return false; + } + + @Override + public int hashCode() { + return children.hashCode(); + } + + @Override + public String toString() { + return "(" + children.stream().map(Object::toString).collect(Collectors.joining(", ")) + ")"; + } + +} + +class IntData implements StructureElement { + final int value; + final IntDistribution distribution; + + IntData(int value, IntDistribution distribution) { + this.value = value; + this.distribution = distribution; + } + + @Override + public void shrink(Predicate suitable) { + if (value == 0 || tryInt(0, suitable)) return; + + int value = this.value; + if (value < 0 && tryInt(-value, suitable)) { + value = -value; + } + while (value != 0 && tryInt(value / 2, suitable)) { + value /= 2; + } + } + + private boolean tryInt(int value, Predicate suitable) { + return distribution.isValidValue(value) && suitable.test(new IntData(value, distribution)); + } + + + @Override + public String toString() { + return String.valueOf(value); + } + + @Override + public int hashCode() { + return value; + } +} \ No newline at end of file diff --git a/jetCheck/test/jetCheck/ExceptionTest.java b/jetCheck/test/jetCheck/ExceptionTest.java new file mode 100644 index 000000000000..1d056168fad5 --- /dev/null +++ b/jetCheck/test/jetCheck/ExceptionTest.java @@ -0,0 +1,68 @@ +package jetCheck; + +import static jetCheck.Generator.*; + +/** + * @author peter + */ +public class ExceptionTest extends PropertyCheckerTestCase { + + public void testFailureReasonUnchanged() { + PropertyFalsified e = checkFails(forAllStable(integers()), i -> { + throw new AssertionError("fail"); + }); + + assertFalse(e.getMessage().contains(PropertyFalsified.FAILURE_REASON_HAS_CHANGED_DURING_MINIMIZATION)); + } + + public void testFailureReasonChangedExceptionClass() { + PropertyFalsified e = checkFails(forAllStable(integers()), i -> { + throw (i == 0 ? new RuntimeException("fail") : new IllegalArgumentException("fail")); + }); + assertTrue(e.getMessage().contains(PropertyFalsified.FAILURE_REASON_HAS_CHANGED_DURING_MINIMIZATION)); + } + + public void testFailureReasonChangedExceptionTrace() { + PropertyFalsified e = checkFails(forAllStable(integers()), i -> { + if (i == 0) { + throw new AssertionError("fail"); + } + else { + throw new AssertionError("fail2"); + } + }); + assertTrue(e.getMessage().contains(PropertyFalsified.FAILURE_REASON_HAS_CHANGED_DURING_MINIMIZATION)); + } + + public void testExceptionWhileGeneratingValue() { + try { + forAllStable(from(data -> { + throw new AssertionError("fail"); + })).shouldHold(i -> true); + fail(); + } + catch (GeneratorException ignore) { + } + } + + public void testExceptionWhileShrinkingValue() { + PropertyFalsified e = checkFails(PropertyChecker.forAll(listsOf(integers()).suchThat(l -> { + if (l.size() == 1 && l.get(0) == 0) throw new RuntimeException("my exception"); + return true; + })), l -> l.stream().allMatch(i -> i > 0)); + + assertEquals("my exception", e.getFailure().getStoppingReason().getMessage()); + assertTrue(StatusNotifier.printStackTrace(e).contains("my exception")); + } + + public void testUnsatisfiableSuchThat() { + try { + PropertyChecker.forAll(integers(-1, 1).suchThat(i -> i > 2)).shouldHold(i -> i == 0); + fail(); + } + catch (GeneratorException e) { + assertTrue(e.getCause() instanceof CannotSatisfyCondition); + } + } + +} diff --git a/slowCheck/test/slowCheck/GeneratorTest.java b/jetCheck/test/jetCheck/GeneratorTest.java similarity index 57% rename from slowCheck/test/slowCheck/GeneratorTest.java rename to jetCheck/test/jetCheck/GeneratorTest.java index ad83d8c2a6a3..da40d3d44074 100644 --- a/slowCheck/test/slowCheck/GeneratorTest.java +++ b/jetCheck/test/jetCheck/GeneratorTest.java @@ -1,12 +1,13 @@ -package slowCheck; +package jetCheck; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; -import static slowCheck.Generator.*; +import static jetCheck.Generator.*; /** * @author peter @@ -28,37 +29,39 @@ public class GeneratorTest extends PropertyCheckerTestCase { public void testListContainsDivisible() { checkFalsified(nonEmptyLists(integers()), l -> l.stream().allMatch(i -> i % 10 != 0), - 9); + 3); } public void testStringContains() { - checkFalsified(stringsOf(asciiPrintableChars()), - s -> !s.contains("a"), - 10); + PropertyFailure failure = checkFalsified(stringsOf(asciiPrintableChars()), + s -> !s.contains("a"), + 17); + assertEquals("a", failure.getMinimalCounterexample().getExampleValue()); } public void testLetterStringContains() { checkFalsified(stringsOf(asciiLetters()), s -> !s.contains("a"), - 5); + 3); } public void testIsSorted() { PropertyFailure> failure = checkFalsified(nonEmptyLists(integers()), l -> l.stream().sorted().collect(Collectors.toList()).equals(l), - 69); - assertEquals(2, failure.getMinimalCounterexample().getExampleValue().size()); + 35); + List value = failure.getMinimalCounterexample().getExampleValue(); + assertEquals(2, value.size()); + assertTrue(value.toString(), value.stream().allMatch(i -> Math.abs(i) < 2)); } public void testSuccess() { - PropertyChecker.forAll(listsOf(integers(-1, 1)), - l -> l.stream().allMatch(i -> Math.abs(i) <= 1)); + PropertyChecker.forAll(listsOf(integers(-1, 1))).shouldHold(l -> l.stream().allMatch(i -> Math.abs(i) <= 1)); } public void testSortedDoublesNonDescending() { PropertyFailure> failure = checkFalsified(listsOf(doubles()), l -> isSorted(l.stream().sorted().collect(Collectors.toList())), - 141); + 58); assertEquals(2, failure.getMinimalCounterexample().getExampleValue().size()); } @@ -72,41 +75,41 @@ public class GeneratorTest extends PropertyCheckerTestCase { } public void testSuchThat() { - PropertyChecker.forAll(integers().suchThat(i -> i < 0), i -> i < 0); + PropertyChecker.forAll(integers().suchThat(i -> i < 0)).shouldHold(i -> i < 0); } public void testStringOfStringChecksAllChars() { checkFalsified(stringsOf("abc "), s -> !s.contains(" "), - 3); + 4); } public void testLongListsHappen() { PropertyFailure> failure = checkFalsified(listsOf(integers()), l -> l.size() < 200, - 631); + 419); assertEquals(200, failure.getMinimalCounterexample().getExampleValue().size()); } public void testNonEmptyList() { - PropertyChecker.forAll(nonEmptyLists(integers()), l -> !l.isEmpty()); + PropertyChecker.forAll(nonEmptyLists(integers())).shouldHold(l -> !l.isEmpty()); } public void testNoDuplicateData() { Set> visited = new HashSet<>(); - PropertyChecker.forAll(listsOf(integers()), l -> visited.add(l)); + PropertyChecker.forAll(listsOf(integers())).shouldHold(l -> visited.add(l)); } public void testOneOf() { List values = new ArrayList<>(); - PropertyChecker.forAll(anyOf(integers(0, 1), integers(10, 1100)), i -> values.add(i)); + PropertyChecker.forAll(anyOf(integers(0, 1), integers(10, 1100))).shouldHold(i -> values.add(i)); assertTrue(values.stream().anyMatch(i -> i < 2)); assertTrue(values.stream().anyMatch(i -> i > 5)); } public void testAsciiIdentifier() { - PropertyChecker.forAll(asciiIdentifiers(), - s -> Character.isJavaIdentifierStart(s.charAt(0)) && s.chars().allMatch(Character::isJavaIdentifierPart)); + PropertyChecker.forAll(asciiIdentifiers()) + .shouldHold(s -> Character.isJavaIdentifierStart(s.charAt(0)) && s.chars().allMatch(Character::isJavaIdentifierPart)); checkFalsified(asciiIdentifiers(), s -> !s.contains("_"), 1); @@ -115,15 +118,40 @@ public class GeneratorTest extends PropertyCheckerTestCase { public void testBoolean() { PropertyFailure> failure = checkFalsified(listsOf(booleans()), l -> !l.contains(true) || !l.contains(false), - 4); + 2); assertEquals(2, failure.getMinimalCounterexample().getExampleValue().size()); } public void testShrinkingNonEmptyList() { PropertyFailure> failure = checkFalsified(nonEmptyLists(integers(0, 100)), l -> !l.contains(42), - 10); + 4); assertEquals(1, failure.getMinimalCounterexample().getExampleValue().size()); } + public void testRecheckWithGivenSeeds() { + Generator> gen = nonEmptyLists(integers(0, 100)); + Predicate> property = l -> !l.contains(42); + + PropertyFailure failure = checkFails(PropertyChecker.forAll(gen), property).getFailure(); + assertTrue(failure.getIterationNumber() > 1); + + PropertyFalsified e; + + e = checkFails(PropertyChecker.forAll(gen).rechecking(failure.getIterationSeed(), failure.getSizeHint()), property); + assertEquals(1, e.getFailure().getIterationNumber()); + + e = checkFails(PropertyChecker.forAll(gen).withSeed(failure.getGlobalSeed()), property); + assertEquals(failure.getIterationNumber(), e.getFailure().getIterationNumber()); + } + + public void testShrinkingComplexString() { + checkFalsified(listsOf(stringsOf(asciiPrintableChars())), + l -> { + String s = l.toString(); + return !"abcdefghijklmnopqrstuvwxyz()[]#!".chars().allMatch(c -> s.indexOf((char)c) >= 0); + }, + 153); + } + } diff --git a/jetCheck/test/jetCheck/PropertyCheckerTestCase.java b/jetCheck/test/jetCheck/PropertyCheckerTestCase.java new file mode 100644 index 000000000000..97ee72176ab7 --- /dev/null +++ b/jetCheck/test/jetCheck/PropertyCheckerTestCase.java @@ -0,0 +1,39 @@ +package jetCheck; + +import junit.framework.TestCase; + +import java.util.function.Predicate; + +/** + * @author peter + */ +abstract class PropertyCheckerTestCase extends TestCase { + + protected PropertyFalsified checkFails(PropertyChecker checker, Predicate predicate) { + try { + checker.shouldHold(predicate); + throw new AssertionError("Can't falsify " + getName()); + } + catch (PropertyFalsified e) { + return e; + } + } + + protected PropertyFailure checkFalsified(Generator generator, Predicate predicate, int minimizationSteps) { + PropertyFalsified e = checkFails(forAllStable(generator), predicate); + //noinspection unchecked + PropertyFailure failure = (PropertyFailure)e.getFailure(); + + System.out.println(" " + getName()); + System.out.println("Value: " + e.getBreakingValue()); + System.out.println("Data: " + e.getData()); + assertEquals(minimizationSteps, failure.getTotalMinimizationExampleCount()); + assertEquals(e.getBreakingValue(), generator.getGeneratorFunction().apply(e.getData())); + + return failure; + } + + protected static PropertyChecker forAllStable(Generator generator) { + return PropertyChecker.forAll(generator).withSeed(0); + } +} diff --git a/jps/jps-builders/jps-builders.iml b/jps/jps-builders/jps-builders.iml index 965ec0416e94..1ff957a1691d 100644 --- a/jps/jps-builders/jps-builders.iml +++ b/jps/jps-builders/jps-builders.iml @@ -13,6 +13,7 @@ + diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/JavaBuilderService.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/JavaBuilderService.java index 442a46012710..16062924fd8a 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/JavaBuilderService.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/JavaBuilderService.java @@ -21,6 +21,7 @@ import org.jetbrains.jps.builders.BuildTargetType; import org.jetbrains.jps.builders.java.JavaModuleBuildTargetType; import org.jetbrains.jps.builders.java.ResourcesTargetType; import org.jetbrains.jps.incremental.dependencies.DependencyResolvingBuilder; +import org.jetbrains.jps.incremental.dependencies.ProjectDependenciesResolver; import org.jetbrains.jps.incremental.instrumentation.NotNullInstrumentingBuilder; import org.jetbrains.jps.incremental.instrumentation.RmiStubsGenerator; import org.jetbrains.jps.incremental.java.JavaBuilder; @@ -29,7 +30,6 @@ import org.jetbrains.jps.service.SharedThreadPool; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; /** @@ -42,6 +42,7 @@ public class JavaBuilderService extends BuilderService { List> types = new ArrayList<>(); types.addAll(JavaModuleBuildTargetType.ALL_TYPES); types.addAll(ResourcesTargetType.ALL_TYPES); + types.add(ProjectDependenciesResolver.ProjectDependenciesResolvingTargetType.INSTANCE); return types; } @@ -60,6 +61,6 @@ public class JavaBuilderService extends BuilderService { @NotNull @Override public List> createBuilders() { - return Collections.singletonList(new ResourcesBuilder()); + return Arrays.asList(new ResourcesBuilder(), new ProjectDependenciesResolver()); } } \ No newline at end of file diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/dependencies/DependencyResolvingBuilder.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/dependencies/DependencyResolvingBuilder.java index 2d7d44961acf..321b784b01ae 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/dependencies/DependencyResolvingBuilder.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/dependencies/DependencyResolvingBuilder.java @@ -86,7 +86,7 @@ public class DependencyResolvingBuilder extends ModuleLevelBuilder{ @Override public void chunkBuildStarted(CompileContext context, ModuleChunk chunk) { try { - checkDependencies(context, chunk); + resolveMissingDependencies(context, chunk.getModules()); } catch (Exception e) { context.putUserData(RESOLVE_ERROR_KEY, e); @@ -101,31 +101,36 @@ public class DependencyResolvingBuilder extends ModuleLevelBuilder{ final Exception error = context.getUserData(RESOLVE_ERROR_KEY); if (error != null) { - final StringBuilder builder = new StringBuilder().append("Error resolving dependencies for ").append(chunk.getPresentableShortName()); - Throwable th = error; - final Set processed = new HashSet<>(); - final Set detailsMessage = new HashSet<>(); - while (th != null && processed.add(th)) { - String details = th.getMessage(); - if (th instanceof UnknownHostException) { - details = "Unknown host: " + details; // hack for UnknownHostException - } - if (details != null && detailsMessage.add(details)) { - builder.append(":\n").append(details); - } - th = th.getCause(); - } - final String msg = builder.toString(); - LOG.info(msg, error); - context.processMessage(new CompilerMessage(NAME, BuildMessage.Kind.ERROR, msg)); - return ExitCode.ABORT; + return reportError(context, chunk.getPresentableShortName(), error); } return ExitCode.OK; } - private static void checkDependencies(CompileContext context, ModuleChunk chunk) throws Exception { - final Collection>> libs = getRepositoryLibraries(chunk); + @NotNull + static ExitCode reportError(CompileContext context, String placePresentableName, Exception error) { + final StringBuilder builder = new StringBuilder().append("Error resolving dependencies for ").append(placePresentableName); + Throwable th = error; + final Set processed = new HashSet<>(); + final Set detailsMessage = new HashSet<>(); + while (th != null && processed.add(th)) { + String details = th.getMessage(); + if (th instanceof UnknownHostException) { + details = "Unknown host: " + details; // hack for UnknownHostException + } + if (details != null && detailsMessage.add(details)) { + builder.append(":\n").append(details); + } + th = th.getCause(); + } + final String msg = builder.toString(); + LOG.info(msg, error); + context.processMessage(new CompilerMessage(NAME, BuildMessage.Kind.ERROR, msg)); + return ExitCode.ABORT; + } + + static void resolveMissingDependencies(CompileContext context, Collection modules) throws Exception { + Collection>> libs = getRepositoryLibraries(modules); if (!libs.isEmpty()) { final ArtifactRepositoryManager repoManager = getRepositoryManager(context); for (JpsTypedLibrary> lib : libs) { @@ -140,6 +145,8 @@ public class DependencyResolvingBuilder extends ModuleLevelBuilder{ } } if (!required.isEmpty()) { + context.processMessage(new ProgressMessage("Resolving '" + lib.getName() + "' library...")); + LOG.debug("Downloading missing files for " + lib.getName() + " library: " + required); final Collection resolved = repoManager.resolveDependency(descriptor.getGroupId(), descriptor.getArtifactId(), descriptor.getVersion()); if (!resolved.isEmpty()) { syncPaths(required, resolved); @@ -229,9 +236,9 @@ public class DependencyResolvingBuilder extends ModuleLevelBuilder{ } @NotNull - private static Collection>> getRepositoryLibraries(ModuleChunk chunk) { + private static Collection>> getRepositoryLibraries(Collection modules) { final Collection>> result = new SmartHashSet<>(); - for (JpsModule module : chunk.getModules()) { + for (JpsModule module : modules) { for (JpsDependencyElement dep : module.getDependenciesList().getDependencies()) { if (dep instanceof JpsLibraryDependency) { final JpsLibrary _lib = ((JpsLibraryDependency)dep).getLibrary(); diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/dependencies/ProjectDependenciesResolver.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/dependencies/ProjectDependenciesResolver.java new file mode 100644 index 000000000000..ab2aad0c30c0 --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/dependencies/ProjectDependenciesResolver.java @@ -0,0 +1,133 @@ +/* + * Copyright 2000-2017 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.jetbrains.jps.incremental.dependencies; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.builders.*; +import org.jetbrains.jps.builders.storage.BuildDataPaths; +import org.jetbrains.jps.incremental.CompileContext; +import org.jetbrains.jps.incremental.ProjectBuildException; +import org.jetbrains.jps.incremental.TargetBuilder; +import org.jetbrains.jps.incremental.messages.ProgressMessage; +import org.jetbrains.jps.indices.IgnoredFileIndex; +import org.jetbrains.jps.indices.ModuleExcludeIndex; +import org.jetbrains.jps.model.JpsModel; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * @author nik + */ +public class ProjectDependenciesResolver extends TargetBuilder { + public static final String TARGET_TYPE_ID = "project-dependencies-resolving"; + + public ProjectDependenciesResolver() { + super(Collections.singletonList(ProjectDependenciesResolvingTargetType.INSTANCE)); + } + + @Override + public void build(@NotNull ProjectDependenciesResolvingTarget target, + @NotNull DirtyFilesHolder holder, + @NotNull BuildOutputConsumer outputConsumer, + @NotNull CompileContext context) throws ProjectBuildException, IOException { + context.processMessage(new ProgressMessage("Resolving repository libraries in the project...")); + try { + DependencyResolvingBuilder.resolveMissingDependencies(context, context.getProjectDescriptor().getProject().getModules()); + } + catch (Exception e) { + DependencyResolvingBuilder.reportError(context, "project", e); + } + } + + @NotNull + @Override + public String getPresentableName() { + return "Project Dependencies Resolver"; + } + + public static class ProjectDependenciesResolvingTarget extends BuildTarget { + public ProjectDependenciesResolvingTarget() { + super(ProjectDependenciesResolvingTargetType.INSTANCE); + } + + @Override + public String getId() { + return "project"; + } + + @Override + public Collection> computeDependencies(BuildTargetRegistry targetRegistry, TargetOutputIndex outputIndex) { + return Collections.emptyList(); + } + + @NotNull + @Override + public List computeRootDescriptors(JpsModel model, + ModuleExcludeIndex index, + IgnoredFileIndex ignoredFileIndex, + BuildDataPaths dataPaths) { + return Collections.emptyList(); + } + + @Nullable + @Override + public BuildRootDescriptor findRootDescriptor(String rootId, BuildRootIndex rootIndex) { + return null; + } + + @NotNull + @Override + public String getPresentableName() { + return "Project Dependencies Resolving"; + } + + @NotNull + @Override + public Collection getOutputRoots(CompileContext context) { + return Collections.emptyList(); + } + } + + public static class ProjectDependenciesResolvingTargetType extends BuildTargetType { + public static final ProjectDependenciesResolvingTargetType INSTANCE = new ProjectDependenciesResolvingTargetType(); + + public ProjectDependenciesResolvingTargetType() { + super(TARGET_TYPE_ID); + } + + @NotNull + @Override + public List computeAllTargets(@NotNull JpsModel model) { + return Collections.singletonList(new ProjectDependenciesResolvingTarget()); + } + + @NotNull + @Override + public BuildTargetLoader createLoader(@NotNull JpsModel model) { + return new BuildTargetLoader() { + @Override + public ProjectDependenciesResolvingTarget createTarget(@NotNull String targetId) { + return new ProjectDependenciesResolvingTarget(); + } + }; + } + } +} diff --git a/jps/model-api/src/org/jetbrains/jps/model/library/JpsMavenRepositoryLibraryDescriptor.java b/jps/model-api/src/org/jetbrains/jps/model/library/JpsMavenRepositoryLibraryDescriptor.java index 1c03214fb91c..6f926e660442 100644 --- a/jps/model-api/src/org/jetbrains/jps/model/library/JpsMavenRepositoryLibraryDescriptor.java +++ b/jps/model-api/src/org/jetbrains/jps/model/library/JpsMavenRepositoryLibraryDescriptor.java @@ -81,4 +81,9 @@ public class JpsMavenRepositoryLibraryDescriptor { public int hashCode() { return myMavenId != null ? myMavenId.hashCode() : 0; } + + @Override + public String toString() { + return myMavenId != null ? myMavenId : "null"; + } } diff --git a/platform/analysis-api/src/com/intellij/analysis/AnalysisScope.java b/platform/analysis-api/src/com/intellij/analysis/AnalysisScope.java index 582895262cba..ff670ccd9563 100644 --- a/platform/analysis-api/src/com/intellij/analysis/AnalysisScope.java +++ b/platform/analysis-api/src/com/intellij/analysis/AnalysisScope.java @@ -197,14 +197,9 @@ public class AnalysisScope { @NotNull private FileIndex getFileIndex() { - final FileIndex fileIndex; - if (myModule != null) { - fileIndex = ModuleRootManager.getInstance(myModule).getFileIndex(); - } - else { - fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex(); - } - return fileIndex; + return myModule == null ? + ProjectRootManager.getInstance(myProject).getFileIndex() : + ModuleRootManager.getInstance(myModule).getFileIndex(); } private static String displayProjectRelativePath(@NotNull PsiFileSystemItem item) { @@ -359,12 +354,11 @@ public class AnalysisScope { @NotNull final PsiManager psiManager, final boolean needReadAction, final boolean clearResolveCache) { - final Runnable runnable = () -> doProcessFile(visitor, psiManager, vFile, clearResolveCache); if (needReadAction && !ApplicationManager.getApplication().isDispatchThread()) { - commitAndRunInSmartMode(runnable, psiManager.getProject()); + commitAndRunInSmartMode(() -> doProcessFile(visitor, psiManager, vFile, clearResolveCache), psiManager.getProject()); } else { - runnable.run(); + doProcessFile(visitor, psiManager, vFile, clearResolveCache); } final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); return indicator == null || !indicator.isCanceled(); @@ -404,11 +398,11 @@ public class AnalysisScope { private static void doProcessFile(@NotNull PsiElementVisitor visitor, @NotNull PsiManager psiManager, @NotNull VirtualFile vFile, boolean clearResolveCache) { + ProgressManager.checkCanceled(); if (!vFile.isValid()) return; PsiFile psiFile = psiManager.findFile(vFile); if (psiFile == null || !shouldHighlightFile(psiFile)) return; - psiFile.accept(visitor); if (clearResolveCache) { psiManager.dropResolveCaches(); diff --git a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/GeneralHighlightingPass.java b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/GeneralHighlightingPass.java index 7530caf848cd..3433b7bb17ea 100644 --- a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/GeneralHighlightingPass.java +++ b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/GeneralHighlightingPass.java @@ -444,14 +444,13 @@ public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingP private static void cancelAndRestartDaemonLater(@NotNull ProgressIndicator progress, @NotNull final Project project) throws ProcessCanceledException { progress.cancel(); - if (!ApplicationManager.getApplication().isUnitTestMode()) { - EdtExecutorService.getScheduledExecutorInstance().schedule(() -> { - Application application = ApplicationManager.getApplication(); - if (!project.isDisposed() && !application.isDisposed() && !application.isUnitTestMode()) { - DaemonCodeAnalyzer.getInstance(project).restart(); - } - }, RESTART_DAEMON_RANDOM.nextInt(100), TimeUnit.MILLISECONDS); - } + Application application = ApplicationManager.getApplication(); + int delay = application.isUnitTestMode() ? 0 : RESTART_DAEMON_RANDOM.nextInt(100); + EdtExecutorService.getScheduledExecutorInstance().schedule(() -> { + if (!project.isDisposed()) { + DaemonCodeAnalyzer.getInstance(project).restart(); + } + }, delay, TimeUnit.MILLISECONDS); throw new ProcessCanceledException(); } diff --git a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightingSessionImpl.java b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightingSessionImpl.java index a2da669634b2..07eecd2fc5a8 100644 --- a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightingSessionImpl.java +++ b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/impl/HighlightingSessionImpl.java @@ -16,6 +16,7 @@ package com.intellij.codeInsight.daemon.impl; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.editor.colors.EditorColorsScheme; @@ -53,10 +54,15 @@ public class HighlightingSessionImpl implements HighlightingSession { myEditorColorsScheme = editorColorsScheme; myProject = psiFile.getProject(); myDocument = PsiDocumentManager.getInstance(myProject).getDocument(psiFile); - myEDTQueue = new TransferToEDTQueue<>("Apply highlighting results", runnable -> { + myEDTQueue = new TransferToEDTQueue("Apply highlighting results", runnable -> { runnable.run(); return true; - }, o -> myProject.isDisposed() || getProgressIndicator().isCanceled(), 200); + }, o -> myProject.isDisposed() || getProgressIndicator().isCanceled(), 200) { + @Override + protected void schedule(@NotNull Runnable updateRunnable) { + ApplicationManager.getApplication().invokeLater(updateRunnable, ModalityState.any()); + } + }; } private static final Key> HIGHLIGHTING_SESSION = Key.create("HIGHLIGHTING_SESSION"); diff --git a/platform/bootstrap/src/com/intellij/idea/Main.java b/platform/bootstrap/src/com/intellij/idea/Main.java index 6b6c51c6dfd3..54b9686b444d 100644 --- a/platform/bootstrap/src/com/intellij/idea/Main.java +++ b/platform/bootstrap/src/com/intellij/idea/Main.java @@ -40,6 +40,7 @@ public class Main { @SuppressWarnings("unused") // left for compatibility and reserved for future use public static final int UNSUPPORTED_JAVA_VERSION = 10; public static final int PRIVACY_POLICY_REJECTION = 11; + public static final int INSTALLATION_CORRUPTED = 12; private static final String AWT_HEADLESS = "java.awt.headless"; private static final String PLATFORM_PREFIX_PROPERTY = "idea.platform.prefix"; diff --git a/platform/build-scripts/groovy/org/jetbrains/intellij/build/BuildOptions.groovy b/platform/build-scripts/groovy/org/jetbrains/intellij/build/BuildOptions.groovy index 06abe02a0ad2..72b2dd3f65e0 100644 --- a/platform/build-scripts/groovy/org/jetbrains/intellij/build/BuildOptions.groovy +++ b/platform/build-scripts/groovy/org/jetbrains/intellij/build/BuildOptions.groovy @@ -52,8 +52,12 @@ class BuildOptions { static final SEARCHABLE_OPTIONS_INDEX_STEP = "search_index" static final PROVIDED_MODULES_LIST_STEP = "provided_modules_list" static final SOURCES_ARCHIVE_STEP = "sources_archive" + /** build Mac OS X artifacts */ + static final MAC_ARTIFACTS = "mac_artifacts" /** product DMG file for Mac OS X. If it is skipped only sit archive will be produced. */ static final MAC_DMG_STEP = "mac_dmg" + /** product TAR.GZ file for Linux. */ + static final LINUX_TAR_GZ_STEP = "linux_targz" /** sign additional binary files in Mac OS X distribution */ static final MAC_SIGN_STEP = "mac_sign" /** create *.exe installer for Windows distribution. If it is skipped only zip archive will be produced. */ @@ -68,6 +72,12 @@ class BuildOptions { public static final String BUILD_DMG_WITHOUT_BUNDLED_JRE = "intellij.build.dmg.without.bundled.jre" boolean buildDmgWithoutBundledJre = SystemProperties.getBooleanProperty(BUILD_DMG_WITHOUT_BUNDLED_JRE, SystemProperties.getBooleanProperty("artifact.mac.no.jdk", false)) + /** + * Pass 'true' to this system property to produce .snap packages. + * A build configuration should have "docker.version >= 17" in requirements. + */ + boolean buildUnixSnaps = SystemProperties.getBooleanProperty("intellij.build.unix.snaps", false) + /** * Path to a zip file containing 'production' and 'test' directories with compiled classes of the project modules inside. */ diff --git a/platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityLibraryLicenses.groovy b/platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityLibraryLicenses.groovy index 58b6b8453431..479374ef99af 100644 --- a/platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityLibraryLicenses.groovy +++ b/platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityLibraryLicenses.groovy @@ -197,11 +197,11 @@ class CommunityLibraryLicenses { licenseUrl: "http://www.eclipse.org/org/documents/edl-v10.php", url: "https://github.com/FasterXML/jackson"), new LibraryLicense(name: "JGit", libraryName: "jgit", version: "4.0", license: "Eclipse Distribution License 1.0", licenseUrl: "http://www.eclipse.org/org/documents/edl-v10.php", url: "https://eclipse.org/jgit/"), - new LibraryLicense(name: "JGoodies Common", libraryName: "jgoodies-common", version: "1.2.1", license: "BSD ", + new LibraryLicense(name: "JGoodies Common", libraryName: "jgoodies-common", version: "1.2.1", license: "BSD", url: "http://www.jgoodies.com/freeware/libraries/looks/", licenseUrl: "https://opensource.org/licenses/bsd-license.html"), - new LibraryLicense(name: "JGoodies Forms", libraryName: "jgoodies-forms", version: "1.1-preview 2006-05-04 11:55:37", license: "BSD ", + new LibraryLicense(name: "JGoodies Forms", libraryName: "jgoodies-forms", version: "1.1-preview 2006-05-04 11:55:37", license: "BSD", url: "http://www.jgoodies.com/freeware/libraries/forms/", licenseUrl: "https://opensource.org/licenses/bsd-license.html"), - new LibraryLicense(name: "JGoodies Looks", libraryName: "jgoodies-looks", version: "2.4.2", license: "BSD ", + new LibraryLicense(name: "JGoodies Looks", libraryName: "jgoodies-looks", version: "2.4.2", license: "BSD", url: "http://www.jgoodies.com/freeware/libraries/looks/", licenseUrl: "https://opensource.org/licenses/bsd-license.html"), new LibraryLicense(name: "jgraphx", libraryName: "jgraphx-3.4.0.1", version: "3.4.0.1", license: "BSD", url: "https://github.com/jgraph/jgraphx"), @@ -289,7 +289,7 @@ class CommunityLibraryLicenses { licenseUrl: "http://apache.org/licenses/LICENSE-2.0"), new LibraryLicense(name: "opentest4j", libraryName: "opentest4j", version: "1.0.0", license: "Apache 2.0", url: "https://github.com/ota4j-team/opentest4j", licenseUrl: "http://apache.org/licenses/LICENSE-2.0"), - new LibraryLicense(name: "pep8.py", attachedTo: "python-helpers", version: "1.6.2", license: "MIT", url: "http://pep8.readthedocs.org/"), + new LibraryLicense(name: "pycodestyle.py", attachedTo: "python-helpers", version: "2.3.1", license: "MIT", url: "http://pycodestyle.readthedocs.io/"), new LibraryLicense(name: "PicoContainer", libraryName: "picocontainer", version: "1.2", license: "BSD", url: "https://github.com/codehaus/picocontainer", licenseUrl: "https://opensource.org/licenses/bsd-license.php"), new LibraryLicense(name: "plexus-archiver", libraryName: "plexus-archiver-2.4.4.jar", version: "2.4.4", license: "Apache 2.0", diff --git a/platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityRepositoryModules.groovy b/platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityRepositoryModules.groovy index 240c69c80691..c687d842d85a 100644 --- a/platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityRepositoryModules.groovy +++ b/platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityRepositoryModules.groovy @@ -250,7 +250,7 @@ class CommunityRepositoryModules { withModule("designer", "android.jar") withModule("manifest-merger") withModule("repository") - withModule("common", "android-base-common.jar") + withModule("common", "android-common.jar") withModule("android-common", "android-common.jar", false) withModule("android-rt", "android-rt.jar", false) withModule("android-annotations", "androidAnnotations.jar") diff --git a/platform/build-scripts/groovy/org/jetbrains/intellij/build/LinuxDistributionCustomizer.groovy b/platform/build-scripts/groovy/org/jetbrains/intellij/build/LinuxDistributionCustomizer.groovy index f2505eeb48fa..5b824b3d03a3 100644 --- a/platform/build-scripts/groovy/org/jetbrains/intellij/build/LinuxDistributionCustomizer.groovy +++ b/platform/build-scripts/groovy/org/jetbrains/intellij/build/LinuxDistributionCustomizer.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -37,6 +37,12 @@ abstract class LinuxDistributionCustomizer { */ boolean buildTarGzWithoutBundledJre = true + /** + * If a .snap package should be produced + */ + String snapName = null + String snapDescription = null + /** * Name of the root directory inside linux .tar.gz archive */ diff --git a/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/CrossPlatformDistributionBuilder.groovy b/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/CrossPlatformDistributionBuilder.groovy index ba9494402aa4..3960564e2dce 100644 --- a/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/CrossPlatformDistributionBuilder.groovy +++ b/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/CrossPlatformDistributionBuilder.groovy @@ -55,7 +55,7 @@ class CrossPlatformDistributionBuilder { mapper(type: "glob", from: "*.jnilib", to: "*.dylib") } - String targetPath = "$buildContext.paths.artifacts/${buildContext.productProperties.baseFileName}${buildContext.fullBuildNumber}.zip" + String targetPath = "$buildContext.paths.artifacts/${buildContext.productProperties.getBaseArtifactName(buildContext.applicationInfo, buildContext.buildNumber)}.zip" buildContext.ant.zip(zipfile: targetPath, duplicate: "fail") { fileset(dir: buildContext.paths.distAll) { exclude(name: "bin/idea.properties") diff --git a/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/LinuxDistributionBuilder.groovy b/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/LinuxDistributionBuilder.groovy index e7209670c0af..9bbf900decb9 100644 --- a/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/LinuxDistributionBuilder.groovy +++ b/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/LinuxDistributionBuilder.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -15,6 +15,7 @@ */ package org.jetbrains.intellij.build.impl +import com.intellij.openapi.util.text.StringUtil import org.jetbrains.intellij.build.BuildContext import org.jetbrains.intellij.build.BuildOptions import org.jetbrains.intellij.build.JvmArchitecture @@ -65,15 +66,18 @@ class LinuxDistributionBuilder extends OsSpecificDistributionBuilder { @Override void buildArtifacts(String osSpecificDistPath) { - if (customizer.buildTarGzWithoutBundledJre) { - buildTarGz(null, osSpecificDistPath) - } - def jreDirectoryPath = buildContext.bundledJreManager.extractLinuxJre() - if (jreDirectoryPath != null) { - buildTarGz(jreDirectoryPath, osSpecificDistPath) - } - else { - buildContext.messages.info("Skipping building Linux distribution with bundled JRE because JRE archive is missing") + buildContext.executeStep("Build Linux .tar.gz", BuildOptions.LINUX_TAR_GZ_STEP) { + if (customizer.buildTarGzWithoutBundledJre) { + buildTarGz(null, osSpecificDistPath) + } + def jreDirectoryPath = buildContext.bundledJreManager.extractLinuxJre() + if (jreDirectoryPath != null) { + buildTarGz(jreDirectoryPath, osSpecificDistPath) + buildSnapPackage(jreDirectoryPath, osSpecificDistPath) + } + else { + buildContext.messages.info("Skipping building Linux distribution with bundled JRE because JRE archive is missing") + } } } @@ -182,4 +186,86 @@ class LinuxDistributionBuilder extends OsSpecificDistributionBuilder { buildContext.notifyArtifactBuilt(gzPath) } } + + private void buildSnapPackage(String jreDirectoryPath, String unixDistPath) { + if (!buildContext.options.buildUnixSnaps || customizer.snapName == null) return + + if (StringUtil.isEmpty(customizer.iconPngPath)) buildContext.messages.error("'iconPngPath' not set") + if (StringUtil.isEmpty(customizer.snapDescription)) buildContext.messages.error("'snapDescription' not set") + + String snapDir = "${buildContext.paths.buildOutputRoot}/dist.snap" + + buildContext.messages.block("Build Linux .snap package") { + buildContext.messages.progress("Preparing files") + + def desktopTemplate = "${buildContext.paths.communityHome}/platform/platform-resources/src/entry.desktop" + def ce = buildContext.productProperties.productCode in ["IC", "PC"] + def productName = buildContext.applicationInfo.productName + if (ce && !productName.contains("Community Edition")) productName += " Community Edition" // IdeaApplicationInfo.xml//names@product + buildContext.ant.copy(file: desktopTemplate, tofile: "${snapDir}/snap/gui/${customizer.snapName}.desktop") { + filterset(begintoken: '$', endtoken: '$') { + filter(token: "NAME", value: productName) + filter(token: "ICON", value: "/bin/${buildContext.productProperties.baseFileName}.png") + filter(token: "SCRIPT", value: "/bin/${buildContext.productProperties.baseFileName}.sh") + filter(token: "WM_CLASS", value: "jetbrains-${buildContext.applicationInfo.shortProductName.toLowerCase()}${ce ? "-ce" : ""}") + } + } + + buildContext.ant.copy(file: customizer.iconPngPath, tofile: "${snapDir}/${customizer.snapName}.png") + + def snapcraftTemplate = "${buildContext.paths.communityHome}/build/snap/snapcraft-template.yaml" + def version = "${buildContext.applicationInfo.majorVersion}.${buildContext.applicationInfo.minorVersion}" + buildContext.ant.copy(file: snapcraftTemplate, tofile: "${snapDir}/snapcraft.yaml") { + filterset(begintoken: '$', endtoken: '$') { + filter(token: "NAME", value: customizer.snapName) + filter(token: "VERSION", value: version) + filter(token: "SUMMARY", value: productName) + filter(token: "DESCRIPTION", value: customizer.snapDescription) + filter(token: "SCRIPT", value: "bin/${buildContext.productProperties.baseFileName}.sh") + } + } + + buildContext.ant.concat(destfile: "${unixDistPath}/bin/idea.properties", append: true) { + filelist(dir: "${buildContext.paths.communityHome}/build/snap", files: "idea-snap.properties") + } + + buildContext.ant.delete(quiet: true) { + fileset(dir: "${unixDistPath}/bin") { + include(name: "fsnotifier") + include(name: "fsnotifier-arm") + include(name: "libyjpagent-linux.so") + } + } + + buildContext.ant.chmod(perm: "755") { + fileset(dir: unixDistPath) { + include(name: "bin/*.sh") + include(name: "bin/*.py") + include(name: "bin/fsnotifier*") + customizer.extraExecutables.each { include(name: it) } + } + fileset(dir: jreDirectoryPath) { + include(name: "jre64/bin/*") + } + } + + buildContext.messages.progress("Building package") + + buildContext.ant.exec(executable: "docker", dir: snapDir, failonerror: true) { + arg(value: "run") + arg(value: "--volume=${snapDir}:/build") + arg(value: "--volume=${buildContext.paths.distAll}:/build/dist.all:ro") + arg(value: "--volume=${unixDistPath}:/build/dist.unix:ro") + arg(value: "--volume=${jreDirectoryPath}:/build/jre:ro") + arg(value: "--workdir=/build") + arg(value: "--env=SNAPCRAFT_SETUP_CORE=1") + arg(value: "snapcore/snapcraft") + arg(value: "snapcraft") + } + + def snapArtifact = "${customizer.snapName}_${version}_amd64.snap" + buildContext.ant.move(file: "${snapDir}/${snapArtifact}", todir: buildContext.paths.artifacts) + buildContext.notifyArtifactBuilt("${buildContext.paths.artifacts}/" + snapArtifact) + } + } } \ No newline at end of file diff --git a/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/MacDistributionBuilder.groovy b/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/MacDistributionBuilder.groovy index bccc804fba36..bd00ee0721d9 100644 --- a/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/MacDistributionBuilder.groovy +++ b/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/MacDistributionBuilder.groovy @@ -93,15 +93,17 @@ class MacDistributionBuilder extends OsSpecificDistributionBuilder { @Override void buildArtifacts(String osSpecificDistPath) { - def macZipPath = buildMacZip(osSpecificDistPath) - if (buildContext.proprietaryBuildTools.macHostProperties == null) { - buildContext.messages.info("A Mac OS build agent isn't configured, dmg artifact won't be produced") - buildContext.notifyArtifactBuilt(macZipPath) - } - else { - buildContext.executeStep("Build dmg artifact for Mac OS X", BuildOptions.MAC_DMG_STEP) { - MacDmgBuilder.signAndBuildDmg(buildContext, customizer, buildContext.proprietaryBuildTools.macHostProperties, macZipPath) - buildContext.ant.delete(file: macZipPath) + buildContext.executeStep("Build artifacts for Mac OS X", BuildOptions.MAC_ARTIFACTS) { + def macZipPath = buildMacZip(osSpecificDistPath) + if (buildContext.proprietaryBuildTools.macHostProperties == null) { + buildContext.messages.info("A Mac OS build agent isn't configured, dmg artifact won't be produced") + buildContext.notifyArtifactBuilt(macZipPath) + } + else { + buildContext.executeStep("Build dmg artifact for Mac OS X", BuildOptions.MAC_DMG_STEP) { + MacDmgBuilder.signAndBuildDmg(buildContext, customizer, buildContext.proprietaryBuildTools.macHostProperties, macZipPath) + buildContext.ant.delete(file: macZipPath) + } } } } diff --git a/platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiRequest.kt b/platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiRequest.kt index 4a959cf83333..4eb44c1e3994 100644 --- a/platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiRequest.kt +++ b/platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiRequest.kt @@ -6,6 +6,7 @@ import io.netty.buffer.ByteBufAllocator import io.netty.buffer.ByteBufUtil import io.netty.channel.Channel import io.netty.handler.codec.http.FullHttpRequest +import io.netty.handler.codec.http.HttpHeaderNames import org.jetbrains.builtInWebServer.PathInfo import org.jetbrains.io.serverHeaderValue import java.net.InetSocketAddress @@ -19,6 +20,8 @@ private val FCGI_KEEP_CONNECTION = 1 private val STDIN = 5 private val VERSION = 1 +private val MAX_CONTENT_LENGTH = 0xFFFF + class FastCgiRequest(val requestId: Int, allocator: ByteBufAllocator) { private val params = allocator.ioBuffer(4096) @@ -52,15 +55,12 @@ class FastCgiRequest(val requestId: Int, allocator: ByteBufAllocator) { } } - private fun writeParamLength(l: Int) { - if (l < 128) { - params.writeByte(l) + private fun writeParamLength(length: Int) { + if (length > 127) { + params.writeInt(length or 0x80000000.toInt()) } else { - params.writeByte(128 or (l shr 24)) - params.writeByte(l shr 16) - params.writeByte(l shr 8) - params.writeByte(l) + params.writeByte(length) } } @@ -89,10 +89,15 @@ class FastCgiRequest(val requestId: Int, allocator: ByteBufAllocator) { val queryIndex = request.uri().indexOf('?') if (queryIndex != -1) { queryString = request.uri().substring(queryIndex + 1) + addHeader("DOCUMENT_URI", request.uri().substring(0, queryIndex)) + } + else { + addHeader("DOCUMENT_URI", request.uri()) } addHeader("QUERY_STRING", queryString) addHeader("CONTENT_LENGTH", request.content().readableBytes().toString()) + addHeader("CONTENT_TYPE", request.headers().get(HttpHeaderNames.CONTENT_TYPE) ?: "") for ((key, value) in request.headers().iteratorAsString()) { if (!key.equals("keep-alive", ignoreCase = true) && !key.equals("connection", ignoreCase = true)) { @@ -121,16 +126,25 @@ class FastCgiRequest(val requestId: Int, allocator: ByteBufAllocator) { writeHeader(buffer, PARAMS, 0) - if (content != null) { - writeHeader(buffer, STDIN, content.readableBytes()) - } - fastCgiChannel.write(buffer) if (content != null) { - fastCgiChannel.write(content) - // channel.write releases - releaseContent = false + var position = content.readerIndex() + var toWrite = content.readableBytes() + while (toWrite > 0) { + val length = Math.min(MAX_CONTENT_LENGTH, toWrite) + + val headerBuffer = fastCgiChannel.alloc().ioBuffer(HEADER_LENGTH, HEADER_LENGTH) + writeHeader(headerBuffer, STDIN, length) + fastCgiChannel.write(headerBuffer) + + val chunk = content.slice(position, length) + // channel.write releases + chunk.retain() + fastCgiChannel.write(chunk) + toWrite -= length + position += length + } val headerBuffer = fastCgiChannel.alloc().ioBuffer(HEADER_LENGTH, HEADER_LENGTH) writeHeader(headerBuffer, STDIN, 0) @@ -139,7 +153,6 @@ class FastCgiRequest(val requestId: Int, allocator: ByteBufAllocator) { } finally { if (releaseContent) { - assert(content != null) content!!.release() } } diff --git a/platform/configuration-store-impl/src/FileBasedStorage.kt b/platform/configuration-store-impl/src/FileBasedStorage.kt index b9b000154acc..7b4aa3458d1e 100644 --- a/platform/configuration-store-impl/src/FileBasedStorage.kt +++ b/platform/configuration-store-impl/src/FileBasedStorage.kt @@ -192,7 +192,7 @@ fun writeFile(file: Path?, requestor: Any, virtualFile: VirtualFile?, element: E if ((LOG.isDebugEnabled || ApplicationManager.getApplication().isUnitTestMode) && !FileUtilRt.isTooLarge(result.length)) { val content = element.toBufferExposingByteArray(lineSeparator.separatorString) if (isEqualContent(result, lineSeparator, content, prependXmlProlog)) { - throw IllegalStateException("Content equals, but it must be handled not on this level: file ${result.name}, content\n${content.toByteArray().toString(StandardCharsets.UTF_8)}") + LOG.error("Content equals, but it must be handled not on this level: file ${result.name}, content\n${content.toByteArray().toString(StandardCharsets.UTF_8)}") } else if (DEBUG_LOG != null && ApplicationManager.getApplication().isUnitTestMode) { DEBUG_LOG = "${result.path}:\n$content\nOld Content:\n${LoadTextUtil.loadText(result)}" diff --git a/platform/core-api/src/com/intellij/ide/PowerSaveMode.java b/platform/core-api/src/com/intellij/ide/PowerSaveMode.java index 1b352709dc30..d842b99d27dc 100644 --- a/platform/core-api/src/com/intellij/ide/PowerSaveMode.java +++ b/platform/core-api/src/com/intellij/ide/PowerSaveMode.java @@ -25,7 +25,7 @@ import com.intellij.util.messages.Topic; */ public class PowerSaveMode { private static final String POWER_SAVE_MODE = "power.save.mode"; - private boolean myEnabled = PropertiesComponent.getInstance().getBoolean(POWER_SAVE_MODE); + private volatile boolean myEnabled = PropertiesComponent.getInstance().getBoolean(POWER_SAVE_MODE); private final MessageBus myBus; public PowerSaveMode(MessageBus bus) { diff --git a/platform/core-api/src/com/intellij/lang/injection/InjectedLanguageManager.java b/platform/core-api/src/com/intellij/lang/injection/InjectedLanguageManager.java index 8ec9f2cc6fb9..a66bd71ba8d7 100644 --- a/platform/core-api/src/com/intellij/lang/injection/InjectedLanguageManager.java +++ b/platform/core-api/src/com/intellij/lang/injection/InjectedLanguageManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -100,4 +100,10 @@ public abstract class InjectedLanguageManager { */ @NotNull public abstract List getNonEditableFragments(@NotNull DocumentWindow window); + + /** + * This method can be invoked on an uncommitted document, before performing commit and using other methods here + * (which don't work for uncommitted document). + */ + public abstract boolean mightHaveInjectedFragmentAtOffset(@NotNull Document hostDocument, int hostOffset); } diff --git a/platform/core-api/src/com/intellij/openapi/project/DumbService.java b/platform/core-api/src/com/intellij/openapi/project/DumbService.java index 3cde5788cf74..424948b4cdd1 100644 --- a/platform/core-api/src/com/intellij/openapi/project/DumbService.java +++ b/platform/core-api/src/com/intellij/openapi/project/DumbService.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -25,6 +25,7 @@ import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.util.*; import com.intellij.util.ThrowableRunnable; import com.intellij.util.messages.Topic; +import org.jetbrains.annotations.Debugger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -73,7 +74,7 @@ public abstract class DumbService { * Note that it's not guaranteed that the dumb mode won't start again during this runnable execution, it should manage that situation explicitly. * @param runnable runnable to run */ - public abstract void runWhenSmart(@NotNull Runnable runnable); + public abstract void runWhenSmart(@Debugger.Capture @NotNull Runnable runnable); /** * Pause the current thread until dumb mode ends and then continue execution. diff --git a/platform/core-api/src/com/intellij/openapi/vfs/VirtualFile.java b/platform/core-api/src/com/intellij/openapi/vfs/VirtualFile.java index b7eb1e9c5aa6..0a9a1f9d9ba4 100644 --- a/platform/core-api/src/com/intellij/openapi/vfs/VirtualFile.java +++ b/platform/core-api/src/com/intellij/openapi/vfs/VirtualFile.java @@ -24,8 +24,10 @@ import com.intellij.openapi.util.*; import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.encoding.EncodingRegistry; +import com.intellij.openapi.vfs.newvfs.events.VFilePropertyChangeEvent; import com.intellij.testFramework.LightVirtualFile; import com.intellij.util.LineSeparator; +import org.intellij.lang.annotations.MagicConstant; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -90,7 +92,7 @@ public abstract class VirtualFile extends UserDataHolderBase implements Modifica * @see VirtualFileListener#propertyChanged * @see VirtualFilePropertyEvent#getPropertyName */ - public static final String PROP_HIDDEN = VFileProperty.HIDDEN.getName(); + public static final String PROP_HIDDEN = "HIDDEN"; /** * Used as a property name in the {@link VirtualFilePropertyEvent} fired when a symlink target of a @@ -101,6 +103,13 @@ public abstract class VirtualFile extends UserDataHolderBase implements Modifica */ public static final String PROP_SYMLINK_TARGET = "symlink"; + /** + * Acceptable values for "propertyName" argument in + * {@link VFilePropertyChangeEvent#VFilePropertyChangeEvent(Object, VirtualFile, String, Object, Object, boolean)} + */ + @MagicConstant(stringValues = {PROP_NAME, PROP_ENCODING, PROP_HIDDEN, PROP_WRITABLE, PROP_SYMLINK_TARGET}) + public @interface PropName {} + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.VirtualFile"); private static final Key BOM_KEY = Key.create("BOM"); private static final Key CHARSET_KEY = Key.create("CHARSET"); diff --git a/platform/core-api/src/com/intellij/openapi/vfs/newvfs/events/VFileCreateEvent.java b/platform/core-api/src/com/intellij/openapi/vfs/newvfs/events/VFileCreateEvent.java index 772e8c16f007..43cc9fdd5296 100644 --- a/platform/core-api/src/com/intellij/openapi/vfs/newvfs/events/VFileCreateEvent.java +++ b/platform/core-api/src/com/intellij/openapi/vfs/newvfs/events/VFileCreateEvent.java @@ -27,7 +27,6 @@ public class VFileCreateEvent extends VFileEvent { @NotNull private final VirtualFile myParent; private final boolean myDirectory; @NotNull private final String myChildName; - private final boolean myReCreation; private VirtualFile myCreatedFile; public VFileCreateEvent(Object requestor, @@ -35,20 +34,10 @@ public class VFileCreateEvent extends VFileEvent { @NotNull String childName, final boolean isDirectory, final boolean isFromRefresh) { - this(requestor, parent, childName, isDirectory, isFromRefresh, false); - } - - public VFileCreateEvent(Object requestor, - @NotNull VirtualFile parent, - @NotNull String childName, - boolean isDirectory, - boolean isFromRefresh, - boolean isReCreation) { super(requestor, isFromRefresh); myChildName = childName; myParent = parent; myDirectory = isDirectory; - myReCreation = isReCreation; } @NotNull @@ -65,14 +54,10 @@ public class VFileCreateEvent extends VFileEvent { return myParent; } - public boolean isReCreation() { - return myReCreation; - } - @NonNls @Override public String toString() { - return "VfsEvent[" + (myReCreation ? "re" : "") + "create " + (myDirectory ? "dir " : "file ") + + return "VfsEvent[create " + (myDirectory ? "dir " : "file ") + myChildName + " in " + myParent.getUrl() + "]"; } @@ -102,16 +87,12 @@ public class VFileCreateEvent extends VFileEvent { public boolean isValid() { if (myParent.isValid()) { boolean childExists = myParent.findChild(myChildName) != null; - return isValid(childExists); + return !childExists; } return false; } - public boolean isValid(boolean childExists) { - return myReCreation == childExists; - } - @Override public boolean equals(final Object o) { if (this == o) return true; @@ -122,7 +103,7 @@ public class VFileCreateEvent extends VFileEvent { if (myDirectory != event.myDirectory) return false; if (!myChildName.equals(event.myChildName)) return false; if (!myParent.equals(event.myParent)) return false; - return myReCreation == event.myReCreation; + return true; } @Override @@ -130,7 +111,6 @@ public class VFileCreateEvent extends VFileEvent { int result = myParent.hashCode(); result = 31 * result + (myDirectory ? 1 : 0); result = 31 * result + myChildName.hashCode(); - result = 31 * result + (myReCreation ? 1 : 0); return result; } } diff --git a/platform/core-api/src/com/intellij/openapi/vfs/newvfs/events/VFilePropertyChangeEvent.java b/platform/core-api/src/com/intellij/openapi/vfs/newvfs/events/VFilePropertyChangeEvent.java index aac5a41a5901..347bc6585767 100644 --- a/platform/core-api/src/com/intellij/openapi/vfs/newvfs/events/VFilePropertyChangeEvent.java +++ b/platform/core-api/src/com/intellij/openapi/vfs/newvfs/events/VFilePropertyChangeEvent.java @@ -34,7 +34,7 @@ public class VFilePropertyChangeEvent extends VFileEvent { public VFilePropertyChangeEvent(Object requestor, @NotNull VirtualFile file, - @NotNull String propertyName, + @VirtualFile.PropName @NotNull String propertyName, @Nullable Object oldValue, @Nullable Object newValue, boolean isFromRefresh) { @@ -86,6 +86,7 @@ public class VFilePropertyChangeEvent extends VFileEvent { } @NotNull + @VirtualFile.PropName public String getPropertyName() { return myPropertyName; } diff --git a/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java b/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java index 7c89ad890ad2..5199394dcabc 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java +++ b/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java @@ -23,6 +23,7 @@ import com.intellij.ide.plugins.cl.PluginClassLoader; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.application.impl.ApplicationInfoImpl; import com.intellij.openapi.components.ExtensionAreas; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.*; @@ -1259,11 +1260,21 @@ public class PluginManagerCore { return detectReasonToNotLoad(descriptor, ourPlugins) != null || isBrokenPlugin(descriptor); } + private static void checkEssentialPluginsAreAvailable(IdeaPluginDescriptorImpl[] plugins) { + Set availableIds = ContainerUtil.map2Set(plugins, plugin -> plugin.getPluginId().getIdString()); + for (String pluginId : ((ApplicationInfoImpl)ApplicationInfoImpl.getShadowInstance()).getEssentialPluginsIds()) { + if (!availableIds.contains(pluginId)) { + throw new InstallationCorruptedException("Required plugin '" + pluginId + "' isn't available"); + } + } + } + private static void initializePlugins(@Nullable StartupProgress progress) { configureExtensions(); final List errors = ContainerUtil.newArrayList(); final IdeaPluginDescriptorImpl[] pluginDescriptors = loadDescriptors(progress, errors); + checkEssentialPluginsAreAvailable(pluginDescriptors); final Class callerClass = ReflectionUtil.findCallerClass(1); assert callerClass != null; @@ -1487,4 +1498,10 @@ public class PluginManagerCore { getLogger().info(t); } } + + static class InstallationCorruptedException extends RuntimeException { + public InstallationCorruptedException(String message) { + super(message); + } + } } diff --git a/platform/core-impl/src/com/intellij/lang/PsiBuilderUtil.java b/platform/core-impl/src/com/intellij/lang/PsiBuilderUtil.java index a31c12c7fd34..7e57878e9b0a 100644 --- a/platform/core-impl/src/com/intellij/lang/PsiBuilderUtil.java +++ b/platform/core-impl/src/com/intellij/lang/PsiBuilderUtil.java @@ -15,6 +15,7 @@ */ package com.intellij.lang; +import com.intellij.lexer.Lexer; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; import org.jetbrains.annotations.NotNull; @@ -129,4 +130,51 @@ public class PsiBuilderUtil { return marker; } + + /** + * Checks if `text` looks like a proper block. + * In particular it + * (1) checks brace balance + * (2) verifies that the block's closing brace is the last token + * + * @param text - text to check + * @param lexer - lexer to use + * @param leftBrace - left brace element type + * @param rightBrace - right brace element type + * @return true if `text` passes the checks + */ + public static boolean hasProperBraceBalance(@NotNull CharSequence text, + @NotNull Lexer lexer, + @NotNull IElementType leftBrace, + @NotNull IElementType rightBrace) { + lexer.start(text); + + if (lexer.getTokenType() != leftBrace) return false; + + lexer.advance(); + int balance = 1; + + while (true) { + IElementType type = lexer.getTokenType(); + + if (type == null) { + //eof: checking balance + return balance == 0; + } + + if (balance == 0) { + //the last brace is not the last token + return false; + } + + if (type == leftBrace) { + balance++; + } + else if (type == rightBrace) { + balance--; + } + + lexer.advance(); + } + } } diff --git a/platform/core-impl/src/com/intellij/openapi/application/ex/ApplicationUtil.java b/platform/core-impl/src/com/intellij/openapi/application/ex/ApplicationUtil.java index 1e1a2c44b4ce..2e14e5c10201 100644 --- a/platform/core-impl/src/com/intellij/openapi/application/ex/ApplicationUtil.java +++ b/platform/core-impl/src/com/intellij/openapi/application/ex/ApplicationUtil.java @@ -24,6 +24,7 @@ import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Ref; import com.intellij.util.ExceptionUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.ide.PooledThreadExecutor; import java.util.concurrent.Callable; @@ -99,8 +100,8 @@ public class ApplicationUtil { // That horrible hack causes all sorts of confusion when we try to analyze the exception cause, e.g. in GlobalInspectionContextImpl.inspectFile(). // To prevent creation of unneeded wrapped exception we supply this method as a bait which stupid ForkJoinTask calls and immediately poisons itself, // causing the original exception to be used unwrapped. (see ForkJoinTask.getThrowableException()) - @Override - public synchronized Throwable initCause(Throwable cause) { + @SuppressWarnings("unused") + public CannotRunReadActionException(@Nullable Throwable cause) { throw this; } } diff --git a/platform/core-impl/src/com/intellij/openapi/application/impl/ApplicationInfoImpl.java b/platform/core-impl/src/com/intellij/openapi/application/impl/ApplicationInfoImpl.java index d5879e9535a7..fdcdbbf311db 100644 --- a/platform/core-impl/src/com/intellij/openapi/application/impl/ApplicationInfoImpl.java +++ b/platform/core-impl/src/com/intellij/openapi/application/impl/ApplicationInfoImpl.java @@ -28,7 +28,6 @@ import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.JBColor; import com.intellij.util.ArrayUtil; -import com.intellij.util.Function; import com.intellij.util.PlatformUtils; import com.intellij.util.SystemProperties; import com.intellij.util.containers.ContainerUtil; @@ -993,6 +992,10 @@ public class ApplicationInfoImpl extends ApplicationInfoEx { return PluginManagerCore.CORE_PLUGIN_ID.equals(pluginId) || ArrayUtil.contains(pluginId, myEssentialPluginsIds); } + public List getEssentialPluginsIds() { + return Collections.unmodifiableList(Arrays.asList(myEssentialPluginsIds)); + } + private static class UpdateUrlsImpl implements UpdateUrls { private String myCheckingUrl; private String myPatchesUrl; diff --git a/platform/core-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java b/platform/core-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java index 00861edb9b79..70a0fc02472c 100644 --- a/platform/core-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java +++ b/platform/core-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java @@ -698,6 +698,8 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { long newModificationStamp, int initialStartOffset, int initialOldLength) { + LOG.trace("updating document " + this + ".\nNext string:'" + newString + "'\nOld string:'" + oldString + "'"); + assertNotNestedModification(); myChangeInProgress = true; try { diff --git a/platform/core-impl/src/com/intellij/openapi/editor/impl/event/DocumentEventImpl.java b/platform/core-impl/src/com/intellij/openapi/editor/impl/event/DocumentEventImpl.java index c1557fee66aa..a7caa691a5e2 100644 --- a/platform/core-impl/src/com/intellij/openapi/editor/impl/event/DocumentEventImpl.java +++ b/platform/core-impl/src/com/intellij/openapi/editor/impl/event/DocumentEventImpl.java @@ -172,7 +172,7 @@ public class DocumentEventImpl extends DocumentEvent { // line numbers in Diff.Change are relative to change start private Diff.Change reBuildDiffIfNeeded() throws FilesTooBigForDiffException { - if (myChange == TOO_BIG_FILE) throw new FilesTooBigForDiffException(0); + if (myChange == TOO_BIG_FILE) throw new FilesTooBigForDiffException(); if (myChange == null) { try { myChange = Diff.buildChanges(myOldString, myNewString); diff --git a/platform/core-impl/src/com/intellij/pom/core/impl/PomModelImpl.java b/platform/core-impl/src/com/intellij/pom/core/impl/PomModelImpl.java index a6b0852fca1e..15724d2050f0 100644 --- a/platform/core-impl/src/com/intellij/pom/core/impl/PomModelImpl.java +++ b/platform/core-impl/src/com/intellij/pom/core/impl/PomModelImpl.java @@ -163,7 +163,7 @@ public class PomModelImpl extends UserDataHolderBase implements PomModel { throw e; } catch(Exception e){ - LOG.error(e); + throwables.add(e); return; } finally{ diff --git a/platform/core-impl/src/com/intellij/psi/SingleRootFileViewProvider.java b/platform/core-impl/src/com/intellij/psi/SingleRootFileViewProvider.java index 2e663979b164..83ce59381431 100644 --- a/platform/core-impl/src/com/intellij/psi/SingleRootFileViewProvider.java +++ b/platform/core-impl/src/com/intellij/psi/SingleRootFileViewProvider.java @@ -67,10 +67,12 @@ public class SingleRootFileViewProvider extends UserDataHolderBase implements Fi private static final Key OUR_NO_SIZE_LIMIT_KEY = Key.create("no.size.limit"); private static final Logger LOG = Logger.getInstance("#" + SingleRootFileViewProvider.class.getCanonicalName()); public static final Key FREE_THREADED = Key.create("FREE_THREADED"); + private static final Key> KNOWN_COPIES = Key.create("KNOWN_COPIES"); @NotNull private final PsiManager myManager; @NotNull private final VirtualFile myVirtualFile; private final boolean myEventSystemEnabled; private final boolean myPhysical; + private boolean myInvalidated; private final AtomicReference myPsiFile = Atomics.newReference(); private volatile Content myContent; private volatile Reference myDocument; @@ -571,10 +573,33 @@ public class SingleRootFileViewProvider extends UserDataHolderBase implements Fi } public void markInvalidated() { + if (myInvalidated) return; + + myInvalidated = true; PsiFile psiFile = getCachedPsi(myBaseLanguage); if (psiFile instanceof PsiFileEx) { ((PsiFileEx)psiFile).markInvalidated(); } + invalidateCopies(); + } + + private void invalidateCopies() { + Set knownCopies = getUserData(KNOWN_COPIES); + if (knownCopies != null) { + for (SingleRootFileViewProvider copy : knownCopies) { + if (copy.getCachedPsiFiles().stream().anyMatch(f -> f.getOriginalFile().getViewProvider() == this)) { + ((PsiManagerEx)myManager).getFileManager().setViewProvider(copy.getVirtualFile(), null); + } + } + } + } + + public void registerAsCopy(@NotNull SingleRootFileViewProvider copy) { + Set copies = getUserData(KNOWN_COPIES); + if (copies == null) { + copies = putUserDataIfAbsent(KNOWN_COPIES, Collections.newSetFromMap(ContainerUtil.createConcurrentWeakMap())); + } + copies.add(copy); } private interface Content { diff --git a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/FileElementInfo.java b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/FileElementInfo.java index 72805de1a6eb..1374b82e6459 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/FileElementInfo.java +++ b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/FileElementInfo.java @@ -73,7 +73,10 @@ class FileElementInfo extends SmartPointerElementInfo { @Override public Segment getRange() { - return myVirtualFile.isValid() ? new TextRange(0, (int)myVirtualFile.getLength()) : null; + if (!myVirtualFile.isValid()) return null; + + Document document = FileDocumentManager.getInstance().getDocument(myVirtualFile); + return document == null ? null : TextRange.from(0, document.getTextLength()); } @NotNull diff --git a/platform/core-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java b/platform/core-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java index 5907f4fc509b..264132de5184 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java +++ b/platform/core-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java @@ -169,9 +169,6 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF // but some VFS listeners receive the same events before that and ask PsiFile.isValid return false; } - if (myOriginalFile != null && myOriginalFile != this && !myOriginalFile.isValid()) { - return false; - } return !myInvalidated; } @@ -545,6 +542,11 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF public void setOriginalFile(@NotNull final PsiFile originalFile) { myOriginalFile = originalFile.getOriginalFile(); + + FileViewProvider original = myOriginalFile.getViewProvider(); + if (myViewProvider instanceof SingleRootFileViewProvider && original instanceof SingleRootFileViewProvider) { + ((SingleRootFileViewProvider)original).registerAsCopy((SingleRootFileViewProvider)myViewProvider); + } } @Override diff --git a/platform/core-impl/src/com/intellij/psi/impl/source/tree/CompositeElement.java b/platform/core-impl/src/com/intellij/psi/impl/source/tree/CompositeElement.java index c7558014fc18..450ebd76c767 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/source/tree/CompositeElement.java +++ b/platform/core-impl/src/com/intellij/psi/impl/source/tree/CompositeElement.java @@ -37,6 +37,7 @@ import com.intellij.psi.impl.source.codeStyle.CodeEditUtil; import com.intellij.psi.stubs.IStubElementType; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; +import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.ArrayFactory; import com.intellij.util.ObjectUtils; import com.intellij.util.concurrency.AtomicFieldUpdater; @@ -573,10 +574,26 @@ public class CompositeElement extends TreeElement { } TreeElement next = cur.getTreeNext(); - cur = next != null ? drillDown(next) : cur.getTreeParent(); + cur = next != null ? drillDown(next) : getNotNullParent(cur); } } + private static TreeElement getNotNullParent(TreeElement cur) { + TreeElement parent = cur.getTreeParent(); + if (parent == null) { + diagnoseNullParent(cur); + } + return parent; + } + + private static void diagnoseNullParent(TreeElement cur) { + PsiElement psi = cur.getPsi(); + if (psi != null) { + PsiUtilCore.ensureValid(psi); + } + throw new IllegalStateException("Null parent of " + cur + " " + cur.getClass()); + } + void setCachedLength(int cachedLength) { myCachedLength = cachedLength; } diff --git a/platform/diff-api/src/com/intellij/diff/DiffContext.java b/platform/diff-api/src/com/intellij/diff/DiffContext.java index 756796b7e6f7..c3aaa9c351ad 100644 --- a/platform/diff-api/src/com/intellij/diff/DiffContext.java +++ b/platform/diff-api/src/com/intellij/diff/DiffContext.java @@ -22,7 +22,7 @@ import com.intellij.openapi.util.UserDataHolderBase; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public abstract class DiffContext implements UserDataHolder { +public abstract class DiffContext implements UserDataHolder, FocusableContext { protected final UserDataHolderBase myUserDataHolder = new UserDataHolderBase(); @Nullable @@ -30,19 +30,6 @@ public abstract class DiffContext implements UserDataHolder { public abstract boolean isWindowFocused(); - /** - * @return whether diff panel holds focus - */ - public abstract boolean isFocused(); - - /** - * Request focus on diff panel ({@link FrameDiffTool.DiffViewer#getPreferredFocusedComponent()} in current viewer) - * NB: focus requested via {@link java.awt.Component#requestFocusInWindow()}, ignoring {@link com.intellij.openapi.wm.IdeFocusManager} - *

- * This method can be used in pair with {@link #isFocused()} to keep focus on modifications of component tree - */ - public abstract void requestFocus(); - /** * @see com.intellij.diff.util.DiffUserDataKeys */ diff --git a/platform/diff-api/src/com/intellij/diff/FocusableContext.java b/platform/diff-api/src/com/intellij/diff/FocusableContext.java new file mode 100644 index 000000000000..eee77404c19a --- /dev/null +++ b/platform/diff-api/src/com/intellij/diff/FocusableContext.java @@ -0,0 +1,28 @@ +/* + * Copyright 2000-2017 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.diff; + +/** + * Can be used in pair with {@link DiffUtil#runPreservingFocus) + * to keep focus on modifications of component tree + *

+ * NB: focus requested via {@link java.awt.Component#requestFocusInWindow()}, ignoring {@link com.intellij.openapi.wm.IdeFocusManager} + */ +public interface FocusableContext { + boolean isFocused(); + + void requestFocus(); +} diff --git a/platform/diff-api/src/com/intellij/diff/merge/MergeContext.java b/platform/diff-api/src/com/intellij/diff/merge/MergeContext.java index 148c74d81a7f..d0c85e31e49d 100644 --- a/platform/diff-api/src/com/intellij/diff/merge/MergeContext.java +++ b/platform/diff-api/src/com/intellij/diff/merge/MergeContext.java @@ -15,6 +15,7 @@ */ package com.intellij.diff.merge; +import com.intellij.diff.FocusableContext; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.UserDataHolder; @@ -23,16 +24,12 @@ import org.jetbrains.annotations.CalledInAwt; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public abstract class MergeContext implements UserDataHolder { +public abstract class MergeContext implements UserDataHolder, FocusableContext { protected final UserDataHolderBase myUserDataHolder = new UserDataHolderBase(); @Nullable public abstract Project getProject(); - public abstract boolean isFocused(); - - public abstract void requestFocus(); - /** * Called by MergeTool on conflict resolve end. Should delegate to the {@link MergeRequest#applyResult(MergeResult)} */ diff --git a/platform/diff-api/src/com/intellij/ide/diff/DirDiffSettings.java b/platform/diff-api/src/com/intellij/ide/diff/DirDiffSettings.java index 3e4a9ba6fdd9..33090403bc55 100644 --- a/platform/diff-api/src/com/intellij/ide/diff/DirDiffSettings.java +++ b/platform/diff-api/src/com/intellij/ide/diff/DirDiffSettings.java @@ -16,6 +16,7 @@ package com.intellij.ide.diff; import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.util.Key; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.PatternUtil; import org.jetbrains.annotations.NotNull; @@ -30,6 +31,8 @@ import java.util.regex.Pattern; * @author Konstantin Bulenkov */ public class DirDiffSettings { + public static final Key KEY = Key.create("Diff.DirDiffSettings"); + public boolean showSize = true; public boolean showDate = true; diff --git a/platform/diff-impl/src/com/intellij/diff/DiffContextEx.java b/platform/diff-impl/src/com/intellij/diff/DiffContextEx.java index 4c725070d255..0f0566965632 100644 --- a/platform/diff-impl/src/com/intellij/diff/DiffContextEx.java +++ b/platform/diff-impl/src/com/intellij/diff/DiffContextEx.java @@ -16,6 +16,7 @@ package com.intellij.diff; import org.jetbrains.annotations.CalledInAwt; +import org.jetbrains.annotations.NotNull; public abstract class DiffContextEx extends DiffContext { /* @@ -41,4 +42,7 @@ public abstract class DiffContextEx extends DiffContext { */ @CalledInAwt public abstract void showProgressBar(boolean enabled); + + @CalledInAwt + public abstract void setWindowTitle(@NotNull String title); } diff --git a/platform/diff-impl/src/com/intellij/diff/impl/DiffRequestProcessor.java b/platform/diff-impl/src/com/intellij/diff/impl/DiffRequestProcessor.java index 3a4719cd38f4..82934073f563 100644 --- a/platform/diff-impl/src/com/intellij/diff/impl/DiffRequestProcessor.java +++ b/platform/diff-impl/src/com/intellij/diff/impl/DiffRequestProcessor.java @@ -278,31 +278,29 @@ public abstract class DiffRequestProcessor implements Disposable { request.putUserData(DiffUserDataKeysEx.SCROLL_TO_CHANGE, scrollToChangePolicy); - boolean hadFocus = isFocused(); + DiffUtil.runPreservingFocus(myContext, () -> { + myState.destroy(); + myToolbarStatusPanel.setContent(null); + myContentPanel.setContent(null); - myState.destroy(); - myToolbarStatusPanel.setContent(null); - myContentPanel.setContent(null); + myToolbarGroup.removeAll(); + myPopupActionGroup.removeAll(); + ActionUtil.clearActions(myMainPanel); - myToolbarGroup.removeAll(); - myPopupActionGroup.removeAll(); - ActionUtil.clearActions(myMainPanel); + myActiveRequest.onAssigned(false); + myActiveRequest = request; + myActiveRequest.onAssigned(true); - myActiveRequest.onAssigned(false); - myActiveRequest = request; - myActiveRequest.onAssigned(true); - - try { - myState = createState(); - myState.init(); - } - catch (Throwable e) { - LOG.error(e); - myState = new ErrorState(new ErrorDiffRequest(DiffBundle.message("error.cant.show.diff.message")), getFittedTool()); - myState.init(); - } - - if (hadFocus) requestFocusInternal(); + try { + myState = createState(); + myState.init(); + } + catch (Throwable e) { + LOG.error(e); + myState = new ErrorState(new ErrorDiffRequest(DiffBundle.message("error.cant.show.diff.message")), getFittedTool()); + myState.init(); + } + }); } protected void setWindowTitle(@NotNull String title) { @@ -338,20 +336,17 @@ public abstract class DiffRequestProcessor implements Disposable { // Misc // - public boolean isWindowFocused() { + protected boolean isWindowFocused() { Window window = SwingUtilities.getWindowAncestor(myPanel); return window != null && window.isFocused(); } - public boolean isFocused() { - return DiffUtil.isFocusedComponent(myProject, myPanel); + private boolean isFocused() { + return DiffUtil.isFocusedComponent(myProject, myContentPanel) || + DiffUtil.isFocusedComponent(myProject, myToolbar.getComponent()); } - public void requestFocus() { - DiffUtil.requestFocus(myProject, getPreferredFocusedComponent()); - } - - protected void requestFocusInternal() { + private void requestFocusInternal() { JComponent component = getPreferredFocusedComponent(); if (component != null) component.requestFocusInWindow(); } @@ -939,6 +934,11 @@ public abstract class DiffRequestProcessor implements Disposable { } } + @Override + public void setWindowTitle(@NotNull String title) { + setTitle(title); + } + @Nullable @Override public Project getProject() { diff --git a/platform/diff-impl/src/com/intellij/diff/merge/MergeRequestProcessor.java b/platform/diff-impl/src/com/intellij/diff/merge/MergeRequestProcessor.java index 614113dab428..58cd77a680ff 100644 --- a/platform/diff-impl/src/com/intellij/diff/merge/MergeRequestProcessor.java +++ b/platform/diff-impl/src/com/intellij/diff/merge/MergeRequestProcessor.java @@ -249,15 +249,13 @@ public abstract class MergeRequestProcessor implements Disposable { return; } - boolean wasFocused = isFocused(); - - destroyViewer(); - myViewer = newViewer; - updateBottomActions(); - rebuildSouthPanel(); - initViewer(); - - if (wasFocused) requestFocusInternal(); + DiffUtil.runPreservingFocus(myContext, () -> { + destroyViewer(); + myViewer = newViewer; + updateBottomActions(); + rebuildSouthPanel(); + initViewer(); + }); } // @@ -328,15 +326,11 @@ public abstract class MergeRequestProcessor implements Disposable { // Misc // - public boolean isFocused() { + private boolean isFocused() { return DiffUtil.isFocusedComponent(myProject, myPanel); } - public void requestFocus() { - DiffUtil.requestFocus(myProject, getPreferredFocusedComponent()); - } - - protected void requestFocusInternal() { + private void requestFocusInternal() { JComponent component = getPreferredFocusedComponent(); if (component != null) component.requestFocusInWindow(); } diff --git a/platform/diff-impl/src/com/intellij/diff/merge/TextMergeViewer.java b/platform/diff-impl/src/com/intellij/diff/merge/TextMergeViewer.java index f63e5c57fc2d..02bc2c9795e4 100644 --- a/platform/diff-impl/src/com/intellij/diff/merge/TextMergeViewer.java +++ b/platform/diff-impl/src/com/intellij/diff/merge/TextMergeViewer.java @@ -1113,7 +1113,8 @@ public class TextMergeViewer implements MergeTool.MergeViewer { @Override protected boolean isVisible(@NotNull ThreeSide side) { - return side == ThreeSide.BASE; + if (side == ThreeSide.BASE) return true; + return side == mySide.select(ThreeSide.LEFT, ThreeSide.RIGHT); } @Override diff --git a/platform/diff-impl/src/com/intellij/diff/tools/dir/DirDiffViewer.java b/platform/diff-impl/src/com/intellij/diff/tools/dir/DirDiffViewer.java index fc1f45cee992..f48c2bd4f986 100644 --- a/platform/diff-impl/src/com/intellij/diff/tools/dir/DirDiffViewer.java +++ b/platform/diff-impl/src/com/intellij/diff/tools/dir/DirDiffViewer.java @@ -16,6 +16,7 @@ package com.intellij.diff.tools.dir; import com.intellij.diff.DiffContext; +import com.intellij.diff.DiffContextEx; import com.intellij.diff.FrameDiffTool; import com.intellij.diff.contents.DiffContent; import com.intellij.diff.contents.DirectoryContent; @@ -33,13 +34,13 @@ import com.intellij.internal.statistic.UsageTrigger; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.diff.impl.dir.DirDiffFrame; import com.intellij.openapi.diff.impl.dir.DirDiffPanel; import com.intellij.openapi.diff.impl.dir.DirDiffTableModel; import com.intellij.openapi.diff.impl.dir.DirDiffWindow; import com.intellij.openapi.project.DefaultProjectFactory; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; +import com.intellij.util.ObjectUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -69,21 +70,19 @@ class DirDiffViewer implements FrameDiffTool.DiffViewer { Project project = context.getProject(); if (project == null) project = DefaultProjectFactory.getInstance().getDefaultProject(); - DirDiffTableModel model = new DirDiffTableModel(project, element1, element2, new DirDiffSettings()); - - myDirDiffPanel = new DirDiffPanel(model, new DirDiffWindow((DirDiffFrame)null) { - @Override - public Window getWindow() { - return null; - } + DirDiffSettings settings = ObjectUtils.notNull(context.getUserData(DirDiffSettings.KEY), new DirDiffSettings()); + DirDiffTableModel model = new DirDiffTableModel(project, element1, element2, settings); + myDirDiffPanel = new DirDiffPanel(model, new DirDiffWindow() { + @NotNull @Override public Disposable getDisposable() { return DirDiffViewer.this; } @Override - public void setTitle(String title) { + public void setTitle(@NotNull String title) { + if (context instanceof DiffContextEx) ((DiffContextEx)context).setWindowTitle(title); } }); diff --git a/platform/diff-impl/src/com/intellij/diff/tools/util/base/DiffPanelBase.java b/platform/diff-impl/src/com/intellij/diff/tools/util/base/DiffPanelBase.java index ea9628bb3d48..5f6a83b344b7 100644 --- a/platform/diff-impl/src/com/intellij/diff/tools/util/base/DiffPanelBase.java +++ b/platform/diff-impl/src/com/intellij/diff/tools/util/base/DiffPanelBase.java @@ -81,13 +81,18 @@ public abstract class DiffPanelBase extends JPanel implements DataProvider { } protected void setCurrentCard(@NotNull String card, boolean keepFocus) { - boolean restoreFocus = keepFocus && myContext.isFocused(); + Runnable task = () -> { + myCardLayout.show(myContentPanel, card); + myCurrentCard = card; + myContentPanel.revalidate(); + }; - myCardLayout.show(myContentPanel, card); - myCurrentCard = card; - myContentPanel.revalidate(); - - if (restoreFocus) myContext.requestFocus(); + if (keepFocus) { + DiffUtil.runPreservingFocus(myContext, task); + } + else { + task.run(); + } } @Nullable diff --git a/platform/diff-impl/src/com/intellij/diff/util/DiffUtil.java b/platform/diff-impl/src/com/intellij/diff/util/DiffUtil.java index f559e5a9200d..d51d480c8c8e 100644 --- a/platform/diff-impl/src/com/intellij/diff/util/DiffUtil.java +++ b/platform/diff-impl/src/com/intellij/diff/util/DiffUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -15,17 +15,14 @@ */ package com.intellij.diff.util; +import com.intellij.codeInsight.daemon.OutsidersPsiFileSupport; import com.intellij.codeStyle.CodeStyleFacade; -import com.intellij.diff.DiffContext; -import com.intellij.diff.DiffDialogHints; -import com.intellij.diff.DiffTool; -import com.intellij.diff.SuppressiveDiffTool; +import com.intellij.diff.*; import com.intellij.diff.comparison.ByWord; import com.intellij.diff.comparison.ComparisonMergeUtil; import com.intellij.diff.comparison.ComparisonPolicy; import com.intellij.diff.comparison.ComparisonUtil; import com.intellij.diff.contents.DiffContent; -import com.intellij.codeInsight.daemon.OutsidersPsiFileSupport; import com.intellij.diff.contents.DocumentContent; import com.intellij.diff.contents.EmptyContent; import com.intellij.diff.fragments.DiffFragment; @@ -84,7 +81,10 @@ import com.intellij.openapi.vfs.newvfs.RefreshQueue; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.openapi.wm.impl.IdeFrameImpl; import com.intellij.testFramework.LightVirtualFile; -import com.intellij.ui.*; +import com.intellij.ui.ColorUtil; +import com.intellij.ui.HyperlinkAdapter; +import com.intellij.ui.JBColor; +import com.intellij.ui.ScreenUtil; import com.intellij.ui.awt.RelativePoint; import com.intellij.ui.components.JBLabel; import com.intellij.util.ArrayUtil; @@ -516,13 +516,13 @@ public class DiffUtil { if (readOnly) title += " " + DiffBundle.message("diff.content.read.only.content.title.suffix"); JPanel panel = new JPanel(new BorderLayout()); - panel.setBorder(IdeBorderFactory.createEmptyBorder(0, 4, 0, 4)); + panel.setBorder(JBUI.Borders.empty(0, 4)); panel.add(new JBLabel(title).setCopyable(true), BorderLayout.CENTER); if (charset != null && separator != null) { JPanel panel2 = new JPanel(); panel2.setLayout(new BoxLayout(panel2, BoxLayout.X_AXIS)); panel2.add(createCharsetPanel(charset, bom)); - panel2.add(Box.createRigidArea(new Dimension(4, 0))); + panel2.add(Box.createRigidArea(JBUI.size(4, 0))); panel2.add(createSeparatorPanel(separator)); panel.add(panel2, BorderLayout.EAST); } @@ -609,7 +609,13 @@ public class DiffUtil { public static boolean isFocusedComponent(@Nullable Project project, @Nullable Component component) { if (component == null) return false; - return IdeFocusManager.getInstance(project).getFocusedDescendantFor(component) != null; + Component ideFocusOwner = IdeFocusManager.getInstance(project).getFocusOwner(); + if (ideFocusOwner != null && SwingUtilities.isDescendingFrom(ideFocusOwner, component)) return true; + + Component jdkFocusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); + if (jdkFocusOwner != null && SwingUtilities.isDescendingFrom(jdkFocusOwner, component)) return true; + + return false; } public static void requestFocus(@Nullable Project project, @Nullable Component component) { @@ -617,6 +623,13 @@ public class DiffUtil { IdeFocusManager.getInstance(project).requestFocus(component, true); } + public static void runPreservingFocus(@NotNull FocusableContext context, @NotNull Runnable task) { + boolean hadFocus = context.isFocused(); + if (hadFocus) KeyboardFocusManager.getCurrentKeyboardFocusManager().clearFocusOwner(); + task.run(); + if (hadFocus) context.requestFocus(); + } + // // Compare // diff --git a/platform/diff-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffDialog.java b/platform/diff-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffDialog.java index 31689c62e603..e9c3aceca0b5 100644 --- a/platform/diff-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffDialog.java +++ b/platform/diff-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffDialog.java @@ -53,7 +53,7 @@ public class DirDiffDialog extends DialogWrapper { @Override protected JComponent createCenterPanel() { - myDiffPanel = new DirDiffPanel(myModel, new DirDiffWindow(this)); + myDiffPanel = new DirDiffPanel(myModel, new DirDiffWindow.Dialog(this)); Disposer.register(getDisposable(), myDiffPanel); return myDiffPanel.getPanel(); } diff --git a/platform/diff-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffFrame.java b/platform/diff-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffFrame.java index 8821eccffd16..fbe910237779 100644 --- a/platform/diff-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffFrame.java +++ b/platform/diff-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffFrame.java @@ -34,7 +34,7 @@ public class DirDiffFrame extends FrameWrapper { super(project, "DirDiffDialog"); setSize(JBUI.size(800, 600)); setTitle(model.getTitle()); - myPanel = new DirDiffPanel(model, new DirDiffWindow(this)); + myPanel = new DirDiffPanel(model, new DirDiffWindow.Frame(this)); Disposer.register(this, myPanel); setComponent(myPanel.getPanel()); if (project != null) { diff --git a/platform/diff-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffTableModel.java b/platform/diff-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffTableModel.java index 0f442db799f3..87b82469a3e9 100644 --- a/platform/diff-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffTableModel.java +++ b/platform/diff-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffTableModel.java @@ -16,11 +16,15 @@ package com.intellij.openapi.diff.impl.dir; import com.intellij.CommonBundle; +import com.intellij.diff.DiffRequestFactory; import com.intellij.ide.IdeBundle; import com.intellij.ide.diff.*; import com.intellij.internal.statistic.UsageTrigger; import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.*; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diff.impl.dir.actions.popup.WarnOnDeletion; import com.intellij.openapi.progress.EmptyProgressIndicator; @@ -36,6 +40,7 @@ import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.VcsBundle; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.TableUtil; import com.intellij.ui.awt.RelativePoint; import com.intellij.ui.components.JBLoadingPanel; @@ -401,7 +406,13 @@ public class DirDiffTableModel extends AbstractTableModel implements DirDiffMode } public String getTitle() { - if (myDisposed) return ""; + if (myDisposed) return "Diff"; + if (mySrc instanceof VirtualFileDiffElement && + myTrg instanceof VirtualFileDiffElement) { + VirtualFile srcFile = ((VirtualFileDiffElement)mySrc).getValue(); + VirtualFile trgFile = ((VirtualFileDiffElement)myTrg).getValue(); + return DiffRequestFactory.getInstance().getTitle(srcFile, trgFile); + } return IdeBundle.message("diff.dialog.title", mySrc.getPresentablePath(), myTrg.getPresentablePath()); } diff --git a/platform/diff-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffWindow.java b/platform/diff-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffWindow.java index dc8bb6b57337..1ff61f7fdad8 100644 --- a/platform/diff-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffWindow.java +++ b/platform/diff-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffWindow.java @@ -16,40 +16,52 @@ package com.intellij.openapi.diff.impl.dir; import com.intellij.openapi.Disposable; +import org.jetbrains.annotations.NotNull; import javax.swing.*; -import java.awt.*; -/** - * @author Konstantin Bulenkov - */ -public class DirDiffWindow { - private final DirDiffDialog myDialog; - private final DirDiffFrame myFrame; +public interface DirDiffWindow { + @NotNull + Disposable getDisposable(); - public DirDiffWindow(DirDiffDialog dialog) { - myDialog = dialog; - myFrame = null; - } + void setTitle(@NotNull String title); - public DirDiffWindow(DirDiffFrame frame) { - myFrame = frame; - myDialog = null; - } - public Window getWindow() { - return myDialog == null ? myFrame.getFrame() : myDialog.getWindow(); - } + class Dialog implements DirDiffWindow { + @NotNull private final DirDiffDialog myDialog; - public Disposable getDisposable() { - return myDialog == null ? myFrame : myDialog.getDisposable(); - } + public Dialog(@NotNull DirDiffDialog dialog) { + myDialog = dialog; + } - public void setTitle(String title) { - if (myDialog == null) { - ((JFrame)myFrame.getFrame()).setTitle(title); - } else { + @NotNull + @Override + public Disposable getDisposable() { + return myDialog.getDisposable(); + } + + @Override + public void setTitle(@NotNull String title) { myDialog.setTitle(title); } } + + class Frame implements DirDiffWindow { + @NotNull private final DirDiffFrame myFrame; + + public Frame(@NotNull DirDiffFrame frame) { + myFrame = frame; + } + + @NotNull + @Override + public Disposable getDisposable() { + return myFrame; + } + + @Override + public void setTitle(@NotNull String title) { + ((JFrame)myFrame.getFrame()).setTitle(title); + } + } } diff --git a/platform/diff-impl/src/com/intellij/openapi/diff/impl/dir/actions/ChangeCompareModeGroup.java b/platform/diff-impl/src/com/intellij/openapi/diff/impl/dir/actions/ChangeCompareModeGroup.java index 50ae0fe3adba..5d75c96e71c0 100644 --- a/platform/diff-impl/src/com/intellij/openapi/diff/impl/dir/actions/ChangeCompareModeGroup.java +++ b/platform/diff-impl/src/com/intellij/openapi/diff/impl/dir/actions/ChangeCompareModeGroup.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -20,11 +20,10 @@ import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.actionSystem.ex.ComboBoxAction; import com.intellij.openapi.diff.impl.dir.DirDiffTableModel; import com.intellij.openapi.project.DumbAware; -import com.intellij.ui.IdeBorderFactory; +import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.NotNull; import javax.swing.*; -import java.awt.*; import java.util.ArrayList; /** @@ -65,14 +64,12 @@ public class ChangeCompareModeGroup extends ComboBoxAction implements ShortcutPr @Override public JComponent createCustomComponent(Presentation presentation) { - JPanel panel = new JPanel(new BorderLayout()); final JLabel label = new JLabel("Compare by:"); label.setDisplayedMnemonicIndex(0); - panel.add(label, BorderLayout.WEST); myButton = (JButton)super.createCustomComponent(presentation).getComponent(0); - panel.add(myButton, BorderLayout.CENTER); - panel.setBorder(IdeBorderFactory.createEmptyBorder(2, 6, 2, 0)); - return panel; + return JBUI.Panels.simplePanel(myButton) + .addToLeft(label) + .withBorder(JBUI.Borders.empty(2, 6, 2, 0)); } @NotNull diff --git a/platform/diff-impl/src/com/intellij/openapi/vcs/ex/RangesBuilder.java b/platform/diff-impl/src/com/intellij/openapi/vcs/ex/RangesBuilder.java index fec32fd937d5..d7464e2b59d8 100644 --- a/platform/diff-impl/src/com/intellij/openapi/vcs/ex/RangesBuilder.java +++ b/platform/diff-impl/src/com/intellij/openapi/vcs/ex/RangesBuilder.java @@ -17,6 +17,7 @@ package com.intellij.openapi.vcs.ex; import com.intellij.diff.comparison.ByLine; import com.intellij.diff.comparison.ComparisonPolicy; +import com.intellij.diff.comparison.DiffTooBigException; import com.intellij.diff.comparison.TrimUtil; import com.intellij.diff.comparison.iterables.DiffIterableUtil; import com.intellij.diff.comparison.iterables.FairDiffIterable; @@ -28,6 +29,7 @@ import com.intellij.openapi.util.Pair; import com.intellij.openapi.vcs.ex.Range.InnerRange; import com.intellij.util.diff.FilesTooBigForDiffException; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; @@ -52,11 +54,16 @@ public class RangesBuilder { int currentShift, int vcsShift, boolean innerWhitespaceChanges) throws FilesTooBigForDiffException { - if (innerWhitespaceChanges) { - return createRangesSmart(current, vcs, currentShift, vcsShift); + try { + if (innerWhitespaceChanges) { + return createRangesSmart(current, vcs, currentShift, vcsShift); + } + else { + return createRangesSimple(current, vcs, currentShift, vcsShift); + } } - else { - return createRangesSimple(current, vcs, currentShift, vcsShift); + catch (DiffTooBigException e) { + throw new FilesTooBigForDiffException(); } } @@ -64,7 +71,7 @@ public class RangesBuilder { private static List createRangesSimple(@NotNull List current, @NotNull List vcs, int currentShift, - int vcsShift) throws FilesTooBigForDiffException { + int vcsShift) throws DiffTooBigException { FairDiffIterable iterable = ByLine.compare(vcs, current, ComparisonPolicy.DEFAULT, DumbProgressIndicator.INSTANCE); List result = new ArrayList<>(); @@ -83,7 +90,7 @@ public class RangesBuilder { private static List createRangesSmart(@NotNull List current, @NotNull List vcs, int currentShift, - int vcsShift) throws FilesTooBigForDiffException { + int vcsShift) throws DiffTooBigException { FairDiffIterable iwIterable = ByLine.compare(vcs, current, ComparisonPolicy.IGNORE_WHITESPACES, DumbProgressIndicator.INSTANCE); RangeBuilder rangeBuilder = new RangeBuilder(current, vcs, currentShift, vcsShift); @@ -140,23 +147,28 @@ public class RangesBuilder { myResult.add(newRange); } - @NotNull + @Nullable private List calcInnerRanges(@NotNull com.intellij.diff.util.Range blockRange) { - List vcs = myVcs.subList(blockRange.start1, blockRange.end1); - List current = myCurrent.subList(blockRange.start2, blockRange.end2); + try { + List vcs = myVcs.subList(blockRange.start1, blockRange.end1); + List current = myCurrent.subList(blockRange.start2, blockRange.end2); - ArrayList result = new ArrayList<>(); - FairDiffIterable iwIterable = ByLine.compare(vcs, current, ComparisonPolicy.IGNORE_WHITESPACES, DumbProgressIndicator.INSTANCE); - for (Pair pair : DiffIterableUtil.iterateAll(iwIterable)) { - com.intellij.diff.util.Range range = pair.first; - Boolean equals = pair.second; + ArrayList result = new ArrayList<>(); + FairDiffIterable iwIterable = ByLine.compare(vcs, current, ComparisonPolicy.IGNORE_WHITESPACES, DumbProgressIndicator.INSTANCE); + for (Pair pair : DiffIterableUtil.iterateAll(iwIterable)) { + com.intellij.diff.util.Range range = pair.first; + Boolean equals = pair.second; - byte type = equals ? Range.EQUAL : getChangeType(range.start1, range.end1, range.start2, range.end2); - result.add(new InnerRange(range.start2 + blockRange.start2, range.end2 + blockRange.start2, - type)); + byte type = equals ? Range.EQUAL : getChangeType(range.start1, range.end1, range.start2, range.end2); + result.add(new InnerRange(range.start2 + blockRange.start2, range.end2 + blockRange.start2, + type)); + } + result.trimToSize(); + return result; + } + catch (DiffTooBigException e) { + return null; } - result.trimToSize(); - return result; } } diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/DvcsUtil.java b/platform/dvcs-impl/src/com/intellij/dvcs/DvcsUtil.java index b15d129c19c4..f6c4725f8875 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/DvcsUtil.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/DvcsUtil.java @@ -36,7 +36,6 @@ import com.intellij.openapi.roots.JdkOrderEntry; import com.intellij.openapi.roots.LibraryOrderEntry; import com.intellij.openapi.roots.OrderEntry; import com.intellij.openapi.roots.ProjectRootManager; -import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.AbstractVcs; @@ -48,7 +47,6 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.StatusBar; import com.intellij.openapi.wm.WindowManager; import com.intellij.openapi.wm.impl.status.StatusBarUtil; -import com.intellij.util.Consumer; import com.intellij.util.Function; import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; @@ -374,21 +372,6 @@ public class DvcsUtil { return rootCandidate; } - public static class Updater implements Consumer { - private final Repository myRepository; - - public Updater(Repository repository) { - myRepository = repository; - } - - @Override - public void consume(Object dummy) { - if (!Disposer.isDisposed(myRepository)) { - myRepository.update(); - } - } - } - public static List sortRepositories(@NotNull Collection repositories) { List validRepositories = ContainerUtil.filter(repositories, t -> t.getRoot().isValid()); Collections.sort(validRepositories, REPOSITORY_COMPARATOR); 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 cc01c95a2b75..2868ec68e54a 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/push/PushController.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/PushController.java @@ -138,10 +138,12 @@ public class PushController implements Disposable { Map priorityLoading = ContainerUtil.newLinkedHashMap(); Map others = ContainerUtil.newLinkedHashMap(); RepositoryNode nodeForCurrentEditor = findNodeByRepo(myCurrentlyOpenedRepository); - MyRepoModel currentRepoModel = myView2Model.get(nodeForCurrentEditor); - if (nodeForCurrentEditor != null && isPreChecked(currentRepoModel)) { - // put current editor repo to be loaded at first - priorityLoading.put(nodeForCurrentEditor, currentRepoModel); + if (nodeForCurrentEditor != null) { + MyRepoModel currentRepoModel = myView2Model.get(nodeForCurrentEditor); + if (isPreChecked(currentRepoModel)) { + // put current editor repo to be loaded at first + priorityLoading.put(nodeForCurrentEditor, currentRepoModel); + } } for (Map.Entry> entry : myView2Model.entrySet()) { diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/VcsCommitInfoBalloon.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/VcsCommitInfoBalloon.java index 015f5baadd04..b72d44957cf8 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/VcsCommitInfoBalloon.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/VcsCommitInfoBalloon.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -22,9 +22,9 @@ import com.intellij.openapi.ui.popup.JBPopup; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.vcs.history.VcsHistoryUtil; import com.intellij.ui.HyperlinkAdapter; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.awt.RelativePoint; import com.intellij.ui.components.panels.Wrapper; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import com.intellij.xml.util.XmlStringUtil; import org.jetbrains.annotations.NotNull; @@ -54,7 +54,7 @@ public class VcsCommitInfoBalloon { myEditorPane.setBackground(HintUtil.getInformationColor()); myEditorPane.setFont(VcsHistoryUtil.getCommitDetailsFont()); myEditorPane.setBorder(HintUtil.createHintBorder()); - Border margin = IdeBorderFactory.createEmptyBorder(3, 3, 3, 3); + Border margin = JBUI.Borders.empty(3, 3, 3, 3); myEditorPane.setBorder(new CompoundBorder(myEditorPane.getBorder(), margin)); myEditorPane.addHyperlinkListener(new HyperlinkAdapter() { @Override diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/ui/CloneDvcsDialog.java b/platform/dvcs-impl/src/com/intellij/dvcs/ui/CloneDvcsDialog.java index 8ef109281e3c..5bdda83d9c46 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/ui/CloneDvcsDialog.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/ui/CloneDvcsDialog.java @@ -118,7 +118,7 @@ public abstract class CloneDvcsDialog extends DialogWrapper { } public String getParentDirectory() { - return myParentDirectory.getText(); + return FileUtil.expandUserHome(myParentDirectory.getText().trim()); } public String getDirectoryName() { diff --git a/platform/editor-ui-api/src/com/intellij/ide/ui/UISettings.kt b/platform/editor-ui-api/src/com/intellij/ide/ui/UISettings.kt index 02282f85a89f..9b1d7fd7cfa1 100644 --- a/platform/editor-ui-api/src/com/intellij/ide/ui/UISettings.kt +++ b/platform/editor-ui-api/src/com/intellij/ide/ui/UISettings.kt @@ -85,7 +85,7 @@ class UISettings : BaseState(), PersistentStateComponent { @get:OptionTag("HIDE_TABS_IF_NEED") var hideTabsIfNeed by storedProperty(true) @get:OptionTag("SHOW_CLOSE_BUTTON") var showCloseButton by storedProperty(true) @get:OptionTag("EDITOR_TAB_PLACEMENT") var editorTabPlacement by storedProperty(1) - @get:OptionTag("HIDE_KNOWN_EXTENSION_IN_TABS") var hdeKnownExtensionInTabs by storedProperty(false) + @get:OptionTag("HIDE_KNOWN_EXTENSION_IN_TABS") var hideKnownExtensionInTabs by storedProperty(false) @get:OptionTag("SHOW_ICONS_IN_QUICK_NAVIGATION") var showIconInQuickNavigation by storedProperty(true) @get:OptionTag("CLOSE_NON_MODIFIED_FILES_FIRST") var closeNonModifiedFilesFirst by storedProperty(false) diff --git a/platform/editor-ui-api/src/com/intellij/openapi/editor/Inlay.java b/platform/editor-ui-api/src/com/intellij/openapi/editor/Inlay.java index 1afa105a8fc2..cc12e3f67873 100644 --- a/platform/editor-ui-api/src/com/intellij/openapi/editor/Inlay.java +++ b/platform/editor-ui-api/src/com/intellij/openapi/editor/Inlay.java @@ -65,4 +65,9 @@ public interface Inlay extends Disposable, UserDataHolderEx { * @see EditorCustomElementRenderer#calcWidthInPixels(Editor) */ void updateSize(); + + /** + * Causes repaint of inlay in editor. + */ + void repaint(); } diff --git a/platform/editor-ui-api/src/com/intellij/openapi/fileEditor/FileEditor.java b/platform/editor-ui-api/src/com/intellij/openapi/fileEditor/FileEditor.java index 26b93bf26956..159a2cb1861a 100644 --- a/platform/editor-ui-api/src/com/intellij/openapi/fileEditor/FileEditor.java +++ b/platform/editor-ui-api/src/com/intellij/openapi/fileEditor/FileEditor.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -19,6 +19,7 @@ import com.intellij.codeHighlighting.BackgroundEditorHighlighter; import com.intellij.ide.structureView.StructureViewBuilder; import com.intellij.openapi.Disposable; import com.intellij.openapi.util.UserDataHolder; +import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -133,4 +134,7 @@ public interface FileEditor extends UserDataHolder, Disposable { default StructureViewBuilder getStructureViewBuilder() { return null; } + + @Nullable + default VirtualFile getFile() { return null; } } diff --git a/platform/editor-ui-ex/src/com/intellij/psi/impl/cache/impl/todo/TodoIndex.java b/platform/editor-ui-ex/src/com/intellij/psi/impl/cache/impl/todo/TodoIndex.java index 2347029537d3..c1cdd5a5a7c4 100644 --- a/platform/editor-ui-ex/src/com/intellij/psi/impl/cache/impl/todo/TodoIndex.java +++ b/platform/editor-ui-ex/src/com/intellij/psi/impl/cache/impl/todo/TodoIndex.java @@ -113,7 +113,7 @@ public class TodoIndex extends FileBasedIndexExtension } }; - private final FileBasedIndex.InputFilter myInputFilter = file -> { + protected final FileBasedIndex.InputFilter myInputFilter = file -> { if (!file.isInLocalFileSystem()) { return false; // do not index TODOs in library sources } diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/project/ModuleData.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/project/ModuleData.java index ab0be13b5c3c..1bb1512ac4c8 100644 --- a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/project/ModuleData.java +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/project/ModuleData.java @@ -32,6 +32,7 @@ public class ModuleData extends AbstractNamedData implements Named, ExternalConf @Nullable private String[] myIdeModuleGroup; @Nullable private String mySourceCompatibility; @Nullable private String myTargetCompatibility; + @Nullable private String mySdkName; @Nullable private String myProductionModuleId; @Nullable private ProjectCoordinate myPublication; @@ -212,6 +213,15 @@ public class ModuleData extends AbstractNamedData implements Named, ExternalConf myTargetCompatibility = targetCompatibility; } + @Nullable + public String getSdkName() { + return mySdkName; + } + + public void setSdkName(@Nullable String sdkName) { + mySdkName = sdkName; + } + @Override public boolean equals(Object o) { if (!(o instanceof ModuleData)) return false; @@ -224,6 +234,7 @@ public class ModuleData extends AbstractNamedData implements Named, ExternalConf if (!myModuleTypeId.equals(that.myModuleTypeId)) return false; if (myVersion != null ? !myVersion.equals(that.myVersion) : that.myVersion != null) return false; if (myDescription != null ? !myDescription.equals(that.myDescription) : that.myDescription != null) return false; + if (mySdkName != null ? !mySdkName.equals(that.mySdkName) : that.mySdkName != null) return false; return true; } @@ -236,6 +247,7 @@ public class ModuleData extends AbstractNamedData implements Named, ExternalConf result = 31 * result + (myGroup != null ? myGroup.hashCode() : 0); result = 31 * result + (myVersion != null ? myVersion.hashCode() : 0); result = 31 * result + (myDescription != null ? myDescription.hashCode() : 0); + result = 31 * result + (mySdkName != null ? mySdkName.hashCode() : 0); return result; } diff --git a/platform/external-system-impl/resources/icons/task.png b/platform/external-system-impl/resources/icons/task.png index 7e8b5eb705c3..d14370bcb985 100644 Binary files a/platform/external-system-impl/resources/icons/task.png and b/platform/external-system-impl/resources/icons/task.png differ diff --git a/platform/external-system-impl/resources/icons/task@2x.png b/platform/external-system-impl/resources/icons/task@2x.png new file mode 100644 index 000000000000..b07deaaf6e39 Binary files /dev/null and b/platform/external-system-impl/resources/icons/task@2x.png differ diff --git a/platform/external-system-impl/resources/icons/taskGroup.png b/platform/external-system-impl/resources/icons/taskGroup.png index 7604d83886e9..5c6ec5b4fc16 100644 Binary files a/platform/external-system-impl/resources/icons/taskGroup.png and b/platform/external-system-impl/resources/icons/taskGroup.png differ diff --git a/platform/external-system-impl/resources/icons/taskGroup@2x.png b/platform/external-system-impl/resources/icons/taskGroup@2x.png new file mode 100644 index 000000000000..9714772b1150 Binary files /dev/null and b/platform/external-system-impl/resources/icons/taskGroup@2x.png differ diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/AbstractModuleDataService.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/AbstractModuleDataService.java index 5d8594c85a82..aac0d1595391 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/AbstractModuleDataService.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/AbstractModuleDataService.java @@ -33,6 +33,9 @@ import com.intellij.openapi.module.ModifiableModuleModel; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.UnloadedModuleDescription; import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.JavaSdk; +import com.intellij.openapi.projectRoots.ProjectJdkTable; +import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.*; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.util.Computable; @@ -41,13 +44,13 @@ import com.intellij.openapi.util.Pair; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.pom.java.LanguageLevel; import com.intellij.ui.CheckBoxList; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.components.JBScrollPane; import com.intellij.util.ArrayUtil; import com.intellij.util.Consumer; import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.ContainerUtilRt; +import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -96,6 +99,7 @@ public abstract class AbstractModuleDataService extends Ab ModifiableRootModel modifiableRootModel = modelsProvider.getModifiableRootModel(module); syncPaths(module, modifiableRootModel, node.getData()); setLanguageLevel(modifiableRootModel, node.getData()); + setSdk(modifiableRootModel, node.getData()); } } @@ -252,9 +256,9 @@ public abstract class AbstractModuleDataService extends Ab for (Module module : orphanModules) { orphanModulesList.setItemSelected(module, true); } - orphanModulesList.setBorder(IdeBorderFactory.createEmptyBorder(8)); + orphanModulesList.setBorder(JBUI.Borders.empty(8)); content.add(orphanModulesList, ExternalSystemUiUtil.getFillLineConstraints(0)); - content.setBorder(IdeBorderFactory.createEmptyBorder(0, 0, 8, 0)); + content.setBorder(JBUI.Borders.empty(0, 0, 8, 0)); DialogWrapper dialog = new DialogWrapper(project) { { @@ -400,6 +404,19 @@ public abstract class AbstractModuleDataService extends Ab } } + private void setSdk(@NotNull ModifiableRootModel modifiableRootModel, E data) { + String skdName = data.getSdkName(); + if (skdName != null) { + ProjectJdkTable projectJdkTable = ProjectJdkTable.getInstance(); + Sdk sdk = projectJdkTable.findJdk(skdName); + if (sdk != null) { + modifiableRootModel.setSdk(sdk); + } else { + modifiableRootModel.setInvalidSdk(skdName, JavaSdk.getInstance().getName()); + } + } + } + private void setBytecodeTargetLevel(@NotNull Project project, @NotNull Module module, @NotNull E data) { String targetLevel = data.getTargetCompatibility(); if (targetLevel != null) { diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/task/ui/ConfigureTasksActivationDialog.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/task/ui/ConfigureTasksActivationDialog.java index 573caa240402..2d937d140091 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/task/ui/ConfigureTasksActivationDialog.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/task/ui/ConfigureTasksActivationDialog.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -46,13 +46,17 @@ import com.intellij.openapi.ui.popup.PopupStep; import com.intellij.openapi.ui.popup.util.BaseListPopupStep; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.WriteExternalException; -import com.intellij.ui.*; +import com.intellij.ui.AnActionButton; +import com.intellij.ui.AnActionButtonRunnable; +import com.intellij.ui.AnActionButtonUpdater; +import com.intellij.ui.ToolbarDecorator; import com.intellij.ui.awt.RelativePoint; import com.intellij.ui.treeStructure.*; import com.intellij.util.ArrayUtil; import com.intellij.util.Function; import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.SwingHelper; import icons.ExternalSystemIcons; import org.jdom.Element; @@ -209,7 +213,7 @@ public class ConfigureTasksActivationDialog extends DialogWrapper { } }). setToolbarPosition(ActionToolbarPosition.RIGHT). - setToolbarBorder(IdeBorderFactory.createEmptyBorder()); + setToolbarBorder(JBUI.Borders.empty()); tasksPanel.add(decorator.createPanel()); return contentPane; } diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/ui/ExternalProjectDataSelectorDialog.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/ui/ExternalProjectDataSelectorDialog.java index cd7f5336ce84..44fb121d0df9 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/ui/ExternalProjectDataSelectorDialog.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/ui/ExternalProjectDataSelectorDialog.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -55,6 +55,7 @@ import com.intellij.ui.treeStructure.Tree; import com.intellij.util.CachedValueImpl; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.tree.TreeUtil; import gnu.trove.TObjectHashingStrategy; import org.jetbrains.annotations.NotNull; @@ -164,7 +165,7 @@ public class ExternalProjectDataSelectorDialog extends DialogWrapper { addExtraAction(new ShowSelectedOnlyButton()). addExtraAction(new SelectRequiredButton()). setToolbarPosition(ActionToolbarPosition.BOTTOM). - setToolbarBorder(IdeBorderFactory.createEmptyBorder()); + setToolbarBorder(JBUI.Borders.empty()); contentPanel.add(decorator.createPanel()); loadingPanel = new JBLoadingPanel(new BorderLayout(), getDisposable()); diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/ui/ExternalProjectPathField.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/ui/ExternalProjectPathField.java index 71964e414e77..6a23fc6385af 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/ui/ExternalProjectPathField.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/ui/ExternalProjectPathField.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -44,7 +44,6 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.EditorTextField; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.TextAccessor; import com.intellij.ui.treeStructure.Tree; import com.intellij.util.TextFieldCompletionProvider; @@ -52,6 +51,7 @@ import com.intellij.util.TextFieldCompletionProviderDumbAware; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.ContainerUtilRt; import com.intellij.util.ui.GridBag; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; @@ -108,7 +108,7 @@ public class ExternalProjectPathField extends ComponentWithBrowseButton popupRef = new Ref<>(); final Tree tree = buildRegisteredProjectsTree(project, externalSystemId); - tree.setBorder(IdeBorderFactory.createEmptyBorder(8)); + tree.setBorder(JBUI.Borders.empty(8)); Runnable treeSelectionCallback = () -> { TreePath path = tree.getSelectionPath(); if (path != null) { diff --git a/platform/icons/src/toolbarDecorator/addYouTrack.png b/platform/icons/src/toolbarDecorator/addYouTrack.png index 7a41b6497342..ef4f3299d6e4 100644 Binary files a/platform/icons/src/toolbarDecorator/addYouTrack.png and b/platform/icons/src/toolbarDecorator/addYouTrack.png differ diff --git a/platform/icons/src/toolbarDecorator/addYouTrack@2x.png b/platform/icons/src/toolbarDecorator/addYouTrack@2x.png index 1ebb849f021e..4ba0141f5dae 100644 Binary files a/platform/icons/src/toolbarDecorator/addYouTrack@2x.png and b/platform/icons/src/toolbarDecorator/addYouTrack@2x.png differ diff --git a/platform/icons/src/toolbarDecorator/addYouTrack@2x_dark.png b/platform/icons/src/toolbarDecorator/addYouTrack@2x_dark.png new file mode 100644 index 000000000000..c6392533110a Binary files /dev/null and b/platform/icons/src/toolbarDecorator/addYouTrack@2x_dark.png differ diff --git a/platform/icons/src/toolbarDecorator/addYouTrack_dark.png b/platform/icons/src/toolbarDecorator/addYouTrack_dark.png new file mode 100644 index 000000000000..68034f7df46a Binary files /dev/null and b/platform/icons/src/toolbarDecorator/addYouTrack_dark.png differ diff --git a/platform/lang-api/src/com/intellij/codeInsight/daemon/MergeableLineMarkerInfo.java b/platform/lang-api/src/com/intellij/codeInsight/daemon/MergeableLineMarkerInfo.java index 18dc5376bbca..0449c679722e 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/daemon/MergeableLineMarkerInfo.java +++ b/platform/lang-api/src/com/intellij/codeInsight/daemon/MergeableLineMarkerInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -21,12 +21,12 @@ import com.intellij.openapi.ui.popup.PopupChooserBuilder; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.awt.RelativePoint; import com.intellij.ui.components.JBLabel; import com.intellij.ui.components.JBList; import com.intellij.util.Function; import com.intellij.util.SmartList; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -151,7 +151,7 @@ public abstract class MergeableLineMarkerInfo extends Line String text = StringUtil.first(elementPresentation, 100, true).replace('\n', ' '); final JBLabel label = new JBLabel(text, icon, SwingConstants.LEFT); - label.setBorder(IdeBorderFactory.createEmptyBorder(2)); + label.setBorder(JBUI.Borders.empty(2)); return label; } diff --git a/platform/lang-api/src/com/intellij/codeInsight/hints/InlayParameterHintsProvider.kt b/platform/lang-api/src/com/intellij/codeInsight/hints/InlayParameterHintsProvider.kt index 691e4ff67067..96ea1355cb48 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/hints/InlayParameterHintsProvider.kt +++ b/platform/lang-api/src/com/intellij/codeInsight/hints/InlayParameterHintsProvider.kt @@ -31,7 +31,7 @@ object InlayParameterHintsExtension : LanguageExtension { // Can be null if parameter info does not track parameter index @Nullable String getParameterCloseChars(); boolean tracksParameterIndex(); + + default void dispose() {} } diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/component/ArrangementAtomMatchConditionComponent.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/component/ArrangementAtomMatchConditionComponent.java index 855c354ce07a..77def4571c02 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/component/ArrangementAtomMatchConditionComponent.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/component/ArrangementAtomMatchConditionComponent.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -175,7 +175,7 @@ public class ArrangementAtomMatchConditionComponent implements ArrangementUiComp GridBagConstraints constraints = new GridBag().anchor(GridBagConstraints.WEST).weightx(1) .insets(0, 0, 0, myCloseButton == null ? ArrangementConstants.BORDER_ARC_SIZE : 0); insetsPanel.add(myTextControl, constraints); - insetsPanel.setBorder(IdeBorderFactory.createEmptyBorder(0, ArrangementConstants.HORIZONTAL_PADDING, 0, 0)); + insetsPanel.setBorder(JBUI.Borders.emptyLeft(ArrangementConstants.HORIZONTAL_PADDING)); insetsPanel.setOpaque(false); JPanel roundBorderPanel = new JPanel(new GridBagLayout()) { diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/group/ArrangementGroupingComponent.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/group/ArrangementGroupingComponent.java index 4bb84a63e397..95e1cc3ab0fb 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/group/ArrangementGroupingComponent.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/group/ArrangementGroupingComponent.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -22,9 +22,9 @@ import com.intellij.application.options.codeStyle.arrangement.ui.ArrangementRepr import com.intellij.application.options.codeStyle.arrangement.util.ArrangementRuleIndexControl; import com.intellij.psi.codeStyle.arrangement.ArrangementUtil; import com.intellij.psi.codeStyle.arrangement.std.*; -import com.intellij.ui.IdeBorderFactory; import com.intellij.util.containers.ContainerUtilRt; import com.intellij.util.ui.GridBag; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -122,7 +122,7 @@ public class ArrangementGroupingComponent extends JPanel implements ArrangementR add(new JLabel(" "), new GridBag().weightx(1).fillCellHorizontally()); setBackground(UIUtil.getListBackground()); - setBorder(IdeBorderFactory.createEmptyBorder(ArrangementConstants.VERTICAL_GAP)); + setBorder(JBUI.Borders.empty(ArrangementConstants.VERTICAL_GAP)); setOpaque(!UIUtil.isUnderIntelliJLaF() && !UIUtil.isUnderNativeMacLookAndFeel() && !UIUtil.isUnderDarcula()); } diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/match/ArrangementMatchingRuleEditor.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/match/ArrangementMatchingRuleEditor.java index bb2438e1d51a..96b2783de6c4 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/match/ArrangementMatchingRuleEditor.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/match/ArrangementMatchingRuleEditor.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -25,9 +25,9 @@ import com.intellij.psi.codeStyle.arrangement.match.StdArrangementEntryMatcher; import com.intellij.psi.codeStyle.arrangement.match.StdArrangementMatchRule; import com.intellij.psi.codeStyle.arrangement.model.ArrangementMatchCondition; import com.intellij.psi.codeStyle.arrangement.std.*; -import com.intellij.ui.IdeBorderFactory; import com.intellij.util.containers.ContainerUtilRt; import com.intellij.util.ui.GridBag; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.MultiRowFlowPanel; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; @@ -91,7 +91,7 @@ public class ArrangementMatchingRuleEditor extends JPanel implements Arrangement private void init(@Nullable List tokens) { setLayout(new GridBagLayout()); - setBorder(IdeBorderFactory.createEmptyBorder(5)); + setBorder(JBUI.Borders.empty(5)); if (tokens != null) { for (CompositeArrangementSettingsToken token : tokens) { diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/match/ArrangementMatchingRulesPanel.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/match/ArrangementMatchingRulesPanel.java index fedc66f83685..0ac1fd41ddf0 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/match/ArrangementMatchingRulesPanel.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/match/ArrangementMatchingRulesPanel.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -25,10 +25,10 @@ import com.intellij.openapi.application.ApplicationBundle; import com.intellij.psi.codeStyle.arrangement.match.ArrangementSectionRule; import com.intellij.psi.codeStyle.arrangement.std.ArrangementStandardSettingsManager; import com.intellij.psi.codeStyle.arrangement.std.StdArrangementRuleAliasToken; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.SideBorder; import com.intellij.ui.components.JBScrollPane; import com.intellij.util.ui.GridBag; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -77,7 +77,7 @@ public class ArrangementMatchingRulesPanel extends JPanel implements DataProvide }; myControl = createRulesControl(language, settingsManager, colorsProvider, callback); scrollPane.setViewportView(myControl); - scrollPane.setViewportBorder(IdeBorderFactory.createEmptyBorder(0, 0, 0, scrollPane.getVerticalScrollBar().getPreferredSize().width)); + scrollPane.setViewportBorder(JBUI.Borders.emptyRight(scrollPane.getVerticalScrollBar().getPreferredSize().width)); CustomizationUtil.installPopupHandler( myControl, ArrangementConstants.ACTION_GROUP_MATCHING_RULES_CONTEXT_MENU, ArrangementConstants.MATCHING_RULES_CONTROL_PLACE ); diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/util/ArrangementListRowDecorator.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/util/ArrangementListRowDecorator.java index 897c9cd22fd3..955064269ed9 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/util/ArrangementListRowDecorator.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/util/ArrangementListRowDecorator.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -27,9 +27,9 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.codeStyle.arrangement.model.ArrangementMatchCondition; import com.intellij.psi.codeStyle.arrangement.std.ArrangementSettingsToken; import com.intellij.psi.codeStyle.arrangement.std.ArrangementUiComponent; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.SimpleColoredComponent; import com.intellij.util.ui.GridBag; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -98,7 +98,7 @@ public class ArrangementListRowDecorator extends JPanel implements ArrangementUi add(new InsetsPanel(mySortLabel), new GridBag().anchor(GridBagConstraints.CENTER).insets(0, 0, 0, ArrangementConstants.HORIZONTAL_GAP)); add(myDelegate.getUiComponent(), new GridBag().weightx(1).anchor(GridBagConstraints.WEST)); add(myEditButton, new GridBag().anchor(GridBagConstraints.EAST)); - setBorder(IdeBorderFactory.createEmptyBorder(ArrangementConstants.VERTICAL_GAP)); + setBorder(JBUI.Borders.empty(ArrangementConstants.VERTICAL_GAP)); } @Override diff --git a/platform/lang-impl/src/com/intellij/application/options/colors/ColorAndFontOptions.java b/platform/lang-impl/src/com/intellij/application/options/colors/ColorAndFontOptions.java index b748955ec0bf..129d75027176 100644 --- a/platform/lang-impl/src/com/intellij/application/options/colors/ColorAndFontOptions.java +++ b/platform/lang-impl/src/com/intellij/application/options/colors/ColorAndFontOptions.java @@ -1254,7 +1254,7 @@ public class ColorAndFontOptions extends SearchableConfigurable.Parent.Abstract @Nullable public SearchableConfigurable findSubConfigurable(@NotNull Class pageClass) { if (mySubPanelFactories == null) { - buildConfigurables(); + getConfigurables(); } for (Map.Entry entry : mySubPanelFactories.entrySet()) { if (pageClass.isInstance(entry.getValue().createPanel().getSettingsPage())) { diff --git a/platform/lang-impl/src/com/intellij/application/options/editor/EditorTabsConfigurable.java b/platform/lang-impl/src/com/intellij/application/options/editor/EditorTabsConfigurable.java index cc589ff4a1d4..03e554769ab9 100644 --- a/platform/lang-impl/src/com/intellij/application/options/editor/EditorTabsConfigurable.java +++ b/platform/lang-impl/src/com/intellij/application/options/editor/EditorTabsConfigurable.java @@ -123,7 +123,7 @@ public class EditorTabsConfigurable implements EditorOptionsProvider { myHideTabsCheckbox.setEnabled(myScrollTabLayoutInEditorCheckBox.isSelected()); myHideTabsCheckbox.setSelected(uiSettings.getHideTabsIfNeed()); myEditorTabPlacement.setSelectedItem(uiSettings.getEditorTabPlacement()); - myHideKnownExtensions.setSelected(uiSettings.getHdeKnownExtensionInTabs()); + myHideKnownExtensions.setSelected(uiSettings.getHideKnownExtensionInTabs()); myShowDirectoryInTabCheckBox.setSelected(uiSettings.getShowDirectoryForNonUniqueFilenames()); myEditorTabLimitField.setText(Integer.toString(uiSettings.getEditorTabLimit())); myReuseNotModifiedTabsCheckBox.setSelected(uiSettings.getReuseNotModifiedTabs()); @@ -170,8 +170,8 @@ public class EditorTabsConfigurable implements EditorOptionsProvider { uiSettings.setEditorTabPlacement(tabPlacement); boolean hide = myHideKnownExtensions.isSelected(); - if (uiSettings.getHdeKnownExtensionInTabs() != hide) uiSettingsChanged = true; - uiSettings.setHdeKnownExtensionInTabs(hide); + if (uiSettings.getHideKnownExtensionInTabs() != hide) uiSettingsChanged = true; + uiSettings.setHideKnownExtensionInTabs(hide); boolean dir = myShowDirectoryInTabCheckBox.isSelected(); if (uiSettings.getShowDirectoryForNonUniqueFilenames() != dir) uiSettingsChanged = true; @@ -204,7 +204,7 @@ public class EditorTabsConfigurable implements EditorOptionsProvider { isModified |= isModified(myReuseNotModifiedTabsCheckBox, uiSettings.getReuseNotModifiedTabs()); int tabPlacement = ((Integer)myEditorTabPlacement.getSelectedItem()).intValue(); isModified |= tabPlacement != uiSettings.getEditorTabPlacement(); - isModified |= myHideKnownExtensions.isSelected() != uiSettings.getHdeKnownExtensionInTabs(); + isModified |= myHideKnownExtensions.isSelected() != uiSettings.getHideKnownExtensionInTabs(); isModified |= myShowDirectoryInTabCheckBox.isSelected() != uiSettings.getShowDirectoryForNonUniqueFilenames(); isModified |= myScrollTabLayoutInEditorCheckBox.isSelected() != uiSettings.getScrollTabLayoutInEditor(); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java index 8b5c7cf406d6..8193a99ed60f 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java @@ -42,6 +42,7 @@ import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.actionSystem.EditorActionManager; import com.intellij.openapi.editor.ex.DocumentEx; import com.intellij.openapi.editor.ex.util.EditorUtil; +import com.intellij.openapi.editor.impl.DocumentImpl; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.IndexNotReadyException; @@ -733,11 +734,20 @@ public class CodeCompletionHandlerBase { if (mayCacheCopy) { final Document document = copy.getViewProvider().getDocument(); assert document != null; + syncAcceptSlashR(file.getViewProvider().getDocument(), document); file.putUserData(FILE_COPY_KEY, new SoftReference<>(Pair.create(copy, document))); } return copy; } + private static void syncAcceptSlashR(Document originalDocument, Document documentCopy) { + if (!(originalDocument instanceof DocumentImpl) || !(documentCopy instanceof DocumentImpl)) { + return; + } + + ((DocumentImpl) documentCopy).setAcceptSlashR(((DocumentImpl) originalDocument).acceptsSlashR()); + } + private static boolean isAutocompleteOnInvocation(final CompletionType type) { final CodeInsightSettings settings = CodeInsightSettings.getInstance(); if (type == CompletionType.SMART) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionAssertions.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionAssertions.java index 2cebbb9f1c1f..d9edfe4a8300 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionAssertions.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionAssertions.java @@ -22,15 +22,14 @@ import com.intellij.injected.editor.DocumentWindow; import com.intellij.injected.editor.EditorWindow; import com.intellij.lang.FileASTNode; import com.intellij.lang.injection.InjectedLanguageManager; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Attachment; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.RuntimeExceptionWithAttachments; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.ex.RangeMarkerEx; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.FileViewProvider; import com.intellij.psi.PsiDocumentManager; @@ -45,7 +44,6 @@ import java.util.List; * @author peter */ class CompletionAssertions { - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.CompletionAssertions"); static void assertCommitSuccessful(Editor editor, PsiFile psiFile) { Document document = editor.getDocument(); @@ -80,9 +78,7 @@ class CompletionAssertions { if (node != null) { message += "\nnode.length=" + node.getTextLength(); String nodeText = node.getText(); - if (nodeText != null) { - message += "\nnode.text.length=" + nodeText.length(); - } + message += "\nnode.text.length=" + nodeText.length(); } VirtualFile virtualFile = viewProvider.getVirtualFile(); message += "\nvirtualFile=" + virtualFile; @@ -90,9 +86,9 @@ class CompletionAssertions { message += "\n" + DebugUtil.currentStackTrace(); throw new LogEventException("Commit unsuccessful", message, - new Attachment(virtualFile.getPath() + "_file.txt", fileText), - createAstAttachment(psiFile, psiFile), - new Attachment("docText.txt", document.getText())); + new Attachment(virtualFile.getPath() + "_file.txt", StringUtil.notNullize(fileText)), + createAstAttachment(psiFile, psiFile), + new Attachment("docText.txt", document.getText())); } static void checkEditorValid(Editor editor) { @@ -212,10 +208,6 @@ class CompletionAssertions { spy = new RangeMarkerSpy(tailWatcher) { @Override protected void invalidated(DocumentEvent e) { - if (ApplicationManager.getApplication().isUnitTestMode()) { - LOG.error("Tail offset invalidated, say thanks to the "+ e); - } - if (invalidateTrace == null) { invalidateTrace = new Throwable(); killer = e; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java index 16b762d86261..9ce3ce563a94 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java @@ -811,7 +811,7 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement void startCompletion(final CompletionInitializationContext initContext) { boolean sync = ApplicationManager.getApplication().isWriteAccessAllowed(); myStrategy = sync ? new SyncCompletion() : new AsyncCompletion(); - myStrategy.startThread(ProgressWrapper.wrap(this), this::scheduleAdvertising); + myStrategy.startThread(ProgressWrapper.wrap(this), ()-> AsyncCompletion.tryReadOrCancel(this, this::scheduleAdvertising)); final WeighingDelegate weigher = myStrategy.delegateWeighing(this); class CalculateItems implements Runnable { @@ -829,7 +829,7 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement } } } - myStrategy.startThread(this, new CalculateItems()); + myStrategy.startThread(this, ()->AsyncCompletion.tryReadOrCancel(this, new CalculateItems())); } private void calculateItems(CompletionInitializationContext initContext, WeighingDelegate weigher) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionThreading.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionThreading.java index 41c5f40fafac..06f687d00c33 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionThreading.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionThreading.java @@ -16,6 +16,7 @@ package com.intellij.codeInsight.completion; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ex.ApplicationManagerEx; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; @@ -102,11 +103,9 @@ class AsyncCompletion extends CompletionThreadingBase { startSemaphore.down(); Future future = ApplicationManager.getApplication().executeOnPooledThread(() -> ProgressManager.getInstance().runProcess(() -> { try { - ApplicationManager.getApplication().runReadAction(() -> { - startSemaphore.up(); - ProgressManager.checkCanceled(); - runnable.run(); - }); + startSemaphore.up(); + ProgressManager.checkCanceled(); + runnable.run(); } catch (ProcessCanceledException ignored) { } }, progressIndicator)); @@ -124,7 +123,7 @@ class AsyncCompletion extends CompletionThreadingBase { while (true) { Computable next = myQueue.poll(30, TimeUnit.MILLISECONDS); if (next != null && !next.compute()) { - indicator.addDelayedMiddleMatches(); + tryReadOrCancel(indicator, () -> indicator.addDelayedMiddleMatches()); return; } indicator.checkCanceled(); @@ -155,7 +154,7 @@ class AsyncCompletion extends CompletionThreadingBase { } else { myQueue.offer(() -> { - indicator.addItem(result); + tryReadOrCancel(indicator, () -> indicator.addItem(result)); return true; }); } @@ -168,13 +167,21 @@ class AsyncCompletion extends CompletionThreadingBase { myBatchList.clear(); myQueue.offer(() -> { + tryReadOrCancel(indicator, () -> indicator.withSingleUpdate(() -> { for (CompletionResult result : batchListCopy) { indicator.addItem(result); } - }); + })); return true; }); } + + static void tryReadOrCancel(ProgressIndicator indicator, Runnable runnable) { + if (!ApplicationManagerEx.getApplicationEx().tryRunReadAction(runnable)) { + indicator.cancel(); + indicator.checkCanceled(); + } + } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionUtil.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionUtil.java index 9aec22e827cc..853805a8db14 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionUtil.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionUtil.java @@ -116,7 +116,20 @@ public class CompletionUtil { if(insertedElement == null) return ""; final String text = insertedElement.getText(); - final int offsetInElement = offset - insertedElement.getTextRange().getStartOffset(); + int startOffset = insertedElement.getTextRange().getStartOffset(); + return findInText(offset, startOffset, idPart, idStart, text); + } + + + public static String findIdentifierPrefix(@NotNull Document document, int offset, ElementPattern idPart, + ElementPattern idStart) { + final String text = document.getText(); + return findInText(offset, 0, idPart, idStart, text); + } + + @NotNull + private static String findInText(int offset, int startOffset, ElementPattern idPart, ElementPattern idStart, String text) { + final int offsetInElement = offset - startOffset; int start = offsetInElement - 1; while (start >=0 ) { if (!idPart.accepts(text.charAt(start))) break; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java index edf55007b5bd..bf629bec0ba4 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java @@ -288,17 +288,6 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements Pers return result; } - @NotNull - @TestOnly - public List runPasses(@NotNull PsiFile file, - @NotNull Document document, - @NotNull TextEditor textEditor, - @NotNull int[] toIgnore, - boolean canChangeDocument, - @Nullable Runnable callbackWhileWaiting) throws ProcessCanceledException { - return runPasses(file, document, Collections.singletonList(textEditor), toIgnore, canChangeDocument, callbackWhileWaiting); - } - private volatile boolean mustWaitForSmartMode = true; @TestOnly public void mustWaitForSmartMode(final boolean mustWait, @NotNull Disposable parent) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ParameterHintsPresentationManager.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ParameterHintsPresentationManager.java index e54126c33f3f..30da8c73fa45 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ParameterHintsPresentationManager.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ParameterHintsPresentationManager.java @@ -90,6 +90,22 @@ public class ParameterHintsPresentationManager implements Disposable { updateRenderer(editor, hint, newText); } + public void setHighlighted(@NotNull Inlay hint, boolean highlighted) { + if (!isParameterHint(hint)) throw new IllegalArgumentException("Not a parameter hint"); + MyRenderer renderer = (MyRenderer)hint.getRenderer(); + boolean oldValue = renderer.highlighted; + if (highlighted != oldValue) { + renderer.highlighted = highlighted; + hint.repaint(); + } + } + + public boolean isHighlighted(@NotNull Inlay hint) { + if (!isParameterHint(hint)) throw new IllegalArgumentException("Not a parameter hint"); + MyRenderer renderer = (MyRenderer)hint.getRenderer(); + return renderer.highlighted; + } + private void updateRenderer(@NotNull Editor editor, @NotNull Inlay hint, @Nullable String newText) { MyRenderer renderer = (MyRenderer)hint.getRenderer(); renderer.update(editor, newText, true); @@ -166,6 +182,7 @@ public class ParameterHintsPresentationManager implements Disposable { private int startWidth; private int steps; private int step; + private boolean highlighted; private MyRenderer(Editor editor, String text, boolean animated) { updateState(editor, text, animated); @@ -215,7 +232,7 @@ public class ParameterHintsPresentationManager implements Disposable { TextAttributes attributes = editor.getColorsScheme().getAttributes(DefaultLanguageHighlighterColors.INLINE_PARAMETER_HINT); if (attributes != null) { MyFontMetrics fontMetrics = getFontMetrics(editor); - Color backgroundColor = attributes.getBackgroundColor(); + Color backgroundColor = getBackgroundColor(attributes); if (backgroundColor != null) { GraphicsConfig config = GraphicsUtil.setupAAPainting(g); GraphicsUtil.paintWithAlpha(g, BACKGROUND_ALPHA); @@ -224,7 +241,7 @@ public class ParameterHintsPresentationManager implements Disposable { g.fillRoundRect(r.x + 2, r.y + gap, r.width - 4, r.height - gap * 2, 8, 8); config.restore(); } - Color foregroundColor = attributes.getForegroundColor(); + Color foregroundColor = getForegroundColor(attributes); if (foregroundColor != null) { g.setColor(foregroundColor); g.setFont(getFont(editor)); @@ -238,6 +255,14 @@ public class ParameterHintsPresentationManager implements Disposable { } } } + + private Color getForegroundColor(TextAttributes attributes) { + return highlighted ? attributes.getBackgroundColor() : attributes.getForegroundColor(); + } + + private Color getBackgroundColor(TextAttributes attributes) { + return highlighted ? attributes.getForegroundColor() : attributes.getBackgroundColor(); + } } private class AnimationStep implements Runnable { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/CodeFoldingManagerImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/CodeFoldingManagerImpl.java index bcd6b86a64c6..f4ccb1fbbad7 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/CodeFoldingManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/CodeFoldingManagerImpl.java @@ -172,10 +172,6 @@ public class CodeFoldingManagerImpl extends CodeFoldingManager implements Projec PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(document); if (file == null || !file.getViewProvider().isPhysical() || !file.isValid()) return; - Editor[] otherEditors = EditorFactory.getInstance().getEditors(document, myProject); - if (otherEditors.length == 0 && !editor.isDisposed()) { - getDocumentFoldingInfo(document).loadFromEditor(editor); - } EditorFoldingInfo.get(editor).dispose(); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/highlighting/TemplateLanguageErrorFilter.java b/platform/lang-impl/src/com/intellij/codeInsight/highlighting/TemplateLanguageErrorFilter.java index 9de7449a68ce..057d1b7510ca 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/highlighting/TemplateLanguageErrorFilter.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/highlighting/TemplateLanguageErrorFilter.java @@ -20,6 +20,7 @@ import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.openapi.util.Key; import com.intellij.psi.*; import com.intellij.psi.tree.TokenSet; +import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NotNull; import java.util.*; @@ -42,7 +43,7 @@ public abstract class TemplateLanguageErrorFilter extends HighlightErrorFilter { @NotNull final TokenSet templateExpressionStartTokens, @NotNull final Class templateFileViewProviderClass) { - this(templateExpressionStartTokens, templateFileViewProviderClass, new String[0]); + this(templateExpressionStartTokens, templateFileViewProviderClass, ArrayUtil.EMPTY_STRING_ARRAY); } protected TemplateLanguageErrorFilter(@NotNull final TokenSet templateExpressionStartTokens, diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hint/ImplementationViewComponent.java b/platform/lang-impl/src/com/intellij/codeInsight/hint/ImplementationViewComponent.java index ce687bad6483..cfee91cb4d3f 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hint/ImplementationViewComponent.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/hint/ImplementationViewComponent.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -165,8 +165,7 @@ public class ImplementationViewComponent extends JPanel { myCountLabel = new JLabel(); final JPanel header = new JPanel(new BorderLayout(2, 0)); - header.setBorder(BorderFactory.createCompoundBorder(IdeBorderFactory.createBorder(SideBorder.BOTTOM), - IdeBorderFactory.createEmptyBorder(0, 0, 0, 5))); + header.setBorder(BorderFactory.createCompoundBorder(IdeBorderFactory.createBorder(SideBorder.BOTTOM), JBUI.Borders.emptyRight(5))); final JPanel toolbarPanel = new JPanel(new GridBagLayout()); final GridBagConstraints gc = new GridBagConstraints(GridBagConstraints.RELATIVE, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0,2,0,0), 0,0); toolbarPanel.add(myToolbar.getComponent(), gc); @@ -220,7 +219,7 @@ public class ImplementationViewComponent extends JPanel { myLabel.setIcon(getIconForFile(psiFile)); myLabel.setForeground(FileStatusManager.getInstance(project).getStatus(file).getColor()); myLabel.setText(file.getPresentableName()); - myLabel.setBorder(new CompoundBorder(IdeBorderFactory.createRoundedBorder(), IdeBorderFactory.createEmptyBorder(0, 0, 0, 5))); + myLabel.setBorder(new CompoundBorder(IdeBorderFactory.createRoundedBorder(), JBUI.Borders.emptyRight(5))); } toolbarPanel.add(myLabel, gc); } @@ -303,7 +302,7 @@ public class ImplementationViewComponent extends JPanel { myLabel.setIcon(getIconForFile(psiFile)); myLabel.setForeground(FileStatusManager.getInstance(project).getStatus(file).getColor()); myLabel.setText(file.getPresentableName()); - myLabel.setBorder(new CompoundBorder(IdeBorderFactory.createRoundedBorder(), IdeBorderFactory.createEmptyBorder(0, 0, 0, 5))); + myLabel.setBorder(new CompoundBorder(IdeBorderFactory.createRoundedBorder(), JBUI.Borders.emptyRight(5))); myLabel.setVisible(true); } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hint/ParameterInfoController.java b/platform/lang-impl/src/com/intellij/codeInsight/hint/ParameterInfoController.java index 4d8b8d03018c..2989dba1e2cb 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hint/ParameterInfoController.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/hint/ParameterInfoController.java @@ -155,8 +155,7 @@ public class ParameterInfoController implements Disposable { myEditorCaretListener = new CaretListener(){ @Override public void caretPositionChanged(CaretEvent e) { - myAlarm.cancelAllRequests(); - addAlarmRequest(); + rescheduleUpdate(); } }; myEditor.getCaretModel().addCaretListener(myEditorCaretListener); @@ -164,16 +163,14 @@ public class ParameterInfoController implements Disposable { myEditor.getDocument().addDocumentListener(new DocumentListener() { @Override public void documentChanged(DocumentEvent e) { - myAlarm.cancelAllRequests(); - addAlarmRequest(); + rescheduleUpdate(); } }, this); MessageBusConnection connection = project.getMessageBus().connect(this); connection.subscribe(ExternalParameterInfoChangesProvider.TOPIC, (e, offset) -> { if (e != myEditor || myLbraceMarker.getStartOffset() != offset) return; - myAlarm.cancelAllRequests(); - addAlarmRequest(); + rescheduleUpdate(); }); PropertyChangeListener lookupListener = new PropertyChangeListener() { @@ -204,6 +201,7 @@ public class ParameterInfoController implements Disposable { if (myDisposed) return; myDisposed = true; myHint.hide(); + myHandler.dispose(); List allControllers = getAllControllers(myEditor); allControllers.remove(this); myEditor.getCaretModel().removeCaretListener(myEditorCaretListener); @@ -257,7 +255,8 @@ public class ParameterInfoController implements Disposable { } } - private void addAlarmRequest(){ + private void rescheduleUpdate(){ + myAlarm.cancelAllRequests(); Runnable request = () -> { if (!myDisposed && !myProject.isDisposed()) { PsiDocumentManager.getInstance(myProject).performLaterWhenAllCommitted(() -> { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/ParameterHintsPass.java b/platform/lang-impl/src/com/intellij/codeInsight/hints/ParameterHintsPass.java index 84207de301b0..9d36aba79312 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hints/ParameterHintsPass.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/ParameterHintsPass.java @@ -35,6 +35,7 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; +import static com.intellij.codeInsight.hints.ParameterHintsPassFactory.forceHintsUpdateOnNextPass; import static com.intellij.codeInsight.hints.ParameterHintsPassFactory.putCurrentPsiModificationStamp; public class ParameterHintsPass extends EditorBoundHighlightingPass { @@ -115,7 +116,13 @@ public class ParameterHintsPass extends EditorBoundHighlightingPass { ParameterHintsUpdater updater = new ParameterHintsUpdater(myEditor, hints, myHints, myShowOnlyIfExistedBeforeHints, myForceImmediateUpdate); updater.update(); keeper.restoreOriginalLocation(false); - putCurrentPsiModificationStamp(myEditor, myFile); + + if (ParameterHintsUpdater.hintRemovalDelayed(myEditor)) { + forceHintsUpdateOnNextPass(myEditor); + } + else { + putCurrentPsiModificationStamp(myEditor, myFile); + } } @NotNull diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/ParameterHintsUpdater.java b/platform/lang-impl/src/com/intellij/codeInsight/hints/ParameterHintsUpdater.java index b3d1815dc8ba..6d84e3ae39d8 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hints/ParameterHintsUpdater.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/ParameterHintsUpdater.java @@ -32,7 +32,7 @@ import java.util.*; import java.util.stream.Stream; public class ParameterHintsUpdater { - + private static final Key HINT_REMOVAL_DELAYED = Key.create("hint.removal.delayed"); private static final Key REPEATED_PASS = Key.create("RepeatedParameterHintsPass"); private final ParameterHintsPresentationManager myHintsManager = ParameterHintsPresentationManager.getInstance(); @@ -43,10 +43,11 @@ public class ParameterHintsUpdater { private final boolean myForceImmediateUpdate; private final Editor myEditor; - private final List myUpdateList; + private final List myEditorInlays; + private List myUpdateList; public ParameterHintsUpdater(@NotNull Editor editor, - @NotNull List inlays, + @NotNull List editorInlays, @NotNull TIntObjectHashMap> newHints, @NotNull TIntObjectHashMap hintsToPreserve, boolean forceImmediateUpdate) { @@ -59,11 +60,13 @@ public class ParameterHintsUpdater { List allCarets = myEditor.getCaretModel().getAllCarets(); allCarets.forEach((caret) -> myCaretMap.put(caret.getOffset(), caret)); - myUpdateList = getInlayUpdates(inlays); + myEditorInlays = editorInlays; } private List getInlayUpdates(List editorHints) { + myEditor.putUserData(HINT_REMOVAL_DELAYED, Boolean.FALSE); + List updates = ContainerUtil.newArrayList(); ParameterHintsPresentationManager presentationManager = ParameterHintsPresentationManager.getInstance(); @@ -72,7 +75,11 @@ public class ParameterHintsUpdater { String presentationText = presentationManager.getHintText(editorHint); ParameterHintsPass.HintData newHint = findAndRemoveMatchingHint(offset, presentationText, myNewHints); String newText = newHint == null ? null : newHint.presentationText; - if (!myForceImmediateUpdate && delayRemoval(editorHint) || isPreserveHint(editorHint, newText)) return; + if (!myForceImmediateUpdate && delayRemoval(editorHint)) { + myEditor.putUserData(HINT_REMOVAL_DELAYED, Boolean.TRUE); + return; + } + if (isPreserveHint(editorHint, newText)) return; updates.add(new InlayUpdateInfo(offset, editorHint, newText, newHint != null && newHint.showAfterCaret)); }); @@ -86,6 +93,10 @@ public class ParameterHintsUpdater { return updates; } + public static boolean hintRemovalDelayed(@NotNull Editor editor) { + return editor.getUserData(HINT_REMOVAL_DELAYED) == Boolean.TRUE; + } + @Nullable private static ParameterHintsPass.HintData findAndRemoveMatchingHint(int offset, String presentationText, TIntObjectHashMap> data) { @@ -120,6 +131,7 @@ public class ParameterHintsUpdater { public void update() { + myUpdateList = getInlayUpdates(myEditorInlays); boolean firstTime = myEditor.getUserData(REPEATED_PASS) == null; boolean isUpdateInBulkMode = myUpdateList.size() > 1000; DocumentUtil.executeInBulk(myEditor.getDocument(), isUpdateInBulkMode, () -> performHintsUpdate(firstTime, isUpdateInBulkMode)); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/PopupActions.kt b/platform/lang-impl/src/com/intellij/codeInsight/hints/PopupActions.kt index 0b991940a4a9..7cb1774d4b63 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hints/PopupActions.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/PopupActions.kt @@ -29,7 +29,6 @@ import com.intellij.lang.Language import com.intellij.notification.Notification import com.intellij.notification.NotificationListener import com.intellij.notification.NotificationType -import com.intellij.openapi.actionSystem.ActionPlaces import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys @@ -275,22 +274,6 @@ class ToggleInlineHintsAction : AnAction() { val isHintsShownNow = EditorSettingsExternalizable.getInstance().isShowParameterNameHints e.presentation.text = if (isHintsShownNow) disableText else enableText e.presentation.isEnabledAndVisible = true - - if (isInMainEditorPopup(e)) { - val file = CommonDataKeys.PSI_FILE.getData(e.dataContext) ?: return - val editor = CommonDataKeys.EDITOR.getData(e.dataContext) ?: return - val caretOffset = editor.caretModel.offset - e.presentation.isEnabledAndVisible = !isHintsShownNow && isPossibleHintNearOffset(file, caretOffset) - } - } - - private fun isInMainEditorPopup(e: AnActionEvent): Boolean { - if (e.place != ActionPlaces.EDITOR_POPUP) return false - - val editor = CommonDataKeys.EDITOR.getData(e.dataContext) ?: return false - val offset = editor.caretModel.offset - - return !editor.inlayModel.hasInlineElementAt(offset) } override fun actionPerformed(e: AnActionEvent) { @@ -349,21 +332,6 @@ fun PsiElement.isOwnsInlayInEditor(editor: Editor): Boolean { return !editor.inlayModel.getInlineElementsInRange(start, textRange.endOffset).isEmpty() } -fun isPossibleHintNearOffset(file: PsiFile, offset: Int): Boolean { - val hintProvider = InlayParameterHintsExtension.forLanguage(file.language) ?: return false - - var element = file.findElementAt(offset) - for (i in 0..3) { - if (element == null) return false - - val hints = hintProvider.getParameterHints(element) - if (hints.isNotEmpty()) return true - element = element.parent - } - - return false -} - fun MethodInfo.toPattern() = this.fullyQualifiedName + '(' + this.paramNames.joinToString(",") + ')' diff --git a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/IntentionHintComponent.java b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/IntentionHintComponent.java index 286c568a114c..a7961589d15a 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/IntentionHintComponent.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/IntentionHintComponent.java @@ -307,8 +307,10 @@ public class IntentionHintComponent implements Disposable, ScrollAwareHint { int yShift = -(NORMAL_BORDER_SIZE + AllIcons.Actions.RealIntentionBulb.getIconHeight()); if (canPlaceBulbOnTheSameLine(editor)) { - final int borderHeight = NORMAL_BORDER_SIZE; - yShift = -(borderHeight + (AllIcons.Actions.RealIntentionBulb.getIconHeight() - editor.getLineHeight()) /2 + 3); + yShift = -(NORMAL_BORDER_SIZE + (AllIcons.Actions.RealIntentionBulb.getIconHeight() - editor.getLineHeight()) / 2 + 3); + } + else if (position.y < editor.getScrollingModel().getVisibleArea().y + editor.getLineHeight()) { + yShift = editor.getLineHeight() - NORMAL_BORDER_SIZE; } final int xShift = AllIcons.Actions.RealIntentionBulb.getIconWidth(); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupUi.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupUi.java index ab1890901145..24ea23b67921 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupUi.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupUi.java @@ -276,10 +276,10 @@ class LookupUi { } SwingUtilities.convertPointToScreen(location, editor.getContentComponent()); - final Rectangle screenRectangle = ScreenUtil.getScreenRectangle(location); + final Rectangle screenRectangle = ScreenUtil.getScreenRectangle(editor.getContentComponent()); if (!isPositionedAboveCaret()) { - int shiftLow = screenRectangle.height - (location.y + dim.height); + int shiftLow = screenRectangle.y + screenRectangle.height - (location.y + dim.height); myPositionedAbove = shiftLow < 0 && shiftLow < location.y - dim.height && location.y >= dim.height; } if (isPositionedAboveCaret()) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/actions/ChooseItemAction.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/actions/ChooseItemAction.java index 032d518ae0b9..b394469cc6f0 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/actions/ChooseItemAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/actions/ChooseItemAction.java @@ -41,11 +41,11 @@ public abstract class ChooseItemAction extends EditorAction { super(handler); } - protected static class Handler extends EditorActionHandler { + public static class Handler extends EditorActionHandler { final boolean focusedOnly; final char finishingChar; - protected Handler(boolean focusedOnly, char finishingChar) { + public Handler(boolean focusedOnly, char finishingChar) { this.focusedOnly = focusedOnly; this.finishingChar = finishingChar; } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ex/GlobalInspectionContextImpl.java b/platform/lang-impl/src/com/intellij/codeInspection/ex/GlobalInspectionContextImpl.java index a715a7c0dcaa..ad31057240bd 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ex/GlobalInspectionContextImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ex/GlobalInspectionContextImpl.java @@ -451,10 +451,17 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp while (true) { Disposable disposable = Disposer.newDisposable(); ProgressIndicator wrapper = new SensitiveProgressWrapper(progressIndicator); - wrapper.start(); - ProgressIndicatorUtils.forceWriteActionPriority(wrapper, disposable); try { + // avoid "attach listener"/"write action" race + ReadAction.run(() -> { + wrapper.start(); + ProgressIndicatorUtils.forceWriteActionPriority(wrapper, disposable); + // there is a chance we are racing with write action, in which case just registered listener might not be called, retry. + if (ApplicationManagerEx.getApplicationEx().isWriteActionPending()) { + throw new ProcessCanceledException(); + } + }); // use wrapper here to cancel early when write action start but do not affect the original indicator ((JobLauncherImpl)JobLauncher.getInstance()).processQueue(filesToInspect, filesFailedToInspect, wrapper, TOMBSTONE, processor); break; diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ex/QuickFixAction.java b/platform/lang-impl/src/com/intellij/codeInspection/ex/QuickFixAction.java index a658223cc08e..b854d5727b22 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ex/QuickFixAction.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ex/QuickFixAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -48,8 +48,8 @@ import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.ui.ClickListener; -import com.intellij.ui.IdeBorderFactory; import com.intellij.util.SequentialModalProgressTask; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; @@ -328,7 +328,7 @@ public class QuickFixAction extends AnAction implements CustomComponentAction { }.installOn(button); JPanel panel = new JPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS)); - panel.setBorder(IdeBorderFactory.createEmptyBorder(7, 0, 8, 0)); + panel.setBorder(JBUI.Borders.empty(7, 0, 8, 0)); panel.add(button); return panel; } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionNodeInfo.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionNodeInfo.java index 2c90e5dfbaaf..e4a29f30b86a 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionNodeInfo.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionNodeInfo.java @@ -25,7 +25,10 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.profile.codeInspection.InspectionProjectProfileManager; import com.intellij.profile.codeInspection.ui.SingleInspectionProfilePanel; import com.intellij.profile.codeInspection.ui.inspectionsTree.InspectionsConfigTreeTable; -import com.intellij.ui.*; +import com.intellij.ui.BrowserHyperlinkListener; +import com.intellij.ui.ClickListener; +import com.intellij.ui.JBColor; +import com.intellij.ui.ScrollPaneFactory; import com.intellij.ui.components.JBLabel; import com.intellij.ui.components.JBLabelDecorator; import com.intellij.ui.components.panels.StatelessCardLayout; @@ -47,7 +50,7 @@ public class InspectionNodeInfo extends JPanel { public InspectionNodeInfo(@NotNull final InspectionTree tree, @NotNull final Project project) { setLayout(new GridBagLayout()); - setBorder(IdeBorderFactory.createEmptyBorder(11, 0, 0, 0)); + setBorder(JBUI.Borders.emptyTop(11)); final InspectionToolWrapper toolWrapper = tree.getSelectedToolWrapper(false); LOG.assertTrue(toolWrapper != null); InspectionProfileImpl currentProfile = InspectionProjectProfileManager.getInstance(project).getCurrentProfile(); diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java index a51a04efceaf..75928c01ee41 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -69,6 +69,7 @@ import com.intellij.util.concurrency.AppExecutorUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.FactoryMap; import com.intellij.util.containers.HashSet; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.tree.TreeUtil; import gnu.trove.THashSet; @@ -615,12 +616,12 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro settings.setAdditionalLinesCount(0); settings.setLeadingWhitespaceShown(true); myPreviewEditor.getColorsScheme().setColor(EditorColors.GUTTER_BACKGROUND, myPreviewEditor.getColorsScheme().getDefaultBackground()); - myPreviewEditor.getScrollPane().setBorder(IdeBorderFactory.createEmptyBorder()); + myPreviewEditor.getScrollPane().setBorder(JBUI.Borders.empty()); } if (problemCount == 0) { myPreviewEditor.getScrollingModel().scrollTo(myPreviewEditor.offsetToLogicalPosition(selectedElement.getTextOffset()), ScrollType.CENTER_UP); } - myPreviewEditor.getComponent().setBorder(IdeBorderFactory.createEmptyBorder()); + myPreviewEditor.getComponent().setBorder(JBUI.Borders.empty()); return Pair.create(myPreviewEditor.getComponent(), myPreviewEditor); } if (selectedEntity == null) { diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsViewUtil.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsViewUtil.java index 4f191a325b75..65a4c88dd841 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsViewUtil.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsViewUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -28,8 +28,8 @@ import com.intellij.pom.Navigatable; import com.intellij.psi.NavigatablePsiElement; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.components.JBLabel; +import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -94,7 +94,7 @@ public class InspectionResultsViewUtil { static JLabel createLabelForText(String text) { final JLabel multipleSelectionLabel = new JBLabel(text); multipleSelectionLabel.setVerticalAlignment(SwingConstants.TOP); - multipleSelectionLabel.setBorder(IdeBorderFactory.createEmptyBorder(16, 12, 0, 0)); + multipleSelectionLabel.setBorder(JBUI.Borders.empty(16, 12, 0, 0)); return multipleSelectionLabel; } } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionViewNavigationPanel.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionViewNavigationPanel.java index 3f3bbdf189ad..35ad08107051 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionViewNavigationPanel.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionViewNavigationPanel.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -15,7 +15,6 @@ */ package com.intellij.codeInspection.ui; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.components.JBLabel; import com.intellij.ui.components.labels.LinkLabel; import com.intellij.util.ui.JBUI; @@ -40,7 +39,7 @@ public class InspectionViewNavigationPanel extends JPanel implements InspectionT myNode = node; myTree = tree; setLayout(new BorderLayout()); - setBorder(IdeBorderFactory.createEmptyBorder(18, 12, 0, 0)); + setBorder(JBUI.Borders.empty(18, 12, 0, 0)); final String titleLabelText = getTitleText(true); add(new JBLabel(titleLabelText), BorderLayout.NORTH); myLinks = new JPanel(); @@ -78,7 +77,7 @@ public class InspectionViewNavigationPanel extends JPanel implements InspectionT TreeUtil.selectInTree((DefaultMutableTreeNode)child, true, myTree); } }; - link.setBorder(IdeBorderFactory.createEmptyBorder(1, 17, 3, 1)); + link.setBorder(JBUI.Borders.empty(1, 17, 3, 1)); myLinks.add(link); } myShownChildrenCount = currentChildrenCount; diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/QuickFixPreviewPanelFactory.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/QuickFixPreviewPanelFactory.java index f4fbe9c9dbf8..939e55ce938b 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/QuickFixPreviewPanelFactory.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/QuickFixPreviewPanelFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -27,9 +27,9 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.SimpleColoredComponent; import com.intellij.util.ui.AsyncProcessIcon; +import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -105,7 +105,10 @@ public class QuickFixPreviewPanelFactory { } if (hasComponents) { - setBorder(IdeBorderFactory.createEmptyBorder(hasFixes ? 2 : 9, (hasFixes || problemCount > 1) ? 8 : 5, hasFixes ? 0 : 8, 0)); + int top = hasFixes ? 2 : 9; + int left = (hasFixes || problemCount > 1) ? 8 : 5; + int bottom = hasFixes ? 0 : 8; + setBorder(JBUI.Borders.empty(top, left, bottom, 0)); } return !hasComponents; } @@ -175,7 +178,7 @@ public class QuickFixPreviewPanelFactory { private LoadingInProgressPreview(InspectionResultsView view) { myView = view; setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); - setBorder(IdeBorderFactory.createEmptyBorder(16, 9, 13, 0)); + setBorder(JBUI.Borders.empty(16, 9, 13, 0)); AsyncProcessIcon waitingIcon = new AsyncProcessIcon("Inspection preview panel updating..."); Disposer.register(this, waitingIcon); myWaitingLabel = getLabel(myView.getTree().getSelectedProblemCount(false)); @@ -210,7 +213,7 @@ public class QuickFixPreviewPanelFactory { private static SimpleColoredComponent getLabel(int problemsCount) { SimpleColoredComponent label = new SimpleColoredComponent(); appendTextToLabel(label, problemsCount); - label.setBorder(IdeBorderFactory.createEmptyBorder(0, 0, 0, 2)); + label.setBorder(JBUI.Borders.empty(0, 0, 0, 2)); return label; } diff --git a/platform/lang-impl/src/com/intellij/execution/ExecutionTargetManagerImpl.java b/platform/lang-impl/src/com/intellij/execution/ExecutionTargetManagerImpl.java index 8206141f0741..29cc62dcda7f 100644 --- a/platform/lang-impl/src/com/intellij/execution/ExecutionTargetManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/execution/ExecutionTargetManagerImpl.java @@ -125,7 +125,7 @@ public class ExecutionTargetManagerImpl extends ExecutionTargetManager implement } } toNotify = - doSetActiveTarget(index >= 0 ? suitable.get(index) : ContainerUtil.getFirstItem(suitable, DefaultExecutionTarget.INSTANCE)); + doSetActiveTarget(index >= 0 ? suitable.get(index) : getDefaultTarget(suitable)); } if (toNotify != null) { @@ -133,6 +133,11 @@ public class ExecutionTargetManagerImpl extends ExecutionTargetManager implement } } + private static ExecutionTarget getDefaultTarget(List suitable){ + ExecutionTarget result = ContainerUtil.find(suitable, ExecutionTarget::isReady); + return result != null ? result : DefaultExecutionTarget.INSTANCE; + } + @Nullable private ExecutionTarget doSetActiveTarget(@NotNull ExecutionTarget newTarget) { mySavedActiveTargetId = null; diff --git a/platform/lang-impl/src/com/intellij/execution/actions/ChooseRunConfigurationPopup.java b/platform/lang-impl/src/com/intellij/execution/actions/ChooseRunConfigurationPopup.java index 2f23700fe81d..f4fe4425abc3 100644 --- a/platform/lang-impl/src/com/intellij/execution/actions/ChooseRunConfigurationPopup.java +++ b/platform/lang-impl/src/com/intellij/execution/actions/ChooseRunConfigurationPopup.java @@ -353,7 +353,7 @@ public class ChooseRunConfigurationPopup implements ExecutorProvider { @Override public String getText() { - return RunManager.getShortenName(getValue().getName()); + return Executor.shortenNameIfNeed(getValue().getName()); } @Override @@ -512,7 +512,7 @@ public class ChooseRunConfigurationPopup implements ExecutorProvider { } public String getName() { - return RunManager.getShortenName(mySettings.getName()); + return Executor.shortenNameIfNeed(mySettings.getName()); } public Icon getIcon() { @@ -1071,7 +1071,7 @@ public class ChooseRunConfigurationPopup implements ExecutorProvider { @Override public String getText() { - return RunManager.getShortenName(configuration.getName()); + return Executor.shortenNameIfNeed(configuration.getName()); } @Override diff --git a/platform/lang-impl/src/com/intellij/execution/actions/RunConfigurationsComboBoxAction.java b/platform/lang-impl/src/com/intellij/execution/actions/RunConfigurationsComboBoxAction.java index 3a4beccb2e54..71b5ac88ec1a 100644 --- a/platform/lang-impl/src/com/intellij/execution/actions/RunConfigurationsComboBoxAction.java +++ b/platform/lang-impl/src/com/intellij/execution/actions/RunConfigurationsComboBoxAction.java @@ -30,7 +30,6 @@ import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.registry.Registry; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.SizedIcon; import com.intellij.ui.components.panels.NonOpaquePanel; import com.intellij.util.IconUtil; @@ -81,7 +80,7 @@ public class RunConfigurationsComboBoxAction extends ComboBoxAction implements D @Nullable Project project, @NotNull Presentation presentation) { if (project != null && target != null && settings != null) { - String name = RunManager.getShortenName(settings.getName()); + String name = Executor.shortenNameIfNeed(settings.getName()); if (target != DefaultExecutionTarget.INSTANCE) { name += " | " + target.getDisplayName(); } else { @@ -127,7 +126,7 @@ public class RunConfigurationsComboBoxAction extends ComboBoxAction implements D ComboBoxButton button = createComboBoxButton(presentation); button.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2)); NonOpaquePanel panel = new NonOpaquePanel(new BorderLayout()); - panel.setBorder(IdeBorderFactory.createEmptyBorder(0, 0, 0, 2)); + panel.setBorder(JBUI.Borders.empty(0, 0, 0, 2)); panel.add(button); return panel; } @@ -207,7 +206,7 @@ public class RunConfigurationsComboBoxAction extends ComboBoxAction implements D disable(presentation); } else { - presentation.setText(ExecutionBundle.message("save.temporary.run.configuration.action.name", RunManager.getShortenName(settings.getName()))); + presentation.setText(ExecutionBundle.message("save.temporary.run.configuration.action.name", Executor.shortenNameIfNeed(settings.getName()))); presentation.setDescription(presentation.getText()); presentation.setVisible(true); presentation.setEnabled(true); @@ -268,7 +267,7 @@ public class RunConfigurationsComboBoxAction extends ComboBoxAction implements D public SelectConfigAction(final RunnerAndConfigurationSettings configuration, final Project project) { myConfiguration = configuration; myProject = project; - String name = RunManager.getShortenName(configuration.getName()); + String name = Executor.shortenNameIfNeed(configuration.getName()); if (name.isEmpty()) { name = " "; } diff --git a/platform/lang-impl/src/com/intellij/execution/compound/CompoundRunConfiguration.java b/platform/lang-impl/src/com/intellij/execution/compound/CompoundRunConfiguration.java index 624679243817..cecd5b92064f 100644 --- a/platform/lang-impl/src/com/intellij/execution/compound/CompoundRunConfiguration.java +++ b/platform/lang-impl/src/com/intellij/execution/compound/CompoundRunConfiguration.java @@ -20,9 +20,8 @@ import com.intellij.execution.configurations.*; import com.intellij.execution.executors.DefaultRunExecutor; import com.intellij.execution.impl.ExecutionManagerImpl; import com.intellij.execution.impl.RunManagerImpl; -import com.intellij.execution.impl.RunnerAndConfigurationSettingsImpl; import com.intellij.execution.runners.ExecutionEnvironment; -import com.intellij.execution.runners.ExecutionEnvironmentBuilder; +import com.intellij.execution.runners.ExecutionUtil; import com.intellij.execution.runners.ProgramRunner; import com.intellij.icons.AllIcons; import com.intellij.openapi.application.ApplicationManager; @@ -96,11 +95,9 @@ public class CompoundRunConfiguration extends RunConfigurationBase implements Ru ApplicationManager.getApplication().invokeLater(() -> { RunManagerImpl manager = RunManagerImpl.getInstanceImpl(getProject()); for (RunConfiguration configuration : getSetToRun()) { - RunnerAndConfigurationSettings settings = new RunnerAndConfigurationSettingsImpl(manager, configuration, false); - ExecutionEnvironmentBuilder builder = ExecutionEnvironmentBuilder.createOrNull(executor, settings); - if (builder != null) { - ExecutionManager.getInstance(getProject()) - .restartRunProfile(builder.activeTarget().dataContext(null).build()); + RunnerAndConfigurationSettings settings = manager.getSettings(configuration); + if (settings != null) { + ExecutionUtil.runConfiguration(settings, executor); } } }); diff --git a/platform/lang-impl/src/com/intellij/execution/dashboard/RunDashboardContent.java b/platform/lang-impl/src/com/intellij/execution/dashboard/RunDashboardContent.java index e049e9c4839e..1b7847d02450 100644 --- a/platform/lang-impl/src/com/intellij/execution/dashboard/RunDashboardContent.java +++ b/platform/lang-impl/src/com/intellij/execution/dashboard/RunDashboardContent.java @@ -18,8 +18,7 @@ package com.intellij.execution.dashboard; import com.intellij.execution.ExecutionBundle; import com.intellij.execution.RunnerAndConfigurationSettings; import com.intellij.execution.actions.StopAction; -import com.intellij.execution.dashboard.tree.DashboardGrouper; -import com.intellij.execution.dashboard.tree.RunDashboardTreeStructure; +import com.intellij.execution.dashboard.tree.*; import com.intellij.execution.runners.FakeRerunAction; import com.intellij.execution.ui.RunContentDescriptor; import com.intellij.execution.ui.RunContentManagerImpl; @@ -45,6 +44,7 @@ import com.intellij.ui.components.JBPanelWithEmptyText; import com.intellij.ui.content.*; import com.intellij.ui.treeStructure.Tree; import com.intellij.util.ObjectUtils; +import com.intellij.util.ui.EditableModel; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -84,6 +84,7 @@ public class RunDashboardContent extends JPanel implements TreeContent, Disposab private final DefaultTreeModel myTreeModel; private AbstractTreeBuilder myBuilder; + private RunDashboardAnimator myAnimator; private AbstractTreeNode myLastSelection; private final Set myCollapsedTreeNodeValues = new HashSet<>(); private final List myGroupers; @@ -102,14 +103,17 @@ public class RunDashboardContent extends JPanel implements TreeContent, Disposab myProject = project; myGroupers = groupers; - myTreeModel = new DefaultTreeModel(new DefaultMutableTreeNode()); - myTree = new Tree(myTreeModel); + myTree = new Tree(); + myTreeModel = new RunDashboardTreeModel(new DefaultMutableTreeNode(), myProject, myTree); + myTree.setModel(myTreeModel); myTree.setRootVisible(false); myTree.setShowsRootHandles(true); myTree.setCellRenderer(new NodeRenderer()); myTree.setLineStyleAngled(); + RowsDnDSupport.install(myTree, (EditableModel)myTreeModel); + myToolbar = createToolbar(); add(myToolbar, BorderLayout.WEST); @@ -338,6 +342,7 @@ public class RunDashboardContent extends JPanel implements TreeContent, Disposab }; myBuilder.initRootNode(); Disposer.register(this, myBuilder); + myAnimator = new RunDashboardAnimatorImpl(myBuilder); } private JComponent createToolbar() { @@ -417,6 +422,11 @@ public class RunDashboardContent extends JPanel implements TreeContent, Disposab return myBuilder; } + @NotNull + public RunDashboardAnimator getAnimator() { + return myAnimator; + } + private class GroupAction extends ToggleAction implements DumbAware { private final DashboardGrouper myGrouper; diff --git a/platform/lang-impl/src/com/intellij/execution/dashboard/RunDashboardManagerImpl.java b/platform/lang-impl/src/com/intellij/execution/dashboard/RunDashboardManagerImpl.java index d9c83ac32f0b..ac0cdd5f918c 100644 --- a/platform/lang-impl/src/com/intellij/execution/dashboard/RunDashboardManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/execution/dashboard/RunDashboardManagerImpl.java @@ -105,6 +105,11 @@ public class RunDashboardManagerImpl implements RunDashboardManager, PersistentS public void runConfigurationChanged(@NotNull RunnerAndConfigurationSettings settings) { updateDashboardIfNeeded(settings); } + + @Override + public void endUpdate() { + updateDashboard(true); + } }); connection.subscribe(ExecutionManager.EXECUTION_TOPIC, new ExecutionListener() { @Override @@ -235,6 +240,13 @@ public class RunDashboardManagerImpl implements RunDashboardManager, PersistentS updateToolWindowContent(); } + @Override + public RunDashboardAnimator getAnimator() { + if (myDashboardContent == null) return null; + + return myDashboardContent.getAnimator(); + } + private void updateDashboardIfNeeded(@Nullable RunnerAndConfigurationSettings settings) { if (settings != null && RunDashboardContributor.getContributor(settings.getType()) != null) { updateDashboard(true); diff --git a/platform/lang-impl/src/com/intellij/execution/dashboard/actions/GroupConfigurationsAction.java b/platform/lang-impl/src/com/intellij/execution/dashboard/actions/GroupConfigurationsAction.java new file mode 100644 index 000000000000..59e4731832f5 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/execution/dashboard/actions/GroupConfigurationsAction.java @@ -0,0 +1,66 @@ +/* + * Copyright 2000-2017 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.execution.dashboard.actions; + +import com.intellij.execution.ExecutionBundle; +import com.intellij.execution.dashboard.DashboardRunConfigurationNode; +import com.intellij.execution.dashboard.RunDashboardContent; +import com.intellij.execution.impl.RunManagerImpl; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * @author Konstantin Aleev + */ +public class GroupConfigurationsAction extends RunConfigurationTreeAction { + public GroupConfigurationsAction() { + super(ExecutionBundle.message("run.dashboard.group.configurations.action.name"), null, AllIcons.Actions.GroupByPackage); + } + + @Override + protected boolean isMultiSelectionAllowed() { + return true; + } + + @Override + protected void doActionPerformed(@NotNull RunDashboardContent content, AnActionEvent e, List nodes) { + Project project = e.getProject(); + if (project == null) return; + + DashboardRunConfigurationNode firstNode = ContainerUtil.getFirstItem(nodes); + String initialValue = firstNode != null ? firstNode.getConfigurationSettings().getFolderName() : null; + String value = Messages.showInputDialog(project, ExecutionBundle.message("run.dashboard.group.configurations.label"), + ExecutionBundle.message("run.dashboard.group.configurations.title"), null, initialValue, null); + if (value == null) return; + + String groupName = value.isEmpty() ? null : value; // If input value is empty then ungroup nodes. + + final RunManagerImpl runManager = RunManagerImpl.getInstanceImpl(project); + runManager.fireBeginUpdate(); + try { + nodes.forEach(node -> node.getConfigurationSettings().setFolderName(groupName)); + } + finally { + runManager.fireEndUpdate(); + } + } +} diff --git a/platform/lang-impl/src/com/intellij/execution/dashboard/actions/RunConfigurationTreeAction.java b/platform/lang-impl/src/com/intellij/execution/dashboard/actions/RunConfigurationTreeAction.java index e7643cc6e034..a80d4c0afe80 100644 --- a/platform/lang-impl/src/com/intellij/execution/dashboard/actions/RunConfigurationTreeAction.java +++ b/platform/lang-impl/src/com/intellij/execution/dashboard/actions/RunConfigurationTreeAction.java @@ -16,6 +16,8 @@ package com.intellij.execution.dashboard.actions; import com.intellij.execution.dashboard.DashboardRunConfigurationNode; +import com.intellij.openapi.actionSystem.AnActionEvent; +import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -31,4 +33,9 @@ public abstract class RunConfigurationTreeAction extends RunDashboardTreeAction< protected Class getTargetNodeClass() { return DashboardRunConfigurationNode.class; } + + @Override + protected boolean isVisibleForAnySelection(@NotNull AnActionEvent e) { + return false; + } } diff --git a/platform/lang-impl/src/com/intellij/execution/dashboard/actions/UngroupConfigurationsActions.java b/platform/lang-impl/src/com/intellij/execution/dashboard/actions/UngroupConfigurationsActions.java new file mode 100644 index 000000000000..3887b9e78e1d --- /dev/null +++ b/platform/lang-impl/src/com/intellij/execution/dashboard/actions/UngroupConfigurationsActions.java @@ -0,0 +1,83 @@ +/* + * Copyright 2000-2017 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.execution.dashboard.actions; + +import com.intellij.execution.ExecutionBundle; +import com.intellij.execution.dashboard.RunDashboardContent; +import com.intellij.execution.dashboard.RunDashboardContributor; +import com.intellij.execution.dashboard.tree.FolderDashboardGroupingRule; +import com.intellij.execution.dashboard.tree.GroupingNode; +import com.intellij.execution.impl.RunManagerImpl; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; + +/** + * @author Konstantin Aleev + */ +public class UngroupConfigurationsActions extends RunDashboardTreeAction { + protected UngroupConfigurationsActions() { + super(ExecutionBundle.message("run.dashboard.ungroup.configurations.action.name"), null, AllIcons.General.Remove); + } + + @Override + protected boolean isVisible4(GroupingNode node) { + return isEnabled4(node); + } + + @Override + protected boolean isEnabled4(GroupingNode node) { + return node.getGroup() instanceof FolderDashboardGroupingRule.FolderDashboardGroup; + } + + @Override + protected boolean isVisibleForAnySelection(@NotNull AnActionEvent e) { + return false; + } + + @Override + protected boolean isMultiSelectionAllowed() { + return true; + } + + @Override + protected Class getTargetNodeClass() { + return GroupingNode.class; + } + + @Override + protected void doActionPerformed(@NotNull RunDashboardContent content, AnActionEvent e, GroupingNode node) { + Project project = e.getProject(); + if (project == null) return; + + final RunManagerImpl runManager = RunManagerImpl.getInstanceImpl(project); + runManager.fireBeginUpdate(); + try { + runManager.getAllSettings().forEach(settings -> { + RunDashboardContributor contributor = RunDashboardContributor.getContributor(settings.getType()); + if (contributor == null || !contributor.isShowInDashboard(settings.getConfiguration())) return; + + if (node.getGroup().getName().equals(settings.getFolderName())) { + settings.setFolderName(null); + } + }); + } + finally { + runManager.fireEndUpdate(); + } + } +} diff --git a/platform/lang-impl/src/com/intellij/execution/dashboard/tree/FolderDashboardGroupingRule.java b/platform/lang-impl/src/com/intellij/execution/dashboard/tree/FolderDashboardGroupingRule.java index 6362f280485d..06669ffdf733 100644 --- a/platform/lang-impl/src/com/intellij/execution/dashboard/tree/FolderDashboardGroupingRule.java +++ b/platform/lang-impl/src/com/intellij/execution/dashboard/tree/FolderDashboardGroupingRule.java @@ -28,6 +28,8 @@ import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.swing.*; + /** * @author konstantin.aleev */ @@ -70,9 +72,15 @@ public class FolderDashboardGroupingRule implements DashboardGroupingRule { RunnerAndConfigurationSettings configurationSettings = ((DashboardRunConfigurationNode)node).getConfigurationSettings(); String folderName = configurationSettings.getFolderName(); if (folderName != null) { - return new DashboardGroupImpl<>(folderName, folderName, AllIcons.Nodes.Folder); + return new FolderDashboardGroup(folderName, folderName, AllIcons.Nodes.Folder); } } return null; } + + public static class FolderDashboardGroup extends DashboardGroupImpl { + public FolderDashboardGroup(String value, String name, Icon icon) { + super(value, name, icon); + } + } } diff --git a/platform/lang-impl/src/com/intellij/execution/dashboard/tree/RunDashboardAnimatorImpl.java b/platform/lang-impl/src/com/intellij/execution/dashboard/tree/RunDashboardAnimatorImpl.java new file mode 100644 index 000000000000..f6c939918538 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/execution/dashboard/tree/RunDashboardAnimatorImpl.java @@ -0,0 +1,153 @@ +/* + * Copyright 2000-2017 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.execution.dashboard.tree; + +import com.intellij.execution.dashboard.DashboardNode; +import com.intellij.execution.dashboard.RunDashboardAnimator; +import com.intellij.icons.AllIcons; +import com.intellij.ide.util.treeView.AbstractTreeBuilder; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.util.Disposer; +import com.intellij.util.Alarm; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.tree.DefaultMutableTreeNode; +import java.util.*; + +/** + * @author Konstantin Aleev + */ +public class RunDashboardAnimatorImpl implements RunDashboardAnimator, Runnable, Disposable { + private static final int FRAMES_COUNT = 12; + private static final int MOVIE_TIME = 1200; + private static final int FRAME_TIME = MOVIE_TIME / FRAMES_COUNT; + + public static final Icon[] FRAMES = new Icon[FRAMES_COUNT]; + + private long myLastInvocationTime = -1; + + private Alarm myAlarm; + private Set myNodes = new HashSet<>(); + private AbstractTreeBuilder myTreeBuilder; + + public RunDashboardAnimatorImpl(AbstractTreeBuilder builder) { + Disposer.register(builder, this); + init(builder); + } + + static { + FRAMES[0] = AllIcons.Process.Step_1; + FRAMES[1] = AllIcons.Process.Step_2; + FRAMES[2] = AllIcons.Process.Step_3; + FRAMES[3] = AllIcons.Process.Step_4; + FRAMES[4] = AllIcons.Process.Step_5; + FRAMES[5] = AllIcons.Process.Step_6; + FRAMES[6] = AllIcons.Process.Step_7; + FRAMES[7] = AllIcons.Process.Step_8; + FRAMES[8] = AllIcons.Process.Step_9; + FRAMES[9] = AllIcons.Process.Step_10; + FRAMES[10] = AllIcons.Process.Step_11; + FRAMES[11] = AllIcons.Process.Step_12; + } + + public static int getCurrentFrameIndex() { + return (int)((System.currentTimeMillis() % MOVIE_TIME) / FRAME_TIME); + } + + public static Icon getCurrentFrame() { + return FRAMES[getCurrentFrameIndex()]; + } + + /** + * Initializes animator: creates alarm and sets tree builder + * + * @param treeBuilder tree builder + */ + protected void init(final AbstractTreeBuilder treeBuilder) { + myAlarm = new Alarm(); + myTreeBuilder = treeBuilder; + } + + @Override + public void run() { + if (!myNodes.isEmpty()) { + final long time = System.currentTimeMillis(); + // optimization: + // we shouldn't repaint if this frame was painted in current interval + if (time - myLastInvocationTime >= FRAME_TIME) { + repaintSubTree(); + myLastInvocationTime = time; + } + } + scheduleRepaint(); + } + + @Override + public void addNode(@NotNull DashboardNode node) { + if (myNodes.add(node) && myNodes.size() == 1) { + scheduleRepaint(); + } + } + + @Override + public void removeNode(@NotNull DashboardNode node) { + if (myNodes.remove(node) && myNodes.isEmpty()) { + repaintSubTree(); + if (myAlarm != null) { + myAlarm.cancelAllRequests(); + } + } + } + + @Override + public void dispose() { + myTreeBuilder = null; + myNodes.clear(); + if (myAlarm != null) { + myAlarm.cancelAllRequests(); + myAlarm = null; + } + } + + private void repaintSubTree() { + if (myTreeBuilder == null || myTreeBuilder.isDisposed()) return; + + List toRemove = ContainerUtil.newSmartList(); + for (DashboardNode node : myNodes) { + DefaultMutableTreeNode treeNode = myTreeBuilder.getUi().getNodeForElement(node, false); + if (treeNode != null) { + myTreeBuilder.queueUpdateFrom(node, false, false); + } + else { + toRemove.add(node); + } + } + myNodes.removeAll(toRemove); + } + + private void scheduleRepaint() { + if (myAlarm == null) { + return; + } + myAlarm.cancelAllRequests(); + if (!myNodes.isEmpty()) { + myAlarm.addRequest(this, FRAME_TIME); + } + } +} + diff --git a/platform/lang-impl/src/com/intellij/execution/dashboard/tree/RunDashboardTreeModel.java b/platform/lang-impl/src/com/intellij/execution/dashboard/tree/RunDashboardTreeModel.java new file mode 100644 index 000000000000..cccb8e6e7b16 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/execution/dashboard/tree/RunDashboardTreeModel.java @@ -0,0 +1,157 @@ +/* + * Copyright 2000-2017 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.execution.dashboard.tree; + +import com.intellij.execution.RunnerAndConfigurationSettings; +import com.intellij.execution.dashboard.DashboardRunConfigurationNode; +import com.intellij.execution.impl.RunManagerImpl; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.project.Project; +import com.intellij.ui.RowsDnDSupport; +import com.intellij.ui.treeStructure.Tree; +import com.intellij.util.ObjectUtils; +import com.intellij.util.ui.EditableModel; +import gnu.trove.TObjectIntHashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeNode; +import java.util.Comparator; + +/** + * @author Konstantin Aleev + */ +public class RunDashboardTreeModel extends DefaultTreeModel implements EditableModel, RowsDnDSupport.RefinedDropSupport { + private final Project myProject; + private final Tree myTree; + + public RunDashboardTreeModel(TreeNode root, @NotNull Project project, @NotNull Tree tree) { + super(root); + myProject = project; + myTree = tree; + } + + @Override + public void addRow() { + } + + @Override + public void exchangeRows(int oldIndex, int newIndex) { + } + + @Override + public boolean canExchangeRows(int oldIndex, int newIndex) { + return false; + } + + @Override + public void removeRow(int idx) { + } + + @Override + public boolean isDropInto(JComponent component, int oldIndex, int newIndex) { + if (myProject.isDisposed() || DumbService.getInstance(myProject).isDumb()) return false; + + + DashboardRunConfigurationNode oldNode = getRunConfigurationNode(oldIndex); + if (oldNode == null) return false; + + GroupingNode newNode = getGroupingNode(newIndex); + return newNode != null && newNode.getGroup() instanceof FolderDashboardGroupingRule.FolderDashboardGroup; + } + + @Override + public boolean canDrop(int oldIndex, int newIndex, @NotNull Position position) { + if (myProject.isDisposed() || DumbService.getInstance(myProject).isDumb()) return false; + + DashboardRunConfigurationNode oldNode = getRunConfigurationNode(oldIndex); + if (oldNode == null) return false; + + if (position == Position.INTO) { + GroupingNode newNode = getGroupingNode(newIndex); + return newNode != null && newNode.getGroup() instanceof FolderDashboardGroupingRule.FolderDashboardGroup; + } + + return getRunConfigurationNode(newIndex) != null; + } + + @Override + public void drop(int oldIndex, int newIndex, @NotNull Position position) { + if (myProject.isDisposed() || DumbService.getInstance(myProject).isDumb()) return; + + DashboardRunConfigurationNode oldNode = getRunConfigurationNode(oldIndex); + if (oldNode == null) return; + + final RunManagerImpl runManager = RunManagerImpl.getInstanceImpl(myProject); + runManager.fireBeginUpdate(); + try { + if (position == Position.INTO) { + GroupingNode newNode = getGroupingNode(newIndex); + if (newNode == null || !(newNode.getGroup() instanceof FolderDashboardGroupingRule.FolderDashboardGroup)) return; + + oldNode.getConfigurationSettings().setFolderName(newNode.getGroup().getName()); + return; + } + + DashboardRunConfigurationNode newNode = getRunConfigurationNode(newIndex); + if (newNode == null) return; + + oldNode.getConfigurationSettings().setFolderName(newNode.getConfigurationSettings().getFolderName()); + + TObjectIntHashMap indices = new TObjectIntHashMap<>(); + int i = 0; + for (RunnerAndConfigurationSettings each : runManager.getAllSettings()) { + if (each.equals(oldNode.getConfigurationSettings())) continue; + + if (each.equals(newNode.getConfigurationSettings())) { + if (position == Position.ABOVE) { + indices.put(oldNode.getConfigurationSettings(), i++); + indices.put(newNode.getConfigurationSettings(), i++); + } + else if (position == Position.BELOW) { + indices.put(newNode.getConfigurationSettings(), i++); + indices.put(oldNode.getConfigurationSettings(), i++); + } + } + else { + indices.put(each, i++); + } + } + runManager.setOrder(Comparator.comparingInt(indices::get)); + } + finally { + runManager.fireEndUpdate(); + } + } + + @Nullable + private DashboardRunConfigurationNode getRunConfigurationNode(int index) { + return getDashboardNode(index, DashboardRunConfigurationNode.class); + } + + @Nullable + private GroupingNode getGroupingNode(int index) { + return getDashboardNode(index, GroupingNode.class); + } + + @Nullable T getDashboardNode(int index, Class nodeClass) { + DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)myTree.getPathForRow(index).getLastPathComponent(); + return ObjectUtils.tryCast(treeNode.getUserObject(), nodeClass); + } +} diff --git a/platform/lang-impl/src/com/intellij/execution/impl/ConfigurationSettingsEditor.java b/platform/lang-impl/src/com/intellij/execution/impl/ConfigurationSettingsEditor.java index fdde144fcf1a..bac73dde9a24 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/ConfigurationSettingsEditor.java +++ b/platform/lang-impl/src/com/intellij/execution/impl/ConfigurationSettingsEditor.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -26,11 +26,11 @@ import com.intellij.openapi.options.*; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Pair; import com.intellij.ui.ColoredListCellRenderer; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.ScrollingUtil; import com.intellij.ui.SimpleTextAttributes; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.Convertor; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.update.Activatable; import com.intellij.util.ui.update.UiNotifyConnector; import org.jetbrains.annotations.NonNls; @@ -149,7 +149,7 @@ public class ConfigurationSettingsEditor extends CompositeSettingsEditor() + if (isUseProjectSchemeManager()) { + project.service() + } + else { + project.service() + } } } -@State(name = "ProjectRunConfigurationManager", storages = arrayOf(Storage(value = "runConfigurations", stateSplitter = ProjectRunConfigurationManager.RunConfigurationStateSplitter::class))) -internal class ProjectRunConfigurationManager(manager: RunManager) : PersistentStateComponent { +@State(name = "ProjectRunConfigurationManager", storages = arrayOf(Storage(value = "runConfigurations", stateSplitter = OldProjectRunConfigurationStateSplitter::class))) +private class ProjectRunConfigurationManager(manager: RunManager) : PersistentStateComponent { private val manager = manager as RunManagerImpl override fun getState(): Element? { - if (Registry.`is`("runManager.use.schemeManager", false)) { - return null - } - val state = Element("state") manager.writeConfigurations(state, manager.getSharedConfigurations()) return state } override fun loadState(state: Element) { - if (Registry.`is`("runManager.use.schemeManager", false)) { - return - } - val existing = THashSet() - for (child in state.getChildren(RunManagerImpl.CONFIGURATION)) { - existing.add(manager.loadConfiguration(child, true).uniqueID) + state.getChildren(RunManagerImpl.CONFIGURATION).mapTo(existing) { + manager.loadConfiguration(it, true).uniqueID } manager.removeNotExistingSharedConfigurations(existing) @@ -86,10 +82,8 @@ internal class ProjectRunConfigurationManager(manager: RunManager) : PersistentS } } } +} - internal class RunConfigurationStateSplitter : StateSplitterEx() { - override fun splitState(state: Element): List> { - return StateSplitterEx.splitState(state, RunManagerImpl.NAME_ATTR) - } - } +internal class OldProjectRunConfigurationStateSplitter : StateSplitterEx() { + override fun splitState(state: Element): List> = StateSplitterEx.splitState(state, RunManagerImpl.NAME_ATTR) } \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/execution/impl/RunManagerImpl.kt b/platform/lang-impl/src/com/intellij/execution/impl/RunManagerImpl.kt index 4673818c639f..fa278fceedde 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/RunManagerImpl.kt +++ b/platform/lang-impl/src/com/intellij/execution/impl/RunManagerImpl.kt @@ -831,7 +831,7 @@ open class RunManagerImpl(internal val project: Project) : RunManagerEx(), Persi for (configuration in allSettings) { if (configuration.isShared) { if (result == null) { - result = ArrayList() + result = ArrayList() } result.add(configuration) } @@ -1051,11 +1051,11 @@ open class RunManagerImpl(internal val project: Project) : RunManagerEx(), Persi } } - if (tasks == templateTasks) { - result = emptyList() + result = if (tasks == templateTasks) { + emptyList() } else { - result = getEffectiveBeforeRunTaskList(tasks, templateTasks = templateTasks, ownIsOnlyEnabled = false, isDisableTemplateTasks = true) + getEffectiveBeforeRunTaskList(tasks, templateTasks = templateTasks, ownIsOnlyEnabled = false, isDisableTemplateTasks = true) } } @@ -1063,7 +1063,7 @@ open class RunManagerImpl(internal val project: Project) : RunManagerEx(), Persi fireBeforeRunTasksUpdated() } - fun checkIfDependenciesAreStable(configuration: RunConfiguration) { + private fun checkIfDependenciesAreStable(configuration: RunConfiguration) { if (isFirstLoadState.get()) return for (runTask in configuration.beforeRunTasks) { if (runTask is RunConfigurationBeforeRunProvider.RunConfigurableBeforeRunTask && runTask.settings != null && runTask.settings.isTemporary) { @@ -1182,4 +1182,4 @@ open class RunManagerImpl(internal val project: Project) : RunManagerEx(), Persi } } -private fun isUseProjectSchemeManager() = Registry.`is`("runManager.use.schemeManager", false) \ No newline at end of file +internal fun isUseProjectSchemeManager() = Registry.`is`("runManager.use.schemeManager", false) \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/execution/impl/statistics/AbstractRunConfigurationTypeUsagesCollector.java b/platform/lang-impl/src/com/intellij/execution/impl/statistics/AbstractRunConfigurationTypeUsagesCollector.java index 549a7ceeb0f7..741170f5dd2d 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/statistics/AbstractRunConfigurationTypeUsagesCollector.java +++ b/platform/lang-impl/src/com/intellij/execution/impl/statistics/AbstractRunConfigurationTypeUsagesCollector.java @@ -20,7 +20,7 @@ import com.intellij.execution.RunnerAndConfigurationSettings; import com.intellij.execution.configurations.ConfigurationFactory; import com.intellij.execution.configurations.ConfigurationType; import com.intellij.execution.configurations.RunConfiguration; -import com.intellij.internal.statistic.AbstractApplicationUsagesCollector; +import com.intellij.internal.statistic.AbstractProjectsUsagesCollector; import com.intellij.internal.statistic.beans.UsageDescriptor; import com.intellij.openapi.project.Project; import com.intellij.util.containers.ContainerUtil; @@ -33,7 +33,7 @@ import java.util.Set; /** * @author Nikolay Matveev */ -public abstract class AbstractRunConfigurationTypeUsagesCollector extends AbstractApplicationUsagesCollector { +public abstract class AbstractRunConfigurationTypeUsagesCollector extends AbstractProjectsUsagesCollector { protected abstract boolean isApplicable(@NotNull RunManager runManager, @NotNull RunnerAndConfigurationSettings settings); diff --git a/platform/lang-impl/src/com/intellij/facet/impl/statistics/FrameworkUsagesCollector.java b/platform/lang-impl/src/com/intellij/facet/impl/statistics/FrameworkUsagesCollector.java index 8d79b9283dce..e8095ae29d77 100644 --- a/platform/lang-impl/src/com/intellij/facet/impl/statistics/FrameworkUsagesCollector.java +++ b/platform/lang-impl/src/com/intellij/facet/impl/statistics/FrameworkUsagesCollector.java @@ -17,20 +17,19 @@ package com.intellij.facet.impl.statistics; import com.intellij.facet.Facet; import com.intellij.facet.FacetManager; -import com.intellij.internal.statistic.AbstractApplicationUsagesCollector; +import com.intellij.internal.statistic.AbstractProjectsUsagesCollector; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.Project; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; -import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashSet; import org.jetbrains.annotations.NotNull; import java.util.Set; -public class FrameworkUsagesCollector extends AbstractApplicationUsagesCollector { +public class FrameworkUsagesCollector extends AbstractProjectsUsagesCollector { public static final String GROUP_ID = "frameworks"; @NotNull diff --git a/platform/lang-impl/src/com/intellij/facet/impl/statistics/ModuleTypeUsagesCollector.java b/platform/lang-impl/src/com/intellij/facet/impl/statistics/ModuleTypeUsagesCollector.java index cb2630a673e5..5828aae24a3a 100644 --- a/platform/lang-impl/src/com/intellij/facet/impl/statistics/ModuleTypeUsagesCollector.java +++ b/platform/lang-impl/src/com/intellij/facet/impl/statistics/ModuleTypeUsagesCollector.java @@ -15,21 +15,20 @@ */ package com.intellij.facet.impl.statistics; -import com.intellij.internal.statistic.AbstractApplicationUsagesCollector; +import com.intellij.internal.statistic.AbstractProjectsUsagesCollector; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.module.ModuleType; import com.intellij.openapi.project.Project; -import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import java.util.HashSet; import java.util.Set; -public class ModuleTypeUsagesCollector extends AbstractApplicationUsagesCollector { +public class ModuleTypeUsagesCollector extends AbstractProjectsUsagesCollector { public static final String GROUP_ID = "module type"; @NotNull diff --git a/platform/lang-impl/src/com/intellij/find/actions/ShowUsagesAction.java b/platform/lang-impl/src/com/intellij/find/actions/ShowUsagesAction.java index 7ce8907dc3fa..342c93253673 100644 --- a/platform/lang-impl/src/com/intellij/find/actions/ShowUsagesAction.java +++ b/platform/lang-impl/src/com/intellij/find/actions/ShowUsagesAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -75,10 +75,7 @@ import com.intellij.util.ArrayUtil; import com.intellij.util.PlatformIcons; import com.intellij.util.Processor; import com.intellij.util.messages.MessageBusConnection; -import com.intellij.util.ui.AsyncProcessIcon; -import com.intellij.util.ui.ColumnInfo; -import com.intellij.util.ui.ListTableModel; -import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.*; import com.intellij.xml.util.XmlStringUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -1200,7 +1197,7 @@ public class ShowUsagesAction extends AnAction implements PopupAction { public Component prepareRenderer(@NotNull TableCellRenderer renderer, int row, int column) { Component component = super.prepareRenderer(renderer, row, column); if (component instanceof JComponent) { - ((JComponent)component).setBorder(IdeBorderFactory.createEmptyBorder(MARGIN, MARGIN, MARGIN, 0)); + ((JComponent)component).setBorder(JBUI.Borders.empty(MARGIN, MARGIN, MARGIN, 0)); } return component; } diff --git a/platform/lang-impl/src/com/intellij/find/impl/FindDialog.java b/platform/lang-impl/src/com/intellij/find/impl/FindDialog.java index 5e636043b1db..33d0da10407f 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/FindDialog.java +++ b/platform/lang-impl/src/com/intellij/find/impl/FindDialog.java @@ -154,17 +154,13 @@ public class FindDialog extends DialogWrapper implements FindUI { setTitle(myHelper.getTitle()); setOKButtonText(FindBundle.message("find.button")); init(); - initByModel(); - updateReplaceVisibility(); - validateFindButton(); - - if (haveResultsPreview()) { - ApplicationManager.getApplication().invokeLater(() -> scheduleResultsUpdate(), ModalityState.any()); - } } @Override public void showUI() { + if (haveResultsPreview()) { + ApplicationManager.getApplication().invokeLater(() -> scheduleResultsUpdate(), ModalityState.any()); + } show(); } @@ -563,18 +559,6 @@ public class FindDialog extends DialogWrapper implements FindUI { } } - @Override - public void updateReplaceVisibility() { - boolean isReplaceState = myHelper.getModel().isReplaceState(); - myReplacePrompt.setVisible(isReplaceState); - myReplaceComboBox.setVisible(isReplaceState); - if (myCbToSkipResultsWhenOneUsage != null) { - myCbToSkipResultsWhenOneUsage.setVisible(!isReplaceState); - } - myCbPreserveCase.setVisible(isReplaceState); - setTitle(myHelper.getTitle()); - } - private void validateFindButton() { boolean okStatus = myHelper.canSearchThisString() || myRbDirectory != null && myRbDirectory.isSelected() && StringUtil.isEmpty(getDirectory()); @@ -1575,6 +1559,15 @@ public class FindDialog extends DialogWrapper implements FindUI { setStringsToComboBox(findInProjectSettings.getRecentReplaceStrings(), myReplaceComboBox, myModel.getStringToReplace()); } updateControls(); + boolean isReplaceState = myModel.isReplaceState(); + myReplacePrompt.setVisible(isReplaceState); + myReplaceComboBox.setVisible(isReplaceState); + if (myCbToSkipResultsWhenOneUsage != null) { + myCbToSkipResultsWhenOneUsage.setVisible(!isReplaceState); + } + myCbPreserveCase.setVisible(isReplaceState); + setTitle(myHelper.getTitle()); + validateFindButton(); } private void navigateToSelectedUsage(JBTable source) { @@ -1675,7 +1668,7 @@ public class FindDialog extends DialogWrapper implements FindUI { setLayout(new BorderLayout()); add(myUsageRenderer, BorderLayout.CENTER); add(myFileAndLineNumber, BorderLayout.EAST); - setBorder(IdeBorderFactory.createEmptyBorder(MARGIN, MARGIN, MARGIN, 0)); + setBorder(JBUI.Borders.empty(MARGIN, MARGIN, MARGIN, 0)); } @Override diff --git a/platform/lang-impl/src/com/intellij/find/impl/FindInProjectUtil.java b/platform/lang-impl/src/com/intellij/find/impl/FindInProjectUtil.java index dd75314ad78d..515503da9df4 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/FindInProjectUtil.java +++ b/platform/lang-impl/src/com/intellij/find/impl/FindInProjectUtil.java @@ -113,6 +113,9 @@ public class FindInProjectUtil { // apply explicit settings from context if (module != null) { model.setModuleName(module.getName()); + directoryName = null;// Explicit 'module' scope is more specific than 'module directory' scope + model.setCustomScope(false); + model.setDirectoryName(null); } Editor editor = CommonDataKeys.EDITOR.getData(dataContext); @@ -124,7 +127,7 @@ public class FindInProjectUtil { } // set project scope if we have no other settings - model.setProjectScope(model.getDirectoryName() == null && model.getModuleName() == null && !model.isCustomScope()); + model.setProjectScope(directoryName == null && module == null && !model.isCustomScope()); } /** diff --git a/platform/lang-impl/src/com/intellij/find/impl/FindPopupPanel.java b/platform/lang-impl/src/com/intellij/find/impl/FindPopupPanel.java index efc2bfffe3e5..05a3bd1a3cb1 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/FindPopupPanel.java +++ b/platform/lang-impl/src/com/intellij/find/impl/FindPopupPanel.java @@ -589,7 +589,7 @@ public class FindPopupPanel extends JBPanel implements FindUI { return size; } }; - scrollPane.setBorder(IdeBorderFactory.createEmptyBorder()); + scrollPane.setBorder(JBUI.Borders.empty()); splitter.setFirstComponent(scrollPane); JPanel bottomPanel = new JPanel(new MigLayout("flowx, ins 4 4 0 4, fillx, hidemode 2, gap 0")); bottomPanel.add(myTabResultsButton); @@ -708,7 +708,17 @@ public class FindPopupPanel extends JBPanel implements FindUI { myReplaceComponent.setText(toReplace); updateControls(); updateScopeDetailsPanel(); - updateReplaceVisibility(); + + boolean isReplaceState = myHelper.isReplaceState(); + myTitleLabel.setText(myHelper.getTitle()); + myReplaceTextArea.setVisible(isReplaceState); + myCbPreserveCase.setVisible(isReplaceState); + if (Registry.is("ide.find.enter.as.ok", false) || isReplaceState) { + myOKHintLabel.setText(KeymapUtil.getKeystrokeText(ENTER)); + } else { + myOKHintLabel.setText(KeymapUtil.getKeystrokeText(ENTER_WITH_MODIFIERS)); + } + myOKButton.setText(FindBundle.message(isReplaceState ? "find.popup.replace.button" : "find.popup.find.button")); } private void updateControls() { @@ -738,19 +748,6 @@ public class FindPopupPanel extends JBPanel implements FindUI { } } - public void updateReplaceVisibility() { - boolean isReplaceState = myHelper.isReplaceState(); - myTitleLabel.setText(myHelper.getTitle()); - myReplaceTextArea.setVisible(isReplaceState); - myCbPreserveCase.setVisible(isReplaceState); - if (Registry.is("ide.find.enter.as.ok", false) || isReplaceState) { - myOKHintLabel.setText(KeymapUtil.getKeystrokeText(ENTER)); - } else { - myOKHintLabel.setText(KeymapUtil.getKeystrokeText(ENTER_WITH_MODIFIERS)); - } - myOKButton.setText(FindBundle.message(isReplaceState ? "find.popup.replace.button" : "find.popup.find.button")); - } - private void updateScopeDetailsPanel() { ((CardLayout)myScopeDetailsPanel.getLayout()).show(myScopeDetailsPanel, mySelectedScope.name); Component firstFocusableComponent = diff --git a/platform/lang-impl/src/com/intellij/find/impl/FindUI.java b/platform/lang-impl/src/com/intellij/find/impl/FindUI.java index 46d8dfb5ada7..f8e7c00820f8 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/FindUI.java +++ b/platform/lang-impl/src/com/intellij/find/impl/FindUI.java @@ -27,8 +27,6 @@ public interface FindUI { void initByModel(); - void updateReplaceVisibility(); - @NotNull Disposable getDisposable(); } diff --git a/platform/lang-impl/src/com/intellij/find/impl/FindUIHelper.java b/platform/lang-impl/src/com/intellij/find/impl/FindUIHelper.java index dcd3aed36c65..379c9c4c3f2b 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/FindUIHelper.java +++ b/platform/lang-impl/src/com/intellij/find/impl/FindUIHelper.java @@ -44,6 +44,7 @@ public class FindUIHelper implements Disposable { myModel = model; myOkHandler = okHandler; myUI = getOrCreateUI(); + myUI.initByModel(); } protected FindUI getOrCreateUI() { @@ -72,7 +73,7 @@ public class FindUIHelper implements Disposable { @Override public void actionPerformed(@NotNull AnActionEvent e) { myModel.setReplaceState(replace); - findDialog.updateReplaceVisibility(); + findDialog.initByModel(); } //@NotNull //private DataContextWrapper prepareDataContextForFind(@NotNull AnActionEvent e) { @@ -114,7 +115,6 @@ public class FindUIHelper implements Disposable { public void setModel(@NotNull FindModel model) { myModel = model; myUI.initByModel(); - myUI.updateReplaceVisibility(); } public void setOkHandler(@NotNull Runnable okHandler) { diff --git a/platform/lang-impl/src/com/intellij/framework/detection/impl/FrameworkDetectionManager.java b/platform/lang-impl/src/com/intellij/framework/detection/impl/FrameworkDetectionManager.java index 5c039dfbf3e3..9ff5d1316f90 100644 --- a/platform/lang-impl/src/com/intellij/framework/detection/impl/FrameworkDetectionManager.java +++ b/platform/lang-impl/src/com/intellij/framework/detection/impl/FrameworkDetectionManager.java @@ -24,10 +24,11 @@ import com.intellij.framework.detection.FrameworkDetector; import com.intellij.framework.detection.impl.exclude.DetectionExcludesConfigurationImpl; import com.intellij.framework.detection.impl.ui.ConfigureDetectedFrameworksDialog; import com.intellij.notification.Notification; +import com.intellij.notification.NotificationAction; import com.intellij.notification.NotificationGroup; -import com.intellij.notification.NotificationListener; import com.intellij.notification.NotificationType; import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.AbstractProjectComponent; import com.intellij.openapi.diagnostic.Logger; @@ -51,7 +52,6 @@ import com.intellij.util.ui.update.Update; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.TestOnly; -import javax.swing.event.HyperlinkEvent; import java.util.*; /** @@ -191,14 +191,14 @@ public class FrameworkDetectionManager extends AbstractProjectComponent implemen String names = StringUtil.join(frameworkNames, ", "); final String text = ProjectBundle.message("framework.detected.info.text", names, frameworkNames.size()); FRAMEWORK_DETECTION_NOTIFICATION - .createNotification("Frameworks detected", text, NotificationType.INFORMATION, new NotificationListener() { + .createNotification("Frameworks Detected", text, NotificationType.INFORMATION, null) + .addAction(new NotificationAction("Configure") { @Override - public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { - if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { - showSetupFrameworksDialog(notification); - } + public void actionPerformed(@NotNull AnActionEvent e, @NotNull Notification notification) { + showSetupFrameworksDialog(notification); } - }).notify(myProject); + }) + .notify(myProject); } } diff --git a/platform/lang-impl/src/com/intellij/ide/actions/AssociateFileTypeAction.java b/platform/lang-impl/src/com/intellij/ide/actions/AssociateFileTypeAction.java index 6523ee2d66fc..fba90ef8f674 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/AssociateFileTypeAction.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/AssociateFileTypeAction.java @@ -21,6 +21,7 @@ import com.intellij.openapi.fileTypes.FileTypeManager; import com.intellij.openapi.fileTypes.FileTypes; import com.intellij.openapi.fileTypes.ex.FileTypeChooser; import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.NonPhysicalFileSystem; import com.intellij.openapi.vfs.VirtualFile; public class AssociateFileTypeAction extends AnAction { @@ -43,6 +44,7 @@ public class AssociateFileTypeAction extends AnAction { else { // the action should also be available for files which have been auto-detected as text or as a particular language (IDEA-79574) haveSmthToDo = FileTypeManager.getInstance().getFileTypeByFileName(file.getName()) == FileTypes.UNKNOWN && + !(file.getFileSystem() instanceof NonPhysicalFileSystem) && !ScratchRootType.getInstance().containsFile(file); } presentation.setVisible(haveSmthToDo || ActionPlaces.isMainMenuOrActionSearch(e.getPlace())); diff --git a/platform/lang-impl/src/com/intellij/ide/actions/GotoRelatedSymbolAction.java b/platform/lang-impl/src/com/intellij/ide/actions/GotoRelatedSymbolAction.java index 7bd513e30549..1ec30d4b065a 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/GotoRelatedSymbolAction.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/GotoRelatedSymbolAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -27,7 +27,7 @@ import com.intellij.openapi.ui.popup.Balloon; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; -import com.intellij.ui.IdeBorderFactory; +import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; @@ -56,7 +56,7 @@ public class GotoRelatedSymbolAction extends AnAction { List items = NavigationUtil.collectRelatedItems(element, dataContext); if (items.isEmpty()) { final JComponent label = HintUtil.createErrorLabel("No related symbols"); - label.setBorder(IdeBorderFactory.createEmptyBorder(2, 7, 2, 7)); + label.setBorder(JBUI.Borders.empty(2, 7)); JBPopupFactory.getInstance().createBalloonBuilder(label) .setFadeoutTime(3000) .setFillColor(HintUtil.getErrorColor()) diff --git a/platform/lang-impl/src/com/intellij/ide/actions/SearchEverywhereAction.java b/platform/lang-impl/src/com/intellij/ide/actions/SearchEverywhereAction.java index a0b03e08fa59..4b87da0b9d76 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/SearchEverywhereAction.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/SearchEverywhereAction.java @@ -701,7 +701,7 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA topPanel.add(controls, BorderLayout.EAST); panel.add(myPopupField, BorderLayout.CENTER); panel.add(topPanel, BorderLayout.NORTH); - panel.setBorder(IdeBorderFactory.createEmptyBorder(3, 5, 4, 5)); + panel.setBorder(JBUI.Borders.empty(3, 5, 4, 5)); DataManager.registerDataProvider(panel, this); final ComponentPopupBuilder builder = JBPopupFactory.getInstance().createComponentPopupBuilder(panel, editor); myBalloon = builder diff --git a/platform/lang-impl/src/com/intellij/ide/bookmarks/actions/BookmarksAction.java b/platform/lang-impl/src/com/intellij/ide/bookmarks/actions/BookmarksAction.java index db35bdda80e4..e45b150b0cba 100644 --- a/platform/lang-impl/src/com/intellij/ide/bookmarks/actions/BookmarksAction.java +++ b/platform/lang-impl/src/com/intellij/ide/bookmarks/actions/BookmarksAction.java @@ -33,13 +33,13 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.openapi.wm.ex.WindowManagerEx; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.components.JBList; import com.intellij.ui.popup.util.DetailViewImpl; import com.intellij.ui.popup.util.ItemWrapper; import com.intellij.ui.popup.util.MasterDetailPopupBuilder; import com.intellij.ui.speedSearch.FilteringListModel; import com.intellij.util.ArrayUtil; +import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -312,7 +312,7 @@ public class BookmarksAction extends AnAction implements DumbAware, MasterDetail @Override protected Editor createEditor(@Nullable Project project, Document document, VirtualFile file) { Editor editor = super.createEditor(project, document, file); - editor.setBorder(IdeBorderFactory.createEmptyBorder()); + editor.setBorder(JBUI.Borders.empty()); return editor; } } diff --git a/platform/lang-impl/src/com/intellij/ide/favoritesTreeView/FavoritesTreeStructure.java b/platform/lang-impl/src/com/intellij/ide/favoritesTreeView/FavoritesTreeStructure.java index 2f8d9172b28c..8f87315368e1 100644 --- a/platform/lang-impl/src/com/intellij/ide/favoritesTreeView/FavoritesTreeStructure.java +++ b/platform/lang-impl/src/com/intellij/ide/favoritesTreeView/FavoritesTreeStructure.java @@ -31,11 +31,11 @@ import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.SmartPsiElementPointer; import com.intellij.psi.impl.PsiManagerImpl; import com.intellij.psi.impl.file.PsiDirectoryImpl; -import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NotNull; @@ -172,10 +172,17 @@ public class FavoritesTreeStructure extends ProjectTreeStructure { if (virtualFile == null) return children; VirtualFile[] virtualFiles = virtualFile.getChildren(); List result = new ArrayList<>(); + PsiManagerImpl psiManager = (PsiManagerImpl)PsiManager.getInstance(myProject); for (VirtualFile file : virtualFiles) { AbstractTreeNode child; - if (file.isDirectory()) child = new PsiDirectoryNode(myProject, new PsiDirectoryImpl((PsiManagerImpl)PsiManager.getInstance(myProject), file), settings); - else child = new PsiFileNode(myProject, PsiUtilCore.getPsiFile(myProject, file), settings); + if (file.isDirectory()) { + child = new PsiDirectoryNode(myProject, new PsiDirectoryImpl(psiManager, file), settings); + } + else { + PsiFile psiFile = psiManager.findFile(file); + if (psiFile == null) continue; + child = new PsiFileNode(myProject, psiFile, settings); + } child.setParent(parent); result.add(child); } diff --git a/platform/lang-impl/src/com/intellij/ide/favoritesTreeView/FavoritesTreeViewPanel.java b/platform/lang-impl/src/com/intellij/ide/favoritesTreeView/FavoritesTreeViewPanel.java index 8a5e28f6f239..5cf59c0ed235 100644 --- a/platform/lang-impl/src/com/intellij/ide/favoritesTreeView/FavoritesTreeViewPanel.java +++ b/platform/lang-impl/src/com/intellij/ide/favoritesTreeView/FavoritesTreeViewPanel.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -20,6 +20,7 @@ import com.intellij.icons.AllIcons; import com.intellij.ide.CopyPasteDelegator; import com.intellij.ide.ExporterToTextFile; import com.intellij.ide.IdeView; +import com.intellij.ide.bookmarks.Bookmark; import com.intellij.ide.dnd.aware.DnDAwareTree; import com.intellij.ide.favoritesTreeView.actions.*; import com.intellij.ide.projectView.PresentationData; @@ -60,6 +61,7 @@ import com.intellij.util.ArrayUtil; import com.intellij.util.EditSourceOnDoubleClickHandler; import com.intellij.util.EditSourceOnEnterKeyHandler; import com.intellij.util.PlatformUtils; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.tree.TreeUtil; import org.jetbrains.annotations.NotNull; @@ -217,9 +219,9 @@ public class FavoritesTreeViewPanel extends JPanel implements DataProvider, Dock action.registerCustomShortcutSet(action.getShortcutSet(), myTree); final JPanel panel = decorator.createPanel(); - panel.setBorder(IdeBorderFactory.createEmptyBorder()); + panel.setBorder(JBUI.Borders.empty()); add(panel, BorderLayout.CENTER); - setBorder(IdeBorderFactory.createEmptyBorder()); + setBorder(JBUI.Borders.empty()); myAutoScrollToSourceHandler = new AutoScrollToSourceHandler() { @Override protected boolean isAutoScrollMode() { @@ -366,6 +368,9 @@ public class FavoritesTreeViewPanel extends JPanel implements DataProvider, Dock } ArrayList result = new ArrayList<>(); for (Object element : elements) { + if (element instanceof Bookmark) { + element = ((Bookmark)element).getFile(); + } if (element instanceof PsiElement) { result.add((PsiElement)element); } diff --git a/platform/lang-impl/src/com/intellij/ide/favoritesTreeView/FavoritesViewSelectInTarget.java b/platform/lang-impl/src/com/intellij/ide/favoritesTreeView/FavoritesViewSelectInTarget.java index f29d52f51f29..1261b7cd8898 100644 --- a/platform/lang-impl/src/com/intellij/ide/favoritesTreeView/FavoritesViewSelectInTarget.java +++ b/platform/lang-impl/src/com/intellij/ide/favoritesTreeView/FavoritesViewSelectInTarget.java @@ -24,7 +24,8 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowId; import com.intellij.openapi.wm.ToolWindowManager; -import com.intellij.psi.*; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFileSystemItem; import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; @@ -106,8 +107,4 @@ public class FavoritesViewSelectInTarget extends SelectInTargetPsiWrapper { return StandardTargetWeights.FAVORITES_WEIGHT; } - @Override - protected boolean canWorkWithCustomObjects() { - return false; - } } \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/ide/fileTemplates/actions/CreateFromTemplateAction.java b/platform/lang-impl/src/com/intellij/ide/fileTemplates/actions/CreateFromTemplateAction.java index 4eac1dbc4db5..4373ae74168b 100644 --- a/platform/lang-impl/src/com/intellij/ide/fileTemplates/actions/CreateFromTemplateAction.java +++ b/platform/lang-impl/src/com/intellij/ide/fileTemplates/actions/CreateFromTemplateAction.java @@ -31,9 +31,10 @@ import java.util.function.Supplier; public class CreateFromTemplateAction extends CreateFromTemplateActionBase { private final Supplier myTemplate; - // Avoid calling the constructor from normal IDE actions, because: - // - Normal actions are preloaded at startup - // - Accessing FileTemplate out of FileTemplateManager triggers costly initialization + /** Avoid calling the constructor from normal IDE actions, because: + * - Normal actions are preloaded at startup + * - Accessing FileTemplate out of FileTemplateManager triggers costly initialization + */ public CreateFromTemplateAction(@NotNull FileTemplate template) { this(template.getName(), FileTemplateUtil.getIcon(template), () -> template); } diff --git a/platform/lang-impl/src/com/intellij/ide/hierarchy/HierarchyBrowserBaseEx.java b/platform/lang-impl/src/com/intellij/ide/hierarchy/HierarchyBrowserBaseEx.java index 163d62f596af..969ad45229b1 100644 --- a/platform/lang-impl/src/com/intellij/ide/hierarchy/HierarchyBrowserBaseEx.java +++ b/platform/lang-impl/src/com/intellij/ide/hierarchy/HierarchyBrowserBaseEx.java @@ -335,6 +335,9 @@ public abstract class HierarchyBrowserBaseEx extends HierarchyBrowserBase implem } public final void changeView(@NotNull final String typeName) { + changeView(typeName, true); + } + public final void changeView(@NotNull final String typeName, boolean requestFocus) { setCurrentViewType(typeName); final PsiElement element = mySmartPsiElementPointer.getElement(); @@ -378,9 +381,11 @@ public abstract class HierarchyBrowserBaseEx extends HierarchyBrowserBase implem } } - IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> { - IdeFocusManager.getGlobalInstance().requestFocus(getCurrentTree(), true); - }); + if (requestFocus) { + IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> { + IdeFocusManager.getGlobalInstance().requestFocus(getCurrentTree(), true); + }); + } } @SuppressWarnings("deprecation") diff --git a/platform/lang-impl/src/com/intellij/ide/impl/ProjectPaneSelectInTarget.java b/platform/lang-impl/src/com/intellij/ide/impl/ProjectPaneSelectInTarget.java index b1331fb73ffd..1cef6a713b6e 100644 --- a/platform/lang-impl/src/com/intellij/ide/impl/ProjectPaneSelectInTarget.java +++ b/platform/lang-impl/src/com/intellij/ide/impl/ProjectPaneSelectInTarget.java @@ -21,11 +21,6 @@ import com.intellij.ide.StandardTargetWeights; import com.intellij.ide.projectView.impl.ProjectViewPane; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.ProjectFileIndex; -import com.intellij.openapi.roots.ProjectRootManager; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiFileSystemItem; public class ProjectPaneSelectInTarget extends ProjectViewSelectInTarget implements DumbAware { public ProjectPaneSelectInTarget(Project project) { @@ -37,33 +32,11 @@ public class ProjectPaneSelectInTarget extends ProjectViewSelectInTarget impleme return SelectInManager.PROJECT; } - @Override - public boolean canSelect(PsiFileSystemItem file) { - return super.canSelect(file) && canSelect(file.getVirtualFile()); - } - @Override public boolean isSubIdSelectable(String subId, SelectInContext context) { return canSelect(context); } - private boolean canSelect(VirtualFile vFile) { - if (vFile != null && vFile.isValid()) { - ProjectFileIndex index = ProjectRootManager.getInstance(myProject).getFileIndex(); - if (index.getContentRootForFile(vFile, false) != null) { - return true; - } - - if (index.isInLibraryClasses(vFile) || index.isInLibrarySource(vFile)) { - return true; - } - - return Comparing.equal(vFile.getParent(), myProject.getBaseDir()); - } - - return false; - } - @Override public String getMinorViewId() { return ProjectViewPane.ID; @@ -74,8 +47,4 @@ public class ProjectPaneSelectInTarget extends ProjectViewSelectInTarget impleme return StandardTargetWeights.PROJECT_WEIGHT; } - @Override - protected boolean canWorkWithCustomObjects() { - return false; - } } diff --git a/platform/lang-impl/src/com/intellij/ide/impl/ProjectViewSelectInTarget.java b/platform/lang-impl/src/com/intellij/ide/impl/ProjectViewSelectInTarget.java index dbfab2bfa603..73a96a6b54bc 100644 --- a/platform/lang-impl/src/com/intellij/ide/impl/ProjectViewSelectInTarget.java +++ b/platform/lang-impl/src/com/intellij/ide/impl/ProjectViewSelectInTarget.java @@ -27,7 +27,10 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.util.ActionCallback; +import com.intellij.openapi.util.Comparing; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowId; @@ -106,7 +109,14 @@ public abstract class ProjectViewSelectInTarget extends SelectInTargetPsiWrapper @Override protected boolean canSelect(PsiFileSystemItem file) { - return true; + VirtualFile vFile = PsiUtilCore.getVirtualFile(file); + if (vFile == null || !vFile.isValid()) return false; + + ProjectFileIndex index = ProjectRootManager.getInstance(myProject).getFileIndex(); + return index.getContentRootForFile(vFile, false) != null || + index.isInLibraryClasses(vFile) || + index.isInLibrarySource(vFile) || + Comparing.equal(vFile.getParent(), myProject.getBaseDir()); } public String getSubIdPresentableName(String subId) { @@ -143,11 +153,6 @@ public abstract class ProjectViewSelectInTarget extends SelectInTargetPsiWrapper return ToolWindowId.PROJECT_VIEW; } - @Override - protected boolean canWorkWithCustomObjects() { - return true; - } - public final void setSubId(String subId) { mySubId = subId; } diff --git a/platform/lang-impl/src/com/intellij/ide/impl/SelectInTargetPsiWrapper.java b/platform/lang-impl/src/com/intellij/ide/impl/SelectInTargetPsiWrapper.java index 5ebb8a1cc868..ee9b06b1497a 100644 --- a/platform/lang-impl/src/com/intellij/ide/impl/SelectInTargetPsiWrapper.java +++ b/platform/lang-impl/src/com/intellij/ide/impl/SelectInTargetPsiWrapper.java @@ -39,7 +39,7 @@ public abstract class SelectInTargetPsiWrapper implements SelectInTarget { public final boolean canSelect(@NotNull SelectInContext context) { if (!isContextValid(context)) return false; - return canWorkWithCustomObjects() || canSelectInner(context); + return canSelectInner(context); } protected boolean canSelectInner(@NotNull SelectInContext context) { @@ -90,7 +90,12 @@ public abstract class SelectInTargetPsiWrapper implements SelectInTarget { protected abstract void select(Object selector, VirtualFile virtualFile, boolean requestFocus); - protected abstract boolean canWorkWithCustomObjects(); + /** + * @deprecated unused, implement canSelectInner(context) instead + */ + protected boolean canWorkWithCustomObjects() { + return false; + } protected abstract void select(PsiElement element, boolean requestFocus); diff --git a/platform/lang-impl/src/com/intellij/ide/impl/StructureViewWrapperImpl.java b/platform/lang-impl/src/com/intellij/ide/impl/StructureViewWrapperImpl.java index 0a7bbd49ec97..4842070b7bd3 100644 --- a/platform/lang-impl/src/com/intellij/ide/impl/StructureViewWrapperImpl.java +++ b/platform/lang-impl/src/com/intellij/ide/impl/StructureViewWrapperImpl.java @@ -18,7 +18,6 @@ package com.intellij.ide.impl; import com.intellij.ide.CommonActionsManager; import com.intellij.ide.DataManager; -import com.intellij.ide.IdeBundle; import com.intellij.ide.projectView.impl.ProjectRootsUtil; import com.intellij.ide.structureView.*; import com.intellij.ide.structureView.impl.StructureViewComposite; @@ -49,6 +48,7 @@ import com.intellij.openapi.wm.ToolWindowId; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.openapi.wm.ex.ToolWindowEx; import com.intellij.psi.PsiElement; +import com.intellij.ui.components.JBPanelWithEmptyText; import com.intellij.ui.content.*; import com.intellij.util.BitUtil; import com.intellij.util.ui.UIUtil; @@ -343,7 +343,12 @@ public class StructureViewWrapperImpl implements StructureViewWrapper, Disposabl } if (myModuleStructureComponent == null && myStructureView == null) { - createSinglePanel(new JLabel(IdeBundle.message("message.nothing.to.show.in.structure.view"), SwingConstants.CENTER)); + createSinglePanel(new JBPanelWithEmptyText() { + @Override + public Color getBackground() { + return UIUtil.getTreeBackground(); + } + }); } for (int i = 0; i < myPanels.length; i++) { diff --git a/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarModel.java b/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarModel.java index f2de60213119..caf50fbb3d18 100644 --- a/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarModel.java +++ b/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarModel.java @@ -30,10 +30,10 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Pair; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; -import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.CommonProcessors; import com.intellij.util.ObjectUtils; import com.intellij.util.Processor; @@ -52,6 +52,7 @@ public class NavBarModel { private int mySelectedIndex; private final Project myProject; private final NavBarModelListener myNotificator; + private final NavBarModelBuilder myBuilder; private boolean myChanged = true; private boolean updated = false; private boolean isFixedComponent = false; @@ -59,6 +60,7 @@ public class NavBarModel { public NavBarModel(final Project project) { myProject = project; myNotificator = project.getMessageBus().syncPublisher(NavBarModelListener.NAV_BAR); + myBuilder = NavBarModelBuilder.getInstance(); } public int getSelectedIndex() { @@ -165,9 +167,11 @@ public class NavBarModel { } } - final List updatedModel = new ArrayList<>(); - - ApplicationManager.getApplication().runReadAction(() -> traverseToRoot(psiElement, roots, updatedModel)); + final Computable> modelUpdater = () -> { + if (!isValid(psiElement)) return new ArrayList<>(); + return myBuilder.createModel(psiElement, roots); + }; + final List updatedModel = ApplicationManager.getApplication().runReadAction(modelUpdater); setModel(ContainerUtil.reverse(updatedModel)); } @@ -210,45 +214,6 @@ public class NavBarModel { } } - private static void traverseToRoot(@NotNull PsiElement psiElement, Set roots, List model) { - if (!isValid(psiElement)) return; - - NavBarModelExtension[] extensions = Extensions.getExtensions(NavBarModelExtension.EP_NAME); - - for (PsiElement e = normalize(getOriginalElement(psiElement)), next = null; - e != null; e = normalize(getOriginalElement(next)), next = null) { - // check if we're running circles due to getParent()->normalize/adjust() - if (model.contains(e)) break; - - model.add(e); - - // check if a root is reached - VirtualFile vFile = PsiUtilCore.getVirtualFile(e); - if (roots.contains(vFile)) break; - - for (NavBarModelExtension ext : extensions) { - PsiElement parent = ext.getParent(e); - if (parent != null && parent != e) { - //noinspection AssignmentToForLoopParameter - next = parent; - break; - } - } - } - } - - @Nullable - private static PsiElement getOriginalElement(@Nullable PsiElement e) { - if (e == null || !e.isValid()) return null; - - PsiFile containingFile = e.getContainingFile(); - if (containingFile != null && containingFile.getVirtualFile() == null) return null; - - PsiElement orig = e.getOriginalElement(); - return !(e instanceof PsiCompiledElement) && orig instanceof PsiCompiledElement ? e : orig; - } - - protected boolean hasChildren(Object object) { return !processChildren(object, new CommonProcessors.FindFirstProcessor<>()); } @@ -274,7 +239,7 @@ public class NavBarModel { } @Nullable - private static PsiElement normalize(@Nullable PsiElement child) { + public static PsiElement normalize(@Nullable PsiElement child) { if (child == null) return null; NavBarModelExtension[] extensions = Extensions.getExtensions(NavBarModelExtension.EP_NAME); diff --git a/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarModelBuilder.java b/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarModelBuilder.java new file mode 100644 index 000000000000..8315a7d0a82c --- /dev/null +++ b/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarModelBuilder.java @@ -0,0 +1,32 @@ +package com.intellij.ide.navigationToolbar; + +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * Creates a new model for navigation bar by taking a giving element and + * traverse path to root adding each element to a model + */ +public abstract class NavBarModelBuilder { + + public static NavBarModelBuilder getInstance() { + return ServiceManager.getService(NavBarModelBuilder.class); + } + + public List createModel(@NotNull PsiElement psiElement, @NotNull Set roots) { + final List model = new ArrayList<>(); + traverseToRoot(psiElement, roots, model); + return model; + } + + abstract void traverseToRoot(@NotNull PsiElement psiElement, + @NotNull Set roots, + @NotNull List model); +} + diff --git a/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarModelBuilderImpl.java b/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarModelBuilderImpl.java new file mode 100644 index 000000000000..5bf8f7da841b --- /dev/null +++ b/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarModelBuilderImpl.java @@ -0,0 +1,57 @@ +package com.intellij.ide.navigationToolbar; + +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiCompiledElement; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiUtilCore; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Set; + +public class NavBarModelBuilderImpl extends NavBarModelBuilder { + + @Override + public void traverseToRoot(@NotNull PsiElement psiElement, @NotNull Set roots, @NotNull List model) { + + NavBarModelExtension[] extensions = Extensions.getExtensions(NavBarModelExtension.EP_NAME); + + for (PsiElement e = normalize(psiElement), next = null; e != null; e = normalize(next), next = null) { + // check if we're running circles due to getParent()->normalize/adjust() + if (model.contains(e)) break; + model.add(e); + + // check if a root is reached + VirtualFile vFile = PsiUtilCore.getVirtualFile(e); + if (roots.contains(vFile)) break; + + for (NavBarModelExtension ext : extensions) { + PsiElement parent = ext.getParent(e); + if (parent != null && parent != e) { + //noinspection AssignmentToForLoopParameter + next = parent; + break; + } + } + } + } + + @Nullable + protected static PsiElement normalize(@Nullable PsiElement e) { + return NavBarModel.normalize(getOriginalElement(e)); + } + + @Nullable + private static PsiElement getOriginalElement(@Nullable PsiElement e) { + if (e == null || !e.isValid()) return null; + + PsiFile containingFile = e.getContainingFile(); + if (containingFile != null && containingFile.getVirtualFile() == null) return null; + + PsiElement orig = e.getOriginalElement(); + return !(e instanceof PsiCompiledElement) && orig instanceof PsiCompiledElement ? e : orig; + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarModelExtension.java b/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarModelExtension.java index 179fbfb8c5b0..4fa694052937 100644 --- a/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarModelExtension.java +++ b/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarModelExtension.java @@ -15,6 +15,7 @@ */ package com.intellij.ide.navigationToolbar; +import com.intellij.openapi.actionSystem.DataProvider; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; @@ -22,6 +23,7 @@ import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.swing.*; import java.util.Collection; /** @@ -35,6 +37,9 @@ import java.util.Collection; public interface NavBarModelExtension { ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.navbar"); + @Nullable + default Icon getIcon(Object object) { return null; } + @Nullable String getPresentableText(Object object); @@ -46,4 +51,8 @@ public interface NavBarModelExtension { @NotNull Collection additionalRoots(Project project); + + @Nullable + @SuppressWarnings("unused") + default Object getData(String dataId, DataProvider provider) { return null; } } diff --git a/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarPanel.java b/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarPanel.java index b9bdbad6f968..6147fcd36aa4 100644 --- a/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarPanel.java +++ b/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarPanel.java @@ -36,6 +36,7 @@ import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.project.Project; @@ -586,6 +587,12 @@ public class NavBarPanel extends JPanel implements DataProvider, PopupOwner, Dis @Override @Nullable public Object getData(String dataId) { + // First of all try to obtain data from extension + for (NavBarModelExtension modelExtension : Extensions.getExtensions(NavBarModelExtension.EP_NAME)) { + Object data = modelExtension.getData(dataId, this); + if (data != null) return data; + } + if (CommonDataKeys.PROJECT.is(dataId)) { return !myProject.isDisposed() ? myProject : null; } diff --git a/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarPopup.java b/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarPopup.java index 3514a7840d1a..0d6d6aedbdf0 100644 --- a/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarPopup.java +++ b/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarPopup.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -23,11 +23,15 @@ import com.intellij.openapi.ui.Queryable; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.ui.*; +import com.intellij.ui.HintHint; +import com.intellij.ui.LightweightHint; +import com.intellij.ui.ListenerUtil; +import com.intellij.ui.ScrollingUtil; import com.intellij.ui.awt.RelativePoint; import com.intellij.ui.components.JBList; import com.intellij.ui.popup.HintUpdateSupply; import com.intellij.ui.speedSearch.ListWithFilter; +import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -161,7 +165,7 @@ public class NavBarPopup extends LightweightHint implements Disposable{ disposables.add(navBarItem); return navBarItem; }); - list.setBorder(IdeBorderFactory.createEmptyBorder(5, 5, 5, 5)); + list.setBorder(JBUI.Borders.empty(5, 5, 5, 5)); installMoveAction(list, panel, -1, KeyEvent.VK_LEFT); installMoveAction(list, panel, 1, KeyEvent.VK_RIGHT); installEnterAction(list, panel, KeyEvent.VK_ENTER); diff --git a/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarPresentation.java b/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarPresentation.java index b5320f6019f3..e4c4fdf44c94 100644 --- a/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarPresentation.java +++ b/platform/lang-impl/src/com/intellij/ide/navigationToolbar/NavBarPresentation.java @@ -63,6 +63,14 @@ public class NavBarPresentation { @Nullable public Icon getIcon(final Object object) { if (!NavBarModel.isValid(object)) return null; + + for (NavBarModelExtension modelExtension : Extensions.getExtensions(NavBarModelExtension.EP_NAME)) { + Icon icon = modelExtension.getIcon(object); + if (icon != null) { + return icon; + } + } + if (object instanceof Project) return AllIcons.Nodes.Project; if (object instanceof Module) return ModuleType.get(((Module)object)).getIcon(); try { diff --git a/platform/lang-impl/src/com/intellij/ide/navigationToolbar/SelectInNavBarTarget.java b/platform/lang-impl/src/com/intellij/ide/navigationToolbar/SelectInNavBarTarget.java index 7858f1122e0e..f7164c05dd47 100644 --- a/platform/lang-impl/src/com/intellij/ide/navigationToolbar/SelectInNavBarTarget.java +++ b/platform/lang-impl/src/com/intellij/ide/navigationToolbar/SelectInNavBarTarget.java @@ -90,11 +90,6 @@ public class SelectInNavBarTarget extends SelectInTargetPsiWrapper implements Du return null; } - @Override - protected boolean canWorkWithCustomObjects() { - return false; - } - public String toString() { return SelectInManager.NAV_BAR; } diff --git a/platform/lang-impl/src/com/intellij/ide/scopeView/ScopePaneSelectInTarget.java b/platform/lang-impl/src/com/intellij/ide/scopeView/ScopePaneSelectInTarget.java index 51c28521530d..4aaee1f9cf18 100644 --- a/platform/lang-impl/src/com/intellij/ide/scopeView/ScopePaneSelectInTarget.java +++ b/platform/lang-impl/src/com/intellij/ide/scopeView/ScopePaneSelectInTarget.java @@ -85,11 +85,6 @@ public class ScopePaneSelectInTarget extends ProjectViewSelectInTarget { return StandardTargetWeights.SCOPE_WEIGHT; } - @Override - protected boolean canWorkWithCustomObjects() { - return false; - } - @Override public boolean isSubIdSelectable(@NotNull String subId, @NotNull SelectInContext context) { PsiFileSystemItem file = getContextPsiFile(context); diff --git a/platform/lang-impl/src/com/intellij/ide/scratch/ScratchFileActions.java b/platform/lang-impl/src/com/intellij/ide/scratch/ScratchFileActions.java index 73172ad25bc1..e010b6556417 100644 --- a/platform/lang-impl/src/com/intellij/ide/scratch/ScratchFileActions.java +++ b/platform/lang-impl/src/com/intellij/ide/scratch/ScratchFileActions.java @@ -80,7 +80,7 @@ public class ScratchFileActions { String place = e.getPlace(); boolean enabled = e.getProject() != null && Registry.is("ide.scratch.enabled") - && (ActionPlaces.isMainMenuOrActionSearch(place) || ActionPlaces.isPopupPlace(place)); + && (e.isFromActionToolbar() || ActionPlaces.isMainMenuOrActionSearch(place)); Presentation presentation = e.getPresentation(); presentation.setEnabledAndVisible(enabled); diff --git a/platform/lang-impl/src/com/intellij/ide/scratch/ScratchProjectViewPane.java b/platform/lang-impl/src/com/intellij/ide/scratch/ScratchProjectViewPane.java index c7a0891eb4b2..cd5e53cb52c9 100644 --- a/platform/lang-impl/src/com/intellij/ide/scratch/ScratchProjectViewPane.java +++ b/platform/lang-impl/src/com/intellij/ide/scratch/ScratchProjectViewPane.java @@ -122,11 +122,6 @@ public class ScratchProjectViewPane extends ProjectViewPane { return ScratchFileService.getInstance().getRootType(vFile) != null; } - @Override - protected boolean canWorkWithCustomObjects() { - return false; - } - @Override public String toString() { return getTitle(); diff --git a/platform/lang-impl/src/com/intellij/ide/todo/configurable/TodoConfigurable.java b/platform/lang-impl/src/com/intellij/ide/todo/configurable/TodoConfigurable.java index c59db024c372..82c8e2d1bee7 100644 --- a/platform/lang-impl/src/com/intellij/ide/todo/configurable/TodoConfigurable.java +++ b/platform/lang-impl/src/com/intellij/ide/todo/configurable/TodoConfigurable.java @@ -68,7 +68,7 @@ public class TodoConfigurable extends BaseConfigurable implements SearchableConf myPatternsModel = new PatternsTableModel(myPatterns); } - private boolean arePatternsModified() { + protected boolean arePatternsModified() { TodoConfiguration todoConfiguration = TodoConfiguration.getInstance(); TodoPattern[] initialPatterns = getTodoPatternsToDisplay(todoConfiguration); if (initialPatterns.length != myPatterns.size()) { diff --git a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/ChooseByNameBase.java b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/ChooseByNameBase.java index 515bed0fc383..77d349da02e6 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/ChooseByNameBase.java +++ b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/ChooseByNameBase.java @@ -450,7 +450,7 @@ public abstract class ChooseByNameBase { myToolArea = new JLabel(JBUI.scale(EmptyIcon.create(1, 24))); } else { - myToolArea.setBorder(IdeBorderFactory.createEmptyBorder(0, 6, 0, 0)); // space between checkbox and filter/show all in view buttons + myToolArea.setBorder(JBUI.Borders.emptyLeft(6)); // space between checkbox and filter/show all in view buttons } hBox.add(myToolArea); hBox.add(toolbarComponent); diff --git a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoActionModel.java b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoActionModel.java index 1f88a3d5f277..238890f4a4fb 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoActionModel.java +++ b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoActionModel.java @@ -50,6 +50,7 @@ import com.intellij.util.concurrency.Semaphore; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.ContainerUtilRt; import com.intellij.util.ui.EmptyIcon; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -551,7 +552,7 @@ public class GotoActionModel implements ChooseByNameModel, Comparator, D int index, boolean isSelected, boolean cellHasFocus) { boolean showIcon = UISettings.getInstance().getShowIconsInMenus(); JPanel panel = new JPanel(new BorderLayout()); - panel.setBorder(IdeBorderFactory.createEmptyBorder(2)); + panel.setBorder(JBUI.Borders.empty(2)); panel.setOpaque(true); Color bg = UIUtil.getListBackground(isSelected); panel.setBackground(bg); @@ -573,7 +574,7 @@ public class GotoActionModel implements ChooseByNameModel, Comparator, D Object value = ((MatchedValue) matchedValue).value; String pattern = ((MatchedValue)matchedValue).pattern; - Border eastBorder = IdeBorderFactory.createEmptyBorder(0, 0, 0, 2); + Border eastBorder = JBUI.Borders.emptyRight(2); if (value instanceof ActionWrapper) { ActionWrapper actionWithParentGroup = (ActionWrapper)value; AnAction anAction = actionWithParentGroup.getAction(); @@ -662,7 +663,7 @@ public class GotoActionModel implements ChooseByNameModel, Comparator, D OnOffButton button = new OnOffButton(); button.setSelected(selected); panel.add(button, BorderLayout.EAST); - panel.setBorder(IdeBorderFactory.createEmptyBorder(0, 2, 0, 2)); + panel.setBorder(JBUI.Borders.empty(0, 2)); } @NotNull diff --git a/platform/lang-impl/src/com/intellij/ide/util/projectWizard/ProjectSettingsStepBase.java b/platform/lang-impl/src/com/intellij/ide/util/projectWizard/ProjectSettingsStepBase.java index 1335be51b30d..9ed4efa9d761 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/projectWizard/ProjectSettingsStepBase.java +++ b/platform/lang-impl/src/com/intellij/ide/util/projectWizard/ProjectSettingsStepBase.java @@ -32,7 +32,6 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.wm.impl.welcomeScreen.AbstractActionWithPanel; import com.intellij.platform.DirectoryProjectGenerator; import com.intellij.platform.ProjectGeneratorPeer; -import com.intellij.platform.WebProjectGenerator; import com.intellij.platform.templates.TemplateProjectDirectoryGenerator; import com.intellij.ui.DocumentAdapter; import com.intellij.ui.JBColor; @@ -162,7 +161,7 @@ public class ProjectSettingsStepBase extends AbstractActionWithPanel implemen protected void initGeneratorListeners() { if (myProjectGenerator instanceof WebProjectTemplate) { - getPeer().addSettingsStateListener(new WebProjectGenerator.SettingsStateListener() { + getPeer().addSettingsListener(new ProjectGeneratorPeer.SettingsListener() { @Override public void stateChanged(boolean validSettings) { checkValid(); diff --git a/platform/lang-impl/src/com/intellij/internal/statistic/editor/EditorSettingsStatisticsCollector.java b/platform/lang-impl/src/com/intellij/internal/statistic/editor/EditorSettingsStatisticsCollector.java index 8e4d37d8aba9..b684b13152f7 100644 --- a/platform/lang-impl/src/com/intellij/internal/statistic/editor/EditorSettingsStatisticsCollector.java +++ b/platform/lang-impl/src/com/intellij/internal/statistic/editor/EditorSettingsStatisticsCollector.java @@ -17,7 +17,7 @@ package com.intellij.internal.statistic.editor; import com.intellij.codeInsight.CodeInsightSettings; import com.intellij.codeInsight.CodeInsightWorkspaceSettings; -import com.intellij.internal.statistic.AbstractApplicationUsagesCollector; +import com.intellij.internal.statistic.AbstractProjectsUsagesCollector; import com.intellij.internal.statistic.CollectUsagesException; import com.intellij.internal.statistic.UsagesCollector; import com.intellij.internal.statistic.beans.GroupDescriptor; @@ -143,7 +143,7 @@ class EditorSettingsStatisticsCollector extends UsagesCollector { } } - public static class ProjectUsages extends AbstractApplicationUsagesCollector { + public static class ProjectUsages extends AbstractProjectsUsagesCollector { @NotNull @Override public GroupDescriptor getGroupId() { diff --git a/platform/lang-impl/src/com/intellij/lang/pratt/PrattParser.java b/platform/lang-impl/src/com/intellij/lang/pratt/PrattParser.java index 184de62863c2..5db73dfb03cc 100644 --- a/platform/lang-impl/src/com/intellij/lang/pratt/PrattParser.java +++ b/platform/lang-impl/src/com/intellij/lang/pratt/PrattParser.java @@ -15,9 +15,10 @@ */ package com.intellij.lang.pratt; -import com.intellij.lang.PsiParser; import com.intellij.lang.ASTNode; +import com.intellij.lang.LangBundle; import com.intellij.lang.PsiBuilder; +import com.intellij.lang.PsiParser; import com.intellij.psi.tree.IElementType; import org.jetbrains.annotations.NotNull; @@ -29,7 +30,7 @@ public abstract class PrattParser implements PsiParser { @Override @NotNull - public final ASTNode parse(final IElementType root, final PsiBuilder builder) { + public final ASTNode parse(@NotNull IElementType root, @NotNull PsiBuilder builder) { final PrattBuilder prattBuilder = PrattBuilderImpl.createBuilder(builder, getRegistry()); final MutableMarker marker = prattBuilder.mark(); parse(prattBuilder); @@ -39,6 +40,9 @@ public abstract class PrattParser implements PsiParser { protected void parse(final PrattBuilder builder) { builder.parse(); - while (!builder.isEof()) builder.advance(); + if (!builder.isEof()) { + builder.error(LangBundle.message("unexpected.token")); + while (!builder.isEof()) builder.advance(); + } } } diff --git a/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/UniqueNameEditorTabTitleProvider.java b/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/UniqueNameEditorTabTitleProvider.java index 6acbe6a7e48b..2378094eacc0 100644 --- a/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/UniqueNameEditorTabTitleProvider.java +++ b/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/UniqueNameEditorTabTitleProvider.java @@ -40,7 +40,7 @@ public class UniqueNameEditorTabTitleProvider implements EditorTabTitleProvider String uniqueName = uiSettings.getEditorTabPlacement() == UISettings.TABS_NONE ? UniqueVFilePathBuilder.getInstance().getUniqueVirtualFilePath(project, file) : UniqueVFilePathBuilder.getInstance().getUniqueVirtualFilePathWithinOpenedFileEditors(project, file); - uniqueName = getEditorTabText(uniqueName, File.separator, uiSettings.getHdeKnownExtensionInTabs()); + uniqueName = getEditorTabText(uniqueName, File.separator, uiSettings.getHideKnownExtensionInTabs()); return uniqueName.equals(file.getName()) ? null : uniqueName; } diff --git a/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/UniqueVFilePathBuilderImpl.java b/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/UniqueVFilePathBuilderImpl.java index 2b286f04e49d..6aa62c838118 100644 --- a/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/UniqueVFilePathBuilderImpl.java +++ b/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/UniqueVFilePathBuilderImpl.java @@ -108,12 +108,9 @@ public class UniqueVFilePathBuilderImpl extends UniqueVFilePathBuilder { } if (uniqueNameBuilderForShortName != null && uniqueNameBuilderForShortName.contains(file)) { - if (file instanceof VirtualFilePathWrapper) { - return ((VirtualFilePathWrapper)file).getPresentablePath(); - } return uniqueNameBuilderForShortName.getShortPath(file); } - return file.getName(); + return file.getPresentableName(); } @Nullable @@ -145,7 +142,9 @@ public class UniqueVFilePathBuilderImpl extends UniqueVFilePathBuilder { path = path == null ? "" : FileUtil.toSystemIndependentName(path); UniqueNameBuilder builder = new UniqueNameBuilder<>(path, File.separator, 25); for (VirtualFile virtualFile: filesWithSameName) { - builder.addPath(virtualFile, virtualFile.getPath()); + String presentablePath = virtualFile instanceof VirtualFilePathWrapper ? + ((VirtualFilePathWrapper)virtualFile).getPresentablePath() : virtualFile.getPath(); + builder.addPath(virtualFile, presentablePath); } return builder; } diff --git a/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/text/PsiAwareTextEditorImpl.java b/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/text/PsiAwareTextEditorImpl.java index 7ff9638a5932..5a29a62adfaf 100644 --- a/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/text/PsiAwareTextEditorImpl.java +++ b/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/text/PsiAwareTextEditorImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -97,11 +97,11 @@ public class PsiAwareTextEditorImpl extends TextEditorImpl { @Override public void dispose() { + super.dispose(); CodeFoldingManager foldingManager = CodeFoldingManager.getInstance(myProject); if (foldingManager != null) { foldingManager.releaseFoldings(getEditor()); } - super.dispose(); } @Override diff --git a/platform/lang-impl/src/com/intellij/openapi/fileTypes/FileTypeUsagesCollector.java b/platform/lang-impl/src/com/intellij/openapi/fileTypes/FileTypeUsagesCollector.java index c8e7da0ab930..41f27ece8df7 100644 --- a/platform/lang-impl/src/com/intellij/openapi/fileTypes/FileTypeUsagesCollector.java +++ b/platform/lang-impl/src/com/intellij/openapi/fileTypes/FileTypeUsagesCollector.java @@ -15,7 +15,7 @@ */ package com.intellij.openapi.fileTypes; -import com.intellij.internal.statistic.AbstractApplicationUsagesCollector; +import com.intellij.internal.statistic.AbstractProjectsUsagesCollector; import com.intellij.internal.statistic.CollectUsagesException; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; @@ -34,7 +34,7 @@ import java.util.Set; /** * @author Nikolay Matveev */ -public class FileTypeUsagesCollector extends AbstractApplicationUsagesCollector { +public class FileTypeUsagesCollector extends AbstractProjectsUsagesCollector { private static final String GROUP_ID = "file-type"; @NotNull diff --git a/platform/lang-impl/src/com/intellij/openapi/util/registry/RegistryUi.java b/platform/lang-impl/src/com/intellij/openapi/util/registry/RegistryUi.java index 1dc4b37b8821..f18022477c30 100644 --- a/platform/lang-impl/src/com/intellij/openapi/util/registry/RegistryUi.java +++ b/platform/lang-impl/src/com/intellij/openapi/util/registry/RegistryUi.java @@ -28,6 +28,7 @@ import com.intellij.openapi.application.ex.ApplicationEx; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.ShadowAction; +import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.ui.*; @@ -328,7 +329,7 @@ public class RegistryUi implements Disposable { @Override protected void dispose() { super.dispose(); - RegistryUi.this.dispose(); + Disposer.dispose(RegistryUi.this); } @Override diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/SingleInspectionProfilePanel.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/SingleInspectionProfilePanel.java index 64b875baf38a..6b607206cc0f 100644 --- a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/SingleInspectionProfilePanel.java +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/SingleInspectionProfilePanel.java @@ -85,6 +85,8 @@ import java.util.List; import static com.intellij.util.containers.ContainerUtil.exists; +import com.intellij.util.containers.Queue; + public class SingleInspectionProfilePanel extends JPanel { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.ex.InspectionToolsPanel"); @NonNls private static final String INSPECTION_FILTER_HISTORY = "INSPECTION_FILTER_HISTORY"; @@ -1048,7 +1050,7 @@ public class SingleInspectionProfilePanel extends JPanel { myBrowser = new JEditorPane(UIUtil.HTML_MIME, EMPTY_HTML); myBrowser.setEditable(false); - myBrowser.setBorder(IdeBorderFactory.createEmptyBorder(5, 5, 5, 5)); + myBrowser.setBorder(JBUI.Borders.empty(5, 5, 5, 5)); myBrowser.addHyperlinkListener(new HyperlinkAdapter() { @Override protected void hyperlinkActivated(HyperlinkEvent e) { @@ -1089,7 +1091,7 @@ public class SingleInspectionProfilePanel extends JPanel { final JScrollPane tree = initTreeScrollPane(); final JPanel northPanel = new JPanel(new GridBagLayout()); - northPanel.setBorder(IdeBorderFactory.createEmptyBorder(2, 0, 2, 0)); + northPanel.setBorder(JBUI.Borders.empty(2, 0, 2, 0)); myProfileFilter.setPreferredSize(new Dimension(20, myProfileFilter.getPreferredSize().height)); northPanel.add(myProfileFilter, new GridBagConstraints(0, 0, 1, 1, 0.5, 1, GridBagConstraints.BASELINE_TRAILING, GridBagConstraints.HORIZONTAL, JBUI.emptyInsets(), 0, 0)); diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/table/ThreeStateCheckBoxRenderer.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/table/ThreeStateCheckBoxRenderer.java index 9f7affed9758..36c3f21d4b9c 100644 --- a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/table/ThreeStateCheckBoxRenderer.java +++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/table/ThreeStateCheckBoxRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -16,8 +16,8 @@ package com.intellij.profile.codeInspection.ui.table; import com.intellij.profile.codeInspection.ui.inspectionsTree.InspectionsConfigTreeTable; -import com.intellij.ui.IdeBorderFactory; import com.intellij.util.SmartList; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.ThreeStateCheckBox; import org.jetbrains.annotations.Nullable; @@ -44,7 +44,7 @@ public class ThreeStateCheckBoxRenderer extends ThreeStateCheckBox implements Ta setThirdStateEnabled(false); setHorizontalAlignment(CENTER); setVerticalAlignment(CENTER); - setBorder(IdeBorderFactory.createEmptyBorder(0, 0, 0, InspectionsConfigTreeTable.getAdditionalPadding())); + setBorder(JBUI.Borders.empty(0, 0, 0, InspectionsConfigTreeTable.getAdditionalPadding())); addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { diff --git a/platform/lang-impl/src/com/intellij/psi/codeStyle/DetectAndAdjustIndentOptionsTask.java b/platform/lang-impl/src/com/intellij/psi/codeStyle/DetectAndAdjustIndentOptionsTask.java index 89f4510677d8..b305645d89c9 100644 --- a/platform/lang-impl/src/com/intellij/psi/codeStyle/DetectAndAdjustIndentOptionsTask.java +++ b/platform/lang-impl/src/com/intellij/psi/codeStyle/DetectAndAdjustIndentOptionsTask.java @@ -38,29 +38,6 @@ import org.jetbrains.annotations.Nullable; import java.util.concurrent.ExecutorService; -class TimeStampedIndentOptions extends IndentOptions { - private long myTimeStamp; - private int myOriginalIndentOptionsHash; - - public TimeStampedIndentOptions(IndentOptions toCopyFrom, long timeStamp) { - copyFrom(toCopyFrom); - myTimeStamp = timeStamp; - } - - void setTimeStamp(long timeStamp) { - myTimeStamp = timeStamp; - } - - public void setOriginalIndentOptionsHash(int originalIndentOptionsHash) { - myOriginalIndentOptionsHash = originalIndentOptionsHash; - } - - public boolean isOutdated(@NotNull Document document, @NotNull IndentOptions defaultForFile) { - return document.getModificationStamp() != myTimeStamp - || defaultForFile.hashCode() != myOriginalIndentOptionsHash; - } -} - class DetectAndAdjustIndentOptionsTask extends ReadTask { private static final Logger LOG = Logger.getInstance(DetectAndAdjustIndentOptionsTask.class); private static final int INDENT_COMPUTATION_TIMEOUT = 5 * Time.SECOND; diff --git a/platform/lang-impl/src/com/intellij/psi/codeStyle/DetectableIndentOptionsProvider.java b/platform/lang-impl/src/com/intellij/psi/codeStyle/DetectableIndentOptionsProvider.java index 98182619655c..fd2fe534d4db 100644 --- a/platform/lang-impl/src/com/intellij/psi/codeStyle/DetectableIndentOptionsProvider.java +++ b/platform/lang-impl/src/com/intellij/psi/codeStyle/DetectableIndentOptionsProvider.java @@ -69,20 +69,32 @@ public class DetectableIndentOptionsProvider extends FileIndentOptionsProvider { return null; } - IndentOptions options = getValidCachedIndentOptions(file, document); - if (options != null) { - return options; + TimeStampedIndentOptions options; + //noinspection SynchronizationOnLocalVariableOrMethodParameter + synchronized (document) { + options = getValidCachedIndentOptions(file, document); + + if (options != null) { + return options; + } + + options = getDefaultIndentOptions(file, document); + options.associateWithDocument(document); } - TimeStampedIndentOptions indentOptions = getDefaultIndentOptions(file, document); - indentOptions.associateWithDocument(document); + scheduleDetectionInBackground(project, document, options); - DetectAndAdjustIndentOptionsTask task = new DetectAndAdjustIndentOptionsTask(project, document, indentOptions, BOUNDED_EXECUTOR); - task.scheduleInBackgroundForCommittedDocument(); - - return indentOptions; + return options; } - + + protected void scheduleDetectionInBackground(@NotNull Project project, + @NotNull Document document, + @NotNull TimeStampedIndentOptions options) + { + DetectAndAdjustIndentOptionsTask task = new DetectAndAdjustIndentOptionsTask(project, document, options, BOUNDED_EXECUTOR); + task.scheduleInBackgroundForCommittedDocument(); + } + @Override public boolean useOnFullReformat() { return false; @@ -194,7 +206,7 @@ public class DetectableIndentOptionsProvider extends FileIndentOptionsProvider { return !FileIndentOptionsProvider.isShowNotification() || myAcceptedFiles.contains(file); } - public IndentOptions getValidCachedIndentOptions(PsiFile file, Document document) { + public TimeStampedIndentOptions getValidCachedIndentOptions(PsiFile file, Document document) { IndentOptions options = IndentOptions.retrieveFromAssociatedDocument(file); if (options instanceof TimeStampedIndentOptions) { final IndentOptions defaultIndentOptions = getDefaultIndentOptions(file, document); diff --git a/platform/lang-impl/src/com/intellij/psi/codeStyle/TimeStampedIndentOptions.java b/platform/lang-impl/src/com/intellij/psi/codeStyle/TimeStampedIndentOptions.java new file mode 100644 index 000000000000..e5ecbf9542eb --- /dev/null +++ b/platform/lang-impl/src/com/intellij/psi/codeStyle/TimeStampedIndentOptions.java @@ -0,0 +1,45 @@ +/* + * Copyright 2000-2017 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.psi.codeStyle; + + +import com.intellij.openapi.editor.Document; +import org.jetbrains.annotations.NotNull; + + +public class TimeStampedIndentOptions extends CommonCodeStyleSettings.IndentOptions { + private long myTimeStamp; + private int myOriginalIndentOptionsHash; + + protected TimeStampedIndentOptions(CommonCodeStyleSettings.IndentOptions toCopyFrom, long timeStamp) { + copyFrom(toCopyFrom); + myTimeStamp = timeStamp; + myOriginalIndentOptionsHash = toCopyFrom.hashCode(); + } + + void setTimeStamp(long timeStamp) { + myTimeStamp = timeStamp; + } + + public void setOriginalIndentOptionsHash(int originalIndentOptionsHash) { + myOriginalIndentOptionsHash = originalIndentOptionsHash; + } + + public boolean isOutdated(@NotNull Document document, @NotNull CommonCodeStyleSettings.IndentOptions defaultForFile) { + return document.getModificationStamp() != myTimeStamp + || defaultForFile.hashCode() != myOriginalIndentOptionsHash; + } +} diff --git a/platform/lang-impl/src/com/intellij/psi/formatter/common/InjectedLanguageBlockBuilder.java b/platform/lang-impl/src/com/intellij/psi/formatter/common/InjectedLanguageBlockBuilder.java index e733970eedad..7b88846507d9 100644 --- a/platform/lang-impl/src/com/intellij/psi/formatter/common/InjectedLanguageBlockBuilder.java +++ b/platform/lang-impl/src/com/intellij/psi/formatter/common/InjectedLanguageBlockBuilder.java @@ -74,7 +74,7 @@ public abstract class InjectedLanguageBlockBuilder { return; } ASTNode node = shredHost.getNode(); - if (node == null) { + if (node == null || !injectionHost.getTextRange().contains(textRange.shiftRight(node.getStartOffset()))) { return; } if (node != injectionHost) { diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedLanguageManagerImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedLanguageManagerImpl.java index be7da928cdb6..33b034afd66c 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedLanguageManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedLanguageManagerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -394,6 +394,11 @@ public class InjectedLanguageManagerImpl extends InjectedLanguageManager impleme return result; } + @Override + public boolean mightHaveInjectedFragmentAtOffset(@NotNull Document hostDocument, int hostOffset) { + return InjectedLanguageUtil.mightHaveInjectedFragmentAtCaret(myProject, hostDocument, hostOffset); + } + private static int appendRange(List result, int start, int length) { if (length > 0) { int lastIndex = result.size() - 1; diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedLanguageUtil.java b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedLanguageUtil.java index 202ded157965..61bb1c92c9cd 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedLanguageUtil.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/InjectedLanguageUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -170,6 +170,21 @@ public class InjectedLanguageUtil { return getEditorForInjectedLanguageNoCommit(editor, file, offset); } + /** + * This is a quick check, that can be performed before committing document and invoking + * {@link #getEditorForInjectedLanguageNoCommit(Editor, Caret, PsiFile)} or other methods here, which don't work + * for uncommitted documents. + */ + public static boolean mightHaveInjectedFragmentAtCaret(@NotNull Project project, @NotNull Document hostDocument, int hostOffset) { + PsiFile hostPsiFile = PsiDocumentManager.getInstance(project).getCachedPsiFile(hostDocument); + if (hostPsiFile == null || !hostPsiFile.isValid()) return false; + ConcurrentList documents = getCachedInjectedDocuments(hostPsiFile); + for (DocumentWindow document : documents) { + if (document.isValid() && document.getHostRange(hostOffset) != null) return true; + } + return false; + } + /** * Invocation of this method on uncommitted {@code file} can lead to unexpected results, including throwing an exception! */ diff --git a/platform/lang-impl/src/com/intellij/refactoring/changeSignature/ChangeSignatureDialogBase.java b/platform/lang-impl/src/com/intellij/refactoring/changeSignature/ChangeSignatureDialogBase.java index 033a1e8d115f..9b76f822d3d3 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/changeSignature/ChangeSignatureDialogBase.java +++ b/platform/lang-impl/src/com/intellij/refactoring/changeSignature/ChangeSignatureDialogBase.java @@ -47,6 +47,7 @@ import com.intellij.ui.treeStructure.Tree; import com.intellij.util.Alarm; import com.intellij.util.Consumer; import com.intellij.util.IJSwingUtilities; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.table.JBListTable; import com.intellij.util.ui.table.JBTableRowEditor; @@ -304,7 +305,7 @@ public abstract class ChangeSignatureDialogBase> CALLBACK = Key.create("Callback"); + public static final Key>> VISIT = Key.create("TreeVisit"); private static final String EXPAND_TAG = "expand"; private static final String SELECT_TAG = "select"; private static final String PATH_TAG = "path"; - private static final int NOT_MATCHED = 0; - private static final int ID_MATCHED = 1; - private static final int OBJECT_MATCHED = 2; + private enum Match {OBJECT, ID_TYPE} @Tag("item") static class PathElement { @@ -78,7 +83,7 @@ public class TreeState implements JDOMExternalizable { Object userObject; final int index; - /** @noinspection unused*/ + @SuppressWarnings("unused") PathElement() { this(null, null, -1, null); } @@ -96,6 +101,17 @@ public class TreeState implements JDOMExternalizable { public String toString() { return id + ": " + type; } + + private boolean isMatchTo(Object object) { + return getMatchTo(object) != null; + } + + private Match getMatchTo(Object object) { + Object userObject = TreeUtil.getUserObject(object); + if (this.userObject != null && this.userObject.equals(userObject)) return Match.OBJECT; + return Comparing.equal(this.id, calcId(userObject)) && + Comparing.equal(this.type, calcType(userObject)) ? Match.ID_TYPE : null; + } } private final List> myExpandedPaths; @@ -224,6 +240,7 @@ public class TreeState implements JDOMExternalizable { } public void applyTo(@NotNull JTree tree, @Nullable Object root) { + if (visit(tree)) return; // AsyncTreeModel#visit if (root == null) return; TreeFacade facade = TreeFacade.getFacade(tree); ActionCallback callback = facade.getInitialized().doWhenDone(new TreeRunnable("TreeState.applyTo: on done facade init") { @@ -251,7 +268,7 @@ public class TreeState implements JDOMExternalizable { for (List path : myExpandedPaths) { if (path.isEmpty()) continue; int index = rootPath.getPathCount() - 1; - if (pathMatches(path.get(index), rootPath.getPathComponent(index)) == NOT_MATCHED) continue; + if (!path.get(index).isMatchTo(rootPath.getPathComponent(index))) continue; expandImpl(0, path, rootPath, tree, indicator); } } @@ -280,15 +297,17 @@ public class TreeState implements JDOMExternalizable { private static TreePath findMatchedChild(@NotNull TreeModel model, @NotNull TreePath treePath, @NotNull PathElement pathElement) { Object parent = treePath.getLastPathComponent(); int childCount = model.getChildCount(parent); + if (childCount <= 0) return null; + boolean idMatchedFound = false; Object idMatchedChild = null; for (int j = 0; j < childCount; j++) { Object child = model.getChild(parent, j); - int match = pathMatches(pathElement, child); - if (match == OBJECT_MATCHED) { + Match match = pathElement.getMatchTo(child); + if (match == Match.OBJECT) { return treePath.pathByAddingChild(child); } - else if (match == ID_MATCHED && !idMatchedFound) { + if (match == Match.ID_TYPE && !idMatchedFound) { idMatchedChild = child; idMatchedFound = true; } @@ -297,21 +316,9 @@ public class TreeState implements JDOMExternalizable { return treePath.pathByAddingChild(idMatchedChild); } - if (childCount > 0) { - int index = Math.max(0, Math.min(pathElement.index, childCount - 1)); - Object child = model.getChild(parent, index); - return treePath.pathByAddingChild(child); - } - - return null; - } - - @MagicConstant(intValues = {NOT_MATCHED, ID_MATCHED, OBJECT_MATCHED}) - private static int pathMatches(@NotNull PathElement pe, Object child) { - Object userObject = TreeUtil.getUserObject(child); - if (pe.userObject != null && pe.userObject.equals(userObject)) return OBJECT_MATCHED; - return Comparing.equal(pe.id, calcId(userObject)) && - Comparing.equal(pe.type, calcType(userObject)) ? ID_MATCHED : NOT_MATCHED; + int index = Math.max(0, Math.min(pathElement.index, childCount - 1)); + Object child = model.getChild(parent, index); + return treePath.pathByAddingChild(child); } private static void expandImpl(int positionInPath, @@ -332,7 +339,7 @@ public class TreeState implements JDOMExternalizable { int childCount = model.getChildCount(parent); for (int j = 0; j < childCount; j++) { Object child = tree.tree.getModel().getChild(parent, j); - if (pathMatches(next, child) != NOT_MATCHED) { + if (next.isMatchTo(child)) { expandImpl(positionInPath + 1, path, treePath.pathByAddingChild(child), tree, indicator); break; } @@ -357,7 +364,6 @@ public class TreeState implements JDOMExternalizable { AbstractTreeBuilder builder = AbstractTreeBuilder.getBuilderFor(tree); return builder != null ? new BuilderFacade(builder) : new JTreeFacade(tree); } - } static class JTreeFacade extends TreeFacade { @@ -436,5 +442,52 @@ public class TreeState implements JDOMExternalizable { } return "TreeState(" + myScrollToSelection + ")\n" + content; } + + private Promise> expand(@NotNull Function> visit, @NotNull JTree tree) { + return collectResults(myExpandedPaths.stream().map(elements -> new Visitor(elements, tree::expandPath)).map(visit).collect(toList())); + } + + private Promise> select(@NotNull Function> visit) { + return collectResults(mySelectedPaths.stream().map(elements -> new Visitor(elements, null)).map(visit).collect(toList())); + } + + private boolean visit(@NotNull JTree tree) { + Function> visit = UIUtil.getClientProperty(tree, VISIT); + if (visit == null) return false; + + expand(visit, tree).done(expanded -> { + if (tree.getSelectionCount() == 0) { + select(visit).done(selected -> { + if (tree.getSelectionCount() == 0) { + for (TreePath path : selected) { + tree.addSelectionPath(path); + } + } + }); + } + }); + return true; + } + + private static final class Visitor implements TreeVisitor { + private final List elements; + private final Consumer consumer; + + private Visitor(List elements, Consumer consumer) { + this.elements = elements; + this.consumer = consumer; + } + + @NotNull + @Override + public Action accept(@NotNull TreePath path) { + int count = path.getPathCount(); + if (count > elements.size()) return Action.SKIP_CHILDREN; + boolean matches = elements.get(count - 1).isMatchTo(path.getLastPathComponent()); + if (!matches) return Action.SKIP_CHILDREN; + if (consumer != null) consumer.accept(path); + return count < elements.size() ? Action.CONTINUE : Action.INTERRUPT; + } + } } diff --git a/platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java b/platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java index 79be355ecc16..d65cd7649101 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java @@ -105,7 +105,7 @@ public class EditorModificationUtil { editor.getSoftWrapModel().beforeDocumentChangeAtCaret(); int oldOffset = editor.getCaretModel().getOffset(); - String filler = calcStringToFillVirtualSpace(editor); + String filler = editor.getSelectionModel().hasSelection() ? "" : calcStringToFillVirtualSpace(editor); if (filler.length() > 0) { s = filler + s; } diff --git a/platform/platform-api/src/com/intellij/openapi/editor/actionSystem/EditorActionHandler.java b/platform/platform-api/src/com/intellij/openapi/editor/actionSystem/EditorActionHandler.java index c24a061872bc..8ca8d3a0af4c 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/actionSystem/EditorActionHandler.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/actionSystem/EditorActionHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -15,11 +15,15 @@ */ package com.intellij.openapi.editor.actionSystem; +import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.editor.Caret; +import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDocumentManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -86,15 +90,23 @@ public abstract class EditorActionHandler { private void doIfEnabled(@NotNull Caret hostCaret, @Nullable DataContext context, @NotNull CaretTask task) { DataContext caretContext = context == null ? null : new CaretSpecificDataContext(context, hostCaret); + Editor editor = hostCaret.getEditor(); if (myWorksInInjected && caretContext != null) { - DataContext injectedCaretContext = AnActionEvent.getInjectedDataContext(caretContext); - Caret injectedCaret = CommonDataKeys.CARET.getData(injectedCaretContext); - if (injectedCaret != null && injectedCaret != hostCaret && isEnabledForCaret(injectedCaret.getEditor(), injectedCaret, injectedCaretContext)) { - task.perform(injectedCaret, injectedCaretContext); - return; + Project project = editor.getProject(); + if (project != null) { + Document document = editor.getDocument(); + if (InjectedLanguageManager.getInstance(project).mightHaveInjectedFragmentAtOffset(document, hostCaret.getOffset())) { + PsiDocumentManager.getInstance(project).commitDocument(document); + DataContext injectedCaretContext = AnActionEvent.getInjectedDataContext(caretContext); + Caret injectedCaret = CommonDataKeys.CARET.getData(injectedCaretContext); + if (injectedCaret != null && injectedCaret != hostCaret && isEnabledForCaret(injectedCaret.getEditor(), injectedCaret, injectedCaretContext)) { + task.perform(injectedCaret, injectedCaretContext); + return; + } + } } } - if (isEnabledForCaret(hostCaret.getEditor(), hostCaret, caretContext)) { + if (isEnabledForCaret(editor, hostCaret, caretContext)) { task.perform(hostCaret, caretContext); } } diff --git a/platform/platform-api/src/com/intellij/openapi/keymap/KeymapUtil.java b/platform/platform-api/src/com/intellij/openapi/keymap/KeymapUtil.java index 5df874b699d3..bbd9aa54985e 100644 --- a/platform/platform-api/src/com/intellij/openapi/keymap/KeymapUtil.java +++ b/platform/platform-api/src/com/intellij/openapi/keymap/KeymapUtil.java @@ -439,6 +439,12 @@ public class KeymapUtil { return shortcut.getFirstKeyStroke(); } + @NotNull + public static String createTooltipText(@NotNull String name, @NotNull String actionId) { + String text = getFirstKeyboardShortcutText(actionId); + return text.isEmpty() ? name : name + " (" + text + ")"; + } + @NotNull public static String createTooltipText(@Nullable String name, @NotNull AnAction action) { String toolTipText = name == null ? "" : name; diff --git a/platform/platform-api/src/com/intellij/openapi/ui/ComboBox.java b/platform/platform-api/src/com/intellij/openapi/ui/ComboBox.java index 0f57a44fbd7d..f29bdabbbedf 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/ComboBox.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/ComboBox.java @@ -18,8 +18,8 @@ package com.intellij.openapi.ui; import com.intellij.openapi.ui.popup.JBPopup; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.components.JBList; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.MacUIUtil; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; @@ -133,7 +133,7 @@ public class ComboBox extends ComboBoxWithWidePopup implements AWTEventLis .setFocusOwners(new Component[]{this}) .setMinSize(new Dimension(getWidth(), -1)) .createPopup(); - list.setBorder(IdeBorderFactory.createEmptyBorder()); + list.setBorder(JBUI.Borders.empty()); myJBPopup.showUnderneathOf(this); list.addFocusListener(new FocusAdapter() { @Override diff --git a/platform/platform-api/src/com/intellij/ui/CheckBoxList.java b/platform/platform-api/src/com/intellij/ui/CheckBoxList.java index 4bf4d2f3b242..01b18514b9e7 100644 --- a/platform/platform-api/src/com/intellij/ui/CheckBoxList.java +++ b/platform/platform-api/src/com/intellij/ui/CheckBoxList.java @@ -135,9 +135,11 @@ public class CheckBoxList extends JBList { private void setPressed(MouseEvent e, boolean pressed) { Point point = e.getPoint(); int index = locationToIndex(point); - JCheckBox cb = getModel().getElementAt(index); - cb.getModel().setPressed(pressed); - UIUtil.repaintViewport(CheckBoxList.this); + if (index >= 0 && index < getModel().getSize()) { + JCheckBox cb = getModel().getElementAt(index); + cb.getModel().setPressed(pressed); + UIUtil.repaintViewport(CheckBoxList.this); + } } }); } diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/dpi/PngComparator.java b/platform/platform-api/src/com/intellij/ui/Expandable.java similarity index 76% rename from platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/dpi/PngComparator.java rename to platform/platform-api/src/com/intellij/ui/Expandable.java index abf4329eb83a..15129b159947 100644 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/dpi/PngComparator.java +++ b/platform/platform-api/src/com/intellij/ui/Expandable.java @@ -13,15 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.testGuiFramework.launcher.dpi; - -import java.io.File; +package com.intellij.ui; /** - * @author Sergey Karashevich + * @author Sergey Malenkov */ -public interface PngComparator { +public interface Expandable { - void compare(File pngFile1, File pngFile2); + void expand(); + void collapse(); } diff --git a/platform/platform-api/src/com/intellij/ui/HighlightableComponent.java b/platform/platform-api/src/com/intellij/ui/HighlightableComponent.java index 839192003c68..762f6d31b96a 100644 --- a/platform/platform-api/src/com/intellij/ui/HighlightableComponent.java +++ b/platform/platform-api/src/com/intellij/ui/HighlightableComponent.java @@ -21,6 +21,7 @@ import com.intellij.openapi.editor.markup.EffectType; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.accessibility.Accessible; @@ -29,6 +30,8 @@ import javax.accessibility.AccessibleRole; import javax.swing.*; import java.awt.*; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; /** * @author Eugene Belyaev @@ -378,6 +381,33 @@ public class HighlightableComponent extends JComponent implements Accessible { return null; } + @NotNull + public Map getHightlightedRegionsBoundsMap() { + + HashMap map = new HashMap<>(); + FontMetrics defFontMetrics = getFontMetrics(getFont()); + + int pivot = getTextOffset(); + int start, end; + + if (myText.length() != 0 && myHighlightedRegions.size() != 0) { + int endIndex = 0; + for (HighlightedRegion hRegion : myHighlightedRegions) { + pivot += defFontMetrics.stringWidth(myText.substring(endIndex, hRegion.startOffset)); + start = pivot; + endIndex = hRegion.endOffset; + + String text = getRegionText(hRegion); + Font regFont = getFont().deriveFont(hRegion.textAttributes.getFontType()); + FontMetrics fontMetrics = getFontMetrics(regFont); + pivot += fontMetrics.stringWidth(text); + end = pivot; + map.put(text, new Rectangle(this.getBounds().x + start, this.getBounds().y, end, this.getBounds().height)); + } + } + return map; + } + public Dimension getPreferredSize() { FontMetrics defFontMetrics = getFontMetrics(getFont()); @@ -412,7 +442,7 @@ public class HighlightableComponent extends JComponent implements Accessible { return new Dimension(width + 2, height); } - private String getRegionText(HighlightedRegion hRegion) { + public String getRegionText(HighlightedRegion hRegion) { String text; if (hRegion.endOffset > myText.length()) { if (hRegion.startOffset < myText.length()) { diff --git a/platform/platform-api/src/com/intellij/ui/HyperlinkLabel.java b/platform/platform-api/src/com/intellij/ui/HyperlinkLabel.java index d73f5d7a6d2c..8d29644358d4 100644 --- a/platform/platform-api/src/com/intellij/ui/HyperlinkLabel.java +++ b/platform/platform-api/src/com/intellij/ui/HyperlinkLabel.java @@ -200,7 +200,7 @@ public class HyperlinkLabel extends HighlightableComponent { myListeners.remove(listener); } - String getText() { + public String getText() { return myHighlightedText.getText(); } diff --git a/platform/platform-api/src/com/intellij/ui/IdeBorderFactory.java b/platform/platform-api/src/com/intellij/ui/IdeBorderFactory.java index 351d99289575..056730aa559b 100644 --- a/platform/platform-api/src/com/intellij/ui/IdeBorderFactory.java +++ b/platform/platform-api/src/com/intellij/ui/IdeBorderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -17,7 +17,7 @@ package com.intellij.ui; import com.intellij.ui.border.IdeaTitledBorder; import com.intellij.util.ui.JBInsets; -import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.JBUI; import org.intellij.lang.annotations.MagicConstant; import org.jetbrains.annotations.NotNull; @@ -64,15 +64,28 @@ public class IdeBorderFactory { return new EmptyBorder(JBInsets.create(insets)); } + /** + * @deprecated + * @see JBUI.Borders#empty() + */ public static Border createEmptyBorder() { - return createEmptyBorder(0); - } - public static Border createEmptyBorder(int thickness) { - return new EmptyBorder(new JBInsets(thickness, thickness, thickness, thickness)); + return JBUI.Borders.empty(); } + /** + * @deprecated + * @see JBUI.Borders#empty(int) + */ + public static Border createEmptyBorder(int thickness) { + return JBUI.Borders.empty(thickness); + } + + /** + * @deprecated + * @see JBUI.Borders#empty(int, int, int, int) + */ public static Border createEmptyBorder(int top, int left, int bottom, int right) { - return new EmptyBorder(new JBInsets(top, left, bottom, right)); + return JBUI.Borders.empty(top, left, bottom, right); } public static TitledBorder createTitledBorder(String s) { diff --git a/platform/platform-api/src/com/intellij/ui/RawCommandLineEditor.java b/platform/platform-api/src/com/intellij/ui/RawCommandLineEditor.java index 2a8402381e3c..707e1b332148 100644 --- a/platform/platform-api/src/com/intellij/ui/RawCommandLineEditor.java +++ b/platform/platform-api/src/com/intellij/ui/RawCommandLineEditor.java @@ -21,6 +21,9 @@ import com.intellij.openapi.fileChooser.FileChooserDescriptor; import com.intellij.openapi.ui.LabeledComponent; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.components.ExpandableTextField; import com.intellij.util.Function; import com.intellij.util.execution.ParametersListUtil; import org.jetbrains.annotations.Nullable; @@ -35,6 +38,7 @@ import java.util.List; public class RawCommandLineEditor extends JPanel implements TextAccessor { private static final Logger LOG = Logger.getInstance("#com.intellij.ui.RawCommandLineEditor"); + private final JTextField myEditor; private final TextFieldWithBrowseButton myTextField; private String myDialogCaption = ""; @@ -44,27 +48,35 @@ public class RawCommandLineEditor extends JPanel implements TextAccessor { public RawCommandLineEditor(final Function> lineParser, final Function, String> lineJoiner) { super(new BorderLayout()); - myTextField = new TextFieldWithBrowseButton(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - if (myDialogCaption == null) { - Container parent = getParent(); - if (parent instanceof LabeledComponent) { - parent = parent.getParent(); + if (Registry.is("raw.command.line.editor.dialog")) { + myTextField = new TextFieldWithBrowseButton(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (myDialogCaption == null) { + Container parent = getParent(); + if (parent instanceof LabeledComponent) { + parent = parent.getParent(); + } + LOG.error("Did not call RawCommandLineEditor.setDialogCaption() in " + parent); + myDialogCaption = "Parameters"; } - LOG.error("Did not call RawCommandLineEditor.setDialogCaption() in " + parent); - myDialogCaption = "Parameters"; + Messages.showTextAreaDialog(myTextField.getTextField(), myDialogCaption, "EditParametersPopupWindow", lineParser, lineJoiner); } - Messages.showTextAreaDialog(myTextField.getTextField(), myDialogCaption, "EditParametersPopupWindow", lineParser, lineJoiner); - } - }); - myTextField.setButtonIcon(AllIcons.Actions.ShowViewer); - add(myTextField, BorderLayout.CENTER); + }); + myEditor = myTextField.getTextField(); + myTextField.setButtonIcon(AllIcons.Actions.ShowViewer); + add(myTextField, BorderLayout.CENTER); + } + else { + myTextField = null; + myEditor = new ExpandableTextField(lineParser, lineJoiner); + add(myEditor, BorderLayout.CENTER); + } setDescriptor(null); } public void setDescriptor(FileChooserDescriptor descriptor) { - InsertPathAction.addTo(myTextField.getTextField(), descriptor); + InsertPathAction.addTo(myEditor, descriptor); } public String getDialogCaption() { @@ -77,29 +89,29 @@ public class RawCommandLineEditor extends JPanel implements TextAccessor { @Override public void setText(@Nullable String text) { - myTextField.setText(text); + myEditor.setText(text); } @Override public String getText() { - return myTextField.getText(); + return StringUtil.notNullize(myEditor.getText()); } public JTextField getTextField() { - return myTextField.getTextField(); + return myEditor; } public Document getDocument() { - return myTextField.getTextField().getDocument(); + return myEditor.getDocument(); } public void attachLabel(JLabel label) { - label.setLabelFor(myTextField.getTextField()); + label.setLabelFor(myEditor); } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); - myTextField.setEnabled(enabled); + (myTextField != null ? myTextField : myEditor).setEnabled(enabled); } } diff --git a/platform/platform-api/src/com/intellij/ui/ScrollPaneFactory.java b/platform/platform-api/src/com/intellij/ui/ScrollPaneFactory.java index 5ccfcca718da..5ffffa60a900 100644 --- a/platform/platform-api/src/com/intellij/ui/ScrollPaneFactory.java +++ b/platform/platform-api/src/com/intellij/ui/ScrollPaneFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -16,6 +16,7 @@ package com.intellij.ui; import com.intellij.ui.components.JBScrollPane; +import com.intellij.util.ui.JBUI; import org.intellij.lang.annotations.JdkConstants; import org.intellij.lang.annotations.MagicConstant; @@ -51,8 +52,8 @@ public class ScrollPaneFactory implements ScrollPaneConstants { public static JScrollPane createScrollPane(Component view, boolean withoutBorder) { JBScrollPane scrollPane = new JBScrollPane(view); if (withoutBorder) { - scrollPane.setBorder(IdeBorderFactory.createEmptyBorder()); // set empty border, because setting null doesn't always take effect - scrollPane.setViewportBorder(IdeBorderFactory.createEmptyBorder()); + scrollPane.setBorder(JBUI.Borders.empty()); // set empty border, because setting null doesn't always take effect + scrollPane.setViewportBorder(JBUI.Borders.empty()); } return scrollPane; } diff --git a/platform/platform-api/src/com/intellij/ui/SearchTextField.java b/platform/platform-api/src/com/intellij/ui/SearchTextField.java index 1d79d8664d00..65b395ce5df2 100644 --- a/platform/platform-api/src/com/intellij/ui/SearchTextField.java +++ b/platform/platform-api/src/com/intellij/ui/SearchTextField.java @@ -31,6 +31,7 @@ import com.intellij.ui.components.JBList; import com.intellij.ui.components.JBTextField; import com.intellij.util.ReflectionUtil; import com.intellij.util.ui.JBInsets; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import javax.swing.*; @@ -201,13 +202,13 @@ public class SearchTextField extends JPanel { myToggleHistoryLabel.setBackground(myTextField.getBackground()); myClearFieldLabel.setBackground(myTextField.getBackground()); - setBorder(new CompoundBorder(IdeBorderFactory.createEmptyBorder(2, 0, 2, 0), originalBorder)); + setBorder(new CompoundBorder(JBUI.Borders.empty(2, 0, 2, 0), originalBorder)); myTextField.setOpaque(true); - myTextField.setBorder(IdeBorderFactory.createEmptyBorder(0, 5, 0, 5)); + myTextField.setBorder(JBUI.Borders.empty(0, 5, 0, 5)); } else { - setBorder(IdeBorderFactory.createEmptyBorder(2, 0, 2, 0)); + setBorder(JBUI.Borders.empty(2, 0, 2, 0)); } } diff --git a/platform/platform-api/src/com/intellij/ui/TitledSeparator.java b/platform/platform-api/src/com/intellij/ui/TitledSeparator.java index 73c09c853f7c..2c85e5e87b79 100644 --- a/platform/platform-api/src/com/intellij/ui/TitledSeparator.java +++ b/platform/platform-api/src/com/intellij/ui/TitledSeparator.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -17,6 +17,7 @@ package com.intellij.ui; import com.intellij.ui.components.JBLabel; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.Nullable; @@ -33,7 +34,7 @@ public class TitledSeparator extends JPanel { public static final int SEPARATOR_LEFT_INSET = 6; public static final int SEPARATOR_RIGHT_INSET = 3; - public static final Border EMPTY_BORDER = IdeBorderFactory.createEmptyBorder(TOP_INSET, 0, BOTTOM_INSET, 0); + public static final Border EMPTY_BORDER = JBUI.Borders.empty(TOP_INSET, 0, BOTTOM_INSET, 0); protected final JBLabel myLabel = new JBLabel() { @Override diff --git a/platform/platform-api/src/com/intellij/ui/components/DefaultScrollBarUI.java b/platform/platform-api/src/com/intellij/ui/components/DefaultScrollBarUI.java index f53420f5f4cc..65d22c663144 100644 --- a/platform/platform-api/src/com/intellij/ui/components/DefaultScrollBarUI.java +++ b/platform/platform-api/src/com/intellij/ui/components/DefaultScrollBarUI.java @@ -38,7 +38,8 @@ import static java.awt.Adjustable.VERTICAL; * @author Sergey.Malenkov */ class DefaultScrollBarUI extends ScrollBarUI { - static final Key> LEADING_AREA = Key.create("PLAIN_SCROLL_BAR_UI_LEADING_AREA");//TODO:support + static final Key LEADING = Key.create("JB_SCROLL_BAR_LEADING_COMPONENT"); + static final Key TRAILING = Key.create("JB_SCROLL_BAR_TRAILING_COMPONENT"); private final Listener myListener = new Listener(); private final Timer myScrollTimer = UIUtil.createNamedTimer("ScrollBarThumbScrollTimer", 60, myListener); @@ -58,7 +59,6 @@ class DefaultScrollBarUI extends ScrollBarUI { private final Rectangle myThumbBounds = new Rectangle(); private final Rectangle myTrackBounds = new Rectangle(); - private final Rectangle myLeadingBounds = new Rectangle(); private final int myThickness; private final int myThicknessMax; private final int myThicknessMin; @@ -84,7 +84,7 @@ class DefaultScrollBarUI extends ScrollBarUI { } int getMinimalThickness() { - return scale(myThicknessMin); + return scale(myScrollBar == null || isOpaque(myScrollBar) ? myThickness : myThicknessMin); } static boolean isOpaque(JComponent c) { @@ -133,31 +133,29 @@ class DefaultScrollBarUI extends ScrollBarUI { void paintThumb(Graphics2D g, int x, int y, int width, int height, JComponent c) { RegionPainter p = ScrollColorProducer.isDark(c) ? ScrollPainter.Thumb.DARCULA : ScrollPainter.Thumb.DEFAULT; - paint(p, g, x, y, width, height, c, myThumbAnimator.myValue, ScrollSettings.isThumbSmallIfOpaque()); + paint(p, g, x, y, width, height, c, myThumbAnimator.myValue, ScrollSettings.isThumbSmallIfOpaque() && isOpaque(c)); } void onThumbMove() { } void paint(RegionPainter p, Graphics2D g, int x, int y, int width, int height, JComponent c, float value, boolean small) { - if (!isOpaque(c)) { - Alignment alignment = Alignment.get(c); - if (alignment == Alignment.LEFT || alignment == Alignment.RIGHT) { - int offset = getTrackOffset(width - getMinimalThickness()); - if (offset > 0) { - width -= offset; - if (alignment == Alignment.RIGHT) x += offset; - } - } - else { - int offset = getTrackOffset(height - getMinimalThickness()); - if (offset > 0) { - height -= offset; - if (alignment == Alignment.BOTTOM) y += offset; - } + Alignment alignment = Alignment.get(c); + if (alignment == Alignment.LEFT || alignment == Alignment.RIGHT) { + int offset = getTrackOffset(width - getMinimalThickness()); + if (offset > 0) { + width -= offset; + if (alignment == Alignment.RIGHT) x += offset; } } - else if (small) { + else { + int offset = getTrackOffset(height - getMinimalThickness()); + if (offset > 0) { + height -= offset; + if (alignment == Alignment.BOTTOM) y += offset; + } + } + if (small) { x += 1; y += 1; width -= 2; @@ -230,9 +228,34 @@ class DefaultScrollBarUI extends ScrollBarUI { public Dimension getPreferredSize(JComponent c) { int thickness = getThickness(); Alignment alignment = Alignment.get(c); - return alignment == Alignment.LEFT || alignment == Alignment.RIGHT - ? new Dimension(thickness, thickness * 2) - : new Dimension(thickness * 2, thickness); + Dimension preferred = new Dimension(thickness, thickness); + if (alignment == Alignment.LEFT || alignment == Alignment.RIGHT) { + preferred.height += preferred.height; + addPreferredHeight(preferred, UIUtil.getClientProperty(myScrollBar, LEADING)); + addPreferredHeight(preferred, UIUtil.getClientProperty(myScrollBar, TRAILING)); + } + else { + preferred.width += preferred.width; + addPreferredWidth(preferred, UIUtil.getClientProperty(myScrollBar, LEADING)); + addPreferredWidth(preferred, UIUtil.getClientProperty(myScrollBar, TRAILING)); + } + return preferred; + } + + private static void addPreferredWidth(Dimension preferred, Component component) { + if (component != null) { + Dimension size = component.getPreferredSize(); + preferred.width += size.width; + if (preferred.height < size.height) preferred.height = size.height; + } + } + + private static void addPreferredHeight(Dimension preferred, Component component) { + if (component != null) { + Dimension size = component.getPreferredSize(); + preferred.height += size.height; + if (preferred.width < size.width) preferred.width = size.width; + } } @Override @@ -247,6 +270,36 @@ class DefaultScrollBarUI extends ScrollBarUI { } Rectangle bounds = new Rectangle(c.getWidth(), c.getHeight()); JBInsets.removeFrom(bounds, c.getInsets()); + // process an area before the track + Component leading = UIUtil.getClientProperty(c, LEADING); + if (leading != null) { + if (alignment == Alignment.LEFT || alignment == Alignment.RIGHT) { + int size = leading.getPreferredSize().height; + leading.setBounds(bounds.x, bounds.y, bounds.width, size); + bounds.height -= size; + bounds.y += size; + } + else { + int size = leading.getPreferredSize().width; + leading.setBounds(bounds.x, bounds.y, size, bounds.height); + bounds.width -= size; + bounds.x += size; + } + } + // process an area after the track + Component trailing = UIUtil.getClientProperty(c, TRAILING); + if (trailing != null) { + if (alignment == Alignment.LEFT || alignment == Alignment.RIGHT) { + int size = trailing.getPreferredSize().height; + bounds.height -= size; + trailing.setBounds(bounds.x, bounds.y + bounds.height, bounds.width, size); + } + else { + int size = trailing.getPreferredSize().width; + bounds.width -= size; + trailing.setBounds(bounds.x + bounds.width, bounds.y, size, bounds.height); + } + } if (parent instanceof JScrollPane) { Color foreground = c.getForeground(); if (foreground != null && !foreground.equals(background) && isBorderNeeded(c)) { @@ -276,25 +329,6 @@ class DefaultScrollBarUI extends ScrollBarUI { if (!isOpaque(c) && myTrackAnimator.myValue > 0) { paintTrack((Graphics2D)g, bounds.x, bounds.y, bounds.width, bounds.height, c); } - // process a square area before the track - RegionPainter leading = UIUtil.getClientProperty(c, LEADING_AREA); - if (leading == null) { - myLeadingBounds.setSize(0, 0); - } - else if (alignment == Alignment.LEFT || alignment == Alignment.RIGHT) { - int size = bounds.width; - myLeadingBounds.setBounds(bounds.x, bounds.y, size, size); - leading.paint((Graphics2D)g, bounds.x, bounds.y, size, size, null); - bounds.height -= size; - bounds.y += size; - } - else { - int size = bounds.height; - myLeadingBounds.setBounds(bounds.x, bounds.y, size, size); - leading.paint((Graphics2D)g, bounds.x, bounds.y, size, size, null); - bounds.width -= size; - bounds.x += size; - } myTrackBounds.setBounds(bounds); updateThumbBounds(); // process additional drawing on the track diff --git a/platform/platform-api/src/com/intellij/ui/components/ExpandableTextField.java b/platform/platform-api/src/com/intellij/ui/components/ExpandableTextField.java new file mode 100644 index 000000000000..f4c0d262f014 --- /dev/null +++ b/platform/platform-api/src/com/intellij/ui/components/ExpandableTextField.java @@ -0,0 +1,243 @@ +/* + * Copyright 2000-2017 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.ui.components; + +import com.intellij.icons.AllIcons; +import com.intellij.openapi.ui.popup.JBPopup; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.Expandable; +import com.intellij.ui.ScreenUtil; +import com.intellij.ui.awt.RelativePoint; +import com.intellij.util.Function; +import com.intellij.util.execution.ParametersListUtil; +import com.intellij.util.ui.JBInsets; +import com.intellij.util.ui.JBUI; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.event.AncestorListener; +import javax.swing.plaf.TextUI; +import javax.swing.text.JTextComponent; +import java.awt.*; +import java.awt.event.*; +import java.util.Arrays; +import java.util.List; + +import static com.intellij.openapi.keymap.KeymapUtil.createTooltipText; +import static java.awt.event.InputEvent.CTRL_MASK; +import static java.beans.EventHandler.create; +import static java.util.Collections.singletonList; +import static javax.swing.KeyStroke.getKeyStroke; +import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS; + +/** + * @author Sergey Malenkov + */ +public class ExpandableTextField extends ExtendableTextField implements Expandable { + private static final int MINIMAL_WIDTH = 50; + private final Function parser; + private final Function joiner; + private JBPopup popup; + private String title; + + /** + * Creates an expandable text field with the default line parser/joiner, + * that uses a whitespaces to split a string to several lines. + */ + public ExpandableTextField() { + this(ParametersListUtil.DEFAULT_LINE_PARSER, ParametersListUtil.DEFAULT_LINE_JOINER); + } + + /** + * Creates an expandable text field with the specified line parser/joiner. + * + * @see ParametersListUtil + */ + public ExpandableTextField(@NotNull Function> parser, @NotNull Function, String> joiner) { + this.parser = text -> StringUtil.join(parser.fun(text), "\n"); + this.joiner = text -> joiner.fun(Arrays.asList(StringUtil.splitByLines(text))); + addAncestorListener(create(AncestorListener.class, this, "collapse")); + addComponentListener(create(ComponentListener.class, this, "collapse")); + putClientProperty("monospaced", true); + setExtensions(createExtensions()); + } + + protected List createExtensions() { + return singletonList(new Extension() { + @Override + public Icon getIcon(boolean hovered) { + return hovered ? AllIcons.General.ExpandComponentHover : AllIcons.General.ExpandComponent; + } + + @Override + public Runnable getActionOnClick() { + return ExpandableTextField.this::expand; + } + + @Override + public String getTooltip() { + return createTooltipText("Expand", "ExpandExpandableComponent"); + } + }); + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + @Override + public void setEnabled(boolean enabled) { + if (!enabled) collapse(); + super.setEnabled(enabled); + } + + @Override + @Deprecated + public void setUI(TextUI ui) { + TextUI suggested = ui; + String name = ui == null ? null : ui.getClass().getSuperclass().getName(); + if (!"com.intellij.ide.ui.laf.darcula.ui.TextFieldWithPopupHandlerUI".equals(name)) { + try { + ui = (TextUI)Class + .forName("com.intellij.ide.ui.laf.darcula.ui.DarculaTextFieldUI") + .getDeclaredMethod("createUI", JComponent.class) + .invoke(null, this); + } + catch (Exception ignore) { + } + } + super.setUI(ui); + if (ui != suggested) { + try { + setBorder((Border)Class.forName("com.intellij.ide.ui.laf.darcula.ui.DarculaTextBorder").newInstance()); + } + catch (Exception ignore) { + } + } + } + + @Override + public void collapse() { + if (popup != null) popup.cancel(); + } + + @Override + public void expand() { + if (popup != null || !isEnabled()) return; + + Font font = getFont(); + FontMetrics metrics = font == null ? null : getFontMetrics(font); + int height = metrics == null ? 16 : metrics.getHeight(); + Dimension size = new Dimension(height * 32, height * 16); + + JTextArea area = new JTextArea(parser.fun(getText())); + area.setFont(font); + area.setWrapStyleWord(true); + area.setLineWrap(true); + area.putClientProperty(Expandable.class, this); + copyCaretPosition(this, area); + UIUtil.addUndoRedoActions(area); + + JBScrollPane pane = new JBScrollPane(area); + pane.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_ALWAYS); + pane.getVerticalScrollBar().setBackground(area.getBackground()); + pane.getVerticalScrollBar().add(JBScrollBar.LEADING, new JLabel(AllIcons.General.CollapseComponent) {{ + setToolTipText(createTooltipText("Collapse", "CollapseExpandableComponent")); + setBorder(JBUI.Borders.empty(5, 0, 5, 5)); + addMouseListener(new MouseAdapter() { + @Override + public void mouseEntered(MouseEvent event) { + setIcon(AllIcons.General.CollapseComponentHover); + } + + @Override + public void mouseExited(MouseEvent event) { + setIcon(AllIcons.General.CollapseComponent); + } + + @Override + public void mousePressed(MouseEvent event) { + collapse(); + } + }); + }}); + + Insets insets = getInsets(); + JBInsets.addTo(size, insets); + JBInsets.addTo(size, pane.getInsets()); + if (size.width - MINIMAL_WIDTH < getWidth()) size.width = getWidth(); + + Point location = new Point(0, 0); + SwingUtilities.convertPointToScreen(location, this); + Rectangle screen = ScreenUtil.getScreenRectangle(this); + int bottom = screen.y - location.y + screen.height; + if (bottom < size.height) { + int top = location.y - screen.y + getHeight(); + if (top < bottom) { + size.height = bottom; + } + else { + if (size.height > top) size.height = top; + location.y -= size.height - getHeight(); + } + } + pane.setPreferredSize(size); + pane.setViewportBorder(BorderFactory.createEmptyBorder(insets.top, insets.left, insets.bottom, insets.right)); + + popup = JBPopupFactory.getInstance() + .createComponentPopupBuilder(pane, area) + .setFocusable(true) + .setRequestFocus(true) + .setTitle(title) + .setLocateByContent(true) + .setCancelOnWindowDeactivation(false) + .setKeyboardActions(singletonList(Pair.create(event -> { + collapse(); + Window window = UIUtil.getWindow(this); + if (window != null) { + window.dispatchEvent(new KeyEvent(this, KeyEvent.KEY_PRESSED, System.currentTimeMillis(), CTRL_MASK, KeyEvent.VK_ENTER, '\r')); + } + }, getKeyStroke(KeyEvent.VK_ENTER, CTRL_MASK)))) + .setCancelCallback(() -> { + try { + setText(joiner.fun(area.getText())); + copyCaretPosition(area, this); + popup = null; + return true; + } + catch (Exception ignore) { + return false; + } + }).createPopup(); + popup.show(new RelativePoint(location)); + } + + private static void copyCaretPosition(JTextComponent source, JTextComponent destination) { + try { + destination.setCaretPosition(source.getCaretPosition()); + } + catch (Exception ignored) { + } + } +} diff --git a/platform/platform-api/src/com/intellij/ui/components/ExtendableTextField.java b/platform/platform-api/src/com/intellij/ui/components/ExtendableTextField.java new file mode 100644 index 000000000000..899108f14e42 --- /dev/null +++ b/platform/platform-api/src/com/intellij/ui/components/ExtendableTextField.java @@ -0,0 +1,97 @@ +/* + * Copyright 2000-2017 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.ui.components; + +import javax.swing.Icon; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static com.intellij.util.ui.JBUI.scale; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.unmodifiableList; + +/** + * @author Sergey Malenkov + */ +public class ExtendableTextField extends JBTextField { + public static final String VARIANT = "extendable"; + private List extensions = emptyList(); + + public ExtendableTextField() { + this(null); + } + + public ExtendableTextField(int columns) { + this(null, columns); + } + + public ExtendableTextField(String text) { + this(text, 20); + } + + public ExtendableTextField(String text, int columns) { + super(text, columns); + } + + public List getExtensions() { + return extensions; + } + + public void setExtensions(Extension... extensions) { + setExtensions(asList(extensions)); + } + + public void setExtensions(Collection extensions) { + setExtensions(new ArrayList<>(extensions)); + } + + private void setExtensions(List extensions) { + putClientProperty("JTextField.variant", null); + this.extensions = unmodifiableList(extensions); + putClientProperty("JTextField.variant", VARIANT); + } + + public interface Extension { + Icon getIcon(boolean hovered); + + default int getIconGap() { + return scale(5); + } + + default int getPreferredSpace() { + Icon icon1 = getIcon(true); + Icon icon2 = getIcon(false); + if (icon1 == null && icon2 == null) return 0; + if (icon1 == null) return getIconGap() + icon2.getIconWidth(); + if (icon2 == null) return getIconGap() + icon1.getIconWidth(); + return getIconGap() + Math.max(icon1.getIconWidth(), icon2.getIconWidth()); + } + + default boolean isIconBeforeText() { + return false; + } + + default Runnable getActionOnClick() { + return null; + } + + default String getTooltip() { + return null; + } + } +} diff --git a/platform/platform-api/src/com/intellij/ui/components/JBList.java b/platform/platform-api/src/com/intellij/ui/components/JBList.java index 05335527a452..861dc99ae1b2 100644 --- a/platform/platform-api/src/com/intellij/ui/components/JBList.java +++ b/platform/platform-api/src/com/intellij/ui/components/JBList.java @@ -125,7 +125,8 @@ public class JBList extends JList implements ComponentWithEmptyText, Compo public void repaint(long tm, int x, int y, int width, int height) { if (width > 0 && height > 0) { ListUI ui = getUI(); - if (ui instanceof WideSelectionListUI) { + // do not paint a line background if layout orientation is not vertical + if (ui instanceof WideSelectionListUI && JList.VERTICAL == getLayoutOrientation()) { x = 0; width = getWidth(); } diff --git a/platform/platform-api/src/com/intellij/ui/components/JBScrollBar.java b/platform/platform-api/src/com/intellij/ui/components/JBScrollBar.java index 68607b14d5a5..b7fdaff64c61 100644 --- a/platform/platform-api/src/com/intellij/ui/components/JBScrollBar.java +++ b/platform/platform-api/src/com/intellij/ui/components/JBScrollBar.java @@ -47,6 +47,20 @@ public class JBScrollBar extends JScrollBar implements TopComponent, Interpolabl * @see UIUtil#putClientProperty */ public static final Key> TRACK = Key.create("JB_SCROLL_BAR_TRACK"); + /** + * This constraint should be used to add a component that will be shown before the scrollbar's track. + * Note that the previously added leading component will be removed. + * + * @see #addImpl(Component, Object, int) + */ + public static final String LEADING = "JB_SCROLL_BAR_LEADING_COMPONENT"; + /** + * This constraint should be used to add a component that will be shown after the scrollbar's track. + * Note that the previously added trailing component will be removed. + * + * @see #addImpl(Component, Object, int) + */ + public static final String TRAILING = "JB_SCROLL_BAR_TRAILING_COMPONENT"; private static final double THRESHOLD = 1D + 1E-5D; private final Interpolator myInterpolator = new Interpolator(this::getValue, this::setCurrentValue); @@ -69,6 +83,17 @@ public class JBScrollBar extends JScrollBar implements TopComponent, Interpolabl putClientProperty("JScrollBar.fastWheelScrolling", Boolean.TRUE); // fast scrolling for JDK 6 } + @Override + protected void addImpl(Component component, Object name, int index) { + Key key = LEADING.equals(name) ? DefaultScrollBarUI.LEADING : TRAILING.equals(name) ? DefaultScrollBarUI.TRAILING : null; + if (key != null) { + Component old = UIUtil.getClientProperty(this, key); + UIUtil.putClientProperty(this, key, component); + if (old != null) remove(old); + } + super.addImpl(component, name, index); + } + @Override public void updateUI() { ScrollBarUI ui = getUI(); diff --git a/platform/platform-api/src/com/intellij/ui/components/JBTextField.java b/platform/platform-api/src/com/intellij/ui/components/JBTextField.java index 744e63f68ab6..a77c709d7072 100644 --- a/platform/platform-api/src/com/intellij/ui/components/JBTextField.java +++ b/platform/platform-api/src/com/intellij/ui/components/JBTextField.java @@ -21,7 +21,9 @@ import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import javax.swing.*; +import javax.swing.plaf.TextUI; import java.awt.*; +import java.awt.event.MouseEvent; public class JBTextField extends JTextField implements ComponentWithEmptyText { private TextComponentEmptyText myEmptyText; @@ -76,4 +78,11 @@ public class JBTextField extends JTextField implements ComponentWithEmptyText { } myEmptyText.paintStatusText(g); } + + @Override + public String getToolTipText(MouseEvent event) { + TextUI ui = getUI(); + String text = ui == null ? null : ui.getToolTipText(this, event.getPoint()); + return text != null ? text : getToolTipText(); + } } diff --git a/platform/platform-api/src/com/intellij/ui/components/JBViewport.java b/platform/platform-api/src/com/intellij/ui/components/JBViewport.java index e150382bc537..7499ab047e38 100644 --- a/platform/platform-api/src/com/intellij/ui/components/JBViewport.java +++ b/platform/platform-api/src/com/intellij/ui/components/JBViewport.java @@ -149,6 +149,12 @@ public class JBViewport extends JViewport implements ZoomableViewport { return null; } + @Override + public void setView(Component view) { + super.setView(view); + updateBorder(view); + } + /* True double buffering is needed to eliminate tearing on blit-accelerated scrolling and to restore frame buffer content without the usual repainting, even when the EDT is blocked. @@ -379,7 +385,7 @@ public class JBViewport extends JViewport implements ZoomableViewport { viewport.setViewSize(viewSize); } - private static void updateBorder(Component view) { + private static void updateBorder(@Nullable Component view) { if (ScrollSettings.isNotSupportedYet(view)) return; if (view instanceof JComponent) { JComponent component = (JComponent)view; @@ -473,7 +479,7 @@ public class JBViewport extends JViewport implements ZoomableViewport { if (viewport == pane.getColumnHeader() ? (!opaque || ScrollSettings.isHeaderOverCorner(pane.getViewport())) : (!opaque && viewport == pane.getViewport())) { - Alignment va = UIUtil.getClientProperty(vsb, Alignment.class); + Alignment va = Alignment.get(vsb); if (va == Alignment.LEFT) { insets.left += vsb.getWidth(); } @@ -489,7 +495,7 @@ public class JBViewport extends JViewport implements ZoomableViewport { if (viewport == pane.getRowHeader() ? (!opaque || ScrollSettings.isHeaderOverCorner(pane.getViewport())) : (!opaque && viewport == pane.getViewport())) { - Alignment ha = UIUtil.getClientProperty(hsb, Alignment.class); + Alignment ha = Alignment.get(hsb); if (ha == Alignment.TOP) { insets.top += hsb.getHeight(); } diff --git a/platform/platform-api/src/com/intellij/ui/components/MacScrollBarUI.java b/platform/platform-api/src/com/intellij/ui/components/MacScrollBarUI.java index 533a1442d150..1178f1e16c14 100644 --- a/platform/platform-api/src/com/intellij/ui/components/MacScrollBarUI.java +++ b/platform/platform-api/src/com/intellij/ui/components/MacScrollBarUI.java @@ -71,7 +71,7 @@ final class MacScrollBarUI extends DefaultScrollBarUI { @Override boolean isTrackExpandable() { - return true; + return !isOpaque(myScrollBar); } @Override diff --git a/platform/platform-api/src/com/intellij/ui/components/WideSelectionListUI.java b/platform/platform-api/src/com/intellij/ui/components/WideSelectionListUI.java index 51a02f5f99fe..1039288ccd60 100644 --- a/platform/platform-api/src/com/intellij/ui/components/WideSelectionListUI.java +++ b/platform/platform-api/src/com/intellij/ui/components/WideSelectionListUI.java @@ -27,7 +27,8 @@ final class WideSelectionListUI extends BasicListUI { @Override public void paint(Graphics g, JComponent c) { - myPaintBounds = g.getClipBounds(); + // do not paint a line background if layout orientation is not vertical + myPaintBounds = !isVerticalList(c) ? null : g.getClipBounds(); super.paint(g, c); } @@ -85,4 +86,16 @@ final class WideSelectionListUI extends BasicListUI { } } } + + /** + * @param component the component being painted + * @return {@code true} if the specified component is a list with vertical orientation + */ + private static boolean isVerticalList(Component component) { + if (component instanceof JList) { + JList list = (JList)component; + return JList.VERTICAL == list.getLayoutOrientation(); + } + return false; + } } diff --git a/platform/platform-impl/src/com/intellij/ui/tree/TreeVisitor.java b/platform/platform-api/src/com/intellij/ui/tree/TreeVisitor.java similarity index 100% rename from platform/platform-impl/src/com/intellij/ui/tree/TreeVisitor.java rename to platform/platform-api/src/com/intellij/ui/tree/TreeVisitor.java diff --git a/platform/platform-api/src/com/intellij/util/io/HttpRequests.java b/platform/platform-api/src/com/intellij/util/io/HttpRequests.java index 7ac69950e284..91ab4122e376 100644 --- a/platform/platform-api/src/com/intellij/util/io/HttpRequests.java +++ b/platform/platform-api/src/com/intellij/util/io/HttpRequests.java @@ -501,12 +501,14 @@ public final class HttpRequests { } if (connection instanceof HttpURLConnection) { + HttpURLConnection httpURLConnection = (HttpURLConnection)connection; + if (LOG.isDebugEnabled()) LOG.debug("connecting to " + url); - int responseCode = ((HttpURLConnection)connection).getResponseCode(); + int responseCode = httpURLConnection.getResponseCode(); if (LOG.isDebugEnabled()) LOG.debug("response: " + responseCode); if (responseCode < 200 || responseCode >= 300 && responseCode != HttpURLConnection.HTTP_NOT_MODIFIED) { - ((HttpURLConnection)connection).disconnect(); + httpURLConnection.disconnect(); if (responseCode == HttpURLConnection.HTTP_MOVED_PERM || responseCode == HttpURLConnection.HTTP_MOVED_TEMP) { request.myUrl = url = connection.getHeaderField("Location"); diff --git a/platform/platform-api/src/com/intellij/util/net/ssl/CertificateInfoPanel.java b/platform/platform-api/src/com/intellij/util/net/ssl/CertificateInfoPanel.java index 86c6f81681de..8ec9885ee143 100644 --- a/platform/platform-api/src/com/intellij/util/net/ssl/CertificateInfoPanel.java +++ b/platform/platform-api/src/com/intellij/util/net/ssl/CertificateInfoPanel.java @@ -3,6 +3,7 @@ package com.intellij.util.net.ssl; import com.intellij.ui.*; import com.intellij.ui.components.JBLabel; import com.intellij.util.ui.FormBuilder; +import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import javax.swing.*; diff --git a/platform/platform-api/src/com/intellij/util/net/ssl/CertificateWarningDialog.form b/platform/platform-api/src/com/intellij/util/net/ssl/CertificateWarningDialog.form index 3421747cd043..49321c0a3cf4 100644 --- a/platform/platform-api/src/com/intellij/util/net/ssl/CertificateWarningDialog.form +++ b/platform/platform-api/src/com/intellij/util/net/ssl/CertificateWarningDialog.form @@ -8,7 +8,6 @@ - diff --git a/platform/platform-api/src/com/intellij/util/net/ssl/CertificateWarningDialog.java b/platform/platform-api/src/com/intellij/util/net/ssl/CertificateWarningDialog.java index d3d55de393c6..594587a4c615 100644 --- a/platform/platform-api/src/com/intellij/util/net/ssl/CertificateWarningDialog.java +++ b/platform/platform-api/src/com/intellij/util/net/ssl/CertificateWarningDialog.java @@ -6,6 +6,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.util.ui.JBDimension; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -40,6 +41,10 @@ public class CertificateWarningDialog extends DialogWrapper { public CertificateWarningDialog(@NotNull X509Certificate certificate, @NotNull String title, @NotNull String message) { super((Project)null, false); + myRootPanel.setPreferredSize(new JBDimension(550, 650)); + myNoticePane.setEditorKit(UIUtil.getHTMLEditorKit()); + myMessagePane.setEditorKit(UIUtil.getHTMLEditorKit()); + myCertificate = certificate; CertificateManager manager = CertificateManager.getInstance(); diff --git a/platform/platform-api/src/com/intellij/util/ui/FormBuilder.java b/platform/platform-api/src/com/intellij/util/ui/FormBuilder.java index f58e7808621b..915c742c2d91 100644 --- a/platform/platform-api/src/com/intellij/util/ui/FormBuilder.java +++ b/platform/platform-api/src/com/intellij/util/ui/FormBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -19,7 +19,6 @@ */ package com.intellij.util.ui; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.components.JBLabel; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -131,7 +130,7 @@ public class FormBuilder { public FormBuilder addTooltip(final String text) { final JBLabel label = new JBLabel(text, UIUtil.ComponentStyle.SMALL, UIUtil.FontColor.BRIGHTER); - label.setBorder(IdeBorderFactory.createEmptyBorder(0, 10, 0, 0)); + label.setBorder(JBUI.Borders.emptyLeft(10)); return addComponentToRightColumn(label, 1); } diff --git a/platform/platform-impl/src/com/intellij/application/options/schemes/AbstractSchemesPanel.java b/platform/platform-impl/src/com/intellij/application/options/schemes/AbstractSchemesPanel.java index a7a468bb989e..933cf50d4a9c 100644 --- a/platform/platform-impl/src/com/intellij/application/options/schemes/AbstractSchemesPanel.java +++ b/platform/platform-impl/src/com/intellij/application/options/schemes/AbstractSchemesPanel.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -27,7 +27,6 @@ import com.intellij.openapi.ui.popup.BalloonBuilder; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.ui.popup.ListPopup; import com.intellij.openapi.util.Disposer; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.JBColor; import com.intellij.ui.awt.RelativePoint; import com.intellij.util.ui.JBDimension; @@ -138,7 +137,7 @@ public abstract class AbstractSchemesPanel convert = depPath -> { + String result = PathUtil.toSystemIndependentName(depPath); + if (!result.startsWith("$APP") && result.contains("2017.1")) { + String migrated = result.replace("2017.1", version); + // check for possible PathMacroUtil.APPLICATION_* + if (pathMacroManager.collapsePath(migrated).startsWith("$APP")) { + return migrated; + } + } + return result; + }; + Consumer> convertList = o -> { + for (ListIterator it = o.listIterator(); it.hasNext(); ) { + it.set(convert.fun(it.next())); + } + }; - makeSystemIndependent(openPaths); + convertList.consume(recentPaths); + convertList.consume(openPaths); Map namesCopy = new HashMap<>(names); names.clear(); for (Map.Entry entry : namesCopy.entrySet()) { - names.put(PathUtil.toSystemIndependentName(entry.getKey()), entry.getValue()); + names.put(convert.fun(entry.getKey()), entry.getValue()); } for (ProjectGroup group : groups) { List paths = new ArrayList<>(group.getProjects()); - makeSystemIndependent(paths); + convertList.consume(paths); group.save(paths); } if (lastPath != null) { - lastPath = PathUtil.toSystemIndependentName(lastPath); + lastPath = convert.fun(lastPath); } Map additionalInfoCopy = new HashMap<>(additionalInfo); additionalInfo.clear(); for (Map.Entry entry : additionalInfoCopy.entrySet()) { - entry.getValue().binFolder = PathUtil.toSystemIndependentName(entry.getValue().binFolder); - additionalInfo.put(PathUtil.toSystemIndependentName(entry.getKey()), entry.getValue()); + entry.getValue().binFolder = convert.fun(entry.getValue().binFolder); + additionalInfo.put(convert.fun(entry.getKey()), entry.getValue()); } if (lastProjectLocation != null) { - lastProjectLocation = PathUtil.toSystemIndependentName(lastProjectLocation); + lastProjectLocation = convert.fun(lastProjectLocation); } } - private static void makeSystemIndependent(List paths) { - List copy = new ArrayList<>(paths); - paths.clear(); - for (String path : copy) { - paths.add(PathUtil.toSystemIndependentName(path)); + public void updateOpenProjectsTimestamps(RecentProjectsManagerBase mgr) { + for (Project project : ProjectManager.getInstance().getOpenProjects()) { + String path = PathUtil.toSystemIndependentName(mgr.getProjectPath(project)); + RecentProjectMetaInfo info = additionalInfo.get(path); + if (info != null) { + info.projectOpenTimestamp = System.currentTimeMillis(); + } } } } @@ -158,6 +181,7 @@ public abstract class RecentProjectsManagerBase extends RecentProjectsManager im public State getState() { synchronized (myStateLock) { myState.validateRecentProjects(); + myState.updateOpenProjectsTimestamps(this); return myState; } } @@ -166,12 +190,10 @@ public abstract class RecentProjectsManagerBase extends RecentProjectsManager im public void loadState(final State state) { state.makePathsSystemIndependent(); removeDuplicates(state); - if (state.lastPath != null && !new File(state.lastPath).exists()) { - state.lastPath = null; - } if (state.lastPath != null) { - File lastFile = new File(state.lastPath); - if (lastFile.isDirectory() && !new File(lastFile, Project.DIRECTORY_STORE_FOLDER).exists()) { + File lastFile = new File(PathUtil.toSystemDependentName(state.lastPath)); + if (!lastFile.exists() || + lastFile.isDirectory() && !new File(lastFile, Project.DIRECTORY_STORE_FOLDER).exists()) { state.lastPath = null; } } diff --git a/platform/platform-impl/src/com/intellij/ide/SystemHealthMonitor.java b/platform/platform-impl/src/com/intellij/ide/SystemHealthMonitor.java index 8a016f8a7fd1..2db5a46f4722 100644 --- a/platform/platform-impl/src/com/intellij/ide/SystemHealthMonitor.java +++ b/platform/platform-impl/src/com/intellij/ide/SystemHealthMonitor.java @@ -16,6 +16,7 @@ package com.intellij.ide; import com.intellij.concurrency.JobScheduler; +import com.intellij.diagnostic.VMOptions; import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.util.ExecUtil; import com.intellij.ide.util.PropertiesComponent; @@ -57,6 +58,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; + public class SystemHealthMonitor implements ApplicationComponent { private static final Logger LOG = Logger.getInstance(SystemHealthMonitor.class); @@ -74,12 +76,21 @@ public class SystemHealthMonitor implements ApplicationComponent { @Override public void initComponent() { checkRuntime(); + checkReservedCodeCacheSize(); checkIBus(); checkSignalBlocking(); checkLauncherScript(); startDiskSpaceMonitoring(); } + private void checkReservedCodeCacheSize() { + int minReservedCodeCacheSize = 240; + int reservedCodeCacheSize = VMOptions.readOption(VMOptions.MemoryKind.CODE_CACHE, true); + if (reservedCodeCacheSize > 0 && reservedCodeCacheSize < minReservedCodeCacheSize) { + showNotification(new KeyHyperlinkAdapter("vmoptions.warn.message"), reservedCodeCacheSize, minReservedCodeCacheSize); + } + } + private void checkRuntime() { if (StringUtil.endsWithIgnoreCase(System.getProperty("java.version", ""), "-ea")) { showNotification(new KeyHyperlinkAdapter("unsupported.jvm.ea.message")); diff --git a/platform/platform-impl/src/com/intellij/ide/actionMacro/ActionMacroManager.java b/platform/platform-impl/src/com/intellij/ide/actionMacro/ActionMacroManager.java index 0de47971e6b8..0828dd7e3062 100644 --- a/platform/platform-impl/src/com/intellij/ide/actionMacro/ActionMacroManager.java +++ b/platform/platform-impl/src/com/intellij/ide/actionMacro/ActionMacroManager.java @@ -288,7 +288,7 @@ public class ActionMacroManager implements PersistentStateComponent, Di @Override public void dispose() { - myIcon.dispose(); + Disposer.dispose(myIcon); if (myBalloon != null) { Disposer.dispose(myBalloon); } diff --git a/platform/platform-impl/src/com/intellij/ide/customize/CustomizeFeaturedPluginsStepPanel.java b/platform/platform-impl/src/com/intellij/ide/customize/CustomizeFeaturedPluginsStepPanel.java index 427fbb58a3ce..f17d70a9d5ef 100644 --- a/platform/platform-impl/src/com/intellij/ide/customize/CustomizeFeaturedPluginsStepPanel.java +++ b/platform/platform-impl/src/com/intellij/ide/customize/CustomizeFeaturedPluginsStepPanel.java @@ -19,7 +19,7 @@ import com.intellij.CommonBundle; import com.intellij.icons.AllIcons; import com.intellij.ide.plugins.IdeaPluginDescriptor; import com.intellij.ide.plugins.PluginManagerCore; -import com.intellij.internal.statistic.ideSettings.IdeInitialConfigButtonUsages; +import com.intellij.internal.statistic.customUsageCollectors.ideSettings.IdeInitialConfigButtonUsages; import com.intellij.openapi.progress.util.AbstractProgressIndicatorExBase; import com.intellij.openapi.ui.VerticalFlowLayout; import com.intellij.openapi.updateSettings.impl.PluginDownloader; diff --git a/platform/platform-impl/src/com/intellij/ide/customize/CustomizeIDEWizardDialog.java b/platform/platform-impl/src/com/intellij/ide/customize/CustomizeIDEWizardDialog.java index a901d42f4547..b086353b1604 100644 --- a/platform/platform-impl/src/com/intellij/ide/customize/CustomizeIDEWizardDialog.java +++ b/platform/platform-impl/src/com/intellij/ide/customize/CustomizeIDEWizardDialog.java @@ -17,7 +17,7 @@ package com.intellij.ide.customize; import com.intellij.ide.cloudConfig.CloudConfigProvider; import com.intellij.ide.startup.StartupActionScriptManager; -import com.intellij.internal.statistic.ideSettings.IdeInitialConfigButtonUsages; +import com.intellij.internal.statistic.customUsageCollectors.ideSettings.IdeInitialConfigButtonUsages; import com.intellij.openapi.application.ApplicationNamesInfo; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.util.text.StringUtil; diff --git a/platform/platform-impl/src/com/intellij/ide/customize/CustomizePluginsStepPanel.java b/platform/platform-impl/src/com/intellij/ide/customize/CustomizePluginsStepPanel.java index 0abeebb03cb3..33d64e2c49e1 100644 --- a/platform/platform-impl/src/com/intellij/ide/customize/CustomizePluginsStepPanel.java +++ b/platform/platform-impl/src/com/intellij/ide/customize/CustomizePluginsStepPanel.java @@ -16,7 +16,7 @@ package com.intellij.ide.customize; import com.intellij.ide.plugins.PluginManager; -import com.intellij.internal.statistic.ideSettings.IdeInitialConfigButtonUsages; +import com.intellij.internal.statistic.customUsageCollectors.ideSettings.IdeInitialConfigButtonUsages; import com.intellij.openapi.application.ApplicationNamesInfo; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.VerticalFlowLayout; diff --git a/platform/platform-impl/src/com/intellij/ide/errorTreeView/actions/TestErrorViewAction.java b/platform/platform-impl/src/com/intellij/ide/errorTreeView/actions/TestErrorViewAction.java index aa3c6c26cc7c..3ee1bda9d5f8 100644 --- a/platform/platform-impl/src/com/intellij/ide/errorTreeView/actions/TestErrorViewAction.java +++ b/platform/platform-impl/src/com/intellij/ide/errorTreeView/actions/TestErrorViewAction.java @@ -20,6 +20,7 @@ import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowId; import com.intellij.openapi.wm.ToolWindowManager; @@ -75,7 +76,7 @@ public abstract class TestErrorViewAction extends AnAction{ } } System.out.println("Expected " + (MESSAGE_COUNT + 1) + " messages;"); - view.dispose(); + Disposer.dispose(view); } }.start(); } diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/InstalledPluginsManagerMain.java b/platform/platform-impl/src/com/intellij/ide/plugins/InstalledPluginsManagerMain.java index 6b03a1fafec5..7c1b11d6be2f 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/InstalledPluginsManagerMain.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/InstalledPluginsManagerMain.java @@ -48,11 +48,11 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.ScrollPaneFactory; import com.intellij.ui.SimpleTextAttributes; import com.intellij.util.ArrayUtilRt; import com.intellij.util.Consumer; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.StatusText; import com.intellij.util.ui.TextTransferable; import org.jetbrains.annotations.NotNull; @@ -473,7 +473,7 @@ public class InstalledPluginsManagerMain extends PluginManagerMain { comp.setVerticalTextPosition(SwingConstants.CENTER); comp.setAlignmentX(Component.RIGHT_ALIGNMENT); panel.add(comp, BorderLayout.WEST); - panel.setBorder(IdeBorderFactory.createEmptyBorder(0, 2, 0, 0)); + panel.setBorder(JBUI.Borders.emptyLeft(2)); return panel; } } diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/PluginManager.java b/platform/platform-impl/src/com/intellij/ide/plugins/PluginManager.java index 1a0d2a65ff84..ec88b20ea530 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/PluginManager.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/PluginManager.java @@ -30,6 +30,7 @@ import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationNamesInfo; import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.application.impl.ApplicationInfoImpl; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.PluginId; import com.intellij.openapi.extensions.impl.PicoPluginExtensionInitializationException; @@ -110,6 +111,13 @@ public class PluginManager extends PluginManagerCore { public static void processException(Throwable t) { if (!IdeaApplication.isLoaded()) { + InstallationCorruptedException corrupted = findCause(t, InstallationCorruptedException.class); + String productName = ApplicationNamesInfo.getInstance().getFullProductName(); + if (corrupted != null) { + Main.showMessage("Corrupted Installation", corrupted.getMessage() + ". Try reinstalling " + productName + " from scratch.", true); + System.exit(Main.INSTALLATION_CORRUPTED); + } + @SuppressWarnings("ThrowableResultOfMethodCallIgnored") StartupAbortedException se = findCause(t, StartupAbortedException.class); if (se == null) se = new StartupAbortedException(t); @SuppressWarnings("ThrowableResultOfMethodCallIgnored") PluginException pe = findCause(t, PluginException.class); @@ -132,12 +140,12 @@ public class PluginManager extends PluginManagerCore { PluginConflictReporter.INSTANCE.reportConflictByClasses(conflictException.getConflictingClasses()); } - if (pluginId != null && !CORE_PLUGIN_ID.equals(pluginId.getIdString())) { + if (pluginId != null && !ApplicationInfoImpl.getShadowInstance().isEssentialPlugin(pluginId.getIdString())) { disablePlugin(pluginId.getIdString()); StringWriter message = new StringWriter(); message.append("Plugin '").append(pluginId.getIdString()).append("' failed to initialize and will be disabled. "); - message.append(" Please restart ").append(ApplicationNamesInfo.getInstance().getFullProductName()).append('.'); + message.append(" Please restart ").append(productName).append('.'); message.append("\n\n"); pe.getCause().printStackTrace(new PrintWriter(message)); diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/RepositoryHelper.java b/platform/platform-impl/src/com/intellij/ide/plugins/RepositoryHelper.java index 569cc9d3b92c..a31305ddf3ce 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/RepositoryHelper.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/RepositoryHelper.java @@ -145,7 +145,7 @@ public class RepositoryHelper { indicator.setText2(IdeBundle.message("progress.connecting.to.plugin.manager", host)); } - RequestBuilder request = HttpRequests.request(url).forceHttps(forceHttps).tuner(connection -> connection.setRequestProperty("If-None-Match", eTag)); + RequestBuilder request = HttpRequests.request(url).forceHttps(forceHttps).tuner(connection -> connection.setRequestProperty("If-None-Match", eTag)).productNameAsUserAgent(); return process(repositoryUrl, request.connect(new HttpRequests.RequestProcessor>() { @Override public List process(@NotNull HttpRequests.Request request) throws IOException { diff --git a/platform/platform-impl/src/com/intellij/ide/ui/ConfigurableOptionsTopHitProvider.java b/platform/platform-impl/src/com/intellij/ide/ui/ConfigurableOptionsTopHitProvider.java index 69bea435f671..3f2940259681 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/ConfigurableOptionsTopHitProvider.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/ConfigurableOptionsTopHitProvider.java @@ -17,6 +17,7 @@ package com.intellij.ide.ui; import com.intellij.ide.ui.search.BooleanOptionDescription; import com.intellij.ide.ui.search.OptionDescription; +import com.intellij.openapi.Disposable; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.ConfigurationException; @@ -141,7 +142,7 @@ public abstract class ConfigurableOptionsTopHitProvider extends OptionsTopHitPro return Collections.emptyList(); } - private static final class Option extends BooleanOptionDescription { + private static final class Option extends BooleanOptionDescription implements Disposable { private final Configurable myConfigurable; private final JCheckBox myCheckBox; @@ -151,6 +152,11 @@ public abstract class ConfigurableOptionsTopHitProvider extends OptionsTopHitPro myCheckBox = checkbox; } + @Override + public void dispose() { + myConfigurable.disposeUIResources(); + } + @Override public boolean isOptionEnabled() { return myCheckBox.isSelected(); diff --git a/platform/platform-impl/src/com/intellij/ide/ui/OptionsTopHitProvider.java b/platform/platform-impl/src/com/intellij/ide/ui/OptionsTopHitProvider.java index f11687ab6d92..2bd4c790334c 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/OptionsTopHitProvider.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/OptionsTopHitProvider.java @@ -18,9 +18,13 @@ package com.intellij.ide.ui; import com.intellij.ide.IdeBundle; import com.intellij.ide.SearchTopHitProvider; import com.intellij.ide.ui.search.OptionDescription; +import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationBundle; +import com.intellij.openapi.components.ComponentManager; import com.intellij.openapi.keymap.KeyMapBundle; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Key; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.codeStyle.MinusculeMatcher; import com.intellij.psi.codeStyle.NameUtil; @@ -31,6 +35,10 @@ import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +import static com.intellij.openapi.application.ApplicationManager.getApplication; +import static java.util.Collections.emptyList; /** * @author Konstantin Bulenkov @@ -39,6 +47,16 @@ public abstract class OptionsTopHitProvider implements SearchTopHitProvider { @NotNull public abstract Collection getOptions(@Nullable Project project); + private Collection getCachedOptions(@Nullable Project project) { + ComponentManager manager = project != null ? project : getApplication(); + if (manager == null) return emptyList(); + + CachedOptions cache = manager.getUserData(CachedOptions.KEY); + if (cache == null) cache = new CachedOptions(manager); + + return cache.map.computeIfAbsent(getClass(), type -> getOptions(project)); + } + @Override public final void consumeTopHits(@NonNls String pattern, Consumer collector, Project project) { if (!pattern.startsWith("#")) return; @@ -57,7 +75,7 @@ public abstract class OptionsTopHitProvider implements SearchTopHitProvider { pattern = pattern.substring(id.length()).trim().toLowerCase(); } final MinusculeMatcher matcher = NameUtil.buildMatcher("*" + pattern, NameUtil.MatchingCaseSensitivity.NONE); - for (OptionDescription option : getOptions(project)) { + for (OptionDescription option : getCachedOptions(project)) { if (matcher.matches(option.getOption())) { collector.consume(option); } @@ -90,4 +108,30 @@ public abstract class OptionsTopHitProvider implements SearchTopHitProvider { @Deprecated public interface CoveredByToggleActions { // for search everywhere only } + + private static final class CachedOptions implements Disposable { + private static final Key KEY = Key.create("cached top hits"); + private final ConcurrentHashMap, Collection> map = new ConcurrentHashMap<>(); + private final ComponentManager manager; + + private CachedOptions(ComponentManager manager) { + this.manager = manager; + Disposer.register(manager, this); + manager.putUserData(KEY, this); + } + + @Override + public void dispose() { + manager.putUserData(KEY, null); + map.values().forEach(CachedOptions::dispose); + } + + private static void dispose(Collection options) { + if (options != null) options.forEach(CachedOptions::dispose); + } + + private static void dispose(OptionDescription option) { + if (option instanceof Disposable) Disposer.dispose((Disposable)option); + } + } } diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/IdeaLaf.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/IdeaLaf.java index ce5d38cca657..4fe90deaca4f 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/IdeaLaf.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/IdeaLaf.java @@ -44,10 +44,10 @@ public final class IdeaLaf extends MetalLookAndFeel { Pair systemFont = UIUtil.getSystemFontData(); if (systemFont != null) { - LafManagerImpl.initFontDefaults(defaults, systemFont.second, new FontUIResource(systemFont.first, Font.PLAIN, systemFont.second)); + LafManagerImpl.initFontDefaults(defaults, new FontUIResource(systemFont.first, Font.PLAIN, systemFont.second)); } else { - LafManagerImpl.initFontDefaults(defaults, 11, new FontUIResource("Tahoma", Font.PLAIN, 11)); + LafManagerImpl.initFontDefaults(defaults, new FontUIResource("Tahoma", Font.PLAIN, 11)); } } diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/LafManagerImpl.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/LafManagerImpl.java index e998950237d4..631a8ef48018 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/LafManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/LafManagerImpl.java @@ -668,7 +668,7 @@ public final class LafManagerImpl extends LafManager implements PersistentStateC UISettings uiSettings = UISettings.getInstance(); if (uiSettings.getOverrideLafFonts()) { storeOriginalFontDefaults(uiDefaults); - initFontDefaults(uiDefaults, uiSettings.getFontSize(), new FontUIResource(uiSettings.getFontFace(), Font.PLAIN, uiSettings.getFontSize())); + initFontDefaults(uiDefaults, new FontUIResource(uiSettings.getFontFace(), Font.PLAIN, uiSettings.getFontSize())); JBUI.setUserScaleFactor(JBUI.getFontScale(uiSettings.getFontSize())); } else { @@ -773,10 +773,10 @@ public final class LafManagerImpl extends LafManager implements PersistentStateC } @SuppressWarnings({"HardCodedStringLiteral"}) - public static void initFontDefaults(UIDefaults defaults, int fontSize, FontUIResource uiFont) { + public static void initFontDefaults(@NotNull UIDefaults defaults, @NotNull FontUIResource uiFont) { defaults.put("Tree.ancestorInputMap", null); - FontUIResource textFont = new FontUIResource("Serif", Font.PLAIN, fontSize); - FontUIResource monoFont = new FontUIResource("Monospaced", Font.PLAIN, fontSize); + FontUIResource textFont = new FontUIResource(uiFont); + FontUIResource monoFont = new FontUIResource("Monospaced", Font.PLAIN, uiFont.getSize()); for (String fontResource : ourPatchableFontResources) { defaults.put(fontResource, uiFont); diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaLaf.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaLaf.java index 176988e93d9e..c4fc6e8ffbff 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaLaf.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaLaf.java @@ -76,7 +76,7 @@ public class DarculaLaf extends BasicLookAndFeel { private static void installMacOSXFonts(UIDefaults defaults) { final String face = "HelveticaNeue-Regular"; final FontUIResource uiFont = getFont(face, 13, Font.PLAIN); - LafManagerImpl.initFontDefaults(defaults, 13, uiFont); + LafManagerImpl.initFontDefaults(defaults, uiFont); for (Object key : new HashSet<>(defaults.keySet())) { Object value = defaults.get(key); if (value instanceof FontUIResource) { diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextBorder.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextBorder.java index 2c0f755afdc5..e9f76baa0ee4 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextBorder.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextBorder.java @@ -46,7 +46,9 @@ public class DarculaTextBorder implements Border, UIResource, ErrorBorderCapable return JBUI.insets(3, 3, 2, 2).asUIResource(); } else { - return JBUI.insets(vOffset, 7, vOffset, 7).asUIResource(); + Insets insets = JBUI.insets(vOffset, 7, vOffset, 7).asUIResource(); + TextFieldWithPopupHandlerUI.updateBorderInsets(c, insets); + return insets; } } diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextFieldUI.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextFieldUI.java index 3c4b99bdf2df..a425e9ea6483 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextFieldUI.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextFieldUI.java @@ -117,6 +117,17 @@ public class DarculaTextFieldUI extends TextFieldWithPopupHandlerUI { final int width = c.getWidth(); final int height = c.getHeight(); final Insets i = border.getBorderInsets(c); + if (!icons.isEmpty()) { + for (IconHolder holder : icons.values()) { + int space = holder.bounds.width + holder.extension.getIconGap(); + if (holder.extension.isIconBeforeText()) { + i.left -= space; + } + else { + i.right -= space; + } + } + } if (c.hasFocus()) { g.fillRoundRect(i.left - JBUI.scale(5), i.top - JBUI.scale(2), width - i.right - i.left + JBUI.scale(10), height - i.top - i.bottom + JBUI.scale(6), JBUI.scale(5), JBUI.scale(5)); } else { @@ -154,10 +165,4 @@ public class DarculaTextFieldUI extends TextFieldWithPopupHandlerUI { clearIcon.paintIcon(null, g, p.x, p.y); } } - - @Override - protected void paintSafely(Graphics g) { - paintBackground(g); - super.paintSafely(g); - } } diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/TextFieldWithPopupHandlerUI.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/TextFieldWithPopupHandlerUI.java index 6ad927022a5f..4f66882c2d9a 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/TextFieldWithPopupHandlerUI.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/TextFieldWithPopupHandlerUI.java @@ -15,59 +15,179 @@ */ package com.intellij.ide.ui.laf.darcula.ui; +import com.intellij.icons.AllIcons; import com.intellij.ide.ui.laf.darcula.DarculaUIUtil; +import com.intellij.ide.ui.laf.intellij.MacIntelliJTextFieldUI; +import com.intellij.ide.ui.laf.intellij.WinIntelliJTextFieldUI; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.registry.Registry; -import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.components.ExtendableTextField; +import com.intellij.ui.components.ExtendableTextField.Extension; +import com.intellij.util.ui.JBInsets; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.plaf.FontUIResource; +import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicTextFieldUI; import javax.swing.text.BadLocationException; +import javax.swing.text.Document; import javax.swing.text.JTextComponent; import javax.swing.text.Position; import java.awt.*; import java.awt.event.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.LinkedHashMap; +import java.util.Objects; + +import static com.intellij.openapi.util.text.StringUtil.isEmpty; +import static com.intellij.util.ui.JBUI.scale; /** * @author Konstantin Bulenkov + * @author Sergey Malenkov */ public abstract class TextFieldWithPopupHandlerUI extends BasicTextFieldUI implements Condition { + private static final String DOCUMENT = "document"; + private static final String MONOSPACED = "monospaced"; + private static final String VARIANT = "JTextField.variant"; + private static final String POPUP = "JTextField.Search.FindPopup"; + private static final String ON_CLEAR = "JTextField.Search.CancelAction"; + @SuppressWarnings("UseDPIAwareInsets") + private final Insets insets = new Insets(0, 0, 0, 0); + protected final LinkedHashMap icons = new LinkedHashMap<>(); protected final JTextField myTextField; - private MyMouseMotionAdapter myMyMouseMotionAdapter; - private MyMouseAdapter myMouseAdapter; - private FocusAdapter myFocusAdapter; + private final Handler handler = new Handler(); + private boolean monospaced; + private Object variant; + private int cursor; + + /** + * @param insets a border insets without a space for icons + * @see javax.swing.border.Border#getBorderInsets + */ + public static void updateBorderInsets(Component c, Insets insets) { + if (c instanceof JTextComponent) { + JTextComponent component = (JTextComponent)c; + if (component.getUI() instanceof TextFieldWithPopupHandlerUI) { + TextFieldWithPopupHandlerUI ui = (TextFieldWithPopupHandlerUI)component.getUI(); + insets.top += ui.insets.top; + insets.left += ui.insets.left; + insets.right += ui.insets.right; + insets.bottom += ui.insets.bottom; + } + } + } public TextFieldWithPopupHandlerUI(JTextField textField) { myTextField = textField; - installListeners(); } + /** + * @return a search icon in one of the four states or {@code null} to hide it + */ + protected Icon getSearchIcon(boolean hovered, boolean clickable) { + return AllIcons.Actions.Search; + } + + /** + * @return a preferred space to paint the search icon + */ + protected int getSearchIconPreferredSpace() { + Icon icon = getSearchIcon(true, true); + return icon == null ? 0 : icon.getIconWidth() + getSearchIconGap(); + } + + /** + * @return a gap between the search icon and the editable area + */ + protected int getSearchIconGap() { + return scale(2); + } + + /** + * @return a clear icon in one of the four states or {@code null} to hide it + */ + protected Icon getClearIcon(boolean hovered, boolean clickable) { + return !clickable ? null : hovered ? AllIcons.Actions.Clean : AllIcons.Actions.CleanLight; + } + + /** + * @return a preferred space to paint the clear icon + */ + protected int getClearIconPreferredSpace() { + Icon icon = getClearIcon(true, true); + return icon == null ? 0 : icon.getIconWidth() + getClearIconGap(); + } + + /** + * @return a gap between the clear icon and the editable area + */ + protected int getClearIconGap() { + return scale(2); + } + + /** + * @return {@code true} if component exists and contains non-empty string + */ protected boolean hasText() { JTextComponent component = getComponent(); - return (component != null) && !StringUtil.isEmpty(component.getText()); + return (component != null) && !isEmpty(component.getText()); } - protected abstract SearchAction getActionUnder(@NotNull Point p); + protected void updateIconsLayout(Rectangle bounds) { + for (IconHolder holder : icons.values()) { + int gap = holder.extension.getIconGap(); + if (holder.extension.isIconBeforeText()) { + holder.bounds.x = bounds.x; + bounds.width -= holder.bounds.width + gap; + bounds.x += holder.bounds.width + gap; + } + else { + bounds.width -= holder.bounds.width + gap; + holder.bounds.x = bounds.x + bounds.width + gap; + } + holder.bounds.y = bounds.y + (bounds.height - holder.bounds.height) / 2; + } + } - protected abstract void showSearchPopup(); + protected SearchAction getActionUnder(@NotNull Point p) { + return null; + } + protected void showSearchPopup() { + } + + /** + * Adds listeners to the current text component and sets its variant. + */ + @Override protected void installListeners() { - final TextFieldWithPopupHandlerUI ui = this; - myFocusAdapter = new MyFocusAdapter(); - myTextField.addFocusListener(myFocusAdapter); - myMyMouseMotionAdapter = new MyMouseMotionAdapter(ui); - myTextField.addMouseMotionListener(myMyMouseMotionAdapter); - myMouseAdapter = new MyMouseAdapter(ui); - myTextField.addMouseListener(myMouseAdapter); + JTextComponent component = getComponent(); + handler.installListener(component.getDocument()); + component.addPropertyChangeListener(handler); + component.addMouseMotionListener(handler); + component.addMouseListener(handler); + component.addFocusListener(handler); + setVariant(component.getClientProperty(VARIANT)); + setMonospaced(component.getClientProperty(MONOSPACED)); } + /** + * Removes all installed listeners from the current text component. + */ @Override protected void uninstallListeners() { - myTextField.removeFocusListener(myFocusAdapter); - myTextField.removeMouseMotionListener(myMyMouseMotionAdapter); - myTextField.removeMouseListener(myMouseAdapter); + JTextComponent component = getComponent(); + component.removeFocusListener(handler); + component.removeMouseListener(handler); + component.removeMouseMotionListener(handler); + component.removePropertyChangeListener(handler); + handler.uninstallListener(component.getDocument()); } @Override @@ -84,7 +204,7 @@ public abstract class TextFieldWithPopupHandlerUI extends BasicTextFieldUI imple if (getActionUnder(me.getPoint()) != null) { if (me.getID() == MouseEvent.MOUSE_CLICKED) { //noinspection SSBasedInspection - SwingUtilities.invokeLater(() -> myMouseAdapter.mouseClicked(me)); + SwingUtilities.invokeLater(() -> handler.mouseClicked(me)); } return true; } @@ -102,7 +222,7 @@ public abstract class TextFieldWithPopupHandlerUI extends BasicTextFieldUI imple @Nullable public static AbstractAction getNewLineAction(Component c) { - if ( !isSearchField(c) || !Registry.is("ide.find.show.add.newline.hint")) return null; + if (!isSearchField(c) || !Registry.is("ide.find.show.add.newline.hint")) return null; Object action = ((JTextField)c).getClientProperty("JTextField.Search.NewLineAction"); return action instanceof AbstractAction ? (AbstractAction)action : null; } @@ -111,45 +231,96 @@ public abstract class TextFieldWithPopupHandlerUI extends BasicTextFieldUI imple POPUP, CLEAR, NEWLINE } - private class MyMouseMotionAdapter extends MouseMotionAdapter { - private final TextFieldWithPopupHandlerUI myUi; + /** + * Default handler for mouse moved, mouse clicked, property changed and document modified. + */ + private final class Handler extends MouseAdapter implements DocumentListener, FocusListener, PropertyChangeListener { + /** + * Starts listening changes in the specified document. + */ + private void installListener(Document document) { + if (document != null) document.addDocumentListener(this); + } - public MyMouseMotionAdapter(TextFieldWithPopupHandlerUI ui) {myUi = ui;} + /** + * Stops listening changes in the specified document. + */ + private void uninstallListener(Document document) { + if (document != null) document.removeDocumentListener(this); + } + + @Override + public void propertyChange(PropertyChangeEvent event) { + if (DOCUMENT.equals(event.getPropertyName())) { + if (event.getOldValue() instanceof Document) uninstallListener((Document)event.getOldValue()); + if (event.getNewValue() instanceof Document) installListener((Document)event.getNewValue()); + } + else if (MONOSPACED.equals(event.getPropertyName())) { + setMonospaced(event.getNewValue()); + } + else if (VARIANT.equals(event.getPropertyName())) { + setVariant(event.getNewValue()); + } + else if (POPUP.equals(event.getPropertyName())) { + updateIcon(icons.get("search")); + } + } + + @Override + public void focusGained(FocusEvent event) { + repaint(false); + } + + @Override + public void focusLost(FocusEvent event) { + repaint(false); + } + + @Override + public void insertUpdate(DocumentEvent event) { + changedUpdate(event); + } + + @Override + public void removeUpdate(DocumentEvent event) { + changedUpdate(event); + } + + @Override + public void changedUpdate(DocumentEvent event) { + updateIcon(icons.get("clear")); + } @Override public void mouseMoved(MouseEvent e) { - if (myUi.getComponent() != null && isSearchField(myTextField)) { - SearchAction action = myUi.getActionUnder(e.getPoint()); + if (!icons.isEmpty()) { + handleMouse(e, false); + } + else if (getComponent() != null && isSearchField(myTextField)) { + SearchAction action = getActionUnder(e.getPoint()); if (action == SearchAction.POPUP && !isSearchFieldWithHistoryPopup(myTextField)) { action = null; } - if (action != null) { - myTextField.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - } else { - myTextField.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); - } + setCursor(action != null ? Cursor.HAND_CURSOR : Cursor.TEXT_CURSOR); } } - } - - private class MyMouseAdapter extends MouseAdapter { - private final TextFieldWithPopupHandlerUI myUi; - - public MyMouseAdapter(TextFieldWithPopupHandlerUI ui) {myUi = ui;} @Override public void mouseClicked(MouseEvent e) { - if (isSearchField(myTextField)) { - final SearchAction action = myUi.getActionUnder(e.getPoint()); + if (!icons.isEmpty()) { + handleMouse(e, true); + } + else if (isSearchField(myTextField)) { + final SearchAction action = getActionUnder(e.getPoint()); if (action != null) { switch (action) { case POPUP: - myUi.showSearchPopup(); + showSearchPopup(); break; case CLEAR: Object listener = myTextField.getClientProperty("JTextField.Search.CancelAction"); if (listener instanceof ActionListener) { - ((ActionListener)listener).actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "action")); + ((ActionListener)listener).actionPerformed(new ActionEvent(myTextField, ActionEvent.ACTION_PERFORMED, "action")); } myTextField.setText(""); break; @@ -167,15 +338,282 @@ public abstract class TextFieldWithPopupHandlerUI extends BasicTextFieldUI imple } } - private class MyFocusAdapter extends FocusAdapter { + @Override + public String getToolTipText(JTextComponent component, Point point) { + if (!icons.isEmpty() && component != null && component.isEnabled()) { + for (IconHolder holder : icons.values()) { + if (holder.bounds.contains(point)) { + return holder.extension.getTooltip(); + } + } + } + return super.getToolTipText(component, point); + } + + @Override + public Dimension getMinimumSize(JComponent c) { + Dimension size = super.getMinimumSize(c); + if (size != null) updatePreferredSize(size); + return size; + } + + @Override + public Dimension getPreferredSize(JComponent c) { + Dimension size = super.getPreferredSize(c); + if (size != null) updatePreferredSize(size); + return size; + } + + protected void updatePreferredSize(Dimension size) { + size.height = Math.max(size.height, getMinimumHeight()); + JBInsets.addTo(size, insets); + } + + protected int getMinimumHeight() { + return 0; + } + + @Override + protected Rectangle getVisibleEditorRect() { + Rectangle bounds = super.getVisibleEditorRect(); + if (bounds != null && !icons.isEmpty()) { + JBInsets.addTo(bounds, insets); + updateIconsLayout(bounds); + } + return bounds; + } + + /** + * Always calls the {@link #paintBackground} method before painting the current text component, + * and then paints visible icons if needed. + * + * @see #getVisibleEditorRect + */ + @Override + protected void paintSafely(Graphics g) { + JTextComponent component = getComponent(); + if (!component.isOpaque()) paintBackground(g); + Shape clip = g.getClip(); + super.paintSafely(g); + if (!icons.isEmpty()) { + g.setClip(clip); + for (IconHolder holder : icons.values()) { + if (holder.icon != null) { + holder.icon.paintIcon(component, g, holder.bounds.x, holder.bounds.y); + } + } + } + } + + /** + * Notifies a repaint manager to repaint the current text component later. + * + * @param invalid {@code true} if needed to revalidate before painting. + */ + private void repaint(boolean invalid) { + JTextComponent component = getComponent(); + if (component != null) { + if (invalid) component.revalidate(); + component.repaint(); + } + } + + private void updateIcon(IconHolder holder) { + if (holder != null) { + Icon icon = holder.extension.getIcon(holder.hovered); + if (holder.icon != icon) repaint(holder.setIcon(icon)); + } + } + + private void handleMouse(MouseEvent event, boolean run) { + JTextComponent component = getComponent(); + if (component != null) { + boolean invalid = false; + boolean repaint = false; + IconHolder result = null; + for (IconHolder holder : icons.values()) { + holder.hovered = component.isEnabled() && holder.bounds.contains(event.getX(), event.getY()); + if (holder.hovered) result = holder; + Icon icon = holder.extension.getIcon(holder.hovered); + if (holder.icon != icon) { + if (holder.setIcon(icon)) invalid = true; + repaint = true; + } + } + if (repaint) repaint(invalid); + Runnable action = result == null ? null : result.extension.getActionOnClick(); + if (action == null) { + setCursor(Cursor.TEXT_CURSOR); + } + else { + setCursor(Cursor.HAND_CURSOR); + if (run) { + action.run(); + event.consume(); + } + } + } + } + + private void setCursor(int cursor) { + if (this.cursor != cursor) { + this.cursor = cursor; // do not update cursor every time + JTextComponent component = getComponent(); + if (component != null) component.setCursor(Cursor.getPredefinedCursor(cursor)); + } + } + + private void setVariant(Object variant) { + if (!Objects.equals(this.variant, variant)) { + this.variant = variant; + + icons.clear(); + insets.set(0, 0, 0, 0); + if (ExtendableTextField.VARIANT.equals(variant)) { + JTextComponent component = getComponent(); + if (component instanceof ExtendableTextField) { + ExtendableTextField field = (ExtendableTextField)component; + for (Extension extension : field.getExtensions()) { + if (extension != null) addExtension(extension); + } + } + } + else if ("search".equals(variant) && (this instanceof MacIntelliJTextFieldUI || this instanceof WinIntelliJTextFieldUI)) { + addExtension(new SearchExtension()); + addExtension(new ClearExtension()); + } + } + } + + private void addExtension(Extension extension) { + icons.put(extension.toString(), new IconHolder(extension)); + if (extension.isIconBeforeText()) { + insets.left = extension.getPreferredSpace(); + } + else { + insets.right = extension.getPreferredSpace(); + } + } + + private void setMonospaced(Object value) { + boolean monospaced = Boolean.TRUE.equals(value); + if (this.monospaced != monospaced) { + this.monospaced = monospaced; + JTextComponent component = getComponent(); + if (component != null) { + Font font = component.getFont(); + if (font == null || font instanceof UIResource) { + font = UIManager.getFont(getPropertyPrefix() + ".font"); + component.setFont(!monospaced ? font : new FontUIResource("monospaced", font.getStyle(), font.getSize())); + } + } + } + } + + + public static final class IconHolder { + public final Rectangle bounds = new Rectangle(); + public final Extension extension; + public boolean hovered; + public Icon icon; + + private IconHolder(Extension extension) { + this.extension = extension; + if (extension instanceof SearchExtension) { + SearchExtension se = (SearchExtension)extension; + se.bounds = bounds; + } + setIcon(extension.getIcon(false)); + } + + private boolean setIcon(Icon icon) { + this.icon = icon; + int width = icon == null ? 0 : icon.getIconWidth(); + int height = icon == null ? 0 : icon.getIconHeight(); + if (bounds.width == width && bounds.height == height) return false; + bounds.width = width; + bounds.height = height; + return true; + } + + public boolean isClickable() { + return null != extension.getActionOnClick(); + } + } + + + private final class SearchExtension implements Extension { + private Rectangle bounds; // should be bound to IconHandler#bounds + @Override - public void focusGained(FocusEvent e) { - myTextField.repaint(); + public Icon getIcon(boolean hovered) { + return getSearchIcon(hovered, null != getActionOnClick()); } @Override - public void focusLost(FocusEvent e) { - myTextField.repaint(); + public int getIconGap() { + return getSearchIconGap(); + } + + @Override + public int getPreferredSpace() { + return getSearchIconPreferredSpace(); + } + + @Override + public boolean isIconBeforeText() { + return true; + } + + @Override + public Runnable getActionOnClick() { + JTextComponent component = getComponent(); + Object property = component == null ? null : component.getClientProperty(POPUP); + JPopupMenu popup = property instanceof JPopupMenu ? (JPopupMenu)property : null; + return popup == null ? null : () -> { + Rectangle editor = getVisibleEditorRect(); + if (editor != null) popup.show(component, bounds.x, editor.y + editor.height); + }; + } + + @Override + public String toString() { + return "search"; + } + } + + private class ClearExtension implements Extension { + @Override + public Icon getIcon(boolean hovered) { + return getClearIcon(hovered, hasText()); + } + + @Override + public int getIconGap() { + return getClearIconGap(); + } + + @Override + public int getPreferredSpace() { + return getClearIconPreferredSpace(); + } + + @Override + public Runnable getActionOnClick() { + JTextComponent component = getComponent(); + return component == null ? null : () -> { + component.setText(null); + Object property = component.getClientProperty(ON_CLEAR); + if (property instanceof ActionListener) { + ActionListener listener = (ActionListener)property; + listener.actionPerformed(new ActionEvent(component, ActionEvent.ACTION_PERFORMED, "clear")); + } + }; + } + + @Override + public String toString() { + return "clear"; } } } diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJTextBorder.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJTextBorder.java index 7649a6fb64d0..6d29ddd093cc 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJTextBorder.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJTextBorder.java @@ -19,6 +19,7 @@ import com.intellij.ide.ui.laf.darcula.DarculaUIUtil; import com.intellij.ide.ui.laf.darcula.ui.TextFieldWithPopupHandlerUI; import com.intellij.openapi.ui.ErrorBorderCapable; import com.intellij.openapi.util.registry.Registry; +import com.intellij.ui.ColorPanel; import com.intellij.ui.Gray; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; @@ -37,7 +38,12 @@ import java.awt.geom.Rectangle2D; public class MacIntelliJTextBorder implements Border, UIResource, ErrorBorderCapable { @Override public Insets getBorderInsets(Component c) { - return JBUI.insets(3, 6).asUIResource(); + if (c instanceof JTextField && c.getParent() instanceof ColorPanel) { + return JBUI.insets(3, 3, 2, 2).asUIResource(); + } + Insets insets = JBUI.insets(3, 8).asUIResource(); + TextFieldWithPopupHandlerUI.updateBorderInsets(c, insets); + return insets; } @Override diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJTextFieldUI.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJTextFieldUI.java index 2c168afac37b..bc3642db7c44 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJTextFieldUI.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/MacIntelliJTextFieldUI.java @@ -17,24 +17,21 @@ package com.intellij.ide.ui.laf.intellij; import com.intellij.ide.ui.laf.darcula.DarculaUIUtil; import com.intellij.ide.ui.laf.darcula.ui.TextFieldWithPopupHandlerUI; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.Gray; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.MacUIUtil; import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NotNull; import javax.swing.*; import javax.swing.plaf.ComponentUI; import javax.swing.text.JTextComponent; import java.awt.*; -import java.awt.geom.Area; import java.awt.geom.Path2D; -import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; /** * @author Konstantin Bulenkov + * @author Sergey Malenkov */ public class MacIntelliJTextFieldUI extends TextFieldWithPopupHandlerUI { @@ -47,86 +44,53 @@ public class MacIntelliJTextFieldUI extends TextFieldWithPopupHandlerUI { return new MacIntelliJTextFieldUI((JTextField)c); } - public void showSearchPopup() { - final Object value = myTextField.getClientProperty("JTextField.Search.FindPopup"); - final JTextComponent editor = getComponent(); - if (editor != null && value instanceof JPopupMenu) { - final JPopupMenu popup = (JPopupMenu)value; - popup.show(editor, getSearchIconCoord().x, editor.getHeight()); - } + @Override + protected Icon getSearchIcon(boolean hovered, boolean clickable) { + return MacIntelliJIconCache.getIcon(clickable ? "searchFieldWithHistory" : "search"); } @Override - public String getToolTipText(JTextComponent t, Point pt) { - if (getActionUnder(pt) == SearchAction.NEWLINE) { - AbstractAction action = getNewLineAction(t); - if (action != null) return (String)action.getValue(Action.SHORT_DESCRIPTION); - } - return super.getToolTipText(t, pt); - } - - public SearchAction getActionUnder(@NotNull Point p) { - int off = JBUI.scale(8); - Point point = new Point(p.x - off, p.y - off); - if (point.distance(getSearchIconCoord()) <= off) { - return SearchAction.POPUP; - } - if (hasText() && point.distance(getClearIconCoord()) <= off) { - return SearchAction.CLEAR; - } - if (getNewLineAction(myTextField) != null && point.distance(getAddNewLineIconCoord()) <= off) { - return SearchAction.NEWLINE; - } - return null; - } - - protected Rectangle getDrawingRect() { - return new Rectangle(0, (myTextField.getHeight() - 28) / 2, myTextField.getWidth(), myTextField.getHeight()); - } - - private static Icon getSearchIcon(Component c) { - return MacIntelliJIconCache.getIcon(isSearchFieldWithHistoryPopup(c) ? "searchFieldWithHistory" : "search"); - } - - protected Point getSearchIconCoord() { - final Rectangle r = getDrawingRect(); - Icon icon = getSearchIcon(myTextField); - return new Point(r.x + (hasText() || myTextField.hasFocus() || isSearchFieldWithHistoryPopup(myTextField) - ? JBUI.scale(8) - : (r.width - icon.getIconWidth()) / 2), - r.y + (r.height - icon.getIconHeight()) / 2); - } - - protected Point getClearIconCoord() { - Rectangle r = getDrawingRect(); - Icon icon = MacIntelliJIconCache.getIcon("searchFieldClear"); - return new Point(r.x + r.width - icon.getIconWidth() - JBUI.scale(6), r.y + (r.height - icon.getIconHeight()) / 2); - } - - protected Point getAddNewLineIconCoord() { - Point point = getClearIconCoord(); - if (!StringUtil.isEmpty(myTextField.getText())) point.x -= JBUI.scale(16) + JBUI.scale(8); - return point; + protected Icon getClearIcon(boolean hovered, boolean clickable) { + return !clickable ? null : MacIntelliJIconCache.getIcon("searchFieldClear"); } @Override - protected void paintBackground(Graphics graphics) { - Graphics2D g = (Graphics2D)graphics; - final JTextComponent c = getComponent(); - final Container parent = c.getParent(); - final Rectangle r = getDrawingRect(); - if (c.isOpaque() && parent != null) { - g.setColor(parent.getBackground()); - g.fillRect(0, 0, c.getWidth(), c.getHeight()); - } + protected int getMinimumHeight() { + return JBUI.scale(28); + } - if (isSearchField(c)) { - paintSearchField(g, c, r); - } - else { - if (c.getBorder() instanceof MacIntelliJTextBorder) { - g.setColor(c.getBackground()); - g.fillRect(JBUI.scale(3), JBUI.scale(3), c.getWidth() - JBUI.scale(6), c.getHeight() - JBUI.scale(6)); + @Override + protected int getClearIconPreferredSpace() { + return super.getClearIconPreferredSpace() - getClearIconGap(); + } + + @Override + protected void updateIconsLayout(Rectangle bounds) { + super.updateIconsLayout(bounds); + JTextComponent component = getComponent(); + if (component == null || component.hasFocus()) return; + IconHolder clear = icons.get("clear"); + if (clear == null || clear.icon != null) return; + IconHolder search = icons.get("search"); + if (search == null || search.icon == null || search.isClickable()) return; + search.bounds.x = bounds.x + (bounds.width - search.bounds.width) / 2; + } + + @Override + protected void paintBackground(Graphics g) { + JTextComponent component = getComponent(); + if (component != null) { + Container parent = component.getParent(); + if (parent != null && component.isOpaque()) { + g.setColor(parent.getBackground()); + g.fillRect(0, 0, component.getWidth(), component.getHeight()); + } + if (isSearchField(component)) { + paintSearchField((Graphics2D)g, component, new Rectangle(component.getWidth(), component.getHeight())); + } + else if (component.getBorder() instanceof MacIntelliJTextBorder) { + g.setColor(component.getBackground()); + g.fillRect(JBUI.scale(3), JBUI.scale(3), component.getWidth() - JBUI.scale(6), component.getHeight() - JBUI.scale(6)); } else { super.paintBackground(g); @@ -134,33 +98,7 @@ public class MacIntelliJTextFieldUI extends TextFieldWithPopupHandlerUI { } } - @NotNull - @Override - public Dimension getPreferredSize(JComponent c) { - Dimension size = super.getPreferredSize(c); - return new Dimension(size.width + getIconsWidth(c), Math.max(28, size.height)); - } - - @Override - public Dimension getMinimumSize(JComponent c) { - Dimension minimumSize = super.getMinimumSize(c); - return new Dimension(minimumSize.width + getIconsWidth(c), minimumSize.height); - } - - private int getIconsWidth(JComponent c) { - int width = 0; - if (isSearchField(c)) { - Icon label = getSearchIcon(c); - width += label.getIconWidth(); - if (hasText()) { - Icon clearIcon = MacIntelliJIconCache.getIcon("searchFieldClear"); - width += clearIcon.getIconWidth() + 3; - } - } - return width; - } - - protected void paintSearchField(Graphics2D g, JTextComponent c, Rectangle r) { + private static void paintSearchField(Graphics2D g, JTextComponent c, Rectangle r) { Graphics2D g2 = (Graphics2D)g.create(); try { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); @@ -191,68 +129,11 @@ public class MacIntelliJTextFieldUI extends TextFieldWithPopupHandlerUI { } g2.translate(-r.x, -r.y); - - boolean withHistoryPopup = isSearchFieldWithHistoryPopup(c); - Icon label = getSearchIcon(c); - boolean isEmpty = !hasText(); - Point point = getSearchIconCoord(); - if (isEmpty && !c.hasFocus() && !withHistoryPopup) { - label.paintIcon(c, g2, point.x, point.y); - } else { - Graphics ig = g2.create(0, 0, c.getWidth(), c.getHeight()); - - Area area = new Area(new Rectangle2D.Double(point.x, point.y, isEmpty ? label.getIconWidth() : 16, label.getIconHeight())); - area.intersect(new Area(ig.getClip())); - ig.setClip(area); - label.paintIcon(c, ig, point.x, point.y); - ig.dispose(); - } - - if (!isEmpty) { - Point ic = getClearIconCoord(); - MacIntelliJIconCache.getIcon("searchFieldClear").paintIcon(c, g2, ic.x, ic.y); - } - - AbstractAction newLineAction = getNewLineAction(c); - if (newLineAction != null) { - Icon newLineIcon = (Icon)newLineAction.getValue(Action.SMALL_ICON); - if (newLineIcon != null) { - newLineIcon.paintIcon(c, g2, getAddNewLineIconCoord().x, r.y); - } - } } finally { g2.dispose(); } } - - @Override - protected Rectangle getVisibleEditorRect() { - Rectangle rect = super.getVisibleEditorRect(); - if (rect != null) { - if (isSearchField(myTextField)) { - int extraOffset = isSearchFieldWithHistoryPopup(myTextField) ? 3 : 0; - rect.width -= 36 + extraOffset; - if (getNewLineAction(myTextField) != null) rect.width -= 24; - rect.x += 19 + extraOffset; - if (rect.height % 2 == 1) { - rect.y += 1; - } - } - else { - rect.x += 2; - rect.width -= 4; - } - } - return rect; - } - - @Override - protected void paintSafely(Graphics g) { - paintBackground(g); - super.paintSafely(g); - } - public static void paintAquaSearchFocusRing(Graphics2D g, Rectangle r, Component c) { Graphics2D g2 = (Graphics2D)g.create(); try { diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/WinIntelliJTextBorder.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/WinIntelliJTextBorder.java index 653a11ed7b94..dd1c936e3e2b 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/WinIntelliJTextBorder.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/WinIntelliJTextBorder.java @@ -37,15 +37,12 @@ import static com.intellij.ide.ui.laf.intellij.WinIntelliJTextFieldUI.adjustInWr public class WinIntelliJTextBorder extends DarculaTextBorder { @Override public Insets getBorderInsets(Component c) { - if (isSearchFieldWithHistoryPopup(c)) { - return JBUI.insets(4, 27, 4, 24).asUIResource(); - } else if (TextFieldWithPopupHandlerUI.isSearchField(c)) { - return JBUI.insets(4, 21, 4, 24).asUIResource(); - } else if (c instanceof JTextField && c.getParent() instanceof ColorPanel) { + if (c instanceof JTextField && c.getParent() instanceof ColorPanel) { return JBUI.insets(3, 3, 2, 2).asUIResource(); - } else { - return JBUI.insets(4, 5).asUIResource(); } + Insets insets = JBUI.insets(4, 5).asUIResource(); + TextFieldWithPopupHandlerUI.updateBorderInsets(c, insets); + return insets; } @Override diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/WinIntelliJTextFieldUI.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/WinIntelliJTextFieldUI.java index 4d6534053635..0c6c34bd586e 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/WinIntelliJTextFieldUI.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/WinIntelliJTextFieldUI.java @@ -18,6 +18,7 @@ package com.intellij.ide.ui.laf.intellij; import com.intellij.ide.ui.laf.darcula.DarculaUIUtil; import com.intellij.ide.ui.laf.darcula.ui.DarculaEditorTextFieldBorder; import com.intellij.ide.ui.laf.darcula.ui.DarculaTextFieldUI; +import com.intellij.ide.ui.laf.darcula.ui.TextFieldWithPopupHandlerUI; import com.intellij.openapi.util.IconLoader; import com.intellij.ui.components.panels.Wrapper; import com.intellij.util.ui.JBInsets; @@ -33,7 +34,7 @@ import java.awt.event.MouseListener; /** * @author Konstantin Bulenkov */ -public class WinIntelliJTextFieldUI extends DarculaTextFieldUI { +public class WinIntelliJTextFieldUI extends TextFieldWithPopupHandlerUI { public static final String HOVER_PROPERTY = "JTextField.hover"; private MouseListener hoverListener; @@ -42,21 +43,23 @@ public class WinIntelliJTextFieldUI extends DarculaTextFieldUI { super(textField); } - @SuppressWarnings({"MethodOverridesStaticMethodOfSuperclass", "UnusedDeclaration"}) + @SuppressWarnings({"MethodOverridesStaticMethodOfSuperclass", "UnusedDeclaration"}) public static ComponentUI createUI(JComponent c) { return new WinIntelliJTextFieldUI((JTextField)c); } - @Override public void installListeners() { + @Override + public void installListeners() { super.installListeners(); - hoverListener = new DarculaUIUtil.MouseHoverPropertyTrigger(myTextField, HOVER_PROPERTY); - myTextField.addMouseListener(hoverListener); + hoverListener = new DarculaUIUtil.MouseHoverPropertyTrigger(getComponent(), HOVER_PROPERTY); + getComponent().addMouseListener(hoverListener); } - @Override public void uninstallListeners() { + @Override + public void uninstallListeners() { super.uninstallListeners(); if (hoverListener != null) { - myTextField.removeMouseListener(hoverListener); + getComponent().removeMouseListener(hoverListener); } } @@ -78,9 +81,6 @@ public class WinIntelliJTextFieldUI extends DarculaTextFieldUI { } paintTextFieldBackground(c, g2); - if (isSearchField(c)) { - paintSearchField(g2, c, getDrawingRect()); - } } finally { g2.dispose(); } @@ -110,36 +110,49 @@ public class WinIntelliJTextFieldUI extends DarculaTextFieldUI { } } - @Override public Dimension getPreferredSize(JComponent c) { - Dimension size = super.getPreferredSize(c); - int maxHeight = JBUI.scale(DarculaEditorTextFieldBorder.isComboBoxEditor(c) ? 18 : 24); - size.height = Math.max(maxHeight, size.height); - return size; + @Override + protected int getMinimumHeight() { + return JBUI.scale(DarculaEditorTextFieldBorder.isComboBoxEditor(getComponent()) ? 18 : 24); } - @Override protected void paintSearchField(Graphics2D g, JTextComponent c, Rectangle r) { - Icon searchIcon = isSearchFieldWithHistoryPopup(c) ? - UIManager.getIcon("TextField.darcula.searchWithHistory.icon") : - UIManager.getIcon("TextField.darcula.search.icon"); - if (searchIcon == null) { - searchIcon = IconLoader.findIcon("/com/intellij/ide/ui/laf/icons/search.png", DarculaTextFieldUI.class, true); - } + @Override + protected Icon getSearchIcon(boolean hovered, boolean clickable) { + Icon icon = UIManager.getIcon(clickable ? "TextField.darcula.searchWithHistory.icon" : "TextField.darcula.search.icon"); + if (icon != null && clickable) { + return new Icon() { + @Override + public void paintIcon(Component c, Graphics g, int x, int y) { + icon.paintIcon(c, g, x, y + JBUI.scale(1)); + } - if (searchIcon != null) { - int yOffset = isSearchFieldWithHistoryPopup(c) ? JBUI.scale(1) : 0; - searchIcon.paintIcon(c, g, JBUI.scale(5), (c.getHeight() - searchIcon.getIconHeight()) / 2 + yOffset); - } + @Override + public int getIconWidth() { + return icon.getIconWidth() + JBUI.scale(4); + } - if (hasText()) { - Icon clearIcon = UIManager.getIcon("TextField.darcula.clear.icon"); - if (clearIcon == null) { - clearIcon = IconLoader.findIcon("/com/intellij/ide/ui/laf/icons/clear.png", DarculaTextFieldUI.class, true); - } - - if (clearIcon != null) { - clearIcon.paintIcon(c, g, c.getWidth() - clearIcon.getIconWidth() - JBUI.scale(5), - (c.getHeight() - clearIcon.getIconHeight()) / 2); - } + @Override + public int getIconHeight() { + return icon.getIconHeight(); + } + }; } + return icon != null ? icon : IconLoader.findIcon("/com/intellij/ide/ui/laf/icons/search.png", DarculaTextFieldUI.class, true); + } + + @Override + protected int getSearchIconGap() { + return 0; + } + + @Override + protected Icon getClearIcon(boolean hovered, boolean clickable) { + if (!clickable) return null; + Icon icon = UIManager.getIcon("TextField.darcula.clear.icon"); + return icon != null ? icon : IconLoader.findIcon("/com/intellij/ide/ui/laf/icons/clear.png", DarculaTextFieldUI.class, true); + } + + @Override + protected int getClearIconGap() { + return JBUI.scale(3); } } diff --git a/platform/platform-impl/src/com/intellij/internal/focus/FocusTracesAction.java b/platform/platform-impl/src/com/intellij/internal/focus/FocusTracesAction.java index 99c837be9c44..de402310aefa 100644 --- a/platform/platform-impl/src/com/intellij/internal/focus/FocusTracesAction.java +++ b/platform/platform-impl/src/com/intellij/internal/focus/FocusTracesAction.java @@ -35,6 +35,10 @@ public class FocusTracesAction extends AnAction implements DumbAware { private static boolean myActive = false; private AWTEventListener myFocusTracker; + public FocusTracesAction() { + setEnabledInModalContext(true); + } + public static boolean isActive() { return myActive; } diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/AbstractApplicationUsagesCollector.java b/platform/platform-impl/src/com/intellij/internal/statistic/AbstractProjectsUsagesCollector.java similarity index 53% rename from platform/platform-impl/src/com/intellij/internal/statistic/AbstractApplicationUsagesCollector.java rename to platform/platform-impl/src/com/intellij/internal/statistic/AbstractProjectsUsagesCollector.java index 945eeea1f097..52dfa633a47e 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/AbstractApplicationUsagesCollector.java +++ b/platform/platform-impl/src/com/intellij/internal/statistic/AbstractProjectsUsagesCollector.java @@ -17,23 +17,38 @@ package com.intellij.internal.statistic; import com.intellij.internal.statistic.beans.UsageDescriptor; import com.intellij.internal.statistic.persistence.ApplicationStatisticsPersistence; -import com.intellij.internal.statistic.persistence.ApplicationStatisticsPersistenceComponent; import com.intellij.internal.statistic.persistence.CollectedUsages; import com.intellij.internal.statistic.persistence.UsageStatisticsPersistenceComponent; +import com.intellij.internal.statistic.utils.StatisticsUtilKt; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.util.containers.ObjectIntHashMap; -import gnu.trove.THashSet; -import gnu.trove.TObjectIntProcedure; import org.jetbrains.annotations.NotNull; -import java.util.Collections; import java.util.Set; -public abstract class AbstractApplicationUsagesCollector extends UsagesCollector { - private static final Logger LOG = Logger.getInstance(AbstractApplicationUsagesCollector.class); +// Collects(summarizes) project-level usages. +// Statistics is regularly sent with some period of time (see com.intellij.internal.statistic.configurable.SendPeriod). +// In this period the user can open/close N projects which are not available at the "special send statistics" time. +// AbstractProjectsUsagesCollector persists the statistics data from opened/closed projects +// and summarizes collected results when data are requested. +// +// Example for groupId="my-team-lead-wants-something": +// 1. project A: key_1= a1, key_2=b1, key_3=c1 // this project was opened and closed (not available just now) +// 2. project B: key_1= a2, key_2=b2 // this project was opened and closed (not available just now) +// 3. project C: key_1= a3, key_4=c3 // this project is open just now +// if "send statistics" action is invoked this UsageCollector returns : +// groupId="my-team-lead-wants-something": key_1=(a1+a2+a3), key_2=(b1+b2), key_3=c1, key_4=c3 +// +public abstract class AbstractProjectsUsagesCollector extends UsagesCollector { - public void persistProjectUsages(@NotNull Project project) { + @NotNull + public abstract Set getProjectUsages(@NotNull Project project) throws CollectUsagesException; + + private static final Logger LOG = Logger.getInstance(AbstractProjectsUsagesCollector.class); + + // Achtung!!! don't invoke this method directly even you can. + void persistProjectUsages(@NotNull Project project) { try { persistProjectUsages(project, new CollectedUsages(getProjectUsages(project), System.currentTimeMillis())); } @@ -42,23 +57,32 @@ public abstract class AbstractApplicationUsagesCollector extends UsagesCollector } } - public void persistProjectUsages(@NotNull Project project, @NotNull CollectedUsages usages) { + private void persistProjectUsages(@NotNull Project project, @NotNull CollectedUsages usages) { persistProjectUsages(project, usages, ApplicationStatisticsPersistenceComponent.getInstance()); } - public void persistProjectUsages(@NotNull Project project, + private void persistProjectUsages(@NotNull Project project, @NotNull CollectedUsages usages, @NotNull ApplicationStatisticsPersistence persistence) { persistence.persistUsages(getGroupId(), project, usages); } @NotNull - public Set getApplicationUsages() { - return getApplicationUsages(ApplicationStatisticsPersistenceComponent.getInstance()); + private Set getApplicationUsages() { + return mergeUsagesPostProcess(getApplicationUsages(ApplicationStatisticsPersistenceComponent.getInstance())); } @NotNull - public Set getApplicationUsages(@NotNull ApplicationStatisticsPersistence persistence) { + @Deprecated + // this method should be overridden only in exceptional cases. + // you have summarized usages from all opened/closed projects here. + // do you want to add/merge something? Don't do it!!! + protected Set mergeUsagesPostProcess(@NotNull Set usagesFromAllProjects) { + return usagesFromAllProjects; + } + + @NotNull + private Set getApplicationUsages(@NotNull ApplicationStatisticsPersistence persistence) { ObjectIntHashMap result = new ObjectIntHashMap<>(); long lastTimeSent = UsageStatisticsPersistenceComponent.getInstance().getLastTimeSent(); for (CollectedUsages usageDescriptors : persistence.getApplicationData(getGroupId()).values()) { @@ -70,56 +94,12 @@ public abstract class AbstractApplicationUsagesCollector extends UsagesCollector } } } - return toUsageDescriptors(result); - } - - @NotNull - private static Set toUsageDescriptors(@NotNull ObjectIntHashMap result) { - if (result.isEmpty()){ - return Collections.emptySet(); - } - else { - final THashSet descriptors = new THashSet<>(result.size()); - result.forEachEntry(new TObjectIntProcedure() { - @Override - public boolean execute(String key, int value) { - descriptors.add(new UsageDescriptor(key, value)); - return true; - } - }); - return descriptors; - } - } - - @NotNull - public static Set merge(@NotNull Set first, @NotNull Set second) { - if (first.isEmpty()) { - return second; - } - - if (second.isEmpty()) { - return first; - } - - final ObjectIntHashMap merged = new ObjectIntHashMap<>(); - addAll(merged, first); - addAll(merged, second); - return toUsageDescriptors(merged); - } - - private static void addAll(@NotNull ObjectIntHashMap result, @NotNull Set usages) { - for (UsageDescriptor usage : usages) { - final String key = usage.getKey(); - result.put(key, result.get(key, 0) + usage.getValue()); - } + return StatisticsUtilKt.toUsageDescriptors(result); } @Override @NotNull - public Set getUsages() throws CollectUsagesException { + public final Set getUsages() throws CollectUsagesException { return getApplicationUsages(); } - - @NotNull - public abstract Set getProjectUsages(@NotNull Project project) throws CollectUsagesException; } diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/persistence/ApplicationStatisticsPersistenceComponent.java b/platform/platform-impl/src/com/intellij/internal/statistic/ApplicationStatisticsPersistenceComponent.java similarity index 87% rename from platform/platform-impl/src/com/intellij/internal/statistic/persistence/ApplicationStatisticsPersistenceComponent.java rename to platform/platform-impl/src/com/intellij/internal/statistic/ApplicationStatisticsPersistenceComponent.java index 9fcb031227a1..258464c17103 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/persistence/ApplicationStatisticsPersistenceComponent.java +++ b/platform/platform-impl/src/com/intellij/internal/statistic/ApplicationStatisticsPersistenceComponent.java @@ -13,14 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.internal.statistic.persistence; +package com.intellij.internal.statistic; import com.intellij.concurrency.JobScheduler; -import com.intellij.internal.statistic.UsagesCollector; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; +import com.intellij.internal.statistic.persistence.ApplicationStatisticsPersistence; +import com.intellij.internal.statistic.persistence.CollectedUsages; +import com.intellij.internal.statistic.utils.StatisticsUploadAssistant; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.*; +import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.project.ProjectManagerListener; @@ -167,7 +170,7 @@ public class ApplicationStatisticsPersistenceComponent extends ApplicationStatis @Override public void projectOpened(Project project) { ScheduledFuture future = - JobScheduler.getScheduler().schedule(() -> UsagesCollector.doPersistProjectUsages(project), DELAY_IN_MIN, TimeUnit.MINUTES); + JobScheduler.getScheduler().schedule(() -> doPersistProjectUsages(project), DELAY_IN_MIN, TimeUnit.MINUTES); persistProjectStatisticsTasks.put(project, future); } @@ -191,7 +194,23 @@ public class ApplicationStatisticsPersistenceComponent extends ApplicationStatis */ public static void persistOpenedProjects() { for (Project project : ProjectManager.getInstance().getOpenProjects()) { - UsagesCollector.doPersistProjectUsages(project); + doPersistProjectUsages(project); + } + } + + private static void doPersistProjectUsages(@NotNull Project project) { + if (StatisticsUploadAssistant.isSendAllowed()) { + synchronized (StatisticsUploadAssistant.LOCK) { + if (!project.isInitialized() || DumbService.isDumb(project)) { + return; + } + + for (UsagesCollector usagesCollector : UsagesCollector.EP_NAME.getExtensions()) { + if (usagesCollector instanceof AbstractProjectsUsagesCollector) { + ((AbstractProjectsUsagesCollector)usagesCollector).persistProjectUsages(project); + } + } + } } } } diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/UsagesCollector.java b/platform/platform-impl/src/com/intellij/internal/statistic/UsagesCollector.java index 16444b991248..6f70bf23ff74 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/UsagesCollector.java +++ b/platform/platform-impl/src/com/intellij/internal/statistic/UsagesCollector.java @@ -17,21 +17,12 @@ package com.intellij.internal.statistic; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.ExtensionPointName; -import com.intellij.openapi.project.DumbService; -import com.intellij.openapi.project.Project; -import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; -import java.util.LinkedHashMap; -import java.util.Map; import java.util.Set; public abstract class UsagesCollector { - private static final Logger LOG = Logger.getInstance(UsagesCollector.class); - - private static final Object LOCK = new Object(); public static final ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.statistics.usagesCollector"); @@ -40,39 +31,4 @@ public abstract class UsagesCollector { @NotNull public abstract GroupDescriptor getGroupId(); - - public static void doPersistProjectUsages(@NotNull Project project) { - if (StatisticsUploadAssistant.isSendAllowed()) { - synchronized (LOCK) { - if (!project.isInitialized() || DumbService.isDumb(project)) { - return; - } - - for (UsagesCollector usagesCollector : EP_NAME.getExtensions()) { - if (usagesCollector instanceof AbstractApplicationUsagesCollector) { - ((AbstractApplicationUsagesCollector)usagesCollector).persistProjectUsages(project); - } - } - } - } - } - - @NotNull - public static Map> getAllUsages(@NotNull Set disabledGroups) { - synchronized (LOCK) { - Map> usageDescriptors = new LinkedHashMap<>(); - for (UsagesCollector usagesCollector : EP_NAME.getExtensions()) { - GroupDescriptor groupDescriptor = usagesCollector.getGroupId(); - if (!disabledGroups.contains(groupDescriptor.getId())) { - try { - usageDescriptors.merge(groupDescriptor, usagesCollector.getUsages(), ContainerUtil::union); - } - catch (CollectUsagesException e) { - LOG.info(e); - } - } - } - return usageDescriptors; - } - } } diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/tmp/SendStatisticsAction.java b/platform/platform-impl/src/com/intellij/internal/statistic/actions/SendStatisticsAction.java similarity index 91% rename from platform/platform-impl/src/com/intellij/internal/statistic/tmp/SendStatisticsAction.java rename to platform/platform-impl/src/com/intellij/internal/statistic/actions/SendStatisticsAction.java index 5dbf71754fc1..3e49fe81134d 100755 --- a/platform/platform-impl/src/com/intellij/internal/statistic/tmp/SendStatisticsAction.java +++ b/platform/platform-impl/src/com/intellij/internal/statistic/actions/SendStatisticsAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.internal.statistic.tmp; +package com.intellij.internal.statistic.actions; -import com.intellij.internal.statistic.StatisticsUploadAssistant; +import com.intellij.internal.statistic.utils.StatisticsUploadAssistant; import com.intellij.internal.statistic.connect.StatisticsResult; import com.intellij.internal.statistic.connect.StatisticsService; -import com.intellij.internal.statistic.persistence.ApplicationStatisticsPersistenceComponent; +import com.intellij.internal.statistic.ApplicationStatisticsPersistenceComponent; import com.intellij.internal.statistic.persistence.UsageStatisticsPersistenceComponent; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/beans/UsageDescriptor.java b/platform/platform-impl/src/com/intellij/internal/statistic/beans/UsageDescriptor.java index 38221cd85651..0df8e41eeaa9 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/beans/UsageDescriptor.java +++ b/platform/platform-impl/src/com/intellij/internal/statistic/beans/UsageDescriptor.java @@ -17,9 +17,9 @@ package com.intellij.internal.statistic.beans; import org.jetbrains.annotations.NotNull; -public class UsageDescriptor implements Comparable { +public final class UsageDescriptor implements Comparable { private final String myKey; - private int myValue; + private final int myValue; public UsageDescriptor(@NotNull String key, int value) { myKey = ConvertUsagesUtil.ensureProperKey(key); @@ -38,14 +38,6 @@ public class UsageDescriptor implements Comparable { return myValue; } - public void setValue(int i) { - myValue = i; - } - - public void advance() { - myValue++; - } - public int compareTo(UsageDescriptor ud) { return getKey().compareTo(ud.myKey); } diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/configurable/StatisticsConfigurationComponent.java b/platform/platform-impl/src/com/intellij/internal/statistic/configurable/StatisticsConfigurationComponent.java index 15d3e63727f0..26e9b020c519 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/configurable/StatisticsConfigurationComponent.java +++ b/platform/platform-impl/src/com/intellij/internal/statistic/configurable/StatisticsConfigurationComponent.java @@ -16,7 +16,7 @@ package com.intellij.internal.statistic.configurable; import com.intellij.internal.statistic.StatisticsBundle; -import com.intellij.internal.statistic.StatisticsUploadAssistant; +import com.intellij.internal.statistic.utils.StatisticsUploadAssistant; import com.intellij.internal.statistic.connect.StatisticsService; import com.intellij.internal.statistic.persistence.UsageStatisticsPersistenceComponent; import com.intellij.openapi.application.ApplicationInfo; diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/connect/RemotelyConfigurableStatisticsService.java b/platform/platform-impl/src/com/intellij/internal/statistic/connect/RemotelyConfigurableStatisticsService.java index 5611b508e891..e47bbe6da2be 100755 --- a/platform/platform-impl/src/com/intellij/internal/statistic/connect/RemotelyConfigurableStatisticsService.java +++ b/platform/platform-impl/src/com/intellij/internal/statistic/connect/RemotelyConfigurableStatisticsService.java @@ -1,6 +1,6 @@ package com.intellij.internal.statistic.connect; -import com.intellij.internal.statistic.StatisticsUploadAssistant; +import com.intellij.internal.statistic.utils.StatisticsUploadAssistant; import com.intellij.notification.Notification; import com.intellij.notification.NotificationListener; import com.intellij.openapi.util.text.StringUtil; diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/ActionsCollector.java b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/actions/ActionsCollector.java similarity index 96% rename from platform/platform-impl/src/com/intellij/internal/statistic/ActionsCollector.java rename to platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/actions/ActionsCollector.java index d5e72e293a11..883b2ba04c98 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/ActionsCollector.java +++ b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/actions/ActionsCollector.java @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.internal.statistic; +package com.intellij.internal.statistic.customUsageCollectors.actions; +import com.intellij.internal.statistic.UsagesCollector; import com.intellij.internal.statistic.beans.ConvertUsagesUtil; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/BuildNumberUsageCollector.java b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/build/BuildNumberUsageCollector.java similarity index 85% rename from platform/platform-impl/src/com/intellij/internal/statistic/BuildNumberUsageCollector.java rename to platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/build/BuildNumberUsageCollector.java index 76bd4df184f1..3af8f07cabf6 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/BuildNumberUsageCollector.java +++ b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/build/BuildNumberUsageCollector.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -13,8 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.internal.statistic; +package com.intellij.internal.statistic.customUsageCollectors.build; +import com.intellij.internal.statistic.CollectUsagesException; +import com.intellij.internal.statistic.UsagesCollector; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; import com.intellij.openapi.application.impl.ApplicationInfoImpl; diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/ideSettings/IdeInitialConfigButtonUsages.java b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/ideSettings/IdeInitialConfigButtonUsages.java similarity index 97% rename from platform/platform-impl/src/com/intellij/internal/statistic/ideSettings/IdeInitialConfigButtonUsages.java rename to platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/ideSettings/IdeInitialConfigButtonUsages.java index 5f1ee44918bd..e2059c3ff014 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/ideSettings/IdeInitialConfigButtonUsages.java +++ b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/ideSettings/IdeInitialConfigButtonUsages.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.internal.statistic.ideSettings; +package com.intellij.internal.statistic.customUsageCollectors.ideSettings; import com.intellij.util.containers.HashSet; diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/ideSettings/IdeInitialConfigUsageCollectors.java b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/ideSettings/IdeInitialConfigUsageCollectors.java similarity index 94% rename from platform/platform-impl/src/com/intellij/internal/statistic/ideSettings/IdeInitialConfigUsageCollectors.java rename to platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/ideSettings/IdeInitialConfigUsageCollectors.java index 41e496a4eb0e..6adc7c97237c 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/ideSettings/IdeInitialConfigUsageCollectors.java +++ b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/ideSettings/IdeInitialConfigUsageCollectors.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.internal.statistic.ideSettings; +package com.intellij.internal.statistic.customUsageCollectors.ideSettings; import com.intellij.ide.WelcomeWizardUtil; import com.intellij.internal.statistic.CollectUsagesException; -import com.intellij.internal.statistic.StatisticsUtilKt; import com.intellij.internal.statistic.UsagesCollector; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; +import com.intellij.internal.statistic.utils.StatisticsUtilKt; import com.intellij.openapi.util.text.StringUtil; import org.jetbrains.annotations.NotNull; @@ -28,7 +28,7 @@ import java.util.Collections; import java.util.Set; import java.util.stream.Collectors; -import static com.intellij.internal.statistic.ideSettings.IdeInitialConfigButtonUsages.*; +import static com.intellij.internal.statistic.customUsageCollectors.ideSettings.IdeInitialConfigButtonUsages.*; public final class IdeInitialConfigUsageCollectors { public static class ConfigImport extends Base { diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/JdkInfoUsageCollector.java b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/jdk/JdkInfoUsageCollector.java similarity index 86% rename from platform/platform-impl/src/com/intellij/internal/statistic/JdkInfoUsageCollector.java rename to platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/jdk/JdkInfoUsageCollector.java index eeae4c86d52e..b9fb210cd139 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/JdkInfoUsageCollector.java +++ b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/jdk/JdkInfoUsageCollector.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -13,8 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.internal.statistic; +package com.intellij.internal.statistic.customUsageCollectors.jdk; +import com.intellij.internal.statistic.CollectUsagesException; +import com.intellij.internal.statistic.UsagesCollector; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; import com.intellij.openapi.util.SystemInfo; diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/JdkSettingsUsageCollector.kt b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/jdk/JdkSettingsUsageCollector.kt similarity index 92% rename from platform/platform-impl/src/com/intellij/internal/statistic/JdkSettingsUsageCollector.kt rename to platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/jdk/JdkSettingsUsageCollector.kt index aab4a81d85b7..b29efd574ba9 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/JdkSettingsUsageCollector.kt +++ b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/jdk/JdkSettingsUsageCollector.kt @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.internal.statistic +package com.intellij.internal.statistic.customUsageCollectors.jdk +import com.intellij.internal.statistic.UsagesCollector import com.intellij.internal.statistic.beans.GroupDescriptor import com.intellij.internal.statistic.beans.UsageDescriptor import java.lang.management.ManagementFactory diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/OsNameUsageCollector.java b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/os/OsNameUsageCollector.java similarity index 85% rename from platform/platform-impl/src/com/intellij/internal/statistic/OsNameUsageCollector.java rename to platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/os/OsNameUsageCollector.java index c939a59da322..1cc9070917d1 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/OsNameUsageCollector.java +++ b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/os/OsNameUsageCollector.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -13,8 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.internal.statistic; +package com.intellij.internal.statistic.customUsageCollectors.os; +import com.intellij.internal.statistic.CollectUsagesException; +import com.intellij.internal.statistic.UsagesCollector; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; import com.intellij.openapi.util.SystemInfo; diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/OsVersionUsageCollector.java b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/os/OsVersionUsageCollector.java similarity index 93% rename from platform/platform-impl/src/com/intellij/internal/statistic/OsVersionUsageCollector.java rename to platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/os/OsVersionUsageCollector.java index ee5b39050459..489f884fcf27 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/OsVersionUsageCollector.java +++ b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/os/OsVersionUsageCollector.java @@ -13,8 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.internal.statistic; +package com.intellij.internal.statistic.customUsageCollectors.os; +import com.intellij.internal.statistic.CollectUsagesException; +import com.intellij.internal.statistic.UsagesCollector; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; import com.intellij.openapi.util.SystemInfo; diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/FontSizeInfoUsageCollector.kt b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/ui/FontSizeInfoUsageCollector.kt similarity index 92% rename from platform/platform-impl/src/com/intellij/internal/statistic/FontSizeInfoUsageCollector.kt rename to platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/ui/FontSizeInfoUsageCollector.kt index 42d9f87e5c4b..4e1f0481f677 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/FontSizeInfoUsageCollector.kt +++ b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/ui/FontSizeInfoUsageCollector.kt @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.internal.statistic +package com.intellij.internal.statistic.customUsageCollectors.ui import com.intellij.ide.ui.UISettings +import com.intellij.internal.statistic.CollectUsagesException +import com.intellij.internal.statistic.UsagesCollector import com.intellij.internal.statistic.beans.GroupDescriptor import com.intellij.internal.statistic.beans.UsageDescriptor import com.intellij.openapi.editor.colors.EditorColorsManager diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/ideSettings/LaFUsagesCollector.java b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/ui/LaFUsagesCollector.java similarity index 63% rename from platform/platform-impl/src/com/intellij/internal/statistic/ideSettings/LaFUsagesCollector.java rename to platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/ui/LaFUsagesCollector.java index 20a3ebf19f15..20a3c31a9bbe 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/ideSettings/LaFUsagesCollector.java +++ b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/ui/LaFUsagesCollector.java @@ -1,4 +1,19 @@ -package com.intellij.internal.statistic.ideSettings; +/* + * Copyright 2000-2017 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.internal.statistic.customUsageCollectors.ui; import com.intellij.ide.ui.LafManager; import com.intellij.internal.statistic.CollectUsagesException; diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/ScaleInfoUsageCollector.java b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/ui/ScaleInfoUsageCollector.java similarity index 88% rename from platform/platform-impl/src/com/intellij/internal/statistic/ScaleInfoUsageCollector.java rename to platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/ui/ScaleInfoUsageCollector.java index 3770a392e824..539b3803df1f 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/ScaleInfoUsageCollector.java +++ b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/ui/ScaleInfoUsageCollector.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -13,8 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.internal.statistic; +package com.intellij.internal.statistic.customUsageCollectors.ui; +import com.intellij.internal.statistic.CollectUsagesException; +import com.intellij.internal.statistic.UsagesCollector; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; import com.intellij.util.ui.JBUI; diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/ShortcutsCollector.java b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/ui/ShortcutsCollector.java similarity index 99% rename from platform/platform-impl/src/com/intellij/internal/statistic/ShortcutsCollector.java rename to platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/ui/ShortcutsCollector.java index 80769a96e7d8..322e2f18deb7 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/ShortcutsCollector.java +++ b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/ui/ShortcutsCollector.java @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.internal.statistic; +package com.intellij.internal.statistic.customUsageCollectors.ui; +import com.intellij.internal.statistic.UsagesCollector; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; import com.intellij.openapi.actionSystem.AnActionEvent; diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/ToolbarClicksCollector.java b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/ui/ToolbarClicksCollector.java similarity index 96% rename from platform/platform-impl/src/com/intellij/internal/statistic/ToolbarClicksCollector.java rename to platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/ui/ToolbarClicksCollector.java index 7aa99a9f0cf2..fad733ef157b 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/ToolbarClicksCollector.java +++ b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/ui/ToolbarClicksCollector.java @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.internal.statistic; +package com.intellij.internal.statistic.customUsageCollectors.ui; +import com.intellij.internal.statistic.UsagesCollector; import com.intellij.internal.statistic.beans.ConvertUsagesUtil; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/UiInfoUsageCollector.java b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/ui/UiInfoUsageCollector.java similarity index 95% rename from platform/platform-impl/src/com/intellij/internal/statistic/UiInfoUsageCollector.java rename to platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/ui/UiInfoUsageCollector.java index 14bbcc667f36..cd32f0a24a8b 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/UiInfoUsageCollector.java +++ b/platform/platform-impl/src/com/intellij/internal/statistic/customUsageCollectors/ui/UiInfoUsageCollector.java @@ -13,10 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.internal.statistic; +package com.intellij.internal.statistic.customUsageCollectors.ui; import com.intellij.ide.GeneralSettings; import com.intellij.ide.ui.UISettings; +import com.intellij.internal.statistic.CollectUsagesException; +import com.intellij.internal.statistic.UsagesCollector; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; import com.intellij.openapi.editor.ex.EditorSettingsExternalizable; diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/ideSettings/IdeSettingsStatisticsUtils.java b/platform/platform-impl/src/com/intellij/internal/statistic/ideSettings/IdeSettingsStatisticsUtils.java deleted file mode 100644 index 20df53c9ea0a..000000000000 --- a/platform/platform-impl/src/com/intellij/internal/statistic/ideSettings/IdeSettingsStatisticsUtils.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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.internal.statistic.ideSettings; - -import com.intellij.internal.statistic.beans.GroupDescriptor; -import com.intellij.internal.statistic.beans.UsageDescriptor; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.components.PersistentStateComponent; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.util.ReflectionUtil; -import com.intellij.util.containers.hash.HashSet; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.lang.reflect.Method; -import java.util.List; -import java.util.Set; - -public class IdeSettingsStatisticsUtils { - public static final GroupDescriptor GROUP = GroupDescriptor.create("IDE Settings", GroupDescriptor.HIGHER_PRIORITY); - - @Nullable - public static Object getApplicationProvider(@NotNull String providerName) { - return getProviderInstance(getApplicationComponentByName(providerName)); - } - - public static Set getUsages(@NotNull IdeSettingsDescriptor descriptor, @NotNull Object componentInstance) { - Set descriptors = new HashSet<>(); - - String providerName = descriptor.myProviderName; - - List propertyNames = descriptor.getPropertyNames(); - if (providerName != null && propertyNames.size() > 0) { - for (String propertyName : propertyNames) { - Object propertyValue = getPropertyValue(componentInstance, propertyName); - - if (propertyValue != null) { - descriptors.add(new UsageDescriptor(getUsageDescriptorKey(providerName, propertyName, propertyValue.toString()), 1)); - } - } - } - return descriptors; - } - - @Nullable - private static Object getPropertyValue(Object componentInstance, String propertyName) { - final Class componentInstanceClass = componentInstance.getClass(); - Object propertyValue = ReflectionUtil.getField(componentInstanceClass, componentInstance, null, propertyName); - if (propertyValue == null) { - Method method = ReflectionUtil.getMethod(componentInstanceClass, "get" + StringUtil.capitalize(propertyName)); - if (method == null) { - method = ReflectionUtil.getMethod(componentInstanceClass, "is" + StringUtil.capitalize(propertyName)); - } - if (method != null) { - try { - propertyValue = method.invoke(componentInstance); - } - catch (Exception ignored) { - } - } - } - return propertyValue; - } - - private static String getUsageDescriptorKey(@NotNull String providerName, @NotNull String name, @NotNull String value) { - final String shortName = StringUtil.getShortName(providerName); - return shortName + "#" + name + "(" + value + ")"; - } - - @Nullable - public static Object getProjectProvider(@Nullable Project project, - @NotNull String providerName) { - return getProviderInstance(getProjectComponentByName(project, providerName)); - } - - @Nullable - private static Object getProviderInstance(Object componentInstance) { - if (componentInstance instanceof PersistentStateComponent) { - return ((PersistentStateComponent)componentInstance).getState(); - } - - return componentInstance; - } - - @Nullable - private static Object getApplicationComponentByName(@NotNull String providerName) { - return ApplicationManager.getApplication().getPicoContainer().getComponentInstance(providerName); - } - - @Nullable - private static Object getProjectComponentByName(@Nullable Project project, String providerName) { - if (project != null) { - return project.getPicoContainer().getComponentInstance(providerName); - } - return null; - } -} diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/ideSettings/ide-settings-statistics.xml b/platform/platform-impl/src/com/intellij/internal/statistic/ideSettings/ide-settings-statistics.xml deleted file mode 100644 index 405f14292e99..000000000000 --- a/platform/platform-impl/src/com/intellij/internal/statistic/ideSettings/ide-settings-statistics.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/updater/SendStatisticsComponent.java b/platform/platform-impl/src/com/intellij/internal/statistic/updater/SendStatisticsComponent.java index d2d24215f6f5..b371aefc6690 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/updater/SendStatisticsComponent.java +++ b/platform/platform-impl/src/com/intellij/internal/statistic/updater/SendStatisticsComponent.java @@ -18,7 +18,7 @@ package com.intellij.internal.statistic.updater; import com.intellij.concurrency.JobScheduler; import com.intellij.ide.FrameStateListener; import com.intellij.ide.FrameStateManager; -import com.intellij.internal.statistic.StatisticsUploadAssistant; +import com.intellij.internal.statistic.utils.StatisticsUploadAssistant; import com.intellij.internal.statistic.connect.StatisticsService; import com.intellij.internal.statistic.connect.StatisticsServiceEP; import com.intellij.notification.NotificationDisplayType; diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/StatisticsUploadAssistant.java b/platform/platform-impl/src/com/intellij/internal/statistic/utils/StatisticsUploadAssistant.java similarity index 77% rename from platform/platform-impl/src/com/intellij/internal/statistic/StatisticsUploadAssistant.java rename to platform/platform-impl/src/com/intellij/internal/statistic/utils/StatisticsUploadAssistant.java index b77965c0b6eb..3b2490b01a1b 100755 --- a/platform/platform-impl/src/com/intellij/internal/statistic/StatisticsUploadAssistant.java +++ b/platform/platform-impl/src/com/intellij/internal/statistic/utils/StatisticsUploadAssistant.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -13,10 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.internal.statistic; +package com.intellij.internal.statistic.utils; import com.intellij.featureStatistics.FeatureUsageTracker; import com.intellij.featureStatistics.FeatureUsageTrackerImpl; +import com.intellij.internal.statistic.CollectUsagesException; +import com.intellij.internal.statistic.UsagesCollector; import com.intellij.internal.statistic.beans.ConvertUsagesUtil; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; @@ -27,16 +29,22 @@ import com.intellij.internal.statistic.connect.StatisticsService; import com.intellij.internal.statistic.persistence.SentUsagesPersistence; import com.intellij.internal.statistic.persistence.UsageStatisticsPersistenceComponent; import com.intellij.openapi.application.impl.ApplicationInfoImpl; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.KeyedExtensionCollector; import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.util.Time; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; public class StatisticsUploadAssistant { + private static final Logger LOG = Logger.getInstance(UsagesCollector.class); + public static final Object LOCK = new Object(); + public String getData() { return getData(Collections.emptySet()); } @@ -80,7 +88,7 @@ public class StatisticsUploadAssistant { @NotNull public static String getDataString(@NotNull Set disabledGroups, int maxSize) { - return getDataString(UsagesCollector.getAllUsages(disabledGroups), maxSize); + return getDataString(getAllUsages(disabledGroups), maxSize); } public static String getDataString(@NotNull Map> usages, int maxSize) { @@ -109,4 +117,23 @@ public class StatisticsUploadAssistant { new StatisticsHttpClientSender(), new StatisticsUploadAssistant()); } + + @NotNull + private static Map> getAllUsages(@NotNull Set disabledGroups) { + synchronized (LOCK) { + Map> usageDescriptors = new LinkedHashMap<>(); + for (UsagesCollector usagesCollector : UsagesCollector.EP_NAME.getExtensions()) { + GroupDescriptor groupDescriptor = usagesCollector.getGroupId(); + if (!disabledGroups.contains(groupDescriptor.getId())) { + try { + usageDescriptors.merge(groupDescriptor, usagesCollector.getUsages(), ContainerUtil::union); + } + catch (CollectUsagesException e) { + LOG.info(e); + } + } + } + return usageDescriptors; + } + } } diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/StatisticsUtil.kt b/platform/platform-impl/src/com/intellij/internal/statistic/utils/StatisticsUtil.kt similarity index 76% rename from platform/platform-impl/src/com/intellij/internal/statistic/StatisticsUtil.kt rename to platform/platform-impl/src/com/intellij/internal/statistic/utils/StatisticsUtil.kt index dc3dede7f166..6972390c0594 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/StatisticsUtil.kt +++ b/platform/platform-impl/src/com/intellij/internal/statistic/utils/StatisticsUtil.kt @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.internal.statistic +package com.intellij.internal.statistic.utils import com.intellij.internal.statistic.beans.UsageDescriptor +import com.intellij.util.containers.ObjectIntHashMap +import gnu.trove.THashSet /** * Constructs a proper UsageDescriptor for a boolean value, @@ -84,3 +86,39 @@ private fun humanize(number: Int): String { val rs = if (r > 0) "${r}" else "" return ms + ks + rs } + +fun toUsageDescriptors(result: ObjectIntHashMap): Set { + if (result.isEmpty) { + return emptySet() + } + else { + val descriptors = THashSet(result.size()) + result.forEachEntry { key, value -> + descriptors.add(UsageDescriptor(key, value)) + true + } + return descriptors + } +} + +fun merge(first: Set, second: Set): Set { + if (first.isEmpty()) { + return second + } + + if (second.isEmpty()) { + return first + } + + val merged = ObjectIntHashMap() + addAll(merged, first) + addAll(merged, second) + return toUsageDescriptors(merged) +} + +private fun addAll(result: ObjectIntHashMap, usages: Set) { + for (usage in usages) { + val key = usage.key + result.put(key, result.get(key, 0) + usage.value) + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionButton.java b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionButton.java index dd27cffc9c9c..7a3faa8b07e0 100644 --- a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionButton.java +++ b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionButton.java @@ -17,7 +17,7 @@ package com.intellij.openapi.actionSystem.impl; import com.intellij.icons.AllIcons; import com.intellij.ide.DataManager; -import com.intellij.internal.statistic.ToolbarClicksCollector; +import com.intellij.internal.statistic.customUsageCollectors.ui.ToolbarClicksCollector; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.actionSystem.ex.ActionButtonLook; import com.intellij.openapi.actionSystem.ex.ActionManagerEx; diff --git a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionManagerImpl.java index bbacb4dd8f84..df60f17b5d94 100644 --- a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionManagerImpl.java @@ -1115,16 +1115,16 @@ public final class ActionManagerImpl extends ActionManagerEx implements Disposab if (isGroup != newAction instanceof ActionGroup) { throw new IllegalStateException("cannot replace a group with an action and vice versa: " + actionId); } + for (String groupId : myId2GroupId.get(actionId)) { + DefaultActionGroup group = ObjectUtils.assertNotNull((DefaultActionGroup)getActionOrStub(groupId)); + group.replaceAction(oldAction, newAction); + } unregisterAction(actionId); if (isGroup) { myId2GroupId.values().remove(actionId); } } registerAction(actionId, newAction, pluginId); - for (String groupId : myId2GroupId.get(actionId)) { - DefaultActionGroup group = ObjectUtils.assertNotNull((DefaultActionGroup)getActionOrStub(groupId)); - group.replaceAction(oldAction, newAction); - } return oldAction; } diff --git a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionToolbarImpl.java b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionToolbarImpl.java index 7596449e660c..6059efae9283 100644 --- a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionToolbarImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionToolbarImpl.java @@ -232,6 +232,7 @@ public class ActionToolbarImpl extends JPanel implements ActionToolbar, QuickAct ourToolbars.remove(this); } + @NotNull @Override public JComponent getComponent() { return this; diff --git a/platform/platform-impl/src/com/intellij/openapi/application/ImportOldConfigsPanel.java b/platform/platform-impl/src/com/intellij/openapi/application/ImportOldConfigsPanel.java index ca602c288fc5..39e6a6b90e2e 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/ImportOldConfigsPanel.java +++ b/platform/platform-impl/src/com/intellij/openapi/application/ImportOldConfigsPanel.java @@ -16,7 +16,7 @@ package com.intellij.openapi.application; import com.intellij.ide.cloudConfig.CloudConfigProvider; -import com.intellij.internal.statistic.ideSettings.IdeInitialConfigButtonUsages; +import com.intellij.internal.statistic.customUsageCollectors.ideSettings.IdeInitialConfigButtonUsages; import com.intellij.openapi.MnemonicHelper; import com.intellij.openapi.ui.TextFieldWithBrowseButton; import com.intellij.openapi.util.SystemInfo; diff --git a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java index 5527aed8c101..0cec29b8083d 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java @@ -1206,24 +1206,22 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App ActivityTracker.getInstance().inc(); fireBeforeWriteActionStart(clazz); - if (!myLock.isWriteLocked()) { - if (!myLock.tryWriteLock()) { - Future reportSlowWrite = ourDumpThreadsOnLongWriteActionWaiting <= 0 ? null : - JobScheduler.getScheduler().scheduleWithFixedDelay(() -> PerformanceWatcher.getInstance().dumpThreads("waiting", true), - ourDumpThreadsOnLongWriteActionWaiting, - ourDumpThreadsOnLongWriteActionWaiting, TimeUnit.MILLISECONDS); - long t = 0; - if (LOG.isDebugEnabled()) { - t = System.currentTimeMillis(); - } - myLock.writeLock(); - if (LOG.isDebugEnabled()) { - long elapsed = System.currentTimeMillis() - t; + if (!myLock.isWriteLocked() && !myLock.tryWriteLock()) { + Future reportSlowWrite = ourDumpThreadsOnLongWriteActionWaiting <= 0 ? null : + JobScheduler.getScheduler() + .scheduleWithFixedDelay(() -> PerformanceWatcher.getInstance().dumpThreads("waiting", true), + ourDumpThreadsOnLongWriteActionWaiting, + ourDumpThreadsOnLongWriteActionWaiting, TimeUnit.MILLISECONDS); + long t = LOG.isDebugEnabled() ? System.currentTimeMillis() : 0; + myLock.writeLock(); + if (LOG.isDebugEnabled()) { + long elapsed = System.currentTimeMillis() - t; + if (elapsed != 0) { LOG.debug("Write action wait time: " + elapsed); } - if (reportSlowWrite != null) { - reportSlowWrite.cancel(false); - } + } + if (reportSlowWrite != null) { + reportSlowWrite.cancel(false); } } } diff --git a/platform/platform-impl/src/com/intellij/openapi/command/impl/UndoRedo.java b/platform/platform-impl/src/com/intellij/openapi/command/impl/UndoRedo.java index 0fe7ae2706db..c0fbefc57a96 100644 --- a/platform/platform-impl/src/com/intellij/openapi/command/impl/UndoRedo.java +++ b/platform/platform-impl/src/com/intellij/openapi/command/impl/UndoRedo.java @@ -210,11 +210,11 @@ abstract class UndoRedo { } private boolean restore(EditorAndState pair, boolean onlyIfDiffers) { - if (myEditor == null || - !myEditor.isValid() || // editor can be invalid if underlying file is deleted during undo (e.g. after undoing scratch file creation) - pair == null || pair.getEditor() == null) { - return false; - } + // editor can be invalid if underlying file is deleted during undo (e.g. after undoing scratch file creation) + if (pair == null || myEditor == null || !myEditor.isValid()) return false; + + FileEditor editor = pair.getEditor(); + if (editor == null) return false; // we cannot simply compare editors here because of the following scenario: // 1. make changes in editor for file A @@ -222,10 +222,12 @@ abstract class UndoRedo { // 3. close editor // 4. re-open editor for A via Ctrl-E // 5. undo -> position is not affected, because instance created in step 4 is not the same!!! - if (!myEditor.getClass().equals(pair.getEditor().getClass())) { - return false; - } + if (!myEditor.getClass().equals(editor.getClass())) return false; + VirtualFile myFile = myEditor.getFile(); + VirtualFile file = editor.getFile(); + if (myFile != null && file != null && !myFile.equals(file)) return false; + // If current editor state isn't equals to remembered state then // we have to try to restore previous state. But sometime it's // not possible to restore it. For example, it's not possible to diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java index 648384611a57..df89f843ed9b 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java @@ -414,37 +414,17 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse } int annotationSize = myTextAnnotationGutterSizes.get(i); - VisualLinesIterator visLinesIterator = new VisualLinesIterator(myEditor, startVisualLine); - while (!visLinesIterator.atEnd() && visLinesIterator.getVisualLine() <= endVisualLine) { - int logLine = visLinesIterator.getStartLogicalLine(); - int y = visLinesIterator.getY(); - String s = gutterProvider.getLineText(logLine, myEditor); - final EditorFontType style = gutterProvider.getStyle(logLine, myEditor); - final Color bg = gutterProvider.getBgColor(logLine, myEditor); - if (bg != null) { - g.setColor(bg); - g.fillRect(x, y, annotationSize, lineHeight); - } - g.setColor(myEditor.getColorsScheme().getColor(gutterProvider.getColor(logLine, myEditor))); - g.setFont(myEditor.getColorsScheme().getFont(style)); - if (!StringUtil.isEmpty(s)) { - // we leave half of the gap before the text - g.drawString(s, GAP_BETWEEN_ANNOTATIONS / 2 + x, y + myEditor.getAscent()); - } - visLinesIterator.advance(); - } if (startVisualLine == 0 && endVisualLine == 0) { //allow paining gutters for empty documents - String s = gutterProvider.getLineText(0, myEditor); - final EditorFontType style = gutterProvider.getStyle(0, myEditor); - final Color bg = gutterProvider.getBgColor(0, myEditor); - if (bg != null) { - g.setColor(bg); - g.fillRect(x, 0, annotationSize, lineHeight); - } - g.setColor(myEditor.getColorsScheme().getColor(gutterProvider.getColor(0, myEditor))); - g.setFont(myEditor.getColorsScheme().getFont(style)); - if (!StringUtil.isEmpty(s)) { - g.drawString(s, GAP_BETWEEN_ANNOTATIONS / 2 + x, myEditor.getAscent()); + paintAnnotationLine(g, gutterProvider, 0, x, 0, annotationSize, lineHeight); + } + else { + VisualLinesIterator visLinesIterator = new VisualLinesIterator(myEditor, startVisualLine); + while (!visLinesIterator.atEnd() && visLinesIterator.getVisualLine() <= endVisualLine) { + int logLine = visLinesIterator.getStartLogicalLine(); + int y = visLinesIterator.getY(); + paintAnnotationLine(g, gutterProvider, logLine, x, y, annotationSize, lineHeight); + + visLinesIterator.advance(); } } @@ -457,6 +437,25 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse } } + private void paintAnnotationLine(Graphics g, TextAnnotationGutterProvider gutterProvider, int line, int x, int y, int width, int height) { + String s = gutterProvider.getLineText(line, myEditor); + final EditorFontType style = gutterProvider.getStyle(line, myEditor); + final Color bg = gutterProvider.getBgColor(line, myEditor); + if (bg != null) { + g.setColor(bg); + g.fillRect(x, y, width, height); + } + if (!StringUtil.isEmpty(s)) { + g.setColor(myEditor.getColorsScheme().getColor(gutterProvider.getColor(line, myEditor))); + Font font = myEditor.getColorsScheme().getFont(style); + if (font.canDisplayUpTo(s) != -1) { + font = UIUtil.getFontWithFallback(font); + } + g.setFont(font); + g.drawString(s, GAP_BETWEEN_ANNOTATIONS / 2 + x, y + myEditor.getAscent()); + } + } + private void paintFoldingTree(Graphics g, Rectangle clip, int firstVisibleOffset, int lastVisibleOffset) { if (isFoldingOutlineShown()) { doPaintFoldingTree((Graphics2D)g, clip, firstVisibleOffset, lastVisibleOffset); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/InlayImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/InlayImpl.java index 1a3f307d9c9e..a52b74a82bb5 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/InlayImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/InlayImpl.java @@ -52,6 +52,14 @@ class InlayImpl extends RangeMarkerImpl implements Inlay, Getter { myEditor.getInlayModel().notifyChanged(this); } + @Override + public void repaint() { + if (isValid() && !myEditor.isDisposed()) { + int offset = getOffset(); + myEditor.repaint(offset, offset, false); + } + } + private void doUpdateSize() { myWidthInPixels = myRenderer.calcWidthInPixels(myEditor); if (myWidthInPixels <= 0) { @@ -70,7 +78,13 @@ class InlayImpl extends RangeMarkerImpl implements Inlay, Getter { @Override protected void onReTarget(int startOffset, int endOffset, int destOffset) { if (DocumentUtil.isInsideSurrogatePair(getDocument(), getOffset())) { - invalidate("moved inside surrogate pair on retarget"); + myEditor.getInlayModel().myMoveInProgress = true; + try { + invalidate("moved inside surrogate pair on retarget"); + } + finally { + myEditor.getInlayModel().myMoveInProgress = false; + } } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/InlayModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/InlayModelImpl.java index 3661c51acca5..33439dadee6d 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/InlayModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/InlayModelImpl.java @@ -24,7 +24,7 @@ import com.intellij.openapi.editor.InlayModel; import com.intellij.openapi.editor.VisualPosition; import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.ex.DocumentEx; -import com.intellij.openapi.editor.ex.PrioritizedDocumentListener; +import com.intellij.openapi.editor.ex.PrioritizedInternalDocumentListener; import com.intellij.openapi.util.Getter; import com.intellij.util.DocumentUtil; import com.intellij.util.EventDispatcher; @@ -46,8 +46,11 @@ public class InlayModelImpl implements InlayModel, Disposable { private final EditorImpl myEditor; private final EventDispatcher myDispatcher = EventDispatcher.create(Listener.class); + + final List myInlaysInvalidatedOnMove = new ArrayList<>(); final RangeMarkerTree myInlayTree; - + + boolean myMoveInProgress; private List myInlaysToTheRightOfCaret; InlayModelImpl(@NotNull EditorImpl editor) { @@ -68,11 +71,18 @@ public class InlayModelImpl implements InlayModel, Disposable { @Override void fireBeforeRemoved(@NotNull InlayImpl markerEx, @NotNull @NonNls Object reason) { if (markerEx.myOffsetBeforeDisposal == -1) { - notifyRemoved(markerEx); + if (myMoveInProgress) { + // delay notification about invalidated inlay - folding model is not consistent at this point + // (FoldingModelImpl.moveTextHappened hasn't been called yet at this point) + myInlaysInvalidatedOnMove.add(markerEx); + } + else { + notifyRemoved(markerEx); + } } } }; - myEditor.getDocument().addDocumentListener(new PrioritizedDocumentListener() { + myEditor.getDocument().addDocumentListener(new PrioritizedInternalDocumentListener() { @Override public int getPriority() { return EditorDocumentPriorities.INLAY_MODEL; @@ -108,6 +118,14 @@ public class InlayModelImpl implements InlayModel, Disposable { myInlaysToTheRightOfCaret = null; } } + + @Override + public void moveTextHappened(int start, int end, int base) { + for (InlayImpl inlay : myInlaysInvalidatedOnMove) { + notifyRemoved(inlay); + } + myInlaysInvalidatedOnMove.clear(); + } }, this); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/LineLayout.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/LineLayout.java index 1ec5314ad034..97fb80293322 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/LineLayout.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/view/LineLayout.java @@ -150,15 +150,18 @@ abstract class LineLayout { List runs = new ArrayList<>(); int flags = view.getBidiFlags(); if (startOffsetInEditor >= 0) { - // running bidi algorithm separately for text fragments corresponding to different lexer tokens + // skipping indent int relLastOffset = 0; + while (relLastOffset < text.length && text[relLastOffset] == ' ' || text[relLastOffset] == '\t') relLastOffset++; + addRuns(runs, text, 0, relLastOffset, flags); + // running bidi algorithm separately for text fragments corresponding to different lexer tokens IElementType lastToken = null; - HighlighterIterator iterator = view.getEditor().getHighlighter().createIterator(startOffsetInEditor); + HighlighterIterator iterator = view.getEditor().getHighlighter().createIterator(startOffsetInEditor + relLastOffset); while (!iterator.atEnd() && iterator.getStart() - startOffsetInEditor < textLength) { int iteratorRelStart = alignToCodePointBoundary(text, iterator.getStart() - startOffsetInEditor); int iteratorRelEnd = alignToCodePointBoundary(text, iterator.getEnd() - startOffsetInEditor); IElementType currentToken = iterator.getTokenType(); - int relStartOffset = Math.max(0, iteratorRelStart); + int relStartOffset = Math.max(relLastOffset, iteratorRelStart); String lcPrefix = getLineCommentPrefix(currentToken); // for line comments we process prefix and following text separately if (!StringUtil.isEmpty(lcPrefix) && lcPrefix.length() <= iteratorRelEnd - iteratorRelStart && diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorWindow.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorWindow.java index 41fdcc2ccc28..77191e55b130 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorWindow.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorWindow.java @@ -1194,8 +1194,16 @@ public class EditorWindow { } public void clear() { + FileEditorManagerImpl manager = getManager(); + FileEditorManagerListener.Before beforePublisher = + manager.getProject().getMessageBus().syncPublisher(FileEditorManagerListener.Before.FILE_EDITOR_MANAGER); + FileEditorManagerListener afterPublisher = + manager.getProject().getMessageBus().syncPublisher(FileEditorManagerListener.FILE_EDITOR_MANAGER); for (EditorWithProviderComposite composite : getEditors()) { - Disposer.dispose(composite); + VirtualFile file = composite.getFile(); + beforePublisher.beforeFileClosed(manager, file); + manager.disposeComposite(composite); + afterPublisher.fileClosed(manager, file); } if (myTabbedPane == null) { myPanel.removeAll(); diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java index a6e75177a0b2..858da0e6a305 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java @@ -677,7 +677,8 @@ public class FileDocumentManagerImpl extends FileDocumentManager implements Virt virtualFile.putUserData(MUST_RECOMPUTE_FILE_TYPE, Boolean.TRUE); } - myConflictResolver.beforeContentChange(event); + if(ourConflictsSolverEnabled) + myConflictResolver.beforeContentChange(event); } public static boolean recomputeFileTypeIfNecessary(@NotNull VirtualFile virtualFile) { @@ -791,6 +792,10 @@ public class FileDocumentManagerImpl extends FileDocumentManager implements Virt } private final Map myDocumentCache = ContainerUtil.createConcurrentWeakValueMap(); + + //temp setter for Rider 2017.1 + public static boolean ourConflictsSolverEnabled = true; + // used in Upsource protected void cacheDocument(@NotNull VirtualFile file, @NotNull Document document) { myDocumentCache.put(file, document); diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/IdeDocumentHistoryImpl.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/IdeDocumentHistoryImpl.java index 9bb725ef021a..15793c351689 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/IdeDocumentHistoryImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/IdeDocumentHistoryImpl.java @@ -17,6 +17,7 @@ package com.intellij.openapi.fileEditor.impl; import com.intellij.ide.ui.UISettings; import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.CommandEvent; import com.intellij.openapi.command.CommandListener; import com.intellij.openapi.command.CommandProcessor; @@ -34,6 +35,7 @@ import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.vfs.*; import com.intellij.openapi.wm.ToolWindowManager; +import com.intellij.psi.ExternalChangeAction; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -190,7 +192,7 @@ public class IdeDocumentHistoryImpl extends IdeDocumentHistory implements Projec private void onDocumentChanged(DocumentEvent e) { Document document = e.getDocument(); final VirtualFile file = getFileDocumentManager().getFile(document); - if (file != null) { + if (file != null && !ApplicationManager.getApplication().hasWriteAction(ExternalChangeAction.class)) { myCurrentCommandHasChanges = true; myChangedFilesInCurrentCommand.add(file); } @@ -296,6 +298,10 @@ public class IdeDocumentHistoryImpl extends IdeDocumentHistory implements Projec return VfsUtilCore.toVirtualFileArray(files); } + public boolean isRecentlyChanged(@NotNull VirtualFile file) { + return myRecentlyChangedFiles.CHANGED_PATHS.contains(file.getPath()); + } + @Override public final void clearHistory() { myBackPlaces.clear(); diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/NonProjectFileWritingAccessProvider.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/NonProjectFileWritingAccessProvider.java index c3270ec73d20..d6ae49596733 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/NonProjectFileWritingAccessProvider.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/NonProjectFileWritingAccessProvider.java @@ -34,7 +34,6 @@ import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.vfs.*; import com.intellij.openapi.vfs.ex.temp.TempFileSystem; import com.intellij.project.ProjectKt; -import com.intellij.util.ArrayUtil; import com.intellij.util.NullableFunction; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; @@ -121,8 +120,10 @@ public class NonProjectFileWritingAccessProvider extends WritingAccessProvider { if (!(file.getFileSystem() instanceof LocalFileSystem)) return true; // do not block e.g., HttpFileSystem, LightFileSystem etc. if (file.getFileSystem() instanceof TempFileSystem) return true; - - if (ArrayUtil.contains(file, IdeDocumentHistory.getInstance(project).getChangedFiles())) return true; + + IdeDocumentHistoryImpl documentHistory = (IdeDocumentHistoryImpl)IdeDocumentHistory.getInstance(project); + + if (documentHistory.isRecentlyChanged(file)) return true; if (!getApp().isUnitTestMode() && FileUtil.isAncestor(new File(FileUtil.getTempDirectory()), VfsUtilCore.virtualToIoFile(file), true)) { diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/http/RemoteFilePanel.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/http/RemoteFilePanel.java index eb73cbb888be..8d04cafed061 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/http/RemoteFilePanel.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/http/RemoteFilePanel.java @@ -17,7 +17,6 @@ package com.intellij.openapi.fileEditor.impl.http; import com.intellij.icons.AllIcons; import com.intellij.openapi.actionSystem.ActionManager; -import com.intellij.openapi.actionSystem.ActionPlaces; import com.intellij.openapi.actionSystem.ActionToolbar; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.application.ApplicationManager; @@ -179,7 +178,7 @@ public class RemoteFilePanel { public void dispose() { myVirtualFile.getFileInfo().removeDownloadingListener(myDownloadingListener); - myProgressUpdatesQueue.dispose(); + Disposer.dispose(myProgressUpdatesQueue); if (myFileEditor != null) { Disposer.dispose(myFileEditor); } diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorImpl.java index 51c9b324b3ff..1638b7a6f9d5 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorImpl.java @@ -80,6 +80,12 @@ public class TextEditorImpl extends UserDataHolderBase implements TextEditor { public void dispose(){ } + @NotNull + @Override + public VirtualFile getFile() { + return myFile; + } + @Override @NotNull public TextEditorComponent getComponent() { diff --git a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/IdeKeyEventDispatcher.java b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/IdeKeyEventDispatcher.java index 2585c2009b33..5ba1026cacd7 100644 --- a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/IdeKeyEventDispatcher.java +++ b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/IdeKeyEventDispatcher.java @@ -19,7 +19,7 @@ import com.intellij.ide.DataManager; import com.intellij.ide.IdeEventQueue; import com.intellij.ide.ProhibitAWTEvents; import com.intellij.ide.impl.DataManagerImpl; -import com.intellij.internal.statistic.ShortcutsCollector; +import com.intellij.internal.statistic.customUsageCollectors.ui.ShortcutsCollector; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.actionSystem.ex.ActionManagerEx; diff --git a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ModifierKeyDoubleClickHandler.java b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ModifierKeyDoubleClickHandler.java index ab82f2c6681d..594055ed5ab2 100644 --- a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ModifierKeyDoubleClickHandler.java +++ b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ModifierKeyDoubleClickHandler.java @@ -17,7 +17,7 @@ package com.intellij.openapi.keymap.impl; import com.intellij.ide.DataManager; import com.intellij.ide.IdeEventQueue; -import com.intellij.internal.statistic.ShortcutsCollector; +import com.intellij.internal.statistic.customUsageCollectors.ui.ShortcutsCollector; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.actionSystem.ex.ActionManagerEx; diff --git a/platform/platform-impl/src/com/intellij/openapi/options/newEditor/SettingsSearch.java b/platform/platform-impl/src/com/intellij/openapi/options/newEditor/SettingsSearch.java index 15ae68274f9c..e21588a0f6df 100644 --- a/platform/platform-impl/src/com/intellij/openapi/options/newEditor/SettingsSearch.java +++ b/platform/platform-impl/src/com/intellij/openapi/options/newEditor/SettingsSearch.java @@ -17,16 +17,14 @@ package com.intellij.openapi.options.newEditor; import com.intellij.ide.ui.laf.darcula.ui.DarculaTextBorder; import com.intellij.ide.ui.laf.darcula.ui.DarculaTextFieldUI; +import com.intellij.ide.ui.laf.darcula.ui.TextFieldWithPopupHandlerUI; import com.intellij.openapi.actionSystem.ShortcutSet; import com.intellij.openapi.keymap.KeymapUtil; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.SearchTextField; -import javax.swing.KeyStroke; -import javax.swing.InputMap; -import javax.swing.JTextField; -import javax.swing.JTree; +import javax.swing.*; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; @@ -45,7 +43,7 @@ abstract class SettingsSearch extends SearchTextField implements KeyListener { if (!SystemInfo.isMac) { JTextField editor = getTextEditor(); editor.putClientProperty("JTextField.variant", "search"); - if (!(editor.getUI() instanceof DarculaTextFieldUI)) { + if (!(editor.getUI() instanceof TextFieldWithPopupHandlerUI)) { editor.setUI((DarculaTextFieldUI)DarculaTextFieldUI.createUI(editor)); editor.setBorder(new DarculaTextBorder()); } diff --git a/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java b/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java index af667987f8ae..0cda249aee85 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java @@ -48,6 +48,7 @@ import com.intellij.util.containers.Queue; import com.intellij.util.io.storage.HeavyProcessLatch; import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.Debugger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; @@ -272,6 +273,7 @@ public class DumbServiceImpl extends DumbService implements Disposable, Modifica return !myProject.isDisposed(); } + @Debugger.Insert(keyExpression = "runnable", group = "com.intellij.openapi.project.DumbService.runWhenSmart") private void updateFinished() { if (!WriteAction.compute(this::switchToSmartMode)) return; diff --git a/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.java index 583752e10bd3..a77364bd4e1a 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.java @@ -528,7 +528,8 @@ public class ProjectManagerImpl extends ProjectManagerEx implements Disposable { private boolean loadProjectWithProgress(ProjectImpl project) throws IOException { try { - if (myProgressManager.getProgressIndicator() != null) { + if (!ApplicationManager.getApplication().isDispatchThread() && + myProgressManager.getProgressIndicator() != null) { initProject(project, null); return true; } diff --git a/platform/platform-impl/src/com/intellij/openapi/project/impl/UndefinedMacrosConfigurable.java b/platform/platform-impl/src/com/intellij/openapi/project/impl/UndefinedMacrosConfigurable.java index cac95c7c17c0..977323d9e43e 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/impl/UndefinedMacrosConfigurable.java +++ b/platform/platform-impl/src/com/intellij/openapi/project/impl/UndefinedMacrosConfigurable.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -21,7 +21,7 @@ import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.project.ProjectBundle; import com.intellij.openapi.ui.MultiLineLabelUI; -import com.intellij.ui.IdeBorderFactory; +import com.intellij.util.ui.JBUI; import javax.swing.*; import java.awt.*; @@ -59,7 +59,7 @@ public class UndefinedMacrosConfigurable implements Configurable{ final JLabel textLabel = new JLabel(myText); textLabel.setUI(new MultiLineLabelUI()); - textLabel.setBorder(IdeBorderFactory.createEmptyBorder(6, 6, 6, 6)); + textLabel.setBorder(JBUI.Borders.empty(6, 6, 6, 6)); mainPanel.add(textLabel, BorderLayout.NORTH); return mainPanel; diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/AbstractUpdateDialog.java b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/AbstractUpdateDialog.java index 0d3b1bc817b3..7b75fbd9924c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/AbstractUpdateDialog.java +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/AbstractUpdateDialog.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -20,7 +20,7 @@ import com.intellij.ide.IdeBundle; import com.intellij.openapi.options.ShowSettingsUtil; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.ui.ColorUtil; -import com.intellij.ui.IdeBorderFactory; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -73,7 +73,7 @@ public abstract class AbstractUpdateDialog extends DialogWrapper { "" + messageBody + ""; area.setBackground(UIUtil.getPanelBackground()); - area.setBorder(IdeBorderFactory.createEmptyBorder()); + area.setBorder(JBUI.Borders.empty()); area.setText(text); area.setCaretPosition(0); area.setEditable(false); diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/PluginDownloader.java b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/PluginDownloader.java index 620fcf0383ba..de462197d50b 100644 --- a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/PluginDownloader.java +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/PluginDownloader.java @@ -239,7 +239,7 @@ public class PluginDownloader { indicator.checkCanceled(); indicator.setText2(IdeBundle.message("progress.downloading.plugin", getPluginName())); - return HttpRequests.request(myPluginUrl).gzip(false).forceHttps(myForceHttps).connect(new HttpRequests.RequestProcessor() { + return HttpRequests.request(myPluginUrl).gzip(false).forceHttps(myForceHttps).productNameAsUserAgent().connect(new HttpRequests.RequestProcessor() { @Override public File process(@NotNull HttpRequests.Request request) throws IOException { request.saveToFile(file, indicator); diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/PluginsAdvertiser.java b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/PluginsAdvertiser.java index 474ca139a353..b2264c23b80f 100644 --- a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/PluginsAdvertiser.java +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/PluginsAdvertiser.java @@ -170,7 +170,7 @@ public class PluginsAdvertiser implements StartupActivity { params.forEach((key, value) -> uriBuilder.addParameter(key, value)); boolean forceHttps = IdeaApplication.isLoaded() && UpdateSettings.getInstance().canUseSecureConnection(); - return HttpRequests.request(uriBuilder.toString()).forceHttps(forceHttps).connect(requestProcessor, errorValue, LOG); + return HttpRequests.request(uriBuilder.toString()).forceHttps(forceHttps).productNameAsUserAgent().connect(requestProcessor, errorValue, LOG); } public static void ensureDeleted() { diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/jar/JarFileSystemImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/jar/JarFileSystemImpl.java index a1649b43f6f6..6b331cc24664 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/jar/JarFileSystemImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/jar/JarFileSystemImpl.java @@ -21,6 +21,7 @@ import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.JarFileSystem; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.impl.ArchiveHandler; import com.intellij.openapi.vfs.newvfs.VfsImplUtil; import com.intellij.util.SystemProperties; import com.intellij.util.containers.ContainerUtil; @@ -55,11 +56,16 @@ public class JarFileSystemImpl extends JarFileSystem { @Nullable public File getMirroredFile(@NotNull VirtualFile vFile) { VirtualFile root = getRootByLocal(vFile); - return root == null ? null : getHandler(root).getFileToUse(); + if (root != null) { + ArchiveHandler handler = getHandler(root); + if (handler instanceof JarHandler) return ((JarHandler)handler).getFileToUse(); + return handler.getFile(); + } + return null; } public boolean isMakeCopyOfJar(@NotNull File originalJar) { - if (myNoCopyJarPaths == null || myNoCopyJarPaths.contains(originalJar.getPath())) return false; + if (myNoCopyJarPaths == null || myNoCopyJarPaths.contains(FileUtil.toSystemIndependentName(originalJar.getPath()))) return false; if (myNoCopyJarDir != null && FileUtil.isAncestor(myNoCopyJarDir, originalJar, false)) return false; return true; } @@ -109,7 +115,7 @@ public class JarFileSystemImpl extends JarFileSystem { @NotNull @Override - protected JarHandler getHandler(@NotNull VirtualFile entryFile) { + protected ArchiveHandler getHandler(@NotNull VirtualFile entryFile) { return VfsImplUtil.getHandler(this, entryFile, JarHandler::new); } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/RefreshSessionImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/RefreshSessionImpl.java index 2704496a7ab0..8f2e8f6a1c88 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/RefreshSessionImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/RefreshSessionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -92,14 +92,19 @@ public class RefreshSessionImpl extends RefreshSession { LOG.error("null passed among " + files); } else { - myWorkQueue.add(file); + addFile(file); } } } @Override public void addFile(@NotNull VirtualFile file) { - myWorkQueue.add(file); + if (file instanceof NewVirtualFile) { + myWorkQueue.add(file); + } + else { + LOG.warn("skipped: " + file + " / " + file.getClass()); + } } @Override diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java index 7dc1fc15fd0d..bc66c0fed6da 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java @@ -594,7 +594,7 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry { String childName = event.getChildName(); // assume there is no need to canonicalize names in VFileCreateEvent boolean childExists = !myData.isAdoptedName(childName) && existingNames.contains(childName); - if (!event.isValid(childExists)) { + if (childExists) { childrenToCreate.remove(i); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileSystemEntry.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileSystemEntry.java index 6448a6ad1e18..43138a54eefb 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileSystemEntry.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileSystemEntry.java @@ -384,7 +384,7 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile { @Override public String getPresentableName() { - if (UISettings.getInstance().getHdeKnownExtensionInTabs() && !isDirectory()) { + if (UISettings.getInstance().getHideKnownExtensionInTabs() && !isDirectory()) { final String nameWithoutExtension = getNameWithoutExtension(); return nameWithoutExtension.isEmpty() ? getName() : nameWithoutExtension; } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/FSRecords.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/FSRecords.java index f4cf5c79b40e..5a0f4988932b 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/FSRecords.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/FSRecords.java @@ -564,6 +564,7 @@ public class FSRecords { } private static ResizeableMappedFile getRecords() { + assert DbConnection.myRecords != null:"Vfs should be initialized"; return DbConnection.myRecords; } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/LocalFileSystemRefreshWorker.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/LocalFileSystemRefreshWorker.java index 43eade6db5be..f3bacbc92bbb 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/LocalFileSystemRefreshWorker.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/LocalFileSystemRefreshWorker.java @@ -44,22 +44,21 @@ import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.DosFileAttributes; import java.nio.file.attribute.PosixFileAttributes; +import java.nio.file.attribute.PosixFilePermission; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; -import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE; - -public class LocalFileSystemRefreshWorker { +class LocalFileSystemRefreshWorker { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.newvfs.persistent.RefreshWorker"); private static final Logger LOG_ATTRIBUTES = Logger.getInstance("#com.intellij.openapi.vfs.newvfs.persistent.RefreshWorker_Attributes"); private final boolean myIsRecursive; - private final Queue myRefreshQueue = new Queue(100); + private final Queue myRefreshQueue = new Queue<>(100); private final List myFileEventSet = new ArrayList<>(); private volatile boolean myCancelled; - public LocalFileSystemRefreshWorker(@NotNull NewVirtualFile refreshRoot, boolean isRecursive) { + LocalFileSystemRefreshWorker(@NotNull NewVirtualFile refreshRoot, boolean isRecursive) { myIsRecursive = isRecursive; myRefreshQueue.addLast(refreshRoot); } @@ -82,11 +81,11 @@ public class LocalFileSystemRefreshWorker { NewVirtualFileSystem fs = root.getFileSystem(); FileAttributes rootAttributes = fs.getAttributes(root); if (rootAttributes == null) { - myFileEventSet.add(createDeletionEvent(root)); + addDeletionEventTo(root, myFileEventSet); root.markClean(); return; } - else if (rootAttributes.isDirectory()) { + if (rootAttributes.isDirectory()) { fs = PersistentFS.replaceWithNativeFS(fs); } @@ -145,7 +144,10 @@ public class LocalFileSystemRefreshWorker { private static final AtomicInteger myRequests = new AtomicInteger(); private static final AtomicLong myTime = new AtomicLong(); - private void fullDirRefresh(NewVirtualFileSystem fs, PersistentFS persistence, TObjectHashingStrategy strategy, VirtualDirectoryImpl dir) { + private void fullDirRefresh(NewVirtualFileSystem fs, + PersistentFS persistence, + TObjectHashingStrategy strategy, + VirtualDirectoryImpl dir) { while (true) { // obtaining directory snapshot String[] currentNames; @@ -164,7 +166,8 @@ public class LocalFileSystemRefreshWorker { token.finish(); } - RefreshingFileVisitor refreshingFileVisitor = new RefreshingFileVisitor(dir, persistence, fs, null, Arrays.asList(children), strategy); + RefreshingFileVisitor refreshingFileVisitor = + new RefreshingFileVisitor(dir, persistence, fs, null, Arrays.asList(children), strategy); refreshingFileVisitor.visit(dir); @@ -206,7 +209,7 @@ public class LocalFileSystemRefreshWorker { token.finish(); } - if (cached.size() == 0 && wanted.size() == 0) return; + if (cached.isEmpty() && wanted.isEmpty()) return; RefreshingFileVisitor refreshingFileVisitor = new RefreshingFileVisitor(dir, persistence, fs, wanted, cached, strategy); refreshingFileVisitor.visit(dir); @@ -228,7 +231,8 @@ public class LocalFileSystemRefreshWorker { } } - private static class RefreshCancelledException extends RuntimeException { } + private static class RefreshCancelledException extends RuntimeException { + } private void checkCancelled(@NotNull NewVirtualFile stopAt) { if (myCancelled || ourCancellingCondition != null && ourCancellingCondition.fun(stopAt)) { @@ -249,32 +253,14 @@ public class LocalFileSystemRefreshWorker { private static Function ourCancellingCondition; @TestOnly - public static void setCancellingCondition(@Nullable Function condition) { + static void setCancellingCondition(@Nullable Function condition) { assert ApplicationManager.getApplication().isUnitTestMode(); ourCancellingCondition = condition; } - static VFileEvent createAttributeChangeEvent(@NotNull VirtualFile file, @NotNull String property, Object current, Object upToDate) { - if (LOG.isTraceEnabled()) LOG.trace("update '" + property + "' file=" + file); - return new VFilePropertyChangeEvent(null, file, property, current, upToDate, true); - } - - static VFileEvent createUpdateContentEvent(@NotNull VirtualFile file) { - if (LOG.isTraceEnabled()) LOG.trace("update file=" + file); - return new VFileContentChangeEvent(null, file, file.getModificationStamp(), -1, true); - } - - static VFileEvent createCreationEvent(@NotNull VirtualFile parent, @NotNull String childName, boolean isDirectory, boolean isReCreation) { - if (LOG.isTraceEnabled()) LOG.trace("create parent=" + parent + " name=" + childName + " dir=" + isDirectory); - return new VFileCreateEvent(null, parent, childName, isDirectory, true, isReCreation); - } - - static VFileEvent createDeletionEvent(@Nullable VirtualFile file) { - if (file != null) { - if (LOG.isTraceEnabled()) LOG.trace("delete file=" + file); - return new VFileDeleteEvent(null, file, true); - } - return null; + private static void addDeletionEventTo(@NotNull VirtualFile file, @NotNull Collection myFileEvents) { + if (LOG.isTraceEnabled()) LOG.trace("delete file=" + file); + myFileEvents.add(new VFileDeleteEvent(null, file, true)); } private class RefreshingFileVisitor extends SimpleFileVisitor { @@ -286,25 +272,43 @@ public class LocalFileSystemRefreshWorker { private final PersistentFS myPersistence; private final NewVirtualFileSystem myFs; - public RefreshingFileVisitor(VirtualFile fileOrDir, - PersistentFS persistence, - NewVirtualFileSystem fs, - Collection persistentChildrenToRefresh, - Collection existingPersistentChildren, - TObjectHashingStrategy strategy) { + RefreshingFileVisitor(VirtualFile fileOrDir, + PersistentFS persistence, + NewVirtualFileSystem fs, + Collection persistentChildrenToRefresh, + Collection existingPersistentChildren, + TObjectHashingStrategy strategy) { myFileOrDir = fileOrDir; myPersistence = persistence; myFs = fs; myPersistentChildren = new THashMap<>(existingPersistentChildren.size(), strategy); - myChildrenWeAreInterested = persistentChildrenToRefresh != null ? new THashSet(persistentChildrenToRefresh, strategy) : null; + myChildrenWeAreInterested = persistentChildrenToRefresh != null ? new THashSet<>(persistentChildrenToRefresh, strategy) : null; - for(VirtualFile child:existingPersistentChildren) { + for (VirtualFile child : existingPersistentChildren) { String name = child.getName(); myPersistentChildren.put(name, child); if (myChildrenWeAreInterested != null) myChildrenWeAreInterested.add(name); } } + private void addAttributeChangeEvent(@NotNull VirtualFile file, + @NotNull @VirtualFile.PropName String property, + Object current, + Object upToDate) { + if (LOG.isTraceEnabled()) LOG.trace("update '" + property + "' file=" + file); + myFileEvents.add(new VFilePropertyChangeEvent(null, file, property, current, upToDate, true)); + } + + private void addUpdateContentEvent(@NotNull VirtualFile file) { + if (LOG.isTraceEnabled()) LOG.trace("update file=" + file); + myFileEvents.add(new VFileContentChangeEvent(null, file, file.getModificationStamp(), -1, true)); + } + + private void addCreationEvent(@NotNull VirtualFile parent, @NotNull String childName, boolean isDirectory) { + if (LOG.isTraceEnabled()) LOG.trace("create parent=" + parent + " name=" + childName + " dir=" + isDirectory); + myFileEvents.add(new VFileCreateEvent(null, parent, childName, isDirectory, true)); + } + @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { String name = file.getName(file.getNameCount() - 1).toString(); @@ -312,9 +316,7 @@ public class LocalFileSystemRefreshWorker { if (acceptsFileName(name)) { NewVirtualFile child = (NewVirtualFile)myPersistentChildren.remove(name); if (child == null) { // new file is created - VFileEvent event = - createCreationEvent(myFileOrDir.isDirectory() ? myFileOrDir : myFileOrDir.getParent(), name, attrs.isDirectory(), false); - myFileEvents.add(event); + addCreationEvent(myFileOrDir.isDirectory() ? myFileOrDir : myFileOrDir.getParent(), name, attrs.isDirectory()); return FileVisitResult.CONTINUE; } @@ -327,9 +329,8 @@ public class LocalFileSystemRefreshWorker { if (currentIsDirectory != attrs.isDirectory() || currentIsSymlink != attrs.isSymbolicLink() || currentIsSpecial != attrs.isOther()) { // symlink or directory or special changed - myFileEvents.add(createDeletionEvent(child)); - VFileEvent event = createCreationEvent(myFileOrDir.isDirectory() ? myFileOrDir : myFileOrDir.getParent(), child.getName(), attrs.isDirectory(), true); - myFileEvents.add(event); + addDeletionEventTo(child, myFileEvents); + addCreationEvent(myFileOrDir.isDirectory() ? myFileOrDir : myFileOrDir.getParent(), child.getName(), attrs.isDirectory()); // ignore everything else child.markClean(); return FileVisitResult.CONTINUE; @@ -337,17 +338,18 @@ public class LocalFileSystemRefreshWorker { String currentName = child.getName(); if (!currentName.equals(name)) { - myFileEvents.add(createAttributeChangeEvent(child, VirtualFile.PROP_NAME, currentName, name)); + addAttributeChangeEvent(child, VirtualFile.PROP_NAME, currentName, name); } if (!attrs.isDirectory()) { if (myPersistence.getTimeStamp(child) != attrs.lastModifiedTime().toMillis() || myPersistence.getLastRecordedLength(child) != attrs.size()) { - myFileEvents.add(createUpdateContentEvent(child)); + addUpdateContentEvent(child); child.markClean(); return FileVisitResult.CONTINUE; } - } else { + } + else { if (myIsRecursive) { myRefreshQueue.addLast(child); } @@ -359,24 +361,27 @@ public class LocalFileSystemRefreshWorker { if (attrs instanceof DosFileAttributes) { DosFileAttributes dosFileAttributes = (DosFileAttributes)attrs; isWritable = attrs.isDirectory() || !dosFileAttributes.isReadOnly(); - } else if (attrs instanceof PosixFileAttributes) { - isWritable = ((PosixFileAttributes)attrs).permissions().contains(OWNER_WRITE); - } else { + } + else if (attrs instanceof PosixFileAttributes) { + isWritable = ((PosixFileAttributes)attrs).permissions().contains(PosixFilePermission.OWNER_WRITE); + } + else { isWritable = file.toFile().canWrite(); } if (LOG_ATTRIBUTES.isDebugEnabled()) { - LOG_ATTRIBUTES.debug("file=" + file + " writable vfs=" + child.isWritable() + " persistence=" + currentWritable + " real=" + isWritable); + LOG_ATTRIBUTES + .debug("file=" + file + " writable vfs=" + child.isWritable() + " persistence=" + currentWritable + " real=" + isWritable); } if (currentWritable != isWritable) { - myFileEvents.add(createAttributeChangeEvent(child, VirtualFile.PROP_WRITABLE, currentWritable, isWritable)); + addAttributeChangeEvent(child, VirtualFile.PROP_WRITABLE, currentWritable, isWritable); } if (attrs instanceof DosFileAttributes) { boolean currentHidden = child.is(VFileProperty.HIDDEN); boolean upToDateHidden = ((DosFileAttributes)attrs).isHidden(); if (currentHidden != upToDateHidden) { - myFileEvents.add(createAttributeChangeEvent(child, VirtualFile.PROP_HIDDEN, currentHidden, upToDateHidden)); + addAttributeChangeEvent(child, VirtualFile.PROP_HIDDEN, currentHidden, upToDateHidden); } } @@ -385,15 +390,15 @@ public class LocalFileSystemRefreshWorker { String upToDateTarget = myFs.resolveSymLink(child); String upToDateVfsTarget = upToDateTarget != null ? FileUtil.toSystemIndependentName(upToDateTarget) : null; if (!Comparing.equal(currentTarget, upToDateVfsTarget)) { - myFileEvents.add(createAttributeChangeEvent(child, VirtualFile.PROP_SYMLINK_TARGET, currentTarget, upToDateVfsTarget)); + addAttributeChangeEvent(child, VirtualFile.PROP_SYMLINK_TARGET, currentTarget, upToDateVfsTarget); } } - if(!child.isDirectory()) child.markClean(); + if (!child.isDirectory()) child.markClean(); } return FileVisitResult.CONTINUE; } - protected boolean acceptsFileName(String name) { + boolean acceptsFileName(String name) { return !VfsUtil.isBadName(name) && (myChildrenWeAreInterested == null || myChildrenWeAreInterested.contains(name)); } @@ -404,12 +409,12 @@ public class LocalFileSystemRefreshWorker { Path path = Paths.get(fileOrDir.getPath()); if (fileOrDir.isDirectory()) { Files.walkFileTree(path, EnumSet.noneOf(FileVisitOption.class), 1, this); - } else { + } + else { visitFile(path, Files.readAttributes(path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)); } } catch (AccessDeniedException ignore) { - int a = 1; } catch (IOException ex) { LOG.error(ex); @@ -419,14 +424,15 @@ public class LocalFileSystemRefreshWorker { long l = myTime.addAndGet(System.nanoTime() - started); if (requests % 1000 == 0) { - System.out.println("refresh:" + myRequests + " for " + (l / 1000000)); + System.out.println("refresh:" + myRequests + " for " + l / 1_000_000 + "ms"); } } - public List getEventSet() { - if (myPersistentChildren.size() > 0) { - for(VirtualFile child:myPersistentChildren.values()) { - myFileEvents.add(createDeletionEvent(child)); + @NotNull + List getEventSet() { + if (!myPersistentChildren.isEmpty()) { + for (VirtualFile child : myPersistentChildren.values()) { + addDeletionEventTo(child, myFileEvents); } myPersistentChildren.clear(); } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSImpl.java index d14e7041688b..4b8954808afb 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSImpl.java @@ -24,7 +24,6 @@ import com.intellij.openapi.components.ApplicationComponent; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.LowMemoryWatcher; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.ShutDownTracker; import com.intellij.openapi.util.io.*; import com.intellij.openapi.util.text.StringUtil; @@ -33,13 +32,18 @@ import com.intellij.openapi.vfs.ex.temp.TempFileSystem; import com.intellij.openapi.vfs.newvfs.*; import com.intellij.openapi.vfs.newvfs.events.*; import com.intellij.openapi.vfs.newvfs.impl.*; -import com.intellij.util.*; +import com.intellij.util.ArrayUtil; +import com.intellij.util.BitUtil; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.UriUtil; import com.intellij.util.containers.ConcurrentIntObjectMap; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import com.intellij.util.io.ReplicatorInputStream; import com.intellij.util.io.URLUtil; import com.intellij.util.messages.MessageBus; +import gnu.trove.THashMap; +import gnu.trove.THashSet; import gnu.trove.TIntArrayList; import gnu.trove.TIntHashSet; import org.jetbrains.annotations.NonNls; @@ -57,8 +61,6 @@ import java.util.concurrent.atomic.AtomicBoolean; public class PersistentFSImpl extends PersistentFS implements ApplicationComponent, Disposable { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.newvfs.persistent.PersistentFS"); - private final MessageBus myEventBus; - private final Map myRoots = ContainerUtil.newConcurrentMap(10, 0.4f, JobSchedulerImpl.CORES_COUNT, FileUtil.PATH_HASHING_STRATEGY); private final ConcurrentIntObjectMap myRootsById = ContainerUtil.createConcurrentIntObjectMap(10, 0.4f, JobSchedulerImpl.CORES_COUNT); @@ -68,11 +70,12 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone private final AtomicBoolean myShutDown = new AtomicBoolean(false); private volatile int myStructureModificationCount; + private final BulkFileListener myPublisher; public PersistentFSImpl(@NotNull MessageBus bus) { - myEventBus = bus; ShutDownTracker.getInstance().registerShutdownTask(this::performShutdown); LowMemoryWatcher.register(this::clearIdCache, this); + myPublisher = bus.syncPublisher(VirtualFileManager.VFS_CHANGES); } @Override @@ -615,8 +618,7 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone VFileContentChangeEvent event = new VFileContentChangeEvent(requestor, file, file.getModificationStamp(), modStamp, false); List events = Collections.singletonList(event); - BulkFileListener publisher = myEventBus.syncPublisher(VirtualFileManager.VFS_CHANGES); - publisher.before(events); + myPublisher.before(events); NewVirtualFileSystem delegate = getDelegate(file); OutputStream ioFileStream = delegate.getOutputStream(file, requestor, modStamp, timeStamp); @@ -636,7 +638,7 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone ioFileStream.close(); executeTouch(file, false, event.getModificationStamp()); - publisher.after(events); + myPublisher.after(events); } } } @@ -668,56 +670,113 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone ApplicationManager.getApplication().assertWriteAccessAllowed(); // optimisation: skip all groupings if (event.isValid()) { - BulkFileListener publisher = myEventBus.syncPublisher(VirtualFileManager.VFS_CHANGES); List events = Collections.singletonList(event); - publisher.before(events); + myPublisher.before(events); applyEvent(event); - publisher.after(events); + myPublisher.after(events); } } - // group events by type and containing directory and validate - // "outApplyEvents" will contain handlers applying the grouped events - // e.g. for [delete(d/x), delete(d2/z), delete(d/y), create(d/x), create(d/z), change(d/x)] - // the outApplyEvents will contain: - // [applyDeletions("d/x, d/y", "d2/z"), applyCreations(d/x, d/z), applyEvent(d/x)] - // returns all validated events - private List groupAndValidate(@NotNull List events, @NotNull List outApplyEvents) { - int groupStart=0; - List validated = new ArrayList<>(events.size()); - VFileEvent eventFromGroup = events.get(0); - for (int i = 1; i <= events.size(); i++) { - VFileEvent event = i==events.size() ? null : events.get(i); - if (i==events.size() || !eventFromGroup.getClass().equals(event.getClass())) { - if (eventFromGroup instanceof VFileCreateEvent) { - groupCreations(events, groupStart, i, validated, outApplyEvents); - } - else if (eventFromGroup instanceof VFileDeleteEvent) { - groupDeletions(events, groupStart, i, validated, outApplyEvents); - } - else { - groupOthers(events, groupStart, i, validated, outApplyEvents); - } - groupStart = i; + // Tries to find a group of non-conflicting events in range [startIndex..inEvents.size()). + // Two events are conflicting if the originating file of one event is an ancestor (non-strict) of the file from the other. + // E.g. "change(a/b/c/x.txt)" and "delete(a/b/c)" are conflicting because "a/b/c/x.txt" is under the "a/b/c" directory from the other event. + // + // returns index after the last grouped event. + private static int groupByPath(@NotNull List inEvents, int startIndex, Set files, Set middleDirs) { + // store all paths from all events (including all parents) + // check the each new event's path against this set and if it's there, this event is conflicting + + int i; + for (i = startIndex; i < inEvents.size(); i++) { + VFileEvent event = inEvents.get(i); + String path = event.getPath(); + if (checkIfConflictingEvent(path, files, middleDirs)) { + break; + } + // some synthetic events really are composite events, e.g. VFileMoveEvent = VFileDeleteEvent+VFileCreateEvent, so both paths should be checked for conflicts + String path2 = null; + if (event instanceof VFilePropertyChangeEvent && ((VFilePropertyChangeEvent)event).getPropertyName().equals(VirtualFile.PROP_NAME)) { + VFilePropertyChangeEvent pce = (VFilePropertyChangeEvent)event; + VirtualFile parent = pce.getFile().getParent(); + String newName = (String)pce.getNewValue(); + path2 = parent == null ? newName : parent.getPath()+"/"+newName; + } + else if (event instanceof VFileCopyEvent) { + path2 = ((VFileCopyEvent)event).getFile().getPath(); + } + else if (event instanceof VFileMoveEvent) { + VFileMoveEvent vme = (VFileMoveEvent)event; + String newName = vme.getFile().getName(); + path2 = vme.getNewParent().getPath() + "/" + newName; + } + if (path2 != null && !path2.equals(path) && checkIfConflictingEvent(path2, files, middleDirs)) { + break; } - eventFromGroup = event; } - return validated; + + return i; } - // events[start..end) are all VFileCreateEvent + private static boolean checkIfConflictingEvent(String path, Set files, Set middleDirs) { + if (!files.add(path) || middleDirs.contains(path)) { + // conflicting event found for (non-strict) descendant, stop + return true; + } + int li = path.length(); + while (true) { + int liPrev = path.lastIndexOf('/', li-1); + if (liPrev == -1) break; + String parentDir = path.substring(0, liPrev); + if (files.contains(parentDir)) { + // conflicting event found for ancestor, stop + return true; + } + if (!middleDirs.add(parentDir)) break; // all parents up already stored, stop + li = liPrev; + } + + return false; + } + + // finds a group of non-conflicting events, validate them. + // "outApplyEvents" will contain handlers for applying the grouped events + // "outValidatedEvents" will contain events for which VFileEvent.isValid() is true + // return index after the last processed event + private int groupAndValidate(@NotNull List events, + int startIndex, + @NotNull List outApplyEvents, + @NotNull List outValidatedEvents, + @NotNull Set files, @NotNull Set middleDirs) { + int endIndex = groupByPath(events, startIndex, files, middleDirs); + // since all events in the group are mutually non-conflicting, we can re-arrange creations/deletions together + groupCreations(events, startIndex, endIndex, outValidatedEvents, outApplyEvents); + groupDeletions(events, startIndex, endIndex, outValidatedEvents, outApplyEvents); + groupOthers(events, startIndex, endIndex, outValidatedEvents, outApplyEvents); + + return endIndex; + } + + // find all VFileCreateEvent events in [start..end) // group them by parent directory, validate in bulk for each directory, and return "applyCreations()" runnable private void groupCreations(@NotNull List events, int start, int end, @NotNull List outValidated, @NotNull List outApplyEvents) { - MultiMap grouped = MultiMap.createSmart(); + MultiMap grouped = new MultiMap(){ + @NotNull + @Override + protected Map> createMap() { + return new THashMap<>(end-start); + } + }; for (int i = start; i < end; i++) { - VFileCreateEvent event = (VFileCreateEvent)events.get(i); + VFileEvent e = events.get(i); + if (!(e instanceof VFileCreateEvent)) continue; + VFileCreateEvent event = (VFileCreateEvent)e; VirtualDirectoryImpl parent = (VirtualDirectoryImpl)event.getParent(); grouped.putValue(parent, event); } @@ -740,47 +799,42 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone }); } } - - // events[start..end) are all VFileDeleteEvent + + // find all VFileDeleteEvent events in [start..end) // group them by parent directory (can be null), filter out files which parent dir is to be deleted too, and return "applyDeletions()" runnable private void groupDeletions(@NotNull List events, int start, int end, @NotNull List outValidated, @NotNull List outApplyEvents) { - List> deletionsWithPath = new ArrayList<>(end-start); - + MultiMap grouped = new MultiMap(){ + @NotNull + @Override + protected Map> createMap() { + return new HashMap<>(end-start); // can be null keys + } + }; + boolean hasValidEvents = false; for (int i = start; i < end; i++) { VFileEvent event = events.get(i); - if (!event.isValid()) continue; - deletionsWithPath.add(Pair.create((VFileDeleteEvent)event, event.getPath())); + if (!(event instanceof VFileDeleteEvent) || !event.isValid()) continue; + VFileDeleteEvent de = (VFileDeleteEvent)event; + @Nullable VirtualDirectoryImpl parent = (VirtualDirectoryImpl)de.getFile().getParent(); + grouped.putValue(parent, de); + outValidated.add(event); + hasValidEvents = true; } - if (!deletionsWithPath.isEmpty()) { - filterOutInvalidDeletions(deletionsWithPath); - - MultiMap grouped = MultiMap.create(); // can be nulls - boolean hasValidEvents = false; - for (Pair pair : deletionsWithPath) { - if (pair == null) continue; // filtered out - VFileDeleteEvent event = pair.getFirst(); - @Nullable VirtualDirectoryImpl parent = (VirtualDirectoryImpl)event.getFile().getParent(); - grouped.putValue(parent, event); - outValidated.add(event); - hasValidEvents = true; - } - - if (hasValidEvents) { - outApplyEvents.add(() -> { - clearIdCache(); - applyDeletions(grouped); - incStructuralModificationCount(); - }); - } + if (hasValidEvents) { + outApplyEvents.add(() -> { + clearIdCache(); + applyDeletions(grouped); + incStructuralModificationCount(); + }); } } - // events[start..end) are neither VFileCreateEvent nor VFileDeleteEvent + // find events other than VFileCreateEvent or VFileDeleteEvent in [start..end) // validate and return "applyEvent()" runnable for each event because it's assumed there won't be too many of them private void groupOthers(@NotNull List events, int start, @@ -789,46 +843,35 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone @NotNull List outApplyEvents) { for (int i = start; i < end; i++) { VFileEvent event = events.get(i); - if (!event.isValid()) continue; + if (event instanceof VFileCreateEvent || event instanceof VFileDeleteEvent || !event.isValid()) continue; outValidated.add(event); outApplyEvents.add(() -> applyEvent(event)); } } - // filter out files which parent dir is to be deleted too. nullize redundant events in the list. - private static void filterOutInvalidDeletions(@NotNull List> deletionsWithPath) { - boolean caseSensitive = deletionsWithPath.get(0).getFirst().getFile().getFileSystem().isCaseSensitive(); - deletionsWithPath.sort((o1, o2) -> StringUtil.compare(o1.getSecond(), o2.getSecond(), !caseSensitive)); - - // loop over sorted paths and if there are "/dir1" and "/dir1/file2" neighbors filter out the latter. - String prevPath = deletionsWithPath.get(0).getSecond(); - for (int i = 1; i < deletionsWithPath.size(); i++) { - String path = deletionsWithPath.get(i).getSecond(); - int prefixLength = StringUtil.commonPrefixLength(prevPath, path, !caseSensitive); - if (prefixLength == prevPath.length() && prefixLength < path.length() && path.charAt(prefixLength) == '/') { - deletionsWithPath.set(i, null); - // continue to compare with the prevPath - } - else { - prevPath = path; - } - } - } - @Override public void processEvents(@NotNull List events) { ApplicationManager.getApplication().assertWriteAccessAllowed(); - List applyEvents = new ArrayList<>(); - List validated = groupAndValidate(events, applyEvents); + int startIndex = 0; + List applyEvents = new ArrayList<>(events.size()); + List validated = new ArrayList<>(events.size()); + Set files = new THashSet<>(events.size()); + Set middleDirs = new THashSet<>(events.size()); + while (startIndex != events.size()) { + applyEvents.clear(); + validated.clear(); + files.clear(); + middleDirs.clear(); + startIndex = groupAndValidate(events, startIndex, applyEvents, validated, files, middleDirs); - if (!validated.isEmpty()) { - BulkFileListener publisher = myEventBus.syncPublisher(VirtualFileManager.VFS_CHANGES); - publisher.before(validated); + if (!validated.isEmpty()) { + myPublisher.before(validated); - applyEvents.forEach(Runnable::run); + applyEvents.forEach(Runnable::run); - publisher.after(validated); + myPublisher.after(validated); + } } } @@ -1157,7 +1200,7 @@ public class PersistentFSImpl extends PersistentFS implements ApplicationCompone private void executeDelete(@NotNull VirtualFile file) { if (!file.exists()) { - LOG.error("Deleting a file, which does not exist: " + file.getPath()); + LOG.error("Deleting a file which does not exist: " +((VirtualFileWithId)file).getId()+ " "+file.getPath()); return; } clearIdCache(); diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/RefreshWorker.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/RefreshWorker.java index e42193fe8e20..366dea8d8330 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/RefreshWorker.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/RefreshWorker.java @@ -240,7 +240,7 @@ public class RefreshWorker { String name = pair.first; FileAttributes childAttributes = pair.second; if (childAttributes != null) { - scheduleCreation(dir, name, childAttributes.isDirectory(), false); + scheduleCreation(dir, name, childAttributes.isDirectory()); } else { LOG.warn("[+] fs=" + fs + " dir=" + dir + " name=" + name); @@ -333,7 +333,7 @@ public class RefreshWorker { String name = pair.first; FileAttributes childAttributes = pair.second; if (childAttributes != null) { - scheduleCreation(dir, name, childAttributes.isDirectory(), false); + scheduleCreation(dir, name, childAttributes.isDirectory()); } } @@ -397,14 +397,14 @@ public class RefreshWorker { if (currentIsDirectory != upToDateIsDirectory || currentIsSymlink != upToDateIsSymlink || currentIsSpecial != upToDateIsSpecial) { scheduleDeletion(child); - scheduleCreation(parent, child.getName(), upToDateIsDirectory, true); + scheduleCreation(parent, child.getName(), upToDateIsDirectory); return true; } return false; } - private void scheduleAttributeChange(@NotNull VirtualFile file, @NotNull String property, Object current, Object upToDate) { + private void scheduleAttributeChange(@NotNull VirtualFile file, @VirtualFile.PropName @NotNull String property, Object current, Object upToDate) { if (LOG.isTraceEnabled()) LOG.trace("update '" + property + "' file=" + file); myEvents.add(new VFilePropertyChangeEvent(null, file, property, current, upToDate, true)); } @@ -414,9 +414,9 @@ public class RefreshWorker { myEvents.add(new VFileContentChangeEvent(null, file, file.getModificationStamp(), -1, true)); } - private void scheduleCreation(@NotNull VirtualFile parent, @NotNull String childName, boolean isDirectory, boolean isReCreation) { + private void scheduleCreation(@NotNull VirtualFile parent, @NotNull String childName, boolean isDirectory) { if (LOG.isTraceEnabled()) LOG.trace("create parent=" + parent + " name=" + childName + " dir=" + isDirectory); - myEvents.add(new VFileCreateEvent(null, parent, childName, isDirectory, true, isReCreation)); + myEvents.add(new VFileCreateEvent(null, parent, childName, isDirectory, true)); } private void scheduleDeletion(@Nullable VirtualFile file) { diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/WelcomeBalloonLayoutImpl.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/WelcomeBalloonLayoutImpl.java index 735855bf3b98..937dba9ccb48 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/WelcomeBalloonLayoutImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/WelcomeBalloonLayoutImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -94,12 +94,13 @@ public class WelcomeBalloonLayoutImpl extends BalloonLayoutImpl { pane.getVerticalScrollBar().addComponentListener(new ComponentAdapter() { @Override public void componentShown(ComponentEvent e) { - pane.setBorder(IdeBorderFactory.createEmptyBorder(SystemInfo.isMac ? 2 : 1, 0, 1, 1)); + int top = SystemInfo.isMac ? 2 : 1; + pane.setBorder(JBUI.Borders.empty(top, 0, 1, 1)); } @Override public void componentHidden(ComponentEvent e) { - pane.setBorder(IdeBorderFactory.createEmptyBorder()); + pane.setBorder(JBUI.Borders.empty()); } }); diff --git a/platform/platform-impl/src/com/intellij/platform/GeneratorPeerImpl.java b/platform/platform-impl/src/com/intellij/platform/GeneratorPeerImpl.java index d6b48a6d1dd4..da58a59d7934 100644 --- a/platform/platform-impl/src/com/intellij/platform/GeneratorPeerImpl.java +++ b/platform/platform-impl/src/com/intellij/platform/GeneratorPeerImpl.java @@ -23,7 +23,6 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; public class GeneratorPeerImpl implements ProjectGeneratorPeer { - private static final JPanel DEFAULT_COMPONENT = new JPanel(); private static final Object DEFAULT_SETTINGS = new Object(); @NotNull private final T mySettings; @NotNull private final JComponent myComponent; @@ -35,7 +34,7 @@ public class GeneratorPeerImpl implements ProjectGeneratorPeer { public GeneratorPeerImpl() { //noinspection unchecked - this((T)DEFAULT_SETTINGS, DEFAULT_COMPONENT); + this((T)DEFAULT_SETTINGS, new JPanel()); } @NotNull diff --git a/platform/platform-impl/src/com/intellij/platform/ProjectGeneratorPeer.java b/platform/platform-impl/src/com/intellij/platform/ProjectGeneratorPeer.java index 31b34ecba045..38249ba26b4c 100644 --- a/platform/platform-impl/src/com/intellij/platform/ProjectGeneratorPeer.java +++ b/platform/platform-impl/src/com/intellij/platform/ProjectGeneratorPeer.java @@ -49,5 +49,25 @@ public interface ProjectGeneratorPeer { boolean isBackgroundJobRunning(); + interface SettingsListener { + void stateChanged(boolean validSettings); + } + + /** + * Adds settings state listener to validate user input + */ + default void addSettingsListener(@NotNull SettingsListener listener) { + addSettingsStateListener(new WebProjectGenerator.SettingsStateListener() { + @Override + public void stateChanged(boolean validSettings) { + listener.stateChanged(validSettings); + } + }); + } + + /** + * Please use {@link #addSettingsListener(SettingsListener)} method instead + */ + @Deprecated void addSettingsStateListener(@NotNull WebProjectGenerator.SettingsStateListener listener); } diff --git a/platform/platform-impl/src/com/intellij/platform/WebProjectGenerator.java b/platform/platform-impl/src/com/intellij/platform/WebProjectGenerator.java index 151bd786418e..fb6619283a78 100644 --- a/platform/platform-impl/src/com/intellij/platform/WebProjectGenerator.java +++ b/platform/platform-impl/src/com/intellij/platform/WebProjectGenerator.java @@ -28,10 +28,10 @@ import javax.swing.*; * and to small IDE (PhpStorm, WebStorm etc. available via File -> 'New Project...'). * * @author Sergey Simonchik + * + * Deprecated since 2017.3. Please use 'WebProjectTemplate' class instead */ - @Deprecated -//[kradima] to be removed in 2017.3 public abstract class WebProjectGenerator extends DirectoryProjectGeneratorBase { @Nullable public Integer getPreferredDescriptionWidth() { @@ -54,6 +54,9 @@ public abstract class WebProjectGenerator extends DirectoryProjectGeneratorBa public abstract String getDescription(); + /** + * Deprecated since 2017.3. Please use 'ProjectGeneratorPeer' class instead + */ @Deprecated public interface GeneratorPeer extends ProjectGeneratorPeer { @NotNull @@ -73,6 +76,9 @@ public abstract class WebProjectGenerator extends DirectoryProjectGeneratorBa void addSettingsStateListener(@NotNull SettingsStateListener listener); } + /* + * Deprecated since 2017.3. Please use 'ProjectGeneratorPeer.SettingsListener' class instead + */ @Deprecated public interface SettingsStateListener { void stateChanged(boolean validSettings); diff --git a/platform/platform-impl/src/com/intellij/ui/AbstractTitledSeparatorWithIcon.java b/platform/platform-impl/src/com/intellij/ui/AbstractTitledSeparatorWithIcon.java index bfd4a2c45136..1d8a0e5e819f 100644 --- a/platform/platform-impl/src/com/intellij/ui/AbstractTitledSeparatorWithIcon.java +++ b/platform/platform-impl/src/com/intellij/ui/AbstractTitledSeparatorWithIcon.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -16,6 +16,7 @@ package com.intellij.ui; import com.intellij.openapi.vcs.changes.RefreshablePanel; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; @@ -50,7 +51,7 @@ public abstract class AbstractTitledSeparatorWithIcon extends JPanel { new GridBagConstraints(1, 0, GridBagConstraints.REMAINDER, 1, 1, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(3, 0, 0, 0), 0, 0); add(mySeparator, gb); - setBorder(IdeBorderFactory.createEmptyBorder(3, 0, 5, 5)); + setBorder(JBUI.Borders.empty(3, 0, 5, 5)); myLabel.setFont(UIUtil.getTitledBorderFont()); myLabel.setForeground(UIUtil.getLabelForeground()); originalText = text; diff --git a/platform/platform-impl/src/com/intellij/ui/AppUIUtil.java b/platform/platform-impl/src/com/intellij/ui/AppUIUtil.java index 914cc3e3826e..0cf54e97fbf1 100644 --- a/platform/platform-impl/src/com/intellij/ui/AppUIUtil.java +++ b/platform/platform-impl/src/com/intellij/ui/AppUIUtil.java @@ -66,6 +66,7 @@ import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED; public class AppUIUtil { private static final String VENDOR_PREFIX = "jetbrains-"; private static final boolean DEBUG_MODE = SystemProperties.getBooleanProperty("idea.debug.mode", false); + private static boolean ourMacDocIconSet = false; public static void updateWindowIcon(@NotNull Window window) { ApplicationInfoEx appInfo = ApplicationInfoImpl.getShadowInstance(); @@ -95,8 +96,9 @@ public class AppUIUtil { if (!SystemInfo.isMac) { window.setIconImages(images); } - else if (DEBUG_MODE) { + else if (DEBUG_MODE && !ourMacDocIconSet) { MacAppIcon.setDockIcon(ImageUtil.toBufferedImage(images.get(0))); + ourMacDocIconSet = true; } } } diff --git a/platform/platform-impl/src/com/intellij/ui/CellRendererPanel.java b/platform/platform-impl/src/com/intellij/ui/CellRendererPanel.java index 7b79211af8c3..96f3bc3b16ca 100644 --- a/platform/platform-impl/src/com/intellij/ui/CellRendererPanel.java +++ b/platform/platform-impl/src/com/intellij/ui/CellRendererPanel.java @@ -16,6 +16,7 @@ package com.intellij.ui; import com.intellij.util.ui.JBInsets; +import sun.awt.AWTAccessor; import javax.swing.*; import java.awt.*; @@ -99,6 +100,14 @@ public class CellRendererPanel extends JPanel { return getComponent(0).getPreferredSize(); } + @Override + public void reshape(int x, int y, int w, int h) { + // suppress per-cell "moved" and "resized" events on paint + // see Component#setBounds, Component#notifyNewBounds + AWTAccessor.getComponentAccessor().setLocation(this, x, y); + AWTAccessor.getComponentAccessor().setSize(this, w, h); + } + public void invalidate() { } diff --git a/platform/platform-impl/src/com/intellij/ui/ColorPicker.java b/platform/platform-impl/src/com/intellij/ui/ColorPicker.java index 00644cda27e3..3976772ceb01 100644 --- a/platform/platform-impl/src/com/intellij/ui/ColorPicker.java +++ b/platform/platform-impl/src/com/intellij/ui/ColorPicker.java @@ -384,7 +384,7 @@ public class ColorPicker extends JPanel implements ColorListener, DocumentListen pipette.setUI(new BasicButtonUI()); pipette.setRolloverEnabled(true); pipette.setIcon(AllIcons.Ide.Pipette); - pipette.setBorder(IdeBorderFactory.createEmptyBorder()); + pipette.setBorder(JBUI.Borders.empty()); pipette.setRolloverIcon(AllIcons.Ide.Pipette_rollover); pipette.setFocusable(false); pipette.addActionListener(new ActionListener() { diff --git a/platform/platform-impl/src/com/intellij/ui/ExpandableActions.java b/platform/platform-impl/src/com/intellij/ui/ExpandableActions.java new file mode 100644 index 000000000000..31020d114529 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ui/ExpandableActions.java @@ -0,0 +1,68 @@ +/* + * Copyright 2000-2017 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.ui; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.project.DumbAwareAction; + +import javax.swing.JComponent; +import java.util.function.Consumer; + +import static com.intellij.openapi.actionSystem.PlatformDataKeys.CONTEXT_COMPONENT; + +public abstract class ExpandableActions extends DumbAwareAction { + private final Consumer consumer; + + private ExpandableActions(Consumer consumer) { + setEnabledInModalContext(true); + this.consumer = consumer; + } + + private static Expandable getExpandable(AnActionEvent event) { + Object component = event.getData(CONTEXT_COMPONENT); + if (component instanceof Expandable) return (Expandable)component; + if (component instanceof JComponent) { + JComponent container = (JComponent)component; + Object property = container.getClientProperty(Expandable.class); + if (property instanceof Expandable) return (Expandable)property; + } + return null; + } + + @Override + public void update(AnActionEvent event) { + Expandable expandable = getExpandable(event); + event.getPresentation().setEnabled(expandable != null); + } + + @Override + public void actionPerformed(AnActionEvent event) { + Expandable expandable = getExpandable(event); + if (expandable != null) consumer.accept(expandable); + } + + public static final class Expand extends ExpandableActions { + public Expand() { + super(Expandable::expand); + } + } + + public static final class Collapse extends ExpandableActions { + public Collapse() { + super(Expandable::collapse); + } + } +} diff --git a/platform/platform-impl/src/com/intellij/ui/FontComboBox.java b/platform/platform-impl/src/com/intellij/ui/FontComboBox.java index 53fbdb705b80..b3fbb48f3a90 100644 --- a/platform/platform-impl/src/com/intellij/ui/FontComboBox.java +++ b/platform/platform-impl/src/com/intellij/ui/FontComboBox.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -23,12 +23,14 @@ import com.intellij.openapi.util.Comparing; import com.intellij.util.ui.FontInfo; import org.jetbrains.annotations.Nullable; -import java.awt.Dimension; +import javax.swing.*; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import javax.swing.plaf.basic.ComboPopup; +import java.awt.*; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import javax.swing.AbstractListModel; -import javax.swing.ComboBoxModel; /** * @author Sergey.Malenkov @@ -53,6 +55,22 @@ public final class FontComboBox extends ComboBox { setPreferredSize(size); setSwingPopup(true); setRenderer(RENDERER); + getModel().addListDataListener(new ListDataListener() { + @Override + public void intervalAdded(ListDataEvent e) {} + + @Override + public void intervalRemoved(ListDataEvent e) {} + + @Override + public void contentsChanged(ListDataEvent e) { + ComboPopup popup = FontComboBox.this.getPopup(); + if (popup != null && popup.isVisible()) { + popup.hide(); + popup.show(); + } + } + }); } public boolean isMonospacedOnly() { diff --git a/platform/platform-impl/src/com/intellij/ui/RightMarginEditorCustomization.java b/platform/platform-impl/src/com/intellij/ui/RightMarginEditorCustomization.java index 074c822e60f9..afbdaee5f126 100644 --- a/platform/platform-impl/src/com/intellij/ui/RightMarginEditorCustomization.java +++ b/platform/platform-impl/src/com/intellij/ui/RightMarginEditorCustomization.java @@ -15,7 +15,6 @@ */ package com.intellij.ui; -import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.ex.EditorEx; import org.jetbrains.annotations.NotNull; @@ -34,13 +33,7 @@ public class RightMarginEditorCustomization extends SimpleEditorCustomization { @Override public void customize(@NotNull EditorEx editor) { - if (isEnabled()) { - editor.getSettings().setRightMarginShown(true); - editor.getSettings().setRightMargin(getRightMarginColumns()); - // ensure we've got a monospace font by loading up the global editor scheme - editor.setColorsScheme(EditorColorsManager.getInstance().getGlobalScheme()); - } else { - editor.getSettings().setRightMarginShown(false); - } + editor.getSettings().setRightMarginShown(isEnabled()); + editor.getSettings().setRightMargin(getRightMarginColumns()); } } diff --git a/platform/platform-impl/src/com/intellij/ui/popup/PopupDispatcher.java b/platform/platform-impl/src/com/intellij/ui/popup/PopupDispatcher.java index 3d2d52a6a19d..fbd9ce1c2771 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/PopupDispatcher.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/PopupDispatcher.java @@ -20,6 +20,7 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ex.ApplicationManagerEx; import com.intellij.openapi.ui.popup.IdePopupEventDispatcher; import com.intellij.openapi.ui.popup.JBPopup; +import com.intellij.openapi.util.Disposer; import org.jetbrains.annotations.Nullable; import javax.swing.*; @@ -111,7 +112,7 @@ public class PopupDispatcher implements AWTEventListener, KeyEventDispatcher, Id public static boolean disposeActiveWizard() { if (ourActiveWizardRoot != null) { ourActiveWizardRoot.disposeChildren(); - ourActiveWizardRoot.dispose(); + Disposer.dispose(ourActiveWizardRoot); return true; } diff --git a/platform/platform-impl/src/com/intellij/ui/popup/WizardPopup.java b/platform/platform-impl/src/com/intellij/ui/popup/WizardPopup.java index 36ff34f31bcb..727f0fb11d79 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/WizardPopup.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/WizardPopup.java @@ -261,7 +261,7 @@ public abstract class WizardPopup extends AbstractPopup implements ActionListene protected void disposeAllParents(InputEvent e) { myDisposeEvent = e; - dispose(); + Disposer.dispose(this); if (myParent != null) { myParent.disposeAllParents(null); } diff --git a/platform/platform-impl/src/com/intellij/ui/popup/list/PopupListElementRenderer.java b/platform/platform-impl/src/com/intellij/ui/popup/list/PopupListElementRenderer.java index 1362b71ffb57..298391b936e1 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/list/PopupListElementRenderer.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/list/PopupListElementRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -25,8 +25,8 @@ import com.intellij.openapi.ui.popup.ListPopupStep; import com.intellij.openapi.ui.popup.ListPopupStepEx; import com.intellij.openapi.ui.popup.util.BaseListPopupStep; import com.intellij.ui.ColorUtil; -import com.intellij.ui.IdeBorderFactory; import com.intellij.util.ArrayUtil; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.Nullable; @@ -76,7 +76,7 @@ public class PopupListElementRenderer extends GroupedItemsListRenderer { createLabel(); panel.add(myTextLabel, BorderLayout.CENTER); myShortcutLabel = new JLabel(); - myShortcutLabel.setBorder(IdeBorderFactory.createEmptyBorder(0, 0, 0, 3)); + myShortcutLabel.setBorder(JBUI.Borders.empty(0, 0, 0, 3)); Color color = UIManager.getColor("MenuItem.acceleratorForeground"); myShortcutLabel.setForeground(color); panel.add(myShortcutLabel, BorderLayout.EAST); diff --git a/platform/platform-resources-en/src/messages/ExecutionBundle.properties b/platform/platform-resources-en/src/messages/ExecutionBundle.properties index caceda316540..6e53a41794a5 100644 --- a/platform/platform-resources-en/src/messages/ExecutionBundle.properties +++ b/platform/platform-resources-en/src/messages/ExecutionBundle.properties @@ -396,6 +396,8 @@ run.dashboard.edit.configuration.action.name=Edit Configuration run.dashboard.edit.configuration.dialog.title=Edit Configuration run.dashboard.remove.configuration.action.name=Remove Configuration run.dashboard.remove.configuration.action.description=Remove Configuration(s) +run.dashboard.group.configurations.action.name=Group Configurations +run.dashboard.ungroup.configurations.action.name=Ungroup Configurations run.dashboard.show.configurations.action.name=Show Configurations run.dashboard.previous.configuration.action.name=Previous Started Configuration run.dashboard.next.configuration.action.name=Next Started Configuration @@ -404,3 +406,5 @@ run.dashboard.remove.configuration.dialog.message=Are you sure to remove selecte run.dashboard.started.group.name=Started run.dashboard.stopped.group.name=Stopped run.dashboard.failed.group.name=Failed +run.dashboard.group.configurations.title=Group Configurations +run.dashboard.group.configurations.label=Group Name: diff --git a/platform/platform-resources-en/src/messages/IdeBundle.properties b/platform/platform-resources-en/src/messages/IdeBundle.properties index 609d748e59ea..6d0221b10f15 100644 --- a/platform/platform-resources-en/src/messages/IdeBundle.properties +++ b/platform/platform-resources-en/src/messages/IdeBundle.properties @@ -276,7 +276,6 @@ message.text.files.do.not.exist=The following files don''t exists: < select.in.project.settings=Project Structure select.in.file.structure=File Structure -message.nothing.to.show.in.structure.view=Nothing to show in the Structure View error.license.collision=This license is being used elsewhere on the network by {0}.\nThe license is per-seat and cannot be used by different users concurrently.\nWould you like to re-activate or shut down? title.license.collision.detected=License Collision Detected message.licensed.to=Licensed to {0} @@ -1158,6 +1157,10 @@ FileChooser.refreshActionLabelText=Refresh browsers.settings=Web Browsers updates.check.period.on.exit=On every exit +vmoptions.warn.message=The code cache size of {0}m is too low. \ + Please increase it to {1}m (or more) to avoid performance degradation. \ + You could do it via "Help | Edit Custom VM Options". + unsupported.jvm.ea.message=Early Access Java versions may cause compatibility issues. Please use stable release. outdated.jvm.version.message1=IDE''s Java runtime ({0}) is outdated - this may cause stability issues. Please update to {1} or newer, or switch to the bundled Java runtime outdated.jvm.version.message2=IDE''s Java runtime ({0}) is outdated - this may cause stability issues. Please update to {1} or newer diff --git a/platform/platform-resources-en/src/messages/InspectionsBundle.properties b/platform/platform-resources-en/src/messages/InspectionsBundle.properties index 69fe9f651d4b..6443ecb8003d 100644 --- a/platform/platform-resources-en/src/messages/InspectionsBundle.properties +++ b/platform/platform-resources-en/src/messages/InspectionsBundle.properties @@ -70,6 +70,7 @@ configure.checker.option.assert.false.method.panel.title=Assert False Methods #messages from dataflow inspection dataflow.message.npe.method.invocation=Method invocation #ref #loc may produce java.lang.NullPointerException +dataflow.message.npe.methodref.invocation=Method reference invocation #ref #loc may produce java.lang.NullPointerException dataflow.message.npe.array.access=Array access #ref #loc may produce java.lang.NullPointerException dataflow.message.npe.field.access.sure=Dereference of #ref #loc will produce java.lang.NullPointerException dataflow.message.npe.field.access=Dereference of #ref #loc may produce java.lang.NullPointerException @@ -83,12 +84,14 @@ dataflow.message.unreachable.switch.label=Switch label #ref #loc is dataflow.message.pointless.assignment.expression=Condition #ref #loc at the left side of assignment expression is always {0}. Can be simplified dataflow.message.passing.null.argument=Passing null argument to parameter annotated as @NotNull dataflow.message.passing.nullable.argument=Argument #ref #loc might be null +dataflow.message.passing.nullable.argument.methodref=Method reference argument might be null dataflow.message.assigning.null=null is assigned to a variable that is annotated with @NotNull dataflow.message.assigning.nullable=Expression #ref might evaluate to null but is assigned to a variable that is annotated with @NotNull dataflow.message.return.null.from.notnull=null is returned by the method declared as @{0} dataflow.message.return.nullable.from.notnull=Expression #ref might evaluate to null but is returned by the method declared as @{0} dataflow.message.return.null.from.notnullable=null is returned by the method which is not declared as @{0} dataflow.message.return.nullable.from.notnullable=Expression #ref might evaluate to null but is returned by the method which is not declared as @{0} +dataflow.message.return.nullable.from.notnull.function=Function may return null, but it's not allowed here dataflow.message.unboxing=Unboxing of #ref #loc may produce java.lang.NullPointerException dataflow.message.unboxing.method.reference=Use of #ref #loc would need unboxing which may produce java.lang.NullPointerException dataflow.too.complex=Method #ref is too complex to analyze by data flow algorithm @@ -324,6 +327,7 @@ inspection.surround.if.quickfix=Surround with ''if ({0} != null)'' inspection.replace.ternary.quickfix=Replace with ''{0} != null ?:'' inspection.surround.if.family=Surround with if inspection.dependency.configure.button.text=Configure dependency rules +inspection.surround.requirenonnull.quickfix=Replace with ''Objects.requireNonNull({0})'' inspection.javadoc.label.text=Additional Javadoc Tags: inspection.javadoc.dialog.title=Edit Additional Javadoc Tags @@ -480,10 +484,10 @@ inspection.compiler.javac.quirks.anno.array.comma.fix=Remove trailing comma inspection.compiler.javac.quirks.qualifier.type.args.problem=Generics in qualifier reference may cause compilation error in some Javac versions (e.g. JDK 5 and JDK 6). inspection.compiler.javac.quirks.qualifier.type.args.fix=Remove generic parameter -inspection.redundant.requires.statement.name=Redundant 'requires' statement in module-info -inspection.redundant.requires.statement.description=Redundant statement ''requires {0}'' -inspection.redundant.requires.statement.fix.family=Delete redundant 'requires' statement -inspection.redundant.requires.statement.fix.name=Delete statement ''requires {0}'' +inspection.redundant.requires.statement.name=Redundant 'requires' directive in module-info +inspection.redundant.requires.statement.description=Redundant directive ''requires {0}'' +inspection.redundant.requires.statement.fix.family=Delete redundant 'requires' directive +inspection.redundant.requires.statement.fix.name=Delete directive ''requires {0}'' inspection.root.node.title=Inspections @@ -767,7 +771,7 @@ inspection.lambda.to.method.call.fix.family.name=Replace lambda expression with inspection.lambda.to.method.call.fix.name=Replace lambda expression with ''{0}'' inspection.module.exports.package.to.itself=Module exports/opens package to itself -exports.to.itself.delete.statement.fix=Delete statement +exports.to.itself.delete.statement.fix=Delete directive exports.to.itself.delete.module.ref.fix=Delete reference to module ''{0}'' inspection.requires.auto.module=Dependencies on automatic modules diff --git a/platform/platform-resources-en/src/messages/VcsBundle.properties b/platform/platform-resources-en/src/messages/VcsBundle.properties index b81cae50d33e..679fdf77e564 100644 --- a/platform/platform-resources-en/src/messages/VcsBundle.properties +++ b/platform/platform-resources-en/src/messages/VcsBundle.properties @@ -113,10 +113,6 @@ message.title.cannot.open.editor=Cannot Open Editor #configuration column.info.configure.vcses.directory=Directory column.name.configure.vcses.vcs=VCS -dialog.title.version.control.configurations=Version Control Configurations -message.text.unable.to.save.settings=Unable to save settings. {0} -message.title.unable.to.save.settings=Unable To Save Settings -label.configure.vcses.available.vcses=Configure available version controls: border.display.dialog.when.commands.invoked=Display options dialog when these commands are invoked border.display.dialog.for.files.creation.deletion=Files Creation/Deletion checkbox.show.clear.read.only.status.dialog=Show "&Clear Read-only Status" Dialog @@ -134,7 +130,6 @@ version.control.main.configurable.name=Version Control configuration.tab.name.general.settings=General Settings configuration.commit.message.margin.title=Commit message right margin configuration.commit.message.margin.prompt=Commit message right margin (columns): -button.configure=&Configure VCS... action.name.rollback=Rollback command.name.rollback.change=Rollback Change action.name.show.difference=Show Difference diff --git a/platform/platform-resources/src/META-INF/LangExtensions.xml b/platform/platform-resources/src/META-INF/LangExtensions.xml index 79a2a0ddad5e..05a5c35c47e7 100644 --- a/platform/platform-resources/src/META-INF/LangExtensions.xml +++ b/platform/platform-resources/src/META-INF/LangExtensions.xml @@ -537,6 +537,8 @@ + diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index 7ea4db595da6..b1523ce0dd02 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -195,9 +195,9 @@ serviceImplementation="com.intellij.openapi.roots.ui.FileAppearanceServiceImpl"/> - - - + + + @@ -380,24 +380,24 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - + diff --git a/platform/platform-resources/src/componentSets/Platform.xml b/platform/platform-resources/src/componentSets/Platform.xml index 5d45fcc7c61c..55d6011e0d7f 100644 --- a/platform/platform-resources/src/componentSets/Platform.xml +++ b/platform/platform-resources/src/componentSets/Platform.xml @@ -93,8 +93,8 @@ - com.intellij.internal.statistic.persistence.ApplicationStatisticsPersistenceComponent - com.intellij.internal.statistic.persistence.ApplicationStatisticsPersistenceComponent + com.intellij.internal.statistic.ApplicationStatisticsPersistenceComponent + com.intellij.internal.statistic.ApplicationStatisticsPersistenceComponent diff --git a/platform/platform-resources/src/componentSets/UICore.xml b/platform/platform-resources/src/componentSets/UICore.xml index c82bffee0e34..788a92ee827f 100644 --- a/platform/platform-resources/src/componentSets/UICore.xml +++ b/platform/platform-resources/src/componentSets/UICore.xml @@ -43,7 +43,7 @@ com.intellij.ide.MacOSApplicationProvider - com.intellij.internal.statistic.ActionsCollector + com.intellij.internal.statistic.customUsageCollectors.actions.ActionsCollector diff --git a/platform/platform-resources/src/idea/LangActions.xml b/platform/platform-resources/src/idea/LangActions.xml index 9071d8e57bc3..6fc709d3e8b2 100644 --- a/platform/platform-resources/src/idea/LangActions.xml +++ b/platform/platform-resources/src/idea/LangActions.xml @@ -304,7 +304,6 @@ - @@ -1033,6 +1032,10 @@ use-shortcut-of="EditorDuplicate"/> + + + diff --git a/platform/platform-resources/src/idea/PlatformActions.xml b/platform/platform-resources/src/idea/PlatformActions.xml index 3da3e00bc5eb..17d6ec053ae7 100644 --- a/platform/platform-resources/src/idea/PlatformActions.xml +++ b/platform/platform-resources/src/idea/PlatformActions.xml @@ -36,6 +36,11 @@ + + + @@ -788,7 +793,7 @@ - + diff --git a/platform/platform-resources/src/keymaps/$default.xml b/platform/platform-resources/src/keymaps/$default.xml index 6c2f296a9a86..df67083eebf0 100644 --- a/platform/platform-resources/src/keymaps/$default.xml +++ b/platform/platform-resources/src/keymaps/$default.xml @@ -214,6 +214,10 @@ + + + + @@ -442,6 +446,10 @@ + + + + diff --git a/platform/lang-impl/testData/codeStyle/importSettings/projectCodeStyleSettings.xml b/platform/platform-tests/testData/codeStyle/importSettings/projectCodeStyleSettings.xml similarity index 100% rename from platform/lang-impl/testData/codeStyle/importSettings/projectCodeStyleSettings.xml rename to platform/platform-tests/testData/codeStyle/importSettings/projectCodeStyleSettings.xml diff --git a/platform/lang-impl/testData/codeStyle/importSettings/standardCodeStyleXml.xml b/platform/platform-tests/testData/codeStyle/importSettings/standardCodeStyleXml.xml similarity index 100% rename from platform/lang-impl/testData/codeStyle/importSettings/standardCodeStyleXml.xml rename to platform/platform-tests/testData/codeStyle/importSettings/standardCodeStyleXml.xml diff --git a/platform/platform-tests/testSrc/com/intellij/execution/impl/ConsoleViewImplTest.java b/platform/platform-tests/testSrc/com/intellij/execution/impl/ConsoleViewImplTest.java index d4f53411627b..b873b951b103 100644 --- a/platform/platform-tests/testSrc/com/intellij/execution/impl/ConsoleViewImplTest.java +++ b/platform/platform-tests/testSrc/com/intellij/execution/impl/ConsoleViewImplTest.java @@ -254,7 +254,7 @@ public class ConsoleViewImplTest extends LightPlatformTestCase { public void testPerformanceOfMergeableTokens() throws Exception { withCycleConsole(1000, console -> - PlatformTestUtil.startPerformanceTest("console print", 3500, () -> { + PlatformTestUtil.startPerformanceTest("console print with mergeable tokens", 3500, () -> { console.clear(); for (int i=0; i<10_000_000; i++) { console.print("xxx\n", ConsoleViewContentType.NORMAL_OUTPUT); diff --git a/platform/platform-tests/testSrc/com/intellij/history/integration/LocalHistoryStorageTest.java b/platform/platform-tests/testSrc/com/intellij/history/integration/LocalHistoryStorageTest.java index f82e14eb7bf6..7e63af6b920c 100644 --- a/platform/platform-tests/testSrc/com/intellij/history/integration/LocalHistoryStorageTest.java +++ b/platform/platform-tests/testSrc/com/intellij/history/integration/LocalHistoryStorageTest.java @@ -16,6 +16,7 @@ package com.intellij.history.integration; import com.intellij.history.core.LocalHistoryStorage; +import com.intellij.openapi.util.Disposer; import com.intellij.util.io.storage.AbstractStorage; import java.io.DataInputStream; @@ -34,7 +35,7 @@ public class LocalHistoryStorageTest extends IntegrationTestCase { @Override protected void tearDown() throws Exception { - myStorage.dispose(); + Disposer.dispose(myStorage); super.tearDown(); } @@ -51,7 +52,7 @@ public class LocalHistoryStorageTest extends IntegrationTestCase { public void testWritingAfterClose() throws Exception { createRecord(); - myStorage.dispose(); + Disposer.dispose(myStorage); try { createRecord(); @@ -93,7 +94,7 @@ public class LocalHistoryStorageTest extends IntegrationTestCase { myStorage.deleteRecordsUpTo(r2); - myStorage.dispose(); + Disposer.dispose(myStorage); myStorage = new LocalHistoryStorage(myRoot.getPath() + "/storage"); assertFirstAndLast(r3, r4); @@ -102,7 +103,7 @@ public class LocalHistoryStorageTest extends IntegrationTestCase { myStorage.deleteRecordsUpTo(r3); - myStorage.dispose(); + Disposer.dispose(myStorage); myStorage = new LocalHistoryStorage(myRoot.getPath() + "/storage"); assertFirstAndLast(r4, r4); @@ -110,7 +111,7 @@ public class LocalHistoryStorageTest extends IntegrationTestCase { int r5 = createRecord(); - myStorage.dispose(); + Disposer.dispose(myStorage); myStorage = new LocalHistoryStorage(myRoot.getPath() + "/storage"); assertFirstAndLast(r4, r5); diff --git a/platform/platform-tests/testSrc/com/intellij/ide/codeStyleSettings/CodeStyleConfigurationTest.java b/platform/platform-tests/testSrc/com/intellij/ide/codeStyleSettings/CodeStyleConfigurationTest.java new file mode 100644 index 000000000000..8a0cdd14c604 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/ide/codeStyleSettings/CodeStyleConfigurationTest.java @@ -0,0 +1,132 @@ +/* + * Copyright 2000-2017 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.ide.codeStyleSettings; + +import com.intellij.lang.xml.XMLLanguage; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CodeStyleSettingsCustomizable; +import com.intellij.psi.codeStyle.CommonCodeStyleSettings; +import org.jdom.Element; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +/** + * @author Rustam Vishnyakov + */ +public class CodeStyleConfigurationTest extends CodeStyleTestCase { + + /** + * Check that indent options are correcly read if mixed with other language options + */ + public void testIndentOptionsRead() throws Exception { + org.jdom.Element rootElement = new Element("option"); + + Element langCodeStyle = new Element("codeStyleSettings"); + langCodeStyle.setAttribute("language", "XML"); + langCodeStyle.addContent(createOption("IF_BRACE_FORCE", "3")); + Element indentOptionsElement = new Element("indentOptions"); + indentOptionsElement.addContent(createOption("INDENT_SIZE", "2")); + indentOptionsElement.addContent(createOption("CONTINUATION_INDENT_SIZE", "3")); + indentOptionsElement.addContent(createOption("TAB_SIZE", "2")); + indentOptionsElement.addContent(createOption("USE_TAB_CHARACTER", "true")); + langCodeStyle.addContent(indentOptionsElement); + rootElement.addContent(langCodeStyle); + + CodeStyleSettings settings = new CodeStyleSettings(); + settings.readExternal(rootElement); + CommonCodeStyleSettings langSettings = settings.getCommonSettings(XMLLanguage.INSTANCE); + assert langSettings != null; + CommonCodeStyleSettings.IndentOptions indentOptions = langSettings.getIndentOptions(); + assert indentOptions != null; + assertEquals(2, indentOptions.INDENT_SIZE); + assertEquals(3, indentOptions.CONTINUATION_INDENT_SIZE); + assertEquals(2, indentOptions.TAB_SIZE); + assertEquals(true, indentOptions.USE_TAB_CHARACTER); + assertEquals(3, langSettings.IF_BRACE_FORCE); + } + + public void testCodeStyleSettingsCustomizableOptions() { + Field[] commonFields = CommonCodeStyleSettings.class.getFields(); + for (Field field : commonFields) { + boolean fieldExistsInCustomizable = false; + String fieldName = field.getName(); + int fieldModifiers = field.getModifiers(); + if (Modifier.isPublic(fieldModifiers) && + !Modifier.isStatic(fieldModifiers) && + !"PARENT_SETTINGS_INSTALLED".equals(fieldName) && + !"FORCE_REARRANGE_MODE".equals(fieldName) + ) { + for (CodeStyleSettingsCustomizable.SpacingOption option : CodeStyleSettingsCustomizable.SpacingOption.values()) { + if (option.toString().equals(field.getName())) { + fieldExistsInCustomizable = true; + break; + } + } + if (!fieldExistsInCustomizable) { + for (CodeStyleSettingsCustomizable.WrappingOrBraceOption option : CodeStyleSettingsCustomizable.WrappingOrBraceOption.values()) { + if (option.toString().equals(field.getName())) { + fieldExistsInCustomizable = true; + break; + } + } + } + if (!fieldExistsInCustomizable) { + for (CodeStyleSettingsCustomizable.BlankLinesOption option : CodeStyleSettingsCustomizable.BlankLinesOption.values()) { + if (option.toString().equals(field.getName())) { + fieldExistsInCustomizable = true; + break; + } + } + } + if (!fieldExistsInCustomizable) { + for (CodeStyleSettingsCustomizable.CommenterOption option : CodeStyleSettingsCustomizable.CommenterOption.values()) { + if (option.toString().equals(field.getName())) { + fieldExistsInCustomizable = true; + break; + } + } + } + assertTrue("Field " + field.getName() + " is not declared in CodeStyleSettingsCustomizable", fieldExistsInCustomizable); + } + } + } + + public void testSaveOtherOptionsChanged() throws Exception { + CodeStyleSettings settings = new CodeStyleSettings(); + settings.OTHER_INDENT_OPTIONS.INDENT_SIZE = 2; + Element root = createOption("config", "root"); + settings.writeExternal(root); + assertXmlOutputEquals( + "\n" + + "", + root); + } + + +} diff --git a/platform/platform-tests/testSrc/com/intellij/ide/codeStyleSettings/CodeStyleSchemesModelTest.java b/platform/platform-tests/testSrc/com/intellij/ide/codeStyleSettings/CodeStyleSchemesModelTest.java index a1351ef9c394..30a32da42618 100644 --- a/platform/platform-tests/testSrc/com/intellij/ide/codeStyleSettings/CodeStyleSchemesModelTest.java +++ b/platform/platform-tests/testSrc/com/intellij/ide/codeStyleSettings/CodeStyleSchemesModelTest.java @@ -20,11 +20,10 @@ import com.intellij.psi.codeStyle.CodeStyleScheme; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.impl.source.codeStyle.CodeStyleSchemeImpl; -import com.intellij.testFramework.LightPlatformTestCase; import java.util.List; -public class CodeStyleSchemesModelTest extends LightPlatformTestCase { +public class CodeStyleSchemesModelTest extends CodeStyleTestCase { private CodeStyleSchemesModel myModel; private CodeStyleScheme myDefaultScheme; diff --git a/platform/platform-tests/testSrc/com/intellij/ide/codeStyleSettings/CodeStyleTestCase.java b/platform/platform-tests/testSrc/com/intellij/ide/codeStyleSettings/CodeStyleTestCase.java new file mode 100644 index 000000000000..d94db376e7d9 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/ide/codeStyleSettings/CodeStyleTestCase.java @@ -0,0 +1,47 @@ +/* + * Copyright 2000-2017 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.ide.codeStyleSettings; + +import com.intellij.openapi.application.ex.PathManagerEx; +import com.intellij.testFramework.LightPlatformTestCase; +import org.jdom.Element; +import org.jdom.output.Format; +import org.jdom.output.XMLOutputter; + +import java.io.IOException; +import java.io.StringWriter; + +public abstract class CodeStyleTestCase extends LightPlatformTestCase { + + protected static final String BASE_PATH = PathManagerEx.getTestDataPath("/../../../platform/platform-tests/testData/codeStyle/"); + + @SuppressWarnings("Duplicates") + public static void assertXmlOutputEquals(String expected, Element root) throws IOException { + StringWriter writer = new StringWriter(); + Format format = Format.getPrettyFormat(); + format.setLineSeparator("\n"); + new XMLOutputter(format).output(root, writer); + String actual = writer.toString(); + assertEquals(expected, actual); + } + + protected static Element createOption(String name, String value) { + Element optionElement = new Element("option"); + optionElement.setAttribute("name", name); + optionElement.setAttribute("value", value); + return optionElement; + } +} diff --git a/platform/lang-impl/testSources/com/intellij/application/options/codeStyle/importSettings/CodeStyleXmlImporterTest.java b/platform/platform-tests/testSrc/com/intellij/ide/codeStyleSettings/CodeStyleXmlImporterTest.java similarity index 72% rename from platform/lang-impl/testSources/com/intellij/application/options/codeStyle/importSettings/CodeStyleXmlImporterTest.java rename to platform/platform-tests/testSrc/com/intellij/ide/codeStyleSettings/CodeStyleXmlImporterTest.java index fba88b0da9ee..20983b1084f6 100644 --- a/platform/lang-impl/testSources/com/intellij/application/options/codeStyle/importSettings/CodeStyleXmlImporterTest.java +++ b/platform/platform-tests/testSrc/com/intellij/ide/codeStyleSettings/CodeStyleXmlImporterTest.java @@ -1,6 +1,20 @@ -package com.intellij.application.options.codeStyle.importSettings; +/* + * Copyright 2000-2017 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.ide.codeStyleSettings; -import com.intellij.openapi.application.ex.PathManagerEx; import com.intellij.openapi.options.SchemeFactory; import com.intellij.openapi.options.SchemeImportException; import com.intellij.openapi.vfs.VfsUtil; @@ -9,13 +23,12 @@ import com.intellij.psi.codeStyle.CodeStyleScheme; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.impl.source.codeStyle.CodeStyleSchemeImpl; import com.intellij.psi.impl.source.codeStyle.CodeStyleSchemeXmlImporter; -import com.intellij.testFramework.LightPlatformTestCase; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; -public class CodeStyleXmlImporterTest extends LightPlatformTestCase { +public class CodeStyleXmlImporterTest extends CodeStyleTestCase { public void testStandardCodeStyleXml() throws SchemeImportException { CodeStyleSettings settings= importSettings(); assertEquals(false, settings.AUTODETECT_INDENTS); @@ -47,7 +60,7 @@ public class CodeStyleXmlImporterTest extends LightPlatformTestCase { @NotNull protected String getTestDataPath() { - return PathManagerEx.getTestDataPath() + "/../../../platform/lang-impl/testData/codeStyle/importSettings/"; + return BASE_PATH + "importSettings/"; } } diff --git a/platform/platform-tests/testSrc/com/intellij/internal/statistics/StatisticsUtilTest.kt b/platform/platform-tests/testSrc/com/intellij/internal/statistics/StatisticsUtilTest.kt index e7c9c9d9f9fe..b3dfb57ee00a 100644 --- a/platform/platform-tests/testSrc/com/intellij/internal/statistics/StatisticsUtilTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/internal/statistics/StatisticsUtilTest.kt @@ -16,8 +16,8 @@ package com.intellij.internal.statistics import com.intellij.internal.statistic.beans.UsageDescriptor -import com.intellij.internal.statistic.getBooleanUsage -import com.intellij.internal.statistic.getCountingUsage +import com.intellij.internal.statistic.utils.getBooleanUsage +import com.intellij.internal.statistic.utils.getCountingUsage import org.junit.Test import kotlin.test.assertEquals diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/EditorColorsSchemeDelegateTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/EditorColorsSchemeDelegateTest.java index ce12d99973e8..d865d265de32 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/EditorColorsSchemeDelegateTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/EditorColorsSchemeDelegateTest.java @@ -23,6 +23,7 @@ import com.intellij.openapi.editor.colors.impl.AppEditorFontOptions; import com.intellij.openapi.editor.colors.impl.EditorColorsManagerImpl; import com.intellij.openapi.editor.colors.impl.FontPreferencesImpl; import com.intellij.testFramework.TestFileType; +import org.jetbrains.annotations.NotNull; public class EditorColorsSchemeDelegateTest extends AbstractEditorTest { private EditorColorsScheme mySavedScheme; @@ -57,12 +58,14 @@ public class EditorColorsSchemeDelegateTest extends AbstractEditorTest { globalPrefs.copyTo(tempCopy); try { assertInstanceOf(globalPrefs, ModifiableFontPreferences.class); + LOG.debug(dumpFontPreferences("globalPrefs", globalPrefs)); ((ModifiableFontPreferences)globalPrefs).register("DummyFont", globalPrefs.getSize(globalPrefs.getFontFamily())); assertEquals(2, globalPrefs.getRealFontFamilies().size()); init("blah", TestFileType.TEXT); FontPreferences editorPrefs = myEditor.getColorsScheme().getFontPreferences(); + LOG.debug(dumpFontPreferences("editorPrefs", editorPrefs)); assertEquals(2, editorPrefs.getRealFontFamilies().size()); assertEquals("DummyFont", editorPrefs.getRealFontFamilies().get(1)); } @@ -70,4 +73,17 @@ public class EditorColorsSchemeDelegateTest extends AbstractEditorTest { tempCopy.copyTo(globalPrefs); } } + + private static String dumpFontPreferences(@NotNull String message, @NotNull FontPreferences fontPreferences) { + StringBuilder sb = new StringBuilder(); + sb.append(message).append(": "); + sb.append("Real font families: "); + boolean isFirst = true; + for (String fontFamily : fontPreferences.getRealFontFamilies()) { + if (isFirst) isFirst = false; else sb.append(", "); + sb.append(fontFamily); + } + return sb.toString(); + } + } diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/EditorImplTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/EditorImplTest.java index ab83cbf719bb..f30aa775908b 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/EditorImplTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/EditorImplTest.java @@ -26,6 +26,7 @@ import com.intellij.openapi.editor.ex.util.EditorUtil; import com.intellij.openapi.editor.markup.HighlighterTargetArea; import com.intellij.openapi.editor.markup.RangeHighlighter; import com.intellij.openapi.editor.markup.TextAttributes; +import com.intellij.openapi.ide.CopyPasteManager; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.testFramework.EditorTestUtil; @@ -34,6 +35,7 @@ import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.awt.*; +import java.awt.datatransfer.StringSelection; public class EditorImplTest extends AbstractEditorTest { public void testPositionCalculationForZeroWidthChars() throws Exception { @@ -490,4 +492,26 @@ public class EditorImplTest extends AbstractEditorTest { paste(); checkResultByText("abc\nabc"); } + + public void testPasteInVirtualSpaceWhenSelectionExists() throws Exception { + initText("foo"); + myEditor.getSettings().setVirtualSpace(true); + mouse().clickAt(0, 10); + executeAction(IdeActions.ACTION_SELECT_ALL); + CopyPasteManager.getInstance().setContents(new StringSelection("bar")); + paste(); + checkResultByText("bar"); + } + + public void testMoveTextNearInlayAtBrokenSurrogates() throws Exception { + initText(""); // Cannot set up text with singular surrogate characters directly + runWriteCommand(() -> myEditor.getDocument().setText(LOW_SURROGATE + HIGH_SURROGATE)); + Inlay inlay = addInlay(2); + FoldRegion region = addCollapsedFoldRegion(1, 2, "..."); + + runWriteCommand(() -> ((DocumentEx)myEditor.getDocument()).moveText(1, 2, 0)); + + assertFalse(inlay.isValid() && inlay.getOffset() == 1); + assertFalse(region.isValid() && (region.getStartOffset() == 1 || region.getEndOffset() == 1)); + } } diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/EditorRtlTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/EditorRtlTest.java index ad960c90f0bc..f5808f60b9aa 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/EditorRtlTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/EditorRtlTest.java @@ -590,4 +590,8 @@ public class EditorRtlTest extends AbstractRtlTest { right(); checkResult("// R"); } + + public void testIndentIsTreatedSeparately() throws Exception { + checkBidiRunBoundaries(" |R", "txt"); + } } diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/project/ProjectUtilTest.kt b/platform/platform-tests/testSrc/com/intellij/openapi/project/ProjectUtilTest.kt index 94d7e7b11d81..9be7e24cd55b 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/project/ProjectUtilTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/openapi/project/ProjectUtilTest.kt @@ -30,7 +30,7 @@ class ProjectUtilTest : PlatformTestCase() { } // https://youtrack.jetbrains.com/issue/IDEA-176128 - fun `testVerticalBarInTheProjectName | `() { + fun testVerticalBarInTheProjectName() { (project as ProjectImpl).setProjectName("World of heavens | Client") val cachePath = appSystemDir.relativize(project.getProjectCachePath("test", true)).toString() diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/project/impl/RecentProjectsTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/project/impl/RecentProjectsTest.java index 5bf436795c28..a1f03b5e4f19 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/project/impl/RecentProjectsTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/project/impl/RecentProjectsTest.java @@ -73,6 +73,32 @@ public class RecentProjectsTest extends PlatformTestCase { doReopenCloseAndCheckGroups(p3, "g2", "g1"); } + public void testTimestampForOpenProjectUpdatesWhenGetStateCalled() throws Exception { + Project project = null; + try { + File path = PlatformTestCase.createTempDir("z1"); + ProjectManagerEx manager = ProjectManagerEx.getInstanceEx(); + project = manager.createProject(null, path.getPath()); + project.save(); + closeProject(project); + project = manager.loadAndOpenProject(path.getPath()); + long timestamp = getProjectOpenTimestamp("z1"); + Thread.sleep(2); + Assert.assertTrue("Timestamp for opened project has not been updated", timestamp < getProjectOpenTimestamp("z1")); + } + finally { + closeProject(project); + } + } + + private static long getProjectOpenTimestamp(String projectName) { + List keys = RecentProjectsManagerBase.getInstanceEx().getState() + .additionalInfo.keySet().stream() + .filter(s -> s.endsWith(projectName)) + .collect(Collectors.toList()); + return RecentProjectsManagerBase.getInstanceEx().getState().additionalInfo.get(keys.get(0)).projectOpenTimestamp; + } + private static void doReopenCloseAndCheck(String projectPath, String... results) throws IOException, JDOMException { Project project = ProjectManager.getInstance().loadAndOpenProject(projectPath); closeProject(project); diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/JarFileSystemTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/JarFileSystemTest.java index e6090ea69bf5..d89b0efeac46 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/JarFileSystemTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/JarFileSystemTest.java @@ -138,12 +138,16 @@ public class JarFileSystemTest extends BareTestFixtureTestCase { assertEquals(0, attributes.length); assertTimestampsEqual(jar.lastModified(), attributes.lastModified); - if (((JarFileSystemImpl)JarFileSystem.getInstance()).isMakeCopyOfJar(jar)) { + JarFileSystemImpl jarFileSystem = (JarFileSystemImpl)JarFileSystem.getInstance(); + if (jarFileSystem.isMakeCopyOfJar(jar)) { // for performance reasons we create file copy on windows when we read contents and have the handle open to the copy Field resolved = handler.getClass().getDeclaredField("myFileWithMirrorResolved"); resolved.setAccessible(true); assertTrue(resolved.get(handler) == null); } + + jarFileSystem.setNoCopyJarForPath(jar.getPath() + JarFileSystem.JAR_SEPARATOR); + assertTrue(!jarFileSystem.isMakeCopyOfJar(jar)); } @Test diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/LocalFileSystemTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/LocalFileSystemTest.java index b2f5cd706667..51b77dec3a2a 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/LocalFileSystemTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/LocalFileSystemTest.java @@ -68,7 +68,7 @@ public class LocalFileSystemTest extends PlatformTestCase { for (VFileEvent event : events) { VirtualFile file = event.getFile(); if (file != null) { - boolean shouldBeValid = !(event instanceof VFileCreateEvent) || ((VFileCreateEvent)event).isReCreation(); + boolean shouldBeValid = !(event instanceof VFileCreateEvent); assertEquals(event.toString(), shouldBeValid, file.isValid()); } } diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/PersistentFsTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/PersistentFsTest.java index 612e84570970..2a1cb7452be5 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/PersistentFsTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/PersistentFsTest.java @@ -15,21 +15,28 @@ */ package com.intellij.openapi.vfs.local; +import com.intellij.openapi.Disposable; import com.intellij.openapi.application.AccessToken; import com.intellij.openapi.application.WriteAction; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.IoTestUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.*; import com.intellij.openapi.vfs.ex.temp.TempFileSystem; +import com.intellij.openapi.vfs.newvfs.BulkFileListener; import com.intellij.openapi.vfs.newvfs.FileAttribute; import com.intellij.openapi.vfs.newvfs.ManagingFS; import com.intellij.openapi.vfs.newvfs.NewVirtualFile; +import com.intellij.openapi.vfs.newvfs.events.*; import com.intellij.openapi.vfs.newvfs.persistent.FSRecords; import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS; import com.intellij.testFramework.LoggedErrorProcessor; import com.intellij.testFramework.PlatformTestCase; +import com.intellij.util.ObjectUtils; +import com.intellij.util.PathUtil; import com.intellij.util.io.DataInputOutputUtil; import com.intellij.util.io.storage.HeavyProcessLatch; import org.apache.log4j.Logger; @@ -39,7 +46,10 @@ import java.io.DataOutputStream; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.List; import java.util.jar.JarFile; +import java.util.stream.Collectors; public class PersistentFsTest extends PlatformTestCase { private PersistentFS myFs; @@ -99,7 +109,7 @@ public class PersistentFsTest extends PlatformTestCase { JarFileSystem jfs = JarFileSystem.getInstance(); String path = x.getPath() + "/../" + x.getName() + JarFileSystem.JAR_SEPARATOR; - NewVirtualFile root = myFs.findRoot(path, jfs); + NewVirtualFile root = ObjectUtils.notNull(myFs.findRoot(path, jfs)); assertFalse(root.getPath(), root.getPath().contains("../")); assertFalse(root.getPath(), root.getPath().contains("/..")); } @@ -170,12 +180,12 @@ public class PersistentFsTest extends PlatformTestCase { try { String rootUrl = "jar://" + FileUtil.toSystemIndependentName(jarFile.getPath()) + "!/"; - String entryUrl = rootUrl + JarFile.MANIFEST_NAME; assertNotNull(getVirtualFile(jarFile)); VirtualFile jarRoot = VirtualFileManager.getInstance().findFileByUrl(rootUrl); assertNotNull(jarRoot); assertTrue(jarRoot.isValid()); assertEquals(0, jarRoot.getChildren().length); + String entryUrl = rootUrl + JarFile.MANIFEST_NAME; assertNull(VirtualFileManager.getInstance().findFileByUrl(entryUrl)); VirtualFile local = JarFileSystem.getInstance().getVirtualFileForJar(jarRoot); @@ -231,7 +241,7 @@ public class PersistentFsTest extends PlatformTestCase { } @NotNull - private static VirtualFile setupFile() throws IOException { + private static VirtualFile setupFile() { File file = IoTestUtil.createTestFile("file.txt"); VirtualFile vFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file); assertNotNull(vFile); @@ -275,4 +285,165 @@ public class PersistentFsTest extends PlatformTestCase { assertEquals(inSessionModCount + 1, managingFS.getModificationCount()); assertFalse(FSRecords.isDirty()); } + + public void testProcessEventsMustIgnoreDeleteDuplicates() throws IOException { + VirtualFile vFile = setupFile(); + checkEvents("Before:[VFileDeleteEvent->file.txt]\nAfter:[VFileDeleteEvent->file.txt]\n", + new VFileDeleteEvent(this, vFile, false), + new VFileDeleteEvent(this, vFile, false)); + } + + private void checkEvents(String expectedEvents, VFileEvent... eventsToApply) { + final StringBuilder log = new StringBuilder(); + Disposable disposable = Disposer.newDisposable(); + getProject().getMessageBus().connect(disposable).subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() { + @Override + public void before(@NotNull List events) { + log("Before:", events); + } + + private void log(String msg, @NotNull List events) { + List names = events.stream().map(e -> e.getClass().getSimpleName() + "->" + PathUtil.getFileName(e.getPath())).collect(Collectors.toList()); + log.append(msg).append(names).append("\n"); + } + + @Override + public void after(@NotNull List events) { + log("After:", events); + } + }); + WriteCommandAction.runWriteCommandAction(getProject(), () -> PersistentFS.getInstance().processEvents(Arrays.asList(eventsToApply))); + Disposer.dispose(disposable); + assertEquals(expectedEvents, log.toString()); + } + + public void testProcessEventsMustGroupDependentEventsCorrectly() throws IOException { + VirtualFile vFile = setupFile(); + checkEvents("Before:[VFileCreateEvent->xx.created, VFileDeleteEvent->file.txt]\n" + + "After:[VFileCreateEvent->xx.created, VFileDeleteEvent->file.txt]\n", + new VFileDeleteEvent(this, vFile, false), + new VFileCreateEvent(this, vFile.getParent(), "xx.created", false, false), + new VFileDeleteEvent(this, vFile, false)); + } + + public void testProcessEventsMustGroupDependentEventsCorrectly2() throws IOException { + File file = new File(createTempDirectory(), "a/b/c/test.txt"); + assertTrue(file.getParentFile().mkdirs()); + assertTrue(file.createNewFile()); + + VirtualFile vFile = myLocalFs.refreshAndFindFileByIoFile(file); + assertNotNull(vFile); + + checkEvents("Before:[VFileCreateEvent->xx.created, VFileCreateEvent->xx.created2, VFileDeleteEvent->test.txt]\n" + + "After:[VFileCreateEvent->xx.created, VFileCreateEvent->xx.created2, VFileDeleteEvent->test.txt]\n" + + "Before:[VFileDeleteEvent->c]\n" + + "After:[VFileDeleteEvent->c]\n", + new VFileDeleteEvent(this, vFile, false), + new VFileCreateEvent(this, vFile.getParent(), "xx.created", false, false), + new VFileCreateEvent(this, vFile.getParent(), "xx.created2", false, false), + new VFileDeleteEvent(this, vFile.getParent(), false)); + } + + public void testProcessEventsMustGroupDependentEventsCorrectly3() throws IOException { + File file = new File(createTempDirectory(), "a/b/c/test.txt"); + assertTrue(file.getParentFile().mkdirs()); + assertTrue(file.createNewFile()); + + VirtualFile vFile = myLocalFs.refreshAndFindFileByIoFile(file); + assertNotNull(vFile); + + checkEvents("Before:[VFileContentChangeEvent->c]\n" + + "After:[VFileContentChangeEvent->c]\n" + + "Before:[VFileDeleteEvent->test.txt]\n" + + "After:[VFileDeleteEvent->test.txt]\n", + + new VFileContentChangeEvent(this, vFile.getParent(), 0, 0, false), + new VFileDeleteEvent(this, vFile, false) + ); + } + + public void testProcessNestedDeletions() throws IOException { + File temp = createTempDirectory(); + File file = new File(temp, "a/b/c/test.txt"); + assertTrue(file.getParentFile().mkdirs()); + assertTrue(file.createNewFile()); + VirtualFile testTxt = ObjectUtils.assertNotNull(myLocalFs.refreshAndFindFileByIoFile(file)); + File file2 = new File(temp, "a/b/c/test2.txt"); + assertTrue(file2.createNewFile()); + VirtualFile test2Txt = ObjectUtils.assertNotNull(myLocalFs.refreshAndFindFileByIoFile(file2)); + + checkEvents("Before:[VFileDeleteEvent->test.txt]\n" + + "After:[VFileDeleteEvent->test.txt]\n" + + "Before:[VFileDeleteEvent->c]\n" + + "After:[VFileDeleteEvent->c]\n", + + new VFileDeleteEvent(this, testTxt, false), + new VFileDeleteEvent(this, testTxt.getParent(), false), + new VFileDeleteEvent(this, test2Txt, false) + ); + } + + public void testProcessCompositeMoveEvents() throws IOException { + File temp = createTempDirectory(); + File file = new File(temp, "a/b/c/test.txt"); + assertTrue(file.getParentFile().mkdirs()); + assertTrue(file.createNewFile()); + VirtualFile testTxt = ObjectUtils.assertNotNull(myLocalFs.refreshAndFindFileByIoFile(file)); + + File newParentF = new File(temp, "a/b/d"); + assertTrue(newParentF.mkdirs()); + VirtualFile newParent = ObjectUtils.assertNotNull(myLocalFs.refreshAndFindFileByIoFile(newParentF)); + + checkEvents("Before:[VFileMoveEvent->test.txt]\n" + + "After:[VFileMoveEvent->test.txt]\n" + + "Before:[VFileDeleteEvent->d]\n" + + "After:[VFileDeleteEvent->d]\n", + + new VFileMoveEvent(this, testTxt, newParent), + new VFileDeleteEvent(this, newParent, false) + ); + } + + public void testProcessCompositeCopyEvents() throws IOException { + File temp = createTempDirectory(); + File file = new File(temp, "a/b/c/test.txt"); + assertTrue(file.getParentFile().mkdirs()); + assertTrue(file.createNewFile()); + VirtualFile testTxt = ObjectUtils.assertNotNull(myLocalFs.refreshAndFindFileByIoFile(file)); + + File newParentF = new File(temp, "a/b/d"); + assertTrue(newParentF.mkdirs()); + VirtualFile newParent = ObjectUtils.assertNotNull(myLocalFs.refreshAndFindFileByIoFile(newParentF)); + + checkEvents("Before:[VFileCopyEvent->new.txt]\n" + + "After:[VFileCopyEvent->new.txt]\n" + + "Before:[VFileDeleteEvent->test.txt]\n" + + "After:[VFileDeleteEvent->test.txt]\n", + + new VFileCopyEvent(this, testTxt, newParent,"new.txt"), + new VFileDeleteEvent(this, testTxt, false) + ); + } + + public void testProcessCompositeRenameEvents() throws IOException { + File temp = createTempDirectory(); + File file = new File(temp, "a/b/c/test.txt"); + assertTrue(file.getParentFile().mkdirs()); + assertTrue(file.createNewFile()); + VirtualFile testTxt = ObjectUtils.assertNotNull(myLocalFs.refreshAndFindFileByIoFile(file)); + + File file2 = new File(temp, "a/b/c/test2.txt"); + assertTrue(file2.createNewFile()); + VirtualFile test2Txt = ObjectUtils.assertNotNull(myLocalFs.refreshAndFindFileByIoFile(file2)); + + checkEvents("Before:[VFileDeleteEvent->test2.txt]\n" + + "After:[VFileDeleteEvent->test2.txt]\n" + + "Before:[VFilePropertyChangeEvent->test.txt]\n" + + "After:[VFilePropertyChangeEvent->test2.txt]\n", + + new VFileDeleteEvent(this, test2Txt, false), + new VFilePropertyChangeEvent(this, testTxt, VirtualFile.PROP_NAME, file.getName(), file2.getName(), false) + ); + } + } diff --git a/platform/platform-tests/testSrc/com/intellij/usagesStatistics/RemotelyConfigurableStatServiceTest.java b/platform/platform-tests/testSrc/com/intellij/usagesStatistics/RemotelyConfigurableStatServiceTest.java index 5cb815cfec04..8c5648c58165 100644 --- a/platform/platform-tests/testSrc/com/intellij/usagesStatistics/RemotelyConfigurableStatServiceTest.java +++ b/platform/platform-tests/testSrc/com/intellij/usagesStatistics/RemotelyConfigurableStatServiceTest.java @@ -15,7 +15,7 @@ */ package com.intellij.usagesStatistics; -import com.intellij.internal.statistic.StatisticsUploadAssistant; +import com.intellij.internal.statistic.utils.StatisticsUploadAssistant; import com.intellij.internal.statistic.connect.RemotelyConfigurableStatisticsService; import com.intellij.internal.statistic.connect.StatisticsConnectionService; import com.intellij.internal.statistic.connect.StatisticsHttpClientSender; diff --git a/platform/platform-tests/testSrc/com/intellij/usagesStatistics/StatisticsUploadAssistantTest.java b/platform/platform-tests/testSrc/com/intellij/usagesStatistics/StatisticsUploadAssistantTest.java index 2d233b8d9a20..49409723a13d 100644 --- a/platform/platform-tests/testSrc/com/intellij/usagesStatistics/StatisticsUploadAssistantTest.java +++ b/platform/platform-tests/testSrc/com/intellij/usagesStatistics/StatisticsUploadAssistantTest.java @@ -17,7 +17,6 @@ package com.intellij.usagesStatistics; import com.intellij.internal.statistic.beans.ConvertUsagesUtil; import com.intellij.internal.statistic.beans.GroupDescriptor; -import com.intellij.internal.statistic.beans.PatchedUsage; import com.intellij.internal.statistic.beans.UsageDescriptor; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.containers.hash.HashMap; @@ -43,7 +42,7 @@ public class StatisticsUploadAssistantTest extends TestCase { } public void testConvertUsagesWithPriority() { - final Map> patchedUsages = new HashMap<>(); + final Map> patchedUsages = new HashMap<>(); createPatchDescriptor(patchedUsages, "low", GroupDescriptor.LOWER_PRIORITY, "l1", 1); createPatchDescriptor(patchedUsages, "low", GroupDescriptor.LOWER_PRIORITY, "l2", 1); @@ -59,7 +58,7 @@ public class StatisticsUploadAssistantTest extends TestCase { } public void testConvertUsagesWithEqualPriority() { - final Map> patchedUsages = new HashMap<>(); + final Map> patchedUsages = new HashMap<>(); createPatchDescriptor(patchedUsages, "g4", GroupDescriptor.HIGHER_PRIORITY, "1", 1); createPatchDescriptor(patchedUsages, "g2", GroupDescriptor.HIGHER_PRIORITY, "2", 1); @@ -71,7 +70,7 @@ public class StatisticsUploadAssistantTest extends TestCase { } public void testConvertString() { - final Map> patchedUsages = new HashMap<>(); + final Map> patchedUsages = new HashMap<>(); createPatchDescriptor(patchedUsages, "g4", GroupDescriptor.HIGHER_PRIORITY, "1", 1); createPatchDescriptor(patchedUsages, "g2", GroupDescriptor.HIGHER_PRIORITY, "2", 1); @@ -124,13 +123,13 @@ public class StatisticsUploadAssistantTest extends TestCase { return usage.getKey() + "=" + usage.getValue(); } - private static void createPatchDescriptor(Map> patchedUsages, String groupId, double priority, String key, int i) { + private static void createPatchDescriptor(Map> patchedUsages, String groupId, double priority, String key, int i) { final GroupDescriptor groupDescriptor = GroupDescriptor.create(groupId, priority); if (!patchedUsages.containsKey(groupDescriptor)){ patchedUsages.put(groupDescriptor, new LinkedHashSet<>()); } - patchedUsages.get(groupDescriptor).add(new PatchedUsage(key, i)); + patchedUsages.get(groupDescriptor).add(new UsageDescriptor(key, i)); } diff --git a/platform/projectModel-impl/src/com/intellij/openapi/module/impl/ModuleManagerImpl.java b/platform/projectModel-impl/src/com/intellij/openapi/module/impl/ModuleManagerImpl.java index 0f91135e5ecb..19c9a742e9e0 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/module/impl/ModuleManagerImpl.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/module/impl/ModuleManagerImpl.java @@ -31,6 +31,8 @@ import com.intellij.openapi.module.*; import com.intellij.openapi.progress.EmptyProgressIndicator; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressIndicatorProvider; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.util.ProgressWrapper; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectBundle; import com.intellij.openapi.roots.ModuleRootManager; @@ -273,16 +275,18 @@ public abstract class ModuleManagerImpl extends ModuleManager implements Disposa } tasks.add(Pair.create(service.submit(() -> { progressIndicator.setFraction(progressIndicator.getFraction() + myProgressStep); - try { - return myProject.isDisposed() ? null : moduleModel.loadModuleInternal(path); - } - catch (IOException e) { - reportError(errors, modulePath, e); - } - catch (Exception e) { - LOG.error(e); - } - return null; + return ProgressManager.getInstance().runProcess(() -> { + try { + return myProject.isDisposed() ? null : moduleModel.loadModuleInternal(path); + } + catch (IOException e) { + reportError(errors, modulePath, e); + } + catch (Exception e) { + LOG.error(e); + } + return null; + }, ProgressWrapper.wrap(progressIndicator)); }), modulePath)); } diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexExcludePolicy.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexExcludePolicy.java index a9eee660c7e2..54b30303106a 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexExcludePolicy.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexExcludePolicy.java @@ -19,10 +19,15 @@ package com.intellij.openapi.roots.impl; import com.intellij.openapi.extensions.AreaInstance; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.ModuleRootModel; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.pointers.VirtualFilePointer; +import com.intellij.util.Function; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; /** * @author yole @@ -33,6 +38,11 @@ public interface DirectoryIndexExcludePolicy { @NotNull VirtualFile[] getExcludeRootsForProject(); + @Nullable + default Function> getExcludeSdkRootsStrategy() { + return null; + } + @NotNull VirtualFilePointer[] getExcludeRootsForModule(@NotNull ModuleRootModel rootModel); diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/RootIndex.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/RootIndex.java index f7bca6a13fc8..6eb907c2605c 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/RootIndex.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/RootIndex.java @@ -24,6 +24,7 @@ import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.module.UnloadedModuleDescription; import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.*; import com.intellij.openapi.roots.impl.libraries.LibraryEx; import com.intellij.openapi.roots.libraries.Library; @@ -36,6 +37,7 @@ import com.intellij.openapi.vfs.VirtualFileWithId; import com.intellij.openapi.vfs.newvfs.events.VFileEvent; import com.intellij.openapi.vfs.pointers.VirtualFilePointer; import com.intellij.util.CollectionQuery; +import com.intellij.util.Function; import com.intellij.util.Query; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; @@ -204,6 +206,30 @@ public class RootIndex { } for (DirectoryIndexExcludePolicy policy : Extensions.getExtensions(DirectoryIndexExcludePolicy.EP_NAME, project)) { info.excludedFromProject.addAll(ContainerUtil.filter(policy.getExcludeRootsForProject(), file -> ensureValid(file, policy))); + + Function> fun = policy.getExcludeSdkRootsStrategy(); + + if (fun != null) { + Set sdks = new HashSet<>(); + + for (Module m : ModuleManager.getInstance(myProject).getModules()) { + Sdk sdk = ModuleRootManager.getInstance(m).getSdk(); + if (sdk != null) { + sdks.add(sdk); + } + } + + Set roots = new HashSet<>(); + + for (Sdk sdk: sdks) { + roots.addAll(Arrays.asList(sdk.getRootProvider().getFiles(OrderRootType.CLASSES))); + } + + for (Sdk sdk: sdks) { + info.excludedFromSdkRoots + .addAll(ContainerUtil.filter(fun.fun(sdk), file -> ensureValid(file, policy) && !roots.contains(file))); + } + } } for (UnloadedModuleDescription description : moduleManager.getUnloadedModuleDescriptions()) { for (VirtualFilePointer pointer : description.getContentRoots()) { @@ -239,7 +265,7 @@ public class RootIndex { /** * A reverse dependency graph of (library, jdk, module, module source) -> (module). - * + *

*

Each edge carries with it the associated OrderEntry that caused the dependency. */ private static class OrderEntryGraph { @@ -299,13 +325,14 @@ public class RootIndex { } }; int dependentUnloadedModulesCacheSize = ModuleManager.getInstance(project).getModules().length / 2; - myDependentUnloadedModulesCache = new SynchronizedSLRUCache>(dependentUnloadedModulesCacheSize, dependentUnloadedModulesCacheSize) { - @NotNull - @Override - public Set createValue(Module key) { - return collectDependentUnloadedModules(key); - } - }; + myDependentUnloadedModulesCache = + new SynchronizedSLRUCache>(dependentUnloadedModulesCacheSize, dependentUnloadedModulesCacheSize) { + @NotNull + @Override + public Set createValue(Module key) { + return collectDependentUnloadedModules(key); + } + }; initGraph(); initLibraryRoots(); } @@ -641,6 +668,7 @@ public class RootIndex { @NotNull final MultiMap classOfLibraries = MultiMap.createSmart(); @NotNull final MultiMap sourceOfLibraries = MultiMap.createSmart(); @NotNull final Set excludedFromProject = ContainerUtil.newHashSet(); + @NotNull final Set excludedFromSdkRoots = ContainerUtil.newHashSet(); @NotNull final Map excludedFromModule = ContainerUtil.newHashMap(); @NotNull final Map> excludeFromContentRootTables = ContainerUtil.newHashMap(); @NotNull final Map packagePrefix = ContainerUtil.newHashMap(); @@ -654,6 +682,7 @@ public class RootIndex { result.addAll(excludedFromLibraries.keySet()); result.addAll(excludedFromModule.keySet()); result.addAll(excludedFromProject); + result.addAll(excludedFromSdkRoots); return result; } @@ -862,7 +891,9 @@ public class RootIndex { Pair> libraryClassRootInfo = info.findLibraryRootInfo(hierarchy, false); VirtualFile libraryClassRoot = libraryClassRootInfo != null ? libraryClassRootInfo.first : null; - boolean inProject = moduleContentRoot != null || libraryClassRoot != null || librarySourceRoot != null; + boolean inProject = moduleContentRoot != null || + ((libraryClassRoot != null || librarySourceRoot != null) && !info.excludedFromSdkRoots.contains(root)); + VirtualFile nearestContentRoot; if (inProject) { nearestContentRoot = moduleContentRoot; @@ -883,7 +914,8 @@ public class RootIndex { Module module = info.contentRootOf.get(nearestContentRoot); String unloadedModuleName = info.contentRootOfUnloaded.get(nearestContentRoot); - FileTypeAssocTable contentExcludePatterns = moduleContentRoot != null ? info.excludeFromContentRootTables.get(moduleContentRoot) : null; + FileTypeAssocTable contentExcludePatterns = + moduleContentRoot != null ? info.excludeFromContentRootTables.get(moduleContentRoot) : null; FileTypeAssocTable libraryExcludePatterns = getLibraryExclusionPatterns(librarySourceRootInfo); DirectoryInfo directoryInfo = contentExcludePatterns != null || libraryExcludePatterns != null @@ -902,7 +934,8 @@ public class RootIndex { FileTypeAssocTable excludePatterns = null; if (libraryRootInfo != null) { for (Object library : libraryRootInfo.second) { - Condition exclusionPredicate = library instanceof SyntheticLibrary ? ((SyntheticLibrary)library).getExcludeCondition() : null; + Condition exclusionPredicate = + library instanceof SyntheticLibrary ? ((SyntheticLibrary)library).getExcludeCondition() : null; if (exclusionPredicate == null) continue; if (excludePatterns == null) { excludePatterns = new FileTypeAssocTable<>(); @@ -946,7 +979,7 @@ public class RootIndex { * An LRU cache with synchronization around the primary cache operations (get() and insertion * of a newly created value). Other map operations are not synchronized. */ - abstract static class SynchronizedSLRUCache extends SLRUMap { + abstract static class SynchronizedSLRUCache extends SLRUMap { protected final Object myLock = new Object(); protected SynchronizedSLRUCache(final int protectedQueueSize, final int probationalQueueSize) { diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/libraries/LibraryTableImplUtil.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/libraries/LibraryTableImplUtil.java index 08664293eb8e..2e6f77bb5d51 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/libraries/LibraryTableImplUtil.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/libraries/LibraryTableImplUtil.java @@ -16,9 +16,14 @@ package com.intellij.openapi.roots.impl.libraries; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.roots.LibraryOrderEntry; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.roots.OrderEntry; import com.intellij.openapi.roots.ProjectModelExternalSource; import com.intellij.openapi.roots.impl.RootModelImpl; import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.roots.libraries.LibraryTable; import com.intellij.openapi.roots.libraries.PersistentLibraryKind; import com.intellij.openapi.util.InvalidDataException; import org.jdom.Element; @@ -50,4 +55,23 @@ public class LibraryTableImplUtil { @Nullable ProjectModelExternalSource externalSource) { return new LibraryImpl(name, kind, null, rootModel, externalSource); } + + public static boolean isValidLibrary(@NotNull Library library) { + LibraryTable table = library.getTable(); + if (table != null) { + String name = library.getName(); + return name != null && table.getLibraryByName(name) == library; + } + + if (!(library instanceof LibraryImpl)) return false; + + Module module = ((LibraryImpl)library).getModule(); + if (module == null) return false; + for (OrderEntry entry : ModuleRootManager.getInstance(module).getOrderEntries()) { + if (entry instanceof LibraryOrderEntry && ((LibraryOrderEntry)entry).getLibrary() == library) { + return true; + } + } + return false; + } } diff --git a/platform/projectModel-impl/src/messages/ProjectBundle.properties b/platform/projectModel-impl/src/messages/ProjectBundle.properties index e3d3f88e8af3..0622d552cfbe 100644 --- a/platform/projectModel-impl/src/messages/ProjectBundle.properties +++ b/platform/projectModel-impl/src/messages/ProjectBundle.properties @@ -31,7 +31,7 @@ project.convert.source.roots.not.under.project.roots.error=There are source root project.convert.javadoc.paths.error=JavaDoc paths cannot be converted. project.convert.multiple.output.paths.error=The project uses multiple output paths. -framework.detected.info.text={0} {1, choice, 1#framework is|2#frameworks are} detected in the project Configure +framework.detected.info.text={0} {1, choice, 1#framework is|2#frameworks are} detected. progress.text.searching.frameworks=Searching for frameworks. Please wait. message.text.stop.searching.for.frameworks={0} is currently searching for frameworks. Would you like to stop the search? diff --git a/platform/remote-servers/impl/src/com/intellij/remoteServer/impl/runtime/ServerConnectionImpl.java b/platform/remote-servers/impl/src/com/intellij/remoteServer/impl/runtime/ServerConnectionImpl.java index 2549e8d3b1ae..4abbfc5cfab9 100644 --- a/platform/remote-servers/impl/src/com/intellij/remoteServer/impl/runtime/ServerConnectionImpl.java +++ b/platform/remote-servers/impl/src/com/intellij/remoteServer/impl/runtime/ServerConnectionImpl.java @@ -212,7 +212,9 @@ public class ServerConnectionImpl implements @Override public void succeeded() { - myAllDeployments.replaceRemotesWith(myCollectedDeployments); + synchronized (myCollectedDeployments) { + myAllDeployments.replaceRemotesWith(myCollectedDeployments); + } myEventDispatcher.queueDeploymentsChanged(ServerConnectionImpl.this); onFinished.run(); @@ -233,7 +235,7 @@ public class ServerConnectionImpl implements @Override public void undeploy(@NotNull Deployment deployment, @NotNull final DeploymentRuntime runtime) { String deploymentName = deployment.getName(); - final StateTransition undeployInProgress = myAllDeployments.startUndeploy(deploymentName); + final UndeployTransition undeployInProgress = myAllDeployments.startUndeploy(deploymentName); myEventDispatcher.queueDeploymentsChanged(this); @@ -389,7 +391,7 @@ public class ServerConnectionImpl implements @Override public void errorOccurred(@NotNull String errorMessage) { myLoggingHandler.printlnSystemMessage("Failed to deploy '" + myDeploymentName + "': " + errorMessage); - synchronized (myAllDeployments.LOCAL_LOCK) { + synchronized (myAllDeployments.myLocalLock) { myDeployment.changeState(DeploymentStatus.DEPLOYING, DeploymentStatus.NOT_DEPLOYED, errorMessage, null); } myEventDispatcher.queueDeploymentsChanged(ServerConnectionImpl.this); @@ -405,8 +407,8 @@ public class ServerConnectionImpl implements } private static class MyDeployments { - private final Object LOCAL_LOCK = new Object(); - private final Object REMOTE_LOCK = new Object(); + private final Object myLocalLock = new Object(); + private final Object myRemoteLock = new Object(); private final Map myRemoteDeployments = new HashMap<>(); private final Map myLocalDeployments = new HashMap<>(); @@ -418,14 +420,14 @@ public class ServerConnectionImpl implements } public void addLocal(@NotNull LocalDeploymentImpl deployment) { - synchronized (LOCAL_LOCK) { + synchronized (myLocalLock) { myLocalDeployments.put(deployment.getName(), deployment); myCachedAllDeployments = null; } } public void replaceRemotesWith(@NotNull Collection newDeployments) { - synchronized (REMOTE_LOCK) { + synchronized (myRemoteLock) { myRemoteDeployments.clear(); myCachedAllDeployments = null; for (DeploymentImpl deployment : newDeployments) { @@ -440,7 +442,7 @@ public class ServerConnectionImpl implements @NotNull DeploymentStatus deploymentStatus, @Nullable String deploymentStatusText) { - synchronized (REMOTE_LOCK) { + synchronized (myRemoteLock) { DeploymentImpl result = myRemoteDeployments.get(deploymentName); if (result != null && !result.getStatus().isTransition()) { result.changeState(result.getStatus(), deploymentStatus, deploymentStatusText, deploymentRuntime); @@ -455,8 +457,8 @@ public class ServerConnectionImpl implements @NotNull DeploymentStatus newStatus, @Nullable String statusText) { - synchronized (LOCAL_LOCK) { - synchronized (REMOTE_LOCK) { + synchronized (myLocalLock) { + synchronized (myRemoteLock) { return deployment.changeState(oldStatus, newStatus, statusText, deploymentRuntime); } } @@ -464,8 +466,8 @@ public class ServerConnectionImpl implements @NotNull public Collection listDeployments() { - synchronized (LOCAL_LOCK) { - synchronized (REMOTE_LOCK) { + synchronized (myLocalLock) { + synchronized (myRemoteLock) { if (myCachedAllDeployments == null) { Collection result = doListDeployments(); myCachedAllDeployments = Collections.unmodifiableList(new ArrayList<>(result)); @@ -477,7 +479,7 @@ public class ServerConnectionImpl implements } private Collection doListDeployments() { - //assumed both LOCAL_LOCK and REMOTE_LOCK + //assumed both myLocalLock and myRemoteLock Set result = new LinkedHashSet<>(); Map orderedDeployments = new TreeMap<>(myDeploymentComparator); @@ -505,18 +507,18 @@ public class ServerConnectionImpl implements @SuppressWarnings("Duplicates") @Nullable - public StateTransition startUndeploy(@NotNull String deploymentName) { + public UndeployTransition startUndeploy(@NotNull String deploymentName) { - synchronized (LOCAL_LOCK) { - synchronized (REMOTE_LOCK) { + synchronized (myLocalLock) { + synchronized (myRemoteLock) { DeploymentImpl local = myLocalDeployments.get(deploymentName); if (local != null) { return new UndeployTransition(local) { @Override public void succeeded() { - synchronized (LOCAL_LOCK) { - if (completeStateChange()) { + synchronized (myLocalLock) { + if (tryChangeToTerminalState(DeploymentStatus.NOT_DEPLOYED)) { myLocalDeployments.remove(getDeployment().getName()); myCachedAllDeployments = null; } @@ -525,8 +527,8 @@ public class ServerConnectionImpl implements @Override public void failed() { - synchronized (LOCAL_LOCK) { - rollbackStateChange(); + synchronized (myLocalLock) { + tryChangeToTerminalState(DeploymentStatus.DEPLOYED); } } }; @@ -536,8 +538,8 @@ public class ServerConnectionImpl implements return new UndeployTransition(remote) { @Override public void succeeded() { - synchronized (REMOTE_LOCK) { - if (completeStateChange()) { + synchronized (myRemoteLock) { + if (tryChangeToTerminalState(DeploymentStatus.NOT_DEPLOYED)) { myRemoteDeployments.remove(getDeployment().getName()); myCachedAllDeployments = null; } @@ -546,8 +548,8 @@ public class ServerConnectionImpl implements @Override public void failed() { - synchronized (REMOTE_LOCK) { - rollbackStateChange(); + synchronized (myRemoteLock) { + tryChangeToTerminalState(DeploymentStatus.DEPLOYED); } } }; @@ -556,48 +558,26 @@ public class ServerConnectionImpl implements } } } - - private static abstract class UndeployTransition extends StateTransitionImpl { - public UndeployTransition(@NotNull DeploymentImpl deployment) { - super(deployment, DeploymentStatus.DEPLOYED, DeploymentStatus.DEPLOYING, DeploymentStatus.NOT_DEPLOYED); - } - } - - private static abstract class StateTransitionImpl implements StateTransition { - private final DeploymentImpl myDeployment; - private final DeploymentStatus myStartStatus; - private final DeploymentStatus myInProgressStatus; - private final DeploymentStatus myEndStatus; - - public StateTransitionImpl(@NotNull DeploymentImpl deployment, - @NotNull DeploymentStatus start, @NotNull DeploymentStatus inProgress, @NotNull DeploymentStatus end) { - - myDeployment = deployment; - myStartStatus = start; - myEndStatus = end; - myInProgressStatus = inProgress; - // - myDeployment.changeState(start, inProgress, null, null); - } - - @NotNull - protected final DeploymentImpl getDeployment() { - return myDeployment; - } - - protected boolean completeStateChange() { - return myDeployment.changeState(myInProgressStatus, myEndStatus, null, null); - } - - protected boolean rollbackStateChange() { - return myDeployment.changeState(myInProgressStatus, myStartStatus, null, null); - } - } } - private interface StateTransition { - void succeeded(); + private static abstract class UndeployTransition { + private final DeploymentImpl myDeployment; - void failed(); + public UndeployTransition(@NotNull DeploymentImpl deployment) { + myDeployment = deployment; + myDeployment.changeState(DeploymentStatus.DEPLOYED, DeploymentStatus.DEPLOYING, null, null); + } + + public abstract void succeeded(); + + public abstract void failed(); + + protected boolean tryChangeToTerminalState(DeploymentStatus terminalState) { + return myDeployment.changeState(DeploymentStatus.DEPLOYING, terminalState, null, null); + } + + protected DeploymentImpl getDeployment() { + return myDeployment; + } } } diff --git a/platform/remote-servers/impl/src/com/intellij/remoteServer/impl/runtime/ui/ServersToolWindowContent.java b/platform/remote-servers/impl/src/com/intellij/remoteServer/impl/runtime/ui/ServersToolWindowContent.java index 0342a8c71195..720ee03a41d6 100644 --- a/platform/remote-servers/impl/src/com/intellij/remoteServer/impl/runtime/ui/ServersToolWindowContent.java +++ b/platform/remote-servers/impl/src/com/intellij/remoteServer/impl/runtime/ui/ServersToolWindowContent.java @@ -116,6 +116,10 @@ public class ServersToolWindowContent extends JPanel implements Disposable, Serv this(project, contribution, ActionGroups.SHARED_ACTION_GROUPS); } + /** + * @param actionGroups allows to customize action groups bound to view toolbars and tree' poup menu. + * Use {@link ActionGroups#SHARED_ACTION_GROUPS} to use a predefined action groups. + */ public ServersToolWindowContent(@NotNull Project project, @NotNull RemoteServersViewContribution contribution, @NotNull ActionGroups actionGroups) { super(new BorderLayout()); diff --git a/platform/remote-servers/impl/src/com/intellij/remoteServer/impl/runtime/ui/tree/actions/ChooseDeploymentWithDebugAction.java b/platform/remote-servers/impl/src/com/intellij/remoteServer/impl/runtime/ui/tree/actions/ChooseDeploymentWithDebugAction.java index 55e2b1cb509f..d357076b8705 100644 --- a/platform/remote-servers/impl/src/com/intellij/remoteServer/impl/runtime/ui/tree/actions/ChooseDeploymentWithDebugAction.java +++ b/platform/remote-servers/impl/src/com/intellij/remoteServer/impl/runtime/ui/tree/actions/ChooseDeploymentWithDebugAction.java @@ -36,4 +36,9 @@ public class ChooseDeploymentWithDebugAction extends ServersTreeAction myNodeByIdMap = new HashMap<>(); private final Set myRunningTestNodes = ContainerUtil.newHashSet(); private final Set myRunningSuiteNodes = ContainerUtil.newHashSet(); - private final SMTestProxy.SMRootTestProxy myTestsRootProxy; private final Node myTestsRootNode; private boolean myIsTestingFinished = false; - private SMTestLocator myLocator = null; private TestProxyPrinterProvider myTestProxyPrinterProvider = null; public GeneralIdBasedToSMTRunnerEventsConvertor(Project project, @NotNull SMTestProxy.SMRootTestProxy testsRootProxy, @NotNull String testFrameworkName) { - super(project, testFrameworkName); - myTestsRootProxy = testsRootProxy; + super(project, testFrameworkName, testsRootProxy); myTestsRootNode = new Node(TreeNodeEvent.ROOT_NODE_ID, null, testsRootProxy); myNodeByIdMap.put(myTestsRootNode.getId(), myTestsRootNode); } - @Override - public void setLocator(@NotNull SMTestLocator locator) { - myLocator = locator; - } - public void onStartTesting() { addToInvokeLater(() -> { myTestsRootNode.setState(State.RUNNING, this); @@ -123,14 +115,22 @@ public class GeneralIdBasedToSMTRunnerEventsConvertor extends GeneralTestEventsP return; } + node = createNode(startedNodeEvent, suite); + if (node == null) return; + if (startedNodeEvent.isRunning()) { + setNodeAndAncestorsRunning(node); + } + } + + private Node createNode(@NotNull BaseStartedNodeEvent startedNodeEvent, boolean suite) { Node parentNode = findValidParentNode(startedNodeEvent); if (parentNode == null) { - return; + return null; } String nodeId = validateAndGetNodeId(startedNodeEvent); if (nodeId == null) { - return; + return null; } String nodeName = startedNodeEvent.getName(); @@ -144,15 +144,26 @@ public class GeneralIdBasedToSMTRunnerEventsConvertor extends GeneralTestEventsP childProxy.setPreferredPrinter(printer); } } - node = new Node(nodeId, parentNode, childProxy); + + Node node = new Node(nodeId, parentNode, childProxy); myNodeByIdMap.put(startedNodeEvent.getId(), node); if (myLocator != null) { childProxy.setLocator(myLocator); } parentNode.getProxy().addChild(childProxy); - if (startedNodeEvent.isRunning()) { - setNodeAndAncestorsRunning(node); - } + return node; + } + + @Override + protected SMTestProxy createSuite(String suiteName, String locationHint, String id, String parentNodeId) { + Node node = createNode(new TestSuiteStartedEvent(suiteName, id, parentNodeId, locationHint, null, null, false), true); + return node.getProxy(); + } + + @Override + protected SMTestProxy createProxy(String testName, String locationHint, String id, String parentNodeId) { + Node node = createNode(new TestStartedEvent(testName, id, parentNodeId, locationHint, null, null, false), false); + return node.getProxy(); } @Nullable @@ -403,18 +414,6 @@ public class GeneralIdBasedToSMTRunnerEventsConvertor extends GeneralTestEventsP return myTestsRootNode; } - @Override - public void onRootPresentationAdded(final String rootName, final String comment, final String rootLocation) { - addToInvokeLater(() -> { - myTestsRootProxy.setPresentation(rootName); - myTestsRootProxy.setComment(comment); - myTestsRootProxy.setRootLocationUrl(rootLocation); - if (myLocator != null) { - myTestsRootProxy.setLocator(myLocator); - } - }); - } - private enum State { NOT_RUNNING, RUNNING, FINISHED, FAILED, IGNORED } diff --git a/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/GeneralTestEventsProcessor.java b/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/GeneralTestEventsProcessor.java index 00d1270b7e34..ca4115743692 100644 --- a/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/GeneralTestEventsProcessor.java +++ b/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/GeneralTestEventsProcessor.java @@ -44,12 +44,16 @@ import java.util.List; public abstract class GeneralTestEventsProcessor implements Disposable { private static final Logger LOG = Logger.getInstance(GeneralTestEventsProcessor.class.getName()); protected final SMTRunnerEventsListener myEventPublisher; + protected final SMTestProxy.SMRootTestProxy myTestsRootProxy; + protected SMTestLocator myLocator = null; private final String myTestFrameworkName; private final Project myProject; private TransferToEDTQueue myTransferToEDTQueue; protected List myListenerAdapters = new ArrayList<>(); - public GeneralTestEventsProcessor(Project project, @NotNull String testFrameworkName) { + protected boolean myTreeBuildBeforeStart = false; + + public GeneralTestEventsProcessor(Project project, @NotNull String testFrameworkName, @NotNull SMTestProxy.SMRootTestProxy testsRootProxy) { myProject = project; myEventPublisher = project.getMessageBus().syncPublisher(SMTRunnerEventsListener.TEST_STATUS); myTestFrameworkName = testFrameworkName; @@ -57,17 +61,85 @@ public abstract class GeneralTestEventsProcessor implements Disposable { runnable.run(); return true; }, project.getDisposed(), 300); + myTestsRootProxy = testsRootProxy; } // tree construction events - public void onRootPresentationAdded(String rootName, String comment, String rootLocation) {} - - public void onSuiteTreeNodeAdded(String testName, String locationHint) { } + public void onRootPresentationAdded(final String rootName, final String comment, final String rootLocation) { + addToInvokeLater(() -> { + myTestsRootProxy.setPresentation(rootName); + myTestsRootProxy.setComment(comment); + myTestsRootProxy.setRootLocationUrl(rootLocation); + if (myLocator != null) { + myTestsRootProxy.setLocator(myLocator); + } + }); + } - public void onSuiteTreeStarted(String suiteName, String locationHint) { } + protected SMTestProxy createProxy(String testName, String locationHint, String id, String parentNodeId) { + return new SMTestProxy(testName, false, locationHint); + } - public void onSuiteTreeEnded(String suiteName) { } - public void onBuildTreeEnded() { } + protected SMTestProxy createSuite(String suiteName, String locationHint, String id, String parentNodeId) { + return new SMTestProxy(suiteName, true, locationHint); + } + + protected final List myBuildTreeRunnables = new ArrayList<>(); + + public void onSuiteTreeNodeAdded(final String testName, final String locationHint, String id, String parentNodeId) { + myTreeBuildBeforeStart = true; + myBuildTreeRunnables.add(() -> { + final SMTestProxy testProxy = createProxy(testName, locationHint, id, parentNodeId); + testProxy.setTreeBuildBeforeStart(); + if (myLocator != null) { + testProxy.setLocator(myLocator); + } + myEventPublisher.onSuiteTreeNodeAdded(testProxy); + for (SMTRunnerEventsListener adapter : myListenerAdapters) { + adapter.onSuiteTreeNodeAdded(testProxy); + } + //ensure root node gets the flag when merged with a single child + testProxy.getParent().setTreeBuildBeforeStart(); + }); + } + + public void onSuiteTreeStarted(final String suiteName, final String locationHint, String id, String parentNodeId) { + myTreeBuildBeforeStart = true; + myBuildTreeRunnables.add(() -> { + final SMTestProxy newSuite = createSuite(suiteName, locationHint, id, parentNodeId); + if (myLocator != null) { + newSuite.setLocator(myLocator); + } + newSuite.setTreeBuildBeforeStart(); + myEventPublisher.onSuiteTreeStarted(newSuite); + for (SMTRunnerEventsListener adapter : myListenerAdapters) { + adapter.onSuiteTreeStarted(newSuite); + } + }); + } + + public void onSuiteTreeEnded(final String suiteName) { + if (myBuildTreeRunnables.size() > 100) { + final ArrayList runnables = new ArrayList<>(myBuildTreeRunnables); + myBuildTreeRunnables.clear(); + processTreeBuildEvents(runnables); + } + } + + public void onBuildTreeEnded() { + final ArrayList runnables = new ArrayList<>(myBuildTreeRunnables); + myBuildTreeRunnables.clear(); + processTreeBuildEvents(runnables); + } + + private void processTreeBuildEvents(final List runnables) { + addToInvokeLater(() -> { + for (Runnable runnable : runnables) { + runnable.run(); + } + runnables.clear(); + }); + } // progress events @@ -201,7 +273,9 @@ public abstract class GeneralTestEventsProcessor implements Disposable { public abstract void onTestsReporterAttached(); - public abstract void setLocator(@NotNull SMTestLocator locator); + public void setLocator(@NotNull SMTestLocator locator) { + myLocator = locator; + } public void addEventsListener(@NotNull SMTRunnerEventsListener listener) { myListenerAdapters.add(listener); diff --git a/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/GeneralToSMTRunnerEventsConvertor.java b/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/GeneralToSMTRunnerEventsConvertor.java index 9b3df986925e..b74cae8fc822 100644 --- a/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/GeneralToSMTRunnerEventsConvertor.java +++ b/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/GeneralToSMTRunnerEventsConvertor.java @@ -40,37 +40,56 @@ public class GeneralToSMTRunnerEventsConvertor extends GeneralTestEventsProcesso private final TestSuiteStack mySuitesStack; private final Set myCurrentChildren = new LinkedHashSet<>(); private boolean myGetChildren = true; - private final SMTestProxy.SMRootTestProxy myTestsRootNode; + private boolean myIsTestingFinished; - private SMTestLocator myLocator = null; - private boolean myTreeBuildBeforeStart = false; + public GeneralToSMTRunnerEventsConvertor(Project project, @NotNull SMTestProxy.SMRootTestProxy testsRootNode, @NotNull String testFrameworkName) { - super(project, testFrameworkName); - myTestsRootNode = testsRootNode; + super(project, testFrameworkName, testsRootNode); mySuitesStack = new TestSuiteStack(testFrameworkName); } @Override - public void setLocator(@NotNull SMTestLocator locator) { - myLocator = locator; + protected SMTestProxy createProxy(String testName, String locationHint, String id, String parentNodeId) { + SMTestProxy proxy = super.createProxy(testName, locationHint, id, parentNodeId); + SMTestProxy currentSuite = getCurrentSuite(); + currentSuite.addChild(proxy); + return proxy; + } + + @Override + protected SMTestProxy createSuite(String suiteName, String locationHint, String id, String parentNodeId) { + SMTestProxy newSuite = super.createSuite(suiteName, locationHint, id, parentNodeId); + final SMTestProxy parentSuite = getCurrentSuite(); + + parentSuite.addChild(newSuite); + + mySuitesStack.pushSuite(newSuite); + + return newSuite; + } + + @Override + public void onSuiteTreeEnded(String suiteName) { + myBuildTreeRunnables.add(() -> mySuitesStack.popSuite(suiteName)); + super.onSuiteTreeEnded(suiteName); } public void onStartTesting() { addToInvokeLater(() -> { - mySuitesStack.pushSuite(myTestsRootNode); - myTestsRootNode.setStarted(); + mySuitesStack.pushSuite(myTestsRootProxy); + myTestsRootProxy.setStarted(); //fire - fireOnTestingStarted(myTestsRootNode); + fireOnTestingStarted(myTestsRootProxy); }); } @Override public void onTestsReporterAttached() { - addToInvokeLater(() -> fireOnTestsReporterAttached(myTestsRootNode)); + addToInvokeLater(() -> fireOnTestsReporterAttached(myTestsRootProxy)); } public void onFinishTesting() { @@ -85,102 +104,20 @@ public class GeneralToSMTRunnerEventsConvertor extends GeneralTestEventsProcesso // or it finished after all tests have been run // Lets assume, if at finish all suites except root suite are passed // then all is ok otherwise process was terminated by user - if (!isTreeComplete(myRunningTestsFullNameToProxy.keySet(), myTestsRootNode)) { - myTestsRootNode.setTerminated(); + if (!isTreeComplete(myRunningTestsFullNameToProxy.keySet(), myTestsRootProxy)) { + myTestsRootProxy.setTerminated(); myRunningTestsFullNameToProxy.clear(); } mySuitesStack.clear(); - myTestsRootNode.setFinished(); + myTestsRootProxy.setFinished(); //fire events - fireOnTestingFinished(myTestsRootNode); + fireOnTestingFinished(myTestsRootProxy); }); stopEventProcessing(); } - @Override - public void onRootPresentationAdded(final String rootName, final String comment, final String rootLocation) { - addToInvokeLater(() -> { - myTestsRootNode.setPresentation(rootName); - myTestsRootNode.setComment(comment); - myTestsRootNode.setRootLocationUrl(rootLocation); - if (myLocator != null) { - myTestsRootNode.setLocator(myLocator); - } - }); - } - - private final List myBuildTreeRunnables = new ArrayList<>(); - - @Override - public void onSuiteTreeNodeAdded(final String testName, final String locationHint) { - myTreeBuildBeforeStart = true; - myBuildTreeRunnables.add(() -> { - final SMTestProxy testProxy = new SMTestProxy(testName, false, locationHint); - testProxy.setTreeBuildBeforeStart(); - if (myLocator != null) { - testProxy.setLocator(myLocator); - } - SMTestProxy currentSuite = getCurrentSuite(); - currentSuite.addChild(testProxy); - myEventPublisher.onSuiteTreeNodeAdded(testProxy); - for (SMTRunnerEventsListener adapter : myListenerAdapters) { - adapter.onSuiteTreeNodeAdded(testProxy); - } - //ensure root node gets the flag when merged with a single child - currentSuite.setTreeBuildBeforeStart(); - }); - } - - @Override - public void onSuiteTreeStarted(final String suiteName, final String locationHint) { - myTreeBuildBeforeStart = true; - myBuildTreeRunnables.add(() -> { - final SMTestProxy parentSuite = getCurrentSuite(); - final SMTestProxy newSuite = new SMTestProxy(suiteName, true, locationHint); - if (myLocator != null) { - newSuite.setLocator(myLocator); - } - newSuite.setTreeBuildBeforeStart(); - parentSuite.addChild(newSuite); - - mySuitesStack.pushSuite(newSuite); - - myEventPublisher.onSuiteTreeStarted(newSuite); - for (SMTRunnerEventsListener adapter : myListenerAdapters) { - adapter.onSuiteTreeStarted(newSuite); - } - }); - } - - @Override - public void onSuiteTreeEnded(final String suiteName) { - myBuildTreeRunnables.add(() -> mySuitesStack.popSuite(suiteName)); - - if (myBuildTreeRunnables.size() > 100) { - final ArrayList runnables = new ArrayList<>(myBuildTreeRunnables); - myBuildTreeRunnables.clear(); - processTreeBuildEvents(runnables); - } - } - - @Override - public void onBuildTreeEnded() { - final ArrayList runnables = new ArrayList<>(myBuildTreeRunnables); - myBuildTreeRunnables.clear(); - processTreeBuildEvents(runnables); - } - - private void processTreeBuildEvents(final List runnables) { - addToInvokeLater(() -> { - for (Runnable runnable : runnables) { - runnable.run(); - } - runnables.clear(); - }); - } - @Override public void setPrinterProvider(@NotNull TestProxyPrinterProvider printerProvider) { } @@ -494,7 +431,7 @@ public class GeneralToSMTRunnerEventsConvertor extends GeneralTestEventsProcesso // or may be we are in debug mode logProblem("Current suite is undefined. Root suite will be used."); myGetChildren = true; - return myTestsRootNode; + return myTestsRootProxy; } @@ -564,7 +501,7 @@ public class GeneralToSMTRunnerEventsConvertor extends GeneralTestEventsProcesso //current suite // // ProcessHandler can fire output available event before processStarted event - currentProxy = mySuitesStack.isEmpty() ? myTestsRootNode : getCurrentSuite(); + currentProxy = mySuitesStack.isEmpty() ? myTestsRootProxy : getCurrentSuite(); } return currentProxy; } diff --git a/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/OutputToGeneralTestEventsConverter.java b/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/OutputToGeneralTestEventsConverter.java index 18acd6446989..9c794df20f2a 100644 --- a/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/OutputToGeneralTestEventsConverter.java +++ b/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/OutputToGeneralTestEventsConverter.java @@ -233,10 +233,10 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer } } - private void fireOnSuiteTreeNodeAdded(String testName, String locationHint) { + private void fireOnSuiteTreeNodeAdded(String testName, String locationHint, String id, String parentNodeId) { final GeneralTestEventsProcessor processor = myProcessor; if (processor != null) { - processor.onSuiteTreeNodeAdded(testName, locationHint); + processor.onSuiteTreeNodeAdded(testName, locationHint, id, parentNodeId); } } @@ -248,11 +248,11 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer } } - private void fireOnSuiteTreeStarted(String suiteName, String locationHint) { + private void fireOnSuiteTreeStarted(String suiteName, String locationHint, String id, String parentNodeId) { final GeneralTestEventsProcessor processor = myProcessor; if (processor != null) { - processor.onSuiteTreeStarted(suiteName, locationHint); + processor.onSuiteTreeStarted(suiteName, locationHint, id, parentNodeId); } } @@ -541,13 +541,13 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer fireOnTestFrameworkAttached(); } else if (SUITE_TREE_STARTED.equals(name)) { - fireOnSuiteTreeStarted(msg.getAttributes().get("name"), msg.getAttributes().get(ATTR_KEY_LOCATION_URL)); + fireOnSuiteTreeStarted(msg.getAttributes().get("name"), msg.getAttributes().get(ATTR_KEY_LOCATION_URL), TreeNodeEvent.getNodeId(msg), msg.getAttributes().get("parentNodeId")); } else if (SUITE_TREE_ENDED.equals(name)) { fireOnSuiteTreeEnded(msg.getAttributes().get("name")); } else if (SUITE_TREE_NODE.equals(name)) { - fireOnSuiteTreeNodeAdded(msg.getAttributes().get("name"), msg.getAttributes().get(ATTR_KEY_LOCATION_URL)); + fireOnSuiteTreeNodeAdded(msg.getAttributes().get("name"), msg.getAttributes().get(ATTR_KEY_LOCATION_URL), TreeNodeEvent.getNodeId(msg), msg.getAttributes().get("parentNodeId")); } else if (BUILD_TREE_ENDED_NODE.equals(name)) { fireOnBuildTreeEnded(); diff --git a/platform/smRunner/testSrc/com/intellij/execution/testframework/sm/runner/MockGeneralTestEventsProcessorAdapter.java b/platform/smRunner/testSrc/com/intellij/execution/testframework/sm/runner/MockGeneralTestEventsProcessorAdapter.java index ce33179abb37..1aa74667bd83 100644 --- a/platform/smRunner/testSrc/com/intellij/execution/testframework/sm/runner/MockGeneralTestEventsProcessorAdapter.java +++ b/platform/smRunner/testSrc/com/intellij/execution/testframework/sm/runner/MockGeneralTestEventsProcessorAdapter.java @@ -33,7 +33,7 @@ public class MockGeneralTestEventsProcessorAdapter extends GeneralTestEventsProc private final List> myOutputChunks = new ArrayList<>(); public MockGeneralTestEventsProcessorAdapter(Project project, String testFrameworkName) { - super(project, testFrameworkName); + super(project, testFrameworkName, new SMTestProxy.SMRootTestProxy()); } @Override diff --git a/platform/smRunner/testSrc/com/intellij/execution/testframework/sm/runner/ui/SMTestRunnerResultsFormTest.java b/platform/smRunner/testSrc/com/intellij/execution/testframework/sm/runner/ui/SMTestRunnerResultsFormTest.java index 50a7fa2804b1..4587ac036d77 100644 --- a/platform/smRunner/testSrc/com/intellij/execution/testframework/sm/runner/ui/SMTestRunnerResultsFormTest.java +++ b/platform/smRunner/testSrc/com/intellij/execution/testframework/sm/runner/ui/SMTestRunnerResultsFormTest.java @@ -290,8 +290,8 @@ public class SMTestRunnerResultsFormTest extends BaseSMTRunnerTestCase { //with test tree build before start actual tests public void testPrependTreeAndSameTestsStartFinish() throws Exception { //send tree - myEventsProcessor.onSuiteTreeStarted("suite1", null); - myEventsProcessor.onSuiteTreeNodeAdded("test1", null); + myEventsProcessor.onSuiteTreeStarted("suite1", null, "suite1", "0"); + myEventsProcessor.onSuiteTreeNodeAdded("test1", null, "test1", "suite1"); myEventsProcessor.onSuiteTreeEnded("suite1"); //start testing diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/MalformedPatternException.java b/platform/structuralsearch/source/com/intellij/structuralsearch/MalformedPatternException.java index e73eddc19550..3efeae3a1fd9 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/MalformedPatternException.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/MalformedPatternException.java @@ -1,11 +1,23 @@ +/* + * Copyright 2000-2017 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.structuralsearch; -import org.jetbrains.annotations.NonNls; - -/** - * Class to indicate incorrect pattern - */ -public class MalformedPatternException extends RuntimeException { +public class MalformedPatternException extends StructuralSearchException { public MalformedPatternException() {} - public MalformedPatternException(String msg) { super(msg); } + public MalformedPatternException(String msg) { + super(msg); + } } diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/Matcher.java b/platform/structuralsearch/source/com/intellij/structuralsearch/Matcher.java index a72e770429a0..c64870e953e6 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/Matcher.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/Matcher.java @@ -201,15 +201,13 @@ public class Matcher { matchContext.setPattern(compiledPattern); out.put(configuration, matchContext); } - catch (UnsupportedPatternException | MalformedPatternException ignored) {} + catch (StructuralSearchException ignored) {} }); } } /** * Finds the matches of given pattern starting from given tree element. - * @throws MalformedPatternException - * @throws UnsupportedPatternException */ public void findMatches(MatchResultSink sink, MatchOptions options) throws MalformedPatternException, UnsupportedPatternException { CompiledPattern compiledPattern = prepareMatching(sink, options); diff --git a/platform/testGuiFramework/testSrc/com/intellij/testGuiFramework/tests/control/KotlinControlTest.kt b/platform/structuralsearch/source/com/intellij/structuralsearch/NoMatchFoundException.java similarity index 69% rename from platform/testGuiFramework/testSrc/com/intellij/testGuiFramework/tests/control/KotlinControlTest.kt rename to platform/structuralsearch/source/com/intellij/structuralsearch/NoMatchFoundException.java index da1498bae869..4c97c7abdaba 100644 --- a/platform/testGuiFramework/testSrc/com/intellij/testGuiFramework/tests/control/KotlinControlTest.kt +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/NoMatchFoundException.java @@ -13,16 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.testGuiFramework.tests.control +package com.intellij.structuralsearch; -import com.intellij.testGuiFramework.remote.RemoteTestCase -import org.junit.Test +/** + * @author Bas Leijdekkers + */ +public class NoMatchFoundException extends StructuralSearchException { -class KotlinControlTest: RemoteTestCase() { - - @Test - fun testAcceptance() { -// val ide = Ide(IdeType.IDEA_ULTIMATE, , 0) + public NoMatchFoundException(String message) { + super(message); } - -} \ No newline at end of file +} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/UnsupportedPatternException.java b/platform/structuralsearch/source/com/intellij/structuralsearch/UnsupportedPatternException.java index 1236264118c2..6bc993317941 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/UnsupportedPatternException.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/UnsupportedPatternException.java @@ -1,9 +1,24 @@ +/* + * Copyright 2000-2017 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.structuralsearch; /** * Exception about encountering yet unsupported pattern event. */ -public class UnsupportedPatternException extends RuntimeException { +public class UnsupportedPatternException extends StructuralSearchException { public UnsupportedPatternException(String _pattern) { super(_pattern); diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/GlobalMatchingVisitor.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/GlobalMatchingVisitor.java index ecfc5958a65f..6b35093c8dd5 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/GlobalMatchingVisitor.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/GlobalMatchingVisitor.java @@ -219,7 +219,7 @@ public class GlobalMatchingVisitor extends AbstractMatchingVisitor { final List matchedNodes = matchContext.getMatchedNodes(); - if (matched) { + if (matched && matchedNodes != null) { dispatchMatched(matchedNodes, matchContext.getResult()); } diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/PatternCompiler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/PatternCompiler.java index a95810201ee8..fcef00feab79 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/PatternCompiler.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/PatternCompiler.java @@ -60,7 +60,7 @@ public class PatternCompiler { private static CompileContext lastTestingContext; public static CompiledPattern compilePattern(final Project project, final MatchOptions options) - throws MalformedPatternException, UnsupportedOperationException { + throws MalformedPatternException, NoMatchFoundException, UnsupportedOperationException { FileType fileType = options.getFileType(); assert fileType instanceof LanguageFileType; Language language = ((LanguageFileType)fileType).getLanguage(); @@ -95,7 +95,7 @@ public class PatternCompiler { } if (filesToScan.size() == 0) { - throw new MalformedPatternException(SSRBundle.message("ssr.will.not.find.anything")); + throw new NoMatchFoundException(SSRBundle.message("ssr.will.not.find.anything", scope.getDisplayName())); } result.setScope(new LocalSearchScope(PsiUtilCore.toPsiElementArray(filesToScan))); } @@ -137,7 +137,7 @@ public class PatternCompiler { MatchOptions options, CompiledPattern pattern, CompileContext context, - String[] applicablePrefixes) { + String[] applicablePrefixes) throws MalformedPatternException { if (applicablePrefixes.length == 0) { return Collections.emptyList(); } @@ -189,7 +189,7 @@ public class PatternCompiler { String[] applicablePrefixes, Pattern[] substitutionPatterns, String[] prefixSequence, - int index) { + int index) throws MalformedPatternException { if (index >= prefixSequence.length) { final List elements = doCompile(project, options, pattern, new ArrayPrefixProvider(prefixSequence), context); if (elements.isEmpty()) { @@ -353,7 +353,7 @@ public class PatternCompiler { MatchOptions options, CompiledPattern result, PrefixProvider prefixProvider, - CompileContext context) { + CompileContext context) throws MalformedPatternException { result.clearHandlers(); final StringBuilder buf = new StringBuilder(); @@ -506,7 +506,8 @@ public class PatternCompiler { } } - private static void addScriptConstraint(Project project, String name, MatchVariableConstraint constraint, SubstitutionHandler handler) { + private static void addScriptConstraint(Project project, String name, MatchVariableConstraint constraint, SubstitutionHandler handler) + throws MalformedPatternException { if (constraint.getScriptCodeConstraint()!= null && constraint.getScriptCodeConstraint().length() > 2) { final String script = StringUtil.unquoteString(constraint.getScriptCodeConstraint()); final String problem = ScriptSupport.checkValidScript(script); diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceDialog.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceDialog.java index d4b1284198d5..918390a25fce 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceDialog.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceDialog.java @@ -193,11 +193,11 @@ public class ReplaceDialog extends SearchDialog { Replacer.checkSupportedReplacementPattern(searchContext.getProject(), ((ReplaceConfiguration)model.getConfig()).getReplaceOptions()); } catch (UnsupportedPatternException ex) { - reportMessage("unsupported.replacement.pattern.message", replaceCriteriaEdit, ex.getMessage()); + reportMessage(SSRBundle.message("unsupported.replacement.pattern.message", ex.getMessage()), replaceCriteriaEdit); return false; } catch (MalformedPatternException ex) { - reportMessage("malformed.replacement.pattern.message", replaceCriteriaEdit, ex.getMessage()); + reportMessage(SSRBundle.message("malformed.replacement.pattern.message", ex.getMessage()), replaceCriteriaEdit); return false; } diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchDialog.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchDialog.java index 1c932acce74d..4089b1684095 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchDialog.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchDialog.java @@ -741,7 +741,7 @@ public class SearchDialog extends DialogWrapper { startSearching(); } catch (MalformedPatternException ex) { - reportMessage("this.pattern.is.malformed.message", searchCriteriaEdit, ex.getMessage()); + reportMessage(SSRBundle.message("this.pattern.is.malformed.message", ex.getMessage()), searchCriteriaEdit); } } @@ -776,20 +776,24 @@ public class SearchDialog extends DialogWrapper { } catch (MalformedPatternException ex) { if (myRunFindActionOnClose) { - reportMessage("this.pattern.is.malformed.message", searchCriteriaEdit, (ex.getMessage() != null) ? ex.getMessage() : ""); + reportMessage(SSRBundle.message("this.pattern.is.malformed.message", + (ex.getMessage() != null) ? ex.getMessage() : ""), searchCriteriaEdit); return false; } } catch (UnsupportedPatternException ex) { - reportMessage("this.pattern.is.unsupported.message", searchCriteriaEdit, ex.getMessage()); + reportMessage(SSRBundle.message("this.pattern.is.unsupported.message", ex.getMessage()), searchCriteriaEdit); + return false; + } + catch (NoMatchFoundException e) { + reportMessage(e.getMessage(), searchCriteriaEdit); return false; } return true; } - protected void reportMessage(@NonNls final String messageId, final Editor editor, final Object... params) { + protected void reportMessage(String message, Editor editor) { com.intellij.util.ui.UIUtil.invokeLaterIfNeeded(() -> { - final String message = messageId != null ? SSRBundle.message(messageId, params) : ""; status.setText(message); status.setToolTipText(message); status.revalidate(); diff --git a/platform/structuralsearch/source/messages/SSRBundle.properties b/platform/structuralsearch/source/messages/SSRBundle.properties index 7a2e063dd13e..dd8e35e30eef 100644 --- a/platform/structuralsearch/source/messages/SSRBundle.properties +++ b/platform/structuralsearch/source/messages/SSRBundle.properties @@ -38,8 +38,8 @@ found.occurrences=Found Matches in {0} targets.node.text=Structural Search Template # search dialog messages -this.pattern.is.malformed.message=This pattern is malformed\n {0} -this.pattern.is.unsupported.message=This pattern is unsupported +this.pattern.is.malformed.message=the specified pattern is malformed\n {0} +this.pattern.is.unsupported.message=the specified pattern is unsupported incorrect.pattern.message=Incorrect pattern used.templates.history.dialog.title=Used Templates History @@ -232,7 +232,7 @@ status.message=Status: overwrite.message=A template with the same name already exists. Replacing it will overwrite its current contents. overwrite.title="{0}" Exists, Replace? template.in.use.message=Template ''{0}'' is used from template ''{1}''. Are you sure you want to remove it? -ssr.will.not.find.anything=It does not match anything in selected scope +ssr.will.not.find.anything=the specified pattern does not match anything in scope ''{0}'' predefined.configuration.class.static.blocks=static blocks in class predefined.configuration.class.instance.initialization.blocks=instance initialization blocks predefined.configuration.class.any.initialization.blocks=any class initialization block diff --git a/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralReplaceTest.java b/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralReplaceTest.java index cdbbf0a03505..e49221490210 100644 --- a/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralReplaceTest.java +++ b/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralReplaceTest.java @@ -2033,6 +2033,16 @@ public class StructuralReplaceTest extends StructuralReplaceTestCase { final String by2 = "@SuppressWarnings(\"NONE\") @Deprecated"; assertEquals("@SuppressWarnings(\"NONE\") @Deprecated\n" + "public class A {}", replacer.testReplace(in, what, by2, options, false)); + + final String in2 = "class X {" + + " @SuppressWarnings(\"unused\") String s;" + + "}"; + final String what2 = "@SuppressWarnings(\"unused\") String '_s;"; + final String by3 = "@SuppressWarnings({\"unused\", \"other\"}) String $s$;"; + assertEquals("class X {" + + " @SuppressWarnings({\"unused\", \"other\"}) String s;" + + "}", replacer.testReplace(in2, what2, by3, options, false)); + } public void testReplacePolyadicExpression() { diff --git a/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralSearchTest.java b/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralSearchTest.java index 97312d15a285..510a1d3c6c11 100644 --- a/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralSearchTest.java +++ b/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralSearchTest.java @@ -1593,15 +1593,15 @@ public class StructuralSearchTest extends StructuralSearchTestCase { " @NotNull private static Collection resolveElements2(final PsiReference reference, final Project project) {}\n" + "}"; - assertEquals("Find annotated methods",2, + assertEquals("Find annotated methods", 2, findMatchesCount(s5, "class '_c {@NotNull '_rt 'method* ('_pt* '_p*){ '_inst*; } }")); - assertEquals("Find annotated methods, 2",2, + assertEquals("Find annotated methods, 2", 2, findMatchesCount(s5, "class '_c {@'_:NotNull '_rt 'method* ('_pt* '_p*){ '_inst*; } }")); String s7 = "class A { void message(@NonNls String msg); }\n" + "class B { void message2(String msg); }\n" + "class C { void message2(String msg); }"; - assertEquals("Find not annotated methods",2,findMatchesCount(s7, "class '_A { void 'b( @'_Ann{0,0}:NonNls String '_); }")); + assertEquals("Find not annotated methods", 2, findMatchesCount(s7, "class '_A { void 'b( @'_Ann{0,0}:NonNls String '_); }")); String s9 = "class A {\n" + " Object[] method1() {}\n" + @@ -1611,29 +1611,42 @@ public class StructuralSearchTest extends StructuralSearchTestCase { " @MyAnnotation Object[] method2(int a) {}\n" + " @NonNls Object[] method3() {}\n" + "}"; - assertEquals("Find not annotated methods, 2",2, + assertEquals("Find not annotated methods, 2", 2, findMatchesCount(s9, "class '_A { @'_Ann{0,0}:NonNls '_Type:Object\\[\\] 'b+( '_pt* '_p* ); }")); - assertEquals("Find not annotated methods, 2",2, + assertEquals("Find not annotated methods, 2", 2, findMatchesCount(s9, "class '_A { @'_Ann{0,0}:NonNls '_Type [] 'b+( '_pt* '_p* ); }")); - assertEquals("Find not annotated methods, 2",2, + assertEquals("Find not annotated methods, 2", 2, findMatchesCount(s9, "class '_A { @'_Ann{0,0}:NonNls '_Type:Object [] 'b+( '_pt* '_p* ); }")); String s11 = "class A {\n" + - "@Foo(value=baz) int a;\n" + - "@Foo(value=baz2) int a2;\n" + - "@Foo(value=baz2) int a3;\n" + - "@Foo(value2=baz3) int a3;\n" + - "@Foo(value2=baz3) int a3;\n" + - "@Foo(value2=baz3) int a3;\n" + - "@Foo(value2=baz4) int a3;\n" + + " @Foo(value=baz) int a;\n" + + " @Foo(value=baz2) int a2;\n" + + " @Foo(baz2) int a3;\n" + + " @Foo(value2=baz2) int a4;\n" + + " @Foo(value2=baz2) int a5;\n" + + " @Foo(value2=baz3) int a6;\n" + + " @Foo(value2=baz3) int a7;\n" + + " @Foo(value2=baz3) int a8;\n" + + " @Foo(value2=baz4) int a9;\n" + + " @Foo int a10;\n" + "}"; - assertEquals("Find anno parameter value",1,findMatchesCount(s11, "@Foo(value=baz) int 'a;")); - assertEquals("Find anno parameter value",2,findMatchesCount(s11, "@Foo(value='baz:baz2 ) int '_a;")); - assertEquals("Find anno parameter value",3,findMatchesCount(s11, "@Foo('value:value2 = baz3 ) int '_a;")); - assertEquals("Find anno parameter value",3,findMatchesCount(s11, "@Foo('value:value2 = '_baz3:baz3 ) int '_a;")); - assertEquals("Find anno parameter value",0,findMatchesCount(s11, "@Foo('value:value2 = '_baz3:baz ) int '_a;")); - assertEquals("Find anno parameter value",4,findMatchesCount(s11, "@Foo('value:value2 = '_baz3 ) int '_a;")); - assertEquals("Find anno parameter value",4,findMatchesCount(s11, "@Foo('value:value2 = ) int '_a;")); + assertEquals("Find anno parameter value", 1, findMatchesCount(s11, "@Foo(value=baz) int 'a;")); + assertEquals("Find anno parameter value", 2, findMatchesCount(s11, "@Foo(value='baz:baz2 ) int '_a;")); + assertEquals("Find anno parameter value", 3, findMatchesCount(s11, "@Foo('value:value2 = baz3 ) int '_a;")); + assertEquals("Find anno parameter value", 3, findMatchesCount(s11, "@Foo('value:value2 = '_baz3:baz3 ) int '_a;")); + assertEquals("Find anno parameter value", 0, findMatchesCount(s11, "@Foo('value:value2 = '_baz3:baz ) int '_a;")); + assertEquals("Find anno parameter value", 6, findMatchesCount(s11, "@Foo('value:value2 = '_baz3 ) int '_a;")); + try { + findMatchesCount(s11, "@Foo('value:value2 = ) int '_a;"); + fail("should report missing value"); + } catch (MalformedPatternException ignored) {} + assertEquals("Match anno parameter name", 6, findMatchesCount(s11, "@Foo(value2='_value)")); + assertEquals("Match anno parameter name 2", 3, findMatchesCount(s11, "@Foo(value='_value)")); + assertEquals("Match anno parameter value only", 4, findMatchesCount(s11, "@Foo(baz2)")); + assertEquals("Match all anno parameters", 9, findMatchesCount(s11, "@Foo('_any)")); + assertEquals("Match all annotations", 10, findMatchesCount(s11, "@Foo('_any*)")); + // todo + //assertEquals("Match annotations without parameters", 1, findMatchesCount(s11, "@Foo('_any{0,0})")); String source1 = "class A {" + " void m() {" + @@ -1677,6 +1690,14 @@ public class StructuralSearchTest extends StructuralSearchTestCase { "}"; assertEquals("Find annotation on instanceof expression", 1, findMatchesCount(source5, "'_a instanceof @HH String")); assertEquals("Match annotation correctly on instanceof expression", 0, findMatchesCount(source5, "'_a instanceof @GG String")); + + String source6 = "@SuppressWarnings({\"WeakerAccess\", \"unused\", \"UnnecessaryInterfaceModifier\"}) class A {" + + " @SuppressWarnings({\"unused\"}) @NotNull int i;" + + "}"; + assertEquals("Find SuppressWarnings annotations", 2, findMatchesCount(source6, "@SuppressWarnings")); + assertEquals("Find SuppressWarnings annotations", 2, findMatchesCount(source6, "@SuppressWarnings(value='_any)")); + assertEquals("Find annotation with 3 value array initializer", 1, findMatchesCount(source6, "@SuppressWarnings({'_value{3,3} })")); + } public void testBoxingAndUnboxing() { @@ -2252,6 +2273,8 @@ public class StructuralSearchTest extends StructuralSearchTestCase { findMatchesCount(source, "import java.util.ArrayList;"); fail("malformed pattern warning expected"); } catch (MalformedPatternException ignored) {} + + findMatchesCount(source, "'_ReturnType '_Method*('_ParameterType '_Parameter);"); } public void testFindInnerClass() { diff --git a/platform/testFramework/extensions/src/com/intellij/keymap/KeymapsTestCase.java b/platform/testFramework/extensions/src/com/intellij/keymap/KeymapsTestCase.java index c249662eb030..85b7abc59cbf 100644 --- a/platform/testFramework/extensions/src/com/intellij/keymap/KeymapsTestCase.java +++ b/platform/testFramework/extensions/src/com/intellij/keymap/KeymapsTestCase.java @@ -42,15 +42,15 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase { { "alt UP", "MethodUp", "Arrangement.Rule.Match.Condition.Move.Up"}, { "alt F1", "SelectIn", "ProjectViewChangeView"}, { "alt INSERT", "FileChooser.NewFolder", "Generate", "NewElement"}, - { "control F10", "javaee.UpdateRunningApplication", "liveedit.UpdateRunningApplication"}, + { "control F10", "Android.HotswapChanges", "javaee.UpdateRunningApplication", "liveedit.UpdateRunningApplication"}, { "control 1", "FileChooser.GotoHome", "GotoBookmark1", "DuplicatesForm.SendToLeft"}, { "control 2", "FileChooser.GotoProject", "GotoBookmark2", "DuplicatesForm.SendToRight"}, { "control 3", "GotoBookmark3", "FileChooser.GotoModule"}, - { "control ADD", "ExpandAll", "ExpandRegion"}, + { "control ADD", "ExpandAll", "ExpandExpandableComponent", "ExpandRegion"}, { "control DIVIDE", "CommentByLineComment", "Images.Editor.ActualSize"}, { "control DOWN", "EditorScrollDown", "EditorLookupDown", "MethodOverloadSwitchDown"}, { "control ENTER", "EditorSplitLine", "ViewSource", "Console.Execute.Multiline"}, - { "control EQUALS", "ExpandAll", "ExpandRegion"}, + { "control EQUALS", "ExpandAll", "ExpandExpandableComponent", "ExpandRegion"}, { "control F5", "Refresh", "Rerun"}, { "control D", "EditorDuplicate", "Diff.ShowDiff", "CompareTwoFiles", "SendEOF", "FileChooser.GotoDesktop"}, { "control L", "FindNext", "Vcs.Log.FocusTextFilter"}, @@ -61,7 +61,7 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase { { "control SLASH", "CommentByLineComment", "Images.Editor.ActualSize"}, { "control U", "GotoSuperMethod", "CommanderSwapPanels"}, { "control UP", "EditorScrollUp", "EditorLookupUp", "MethodOverloadSwitchUp"}, - { "control SUBTRACT", "CollapseAll", "CollapseRegion"}, + { "control SUBTRACT", "CollapseAll", "CollapseExpandableComponent", "CollapseRegion"}, { "control alt A", "ChangesView.AddUnversioned", "Diagram.DeselectAll"}, { "control alt E", "PerforceDirect.Edit", "Console.History.Browse"}, { "control alt DOWN", "NextOccurence", "Console.TableResult.NextPage"}, @@ -71,7 +71,7 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase { { "control alt N", "Inline", "Console.TableResult.SetNull"}, { "ctrl alt H", "CallHierarchy", "ChangesView.ShelveSilently"}, { "ctrl alt U", "ShowUmlDiagramPopup", "ChangesView.UnshelveSilently"}, - { "control MINUS", "CollapseAll", "CollapseRegion"}, + { "control MINUS", "CollapseAll", "CollapseExpandableComponent", "CollapseRegion"}, { "control PERIOD", "EditorChooseLookupItemDot", "CollapseSelection"}, { "shift DELETE", "$Cut", "Maven.Uml.Exclude"}, { "shift ENTER", "EditorStartNewLine", "Console.TableResult.EditValueMaximized", "OpenElementInNewWindow"}, @@ -113,6 +113,7 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase { { "meta R", "Refresh", "Rerun", "Replace", "org.jetbrains.plugins.ruby.rails.console.ReloadSources"}, { "control O", "ExportToTextFile", "OverrideMethods", }, { "control ENTER", "Generate", "NewElement"}, + { "ctrl meta R", "Android.HotswapChanges", "RerunTests"}, { "meta 1", "ActivateProjectToolWindow", "FileChooser.GotoHome", "DuplicatesForm.SendToLeft"}, { "meta 2", "ActivateFavoritesToolWindow", "FileChooser.GotoProject", "DuplicatesForm.SendToRight"}, { "meta 3", "ActivateFindToolWindow", "FileChooser.GotoModule"}, @@ -174,7 +175,7 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase { { "alt F8", "ReformatCode", "ForceStepInto", "EvaluateExpression"}, { "control COMMA", "GotoClass", "GotoChangedFile"}, { "control F1", "ExternalJavaDoc", "ShowErrorDescription"}, - { "control F10", "RunToCursor", "javaee.UpdateRunningApplication", "liveedit.UpdateRunningApplication"}, + { "control F10", "Android.HotswapChanges", "RunToCursor", "javaee.UpdateRunningApplication", "liveedit.UpdateRunningApplication"}, { "control F5", "Rerun", "Run"}, { "control N", "FileChooser.NewFolder", "Generate", }, { "control P", "FileChooser.TogglePathShowing", "Print"}, @@ -210,7 +211,7 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase { { "alt DOWN", "ShowContent", "MoveStatementDown", "Arrangement.Rule.Match.Condition.Move.Down"}, { "alt UP", "MoveStatementUp", "Arrangement.Rule.Match.Condition.Move.Up"}, { "alt HOME", "ViewNavigationBar", "ShowNavBar"}, - { "control F10", "ShowPopupMenu", "javaee.UpdateRunningApplication", "liveedit.UpdateRunningApplication"}, + { "control F10", "Android.HotswapChanges", "ShowPopupMenu", "javaee.UpdateRunningApplication", "liveedit.UpdateRunningApplication"}, { "control D", "EditorDeleteLine", "Diff.ShowDiff", "CompareTwoFiles", "SendEOF", "FileChooser.GotoDesktop"}, { "control L", "Vcs.Log.FocusTextFilter", "GotoLine"}, { "control N", "ShowPopupMenu", "FileChooser.NewFolder"}, diff --git a/platform/testFramework/src/com/intellij/testFramework/ExpectedHighlightingData.java b/platform/testFramework/src/com/intellij/testFramework/ExpectedHighlightingData.java index 5874488666f8..2432ac9a63ad 100644 --- a/platform/testFramework/src/com/intellij/testFramework/ExpectedHighlightingData.java +++ b/platform/testFramework/src/com/intellij/testFramework/ExpectedHighlightingData.java @@ -224,7 +224,7 @@ public class ExpectedHighlightingData { endOffset -= endTag.length(); LineMarkerInfo markerInfo = new LineMarkerInfo(myFile, new TextRange(startOffset, endOffset), null, Pass.LINE_MARKERS, - new ConstantFunction<>(descr), null, + new ConstantFunction<>(StringUtil.unescapeStringCharacters(descr)), null, GutterIconRenderer.Alignment.RIGHT); myLineMarkerInfos.put(document.createRangeMarker(startOffset, endOffset), markerInfo); @@ -317,7 +317,8 @@ public class ExpectedHighlightingData { final Matcher closingTagMatcher = Pattern.compile("").matcher(text); while (true) { if (!closingTagMatcher.find(pos)) { - LOG.error("Cannot find closing in position " + pos); + toContinueFrom = pos; + break; } final int nextTagStart = matcher.find(pos) ? matcher.start() : text.length(); diff --git a/platform/testFramework/src/com/intellij/testFramework/LexerTestCase.java b/platform/testFramework/src/com/intellij/testFramework/LexerTestCase.java index c7949b801626..0d6a09af8cb0 100644 --- a/platform/testFramework/src/com/intellij/testFramework/LexerTestCase.java +++ b/platform/testFramework/src/com/intellij/testFramework/LexerTestCase.java @@ -18,6 +18,7 @@ package com.intellij.testFramework; import com.intellij.lang.TokenWrapper; import com.intellij.lexer.Lexer; import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.editor.highlighter.HighlighterIterator; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.tree.IElementType; @@ -109,12 +110,29 @@ public abstract class LexerTestCase extends UsefulTestCase { StringBuilder result = new StringBuilder(); IElementType tokenType; while ((tokenType = lexer.getTokenType()) != null) { - result.append(tokenType).append(" ('").append(getTokenText(lexer)).append("')\n"); + result.append(printSingleToken(text, tokenType, lexer.getTokenStart(), lexer.getTokenEnd())); lexer.advance(); } return result.toString(); } + @NotNull + public static String printTokens(@NotNull HighlighterIterator iterator) { + CharSequence text = iterator.getDocument().getCharsSequence(); + StringBuilder result = new StringBuilder(); + IElementType tokenType; + while (!iterator.atEnd()) { + tokenType = iterator.getTokenType(); + result.append(printSingleToken(text, tokenType, iterator.getStart(), iterator.getEnd())); + iterator.advance(); + } + return result.toString(); + } + + public static String printSingleToken(CharSequence fileText, IElementType tokenType, int start, int end) { + return tokenType + " ('" + getTokenText(tokenType, fileText, start, end) + "')\n"; + } + protected void doFileTest(@NonNls String fileExt) { doTest(loadTestDataFile("." + fileExt)); } @@ -137,15 +155,13 @@ public abstract class LexerTestCase extends UsefulTestCase { return true; } - private static String getTokenText(Lexer lexer) { - final IElementType tokenType = lexer.getTokenType(); + @NotNull + private static String getTokenText(IElementType tokenType, CharSequence sequence, int start, int end) { if (tokenType instanceof TokenWrapper) { return ((TokenWrapper)tokenType).getValue(); } - String text = lexer.getBufferSequence().subSequence(lexer.getTokenStart(), lexer.getTokenEnd()).toString(); - text = StringUtil.replace(text, "\n", "\\n"); - return text; + return StringUtil.replace(sequence.subSequence(start, end).toString(), "\n", "\\n"); } protected abstract Lexer createLexer(); diff --git a/platform/testFramework/src/com/intellij/testFramework/fixtures/TestLookupElementPresentation.java b/platform/testFramework/src/com/intellij/testFramework/fixtures/TestLookupElementPresentation.java new file mode 100644 index 000000000000..23df14b39bcf --- /dev/null +++ b/platform/testFramework/src/com/intellij/testFramework/fixtures/TestLookupElementPresentation.java @@ -0,0 +1,55 @@ +/* + * Copyright 2000-2017 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.testFramework.fixtures; + +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementPresentation; +import com.intellij.ui.DeferredIcon; +import com.intellij.ui.LayeredIcon; +import com.intellij.ui.RowIcon; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +/** + * @author peter + */ +public class TestLookupElementPresentation extends LookupElementPresentation { + + @NotNull + public static TestLookupElementPresentation renderReal(@NotNull LookupElement e) { + TestLookupElementPresentation p = new TestLookupElementPresentation() { + @Override + public boolean isReal() { + return true; + } + }; + e.renderElement(p); + return p; + } + + @Nullable + public static Icon unwrapIcon(@Nullable Icon icon) { + while (true) { + if (icon instanceof RowIcon) icon = ((RowIcon)icon).getIcon(0); + else if (icon instanceof DeferredIcon) icon = ((DeferredIcon)icon).evaluate(); + else if (icon instanceof LayeredIcon) icon = ((LayeredIcon)icon).getIcon(0); + else return icon; + } + } + +} diff --git a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java index 0e1015b75f53..18c532627fc8 100644 --- a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java +++ b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java @@ -211,7 +211,8 @@ public class CodeInsightTestFixtureImpl extends BaseFixture implements CodeInsig int oldDelay = settings.AUTOREPARSE_DELAY; try { settings.AUTOREPARSE_DELAY = 0; - List infos = codeAnalyzer.runPasses(file, editor.getDocument(), textEditor, toIgnore, canChangeDocument, null); + List infos = + codeAnalyzer.runPasses(file, editor.getDocument(), Collections.singletonList(textEditor), toIgnore, canChangeDocument, null); infos.addAll(DaemonCodeAnalyzerEx.getInstanceEx(project).getFileLevelHighlights(project, file)); return infos; } diff --git a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/HeavyIdeaTestFixtureImpl.java b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/HeavyIdeaTestFixtureImpl.java index 91fb97a46318..a35df66b55f0 100644 --- a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/HeavyIdeaTestFixtureImpl.java +++ b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/HeavyIdeaTestFixtureImpl.java @@ -48,6 +48,7 @@ import com.intellij.testFramework.builders.ModuleFixtureBuilder; import com.intellij.testFramework.fixtures.HeavyIdeaTestFixture; import com.intellij.util.ObjectUtils; import com.intellij.util.PathUtil; +import com.intellij.util.lang.CompoundRuntimeException; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -57,9 +58,14 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.PrintStream; +import java.nio.file.Files; +import java.util.Comparator; import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; /** * Creates new project for each test. @@ -111,10 +117,22 @@ class HeavyIdeaTestFixtureImpl extends BaseFixture implements HeavyIdeaTestFixtu for (File fileToDelete : myFilesToDelete) { runAll = runAll.append(() -> { - if (!FileUtil.delete(fileToDelete)) { - throw new IOException("Can't delete " + fileToDelete); - } - }); + List errors = Files.walk(fileToDelete.toPath()) + .sorted(Comparator.reverseOrder()) + .map(x -> { + try { + Files.delete(x); + return null; + } + catch (IOException e) { + return e; + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + CompoundRuntimeException.throwIfNotEmpty(errors); + }); } runAll diff --git a/platform/testFramework/src/com/intellij/testFramework/utils/inlays/InlayParameterHintsTest.kt b/platform/testFramework/src/com/intellij/testFramework/utils/inlays/InlayParameterHintsTest.kt index 5ec9f101b54a..b1d45946c5c9 100644 --- a/platform/testFramework/src/com/intellij/testFramework/utils/inlays/InlayParameterHintsTest.kt +++ b/platform/testFramework/src/com/intellij/testFramework/utils/inlays/InlayParameterHintsTest.kt @@ -16,7 +16,6 @@ package com.intellij.testFramework.utils.inlays import com.intellij.codeInsight.daemon.impl.ParameterHintsPresentationManager -import com.intellij.codeInsight.hints.InlayInfo import com.intellij.codeInsight.hints.settings.ParameterNameHintsSettings import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.editor.Document @@ -37,7 +36,7 @@ class InlayHintsChecker(private val myFixture: CodeInsightTestFixture) { private var isParamHintsEnabledBefore = false companion object { - val pattern: Pattern = Pattern.compile("()|()|()|") + val pattern: Pattern = Pattern.compile("()|()|()|<(hint|HINT)\\s+text=\"([^\"\n\r]+)\"\\s*/>") private val default = ParameterNameHintsSettings() } @@ -67,13 +66,13 @@ class InlayHintsChecker(private val myFixture: CodeInsightTestFixture) { fun verifyInlaysAndCaretInfo(expectedInlaysAndCaret: CaretAndInlaysInfo, originalText: String) { val file = myFixture.file val document = myFixture.getDocument(file) - val actual: List> = getActualInlays() + val actual: List> = getActualInlays() - val expected = expectedInlaysAndCaret.inlays.map { Pair(it.offset, it.text) } + val expected = expectedInlaysAndCaret.inlays if (expectedInlaysAndCaret.inlays.size != actual.size || actual.zip(expected).any { it.first != it.second }) { val proposedText = StringBuilder(document.text) - actual.asReversed().forEach { proposedText.insert(it.first, "") } + actual.asReversed().forEach { proposedText.insert(it.first, "<${if(it.third) "HINT" else "hint"} text=\"${it.second}\" />") } VfsTestUtil.TEST_DATA_FILE_PATH.get(file.virtualFile)?.let { originalPath -> throw FileComparisonFailure("Hints differ", originalText, proposedText.toString(), originalPath) @@ -94,14 +93,14 @@ class InlayHintsChecker(private val myFixture: CodeInsightTestFixture) { } } - private fun getActualInlays(): List> { + private fun getActualInlays(): List> { val editor = myFixture.editor val allInlays = editor.inlayModel.getInlineElementsInRange(0, editor.document.textLength) val hintManager = ParameterHintsPresentationManager.getInstance() return allInlays .filter { hintManager.isParameterHint(it) } - .map { it.offset to hintManager.getHintText(it)} + .map { Triple(it.offset, hintManager.getHintText(it), hintManager.isHighlighted(it))} .sortedBy { it.first } } @@ -109,7 +108,7 @@ class InlayHintsChecker(private val myFixture: CodeInsightTestFixture) { val text = document.text val matcher = pattern.matcher(text) - val inlays = mutableListOf() + val inlays = mutableListOf>() var extractedLength = 0 var caretOffset : Int? = null var inlaysBeforeCaret = 0 @@ -124,7 +123,7 @@ class InlayHintsChecker(private val myFixture: CodeInsightTestFixture) { if (matcher.group(1) != null) { caretOffset = realStartOffset inlays.asReversed() - .takeWhile { it.offset == caretOffset } + .takeWhile { it.first == caretOffset } .forEach { inlaysBeforeCaret++ } } else if (matcher.group(2) != null) { @@ -134,7 +133,7 @@ class InlayHintsChecker(private val myFixture: CodeInsightTestFixture) { selectionEnd = realStartOffset } else { - inlays += InlayInfo(matcher.group(4), realStartOffset) + inlays += Triple(realStartOffset, matcher.group(5), matcher.group(4) == "HINT") } removeText(document, realStartOffset, matchedLength) @@ -155,4 +154,5 @@ class InlayHintsChecker(private val myFixture: CodeInsightTestFixture) { } -class CaretAndInlaysInfo (val caretOffset: Int?, val inlaysBeforeCaret: Int, val selection: TextRange?, val inlays: List) \ No newline at end of file +class CaretAndInlaysInfo (val caretOffset: Int?, val inlaysBeforeCaret: Int, val selection: TextRange?, + val inlays: List>) \ No newline at end of file diff --git a/platform/testFramework/testFramework.iml b/platform/testFramework/testFramework.iml index ff60a4178fdf..0a76638cb42a 100644 --- a/platform/testFramework/testFramework.iml +++ b/platform/testFramework/testFramework.iml @@ -26,6 +26,6 @@ - + \ No newline at end of file diff --git a/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/ActionOnRange.java b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/ActionOnRange.java index 73c6b6801b45..e3291beab063 100644 --- a/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/ActionOnRange.java +++ b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/ActionOnRange.java @@ -18,6 +18,7 @@ package com.intellij.testFramework.propertyBased; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.ProperTextRange; import com.intellij.openapi.util.Segment; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.vfs.VirtualFile; @@ -34,10 +35,16 @@ import org.jetbrains.annotations.Nullable; abstract class ActionOnRange implements MadTestingAction { private final SmartPsiFileRange myMarker; private TextRange myFinalRange; + protected final int myInitialStart; + protected final int myInitialEnd; ActionOnRange(PsiFile file, int start, int end) { - myMarker = SmartPointerManager.getInstance(file.getProject()).createSmartPsiFileRangePointer(file, new TextRange(start, end)); - assert file.getTextLength() == getDocument().getTextLength() : file + " " + getDocument(); + myInitialStart = start; + myInitialEnd = end; + myMarker = SmartPointerManager.getInstance(file.getProject()).createSmartPsiFileRangePointer(file, new ProperTextRange(start, end)); + int length = file.getTextLength(); + assert length == getDocument().getTextLength() : file + " " + getDocument(); + assert end <= length : end + " >= " + length; } @NotNull @@ -60,13 +67,20 @@ abstract class ActionOnRange implements MadTestingAction { return FileDocumentManager.getInstance().getDocument(getVirtualFile()); } - int getStartOffset() { - TextRange range = getFinalRange(); - return range == null ? -1 : range.getStartOffset(); + Segment getCurrentRange() { + return myFinalRange != null ? myFinalRange : myMarker.getRange(); + } + + int getCurrentStartOffset() { + return getStartOffset(getCurrentRange()); } - Segment getCurrentRange() { - return myFinalRange == null ? myMarker.getRange() : myFinalRange; + int getFinalStartOffset() { + return getStartOffset(getFinalRange()); + } + + private static int getStartOffset(Segment range) { + return range == null ? -1 : range.getStartOffset(); } @Nullable diff --git a/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/CheckHighlighterConsistency.java b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/CheckHighlighterConsistency.java new file mode 100644 index 000000000000..3a0190dd18d2 --- /dev/null +++ b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/CheckHighlighterConsistency.java @@ -0,0 +1,93 @@ +/* + * Copyright 2000-2017 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.testFramework.propertyBased; + +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.editor.ex.util.LexerEditorHighlighter; +import com.intellij.openapi.editor.highlighter.HighlighterIterator; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.psi.PsiFile; +import com.intellij.testFramework.LexerTestCase; +import org.jetbrains.annotations.NotNull; +import org.junit.Assert; +import jetCheck.Generator; + +/** + * Checks that incrementally updated editor highlighter produces the same result as it would + * after full text lexing. This makes sense to test if your language has: + *

  • + *
      Complex highlighting lexer, e.g. with some additional non-jflex state inside
    + *
      Complex highlighter. e.g. {@link com.intellij.openapi.editor.ex.util.LayeredLexerEditorHighlighter}
    + *
      Language depending on file content
    + *
  • + * + * @author peter + */ +public class CheckHighlighterConsistency implements MadTestingAction { + private final PsiFile myFile; + + public CheckHighlighterConsistency(PsiFile file) { + myFile = file; + } + + @Override + public void performAction() { + Editor editor = FileEditorManager.getInstance(myFile.getProject()).getSelectedTextEditor(); + assert editor.getDocument() == myFile.getViewProvider().getDocument(); + + performCheck(editor); + } + + public static void performCheck(@NotNull Editor editor) { + LexerEditorHighlighter highlighter = (LexerEditorHighlighter)((EditorEx)editor).getHighlighter(); + CharSequence text = editor.getDocument().getImmutableCharSequence(); + String incremental = dumpHighlighterTokens(highlighter, text); + + highlighter.setText(""); + highlighter.setText(text); + String full = dumpHighlighterTokens(highlighter, text); + + if (!full.equals(incremental)) { + Assert.assertEquals("Full lexer highlighter:\n" + full, "Incremental lexer highlighter:\n" + incremental); + } + } + + @NotNull + private static String dumpHighlighterTokens(LexerEditorHighlighter highlighter, CharSequence text) { + StringBuilder tokens = new StringBuilder(); + HighlighterIterator iterator = highlighter.createIterator(0); + while (!iterator.atEnd()) { + tokens.append(iterator.getStart()).append(" ") + .append(LexerTestCase.printSingleToken(text, iterator.getTokenType(), iterator.getStart(), iterator.getEnd())); + iterator.advance(); + } + return tokens.toString(); + } + + public static boolean runActionsInEditor(FileWithActions actions) { + FileEditorManager.getInstance(actions.getPsiFile().getProject()).openFile(actions.getPsiFile().getVirtualFile(), true); + return actions.runActions(); + } + + @NotNull + public static Generator randomEditsWithHighlighterChecks(PsiFile file) { + return Generator.anyOf(Generator.constant(new CheckHighlighterConsistency(file)), + InsertString.asciiInsertions(file), + DeleteRange.psiRangeDeletions(file)); + } + +} diff --git a/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/CompletionPolicy.java b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/CompletionPolicy.java index 1a3f4e49c7a1..6d62dbd52f06 100644 --- a/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/CompletionPolicy.java +++ b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/CompletionPolicy.java @@ -28,21 +28,22 @@ import org.jetbrains.annotations.Nullable; public class CompletionPolicy { /** - * @return the lookup string of an element that should be suggested in the given position + * @return string consisting of characters that can be used to insert the chosen lookup item */ - public String getExpectedVariant(Editor editor, PsiFile file) { - PsiElement leaf = file.findElementAt(editor.getCaretModel().getOffset()); - if (leaf == null) { - return null; - } - PsiReference ref = file.findReferenceAt(editor.getCaretModel().getOffset()); - return getExpectedVariant(editor, file, leaf, ref); - + public String getPossibleSelectionCharacters() { + return "\n\t\r .("; } + /** + * @return the lookup string of an element that should be suggested in the given position + */ @Nullable protected String getExpectedVariant(@NotNull Editor editor, @NotNull PsiFile file, @NotNull PsiElement leaf, @Nullable PsiReference ref) { - String leafText = leaf.getText(); + if (MadTestingUtil.isAfterError(file, leaf.getTextRange().getStartOffset())) { + return null; + } + + String leafText = ref != null ? ref.getRangeInElement().substring(ref.getElement().getText()) : leaf.getText(); if (leafText.isEmpty() || !Character.isLetter(leafText.charAt(0)) || leaf instanceof PsiWhiteSpace || @@ -53,8 +54,8 @@ public class CompletionPolicy { if (isDeclarationName(editor, file, leaf)) return null; if (ref != null) { - PsiElement refTarget = ref.resolve(); - if (refTarget == null || !shouldSuggestReferenceText(ref)) return null; + PsiElement target = getValidResolveResult(ref); + if (target == null || !shouldSuggestReferenceText(ref, target)) return null; } else { if (!SyntaxTraverser.psiTraverser(file).filter(PsiErrorElement.class).isEmpty()) { @@ -65,6 +66,21 @@ public class CompletionPolicy { return leafText; } + public boolean shouldCheckDuplicates(@NotNull Editor editor, @NotNull PsiFile file, @Nullable PsiElement leaf) { + return true; + } + + private static PsiElement getValidResolveResult(@NotNull PsiReference ref) { + if (ref instanceof PsiPolyVariantReference) { + for (ResolveResult result : ((PsiPolyVariantReference)ref).multiResolve(false)) { + if (!result.isValidResult()) { + return null; + } + } + } + return ref.resolve(); + } + private static boolean isDeclarationName(Editor editor, PsiFile file, PsiElement leaf) { PsiElement target = TargetElementUtil.findTargetElement(editor, TargetElementUtil.ELEMENT_NAME_ACCEPTED | TargetElementUtil.REFERENCED_ELEMENT_ACCEPTED); if (target != null) target = target.getNavigationElement(); @@ -77,7 +93,7 @@ public class CompletionPolicy { return true; } - protected boolean shouldSuggestReferenceText(@NotNull PsiReference ref) { + protected boolean shouldSuggestReferenceText(@NotNull PsiReference ref, @NotNull PsiElement target) { return true; } } diff --git a/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/DeleteRange.java b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/DeleteRange.java index 05aeabb7ca53..450b7f5239ac 100644 --- a/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/DeleteRange.java +++ b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/DeleteRange.java @@ -21,8 +21,8 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; -import slowCheck.Generator; -import slowCheck.IntDistribution; +import jetCheck.Generator; +import jetCheck.IntDistribution; import java.util.Objects; @@ -48,12 +48,12 @@ public class DeleteRange extends ActionOnRange { return new DeleteRange(psiFile, commonParent.getTextRange().getStartOffset(), commonParent.getTextRange().getEndOffset()); - }).suchThat(Objects::nonNull); + }).suchThat(Objects::nonNull).noShrink(); } @Override public String toString() { - return "DeleteRange: " + getVirtualFile().getPath() + " " + getCurrentRange(); + return "DeleteRange{" + getVirtualFile().getPath() + " " + getCurrentRange() + ", raw=(" + myInitialStart + ", " + myInitialEnd + ")}"; } public void performAction() { diff --git a/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/FileWithActions.java b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/FileWithActions.java index b1dab625fb82..28a5496ccf05 100644 --- a/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/FileWithActions.java +++ b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/FileWithActions.java @@ -35,7 +35,7 @@ public class FileWithActions { myActions = actions; } - PsiFile getPsiFile() { + public PsiFile getPsiFile() { return myFile; } diff --git a/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/InsertString.java b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/InsertString.java index d98e9d1c0216..fee949ee4948 100644 --- a/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/InsertString.java +++ b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/InsertString.java @@ -18,7 +18,7 @@ package com.intellij.testFramework.propertyBased; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.psi.PsiFile; import org.jetbrains.annotations.NotNull; -import slowCheck.Generator; +import jetCheck.Generator; public class InsertString extends ActionOnRange { private final String myToInsert; @@ -29,18 +29,18 @@ public class InsertString extends ActionOnRange { } public static Generator asciiInsertions(@NotNull PsiFile psiFile) { - return Generator.zipWith(Generator.integers(0, psiFile.getTextLength()), + return Generator.zipWith(Generator.integers(0, psiFile.getTextLength()).noShrink(), Generator.stringsOf(Generator.asciiPrintableChars()), (offset, toInsert) -> new InsertString(psiFile, offset, toInsert)); } @Override public String toString() { - return "InsertString: " + getVirtualFile().getPath() + " " + getStartOffset() + " '" + myToInsert + "'"; + return "InsertString{" + getVirtualFile().getPath() + " " + getCurrentStartOffset() + " '" + myToInsert + "', raw=" + myInitialStart + "}"; } public void performAction() { - int offset = getStartOffset(); + int offset = getFinalStartOffset(); if (offset < 0) return; WriteCommandAction.runWriteCommandAction(getProject(), () -> getDocument().insertString(offset, myToInsert)); diff --git a/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/IntentionPolicy.java b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/IntentionPolicy.java index 1db70756b1c9..adc7d4d82a63 100644 --- a/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/IntentionPolicy.java +++ b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/IntentionPolicy.java @@ -27,6 +27,6 @@ public class IntentionPolicy { } protected boolean shouldSkipIntention(@NotNull String actionText) { - return false; + return actionText.startsWith("Typo: Change to..."); // doesn't change file text (starts live template); } } diff --git a/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/InvokeCompletion.java b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/InvokeCompletion.java index ea740d4d70d3..ec81292a6d93 100644 --- a/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/InvokeCompletion.java +++ b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/InvokeCompletion.java @@ -15,6 +15,7 @@ */ package com.intellij.testFramework.propertyBased; +import com.intellij.codeInsight.TargetElementUtil; import com.intellij.codeInsight.completion.CodeCompletionHandlerBase; import com.intellij.codeInsight.completion.CompletionType; import com.intellij.codeInsight.lookup.LookupElement; @@ -32,14 +33,17 @@ import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiReference; import com.intellij.testFramework.PsiTestUtil; +import com.intellij.testFramework.fixtures.TestLookupElementPresentation; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.UIUtil; +import jetCheck.Generator; +import jetCheck.IntDistribution; import junit.framework.TestCase; import org.jetbrains.annotations.NotNull; -import slowCheck.Generator; -import slowCheck.IntDistribution; import java.util.Arrays; import java.util.HashSet; @@ -51,23 +55,20 @@ import java.util.Set; */ public class InvokeCompletion extends ActionOnRange { private final int myItemIndexRaw; - private LookupElement mySelectedItem; private final char myCompletionChar; private final CompletionPolicy myPolicy; - private final String myConstructorArgs; + private String myLog = "not invoked"; InvokeCompletion(PsiFile file, int offset, int itemIndexRaw, char completionChar, CompletionPolicy policy) { super(file, offset, offset); - this.myItemIndexRaw = itemIndexRaw; - this.myCompletionChar = completionChar; + myItemIndexRaw = itemIndexRaw; + myCompletionChar = completionChar; myPolicy = policy; - myConstructorArgs = "_, " + offset + ", " + itemIndexRaw + ", '" + StringUtil.escapeStringCharacters(String.valueOf(completionChar)) + "', _"; } @Override public String toString() { - return "InvokeCompletion(" + myConstructorArgs + ")" + - "{" + getVirtualFile().getPath() + ", offset=" + getStartOffset() + ", selected=" + mySelectedItem + '}'; + return "InvokeCompletion{" + getVirtualFile().getPath() + ", " + myLog + ", raw=" + myInitialStart + "," + myItemIndexRaw + "}"; } @Override @@ -78,8 +79,10 @@ public class InvokeCompletion extends ActionOnRange { assert editor != null; PsiDocumentManager.getInstance(project).commitAllDocuments(); - int offset = getStartOffset(); + int offset = getFinalStartOffset(); if (offset < 0) return; + + myLog = "offset=" + offset; editor.getCaretModel().moveToOffset(offset); @@ -100,36 +103,69 @@ public class InvokeCompletion extends ActionOnRange { } private void performCompletion(Editor editor) { - String expectedVariant = myPolicy.getExpectedVariant(editor, getFile()); + int caretOffset = editor.getCaretModel().getOffset(); + int adjustedOffset = TargetElementUtil.adjustOffset(getFile(), getDocument(), caretOffset); + + PsiElement leaf = getFile().findElementAt(adjustedOffset); + PsiReference ref = getFile().findReferenceAt(adjustedOffset); + + String expectedVariant = leaf == null ? null : myPolicy.getExpectedVariant(editor, getFile(), leaf, ref); + boolean prefixEqualsExpected = isPrefixEqualToExpectedVariant(caretOffset, leaf, ref, expectedVariant); + boolean shouldCheckDuplicates = myPolicy.shouldCheckDuplicates(editor, getFile(), leaf); + long stampBefore = getDocument().getModificationStamp(); new CodeCompletionHandlerBase(CompletionType.BASIC).invokeCompletion(getProject(), editor); + + String notFound = ". Please either fix completion so that the variant is suggested, " + + "or, if absolutely needed, tweak CompletionPolicy to exclude it."; LookupEx lookup = LookupManager.getActiveLookup(editor); if (lookup == null) { - if (expectedVariant == null) return; - TestCase.fail("No lookup, but expected " + expectedVariant + " among completion variants"); + if (editor.getCaretModel().getOffset() != caretOffset || getDocument().getModificationStamp() != stampBefore) { + myLog += ", auto-inserted"; + return; + } + myLog += ", no lookup"; + if (expectedVariant == null || prefixEqualsExpected) return; + TestCase.fail("No lookup, but expected '" + expectedVariant + "' among completion variants" + notFound); } List items = lookup.getItems(); if (expectedVariant != null) { LookupElement sameItem = ContainerUtil.find(items, e -> e.getAllLookupStrings().contains(expectedVariant)); - TestCase.assertNotNull("No variant " + expectedVariant + " among " + items, sameItem); + TestCase.assertNotNull("No variant '" + expectedVariant + "' among " + items + notFound, sameItem); } - checkNoDuplicates(items); + if (shouldCheckDuplicates) { + checkNoDuplicates(items); + } LookupElement item = items.get(myItemIndexRaw % items.size()); - mySelectedItem = item; + myLog += ", selected '" + item + "' with '" + StringUtil.escapeStringCharacters(String.valueOf(myCompletionChar)) + "'"; ((LookupImpl)lookup).finishLookup(myCompletionChar, item); } + private boolean isPrefixEqualToExpectedVariant(int caretOffset, PsiElement leaf, PsiReference ref, String expectedVariant) { + if (expectedVariant == null) return false; + + int expectedEnd = ref != null ? ref.getRangeInElement().getEndOffset() + ref.getElement().getTextRange().getStartOffset() : + leaf != null ? leaf.getTextRange().getEndOffset() : + 0; + return expectedEnd == caretOffset && getFile().getText().substring(0, caretOffset).endsWith(expectedVariant); + } + private static void checkNoDuplicates(List items) { Set> presentations = new HashSet<>(); for (LookupElement item : items) { - LookupElementPresentation p = LookupElementPresentation.renderElement(item); - List info = Arrays.asList(p.getItemText(), p.getItemTextForeground(), p.isItemTextBold(), p.isItemTextUnderlined(), + LookupElementPresentation p = TestLookupElementPresentation.renderReal(item); + if (seemsTruncated(p.getItemText()) || seemsTruncated(p.getTailText()) || seemsTruncated(p.getTypeText())) { + continue; + } + + List info = Arrays.asList(TestLookupElementPresentation.unwrapIcon(p.getIcon()), + p.getItemText(), p.getItemTextForeground(), p.isItemTextBold(), p.isItemTextUnderlined(), p.getTailFragments(), - p.getTypeText(), p.getTypeIcon(), p.isTypeGrayed(), + p.getTypeText(), TestLookupElementPresentation.unwrapIcon(p.getTypeIcon()), p.isTypeGrayed(), p.isStrikeout()); if (!presentations.add(info)) { TestCase.fail("Duplicate suggestions: " + p); @@ -137,6 +173,10 @@ public class InvokeCompletion extends ActionOnRange { } } + private static boolean seemsTruncated(String text) { + return text != null && text.contains("..."); + } + @NotNull public static Generator completions(PsiFile psiFile, CompletionPolicy policy) { return Generator.from(data -> { @@ -144,7 +184,8 @@ public class InvokeCompletion extends ActionOnRange { assert document != null; int offset = data.drawInt(IntDistribution.uniform(0, document.getTextLength())); int itemIndex = data.drawInt(IntDistribution.uniform(0, 100)); - char c = Generator.sampledFrom('\n', '\t', '\r', ' ', '.', '(').generateUnstructured(data); + String selectionCharacters = policy.getPossibleSelectionCharacters(); + char c = selectionCharacters.charAt(data.drawInt(IntDistribution.uniform(0, selectionCharacters.length() - 1))); return new InvokeCompletion(psiFile, offset, itemIndex, c, policy); }); } diff --git a/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/InvokeIntention.java b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/InvokeIntention.java index a75a11a59afe..5eae95e338e5 100644 --- a/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/InvokeIntention.java +++ b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/InvokeIntention.java @@ -18,6 +18,7 @@ package com.intellij.testFramework.propertyBased; import com.intellij.codeInsight.daemon.impl.HighlightInfo; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileEditorManager; @@ -32,21 +33,20 @@ import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import slowCheck.Generator; +import jetCheck.Generator; import java.util.List; public class InvokeIntention extends ActionOnRange { + private static final Logger LOG = Logger.getInstance("#com.intellij.testFramework.propertyBased.InvokeIntention"); private final int myIntentionIndex; private final IntentionPolicy myPolicy; - private final String myConstructorArgs; private String myInvocationLog = "not invoked"; InvokeIntention(PsiFile file, int offset, int intentionIndex, IntentionPolicy policy) { super(file, offset, offset); myIntentionIndex = intentionIndex; myPolicy = policy; - myConstructorArgs = "_ , " + offset + ", " + intentionIndex + ", _"; } @NotNull @@ -57,11 +57,11 @@ public class InvokeIntention extends ActionOnRange { @Override public String toString() { - return "InvokeIntention(" + myConstructorArgs + "){" + getVirtualFile().getPath() + "," + myInvocationLog + "}"; + return "InvokeIntention{" + getVirtualFile().getPath() + ", " + myInvocationLog + ", raw=(" + myInitialStart + "," + myIntentionIndex + ")}"; } public void performAction() { - int offset = getStartOffset(); + int offset = getFinalStartOffset(); myInvocationLog = "offset " + offset; if (offset < 0) return; @@ -73,28 +73,43 @@ public class InvokeIntention extends ActionOnRange { PsiFile file = PsiUtilBase.getPsiFileInEditor(editor, getProject()); IntentionAction intention = getRandomIntention(editor, file); - if (intention == null) return; - myInvocationLog += ", invoke '" + intention.getText() + "'"; + if (intention == null) { + myInvocationLog += ", no intentions found after highlighting"; + return; + } + myInvocationLog += ", invoked '" + intention.getText() + "'"; + String intentionString = intention.toString(); Document changedDocument = getDocumentToBeChanged(intention); String textBefore = changedDocument == null ? null : changedDocument.getText(); Runnable r = () -> CodeInsightTestFixtureImpl.invokeIntention(intention, file, editor, intention.getText()); - if (changedDocument != null) { - MadTestingUtil.restrictChangesToDocument(changedDocument, r); - } else { - r.run(); - } + try { + if (changedDocument != null) { + MadTestingUtil.restrictChangesToDocument(changedDocument, r); + } else { + r.run(); + } - if (changedDocument != null && - PsiDocumentManager.getInstance(project).isDocumentBlockedByPsi(changedDocument)) { - throw new AssertionError("Document is left blocked by PSI"); - } - if (!hasErrors && textBefore != null && textBefore.equals(changedDocument.getText())) { - throw new AssertionError("No change was performed in the document"); - } + if (changedDocument != null && + PsiDocumentManager.getInstance(project).isDocumentBlockedByPsi(changedDocument)) { + throw new AssertionError("Document is left blocked by PSI"); + } + if (!hasErrors && textBefore != null && textBefore.equals(changedDocument.getText())) { + String message = "No change was performed in the document"; + if (intention.startInWriteAction()) { + message += ".\nIf it's by design that " + intentionString + " doesn't change source files, " + + "it should return false from 'startInWriteAction'"; + } + throw new AssertionError(message); + } - PsiTestUtil.checkPsiStructureWithCommit(getFile(), PsiTestUtil::checkStubsMatchText); + PsiTestUtil.checkPsiStructureWithCommit(getFile(), PsiTestUtil::checkStubsMatchText); + } + catch (Throwable error) { + LOG.debug("Error occurred in " + this + ", text before:\n" + textBefore); + throw error; + } } @Nullable @@ -106,8 +121,15 @@ public class InvokeIntention extends ActionOnRange { @Nullable private IntentionAction getRandomIntention(Editor editor, PsiFile file) { - List actions = ContainerUtil.filter(CodeInsightTestFixtureImpl.getAvailableIntentions(editor, file), - myPolicy::mayInvokeIntention); - return actions.isEmpty() ? null : actions.get(myIntentionIndex % actions.size()); + List actions = ContainerUtil.filter(CodeInsightTestFixtureImpl.getAvailableIntentions(editor, file), myPolicy::mayInvokeIntention); + if (actions.isEmpty()) return null; + + // skip only after checking intentions for applicability, to catch possible exceptions from them + int offset = editor.getCaretModel().getOffset(); + if (MadTestingUtil.isAfterError(file, offset) || MadTestingUtil.isAfterError(file, offset - 1)) { + return null; + } + + return actions.get(myIntentionIndex % actions.size()); } } diff --git a/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/MadTestingAction.java b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/MadTestingAction.java index 678e15c6926a..04408c7f9fc7 100644 --- a/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/MadTestingAction.java +++ b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/MadTestingAction.java @@ -15,8 +15,6 @@ */ package com.intellij.testFramework.propertyBased; -import com.intellij.openapi.fileEditor.FileDocumentManager; - import java.util.List; /** @@ -31,8 +29,6 @@ public interface MadTestingAction { MadTestingAction action = list.get(i); if (i > 0 && action == list.get(i - 1)) continue; - FileDocumentManager.getInstance().saveAllDocuments(); - action.performAction(); } } diff --git a/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/MadTestingUtil.java b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/MadTestingUtil.java index bc2b7d7cd2a2..7597248b1158 100644 --- a/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/MadTestingUtil.java +++ b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/MadTestingUtil.java @@ -30,6 +30,7 @@ import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; import com.intellij.openapi.fileTypes.FileTypeManager; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VirtualFile; @@ -37,15 +38,16 @@ import com.intellij.profile.codeInspection.InspectionProjectProfileManager; import com.intellij.profile.codeInspection.ProjectInspectionProfileManager; import com.intellij.psi.*; import com.intellij.psi.impl.source.PostprocessReformattingAspect; +import com.intellij.psi.util.PsiTreeUtil; import com.intellij.testFramework.RunAll; import com.intellij.testFramework.UsefulTestCase; import com.intellij.testFramework.fixtures.CodeInsightTestFixture; import com.intellij.util.ThrowableRunnable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import slowCheck.DataStructure; -import slowCheck.Generator; -import slowCheck.IntDistribution; +import jetCheck.DataStructure; +import jetCheck.Generator; +import jetCheck.IntDistribution; import java.io.File; import java.io.FileFilter; @@ -72,7 +74,17 @@ public class MadTestingUtil { }); } - private static void watchDocumentChanges(ThrowableRunnable r, final Consumer eventHandler) throws E { + public static void prohibitDocumentChanges(Runnable r) { + watchDocumentChanges(r::run, event -> { + Document changed = event.getDocument(); + VirtualFile file = FileDocumentManager.getInstance().getFile(changed); + if (file != null && file.isInLocalFileSystem()) { + throw new AssertionError("Unexpected document change: " + changed); + } + }); + } + + private static void watchDocumentChanges(ThrowableRunnable r, Consumer eventHandler) throws E { Disposable disposable = Disposer.newDisposable(); EditorFactory.getInstance().getEventMulticaster().addDocumentListener(new DocumentListener() { @Override @@ -153,7 +165,7 @@ public class MadTestingUtil { } /** - * Finds files under {@code rootPath} (e.g. test data root) satisfying {@code fileFilter condition} (e.g. correct extension) and uses {@code actions} to generate actions those files (e.g. invoke compleiton/intentions or random editing). + * Finds files under {@code rootPath} (e.g. test data root) satisfying {@code fileFilter condition} (e.g. correct extension) and uses {@code actions} to generate actions those files (e.g. invoke completion/intentions or random editing). * Almost: the files with same paths and contents are created inside the test project, then the actions are executed on them. * Note that the test project contains only one file at each moment, so it's best to test actions that don't require much environment. * @return @@ -180,10 +192,12 @@ public class MadTestingUtil { PsiDocumentManager.getInstance(fixture.getProject()).commitAllDocuments(); PsiFile file = PsiManager.getInstance(fixture.getProject()).findFile(vFile); if (file instanceof PsiBinaryFile || file instanceof PsiPlainTextFile) { - return Generator.constant(null); + // no operations, but the just created file needs to be deleted (in FileWithActions#runActions) + // todo a side-effect-free generator + return Generator.constant(new FileWithActions(file, Collections.emptyList())); } return Generator.nonEmptyLists(actions.apply(file)).map(a -> new FileWithActions(file, a)); - }).suchThat(Objects::nonNull); + }); } private static boolean shouldGoInsiderDir(@NotNull String name) { @@ -204,9 +218,8 @@ public class MadTestingUtil { @NotNull private static VirtualFile copyFileToProject(File ioFile, CodeInsightTestFixture fixture, String rootPath) { - //todo strip test data markup try { - String path = FileUtil.getRelativePath(rootPath, ioFile.getPath(), '/'); + String path = FileUtil.getRelativePath(FileUtil.toCanonicalPath(rootPath), ioFile.getPath(), '/'); assert path != null; VirtualFile existing = fixture.findFileInTempDir(path); if (existing != null) { @@ -220,6 +233,10 @@ public class MadTestingUtil { } } + /** + * Generates actions checking that incremental reparse produces the same PSI as full reparse. This check makes sense + * in languages employing {@link com.intellij.psi.tree.ILazyParseableElementTypeBase}. + */ @NotNull public static Generator randomEditsWithReparseChecks(PsiFile file) { return Generator.anyOf(DeleteRange.psiRangeDeletions(file), @@ -227,6 +244,18 @@ public class MadTestingUtil { InsertString.asciiInsertions(file)); } + static boolean isAfterError(PsiFile file, int offset) { + PsiElement leaf = file.findElementAt(offset); + Set errorOffsets = SyntaxTraverser.psiTraverser(file) + .filter(PsiErrorElement.class) + .map(PsiTreeUtil::nextVisibleLeaf) + .filter(Condition.NOT_NULL) + .map(e -> e.getTextRange().getStartOffset()) + .toSet(); + return !errorOffsets.isEmpty() && + SyntaxTraverser.psiApi().parents(leaf).find(e -> errorOffsets.contains(e.getTextRange().getStartOffset())) != null; + } + private static class FileGenerator implements Function { private final File myRoot; private final FileFilter myFilter; diff --git a/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/RehighlightAllEditors.java b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/RehighlightAllEditors.java index c7d9a6daece6..75df921c9c88 100644 --- a/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/RehighlightAllEditors.java +++ b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/RehighlightAllEditors.java @@ -17,10 +17,12 @@ package com.intellij.testFramework.propertyBased; import com.intellij.codeInsight.daemon.impl.HighlightInfo; 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.fileEditor.TextEditor; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.testFramework.PsiTestUtil; @@ -55,9 +57,13 @@ public class RehighlightAllEditors implements MadTestingAction { @NotNull static List highlightEditor(Editor editor, Project project) { + FileDocumentManager.getInstance().saveAllDocuments(); // to avoid async document changes on automatic save during highlighting PsiDocumentManager.getInstance(project).commitAllDocuments(); PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()); PsiTestUtil.checkStubsMatchText(file); - return CodeInsightTestFixtureImpl.instantiateAndRun(file, editor, new int[0], false); + Ref> infos = Ref.create(); + MadTestingUtil.prohibitDocumentChanges( + () -> infos.set(CodeInsightTestFixtureImpl.instantiateAndRun(file, editor, new int[0], false))); + return infos.get(); } } diff --git a/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/StripTestDataMarkup.java b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/StripTestDataMarkup.java new file mode 100644 index 000000000000..ae2b6e3f297d --- /dev/null +++ b/platform/testFramework/testSrc/com/intellij/testFramework/propertyBased/StripTestDataMarkup.java @@ -0,0 +1,51 @@ +/* + * Copyright 2000-2017 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.testFramework.propertyBased; + +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiFile; +import com.intellij.testFramework.ExpectedHighlightingData; +import com.intellij.testFramework.PsiTestUtil; + +/** + * @author peter + */ +public class StripTestDataMarkup implements MadTestingAction { + private final PsiFile myFile; + + public StripTestDataMarkup(PsiFile file) { + myFile = file; + } + + @Override + public void performAction() { + WriteCommandAction.runWriteCommandAction(myFile.getProject(), () -> { + Document document = myFile.getViewProvider().getDocument(); + new ExpectedHighlightingData(document, true, true, true, true, myFile).init(); + removeMarkup(document, ""); + removeMarkup(document, ""); + removeMarkup(document, ""); + removeMarkup(document, ""); + }); + PsiTestUtil.checkPsiStructureWithCommit(myFile, PsiTestUtil::checkStubsMatchText); + } + + private static void removeMarkup(Document document, String marker) { + document.setText(StringUtil.replace(document.getText(), marker, "")); + } +} diff --git a/platform/testGuiFramework/HowToUseFramework.md b/platform/testGuiFramework/HowToUseFramework.md index cf8bd4ce07a6..6c5a887b087f 100644 --- a/platform/testGuiFramework/HowToUseFramework.md +++ b/platform/testGuiFramework/HowToUseFramework.md @@ -49,7 +49,7 @@ To run GUI test from context run configuration just right click on editor with o to run test by clicking icon on the gutter of opened editor. All test are running with IntelliJ IDEA Community from source code. If you need to start with other IntelliJ-based IDE just add `@RunWithIde()` and specify IDE with [`IdeType`](src/com/intellij/testGuiFramework/launcher/ide/IdeType.kt) annotation before test class: ```kotlin -@RunWithIde(IdeType.WEBSTORM) +@RunWithIde(WebStormIde::class) class SomeWebStormTest : GuiTestCase() ``` @@ -57,7 +57,7 @@ JUnit framework starts GUI test. As all GUI tests are derived from from [`GuiTes JUnit uses custom runner: [`GuiTestLocalRunner`](src/com/intellij/testGuiFramework/framework/GuiTestLocalRunner.kt). 1. `GuiTestLocalRunner` starts [`JUnitServer`](src/com/intellij/testGuiFramework/remote/server/JUnitServer.kt) to send tests and receive test states. 2. `JUnitServer` open a port to accept connection from IDE. -3. `GuiTestLocalRunner` runs IDE, which is described in GUI test class annotation: `@RunWithIde(IdeType.IDEA_ULTIMATE)`. +3. `GuiTestLocalRunner` runs IDE, which is described in GUI test class annotation: `@RunWithIde(CommunityIde::class)`. 4. [`GuiTestLocalLauncher`](src/com/intellij/testGuiFramework/launcher/GuiTestLocalLauncher.kt) composes classpath from the main module of specified IDE, `testGuiFramework` module and test. Than starts IDE with additional arguments `guitest port={a port from JUnitServer}`. 5. IDE starts with [`JUnitClient`](src/com/intellij/testGuiFramework/remote/client/JUnitClient.kt) and connects to `JUnitServer`. `JUnitClient` @@ -189,7 +189,7 @@ import com.intellij.testGuiFramework.launcher.ide.IdeType * the code directly or remotely. All tests are running with IntelliJ IDEA Community Edition by default. To change IDE test class should be * annotated with @com.intellij.testGuiFramework.framework.RunWithIde annotation. Check com.intellij.testGuiFramework.launcher.ide.IdeType */ -@RunWithIde(IdeType.WEBSTORM) +@RunWithIde(WebStormIde::class) class SomeGuiTest: GuiTestCase() { @org.junit.Test // test function should be annotated with @Test annotation diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/cellReader/ExtendedCellReaders.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/cellReader/ExtendedCellReaders.kt new file mode 100644 index 000000000000..daa5460f3333 --- /dev/null +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/cellReader/ExtendedCellReaders.kt @@ -0,0 +1,136 @@ +/* + * Copyright 2000-2017 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.testGuiFramework.cellReader + +import com.intellij.testGuiFramework.framework.GuiTestUtil +import com.intellij.ui.SimpleColoredComponent +import com.intellij.ui.components.JBList +import org.fest.swing.cell.JComboBoxCellReader +import org.fest.swing.cell.JListCellReader +import org.fest.swing.cell.JTableCellReader +import org.fest.swing.cell.JTreeCellReader +import org.fest.swing.core.BasicRobot +import org.fest.swing.core.GenericTypeMatcher +import org.fest.swing.driver.BasicJComboBoxCellReader +import org.fest.swing.driver.BasicJListCellReader +import org.fest.swing.driver.BasicJTableCellReader +import org.fest.swing.driver.BasicJTreeCellReader +import org.fest.swing.edt.GuiActionRunner +import org.fest.swing.edt.GuiQuery +import org.fest.swing.exception.ComponentLookupException +import java.awt.Component +import java.awt.Container +import java.util.* +import javax.annotation.Nonnull +import javax.annotation.Nullable +import javax.swing.* + + +/** + * @author Sergey Karashevich + */ +class ExtendedJTreeCellReader : BasicJTreeCellReader(), JTreeCellReader { + + override fun valueAt(tree: JTree, modelValue: Any?): String? { + if (modelValue == null) return null + + val cellRendererComponent = tree.cellRenderer.getTreeCellRendererComponent(tree, modelValue, false, false, false, 0, false) + return getValueWithCellRenderer(cellRendererComponent) + } + +} + +class ExtendedJListCellReader : BasicJListCellReader(), JListCellReader { + + @Nullable + override fun valueAt(@Nonnull list: JList<*>, index: Int): String? { + + val element = list.model.getElementAt(index) + val cellRendererComponent = GuiTestUtil.getListCellRendererComponent(list, element, index) + return getValueWithCellRenderer(cellRendererComponent) + } +} + +class ExtendedJTableCellReader: BasicJTableCellReader(), JTableCellReader { + + override fun valueAt(table: JTable, row: Int, column: Int): String? { + val cellRendererComponent = table.prepareRenderer(table.getCellRenderer(row, column), row, column) + return super.valueAt(table, row, column) ?: getValueWithCellRenderer(cellRendererComponent) + } +} + +class ExtendedJComboboxCellReader : BasicJComboBoxCellReader(), JComboBoxCellReader { + + private val REFERENCE_JLIST = newJList() + + override fun valueAt(comboBox: JComboBox<*>, index: Int): String? { + val item: Any = comboBox.getItemAt(index) + val listCellRenderer: ListCellRenderer = comboBox.renderer as ListCellRenderer + val cellRendererComponent = listCellRenderer.getListCellRendererComponent(REFERENCE_JLIST, item, index, true, true) + return getValueWithCellRenderer(cellRendererComponent) + } + + @Nonnull + private fun newJList(): JList { + return GuiActionRunner.execute(object : GuiQuery>() { + override fun executeInEDT(): JList = JBList() + })!! + } +} + +private fun getValueWithCellRenderer(cellRendererComponent: Component): String? { + val result = when (cellRendererComponent) { + is JLabel -> cellRendererComponent.text + is SimpleColoredComponent -> cellRendererComponent.getText() + else -> cellRendererComponent.findText() + } + return result?.trimEnd() +} + + +private fun SimpleColoredComponent.getText(): String? + = this.iterator().asSequence().joinToString() + +private fun Component.findText(): String? { + try { + assert(this is Container) + val container = this as Container + val resultList = ArrayList() + resultList.addAll( + findAllWithRobot(container, JLabel::class.java) + .filter { !it.text.isNullOrEmpty() } + .map { it.text } + ) + resultList.addAll( + findAllWithRobot(container, SimpleColoredComponent::class.java) + .filter { !it.getText().isNullOrEmpty() } + .map { it.getText()!! } + ) + return resultList.filter { !it.isNullOrEmpty() }.firstOrNull() + } + catch (ignored: ComponentLookupException) { + return null + } +} + +private fun findAllWithRobot(container: Container, clazz: Class): List { + val robot = BasicRobot.robotWithCurrentAwtHierarchyWithoutScreenLock() + val result = robot.finder().findAll(container, object : GenericTypeMatcher(clazz) { + override fun isMatching(component: ComponentType) = true + }) + robot.cleanUpWithoutDisposingWindows() + return result.toList() +} diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/cellReader/ExtendedJListCellReader.java b/platform/testGuiFramework/src/com/intellij/testGuiFramework/cellReader/ExtendedJListCellReader.java deleted file mode 100644 index 497387092064..000000000000 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/cellReader/ExtendedJListCellReader.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2000-2016 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.testGuiFramework.cellReader; - -import com.intellij.testGuiFramework.framework.GuiTestUtil; -import org.fest.swing.cell.JListCellReader; -import org.fest.swing.core.BasicRobot; -import org.fest.swing.driver.BasicJListCellReader; -import org.fest.swing.exception.ComponentLookupException; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.swing.*; -import java.awt.*; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -/** - * @author Sergey Karashevich - */ -public class ExtendedJListCellReader extends BasicJListCellReader implements JListCellReader { - - public ExtendedJListCellReader() { - super(); - } - - @Nullable - @Override - public String valueAt(@Nonnull JList list, int index) { - - Object element = list.getModel().getElementAt(index); - Component cellRenComp = GuiTestUtil.getListCellRendererComponent(list, element, index); - if (cellRenComp instanceof JLabel) { - return ((JLabel)cellRenComp).getText(); - } - else if (cellRenComp instanceof JPanel) { - try { - Component foundComp = - BasicRobot.robotWithNewAwtHierarchyWithoutScreenLock().finder().find((JPanel)cellRenComp, component -> component instanceof JLabel); - return ((JLabel)foundComp).getText(); - } - catch (ComponentLookupException ignored) { - } - } - - Method getNameMethod = null; - try { - getNameMethod = element.getClass().getMethod("getName"); - Object result = getNameMethod.invoke(element); - assert (result instanceof String); - return (String)result; - } - catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - try { - getNameMethod = element.getClass().getMethod("getText"); - Object result = getNameMethod.invoke(element); - assert (result instanceof String); - return (String)result; - } - catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e1) { - return null; - } - } - } -} diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/cellReader/ExtendedJTreeCellReader.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/cellReader/ExtendedJTreeCellReader.kt deleted file mode 100644 index 309b160bd3fc..000000000000 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/cellReader/ExtendedJTreeCellReader.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2000-2017 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.testGuiFramework.cellReader - -import com.intellij.ui.SimpleColoredComponent -import org.fest.swing.cell.JTreeCellReader -import org.fest.swing.core.BasicRobot -import org.fest.swing.core.GenericTypeMatcher -import org.fest.swing.driver.BasicJTreeCellReader -import org.fest.swing.exception.ComponentLookupException -import java.awt.Component -import java.awt.Container -import java.util.* -import javax.swing.JLabel -import javax.swing.JTree - -/** - * @author Sergey Karashevich - */ -class ExtendedJTreeCellReader : BasicJTreeCellReader(), JTreeCellReader { - - override fun valueAt(tree: JTree, modelValue: Any?): String? { - if (modelValue == null) return null - - val cellRendererComponent = tree.cellRenderer.getTreeCellRendererComponent(tree, modelValue, false, false, false, 0, false) - when (cellRendererComponent) { - is JLabel -> return cellRendererComponent.text - is SimpleColoredComponent -> return cellRendererComponent.getText() - else -> return cellRendererComponent.findText() - } - } - - private fun SimpleColoredComponent.getText(): String? - = this.iterator().asSequence().joinToString() - - private fun Component.findText(): String? { - try { - assert(this is Container) - val container = this as Container - val resultList = ArrayList() - resultList.addAll( - findAllWithRobot(container, JLabel::class.java) - .filter { !it.text.isNullOrEmpty() } - .map { it.text } - ) - resultList.addAll( - findAllWithRobot(container, SimpleColoredComponent::class.java) - .filter { !it.getText().isNullOrEmpty() } - .map { it.getText()!! } - ) - return resultList.filter { !it.isNullOrEmpty() }.firstOrNull() - } - catch (ignored: ComponentLookupException) { - return null - } - } - - fun findAllWithRobot(container: Container, clazz: Class): List { - val robot = BasicRobot.robotWithCurrentAwtHierarchyWithoutScreenLock() - val result = robot.finder().findAll(container, object : GenericTypeMatcher(clazz) { - override fun isMatching(component: ComponentType) = true - }) - robot.cleanUpWithoutDisposingWindows() - return result.toList() - } - -} diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/driver/ExtendedJTreeDriver.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/driver/ExtendedJTreeDriver.kt index 53a955bd62e5..dd0c70e51ad1 100644 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/driver/ExtendedJTreeDriver.kt +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/driver/ExtendedJTreeDriver.kt @@ -63,7 +63,15 @@ class ExtendedJTreeDriver(robot: Robot) : JTreeDriver(robot) { robot.click(tree, point, button, times) } - fun checkPathExists(tree: JTree, pathStrings: List) { matchingPathFor(tree, pathStrings) } + //XPath contains order number of similar nodes: clickXPath("test(1)", "Java") - here (1) means the first node + fun clickXPath(tree: JTree, xPathStrings: List, button: MouseButton = MouseButton.LEFT_BUTTON, times: Int = 1) { + val point = scrollToXPath(tree, xPathStrings) + robot.click(tree, point, button, times) + } + + fun checkPathExists(tree: JTree, pathStrings: List) { + matchingPathFor(tree = tree, pathStrings = pathStrings, isUniquePath = false) + } fun nodeValue(tree: JTree, pathStrings: List): String? = nodeText(tree, pathStrings) @@ -96,6 +104,11 @@ class ExtendedJTreeDriver(robot: Robot) : JTreeDriver(robot) { return scrollToMatchingPath(tree, pathStrings).third!! } + fun scrollToXPath(tree: JTree, xPathStrings: List): Point { + robot.waitForIdle() + return scrollToMatchingXPath(tree, xPathStrings).third!! + } + fun showPopupMenu(tree: JTree, pathStrings: List): JPopupMenu { val info = scrollToMatchingPath(tree, pathStrings) robot.waitForIdle() @@ -114,9 +127,13 @@ class ExtendedJTreeDriver(robot: Robot) : JTreeDriver(robot) { ///OVERRIDDEN FUNCTIONS///// override fun clickPath(tree: JTree, path: String) = clickPath(tree, listOf(path)) + override fun clickPath(tree: JTree, path: String, button: MouseButton) = clickPath(tree, listOf(path), button) override fun clickPath(tree: JTree, path: String, mouseClickInfo: MouseClickInfo) = clickPath(tree, listOf(path), mouseClickInfo) - override fun checkPathExists(tree: JTree, path: String) { matchingPathFor(tree, listOf(path)) } + override fun checkPathExists(tree: JTree, path: String) { + matchingPathFor(tree, listOf(path)) + } + override fun nodeValue(tree: JTree, path: String): String? = nodeValue(tree, listOf(path)) override fun nodeValue(tree: JTree, row: Int): String? = nodeText(tree, row, location) override fun doubleClickPath(tree: JTree, path: String) = doubleClickPath(tree, listOf(path)) @@ -132,9 +149,13 @@ class ExtendedJTreeDriver(robot: Robot) : JTreeDriver(robot) { ///PRIVATE FUNCTIONS///// - private fun doubleClick(tree: JTree, p: Point) { robot.click(tree, p, MouseButton.LEFT_BUTTON, 2) } + private fun doubleClick(tree: JTree, p: Point) { + robot.click(tree, p, MouseButton.LEFT_BUTTON, 2) + } - private fun rightClick(@Nonnull tree: JTree, @Nonnull p: Point) { robot.click(tree, p, MouseButton.RIGHT_BUTTON, 1) } + private fun rightClick(@Nonnull tree: JTree, @Nonnull p: Point) { + robot.click(tree, p, MouseButton.RIGHT_BUTTON, 1) + } private fun scrollToMatchingPath(tree: JTree, pathStrings: List): Triple { val matchingPath = verifyJTreeIsReadyAndFindMatchingPath(tree, pathStrings) @@ -143,6 +164,13 @@ class ExtendedJTreeDriver(robot: Robot) : JTreeDriver(robot) { return Triple.of(matchingPath, info.first, info.second) } + private fun scrollToMatchingXPath(tree: JTree, xPathStrings: List): Triple { + val matchingPath = verifyJTreeIsReadyAndFindMatchingXPath(tree, xPathStrings) + makeVisible(tree, matchingPath, false) + val info = scrollToPathToSelect(tree, matchingPath, location) + return Triple.of(matchingPath, info.first, info.second) + } + private fun scrollToPathToSelect(@Nonnull tree: JTree, @Nonnull path: TreePath, @Nonnull location: JTreeLocation): Pair { return computeOnEdt { val isSelected = tree.selectionCount == 1 && tree.isPathSelected(path) @@ -158,12 +186,12 @@ class ExtendedJTreeDriver(robot: Robot) : JTreeDriver(robot) { private fun scrollToMatchingPathAndGetToggleInfo(tree: JTree, pathStrings: List): Triple { - return computeOnEdt{ - ComponentPreconditions.checkEnabledAndShowing(tree) - val matchingPath = matchingPathFor(tree, pathStrings) - val point = scrollToTreePath(tree, matchingPath, location) - Triple.of(tree.isExpanded(matchingPath), point, tree.toggleClickCount) - }!! + return computeOnEdt { + ComponentPreconditions.checkEnabledAndShowing(tree) + val matchingPath = matchingPathFor(tree, pathStrings) + val point = scrollToTreePath(tree, matchingPath, location) + Triple.of(tree.isExpanded(matchingPath), point, tree.toggleClickCount) + }!! } private fun makeVisible(tree: JTree, path: TreePath, expandWhenFound: Boolean): Boolean { @@ -187,7 +215,8 @@ class ExtendedJTreeDriver(robot: Robot) : JTreeDriver(robot) { try { pause(timeout) { childCount(tree, path) != 0 } - } catch (waitTimedOutError: WaitTimedOutError) { + } + catch (waitTimedOutError: WaitTimedOutError) { throw LocationUnavailableException(waitTimedOutError.message!!) } @@ -217,13 +246,14 @@ class ExtendedJTreeDriver(robot: Robot) : JTreeDriver(robot) { for (stringIndex in 0..pathElementCount - 1) { val pathString = pathStrings[stringIndex] if (stringIndex == 0 && tree.isRootVisible) { - if (pathString != value(tree, node)) throw pathNotFound(pathStrings) + if (pathString != value(tree, node)) throw pathNotFound(pathStrings) newPathValues.add(node) } else { try { node = traverseChildren(tree, node, pathString) ?: throw pathNotFound(pathStrings) - } catch(e: LoadingNodeException) { //if we met loading node let's tell it to caller and probably expand path to clarify this node + } + catch(e: LoadingNodeException) { //if we met loading node let's tell it to caller and probably expand path to clarify this node e.treePath = TreePath(newPathValues.toTypedArray()) throw e } @@ -252,6 +282,98 @@ class ExtendedJTreeDriver(robot: Robot) : JTreeDriver(robot) { return match } + + fun findMatchingPath(tree: JTree, pathStrings: List, isUniquePath: Boolean = true): TreePath { + if (isUniquePath) return findMatchingPath(tree, pathStrings) + + val model = tree.model + if (tree.isRootVisible) { + if (pathStrings[0] != value(tree, model.root)) throw pathNotFound(pathStrings) + if (pathStrings.size == 1) return TreePath(arrayOf(model.root)) + val result: TreePath = findMatchingPath(tree, model.root, pathStrings.subList(1, pathStrings.size)) ?: throw pathNotFound( + pathStrings) + return TreePath(arrayOf(model.root, *result.path)) + } + else { + return findMatchingPath(tree, model.root, pathStrings) ?: throw pathNotFound(pathStrings) + } + } + + /** + * this method tries to find any path. If tree contains multiple of searchable path it still accepts + */ + private fun findMatchingPath(tree: JTree, node: Any, pathStrings: List): TreePath? { + val model = tree.model + val childCount = model.getChildCount(node) + + for (childIndex in 0..childCount - 1) { + val child = model.getChild(node, childIndex) + if (child is LoadingNode) throw LoadingNodeException(child, null) + if (pathStrings.size == 1 && value(tree, child) == pathStrings[0]) { + return TreePath(arrayOf(child)) + } + else { + if (pathStrings[0] == value(tree, child)) { + val childResult = findMatchingPath(tree, child, pathStrings.subList(1, pathStrings.size)) + if (childResult != null) return TreePath(arrayOf(child, *childResult.path)) + } + } + } + return null + } + + fun findMatchingXPath(tree: JTree, xPathStrings: List): TreePath { + val model = tree.model + if (tree.isRootVisible) { + if (xPathStrings[0] != value(tree, model.root)) throw pathNotFound(xPathStrings) + if (xPathStrings.size == 1) return TreePath(arrayOf(model.root)) + + val result: TreePath = findMatchingXPath(tree, model.root, xPathStrings.subList(1, xPathStrings.size)) ?: throw pathNotFound( + xPathStrings) + return TreePath(arrayOf(model.root, *result.path)) + } + else { + return findMatchingXPath(tree, model.root, xPathStrings) ?: throw pathNotFound(xPathStrings) + } + } + + private fun findMatchingXPath(tree: JTree, node: Any, xPathStrings: List): TreePath? { + val model = tree.model + val childCount = model.getChildCount(node) + + val order = xPathStrings[0].getOrder() ?: 0 + val original = if (xPathStrings[0].hasOrder()) xPathStrings[0].subSequence(0, + xPathStrings[0].length - 2 - (order.toString().length)) + else xPathStrings[0] + var currentOrder = 0 + + for (childIndex in 0..childCount - 1) { + val child = model.getChild(node, childIndex) + if (original == value(tree, child)) { + if (currentOrder == order) { + if (xPathStrings.size == 1) { + return TreePath(arrayOf(child)) + } else { + val childResult = findMatchingXPath(tree, child, xPathStrings.subList(1, xPathStrings.size)) + if (childResult != null) return TreePath(arrayOf(child, *childResult.path)) + } + } else { + currentOrder++ + } + } + } + return null + } + + private fun String.hasOrder(): Boolean = + Regex("\\(\\d\\)").find(this)?.value?.isNotEmpty() ?: false + + + private fun String.getOrder(): Int? { + val find: MatchResult = Regex("\\(\\d\\)").find(this) ?: return null + return find.value.removeSurrounding("(", ")").toInt() + } + private fun pathNotFound(path: List): LocationUnavailableException { throw LocationUnavailableException("Unable to find path \"$path\"") } @@ -276,7 +398,7 @@ class ExtendedJTreeDriver(robot: Robot) : JTreeDriver(robot) { /** * node that has as child LoadingNode */ - class LoadingNodeException(val node: Any, var treePath: TreePath?): Exception("Meet loading node: $node") + class LoadingNodeException(val node: Any, var treePath: TreePath?) : Exception("Meet loading node: $node") private fun childCount(tree: JTree, path: TreePath): Int { @@ -296,7 +418,7 @@ class ExtendedJTreeDriver(robot: Robot) : JTreeDriver(robot) { private fun nodeText(tree: JTree, pathStrings: List): String? { return computeOnEdt { - val matchingPath = matchingPathWithRootIfInvisible(tree, pathStrings) + val matchingPath = matchingPathWithRootIfInvisible(tree, pathStrings, true) pathFinder.cellReader().valueAt(tree, matchingPath.lastPathComponent!!) } } @@ -305,7 +427,14 @@ class ExtendedJTreeDriver(robot: Robot) : JTreeDriver(robot) { private fun verifyJTreeIsReadyAndFindMatchingPath(tree: JTree, pathStrings: List): TreePath { return computeOnEdt { ComponentPreconditions.checkEnabledAndShowing(tree) - matchingPathWithRootIfInvisible(tree, pathStrings) + matchingPathWithRootIfInvisible(tree, pathStrings, true) + }!! + } + + private fun verifyJTreeIsReadyAndFindMatchingXPath(tree: JTree, xPathStrings: List): TreePath { + return computeOnEdt { + ComponentPreconditions.checkEnabledAndShowing(tree) + matchingXPathWithRootIfInvisible(tree, xPathStrings, false) }!! } @@ -313,20 +442,26 @@ class ExtendedJTreeDriver(robot: Robot) : JTreeDriver(robot) { /** * we are trying to find TreePath for a tree, if we met loading node (LoadingTreeNode) */ - private fun matchingPathFor(tree: JTree, pathStrings: List, countDownAttempts: Int = 30): TreePath { + private fun matchingPathFor(tree: JTree, pathStrings: List, countDownAttempts: Int = 30, isUniquePath: Boolean = true): TreePath { if (countDownAttempts == 0) throw Exception("Unable to find path($pathStrings) for tree: $tree, attempts count exceeded") try { return computeOnEdtWithTry { - matchingPathWithRootIfInvisible(tree, pathStrings) + matchingPathWithRootIfInvisible(tree, pathStrings, isUniquePath) }!! - } catch (e: LoadingNodeException) { + } + catch (e: LoadingNodeException) { if (e.treePath != null) expandTreePath(tree, e.treePath!!) - return matchingPathFor(tree, pathStrings, countDownAttempts - 1) + return matchingPathFor(tree, pathStrings, countDownAttempts - 1, isUniquePath) } } - private fun matchingPathWithRootIfInvisible(tree: JTree, pathStrings: List): TreePath { - val matchingPath = pathFinder.findMatchingPath(tree, pathStrings) + private fun matchingPathWithRootIfInvisible(tree: JTree, pathStrings: List, isUniquePath: Boolean): TreePath { + val matchingPath = pathFinder.findMatchingPath(tree, pathStrings, isUniquePath) + return addRootIfInvisible(tree, matchingPath) + } + + private fun matchingXPathWithRootIfInvisible(tree: JTree, xPathStrings: List, isUniquePath: Boolean): TreePath { + val matchingPath = pathFinder.findMatchingXPath(tree, xPathStrings) return addRootIfInvisible(tree, matchingPath) } @@ -396,7 +531,7 @@ class ExtendedJTreeDriver(robot: Robot) : JTreeDriver(robot) { } private fun pause(timeout: Long, condition: () -> Boolean) { - Pause.pause(object: Condition("ExtendedJTreeDriver wait condition:") { + Pause.pause(object : Condition("ExtendedJTreeDriver wait condition:") { override fun test() = condition() }) } diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/driver/HyperlinkLabelDriver.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/driver/HyperlinkLabelDriver.kt new file mode 100644 index 000000000000..4697e346886e --- /dev/null +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/driver/HyperlinkLabelDriver.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2000-2017 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.testGuiFramework.driver + +import org.fest.swing.core.Robot +import org.fest.swing.driver.ComponentDriver + +class HyperlinkLabelDriver(val robot: Robot): ComponentDriver(robot) \ No newline at end of file diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/fixtures/HyperlinkLabelFixture.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/fixtures/HyperlinkLabelFixture.kt new file mode 100644 index 000000000000..a7856667fb48 --- /dev/null +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/fixtures/HyperlinkLabelFixture.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2017 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.testGuiFramework.fixtures + +import com.intellij.ui.HyperlinkLabel +import org.fest.swing.core.Robot +import org.fest.swing.exception.ComponentLookupException +import java.awt.Point +import java.awt.Rectangle + + +class HyperlinkLabelFixture(robot: Robot, val hyperlinkLabel: HyperlinkLabel) : ComponentFixture( + HyperlinkLabelFixture::class.java, robot, + hyperlinkLabel) { + + fun clickLink(regionText: String) { + if (!hyperlinkLabel.hightlightedRegionsBoundsMap.containsKey(regionText)) throw ComponentLookupException( + "Unable to find highlighted region \"$regionText\" in HyperlinkLabel:\"${hyperlinkLabel.text}\"") + val rectangle = hyperlinkLabel.hightlightedRegionsBoundsMap[regionText] + driver().click(hyperlinkLabel, rectangle!!.center()) + } + + private fun Rectangle.center(): Point + = Point(this.x + this.width / 2, this.y + this.height / 2) + +} \ No newline at end of file diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/fixtures/ProjectViewFixture.java b/platform/testGuiFramework/src/com/intellij/testGuiFramework/fixtures/ProjectViewFixture.java index 29a2dc190f6b..e27277b9646f 100644 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/fixtures/ProjectViewFixture.java +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/fixtures/ProjectViewFixture.java @@ -65,6 +65,7 @@ import java.util.concurrent.atomic.AtomicReference; import static com.intellij.ide.projectView.BaseProjectTreeBuilder.getBuilderFor; import static com.intellij.testGuiFramework.framework.GuiTestUtil.SHORT_TIMEOUT; +import static com.intellij.testGuiFramework.framework.GuiTestUtil.THIRTY_SEC_TIMEOUT; import static org.fest.assertions.Assertions.assertThat; import static org.fest.reflect.core.Reflection.field; import static org.fest.swing.timing.Pause.pause; @@ -181,6 +182,30 @@ public class ProjectViewFixture extends ToolWindowFixture { */ @NotNull public NodeFixture path(String... pathTo) { + Ref nodeFixtureRef = new Ref<>(); + try { + pause(new Condition("Waiting for a node by path: " + Arrays.toString(pathTo)) { + @Override + public boolean test() { + try { + nodeFixtureRef.set(getNodeFixtureByPath(pathTo)); + return true; + } + catch (ComponentLookupException e) { + return false; + } + } + }, THIRTY_SEC_TIMEOUT); + } catch (WaitTimedOutError timedOutError) { + AbstractTreeStructure treeStructure = selectProjectPane().getTreeStructure(); + StringBuilder projectViewStructure = PlatformTestUtil.print(treeStructure, treeStructure.getRootElement(), 10, null, 100, ' ', null); + LOG.error("Unable to find path: " + Arrays.toString(pathTo) + " for current project structure.\nActual project structure" + projectViewStructure, timedOutError); + } + return nodeFixtureRef.get(); + } + + @NotNull + private NodeFixture getNodeFixtureByPath(String[] pathTo) { if (pathTo.length == 1) { if (pathTo[0].contains("/")) { String[] newPath = pathTo[0].split("/"); @@ -191,7 +216,7 @@ public class ProjectViewFixture extends ToolWindowFixture { } public boolean containsPath(String... pathTo) { - return path(pathTo) == null; + return getNodeFixtureByPath(pathTo) != null; } public class PaneFixture { diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/fixtures/extended/ExtendedTableFixture.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/fixtures/extended/ExtendedTableFixture.kt new file mode 100644 index 000000000000..d21e7dbcc6ef --- /dev/null +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/fixtures/extended/ExtendedTableFixture.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2000-2017 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.testGuiFramework.fixtures.extended + +import com.intellij.testGuiFramework.cellReader.ExtendedJTableCellReader +import org.fest.swing.core.Robot +import org.fest.swing.fixture.JTableFixture +import javax.swing.JCheckBox +import javax.swing.JTable + +class ExtendedTableFixture(val myRobot: Robot, val myTable: JTable) : JTableFixture(myRobot, myTable) { + + init { + replaceCellReader(ExtendedJTableCellReader()) + } + + fun row(i: Int): RowFixture = RowFixture(i, this) + fun row(value: String): RowFixture = RowFixture(cell(value).row(), this) + + +} + +class RowFixture(val rowNumber: Int, val tableFixture: ExtendedTableFixture) { + + val myTable = tableFixture.myTable + + fun hasCheck(): Boolean = + (0..myTable.columnCount - 1) + .map { myTable.prepareRenderer(myTable.getCellRenderer(rowNumber, it), rowNumber, it) } + .any { it is JCheckBox } + + + fun isCheck(): Boolean { + val checkBox = getCheckBox() + return checkBox.isSelected + + } + + fun check() { + val checkBox = getCheckBox() + return checkBox.model.setSelected(true) + } + + fun uncheck() { + val checkBox = getCheckBox() + return checkBox.model.setSelected(false) + } + + fun values(): List { + val cellReader = ExtendedJTableCellReader() + return (0..myTable.columnCount - 1) + .map { cellReader.valueAt(myTable, rowNumber, it) ?: "null" } + } + + private fun getCheckBox(): JCheckBox { + if (!hasCheck()) throw Exception("Unable to find checkbox cell in row: $rowNumber") + return (0..myTable.columnCount - 1) + .map { myTable.prepareRenderer(myTable.getCellRenderer(rowNumber, it), rowNumber, it) } + .find { it is JCheckBox } as JCheckBox + } + +} \ No newline at end of file diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/fixtures/extended/ExtendedTreeFixture.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/fixtures/extended/ExtendedTreeFixture.kt index c54f3cef2f93..bb0382cc3477 100644 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/fixtures/extended/ExtendedTreeFixture.kt +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/fixtures/extended/ExtendedTreeFixture.kt @@ -58,6 +58,13 @@ class ExtendedTreeFixture(val robot: Robot, val tree: JTree) : JTreeFixture(robo fun clickPath(pathStrings: List, button: MouseButton = MouseButton.LEFT_BUTTON, times: Int = 1) = myDriver.clickPath(tree, pathStrings, button, times) + fun clickXPath(vararg xPathStrings: String) + = myDriver.clickXPath(tree, xPathStrings.toList()) + + fun clickXPath(xPathStrings: List) { + myDriver.clickXPath(tree, xPathStrings, MouseButton.LEFT_BUTTON, 1) + } + fun clickPath(vararg pathStrings: String, button: MouseButton = MouseButton.LEFT_BUTTON, times: Int = 1) = myDriver.clickPath(tree, pathStrings.toList(), button, times) diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/framework/FirstStartWith.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/framework/FirstStartWith.kt new file mode 100644 index 000000000000..15bea9785447 --- /dev/null +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/framework/FirstStartWith.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2000-2017 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.testGuiFramework.framework + +import com.intellij.testGuiFramework.impl.FirstStart +import kotlin.reflect.KClass + +@kotlin.annotation.Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE) +annotation class FirstStartWith(val value: KClass) diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/framework/GuiTestLocalRunner.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/framework/GuiTestLocalRunner.kt index 88cd88c0710c..1256de53acaa 100755 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/framework/GuiTestLocalRunner.kt +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/framework/GuiTestLocalRunner.kt @@ -19,6 +19,7 @@ import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.util.Ref import com.intellij.testGuiFramework.impl.GuiTestStarter import com.intellij.testGuiFramework.launcher.GuiTestLocalLauncher.runIdeLocally +import com.intellij.testGuiFramework.launcher.ide.CommunityIde import com.intellij.testGuiFramework.launcher.ide.Ide import com.intellij.testGuiFramework.launcher.ide.IdeType import com.intellij.testGuiFramework.remote.server.JUnitServerHolder @@ -33,6 +34,7 @@ import org.junit.runner.notification.RunNotifier import org.junit.runners.BlockJUnit4ClassRunner import org.junit.runners.model.FrameworkMethod import org.junit.runners.model.InitializationError +import kotlin.reflect.KClass class GuiTestLocalRunner @Throws(InitializationError::class) @@ -140,7 +142,8 @@ constructor(testClass: Class<*>) : BlockJUnit4ClassRunner(testClass) { fun getIdeFromAnnotation(clazz: Class<*>): Ide { val annotation = clazz.getAnnotation(RunWithIde::class.java) - val ideType = annotation?.value ?: IdeType.IDEA_COMMUNITY //ide community by default + val value = annotation?.value + val ideType = if(value != null) (value as KClass).java.newInstance() else CommunityIde() return Ide(ideType, 0, 0) } } diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/framework/GuiTestSuite.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/framework/GuiTestSuite.kt index 62492f4eb095..614507555e8b 100644 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/framework/GuiTestSuite.kt +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/framework/GuiTestSuite.kt @@ -22,33 +22,42 @@ import org.junit.runner.notification.RunNotifier import org.junit.runners.Suite import org.junit.runners.model.RunnerBuilder -class GuiTestSuite(suiteClass: Class<*>, val builder: RunnerBuilder): Suite(suiteClass, builder) { +class GuiTestSuite(suiteClass: Class<*>, val builder: RunnerBuilder) : Suite(suiteClass, builder) { //IDE type to run suite tests with val myIde = GuiTestLocalRunner.getIdeFromAnnotation(suiteClass) var myFirstStart = true - val LOG = org.apache.log4j.Logger.getLogger("#com.intellij.testGuiFramework.framework.GuiTestSuite") + val UNDEFINED_FIRST_CLASS = "undefined" + val myFirstStartClassName: String by lazy { + val annotation = suiteClass.getAnnotation(FirstStartWith::class.java) + val value = annotation?.value + if (value != null) value.java.canonicalName else UNDEFINED_FIRST_CLASS + } + val LOG = org.apache.log4j.Logger.getLogger("#com.intellij.testGuiFramework.framework.GuiTestSuite")!! override fun runChild(runner: Runner, notifier: RunNotifier?) { try { //let's start IDE to complete installation, import configs and etc before running tests if (myFirstStart) firstStart() - val testClass = runner.description.testClass //check that ide types are equal - check(GuiTestLocalRunner.getIdeFromAnnotation(testClass).ideType == myIde.ideType) + check(GuiTestLocalRunner.getIdeFromAnnotation(testClass).ideType.javaClass == myIde.ideType.javaClass) val guiTestLocalRunner = GuiTestLocalRunner(testClass) super.runChild(guiTestLocalRunner, notifier) - } catch (e: Exception) { + } + catch (e: Exception) { LOG.error(e) notifier?.fireTestFailure(Failure(runner.description, e)) } } private fun firstStart() { + if (myFirstStartClassName == UNDEFINED_FIRST_CLASS) return LOG.info("IDE is configuring for the first time...") - GuiTestLocalLauncher.firstStartIdeLocally(myIde) + GuiTestLocalLauncher.firstStartIdeLocally(myIde, myFirstStartClassName) myFirstStart = false } + + } diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/framework/RunWithIde.java b/platform/testGuiFramework/src/com/intellij/testGuiFramework/framework/RunWithIde.java index 23997d41603e..741fff88af45 100644 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/framework/RunWithIde.java +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/framework/RunWithIde.java @@ -15,6 +15,7 @@ */ package com.intellij.testGuiFramework.framework; +import com.intellij.testGuiFramework.launcher.ide.CommunityIde; import com.intellij.testGuiFramework.launcher.ide.IdeType; import java.lang.annotation.ElementType; @@ -25,5 +26,5 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface RunWithIde { - IdeType value() default IdeType.IDEA_ULTIMATE; + Class value() default CommunityIde.class; } diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/generators/Generators.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/generators/Generators.kt index ddaf7793838d..ea6e79db9662 100644 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/generators/Generators.kt +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/generators/Generators.kt @@ -17,7 +17,6 @@ package com.intellij.testGuiFramework.generators -import com.intellij.framework.PresentableVersion import com.intellij.ide.plugins.PluginTable import com.intellij.ide.projectView.impl.ProjectViewTree import com.intellij.openapi.actionSystem.impl.ActionButton @@ -32,7 +31,8 @@ import com.intellij.openapi.wm.impl.ToolWindowImpl import com.intellij.openapi.wm.impl.ToolWindowManagerImpl import com.intellij.openapi.wm.impl.WindowManagerImpl import com.intellij.openapi.wm.impl.welcomeScreen.FlatWelcomeFrame -import com.intellij.platform.ProjectTemplate +import com.intellij.testGuiFramework.cellReader.ExtendedJListCellReader +import com.intellij.testGuiFramework.cellReader.ExtendedJTableCellReader import com.intellij.testGuiFramework.fixtures.MessageDialogFixture import com.intellij.testGuiFramework.fixtures.MessagesFixture import com.intellij.testGuiFramework.fixtures.SettingsTreeFixture @@ -49,12 +49,12 @@ import com.intellij.testGuiFramework.impl.GuiTestUtilKt.getComponentText import com.intellij.testGuiFramework.impl.GuiTestUtilKt.isTextComponent import com.intellij.testGuiFramework.impl.GuiTestUtilKt.onHeightCenter import com.intellij.ui.CheckboxTree +import com.intellij.ui.HyperlinkLabel import com.intellij.ui.components.JBCheckBox import com.intellij.ui.components.JBList import com.intellij.ui.components.labels.ActionLink import com.intellij.ui.components.labels.LinkLabel import com.intellij.ui.messages.SheetController -import com.intellij.ui.popup.PopupFactoryImpl import com.intellij.ui.treeStructure.SimpleTree import com.intellij.util.ui.tree.TreeUtil import org.fest.reflect.core.Reflection.field @@ -168,6 +168,17 @@ class SimpleTreeGenerator : ComponentCodeGenerator { } } +class JTableGenerator: ComponentCodeGenerator { + override fun accept(cmp: Component) = cmp is JTable + + override fun generate(cmp: JTable, me: MouseEvent, cp: Point): String { + val row = cmp.rowAtPoint(cp) + val col = cmp.columnAtPoint(cp) + val cellText = ExtendedJTableCellReader().valueAt(cmp, row, col) + return "table(\"$cellText\").cell(\"$cellText\")".addClick(me) + } +} + class JBCheckBoxGenerator : ComponentCodeGenerator { override fun priority() = 1 override fun accept(cmp: Component) = cmp is JBCheckBox @@ -201,6 +212,16 @@ class LinkLabelGenerator : ComponentCodeGenerator> { override fun generate(cmp: LinkLabel<*>, me: MouseEvent, cp: Point) = "linkLabel(\"${cmp.text}\").click()" } + +class HyperlinkLabelGenerator : ComponentCodeGenerator { + override fun accept(cmp: Component) = cmp is HyperlinkLabel + override fun generate(cmp: HyperlinkLabel, me: MouseEvent, cp: Point): String { + //we assume, that hyperlink label has only one highlighted region + val linkText = cmp.hightlightedRegionsBoundsMap.keys.toList().firstOrNull() ?: "null" + return "hyperlinkLabel(\"${cmp.text}\").clickLink(\"$linkText\")" + } +} + class JTreeGenerator : ComponentCodeGenerator { override fun accept(cmp: Component) = cmp is JTree private fun JTree.getPath(cp: Point) = this.getClosestPathForLocation(cp.x, cp.y) @@ -328,6 +349,7 @@ class JDialogGenerator : GlobalContextCodeGenerator() { class IdeFrameGenerator : GlobalContextCodeGenerator() { override fun accept(cmp: Component): Boolean { + if (cmp !is JComponent) return false val parent = (cmp as JComponent).rootPane.parent return (parent is JFrame) && parent.title != "GUI Script Editor" } @@ -553,55 +575,12 @@ object Utils { return "" } - fun getCellText(jbList: JBList<*>, pointOnList: Point): String? { - val index = jbList.locationToIndex(pointOnList) - val cellBounds = jbList.getCellBounds(index, index) - if (cellBounds.contains(pointOnList)) { - val elementAt = jbList.model.getElementAt(index) - when (elementAt) { - is PopupFactoryImpl.ActionItem -> return elementAt.text - is ProjectTemplate -> return elementAt.name - else -> { - val listCellRendererComponent = GuiTestUtil.getListCellRendererComponent(jbList, elementAt, index) as JComponent - if (listCellRendererComponent is JPanel) { - val labels = withRobot { robot -> robot.finder().findAll(listCellRendererComponent, ComponentMatcher { it is JLabel }) } - return labels.filterIsInstance(JLabel::class.java).filter { it.text.isNotEmpty() }.firstOrNull()?.text - } - return elementAt.toString() - } - } - } - return null - } - fun getCellText(jList: JList<*>, pointOnList: Point): String? { - val index = jList.locationToIndex(pointOnList) - val cellBounds = jList.getCellBounds(index, index) - if (cellBounds.contains(pointOnList)) { - val elementAt = jList.model.getElementAt(index) - val listCellRendererComponent = GuiTestUtil.getListCellRendererComponent(jList, elementAt, index) - when (elementAt) { - is PopupFactoryImpl.ActionItem -> return elementAt.text - is ProjectTemplate -> return elementAt.name - is PresentableVersion -> return elementAt.presentableName - javaClass.canonicalName == "com.intellij.ide.util.frameworkSupport.FrameworkVersion" -> { - val getNameMethod = elementAt.javaClass.getMethod("getVersionName") - val name = getNameMethod.invoke(elementAt) - return name as String - } - else -> { - if (listCellRendererComponent is JPanel) { - if (!listCellRendererComponent.components.isEmpty() && listCellRendererComponent.components[0] is JLabel) - return (listCellRendererComponent.components[0] as JLabel).text - } - else - if (listCellRendererComponent is JLabel) - return listCellRendererComponent.text - return elementAt.toString() - } - } + return withRobot { robot -> + val extCellReader = ExtendedJListCellReader() + val index = jList.locationToIndex(pointOnList) + extCellReader.valueAt(jList, index) } - return null } fun convertSimpleTreeItemToPath(tree: SimpleTree, itemName: String): String { @@ -711,5 +690,14 @@ object Utils { val result = robotFunction(robot) return result } + +} + +private fun String.addClick(me: MouseEvent): String { + return when { + me.isLeftButton() && me.clickCount == 2 -> "$this.doubleClick()" + me.isRightButton() -> "$this.rightClick()" + else -> "$this.click()" + } } diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/impl/FirstStart.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/impl/FirstStart.kt index 03f47aeb407d..18922140a31b 100644 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/impl/FirstStart.kt +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/impl/FirstStart.kt @@ -22,7 +22,6 @@ import com.intellij.testGuiFramework.impl.FirstStart.Utils.button import com.intellij.testGuiFramework.impl.FirstStart.Utils.dialog import com.intellij.testGuiFramework.impl.FirstStart.Utils.radioButton import com.intellij.testGuiFramework.launcher.ide.IdeType -import com.intellij.util.PlatformUtils import org.fest.swing.core.GenericTypeMatcher import org.fest.swing.core.Robot import org.fest.swing.core.SmartWaitRobot @@ -51,31 +50,6 @@ abstract class FirstStart(val ideType: IdeType) { protected val LOG = Logger.getInstance(this.javaClass.name) - companion object { - - @Suppress("unused") //Called via reflection from FirstStarter - @JvmStatic - fun guessIdeAndStartRobot(): FirstStart { - when (System.getProperty(PlatformUtils.PLATFORM_PREFIX_KEY, PlatformUtils.IDEA_PREFIX)) { - PlatformUtils.IDEA_PREFIX -> return IdeaUltimateFirstStart() - PlatformUtils.IDEA_CE_PREFIX -> return IdeaCommunityFirstStart() - PlatformUtils.APPCODE_PREFIX -> TODO("Create complete installation case for AppCode") - PlatformUtils.CLION_PREFIX -> TODO("Create complete installation case for CLion") - PlatformUtils.PYCHARM_PREFIX -> TODO("Create complete installation case for PyCharm") - PlatformUtils.PYCHARM_CE_PREFIX -> TODO("Create complete installation case for PyCharm") - PlatformUtils.PYCHARM_EDU_PREFIX -> TODO("Create complete installation case for PyCharm") - PlatformUtils.RUBY_PREFIX -> TODO("Create complete installation case for RubyMine") - PlatformUtils.PHP_PREFIX -> TODO("Create complete installation case for PhpStorm") - PlatformUtils.WEB_PREFIX -> return WebStormFirstStart() - PlatformUtils.DBE_PREFIX -> TODO("Create complete installation case for DataGrip") - PlatformUtils.RIDER_PREFIX -> TODO("Create complete installation case for Rider") - PlatformUtils.GOIDE_PREFIX -> TODO("Create complete installation case for Gogland") - else -> return IdeaCommunityFirstStart() - } - - } - } - val myRobot: Robot val robotThread: Thread = thread(start = false, name = FIRST_START_ROBOT_THREAD) { @@ -88,6 +62,14 @@ abstract class FirstStart(val ideType: IdeType) { robotThread.start() } + companion object { + fun guessIdeAndStartRobot() { + val firstStartClass = System.getProperty("idea.gui.test.first.start.class") + val firstStart = Class.forName(firstStartClass).newInstance() as FirstStart + firstStart.completeInstallation() + } + } + protected abstract fun completeFirstStart() private fun awtIsNotStarted() @@ -143,16 +125,6 @@ abstract class FirstStart(val ideType: IdeType) { } } - protected fun webStormInitialConfiguration() { - with(myRobot) { - val title = "WebStorm Initial Configuration" - LOG.info("Waiting for '$title' dialog") - dialog(title, 120) - LOG.info("Click OK on '$title'") - button("OK", 120) - } - } - protected fun customizeIntellijIdea() { with(myRobot) { val title = "Customize IntelliJ IDEA" @@ -221,38 +193,3 @@ abstract class FirstStart(val ideType: IdeType) { } } - -class IdeaCommunityFirstStart : FirstStart(ideType = IdeType.IDEA_COMMUNITY) { - - override fun completeFirstStart() { - completeInstallation() - acceptAgreement() - customizeIntellijIdea() - waitWelcomeFrameAndClose() - } -} - -class IdeaUltimateFirstStart : FirstStart(ideType = IdeType.IDEA_ULTIMATE) { - - override fun completeFirstStart() { - completeInstallation() - acceptAgreement() - customizeIntellijIdea() - waitWelcomeFrameAndClose() - } - - -} - -class WebStormFirstStart : FirstStart(ideType = IdeType.WEBSTORM) { - - override fun completeFirstStart() { - completeInstallation() - acceptAgreement() - waitWelcomeFrame() - webStormInitialConfiguration() - waitWelcomeFrameAndClose() - } - -} - diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/impl/FirstStartToRemove.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/impl/FirstStartToRemove.kt deleted file mode 100644 index 625d763bea96..000000000000 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/impl/FirstStartToRemove.kt +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2000-2017 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.testGuiFramework.impl - -import com.intellij.idea.Main -import com.intellij.openapi.application.ApplicationManager -import com.intellij.testGuiFramework.fixtures.JDialogFixture -import com.intellij.testGuiFramework.framework.GuiTestUtil -import com.intellij.util.PlatformUtils.* -import org.fest.swing.core.BasicRobot -import org.fest.swing.core.ComponentMatcher -import org.fest.swing.core.Robot -import org.fest.swing.exception.ComponentLookupException -import org.fest.swing.fixture.JRadioButtonFixture -import org.fest.swing.timing.Condition -import org.fest.swing.timing.Pause -import org.fest.swing.timing.Timeout -import java.awt.Toolkit -import java.util.concurrent.TimeUnit -import javax.swing.JRadioButton - -/** - * @author Sergey Karashevich - */ -class FirstStartToRemove(robot: Robot) : GuiTestCase() { - - fun webstormBeforeStart() { - dialog("Complete Installation") { button("OK").click() } - } - - fun webstormAfterStart() { - dialog("WebStorm Initial Configuration") { button("OK").click() } - } - - fun idea(): Unit = TODO("Create complete installation case for IntelliJ IDEA") - - fun completeBefore() { - - when (System.getProperty(PLATFORM_PREFIX_KEY, IDEA_PREFIX)) { - IDEA_PREFIX -> TODO("Create complete installation case for IntelliJ IDEA") - IDEA_CE_PREFIX -> TODO("Create complete installation case for IntelliJ IDEA Community Edition") - APPCODE_PREFIX -> TODO("Create complete installation case for AppCode") - CLION_PREFIX -> TODO("Create complete installation case for CLion") - PYCHARM_PREFIX -> TODO("Create complete installation case for PyCharm") - PYCHARM_CE_PREFIX -> TODO("Create complete installation case for PyCharm Community Edition") - PYCHARM_EDU_PREFIX -> TODO("Create complete installation case for PyCharm Educational Edition") - RUBY_PREFIX -> TODO("Create complete installation case for RubyMine") - PHP_PREFIX -> TODO("Create complete installation case for PhpStorm") - WEB_PREFIX -> webstormBeforeStart() - DBE_PREFIX -> TODO("Create complete installation case for DataGrip") - RIDER_PREFIX -> TODO("Create complete installation case for Rider") - GOIDE_PREFIX -> TODO("Create complete installation case for Gogland") - else -> TODO("Unsupported IDE prefix") - } - } - - fun completeAfter() { - - when (System.getProperty(PLATFORM_PREFIX_KEY, IDEA_PREFIX)) { - IDEA_PREFIX -> TODO("Create complete installation case for IntelliJ IDEA") - IDEA_CE_PREFIX -> TODO("Create complete installation case for IntelliJ IDEA Community Edition") - APPCODE_PREFIX -> TODO("Create complete installation case for AppCode") - CLION_PREFIX -> TODO("Create complete installation case for CLion") - PYCHARM_PREFIX -> TODO("Create complete installation case for PyCharm") - PYCHARM_CE_PREFIX -> TODO("Create complete installation case for PyCharm Community Edition") - PYCHARM_EDU_PREFIX -> TODO("Create complete installation case for PyCharm Educational Edition") - RUBY_PREFIX -> TODO("Create complete installation case for RubyMine") - PHP_PREFIX -> TODO("Create complete installation case for PhpStorm") - WEB_PREFIX -> webstormAfterStart() - DBE_PREFIX -> TODO("Create complete installation case for DataGrip") - RIDER_PREFIX -> TODO("Create complete installation case for Rider") - GOIDE_PREFIX -> TODO("Create complete installation case for Gogland") - else -> TODO("Unsupported IDE prefix") - } - } - - init { - super.setRobot(robot) - } - - companion object { - - - @JvmStatic fun main(args: Array) { - - object : Thread("GUI Test Thread") { - - fun startRobotActivity() { - val myRobot = BasicRobot.robotWithNewAwtHierarchyWithoutScreenLock() - val dialogCompleteInstallation = JDialogFixture.find(myRobot, "Complete Installation") - val foundComponent: JRadioButton = myRobot.finder().find { component -> component is JRadioButton && component.text.equals("Do not import settings") } as JRadioButton - JRadioButtonFixture(myRobot, foundComponent).click() - GuiTestUtil.findAndClickButton(dialogCompleteInstallation, "OK") - - val dialogUiCustom = JDialogFixture.find(myRobot, "Customize IntelliJ IDEA") - GuiTestUtil.findAndClickButton(dialogUiCustom, "Skip All and Set Defaults") - - Pause.pause(object: Condition("Waiting for welcome frame") { - override fun test(): Boolean { - try { - return myRobot.finder().findAll(ComponentMatcher { component -> component!!.javaClass.name == "com.intellij.openapi.wm.impl.welcomeScreen.FlatWelcomeFrame" }).size == 1 - } catch (cle: ComponentLookupException) { - return false - } - } - }, Timeout.timeout(1, TimeUnit.MINUTES)) - reflectiveExit() - myRobot.cleanUp() - } - - private fun reflectiveExit() { - val appManager = Toolkit.getDefaultToolkit().systemEventQueue.javaClass.classLoader.loadClass(ApplicationManager::class.java.name) - val getAppMethod = appManager.getMethod("getApplication") - val app = getAppMethod.invoke(null) - app.javaClass.getMethod("exit").invoke(app) - } - - fun awtIsStarted() = Thread.getAllStackTraces().keys.any { thread -> thread.name.toLowerCase().contains("awt") } - - override fun run() { - while (!awtIsStarted()) Thread.sleep(100) //we need to wait when awt thread will appear to avoid create it with wrong classloader - startRobotActivity() - } - }.start() - Main.main(args) - } - } - -} \ No newline at end of file diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/impl/FirstStarter.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/impl/FirstStarter.kt index 48cc1e727d42..005f8cfb336d 100644 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/impl/FirstStarter.kt +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/impl/FirstStarter.kt @@ -25,7 +25,6 @@ class FirstStarter { } fun main(args: Array) { - startRobotRoutine() startIdeMainRoutine(args) } diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/impl/GuiTestCase.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/impl/GuiTestCase.kt index a96e8e6ba4e6..8791a716089b 100644 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/impl/GuiTestCase.kt +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/impl/GuiTestCase.kt @@ -17,8 +17,11 @@ package com.intellij.testGuiFramework.impl import com.intellij.ide.GeneralSettings import com.intellij.openapi.ui.ComponentWithBrowseButton +import com.intellij.openapi.util.Ref import com.intellij.openapi.util.io.FileUtil +import com.intellij.testGuiFramework.cellReader.ExtendedJComboboxCellReader import com.intellij.testGuiFramework.cellReader.ExtendedJListCellReader +import com.intellij.testGuiFramework.cellReader.ExtendedJTableCellReader import com.intellij.testGuiFramework.fixtures.* import com.intellij.testGuiFramework.fixtures.extended.ExtendedTreeFixture import com.intellij.testGuiFramework.fixtures.newProjectWizard.NewProjectWizardFixture @@ -30,10 +33,12 @@ import com.intellij.testGuiFramework.impl.GuiTestUtilKt.findBoundedComponentByTe import com.intellij.testGuiFramework.impl.GuiTestUtilKt.getComponentText import com.intellij.testGuiFramework.impl.GuiTestUtilKt.isTextComponent import com.intellij.testGuiFramework.launcher.system.SystemInfo.isMac +import com.intellij.ui.HyperlinkLabel import com.intellij.ui.components.labels.LinkLabel import com.intellij.util.net.HttpConfigurable import org.fest.swing.core.GenericTypeMatcher import org.fest.swing.core.SmartWaitRobot +import org.fest.swing.exception.ActionFailedException import org.fest.swing.exception.ComponentLookupException import org.fest.swing.exception.WaitTimedOutError import org.fest.swing.fixture.* @@ -42,6 +47,7 @@ import org.fest.swing.timing.Condition import org.fest.swing.timing.Pause import org.fest.swing.timing.Timeout import org.fest.swing.timing.Timeout.timeout +import org.junit.After import java.awt.Component import java.awt.Container import java.io.File @@ -77,6 +83,10 @@ import javax.swing.text.JTextComponent */ open class GuiTestCase : GuiTestBase() { + @After + fun closeProjects(){ + closeAllProjects() + } class GuiSettings internal constructor() { @@ -339,6 +349,18 @@ open class GuiTestCase : GuiTestBase() { else throw UnsupportedOperationException( "Sorry, unable to find JTree component \"${if (pathStrings.isNotEmpty()) "by path ${pathStrings}" else ""}\" with ${target().toString()} as a Container") + /** + * Finds a JTable component in hierarchy of context component by a cellText and returns JTableFixture. + * + * @timeout in seconds to find JTable component + * @throws ComponentLookupException if component has not been found or timeout exceeded + */ + fun ComponentFixture.table(cellText: String, + timeout: Long = defaultTimeout): JTableFixture = if (target() is Container) table( + target() as Container, cellText, timeout) + else throw UnsupportedOperationException( + "Sorry, unable to find JTable component with cell text \"$cellText\" with ${target().toString()} as a Container") + /** * Finds popup on screen with item (itemName) and clicks on it item * @@ -363,6 +385,13 @@ open class GuiTestCase : GuiTestBase() { else throw UnsupportedOperationException( "Sorry, unable to find LinkLabel component with ${target().toString()} as a Container") + + fun ComponentFixture.hyperlinkLabel(labelText: String, /*timeout in seconds*/ + timeout: Long = defaultTimeout): HyperlinkLabelFixture + = if (target() is Container) hyperlinkLabel(target() as Container, labelText, timeout) + else throw UnsupportedOperationException( + "Sorry, unable to find HyperlinkLabel component by label text: \"$labelText\" with ${target().toString()} as a Container") + /** * Finds a table of plugins component in hierarchy of context component by a link name and returns fixture for it. * @@ -472,7 +501,7 @@ open class GuiTestCase : GuiTestBase() { if (containingItem == null) true //if were searching for any jList() else { val elements = (0..jList.model.size - 1).map { it -> extCellReader.valueAt(jList, it) } - elements.any { it.toString() == containingItem } && jList.isShowing + elements.any { it.toString() == containingItem } && jList.isShowing } } val jListFixture = JListFixture(myRobot, myJList) @@ -498,9 +527,16 @@ open class GuiTestCase : GuiTestBase() { private fun combobox(container: Container, text: String, timeout: Long): JComboBoxFixture { //wait until label has appeared waitUntilFound(container, Component::class.java, - timeout) { component -> component.isTextComponent() && component.getComponentText() == text } + timeout) { component -> + component.isEnabled + && component.isShowing + && component.isTextComponent() + && component.getComponentText() == text + } val comboBox = findBoundedComponentByText(myRobot, container, text, JComboBox::class.java) - return JComboBoxFixture(myRobot, comboBox) + val comboboxFixture = JComboBoxFixture(myRobot, comboBox) + comboboxFixture.replaceCellReader(ExtendedJComboboxCellReader()) + return comboboxFixture } private fun checkbox(container: Container, labelText: String, timeout: Long): CheckBoxFixture { @@ -554,6 +590,27 @@ open class GuiTestCase : GuiTestBase() { return ComponentFixture, LinkLabel<*>>(ComponentFixture::class.java, myRobot, myLinkLabel) } + private fun hyperlinkLabel(container: Container, labelText: String, timeout: Long): HyperlinkLabelFixture { + val hyperlinkLabel = waitUntilFound(myRobot, container, object : GenericTypeMatcher(HyperlinkLabel::class.java) { + override fun isMatching(someHyperLinkLabel: HyperlinkLabel) = (someHyperLinkLabel.isShowing && (someHyperLinkLabel.text == labelText)) + }, timeout.toFestTimeout()) + return HyperlinkLabelFixture(myRobot, hyperlinkLabel) + } + + private fun table(container: Container, cellText: String, timeout: Long): JTableFixture { + return waitUntilFoundFixture(container, JTable::class.java, timeout) { + val jTableFixture = JTableFixture(myRobot, it) + jTableFixture.replaceCellReader(ExtendedJTableCellReader()) + val hasCellWithText = try { + jTableFixture.cell(cellText); true + } + catch (e: ActionFailedException) { + false + } + Pair(hasCellWithText, jTableFixture) + } + } + private fun pluginTable(container: Container, timeout: Long) = PluginTableFixture.find(myRobot, container, timeout.toFestTimeout()) private fun popupClick(container: Container, itemName: String, timeout: Long) { @@ -626,6 +683,21 @@ open class GuiTestCase : GuiTestBase() { }, timeout.toFestTimeout()) } + protected fun waitUntilFoundFixture(container: Container?, + componentClass: Class, + timeout: Long, + matcher: (ComponentType) -> Pair): Fixture { + val ref = Ref() + GuiTestUtil.waitUntilFound(myRobot, container, object : GenericTypeMatcher(componentClass) { + override fun isMatching(cmp: ComponentType): Boolean { + val (matched, fixture) = matcher(cmp) + if (matched) ref.set(fixture) + return matched + } + }, timeout.toFestTimeout()) + return ref.get() + } + fun pause(condition: String = "Unspecified condition", timeoutSeconds: Long = 120, testFunction: () -> Boolean) { Pause.pause(object : Condition(condition) { override fun test() = testFunction() diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/GuiTestLauncher.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/GuiTestLauncher.kt deleted file mode 100644 index 3de7bea568a9..000000000000 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/GuiTestLauncher.kt +++ /dev/null @@ -1,161 +0,0 @@ -package com.intellij.testGuiFramework.launcher - -import com.intellij.openapi.diagnostic.Logger -import com.intellij.testGuiFramework.launcher.classpath.ClassPathBuilder -import com.intellij.testGuiFramework.launcher.file.PathManager -import com.intellij.testGuiFramework.launcher.ide.Ide -import com.intellij.testGuiFramework.launcher.ide.IdeRunArgs -import com.intellij.testGuiFramework.launcher.ide.IdeTestFixture -import com.intellij.testGuiFramework.launcher.teamcity.TeamCityManager.CUSTOM_CONFIG_PATH -import com.intellij.testGuiFramework.launcher.teamcity.TeamCityManager.FEST_LIB_PATH -import com.intellij.testGuiFramework.launcher.teamcity.TeamCityManager.GUI_TEST_DATA_DIR -import com.intellij.testGuiFramework.launcher.teamcity.TeamCityManager.JDK_PATH -import com.intellij.testGuiFramework.launcher.teamcity.TeamCityManager.JUNIT_PATH -import com.intellij.testGuiFramework.launcher.teamcity.TeamCityManager.PLATFORM_PREFIX -import com.intellij.testGuiFramework.launcher.teamcity.TeamCityManager.TEST_CLASSES_DIR -import com.intellij.testGuiFramework.launcher.teamcity.TeamCityManager.TEST_GUI_FRAMEWORK_PATH -import com.intellij.testGuiFramework.remote.transport.JUnitTestContainer -import com.intellij.testGuiFramework.remote.server.JUnitServerHolder -import com.intellij.testGuiFramework.remote.transport.MessageType -import com.intellij.testGuiFramework.remote.transport.TransportMessage -import org.junit.Assert -import java.io.BufferedReader -import java.io.File -import java.io.InputStreamReader -import java.util.stream.Collectors - -/** - * @author Sergey Karashevich - */ - -object GuiTestLauncher { - - val JUNIT_STARTER by lazy { "org.junit.runner.JUnitCore" } - val javaExec by lazy { "${JDK_PATH}${File.separator}bin${File.separator}java" } - val SET_GUI_TEST_DATA_DIR by lazy { "-DGUI_TEST_DATA_DIR=${GUI_TEST_DATA_DIR}" } - val SET_CUSTOM_CONFIG_PATH by lazy { "-DCUSTOM_CONFIG_PATH=${CUSTOM_CONFIG_PATH ?: ""}" } - - val LOG: Logger = Logger.getInstance("#com.intellij.testGuiFramework.launcher.GuiTestLauncher") - - fun createArgs(ideaLibPath: String, testClass: String, testClassPath: String): List { - val classpath = ClassPathBuilder(ideaLibPath, JDK_PATH, JUNIT_PATH, FEST_LIB_PATH, TEST_GUI_FRAMEWORK_PATH).build(testClassPath) - val resultingArgs = listOf(javaExec, - "-Didea.platform.prefix=${PLATFORM_PREFIX}", - SET_GUI_TEST_DATA_DIR, - SET_CUSTOM_CONFIG_PATH, - "-classpath", - classpath, - JUNIT_STARTER, - testClass) - LOG.info("Running with args: ${resultingArgs.joinToString(" ")}") - return resultingArgs - } - - fun createArgs(ideaLibPath: String, testClasses: List, testClassPath: String): List { - val classpath = ClassPathBuilder(ideaLibPath, JDK_PATH, JUNIT_PATH, FEST_LIB_PATH, TEST_GUI_FRAMEWORK_PATH).build(testClassPath) - val resultingArgs = listOf(javaExec, - "-Didea.platform.prefix=${PLATFORM_PREFIX}", - SET_GUI_TEST_DATA_DIR, - SET_CUSTOM_CONFIG_PATH, - "-classpath", - classpath, - JUNIT_STARTER, - testClasses.joinToString(", ")) - LOG.info("Running with args: ${resultingArgs.joinToString(" ")}") - return resultingArgs - } - - fun createArgs(ideaLibPath: String, ideRunArgs: IdeRunArgs): List { - val classpath = ClassPathBuilder(ideaLibPath, JDK_PATH, JUNIT_PATH, FEST_LIB_PATH, TEST_GUI_FRAMEWORK_PATH).build(TEST_CLASSES_DIR) - val resultingArgs = listOf(javaExec) - .plus(ideRunArgs.jvmParams) - .plus(ideRunArgs.ideParams) - .plus("-classpath") - .plus(classpath) - .plus(JUNIT_STARTER) - .plus(ideRunArgs.testClasses) - LOG.info("Running with args: ${resultingArgs.joinToString(" ")}") - return resultingArgs - } - - fun runTest(ide: Ide, testClassAndMethod: String) { - val testClassName = testClassAndMethod.split("#")[0] - val testMethodName = testClassAndMethod.split("#")[1] - val testClass = Class.forName(testClassName) - if (!JUnitServerHolder.getServer().isConnected()) //if not connected to JUnitClient - GuiTestLocalLauncher.runIdeLocally(port = JUnitServerHolder.getServer().getPort(), ide = ide) //todo: add IDE specification here - val jUnitTestContainer = JUnitTestContainer(testClass, testMethodName) - JUnitServerHolder.getServer().send(TransportMessage(MessageType.RUN_TEST, jUnitTestContainer)) - } - - fun runIde(ide: Ide, ideRunArgs: IdeRunArgs): Unit { - LOG.info("Running $ide with $ideRunArgs") - val pathToSave = PathManager.getWorkDirPath() - LOG.info("IDE path to save is set to: $pathToSave") - val runnable: () -> Unit = { - val ideaStartTest = ProcessBuilder().inheritIO().command( - createArgs(ideaLibPath = PathManager.getSystemSpecificIdeLibPath(pathToSave), ideRunArgs = ideRunArgs)) - val process = ideaStartTest.start() - val wait = process.waitFor() - if (process.exitValue() != 1) println("Execution successful") - else { - Assert.fail("Process execution error:") - Assert.fail(BufferedReader(InputStreamReader(process.errorStream)).lines().collect(Collectors.joining("\n"))) - } - } - val ideaTestThread = Thread(runnable, "IdeaTestThread") - ideaTestThread.start() - } - - fun runIde(ide: Ide, testClasses: List): Unit { - LOG.info("Running $ide for test classes: ${testClasses.joinToString(", ")}") - val pathToSave = PathManager.getWorkDirPath() - val runnable: () -> Unit = { - val args = createArgs(ideaLibPath = PathManager.getSystemSpecificIdeLibPath(pathToSave), testClasses = testClasses, - testClassPath = TEST_CLASSES_DIR) - val ideaStartTest = ProcessBuilder().inheritIO().command(args) - val process = ideaStartTest.start() - val wait = process.waitFor() - if (process.exitValue() != 1) println("Execution successful") - else { - System.err.println("Process execution error:") - System.err.println(BufferedReader(InputStreamReader(process.errorStream)).lines().collect(Collectors.joining("\n"))) - } - } - val ideaTestThread = Thread(runnable, "IdeaTestThread") - ideaTestThread.start() - } - - fun runIdeLocally(ide: Ide, args: List) { - LOG.info("Running $ide locally") - val runnable: () -> Unit = { - val ideaStartTest = ProcessBuilder().inheritIO().command(args) - val process = ideaStartTest.start() - val wait = process.waitFor() - if (process.exitValue() != 1) println("Execution successful") - else { - System.err.println("Process execution error:") - System.err.println(BufferedReader(InputStreamReader(process.errorStream)).lines().collect(Collectors.joining("\n"))) - } - } - val ideaTestThread = Thread(runnable, "IdeaTestThread") - ideaTestThread.start() - } - - fun runIde(ide: Ide, func: IdeTestFixture.() -> Unit) { - val ideTestFixture = IdeTestFixture(ide) - func(ideTestFixture) - runIde(ide, ideTestFixture.buildArgs()) - } - - fun multiDpiTest(ide: Ide, dpiScale: String, func: IdeTestFixture.() -> Unit) { - val ideTestFixture = IdeTestFixture(ide) - func(ideTestFixture) - runIde(ide, ideTestFixture.buildArgs()) - ideTestFixture.jvmConfig(listOf("-Dsun.java2d.uiScale.enabled=true", "-Dsun.java2d.uiScale=$dpiScale")) - runIde(ide, ideTestFixture.buildArgs()) - } - - fun multiDpiTest(ide: Ide, dpiScale: Float, func: IdeTestFixture.() -> Unit) = multiDpiTest(ide, dpiScale.toString(), func) - -} \ No newline at end of file diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/GuiTestLocalLauncher.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/GuiTestLocalLauncher.kt index a3fcc397e6a5..1e62e8ddbe48 100644 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/GuiTestLocalLauncher.kt +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/GuiTestLocalLauncher.kt @@ -23,8 +23,9 @@ import com.intellij.testGuiFramework.impl.GuiTestStarter import com.intellij.testGuiFramework.launcher.classpath.ClassPathBuilder import com.intellij.testGuiFramework.launcher.classpath.ClassPathBuilder.Companion.isWin import com.intellij.testGuiFramework.launcher.classpath.PathUtils +import com.intellij.testGuiFramework.launcher.ide.CommunityIde import com.intellij.testGuiFramework.launcher.ide.Ide -import com.intellij.testGuiFramework.launcher.ide.IdeType +import com.intellij.testGuiFramework.launcher.system.SystemInfo import org.jetbrains.jps.model.JpsElementFactory import org.jetbrains.jps.model.JpsProject import org.jetbrains.jps.model.java.JpsJavaExtensionService @@ -80,24 +81,24 @@ object GuiTestLocalLauncher { } } - fun runIdeLocally(ide: Ide = Ide(IdeType.IDEA_ULTIMATE, 0, 0), port: Int = 0) { + fun runIdeLocally(ide: Ide = Ide(CommunityIde(), 0, 0), port: Int = 0) { //todo: check that we are going to run test locally val args = createArgs(ide = ide, port = port) return startIde(ide = ide, args = args) } - fun runIdeByPath(path: String, ide: Ide = Ide(IdeType.IDEA_ULTIMATE, 0, 0), port: Int = 0) { + fun runIdeByPath(path: String, ide: Ide = Ide(CommunityIde(), 0, 0), port: Int = 0) { //todo: check that we are going to run test locally val args = createArgsByPath(path, port) return startIde(ide = ide, args = args) } - fun firstStartIdeLocally(ide: Ide = Ide(IdeType.IDEA_ULTIMATE, 0, 0)) { - val args = createArgsForFirstStart(ide) + fun firstStartIdeLocally(ide: Ide = Ide(CommunityIde(), 0, 0), firstStartClassName: String = "undefined") { + val args = createArgsForFirstStart(ide = ide, firstStartClassName = firstStartClassName) return startIdeAndWait(ide = ide, args = args) } - fun firstStartIdeByPath(path: String, ide: Ide = Ide(IdeType.IDEA_ULTIMATE, 0, 0)) { + fun firstStartIdeByPath(path: String, ide: Ide = Ide(CommunityIde(), 0, 0)) { val args = createArgsForFirstStartByPath(ide, path) return startIdeAndWait(ide = ide, args = args) } @@ -141,15 +142,23 @@ object GuiTestLocalLauncher { private fun createArgs(ide: Ide, mainClass: String = "com.intellij.idea.Main", port: Int = 0): List - = createArgsBase(ide, mainClass, GuiTestStarter.COMMAND_NAME, port) + = createArgsBase(ide = ide, + mainClass = mainClass, + commandName = GuiTestStarter.COMMAND_NAME, + port = port) - private fun createArgsForFirstStart(ide: Ide, port: Int = 0): List - = createArgsBase(ide, "com.intellij.testGuiFramework.impl.FirstStarterKt", null, port) + private fun createArgsForFirstStart(ide: Ide, firstStartClassName: String = "undefined", port: Int = 0): List + = createArgsBase(ide = ide, + mainClass = "com.intellij.testGuiFramework.impl.FirstStarterKt", + firstStartClassName = firstStartClassName, + commandName = null, + port = port) - private fun createArgsBase(ide: Ide, mainClass: String, commandName: String?, port: Int): List { + private fun createArgsBase(ide: Ide, mainClass: String, commandName: String?, firstStartClassName: String = "undefined", port: Int): List { var resultingArgs = listOf() .plus(getCurrentJavaExec()) .plus(getDefaultVmOptions(ide)) + .plus("-Didea.gui.test.first.start.class=$firstStartClassName") .plus("-classpath") .plus(getOsSpecificClasspath(ide.ideType.mainModule)) .plus(mainClass) @@ -162,12 +171,13 @@ object GuiTestLocalLauncher { } - private fun createArgsForFirstStartByPath(ide: Ide, path: String): List { + private fun createArgsForFirstStartByPath(ide: Ide, path: String, firstStartClassName: String = "undefined"): List { val classpath = PathUtils(path).makeClassPathBuilder().build(emptyList()) val resultingArgs = listOf() .plus(getCurrentJavaExec()) .plus(getDefaultVmOptions(ide)) + .plus("-Didea.gui.test.first.start.class=$firstStartClassName") .plus("-classpath") .plus(classpath) .plus(com.intellij.testGuiFramework.impl.FirstStarter::class.qualifiedName!! + "Kt") @@ -175,12 +185,13 @@ object GuiTestLocalLauncher { } private fun createArgsByPath(path: String, port: Int = 0): List { - var resultingArgs = listOf() - .plus("open") - .plus(path) //path to exec - .plus("--args") - .plus(GuiTestStarter.COMMAND_NAME) - if (port != 0) resultingArgs = resultingArgs.plus("port=$port") + val resultingArgs = mutableListOf( + path, + "--args", + GuiTestStarter.COMMAND_NAME + ) + if (SystemInfo.isMac()) resultingArgs.add(0, "open") + if (port != 0) resultingArgs.add("port=$port") LOG.info("Running with args: ${resultingArgs.joinToString(" ")}") return resultingArgs } @@ -188,26 +199,26 @@ object GuiTestLocalLauncher { /** * Default VM options to start IntelliJ IDEA (or IDEA-based IDE). To customize options use com.intellij.testGuiFramework.launcher.GuiTestOptions */ - private fun getDefaultVmOptions(ide: Ide) : List { + private fun getDefaultVmOptions(ide: Ide): List { return listOf() - .plus("-Xmx${GuiTestOptions.getXmxSize()}m") - .plus("-XX:ReservedCodeCacheSize=150m") - .plus("-XX:+UseConcMarkSweepGC") - .plus("-XX:SoftRefLRUPolicyMSPerMB=50") - .plus("-XX:MaxJavaStackTraceDepth=10000") - .plus("-ea") - .plus("-Xbootclasspath/p:${GuiTestOptions.getBootClasspath()}") - .plus("-Dsun.awt.disablegrab=true") - .plus("-Dsun.io.useCanonCaches=false") - .plus("-Djava.net.preferIPv4Stack=true") - .plus("-Dapple.laf.useScreenMenuBar=${GuiTestOptions.useAppleScreenMenuBar().toString()}") - .plus("-Didea.is.internal=${GuiTestOptions.isInternal().toString()}") - .plus("-Didea.config.path=${GuiTestOptions.getConfigPath()}") - .plus("-Didea.system.path=${GuiTestOptions.getSystemPath()}") - .plus("-Dfile.encoding=${GuiTestOptions.getEncoding()}") - .plus("-Didea.platform.prefix=${ide.ideType.platformPrefix}") - .plus("-Xdebug") - .plus("-Xrunjdwp:transport=dt_socket,server=y,suspend=${GuiTestOptions.suspendDebug()},address=${GuiTestOptions.getDebugPort()}") + .plus("-Xmx${GuiTestOptions.getXmxSize()}m") + .plus("-XX:ReservedCodeCacheSize=240m") + .plus("-XX:+UseConcMarkSweepGC") + .plus("-XX:SoftRefLRUPolicyMSPerMB=50") + .plus("-XX:MaxJavaStackTraceDepth=10000") + .plus("-ea") + .plus("-Xbootclasspath/p:${GuiTestOptions.getBootClasspath()}") + .plus("-Dsun.awt.disablegrab=true") + .plus("-Dsun.io.useCanonCaches=false") + .plus("-Djava.net.preferIPv4Stack=true") + .plus("-Dapple.laf.useScreenMenuBar=${GuiTestOptions.useAppleScreenMenuBar().toString()}") + .plus("-Didea.is.internal=${GuiTestOptions.isInternal().toString()}") + .plus("-Didea.config.path=${GuiTestOptions.getConfigPath()}") + .plus("-Didea.system.path=${GuiTestOptions.getSystemPath()}") + .plus("-Dfile.encoding=${GuiTestOptions.getEncoding()}") + .plus("-Didea.platform.prefix=${ide.ideType.platformPrefix}") + .plus("-Xdebug") + .plus("-Xrunjdwp:transport=dt_socket,server=y,suspend=${GuiTestOptions.suspendDebug()},address=${GuiTestOptions.getDebugPort()}") } private fun getCurrentJavaExec(): String { @@ -279,8 +290,8 @@ object GuiTestLocalLauncher { * @return true if classloader's output path is the same to module's output path (and also same to project) */ private fun needToChangeProjectOutput(project: JpsProject): Boolean = - JpsJavaExtensionService.getInstance().getProjectExtension(project)?.outputUrl == - getOutputRootFromClassloader().path + JpsJavaExtensionService.getInstance().getProjectExtension(project)?.outputUrl == + getOutputRootFromClassloader().path private fun JpsProject.changeOutputIfNeeded() { @@ -290,8 +301,4 @@ object GuiTestLocalLauncher { } } -} - -fun main(args: Array) { - GuiTestLocalLauncher.firstStartIdeLocally(Ide(ideType = IdeType.WEBSTORM, version = 0, build = 0)) } \ No newline at end of file diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/download/IdeDownloader.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/download/IdeDownloader.kt deleted file mode 100644 index 00b4a768a422..000000000000 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/download/IdeDownloader.kt +++ /dev/null @@ -1,74 +0,0 @@ -package com.intellij.testGuiFramework.launcher.download - -import com.intellij.openapi.diagnostic.Logger -import com.intellij.testGuiFramework.launcher.file.FileUtils -import com.intellij.testGuiFramework.launcher.file.PathManager -import com.intellij.testGuiFramework.launcher.file.PathManager.getSystemSpecificIdeLibPath -import com.intellij.testGuiFramework.launcher.ide.Ide -import com.intellij.testGuiFramework.launcher.ide.IdeType -import com.intellij.testGuiFramework.launcher.system.SystemInfo -import com.intellij.testGuiFramework.launcher.teamcity.TeamCityManager.baseUrl -import com.intellij.testGuiFramework.launcher.teamcity.TeamCityManager.guestAuth -import com.intellij.testGuiFramework.launcher.zip.ZipUtils -import java.io.File -import java.io.FileOutputStream -import java.net.URL -import java.nio.channels.Channels - - -/** - * @author Sergey Karashevich - */ -object IdeDownloader { - - val LOG: Logger = Logger.getInstance("#com.intellij.testGuiFramework.launcher.download.IdeDownloader") - - val buildArtifactPath = "-{build.number}" - val unscramblePath = "unscrambled" - - - fun download(url: URL, pathFile: String) { - LOG.info("downloading from URL: $url to $pathFile ...") - val rbc = Channels.newChannel(url.openStream()) - val fos = FileOutputStream(pathFile) - fos.channel.transferFrom(rbc, 0, java.lang.Long.MAX_VALUE) - LOG.info("downloading done") - fos.close() - } - - fun unpack(pathFile: String) { - //check extension - LOG.info("unpacking $pathFile in the same dir ...") - val workDir = File(pathFile).parentFile - when(SystemInfo.getSystemType()) { - - SystemInfo.SystemType.WINDOWS -> ZipUtils.installExe(pathFile, workDir.path) - SystemInfo.SystemType.UNIX -> TODO() - SystemInfo.SystemType.MAC -> ZipUtils.extractSit(pathFile, workDir.path) - } - LOG.info("unpacking done") - } - - fun unscramble(ide: Ide, workDir: String) { - val pathToFile = "$workDir${File.separator}idea.jar" - LOG.info("unscrambling $pathToFile") - download(buildUnscrambleUrl(ide), pathToFile) - FileUtils.copy(from = pathToFile, to = "${getSystemSpecificIdeLibPath(workDir)}${File.separator}idea.jar") - LOG.info("unscrambling done") - } - - fun buildUrl(ide: Ide, extension: String = "zip"): URL = URL("${baseUrl}/${guestAuth}/${ide.ideType.buildTypeExtId}/${ide.version}.${ide.build}/${ide.ideType.id}$buildArtifactPath.$extension") - fun buildUnscrambleUrl(ide: Ide): URL = URL("${baseUrl}/${guestAuth}/${ide.ideType.buildTypeExtId}/${ide.version}.${ide.build}/$unscramblePath/${ide.ideType.ideJarName}") - - @JvmStatic - fun main(args: Array) { - val ide = Ide(ideType = IdeType.IDEA_COMMUNITY, version = 171, build = 3085) - val ext = SystemInfo.getExt() - val pathToSave = PathManager.getWorkDirPath() - val path = "$pathToSave${File.separator}${ide.ideType.id}-${ide.version}.${ide.build}.$ext" - - download(buildUrl(ide = ide, extension = ext), path) - unpack(path) - unscramble(ide, pathToSave) - } -} \ No newline at end of file diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/dpi/DpiComparator.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/dpi/DpiComparator.kt deleted file mode 100644 index 323b4d2e7226..000000000000 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/dpi/DpiComparator.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.intellij.testGuiFramework.launcher.dpi - -import java.io.File -import java.io.FileNotFoundException -import java.util.* - -/** - * @author Sergey Karashevich - */ -class DpiComparator(val dirToScan: File, val comparator: PngComparator) { - - val pngPairs: MutableMap = HashMap() - - companion object { - fun process(pathToDir: String, comparator: PngComparator) { - val dir = File(pathToDir) - if (!dir.exists()) throw FileNotFoundException("Unable to find directory: $pathToDir") - DpiComparator(dir, comparator).scan().compare() - } - } - - private fun scan(): DpiComparator { - val files = dirToScan.listFiles().toCollection(ArrayList(dirToScan.listFiles().size)) - while (files.isNotEmpty()) { - val extractedPair = files.extractPair() ?: continue - pngPairs.put(extractedPair.first, extractedPair.second) - } - return this - } - - private fun MutableList.extractPair(): Pair? { - val file = this.firstOrNull() ?: throw ArrayIndexOutOfBoundsException("Array with files is empty") - val customDpiRegex = Regex("@\\d+\\.*\\d*x\\.png") - if (file.name.contains(customDpiRegex)) { - val fileNameToSearch = file.name.replace(customDpiRegex, ".png") - val foundPair : File? = this.find { file -> file.name == fileNameToSearch } - this.remove(file) - if (foundPair == null) return null - this.remove(foundPair) - return Pair(foundPair, file) //the first one goes without dpi - } else { - val trimmedName = file.name.removeSuffix(".png") - val foundPair : File? = this.find { file -> file.name.startsWith(trimmedName)} - this.remove(file) - if (foundPair == null) return null - this.remove(foundPair) - return Pair(file, foundPair) //the first one goes without dpi - } - } - - private fun compare() { - pngPairs.forEach { pair -> comparator.compare(pair.key, pair.value) } - } - -} \ No newline at end of file diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/file/FileUtils.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/file/FileUtils.kt deleted file mode 100644 index f079a8b40961..000000000000 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/file/FileUtils.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.intellij.testGuiFramework.launcher.file - -import com.intellij.openapi.diagnostic.Logger -import java.nio.file.Files -import java.nio.file.Paths -import java.nio.file.StandardCopyOption - - -/** - * @author Sergey Karashevich - */ -object FileUtils { - - val LOG: Logger = Logger.getInstance("#com.intellij.testGuiFramework.launcher.file.FileUtils") - - fun copy(from: String, to: String, copyOption: StandardCopyOption = StandardCopyOption.REPLACE_EXISTING) { - LOG.info("copying files from $from to $to") - val fromPath = Paths.get(from) - val toPath = Paths.get(to) //convert from String to Path - Files.copy(fromPath, toPath, copyOption) - LOG.info("done") - } - - fun getAppFilePath(location: String): String { - val fromPath = Paths.get(location) - val fileApp = fromPath.toFile().listFiles().filter {file -> file.name.endsWith(".app")}.firstOrNull() ?: throw Exception("Unable to find .app file in $location") - return fileApp.path - } - - fun exists(location: String): Boolean = Paths.get(location).toFile().exists() - -} \ No newline at end of file diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/file/PathManager.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/file/PathManager.kt deleted file mode 100644 index 160f5229f63b..000000000000 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/file/PathManager.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.intellij.testGuiFramework.launcher.file - -import com.intellij.testGuiFramework.launcher.system.SystemInfo -import com.intellij.testGuiFramework.launcher.teamcity.TeamCityManager -import java.io.File - -/** - * @author Sergey Karashevich - */ -object PathManager { - - fun getWorkDirPath() = TeamCityManager.WORK_DIR_PATH - - fun getSystemSpecificIdePath(workDir: String): String { - when (SystemInfo.getSystemType()) { - SystemInfo.SystemType.WINDOWS -> return workDir - SystemInfo.SystemType.UNIX -> TODO() - SystemInfo.SystemType.MAC -> return FileUtils.getAppFilePath(workDir) - } - } - - fun getSystemSpecificIdeLibPath(workDir: String): String { - var path = getSystemSpecificIdePath(workDir) - if (SystemInfo.getSystemType() == SystemInfo.SystemType.MAC) - path += "${File.separator}Contents" - path += "${File.separator}lib" - val file = File(path) - return file.path - } - - fun toSysSpecPath(unixStylePath: String) = if (SystemInfo.getSystemType() == SystemInfo.SystemType.WINDOWS) unixStylePath.replace("/", File.separator) else unixStylePath - -} \ No newline at end of file diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/ide/CommunityIde.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/ide/CommunityIde.kt new file mode 100644 index 000000000000..90c6ceeb8ab4 --- /dev/null +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/ide/CommunityIde.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2000-2017 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.testGuiFramework.launcher.ide + +class CommunityIde: IdeType(platformPrefix = "Idea", ideJarName = "idea.jar", mainModule = "community-main") \ No newline at end of file diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/ide/CommunityIdeFirstStart.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/ide/CommunityIdeFirstStart.kt new file mode 100644 index 000000000000..2ec342bc7ffe --- /dev/null +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/ide/CommunityIdeFirstStart.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2000-2017 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.testGuiFramework.launcher.ide + +import com.intellij.testGuiFramework.impl.FirstStart + +class CommunityIdeFirstStart : FirstStart(ideType = CommunityIde()) { + + override fun completeFirstStart() { + completeInstallation() + acceptAgreement() + customizeIntellijIdea() + waitWelcomeFrameAndClose() + } +} \ No newline at end of file diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/ide/IdeTestFixture.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/ide/IdeTestFixture.kt deleted file mode 100644 index 54da87157eb6..000000000000 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/ide/IdeTestFixture.kt +++ /dev/null @@ -1,83 +0,0 @@ -package com.intellij.testGuiFramework.launcher.ide - -import com.intellij.testGuiFramework.launcher.teamcity.TeamCityManager -import com.intellij.testGuiFramework.launcher.teamcity.TeamCityManager.PLATFORM_PREFIX -import java.util.* - -/** - * @author Sergey Karashevich - */ -class IdeTestFixture(val ide: Ide) { - - private var testList: MutableList = ArrayList() - private var configPath: String? = null - private var systemPath: String? = null - private var pluginList: MutableList = ArrayList() - private val ideRunArgs: IdeRunArgs = IdeRunArgs(ArrayList(), ArrayList(), ArrayList()) - - private var configureAfterInstallation = false - - - fun config(customConfigPath: String) { - configPath = customConfigPath - } - - fun system(customSystemPath: String) { - systemPath = customSystemPath - } - - fun jvmConfig(param: String) { - ideRunArgs.jvmParams.add(param) - } - - fun jvmConfig(params: List) { - ideRunArgs.jvmParams.addAll(params) - } - - fun jvmConfig(vararg params: String) { - ideRunArgs.jvmParams.addAll(params.toList()) - } - - fun debug(transport: String = "dt_socket", server: Boolean = true, suspend: Boolean = true, port: Int) { - if (!ideRunArgs.jvmParams.contains("-Xdebug")) ideRunArgs.jvmParams.add("-Xdebug") - ideRunArgs.jvmParams.forEach { jvmParameter -> if (jvmParameter.startsWith("-Xrunjdwp:")) ideRunArgs.jvmParams.remove(jvmParameter)} //remove all debug specific params - ideRunArgs.jvmParams.add("-Xrunjdwp:transport=$transport,server=${if (server) "y" else "n"},suspend=${if (suspend) "y" else "n"},address=$port") - } - - fun firstStart() { configureAfterInstallation = true } - - - fun installPlugin(pluginToInstall: String) { - installPlugins(listOf(pluginToInstall)) - } - - fun installPlugins(pluginsToInstall: List) { - pluginList.addAll(pluginsToInstall) - } - - fun test(testClassName: String) { - testList.add(testClassName) - } - - fun buildArgs(): IdeRunArgs { - if (pluginList.isNotEmpty()) { - val pluginsStr = pluginList.joinToString(", ") - ideRunArgs.ideParams.add("-DGUI_TEST_ADD_PLUGINS=$pluginsStr") - ideRunArgs.testClasses.clear() - ideRunArgs.testClasses.add("com.intellij.testGuiFramework.impl.PluginInstaller") - } else { - ideRunArgs.testClasses.addAll(testList) - } - if (configureAfterInstallation) ideRunArgs.ideParams.add("-DGUI_FIRST_START=true") - if (!configPath.isNullOrEmpty()) ideRunArgs.ideParams.add("-DCUSTOM_CONFIG_PATH=$configPath") - if (ide.ideType.platformPrefix.isNotEmpty()) ideRunArgs.ideParams.add("-Didea.platform.prefix=${ide.ideType.platformPrefix}") else "-Didea.platform.prefix=${PLATFORM_PREFIX}" - ideRunArgs.ideParams.add("-DGUI_TEST_DATA_DIR=${TeamCityManager.GUI_TEST_DATA_DIR}") - return ideRunArgs - } -} - -data class IdeRunArgs(val jvmParams: MutableList, val ideParams: MutableList, val testClasses: MutableList) { - override fun toString(): String { - return "IdeRunArgs(jvmParams=(${jvmParams.joinToString(", ")}), ideParams=(${ideParams.joinToString(", ")}), testClasses=${testClasses.joinToString(", ")})" - } -} diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/ide/IdeType.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/ide/IdeType.kt index 047541930f7e..a21ab5370000 100644 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/ide/IdeType.kt +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/ide/IdeType.kt @@ -3,13 +3,4 @@ package com.intellij.testGuiFramework.launcher.ide /** * @author Sergey Karashevich */ -enum class IdeType(val id: String, val buildTypeExtId: String, val platformPrefix: String, val ideJarName: String, val mainModule: String) { - IDEA_COMMUNITY(id = "IdeaIC", buildTypeExtId = "ijplatform_master_Idea_Installers", platformPrefix = "Idea", ideJarName = "idea.jar", mainModule = "community-main"), - IDEA_ULTIMATE(id = "IdeaIU", buildTypeExtId = "ijplatform_master_Idea_Installers", platformPrefix = "", ideJarName = "idea.jar", mainModule = "main"), - WEBSTORM(id = "WebStorm-EAP", buildTypeExtId = "bt3948", platformPrefix = "WebStorm", ideJarName = "webstorm.jar", mainModule = "main_WebStorm"), - PYCHARM(id = "null", buildTypeExtId = "null", platformPrefix = "Python", ideJarName = "pycharm.jar", mainModule = "main_pycharm"), - PYCHARM_COMMUNITY(id = "null", buildTypeExtId = "null", platformPrefix = "PyCharmCore", ideJarName = "pycharm.jar", mainModule = "main_pycharm_ce"), - PYCHARM_EDU(id = "null", buildTypeExtId = "null", platformPrefix = "PyCharmEdu", ideJarName = "pycharm.jar", mainModule = "main_pycharm_edu"), - ANDROID_STUDIO(id = "null", buildTypeExtId = "null", platformPrefix = "AndroidStudio", ideJarName = "idea.jar", mainModule = "main_AndroidStudio"), - PHPSTORM(id = "PhpStorm-EAP", buildTypeExtId = "bt1028s", platformPrefix = "PhpStorm", ideJarName = "phpstorm.jar", mainModule = "main_phpstorm"), -} +abstract class IdeType(val platformPrefix: String, val ideJarName: String, val mainModule: String) \ No newline at end of file diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/teamcity/TeamCityManager.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/teamcity/TeamCityManager.kt deleted file mode 100644 index 1ba3b8d962ed..000000000000 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/teamcity/TeamCityManager.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.intellij.testGuiFramework.launcher.teamcity - -/** - * @author Sergey Karashevich - */ -object TeamCityManager { - - val baseUrl: String = "http://buildserver.labs.intellij.net" - val guestAuth = "guestAuth/repository/download" - - val JDK_PATH: String = getSystemOrEnv("JDK_PATH") - val IDEA_PATH: String = getSystemOrEnv("IDEA_PATH") - val FEST_LIB_PATH: String = getSystemOrEnv("FEST_LIB_PATH") - val TEST_CLASSES_DIR: String = getSystemOrEnv("TEST_CLASSES_DIR") - val TEST_GUI_FRAMEWORK_PATH: String = getSystemOrEnv("TEST_GUI_FRAMEWORK_PATH") - val GUI_TEST_DATA_DIR: String = getSystemOrEnv("GUI_TEST_DATA_DIR") - val WORK_DIR_PATH: String = getSystemOrEnv("WORK_DIR_PATH") - val JUNIT_PATH: String = getSystemOrEnv("JUNIT_PATH") - val PLATFORM_PREFIX: String? = getSystemOrEnvNoSafe("PLATFORM_PREFIX") - val CUSTOM_CONFIG_PATH: String? = getSystemOrEnvNoSafe("CUSTOM_CONFIG_PATH") - - fun getSystemOrEnv(key: String): String { - try { - return if (System.getenv(key) != null) System.getenv(key) else System.getProperty(key) - } catch(e: IllegalStateException) { - throw IllegalStateException("${e.message}: caused by key: \"$key\"") - } - } - - fun getSystemOrEnvNoSafe(key: String): String? { - try { - return if (System.getenv(key) != null) System.getenv(key) else System.getProperty(key) - } catch(e: IllegalStateException) { - System.err.println("${e.message}: caused by key: \"$key\"") - return null - } - } -} \ No newline at end of file diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/zip/ZipUtils.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/zip/ZipUtils.kt deleted file mode 100644 index d76c74fb4d90..000000000000 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/launcher/zip/ZipUtils.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.intellij.testGuiFramework.launcher.zip - -import java.io.BufferedReader -import java.io.InputStreamReader -import java.util.stream.Collectors - -/** - * @author Sergey Karashevich - */ -object ZipUtils{ - - - fun unzip(zipArchive: String, destination: String) { -// ZipFile(zipArchive).extractAll(destination) - } - - fun installExe(exeFilePath: String, destination: String) { - //setup.exe /S /D=C:\test - val processBuilder = ProcessBuilder().inheritIO().command(exeFilePath, "/S", "/D=$destination") - val process = processBuilder.start() - process.waitFor() - if (process.exitValue() == 1) { - val errMessage = BufferedReader(InputStreamReader(process.errorStream)).lines().collect(Collectors.joining("\n")) - System.err.println(errMessage) - throw UnzipException("Error during installing .exe file from $exeFilePath to $destination") - } - } - - fun extractSit(sitArchive: String, destination: String) { - val processBuilder = ProcessBuilder().inheritIO().command("unzip", "-q", sitArchive, "-d", destination) - val process = processBuilder.start() - process.waitFor() - if (process.exitValue() == 1) { - val errMessage = BufferedReader(InputStreamReader(process.errorStream)).lines().collect(Collectors.joining("\n")) - System.err.println(errMessage) - throw UnzipException("Error during expanding .sit archive from $sitArchive to $destination") - } - } - - class UnzipException(message: String): Exception(message) -} \ No newline at end of file diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/script/ScriptAction.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/script/ScriptAction.kt deleted file mode 100644 index dd6e43ce2e12..000000000000 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/script/ScriptAction.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2000-2016 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.testGuiFramework.script - -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.application.ApplicationManager -import com.intellij.testGuiFramework.impl.GuiTestCase -import org.fest.swing.core.FastRobot - -/** - * @author Sergey Karashevich - */ -class ScriptAction(val scriptName: String) : AnAction("guiTest." + scriptName) { - - override fun actionPerformed(e: AnActionEvent?) { - - val myPackage = "com.intellij.testGuiFramework.tests" - val loadedClass = this.javaClass.classLoader.loadClass("${myPackage}.${scriptName}") - val guiTest = loadedClass.newInstance() as GuiTestCase - - //setting advanced robot - guiTest.setRobot(FastRobot()) - - val testsInClass = loadedClass.methods.filter { it.name.contains("test") } - ApplicationManager.getApplication().executeOnPooledThread { testsInClass.get(0).invoke(guiTest) } - } - -} diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/script/ScriptRunnerAction.kt b/platform/testGuiFramework/src/com/intellij/testGuiFramework/script/ScriptRunnerAction.kt deleted file mode 100644 index 45d5a852128a..000000000000 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/script/ScriptRunnerAction.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2000-2016 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.testGuiFramework.script - -import com.intellij.openapi.actionSystem.ActionManager -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.actionSystem.AnActionEvent -import java.io.File - -/** - * Created by jetbrains on 12/09/16. - */ -class ScriptRunnerAction() : AnAction(){ - - override fun actionPerformed(e: AnActionEvent?) { - val myPackage = "com.intellij.tests.gui.test" - val packagePath = myPackage.replace("", "/") - val testFolder = this.javaClass.classLoader.getResource(packagePath) - val testFiles = File(testFolder.file).listFiles().filter { !it.name.contains("$") } //remove inlined classes - val actionManager = ActionManager.getInstance() - - testFiles.forEach { - val testName = it.name.substring(0, it.name.indexOf('.')) - val genId = "guiTest." + testName - val script = com.intellij.testGuiFramework.script.ScriptAction(testName) - if (actionManager.getAction(genId) != null) - actionManager.unregisterAction(genId) - actionManager.registerAction(genId, script) - } - } - -} \ No newline at end of file diff --git a/platform/testGuiFramework/testSrc/com/intellij/testGuiFramework/tests/ControlTest.kt b/platform/testGuiFramework/testSrc/com/intellij/testGuiFramework/tests/ControlTest.kt deleted file mode 100644 index bccf069176f6..000000000000 --- a/platform/testGuiFramework/testSrc/com/intellij/testGuiFramework/tests/ControlTest.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2000-2017 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.testGuiFramework.tests - -import com.intellij.testGuiFramework.launcher.ide.Ide -import com.intellij.testGuiFramework.launcher.ide.IdeType -import com.intellij.testGuiFramework.remote.RemoteTestCase -import org.junit.Test - -/** - * @author Sergey Karashevich - */ -class ControlTest: RemoteTestCase() { - - @Test - fun testColorSchemeTest() { - - val ide = Ide(IdeType.IDEA_ULTIMATE, 0, 0) // if path is not specified than run current IntelliJ IDEA from compiled sources - -// startAndClose(ide, "localhost", 5009, "/Users/jetbrains/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/172.2300/IntelliJ IDEA 2017.2 EAP.app") { - startAndClose(ide, "localhost", 5009) { - runTest("com.intellij.testGuiFramework.tests.ColorSchemeTest#testFail") - } - } - -} \ No newline at end of file diff --git a/platform/testGuiFramework/testSrc/com/intellij/testGuiFramework/tests/community/CommandLineProjectTest.kt b/platform/testGuiFramework/testSrc/com/intellij/testGuiFramework/tests/community/CommandLineProjectTest.kt index 08220f1bd35e..20ae6fc2b598 100644 --- a/platform/testGuiFramework/testSrc/com/intellij/testGuiFramework/tests/community/CommandLineProjectTest.kt +++ b/platform/testGuiFramework/testSrc/com/intellij/testGuiFramework/tests/community/CommandLineProjectTest.kt @@ -21,11 +21,11 @@ import com.intellij.openapi.ui.MultiLineLabelUI import com.intellij.openapi.util.text.StringUtil import com.intellij.testGuiFramework.framework.RunWithIde import com.intellij.testGuiFramework.impl.GuiTestCase -import com.intellij.testGuiFramework.launcher.ide.IdeType +import com.intellij.testGuiFramework.launcher.ide.CommunityIde import org.junit.Assert.assertTrue import org.junit.Test -@RunWithIde(IdeType.IDEA_COMMUNITY) +@RunWithIde(CommunityIde::class) class CommandLineProjectTest: GuiTestCase() { val testProjectName = "test-cmd-template" diff --git a/platform/testGuiFramework/testSrc/com/intellij/testGuiFramework/tests/community/CommunityTestSuite.kt b/platform/testGuiFramework/testSrc/com/intellij/testGuiFramework/tests/community/CommunityTestSuite.kt index 45047ad53480..713d5b9dbce8 100644 --- a/platform/testGuiFramework/testSrc/com/intellij/testGuiFramework/tests/community/CommunityTestSuite.kt +++ b/platform/testGuiFramework/testSrc/com/intellij/testGuiFramework/tests/community/CommunityTestSuite.kt @@ -15,13 +15,16 @@ */ package com.intellij.testGuiFramework.tests.community +import com.intellij.testGuiFramework.framework.FirstStartWith import com.intellij.testGuiFramework.framework.GuiTestSuite import com.intellij.testGuiFramework.framework.RunWithIde -import com.intellij.testGuiFramework.launcher.ide.IdeType +import com.intellij.testGuiFramework.launcher.ide.CommunityIde +import com.intellij.testGuiFramework.launcher.ide.CommunityIdeFirstStart import org.junit.runner.RunWith import org.junit.runners.Suite @RunWith(GuiTestSuite::class) -@RunWithIde(IdeType.IDEA_COMMUNITY) +@RunWithIde(CommunityIde::class) +@FirstStartWith(CommunityIdeFirstStart::class) @Suite.SuiteClasses(CommandLineProjectTest::class) class CommunityTestSuite \ No newline at end of file diff --git a/platform/usageView/src/com/intellij/usages/impl/UsageContextPanelBase.java b/platform/usageView/src/com/intellij/usages/impl/UsageContextPanelBase.java index 8ec737a0bc87..35457f8bb636 100644 --- a/platform/usageView/src/com/intellij/usages/impl/UsageContextPanelBase.java +++ b/platform/usageView/src/com/intellij/usages/impl/UsageContextPanelBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -18,10 +18,10 @@ package com.intellij.usages.impl; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiDocumentManager; -import com.intellij.ui.IdeBorderFactory; import com.intellij.usageView.UsageInfo; import com.intellij.usages.UsageContextPanel; import com.intellij.usages.UsageViewPresentation; +import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -41,7 +41,7 @@ public abstract class UsageContextPanelBase extends JPanel implements UsageConte myProject = project; myPresentation = presentation; setLayout(new BorderLayout()); - setBorder(IdeBorderFactory.createEmptyBorder()); + setBorder(JBUI.Borders.empty()); } @NotNull diff --git a/platform/usageView/src/com/intellij/usages/impl/UsageViewImpl.java b/platform/usageView/src/com/intellij/usages/impl/UsageViewImpl.java index e7bf59a5ece3..b481623485bd 100644 --- a/platform/usageView/src/com/intellij/usages/impl/UsageViewImpl.java +++ b/platform/usageView/src/com/intellij/usages/impl/UsageViewImpl.java @@ -475,6 +475,10 @@ public class UsageViewImpl implements UsageView { if (eachPath == null) continue; treeState.invalidatePathBounds(eachPath); + Object node = eachPath.getLastPathComponent(); + if (node instanceof UsageNode) { + ((UsageNode)node).update(this, edtNodeChangedQueue); + } } myTree.repaint(visibleRect); } diff --git a/platform/usageView/src/com/intellij/usages/impl/UsageViewManagerImpl.java b/platform/usageView/src/com/intellij/usages/impl/UsageViewManagerImpl.java index c1608f799443..81ce351e64b4 100644 --- a/platform/usageView/src/com/intellij/usages/impl/UsageViewManagerImpl.java +++ b/platform/usageView/src/com/intellij/usages/impl/UsageViewManagerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -15,14 +15,12 @@ */ package com.intellij.usages.impl; -import com.intellij.find.FindBundle; import com.intellij.find.SearchInBackgroundOption; import com.intellij.injected.editor.VirtualFileWindow; import com.intellij.openapi.actionSystem.DataKey; import com.intellij.openapi.actionSystem.DataSink; import com.intellij.openapi.actionSystem.TypeSafeDataProvider; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ApplicationNamesInfo; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; @@ -167,7 +165,7 @@ public class UsageViewManagerImpl extends UsageViewManager { }); return scope[0]; } - return GlobalSearchScope.allScope(myProject); // by default do not warn of falling out of scope + return GlobalSearchScope.everythingScope(myProject); // by default do not warn of falling out of scope } @Override diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 2e62307f62e5..cea257e445ef 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -329,7 +329,7 @@ vcs.log.bek.sort.disabled.description=Disable IntelliSort vcs.log.linear.bek.sort=false vcs.log.linear.bek.sort.description=Third kind of IntelliSort that simplifies merges and tries to show history as linear vcs.log.recent.commits.count=1000 -vcs.log.recent.commits.count.description=Before full log is loaded (which can take some time), a number of recent commits is loaded to show quickly +vcs.log.recent.commits.count.description=Before full log is loaded (which can take some time), a number of recent commits is loaded to be shown quickly vcs.log.open.another.log.visible=false vcs.log.open.another.log.visible.description=An action that opens a new tab with log vcs.log.keep.up.to.date=true @@ -688,6 +688,9 @@ emmet.segments.limit=50 emmet.template.length.limit.kilobytes=15 command.line.execution.timeout=30 +raw.command.line.editor.dialog=false +raw.command.line.editor.dialog.description=Use dialog to edit command line + ide.settings.keymap.input.method.enabled=false ide.settings.keymap.input.method.enabled.description=Use input method instead of simple key event to enter shortcuts. @@ -902,6 +905,12 @@ cidr.debugger.value.maxChildren=50 cidr.debugger.value.maxChildren.description=Number of children of a composite variable or watch value shown by default.\n\ Increasing this value may lead to timeouts during evaluation. +cidr.navigation.gotoDeclaration.overrides.showUsages=true +cidr.navigation.gotoDeclaration.overrides.showUsages.description = \ + When enabled, invoking "Navigate Declaration" (Ctrl+B / Ctrl+MouseClick) on a function/method switches between its definition and \ + an associated declaration, if any.\n\ + Turn off to get back the IntelliJ IDEA default behavior, that is, when invoked on a function/method definition, show the usages popup \ + instead (just like it does for functions with no associated declaration). cidr.show.compiler.info=false cidr.show.clangtidy.info=false diff --git a/platform/util/src/com/intellij/util/EnvironmentUtil.java b/platform/util/src/com/intellij/util/EnvironmentUtil.java index 52a81aea9cfc..bff480b2f32e 100644 --- a/platform/util/src/com/intellij/util/EnvironmentUtil.java +++ b/platform/util/src/com/intellij/util/EnvironmentUtil.java @@ -22,7 +22,6 @@ import com.intellij.openapi.util.AtomicNotNullLazyValue; import com.intellij.openapi.util.NotNullLazyValue; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.util.concurrency.AppExecutorUtil; @@ -56,7 +55,9 @@ public class EnvironmentUtil { private static final Future> ourEnvGetter; static { - if (SystemInfo.isMac && "unlocked".equals(System.getProperty("__idea.mac.env.lock")) && Registry.is("idea.fix.mac.env")) { + if (SystemInfo.isMac && + "unlocked".equals(System.getProperty("__idea.mac.env.lock")) && + SystemProperties.getBooleanProperty("idea.fix.mac.env", true)) { ourEnvGetter = AppExecutorUtil.getAppExecutorService().submit(new Callable>() { @Override public Map call() throws Exception { diff --git a/platform/util/src/com/intellij/util/ImageLoader.java b/platform/util/src/com/intellij/util/ImageLoader.java index b240d367ca97..3e99f4ad3cb7 100644 --- a/platform/util/src/com/intellij/util/ImageLoader.java +++ b/platform/util/src/com/intellij/util/ImageLoader.java @@ -164,17 +164,20 @@ public class ImageLoader implements Serializable { float pixScale) { ImageDescList vars = new ImageDescList(); - if (retina || dark) { + + boolean ideSvgIconSupport = Registry.is("ide.svg.icon"); + + if (retina || dark || ideSvgIconSupport) { final String name = FileUtil.getNameWithoutExtension(file); final String ext = FileUtilRt.getExtension(file); pixScale = adjustScaleFactor(allowFloatScaling, pixScale); - if (Registry.is("ide.svg.icon") && dark) { + if (ideSvgIconSupport && dark) { vars.add(new ImageDesc(name + "_dark.svg", cls, pixScale, ImageDesc.Type.SVG)); } - if (Registry.is("ide.svg.icon")) { + if (ideSvgIconSupport) { vars.add(new ImageDesc(name + ".svg", cls, pixScale, ImageDesc.Type.SVG)); } diff --git a/platform/util/src/com/intellij/util/SVGLoader.java b/platform/util/src/com/intellij/util/SVGLoader.java index d2db20f493da..d7e7044755e0 100644 --- a/platform/util/src/com/intellij/util/SVGLoader.java +++ b/platform/util/src/com/intellij/util/SVGLoader.java @@ -32,6 +32,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URISyntaxException; import java.net.URL; +import java.util.ArrayList; import java.util.List; /** @@ -84,14 +85,19 @@ public class SVGLoader { } public static ViewBox fromString(String s) { - final List values = StringUtil.split(s, " "); + List values = new ArrayList(4); + for (String token : StringUtil.tokenize(s, ", ")) { + values.add(token); + } + if (values.size() == 4) { return new ViewBox(Float.parseFloat(values.get(0)), Float.parseFloat(values.get(1)), Float.parseFloat(values.get(2)), Float.parseFloat(values.get(3))); } - throw new IllegalArgumentException("String should be formatted like 'x y width height'"); + + throw new IllegalArgumentException("String should be formatted like 'x y width height' or 'x, y, width, height'"); } } diff --git a/platform/util/src/com/intellij/util/diff/FilesTooBigForDiffException.java b/platform/util/src/com/intellij/util/diff/FilesTooBigForDiffException.java index e535652fca11..25602640d04f 100644 --- a/platform/util/src/com/intellij/util/diff/FilesTooBigForDiffException.java +++ b/platform/util/src/com/intellij/util/diff/FilesTooBigForDiffException.java @@ -12,14 +12,7 @@ public class FilesTooBigForDiffException extends Exception { // Do not try to compare two lines by-word after this much fails. public static final int MAX_BAD_LINES = 3; - private final int myNumLines; - - public FilesTooBigForDiffException(int numLines) { + public FilesTooBigForDiffException() { super("Can not calculate diff. File is too big and there are too many changes."); - myNumLines = numLines; - } - - public int getNumLines() { - return myNumLines; } } diff --git a/platform/util/src/com/intellij/util/diff/MyersLCS.java b/platform/util/src/com/intellij/util/diff/MyersLCS.java index 11534b8e84ce..c0984074d0db 100644 --- a/platform/util/src/com/intellij/util/diff/MyersLCS.java +++ b/platform/util/src/com/intellij/util/diff/MyersLCS.java @@ -184,7 +184,7 @@ class MyersLCS { } else { //The difference is more than the given estimate - if (throwException) throw new FilesTooBigForDiffException(0); + if (throwException) throw new FilesTooBigForDiffException(); } } } diff --git a/platform/util/src/com/intellij/util/diff/PatienceIntLCS.java b/platform/util/src/com/intellij/util/diff/PatienceIntLCS.java index 9a26a304d017..0ee64c5e1513 100644 --- a/platform/util/src/com/intellij/util/diff/PatienceIntLCS.java +++ b/platform/util/src/com/intellij/util/diff/PatienceIntLCS.java @@ -165,6 +165,6 @@ class PatienceIntLCS { private void checkReduction(int count1, int count2) throws FilesTooBigForDiffException { if (count1 * 2 < myCount1) return; if (count2 * 2 < myCount2) return; - throw new FilesTooBigForDiffException(0); + throw new FilesTooBigForDiffException(); } } diff --git a/platform/util/src/com/intellij/util/io/storage/AbstractStorage.java b/platform/util/src/com/intellij/util/io/storage/AbstractStorage.java index 1b91fb327541..46ca0d1a4c35 100644 --- a/platform/util/src/com/intellij/util/io/storage/AbstractStorage.java +++ b/platform/util/src/com/intellij/util/io/storage/AbstractStorage.java @@ -22,6 +22,7 @@ package com.intellij.util.io.storage; import com.intellij.openapi.Disposable; import com.intellij.openapi.Forceable; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream; import com.intellij.openapi.util.io.ByteSequence; import com.intellij.openapi.util.io.FileUtil; @@ -111,7 +112,7 @@ public abstract class AbstractStorage implements Disposable, Forceable { catch (IOException e) { LOG.info(e.getMessage()); if (recordsTable != null) { - recordsTable.dispose(); + Disposer.dispose(recordsTable); } boolean deleted = deleteFiles(storageFilePath); @@ -169,8 +170,8 @@ public abstract class AbstractStorage implements Disposable, Forceable { } } - myDataTable.dispose(); - newDataTable.dispose(); + Disposer.dispose(myDataTable); + Disposer.dispose(newDataTable); if (!FileUtil.delete(oldDataFile)) { throw new IOException("Can't delete file: " + oldDataFile); @@ -330,8 +331,9 @@ public abstract class AbstractStorage implements Disposable, Forceable { @Override public void dispose() { synchronized (myLock) { - myRecordsTable.dispose(); - myDataTable.dispose(); + Disposer.dispose(myRecordsTable); + Disposer.dispose(myDataTable); + } } diff --git a/platform/util/src/com/intellij/util/ref/DebugReflectionUtil.java b/platform/util/src/com/intellij/util/ref/DebugReflectionUtil.java index 33952fcfa874..b2d1a98e550e 100644 --- a/platform/util/src/com/intellij/util/ref/DebugReflectionUtil.java +++ b/platform/util/src/com/intellij/util/ref/DebugReflectionUtil.java @@ -15,6 +15,7 @@ */ package com.intellij.util.ref; +import com.intellij.ReviseWhenPortedToJDK; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.UserDataHolderEx; @@ -90,6 +91,15 @@ public class DebugReflectionUtil { catch (NoClassDefFoundError e) { cached = EMPTY_FIELD_ARRAY; } + catch (@ReviseWhenPortedToJDK("9") RuntimeException e) { + // field.setAccessible() can now throw this exception when accessing unexported module + if (e.getClass().getName().equals("java.lang.reflect.InaccessibleObjectException")) { + cached = EMPTY_FIELD_ARRAY; + } + else { + throw e; + } + } allFields.put(aClass, cached); } diff --git a/platform/util/src/com/intellij/util/ui/UIUtil.java b/platform/util/src/com/intellij/util/ui/UIUtil.java index a8b7faabd49b..89749fcee0d2 100644 --- a/platform/util/src/com/intellij/util/ui/UIUtil.java +++ b/platform/util/src/com/intellij/util/ui/UIUtil.java @@ -2643,6 +2643,10 @@ public class UIUtil { } } + public static FontUIResource getFontWithFallback(@NotNull Font font) { + return getFontWithFallback(font.getFamily(), font.getStyle(), font.getSize()); + } + public static FontUIResource getFontWithFallback(@NotNull String familyName, @JdkConstants.FontStyle int style, int size) { Font fontWithFallback = new StyleContext().getFont(familyName, style, size); return fontWithFallback instanceof FontUIResource ? (FontUIResource)fontWithFallback : new FontUIResource(fontWithFallback); diff --git a/platform/util/testSrc/com/intellij/util/text/StringUtilTest.java b/platform/util/testSrc/com/intellij/util/text/StringUtilTest.java index 7a6669f1961e..cc3acb45e840 100644 --- a/platform/util/testSrc/com/intellij/util/text/StringUtilTest.java +++ b/platform/util/testSrc/com/intellij/util/text/StringUtilTest.java @@ -24,8 +24,8 @@ import com.intellij.xml.util.XmlStringUtil; import org.jdom.Verifier; import org.jetbrains.annotations.NotNull; import org.junit.Test; -import slowCheck.Generator; -import slowCheck.PropertyChecker; +import jetCheck.Generator; +import jetCheck.PropertyChecker; import java.nio.CharBuffer; import java.util.Arrays; @@ -186,7 +186,7 @@ public class StringUtilTest { @Test public void testNaturalCompareTransitivityProperty() { - PropertyChecker.forAll(Generator.listsOf(Generator.stringsOf("ab01()_# ")), l -> { + PropertyChecker.forAll(Generator.listsOf(Generator.stringsOf("ab01()_# "))).shouldHold(l -> { List sorted = ContainerUtil.sorted(l, StringUtil::naturalCompare); for (int i = 0; i < sorted.size(); i++) { for (int j = i + 1; j < sorted.size(); j++) { diff --git a/platform/util/testSrc/util-tests.iml b/platform/util/testSrc/util-tests.iml index dd828e3bab1a..7e77249edbf6 100644 --- a/platform/util/testSrc/util-tests.iml +++ b/platform/util/testSrc/util-tests.iml @@ -17,6 +17,6 @@ - + \ No newline at end of file diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/ui/SearchFieldAction.java b/platform/vcs-api/src/com/intellij/openapi/vcs/ui/SearchFieldAction.java index f77cc1a18b27..4bb6d1915bf2 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/ui/SearchFieldAction.java +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/ui/SearchFieldAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -19,7 +19,6 @@ import com.intellij.icons.AllIcons; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.actionSystem.ex.CustomComponentAction; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.SearchTextField; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; @@ -61,7 +60,7 @@ public abstract class SearchFieldAction extends AnAction implements CustomCompon } }; Border border = myField.getBorder(); - Border emptyBorder = IdeBorderFactory.createEmptyBorder(3, 0, 2, 0); + Border emptyBorder = JBUI.Borders.empty(3, 0, 2, 0); if (border instanceof CompoundBorder) { if (!UIUtil.isUnderDarcula()) { myField.setBorder(new CompoundBorder(emptyBorder, ((CompoundBorder)border).getInsideBorder())); diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/ui/TextFieldAction.java b/platform/vcs-api/src/com/intellij/openapi/vcs/ui/TextFieldAction.java index b189f699167d..5f5409cca464 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/ui/TextFieldAction.java +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/ui/TextFieldAction.java @@ -1,9 +1,12 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -18,7 +21,7 @@ import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.actionSystem.ex.CustomComponentAction; import com.intellij.openapi.util.SystemInfo; import com.intellij.ui.ClickListener; -import com.intellij.ui.IdeBorderFactory; +import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -75,10 +78,10 @@ public abstract class TextFieldAction extends AnAction implements CustomComponen originalBorder = myField.getBorder(); } - panel.setBorder(new CompoundBorder(IdeBorderFactory.createEmptyBorder(4, 0, 4, 0), originalBorder)); + panel.setBorder(new CompoundBorder(JBUI.Borders.empty(4, 0, 4, 0), originalBorder)); myField.setOpaque(true); - myField.setBorder(IdeBorderFactory.createEmptyBorder(0, 5, 0, 5)); + myField.setBorder(JBUI.Borders.empty(0, 5, 0, 5)); new ClickListener() { @Override diff --git a/platform/vcs-api/src/com/intellij/vcsUtil/VcsUtil.java b/platform/vcs-api/src/com/intellij/vcsUtil/VcsUtil.java index 9087f7a81058..e5fbebf92cdd 100644 --- a/platform/vcs-api/src/com/intellij/vcsUtil/VcsUtil.java +++ b/platform/vcs-api/src/com/intellij/vcsUtil/VcsUtil.java @@ -35,6 +35,8 @@ import com.intellij.openapi.vcs.actions.VcsContextFactory; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vcs.changes.ContentRevision; import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vcs.history.ShortVcsRevisionNumber; +import com.intellij.openapi.vcs.history.VcsRevisionNumber; import com.intellij.openapi.vcs.roots.VcsRootDetector; import com.intellij.openapi.vfs.*; import com.intellij.openapi.vfs.newvfs.RefreshQueue; @@ -530,6 +532,12 @@ public class VcsUtil { return sb.toString(); } + public static String getShortRevisionString(@NotNull VcsRevisionNumber revision) { + return revision instanceof ShortVcsRevisionNumber + ? ((ShortVcsRevisionNumber)revision).toShortString() + : revision.asString(); + } + public static VirtualFile[] paths2VFiles(String[] paths) { VirtualFile[] files = new VirtualFile[paths.length]; for (int i = 0; i < paths.length; i++) { diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/apply/GenericPatchApplier.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/apply/GenericPatchApplier.java index 966d5e1d859b..5b8a2f921054 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/apply/GenericPatchApplier.java +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/apply/GenericPatchApplier.java @@ -901,6 +901,7 @@ public class GenericPatchApplier { final boolean emptyFile = myLines.isEmpty() || myLines.size() == 1 && myLines.get(0).trim().length() == 0; if (emptyFile) { myNotBound.add(splitHunk); + processAppliedInfoForUnApplied(splitHunk); } return emptyFile; } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/CommittedChangeListRenderer.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/CommittedChangeListRenderer.java index 39feebf48b93..bb32609c062b 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/CommittedChangeListRenderer.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/CommittedChangeListRenderer.java @@ -16,7 +16,6 @@ package com.intellij.openapi.vcs.changes.committed; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.AbstractVcs; import com.intellij.openapi.vcs.CachingCommittedChangesProvider; @@ -56,8 +55,9 @@ public class CommittedChangeListRenderer extends ColoredTreeCellRenderer { return DateFormatUtil.formatPrettyDateTime(date); } - public static Pair getDescriptionOfChangeList(final String text) { - return new Pair<>(text.replaceAll("\n", " // "), text.contains("\n")); + @NotNull + public static String getDescriptionOfChangeList(@NotNull String text) { + return text.replaceAll("\n", " // "); } public static String truncateDescription(final String initDescription, final FontMetrics fontMetrics, int maxWidth) { @@ -100,9 +100,7 @@ public class CommittedChangeListRenderer extends ColoredTreeCellRenderer { } int dateCommitterSize = myDateWidth + boldMetrics.stringWidth(changeList.getCommitterName()); - final Pair descriptionInfo = getDescriptionOfChangeList(changeList.getName().trim()); - boolean truncated = descriptionInfo.getSecond().booleanValue(); - String description = descriptionInfo.getFirst(); + String description = getDescriptionOfChangeList(changeList.getName().trim()); for (CommittedChangeListDecorator decorator : myDecorators) { final Icon icon = decorator.decorate(changeList); @@ -145,14 +143,14 @@ public class CommittedChangeListRenderer extends ColoredTreeCellRenderer { append(branch, SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES); } - if (description.isEmpty() && !truncated) { + if (description.isEmpty()) { append(VcsBundle.message("committed.changes.empty.comment"), SimpleTextAttributes.GRAYED_ATTRIBUTES); appendTextPadding(descMaxWidth); } else if (descMaxWidth < 0) { myRenderer.appendTextWithLinks(description); } - else if (descWidth < descMaxWidth && !truncated) { + else if (descWidth < descMaxWidth) { myRenderer.appendTextWithLinks(description); appendTextPadding(descMaxWidth); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/LazyConfigurable.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/LazyConfigurable.java deleted file mode 100644 index d0f46ccec353..000000000000 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/LazyConfigurable.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2000-2010 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.vcs.configurable; - -import com.intellij.openapi.options.Configurable; -import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.options.UnnamedConfigurable; -import com.intellij.openapi.util.Getter; - -import javax.swing.*; -import java.awt.*; - -public class LazyConfigurable implements UnnamedConfigurable { - private final Getter myGetter; - private Configurable myDelegate; - private final JPanel myParentPanel; - - public LazyConfigurable(Getter getter, JPanel parentPanel) { - myGetter = getter; - myParentPanel = parentPanel; - myParentPanel.setLayout(new BorderLayout()); - myParentPanel.removeAll(); - } - - @Override - public void apply() throws ConfigurationException { - if (myDelegate != null) { - myDelegate.apply(); - } - } - @Override - public JComponent createComponent() { - if (myDelegate == null) { - myDelegate = myGetter.get(); - myParentPanel.add(myDelegate.createComponent(), BorderLayout.CENTER); - } - return myParentPanel; - } - @Override - public boolean isModified() { - if (myDelegate != null) { - return myDelegate.isModified(); - } - return false; - } - @Override - public void reset() { - if (myDelegate != null) { - myDelegate.reset(); - } - } - @Override - public void disposeUIResources() { - if (myDelegate != null) { - myDelegate.disposeUIResources(); - } - } - - public JPanel getParentPanel() { - return myParentPanel; - } -} diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsConfigurationsDialog.form b/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsConfigurationsDialog.form deleted file mode 100644 index 5d66a9d65dd4..000000000000 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsConfigurationsDialog.form +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsConfigurationsDialog.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsConfigurationsDialog.java deleted file mode 100644 index 75691918742a..000000000000 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsConfigurationsDialog.java +++ /dev/null @@ -1,208 +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 com.intellij.openapi.vcs.configurable; - -import com.intellij.openapi.options.Configurable; -import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.options.UnnamedConfigurable; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.vcs.ProjectLevelVcsManager; -import com.intellij.openapi.vcs.VcsBundle; -import com.intellij.openapi.vcs.impl.VcsDescriptor; -import com.intellij.openapi.vcs.impl.projectlevelman.AllVcses; -import com.intellij.ui.ColoredListCellRenderer; -import com.intellij.ui.SimpleTextAttributes; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; -import java.awt.*; -import java.util.HashMap; -import java.util.Map; - -public class VcsConfigurationsDialog extends DialogWrapper{ - private JList myVcses; - private JPanel myVcsConfigurationPanel; - private final Project myProject; - private JPanel myVersionControlConfigurationsPanel; - private static final String NONE = VcsBundle.message("none.vcs.presentation"); - - private final Map myVcsNameToConfigurableMap = new HashMap<>(); - private static final ColoredListCellRenderer VCS_LIST_RENDERER = new ColoredListCellRenderer() { - protected void customizeCellRenderer(@NotNull JList list, Object value, int index, boolean selected, boolean hasFocus) { - String name = value == null ? NONE : ((VcsDescriptor) value).getDisplayName(); - append(name, new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, list.getForeground())); - } - }; - private JScrollPane myVcsesScrollPane; - - @Nullable - private final JComboBox myVcsesToUpdate; - - public VcsConfigurationsDialog(Project project, @Nullable JComboBox vcses, VcsDescriptor selectedVcs) { - super(project, false); - myProject = project; - - VcsDescriptor[] abstractVcses = collectAvailableNames(); - initList(abstractVcses); - - initVcsConfigurable(abstractVcses); - - updateConfiguration(); - myVcsesToUpdate = vcses; - for (String vcsName : myVcsNameToConfigurableMap.keySet()) { - UnnamedConfigurable configurable = myVcsNameToConfigurableMap.get(vcsName); - if (configurable != null && configurable.isModified()) configurable.reset(); - } - updateConfiguration(); - if (selectedVcs != null){ - myVcses.setSelectedValue(selectedVcs, true); - } - init(); - setTitle(VcsBundle.message("dialog.title.version.control.configurations")); - } - - private void updateConfiguration() { - int selectedIndex = myVcses.getSelectionModel().getMinSelectionIndex(); - final VcsDescriptor currentVcs; - currentVcs = selectedIndex >= 0 ? (VcsDescriptor)(myVcses.getModel()).getElementAt(selectedIndex) : null; - String currentName = currentVcs == null ? NONE : currentVcs.getName(); - if (currentVcs != null) { - final UnnamedConfigurable unnamedConfigurable = myVcsNameToConfigurableMap.get(currentName); - unnamedConfigurable.createComponent(); - unnamedConfigurable.reset(); - } - final CardLayout cardLayout = (CardLayout)myVcsConfigurationPanel.getLayout(); - cardLayout.show(myVcsConfigurationPanel, currentName); - } - - private void initVcsConfigurable(VcsDescriptor[] vcses) { - myVcsConfigurationPanel.setLayout(new CardLayout()); - MyNullConfigurable nullConfigurable = new MyNullConfigurable(); - myVcsNameToConfigurableMap.put(NONE, nullConfigurable); - myVcsConfigurationPanel.add(nullConfigurable.createComponent(), NONE); - for (VcsDescriptor vcs : vcses) { - addConfigurationPanelFor(vcs); - } - } - - private void addConfigurationPanelFor(final VcsDescriptor vcs) { - String name = vcs.getName(); - final JPanel parentPanel = new JPanel(); - final LazyConfigurable lazyConfigurable = new LazyConfigurable( - () -> AllVcses.getInstance(myProject).getByName(vcs.getName()).getConfigurable(), parentPanel); - myVcsNameToConfigurableMap.put(name, lazyConfigurable); - myVcsConfigurationPanel.add(parentPanel, name); - } - - private void initList(VcsDescriptor[] names) { - DefaultListModel model = new DefaultListModel(); - - for (VcsDescriptor name : names) { - model.addElement(name); - } - - myVcses.setModel(model); - myVcses.getSelectionModel().addListSelectionListener(new ListSelectionListener() { - public void valueChanged(ListSelectionEvent e) { - updateConfiguration(); - } - }); - - myVcses.setCellRenderer(VCS_LIST_RENDERER); - - myVcsesScrollPane.setMinimumSize(myVcsesScrollPane.getPreferredSize()); - } - - - private VcsDescriptor[] collectAvailableNames() { - return ProjectLevelVcsManager.getInstance(myProject).getAllVcss(); - } - - protected JComponent createCenterPanel() { - return myVersionControlConfigurationsPanel; - } - - protected void doOKAction() { - for (String vcsName : myVcsNameToConfigurableMap.keySet()) { - UnnamedConfigurable configurable = myVcsNameToConfigurableMap.get(vcsName); - if (configurable != null && configurable.isModified()) { - try { - configurable.apply(); - } - catch (ConfigurationException e) { - Messages.showErrorDialog(VcsBundle.message("message.text.unable.to.save.settings", e.getMessage()), - VcsBundle.message("message.title.unable.to.save.settings")); - } - } - } - - final JComboBox vcsesToUpdate = myVcsesToUpdate; - if (vcsesToUpdate != null) { - final VcsDescriptor wrapper = (VcsDescriptor) myVcses.getSelectedValue(); - vcsesToUpdate.setSelectedItem(wrapper); - final ComboBoxModel model = vcsesToUpdate.getModel(); - for(int i = 0; i < model.getSize(); i++){ - final Object vcsWrapper = model.getElementAt(i); - if (vcsWrapper instanceof VcsDescriptor){ - final VcsDescriptor defaultVcsWrapper = (VcsDescriptor) vcsWrapper; - if (defaultVcsWrapper.equals(wrapper)){ - vcsesToUpdate.setSelectedIndex(i); - break; - } - } - } - } - - super.doOKAction(); - } - - protected void dispose() { - myVcses.setCellRenderer(new DefaultListCellRenderer()); - super.dispose(); - } - - private static class MyNullConfigurable implements Configurable { - public String getDisplayName() { - return NONE; - } - - public String getHelpTopic() { - return "project.propVCSSupport"; - } - - public JComponent createComponent() { - return new JPanel(); - } - - public boolean isModified() { - return false; - } - - public void apply() throws ConfigurationException { - } - - public void reset() { - } - - public void disposeUIResources() { - } - } -} diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsDirectoryConfigurationPanel.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsDirectoryConfigurationPanel.java index df7284cdcd47..d94c4eb7808e 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsDirectoryConfigurationPanel.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsDirectoryConfigurationPanel.java @@ -21,6 +21,7 @@ import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ComboBox; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.*; @@ -45,8 +46,6 @@ import javax.swing.*; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.io.File; @@ -68,7 +67,7 @@ public class VcsDirectoryConfigurationPanel extends JPanel implements Configurab private final String myProjectMessage; private final ProjectLevelVcsManager myVcsManager; private final TableView myDirectoryMappingTable; - private final ComboboxWithBrowseButton myVcsComboBox = new ComboboxWithBrowseButton(); + private final ComboBox myVcsComboBox = new ComboBox<>(); private final List myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private final MyDirectoryRenderer myDirectoryRenderer; @@ -248,14 +247,14 @@ public class VcsDirectoryConfigurationPanel extends JPanel implements Configurab return new AbstractTableCellEditor() { @Override public Object getCellEditorValue() { - final VcsDescriptor selectedVcs = (VcsDescriptor)myVcsComboBox.getComboBox().getSelectedItem(); + final VcsDescriptor selectedVcs = (VcsDescriptor)myVcsComboBox.getSelectedItem(); return ((selectedVcs == null) || selectedVcs.isNone()) ? "" : selectedVcs.getName(); } @Override public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { String vcsName = (String)value; - myVcsComboBox.getComboBox().setSelectedItem(myAllVcss.get(vcsName)); + myVcsComboBox.setSelectedItem(myAllVcss.get(vcsName)); return myVcsComboBox; } }; @@ -319,9 +318,8 @@ public class VcsDirectoryConfigurationPanel extends JPanel implements Configurab }; initializeModel(); - final JComboBox comboBox = myVcsComboBox.getComboBox(); - comboBox.setModel(buildVcsWrappersModel(myProject)); - comboBox.addItemListener(new ItemListener() { + myVcsComboBox.setModel(buildVcsWrappersModel(myProject)); + myVcsComboBox.addItemListener(new ItemListener() { @Override public void itemStateChanged(final ItemEvent e) { if (myDirectoryMappingTable.isEditing()) { @@ -329,13 +327,6 @@ public class VcsDirectoryConfigurationPanel extends JPanel implements Configurab } } }); - myVcsComboBox.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - final VcsDescriptor vcsWrapper = ((VcsDescriptor)comboBox.getSelectedItem()); - new VcsConfigurationsDialog(project, comboBox, vcsWrapper).show(); - } - }); myDirectoryMappingTable.setRowHeight(myVcsComboBox.getPreferredSize().height); if (myIsDisabled) { @@ -393,12 +384,13 @@ public class VcsDirectoryConfigurationPanel extends JPanel implements Configurab (mapping.isDefaultMapping() ? checker.isRoot(myProject.getBasePath()) : checker.isRoot(mapping.getDirectory())); } - public static DefaultComboBoxModel buildVcsWrappersModel(final Project project) { + @NotNull + public static DefaultComboBoxModel buildVcsWrappersModel(@NotNull Project project) { final VcsDescriptor[] vcsDescriptors = ProjectLevelVcsManager.getInstance(project).getAllVcss(); final VcsDescriptor[] result = new VcsDescriptor[vcsDescriptors.length + 1]; result[0] = VcsDescriptor.createFictive(); System.arraycopy(vcsDescriptors, 0, result, 1, vcsDescriptors.length); - return new DefaultComboBoxModel(result); + return new DefaultComboBoxModel<>(result); } private void addMapping() { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsMappingConfigurationDialog.form b/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsMappingConfigurationDialog.form index 83ca9d1b5efd..4ffb98b34350 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsMappingConfigurationDialog.form +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsMappingConfigurationDialog.form @@ -18,7 +18,7 @@ - + @@ -30,7 +30,7 @@ - + diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsMappingConfigurationDialog.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsMappingConfigurationDialog.java index 048250ee9d8a..b4d026212284 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsMappingConfigurationDialog.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/configurable/VcsMappingConfigurationDialog.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -17,36 +17,39 @@ package com.intellij.openapi.vcs.configurable; import com.intellij.openapi.fileChooser.FileChooserDescriptor; -import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.options.UnnamedConfigurable; import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.*; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vcs.*; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vcs.VcsDirectoryMapping; import com.intellij.openapi.vcs.impl.DefaultVcsRootPolicy; import com.intellij.openapi.vcs.impl.VcsDescriptor; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.components.JBLabel; -import com.intellij.util.continuation.ModalityIgnorantBackgroundableTask; -import com.intellij.xml.util.XmlStringUtil; import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.awt.*; -import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.util.HashMap; import java.util.Map; -/** - * @author yole - */ +import static com.google.common.collect.Maps.uniqueIndex; +import static com.intellij.openapi.fileChooser.FileChooserDescriptorFactory.createSingleFolderDescriptor; +import static com.intellij.openapi.util.io.FileUtil.toSystemDependentName; +import static com.intellij.openapi.util.io.FileUtil.toSystemIndependentName; +import static com.intellij.openapi.vcs.configurable.VcsDirectoryConfigurationPanel.buildVcsWrappersModel; +import static com.intellij.util.ObjectUtils.notNull; +import static com.intellij.util.containers.ContainerUtil.list; +import static com.intellij.util.containers.UtilKt.getIfSingle; +import static com.intellij.xml.util.XmlStringUtil.wrapInHtml; + public class VcsMappingConfigurationDialog extends DialogWrapper { - private final Project myProject; - private JComboBox myVCSComboBox; + @NotNull private final Project myProject; + private ComboBox myVCSComboBox; private TextFieldWithBrowseButton myDirectoryTextField; private JPanel myPanel; private JPanel myVcsConfigurablePlaceholder; @@ -57,29 +60,21 @@ public class VcsMappingConfigurationDialog extends DialogWrapper { private VcsDirectoryMapping myMappingCopy; private JComponent myVcsConfigurableComponent; private ProjectLevelVcsManager myVcsManager; - private final Map myVcses; + @NotNull private final Map myVcses; - public VcsMappingConfigurationDialog(final Project project, final String title) { + public VcsMappingConfigurationDialog(@NotNull Project project, String title) { super(project, false); myProject = project; myVcsManager = ProjectLevelVcsManager.getInstance(myProject); - final VcsDescriptor[] vcsDescriptors = myVcsManager.getAllVcss(); - myVcses = new HashMap<>(); - for (VcsDescriptor vcsDescriptor : vcsDescriptors) { - myVcses.put(vcsDescriptor.getName(), vcsDescriptor); - } - myVCSComboBox.setModel(VcsDirectoryConfigurationPanel.buildVcsWrappersModel(project)); - myDirectoryTextField.addActionListener(new MyBrowseFolderListener("Select Directory", "Select directory to map to a VCS", - myDirectoryTextField, project, - FileChooserDescriptorFactory.createSingleFolderDescriptor())); + myVcses = uniqueIndex(list(myVcsManager.getAllVcss()), VcsDescriptor::getName); + myVCSComboBox.setModel(buildVcsWrappersModel(project)); + myDirectoryTextField.addActionListener( + new MyBrowseFolderListener("Select Directory", "Select directory to map to a VCS", myDirectoryTextField, project, + createSingleFolderDescriptor())); myMappingCopy = new VcsDirectoryMapping("", ""); setTitle(title); init(); - myVCSComboBox.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - updateVcsConfigurable(); - } - }); + myVCSComboBox.addActionListener(e -> updateVcsConfigurable()); } protected JComponent createCenterPanel() { @@ -89,12 +84,8 @@ public class VcsMappingConfigurationDialog extends DialogWrapper { public void setMapping(@NotNull VcsDirectoryMapping mapping) { myMappingCopy = new VcsDirectoryMapping(mapping.getDirectory(), mapping.getVcs(), mapping.getRootSettings()); myProjectRadioButton.setSelected(myMappingCopy.isDefaultMapping()); - myDirectoryRadioButton.setSelected(! myProjectRadioButton.isSelected()); - if (myMappingCopy.isDefaultMapping()) { - myDirectoryTextField.setText(""); - } else { - myDirectoryTextField.setText(FileUtil.toSystemDependentName(mapping.getDirectory())); - } + myDirectoryRadioButton.setSelected(!myProjectRadioButton.isSelected()); + myDirectoryTextField.setText(myMappingCopy.isDefaultMapping() ? "" : toSystemDependentName(mapping.getDirectory())); myVCSComboBox.setSelectedItem(myVcses.get(mapping.getVcs())); updateVcsConfigurable(); @@ -107,7 +98,7 @@ public class VcsMappingConfigurationDialog extends DialogWrapper { public VcsDirectoryMapping getMapping() { VcsDescriptor wrapper = (VcsDescriptor) myVCSComboBox.getSelectedItem(); String vcs = wrapper == null || wrapper.isNone() ? "" : wrapper.getName(); - String directory = myProjectRadioButton.isSelected() ? "" : FileUtil.toSystemIndependentName(myDirectoryTextField.getText()); + String directory = myProjectRadioButton.isSelected() ? "" : toSystemIndependentName(myDirectoryTextField.getText()); return new VcsDirectoryMapping(directory, vcs, myMappingCopy.getRootSettings()); } @@ -118,13 +109,13 @@ public class VcsMappingConfigurationDialog extends DialogWrapper { myVcsConfigurable = null; } VcsDescriptor wrapper = (VcsDescriptor) myVCSComboBox.getSelectedItem(); - if (wrapper != null && (! wrapper.isNone())) { - final AbstractVcs vcs = myVcsManager.findVcsByName(wrapper.getName()); + if (wrapper != null && !wrapper.isNone()) { + AbstractVcs vcs = myVcsManager.findVcsByName(wrapper.getName()); if (vcs != null) { UnnamedConfigurable configurable = vcs.getRootConfigurable(myMappingCopy); if (configurable != null) { myVcsConfigurable = configurable; - myVcsConfigurableComponent = myVcsConfigurable.createComponent(); + myVcsConfigurableComponent = notNull(myVcsConfigurable.createComponent()); myVcsConfigurablePlaceholder.add(myVcsConfigurableComponent, BorderLayout.CENTER); } } @@ -132,12 +123,6 @@ public class VcsMappingConfigurationDialog extends DialogWrapper { pack(); } - @NotNull - @Override - protected Action[] createLeftSideActions() { - return new Action[] { new ConfigureVcsAction() }; - } - protected void doOKAction() { if (myVcsConfigurable != null) { try { @@ -156,24 +141,22 @@ public class VcsMappingConfigurationDialog extends DialogWrapper { myDirectoryRadioButton = new JRadioButton(); bg.add(myProjectRadioButton); bg.add(myDirectoryRadioButton); - final ActionListener al = new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - myDirectoryTextField.setEnabled(myDirectoryRadioButton.isSelected()); - } - }; - myProjectRadioButton.addActionListener(al); - myDirectoryRadioButton.addActionListener(al); + ActionListener listener = e -> myDirectoryTextField.setEnabled(myDirectoryRadioButton.isSelected()); + myProjectRadioButton.addActionListener(listener); + myDirectoryRadioButton.addActionListener(listener); myDirectoryRadioButton.setSelected(true); } public void initProjectMessage() { - myProjectButtonComment.setText(XmlStringUtil.wrapInHtml(DefaultVcsRootPolicy.getInstance(myProject).getProjectConfigurationMessage(myProject))); + myProjectButtonComment.setText(wrapInHtml(DefaultVcsRootPolicy.getInstance(myProject).getProjectConfigurationMessage(myProject))); } private class MyBrowseFolderListener extends ComponentWithBrowseButton.BrowseFolderActionListener { - public MyBrowseFolderListener(String title, String description, TextFieldWithBrowseButton textField, Project project, + public MyBrowseFolderListener(String title, + String description, + TextFieldWithBrowseButton textField, + Project project, FileChooserDescriptor fileChooserDescriptor) { super(title, description, textField, project, fileChooserDescriptor, TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT); } @@ -182,9 +165,9 @@ public class VcsMappingConfigurationDialog extends DialogWrapper { protected VirtualFile getInitialFile() { // suggest project base dir only if nothing is typed in the component. String text = getComponentText(); - if(text.length() == 0) { + if (text.isEmpty()) { VirtualFile file = myProject.getBaseDir(); - if(file != null) { + if (file != null) { return file; } } @@ -195,54 +178,24 @@ public class VcsMappingConfigurationDialog extends DialogWrapper { protected void onFileChosen(@NotNull final VirtualFile chosenFile) { String oldText = myDirectoryTextField.getText(); super.onFileChosen(chosenFile); - final VcsDescriptor wrapper = (VcsDescriptor) myVCSComboBox.getSelectedItem(); - if (oldText.length() == 0 && (wrapper == null || wrapper.isNone())) { - final ModalityIgnorantBackgroundableTask task = - new ModalityIgnorantBackgroundableTask(myProject, "Looking for VCS administrative area", false) { - VcsDescriptor probableVcs = null; + VcsDescriptor wrapper = (VcsDescriptor)myVCSComboBox.getSelectedItem(); + if (oldText.isEmpty() && (wrapper == null || wrapper.isNone())) { + new Task.Backgroundable(myProject, "Looking for VCS Administrative Area", false) { + private VcsDescriptor probableVcs = null; - @Override - protected void doInAwtIfFail(@NotNull Exception e) { - } + @Override + public void run(@NotNull ProgressIndicator indicator) { + probableVcs = getIfSingle(myVcses.values().stream().filter(descriptor -> descriptor.probablyUnderVcs(chosenFile))); + } - @Override - protected void doInAwtIfCancel() { + @Override + public void onSuccess() { + if (probableVcs != null) { + myVCSComboBox.setSelectedItem(probableVcs); } - - @Override - protected void doInAwtIfSuccess() { - if (probableVcs != null) { - // todo none - myVCSComboBox.setSelectedItem(probableVcs); - } - } - - @Override - protected void runImpl(@NotNull ProgressIndicator indicator) { - for (VcsDescriptor vcs : myVcses.values()) { - if (vcs.probablyUnderVcs(chosenFile)) { - if (probableVcs != null) { - probableVcs = null; - break; - } - probableVcs = vcs; - } - } - } - }; - ProgressManager.getInstance().run(task); + } + }.queue(); } } } - - private class ConfigureVcsAction extends AbstractAction { - public ConfigureVcsAction() { - super(VcsBundle.message("button.configure")); - } - - public void actionPerformed(ActionEvent e) { - VcsDescriptor wrapper = (VcsDescriptor) myVCSComboBox.getSelectedItem(); - new VcsConfigurationsDialog(myProject, null, wrapper).show(); - } - } } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/history/FileHistoryPanelImpl.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/history/FileHistoryPanelImpl.java index a437986e8c60..4364fe57167b 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/history/FileHistoryPanelImpl.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/history/FileHistoryPanelImpl.java @@ -64,6 +64,7 @@ import com.intellij.util.text.DateFormatUtil; import com.intellij.util.ui.ColumnInfo; import com.intellij.util.ui.StatusText; import com.intellij.util.ui.UIUtil; +import com.intellij.vcsUtil.VcsUtil; import org.jetbrains.annotations.CalledInAwt; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -246,7 +247,7 @@ public class FileHistoryPanelImpl extends PanelWithActionsAndCloseButton impleme public static String getPresentableText(@NotNull VcsFileRevision revision, boolean withMessage) { // implementation reflected by com.intellij.vcs.log.ui.frame.VcsLogGraphTable.getPresentableText() StringBuilder sb = new StringBuilder(); - sb.append(FileHistoryPanelImpl.RevisionColumnInfo.toString(revision.getRevisionNumber())).append(" "); + sb.append(VcsUtil.getShortRevisionString(revision.getRevisionNumber())).append(" "); sb.append(revision.getAuthor()); long time = revision.getRevisionDate().getTime(); sb.append(" on ").append(DateFormatUtil.formatDate(time)).append(" at ").append(DateFormatUtil.formatTime(time)); @@ -668,19 +669,12 @@ public class FileHistoryPanelImpl extends PanelWithActionsAndCloseButton impleme myRenderer = new ColoredTableCellRenderer() { protected void customizeCellRenderer(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) { setOpaque(selected); - append(RevisionColumnInfo.toString((VcsRevisionNumber)value)); + append(VcsUtil.getShortRevisionString((VcsRevisionNumber)value)); SpeedSearchUtil.applySpeedSearchHighlighting(table, this, false, selected); } }; } - @NotNull - private static String toString(@NotNull VcsRevisionNumber number) { - return number instanceof ShortVcsRevisionNumber - ? ((ShortVcsRevisionNumber)number).toShortString() - : number.asString(); - } - @Nullable @Override public VcsRevisionNumber valueOf(VcsFileRevision revision) { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/history/FileHistorySessionPartner.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/history/FileHistorySessionPartner.java index ab5ed896ea6d..2fc705f5b4ee 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/history/FileHistorySessionPartner.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/history/FileHistorySessionPartner.java @@ -31,6 +31,7 @@ import com.intellij.ui.content.ContentManager; import com.intellij.util.BufferedListConsumer; import com.intellij.util.Consumer; import com.intellij.util.ContentUtilEx; +import com.intellij.vcsUtil.VcsUtil; import org.jetbrains.annotations.CalledInAwt; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -159,14 +160,7 @@ public class FileHistorySessionPartner implements VcsAppendableHistorySessionPar if (!selectedExistingContent) { String tabName = myPath.getName(); if (myStartingRevisionNumber != null) { - tabName += " ("; - if (myStartingRevisionNumber instanceof ShortVcsRevisionNumber) { - tabName += ((ShortVcsRevisionNumber)myStartingRevisionNumber).toShortString(); - } - else { - tabName += myStartingRevisionNumber.asString(); - } - tabName += ")"; + tabName += " (" + VcsUtil.getShortRevisionString(myStartingRevisionNumber) + ")"; } ContentUtilEx.addTabbedContent(manager, myFileHistoryPanel, "History", tabName, true); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/history/impl/VcsSelectionHistoryDialog.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/history/impl/VcsSelectionHistoryDialog.java index f1cc469fcb26..26eb119bfc69 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/history/impl/VcsSelectionHistoryDialog.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/history/impl/VcsSelectionHistoryDialog.java @@ -292,12 +292,10 @@ public class VcsSelectionHistoryDialog extends FrameWrapper implements DataProvi if (data.isLoading()) { VcsFileRevision revision = data.getCurrentLoadingRevision(); - if (revision != null) { - myStatusLabel.setText("Loading revision " + revision.getRevisionNumber() + ""); - } - else { - myStatusLabel.setText("Loading..."); - } + String loadingString = revision != null + ? String.format("Loading revision %s...", VcsUtil.getShortRevisionString(revision.getRevisionNumber())) + : "Loading..."; + myStatusLabel.setText(String.format("%s (%s/%s)", loadingString, data.myBlocks.size(), myRevisions.size())); myStatusSpinner.resume(); myStatusSpinner.setVisible(true); diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/AbstractVcsHelperImpl.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/AbstractVcsHelperImpl.java index b25a713ec5e2..a9cfe177ab72 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/AbstractVcsHelperImpl.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/AbstractVcsHelperImpl.java @@ -52,7 +52,10 @@ import com.intellij.openapi.vcs.changes.CommitResultHandler; import com.intellij.openapi.vcs.changes.LocalChangeList; import com.intellij.openapi.vcs.changes.committed.*; import com.intellij.openapi.vcs.changes.ui.*; -import com.intellij.openapi.vcs.history.*; +import com.intellij.openapi.vcs.history.FileHistoryRefresher; +import com.intellij.openapi.vcs.history.FileHistoryRefresherI; +import com.intellij.openapi.vcs.history.VcsHistoryProvider; +import com.intellij.openapi.vcs.history.VcsRevisionNumber; import com.intellij.openapi.vcs.merge.MergeDialogCustomizer; import com.intellij.openapi.vcs.merge.MergeProvider; import com.intellij.openapi.vcs.merge.MultipleFileMergeDialog; @@ -590,10 +593,7 @@ public class AbstractVcsHelperImpl extends AbstractVcsHelper { if (provider == null) return; if (isNonLocal && provider.getForNonLocal(virtualFile) == null) return; - final String title = VcsBundle.message("paths.affected.in.revision", - revision instanceof ShortVcsRevisionNumber - ? ((ShortVcsRevisionNumber)revision).toShortString() - : revision.asString()); + final String title = VcsBundle.message("paths.affected.in.revision", VcsUtil.getShortRevisionString(revision)); final CommittedChangeList[] list = new CommittedChangeList[1]; final FilePath[] targetPath = new FilePath[1]; final VcsException[] exc = new VcsException[1]; diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/statistics/VcsOptionsUsagesCollector.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/statistics/VcsOptionsUsagesCollector.java index 5d2765062fdd..6f88902e1a4f 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/statistics/VcsOptionsUsagesCollector.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/statistics/VcsOptionsUsagesCollector.java @@ -15,7 +15,7 @@ */ package com.intellij.openapi.vcs.statistics; -import com.intellij.internal.statistic.AbstractApplicationUsagesCollector; +import com.intellij.internal.statistic.AbstractProjectsUsagesCollector; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; import com.intellij.openapi.project.Project; @@ -25,10 +25,10 @@ import org.jetbrains.annotations.NotNull; import java.util.Set; -import static com.intellij.internal.statistic.StatisticsUtilKt.getBooleanUsage; -import static com.intellij.internal.statistic.StatisticsUtilKt.getEnumUsage; +import static com.intellij.internal.statistic.utils.StatisticsUtilKt.getBooleanUsage; +import static com.intellij.internal.statistic.utils.StatisticsUtilKt.getEnumUsage; -public class VcsOptionsUsagesCollector extends AbstractApplicationUsagesCollector { +public class VcsOptionsUsagesCollector extends AbstractProjectsUsagesCollector { private static final String GROUP_ID = "vcs-options"; @NotNull diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/statistics/VcsUsagesCollector.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/statistics/VcsUsagesCollector.java index c3f53ea8e25d..8f6d0ae13b4d 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/statistics/VcsUsagesCollector.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/statistics/VcsUsagesCollector.java @@ -15,7 +15,7 @@ */ package com.intellij.openapi.vcs.statistics; -import com.intellij.internal.statistic.AbstractApplicationUsagesCollector; +import com.intellij.internal.statistic.AbstractProjectsUsagesCollector; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; import com.intellij.openapi.project.Project; @@ -25,7 +25,7 @@ import org.jetbrains.annotations.NotNull; import java.util.Set; -public class VcsUsagesCollector extends AbstractApplicationUsagesCollector { +public class VcsUsagesCollector extends AbstractProjectsUsagesCollector { private static final String GROUP_ID = "vcs"; @NotNull diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/update/AbstractCommonUpdateAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/update/AbstractCommonUpdateAction.java index 95d63e560dff..12f30144f050 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/update/AbstractCommonUpdateAction.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/update/AbstractCommonUpdateAction.java @@ -551,7 +551,7 @@ public abstract class AbstractCommonUpdateAction extends AbstractVcsAction { cache.processUpdatedFiles(myUpdatedFiles, incomingChangeLists -> tree.setChangeLists(incomingChangeLists)); Notification notification = prepareNotification(tree, someSessionWasCancelled); - notification.addAction(new ViewUpdateInfoNotification(myProject, tree, "View")); + notification.addAction(new ViewUpdateInfoNotification(myProject, tree, "View", notification)); VcsNotifier.getInstance(myProject).notify(notification); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/vfs/VcsVirtualFile.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/vfs/VcsVirtualFile.java index 645511d0cce7..49ec0d22f221 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/vfs/VcsVirtualFile.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/vfs/VcsVirtualFile.java @@ -21,12 +21,12 @@ import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.vcs.VcsBundle; import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.history.ShortVcsRevisionNumber; import com.intellij.openapi.vcs.history.VcsFileRevision; import com.intellij.openapi.vcs.history.VcsRevisionNumber; import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.openapi.vfs.VirtualFileSystem; import com.intellij.util.ArrayUtil; +import com.intellij.vcsUtil.VcsUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -83,12 +83,7 @@ public class VcsVirtualFile extends AbstractVcsVirtualFile { myModificationStamp++; final VcsRevisionNumber revisionNumber = myFileRevision.getRevisionNumber(); - if (revisionNumber instanceof ShortVcsRevisionNumber) { - setRevision(((ShortVcsRevisionNumber) revisionNumber).toShortString()); - } - else { - setRevision(revisionNumber.asString()); - } + setRevision(VcsUtil.getShortRevisionString(revisionNumber)); myContent = myFileRevision.getContent(); myCharset = new CharsetToolkit(myContent).guessEncoding(myContent.length); ApplicationManager.getApplication().runWriteAction(new Runnable() { diff --git a/platform/vcs-impl/src/com/intellij/util/continuation/ModalityIgnorantBackgroundableTask.java b/platform/vcs-impl/src/com/intellij/util/continuation/ModalityIgnorantBackgroundableTask.java deleted file mode 100644 index 0b7e1ccdd834..000000000000 --- a/platform/vcs-impl/src/com/intellij/util/continuation/ModalityIgnorantBackgroundableTask.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.util.continuation; - -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.Task; -import com.intellij.openapi.project.Project; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -public abstract class ModalityIgnorantBackgroundableTask extends Task.Backgroundable { - private final static Logger LOG = Logger.getInstance(ModalityIgnorantBackgroundableTask.class); - - public ModalityIgnorantBackgroundableTask(@Nullable Project project, @NotNull String title, boolean canBeCancelled) { - super(project, title, canBeCancelled); - } - - protected abstract void doInAwtIfFail(@NotNull Exception e); - protected abstract void doInAwtIfCancel(); - protected abstract void doInAwtIfSuccess(); - protected abstract void runImpl(@NotNull ProgressIndicator indicator); - - @Override - public void run(@NotNull ProgressIndicator indicator) { - try { - runImpl(indicator); - } - catch (Exception e) { - LOG.info(e); - SwingUtilities.invokeLater(() -> doInAwtIfFail(e)); - return; - } - - SwingUtilities.invokeLater(() -> { - if (indicator.isCanceled()) { - doInAwtIfCancel(); - } - else { - doInAwtIfSuccess(); - } - }); - } -} diff --git a/platform/vcs-impl/src/com/intellij/vcs/ViewUpdateInfoNotification.java b/platform/vcs-impl/src/com/intellij/vcs/ViewUpdateInfoNotification.java index 260ae0c6888f..73623faa40f4 100644 --- a/platform/vcs-impl/src/com/intellij/vcs/ViewUpdateInfoNotification.java +++ b/platform/vcs-impl/src/com/intellij/vcs/ViewUpdateInfoNotification.java @@ -17,8 +17,10 @@ package com.intellij.vcs; import com.intellij.notification.Notification; import com.intellij.notification.NotificationAction; +import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vcs.ex.ProjectLevelVcsManagerEx; import com.intellij.openapi.vcs.update.UpdateInfoTree; import com.intellij.openapi.wm.ToolWindowId; @@ -31,10 +33,17 @@ public class ViewUpdateInfoNotification extends NotificationAction { @NotNull private final Project myProject; @NotNull private final UpdateInfoTree myTree; - public ViewUpdateInfoNotification(@NotNull Project project, @NotNull UpdateInfoTree updateInfoTree, @NotNull String actionName) { + public ViewUpdateInfoNotification(@NotNull Project project, @NotNull UpdateInfoTree updateInfoTree, @NotNull String actionName, + @NotNull Notification notification) { super(actionName); myProject = project; myTree = updateInfoTree; + Disposer.register(updateInfoTree, new Disposable() { + @Override + public void dispose() { + notification.expire(); + } + }); } @Override diff --git a/platform/vcs-log/api/src/com/intellij/vcs/log/graph/GraphColorManager.java b/platform/vcs-log/graph-api/src/com/intellij/vcs/log/graph/GraphColorManager.java similarity index 96% rename from platform/vcs-log/api/src/com/intellij/vcs/log/graph/GraphColorManager.java rename to platform/vcs-log/graph-api/src/com/intellij/vcs/log/graph/GraphColorManager.java index 686bfb4fa462..db6d908c5e6b 100644 --- a/platform/vcs-log/api/src/com/intellij/vcs/log/graph/GraphColorManager.java +++ b/platform/vcs-log/graph-api/src/com/intellij/vcs/log/graph/GraphColorManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 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. @@ -15,8 +15,6 @@ */ package com.intellij.vcs.log.graph; -import com.intellij.vcs.log.VcsLogRefManager; - public interface GraphColorManager { /** diff --git a/platform/vcs-log/graph/src/com/intellij/vcs/log/graph/impl/permanent/PermanentLinearGraphBuilder.java b/platform/vcs-log/graph/src/com/intellij/vcs/log/graph/impl/permanent/PermanentLinearGraphBuilder.java index cd58626b302b..3a499caa9f16 100644 --- a/platform/vcs-log/graph/src/com/intellij/vcs/log/graph/impl/permanent/PermanentLinearGraphBuilder.java +++ b/platform/vcs-log/graph/src/com/intellij/vcs/log/graph/impl/permanent/PermanentLinearGraphBuilder.java @@ -25,10 +25,7 @@ import com.intellij.vcs.log.graph.utils.impl.BitSetFlags; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import static com.intellij.vcs.log.graph.impl.permanent.DuplicateParentFixer.fixDuplicateParentCommits; @@ -155,7 +152,7 @@ public class PermanentLinearGraphBuilder { private void fixUnderdoneEdges(@NotNull NotNullFunction notLoadedCommitToId) { List commitIds = ContainerUtil.newArrayList(upAdjacentNodes.keySet()); - ContainerUtil.sort(commitIds, (o1, o2) -> Collections.min(upAdjacentNodes.get(o1)) - Collections.min(upAdjacentNodes.get(o2))); + ContainerUtil.sort(commitIds, Comparator.comparingInt(o -> Collections.min(upAdjacentNodes.get(o)))); for (CommitId notLoadCommit : commitIds) { int notLoadId = notLoadedCommitToId.fun(notLoadCommit); for (int upNodeIndex : upAdjacentNodes.get(notLoadCommit)) { diff --git a/platform/vcs-log/graph/src/com/intellij/vcs/log/graph/linearBek/LinearBekController.java b/platform/vcs-log/graph/src/com/intellij/vcs/log/graph/linearBek/LinearBekController.java index 936b52f2aeea..66c5a1528857 100644 --- a/platform/vcs-log/graph/src/com/intellij/vcs/log/graph/linearBek/LinearBekController.java +++ b/platform/vcs-log/graph/src/com/intellij/vcs/log/graph/linearBek/LinearBekController.java @@ -169,7 +169,7 @@ public class LinearBekController extends CascadeController { } private SortedSet collectNodesToCollapse(GraphNode node) { - SortedSet toCollapse = new TreeSet<>((o1, o2) -> o2.compareTo(o1)); + SortedSet toCollapse = new TreeSet<>(Comparator.reverseOrder()); for (LinearBekGraphBuilder.MergeFragment f : collectFragmentsToCollapse(node)) { toCollapse.add(f.getParent()); toCollapse.addAll(f.getTailsAndBody()); diff --git a/platform/vcs-log/graph/test/com/intellij/vcs/log/graph/StrUtils.kt b/platform/vcs-log/graph/test/com/intellij/vcs/log/graph/StrUtils.kt index adf2a65bb616..a6fa3aac5b7b 100644 --- a/platform/vcs-log/graph/test/com/intellij/vcs/log/graph/StrUtils.kt +++ b/platform/vcs-log/graph/test/com/intellij/vcs/log/graph/StrUtils.kt @@ -28,7 +28,7 @@ import com.intellij.vcs.log.graph.parser.EdgeNodeCharConverter.toChar fun LinearGraph.asString(sorted: Boolean = false): String { val s = StringBuilder() for (nodeIndex in 0..nodesCount() - 1) { - if (nodeIndex > 0) s.append("\n"); + if (nodeIndex > 0) s.append("\n") val node = getGraphNode(nodeIndex) s.append(node.asString()).append(CommitParser.SEPARATOR) @@ -38,7 +38,7 @@ fun LinearGraph.asString(sorted: Boolean = false): String { } adjEdges.map { it.asString() }.joinTo(s, separator = " ") } - return s.toString(); + return s.toString() } fun GraphNode.asString(): String = "${nodeIndex}_${toChar(type)}" diff --git a/platform/vcs-log/graph/test/com/intellij/vcs/log/graph/linearBek/LinearBekExpandTest.kt b/platform/vcs-log/graph/test/com/intellij/vcs/log/graph/linearBek/LinearBekExpandTest.kt index bcdd94bdff17..1c5078bf9ba7 100644 --- a/platform/vcs-log/graph/test/com/intellij/vcs/log/graph/linearBek/LinearBekExpandTest.kt +++ b/platform/vcs-log/graph/test/com/intellij/vcs/log/graph/linearBek/LinearBekExpandTest.kt @@ -23,7 +23,7 @@ import org.junit.Test class LinearBekExpandTest { fun runTest(beforeLinearBekBuilder: TestGraphBuilder.() -> Unit, afterExpansionBuilder: TestGraphBuilder.() -> Unit, fromNodeToExpand: Int, toNodeToExpand: Int) { val afterLinearBek = runLinearBek(beforeLinearBekBuilder) - afterLinearBek.expandEdge(GraphEdge.createNormalEdge(fromNodeToExpand, toNodeToExpand, GraphEdgeType.DOTTED)); + afterLinearBek.expandEdge(GraphEdge.createNormalEdge(fromNodeToExpand, toNodeToExpand, GraphEdgeType.DOTTED)) assertEquals(afterExpansionBuilder, afterLinearBek) } diff --git a/platform/vcs-log/graph/test/com/intellij/vcs/log/graph/linearBek/TestUtils.kt b/platform/vcs-log/graph/test/com/intellij/vcs/log/graph/linearBek/TestUtils.kt index 396719dbd589..9b6e9fa2e8dd 100644 --- a/platform/vcs-log/graph/test/com/intellij/vcs/log/graph/linearBek/TestUtils.kt +++ b/platform/vcs-log/graph/test/com/intellij/vcs/log/graph/linearBek/TestUtils.kt @@ -51,7 +51,7 @@ fun runBek(graphBuilder: TestGraphBuilder.() -> Unit): BekBaseController.BekLine val edge = BekChecker.findReversedEdge(afterBek) if (edge != null) { - assertNull(Pair(bekMap.getUsualIndex(edge.first), bekMap.getUsualIndex(edge.second)), "Found reversed edge"); + assertNull(Pair(bekMap.getUsualIndex(edge.first), bekMap.getUsualIndex(edge.second)), "Found reversed edge") } return afterBek diff --git a/platform/vcs-log/graph/vcs-log-graph.iml b/platform/vcs-log/graph/vcs-log-graph.iml index 338638868235..ead64783ff63 100644 --- a/platform/vcs-log/graph/vcs-log-graph.iml +++ b/platform/vcs-log/graph/vcs-log-graph.iml @@ -9,7 +9,6 @@ - diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/SingleTaskController.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/SingleTaskController.java index e53c0fc29d1b..8ebbcac929e2 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/SingleTaskController.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/SingleTaskController.java @@ -16,6 +16,8 @@ package com.intellij.vcs.log.data; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.util.Consumer; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; @@ -41,13 +43,15 @@ public abstract class SingleTaskController { @NotNull private final Consumer myResultHandler; @NotNull private final Object LOCK = new Object(); + private final boolean myCancelRunning; @NotNull private List myAwaitingRequests; - private boolean myActive; + @Nullable private ProgressIndicator myRunningTask; - public SingleTaskController(@NotNull Consumer handler) { + public SingleTaskController(@NotNull Consumer handler, boolean cancelRunning) { myResultHandler = handler; - myAwaitingRequests = ContainerUtil.newArrayList(); + myAwaitingRequests = ContainerUtil.newLinkedList(); + myCancelRunning = cancelRunning; } /** @@ -59,11 +63,20 @@ public abstract class SingleTaskController { synchronized (LOCK) { myAwaitingRequests.add(requests); LOG.debug("Added requests: " + requests); - if (!myActive) { - startNewBackgroundTask(); - LOG.debug("Started a new bg task"); - myActive = true; + if (myRunningTask != null && myCancelRunning) { + cancelTask(myRunningTask); } + if (myRunningTask == null) { + myRunningTask = startNewBackgroundTask(); + LOG.debug("Started a new bg task " + myRunningTask); + } + } + } + + private void cancelTask(@NotNull ProgressIndicator t) { + if (t.isRunning()) { + t.cancel(); + LOG.debug("Canceled task " + myRunningTask); } } @@ -71,7 +84,8 @@ public abstract class SingleTaskController { * Starts new task on a background thread.
    * NB: Don't invoke StateController methods inside this method, otherwise a deadlock will happen. */ - protected abstract void startNewBackgroundTask(); + @NotNull + protected abstract ProgressIndicator startNewBackgroundTask(); /** * Returns all awaiting requests and clears the queue.
    @@ -81,16 +95,43 @@ public abstract class SingleTaskController { public final List popRequests() { synchronized (LOCK) { List requests = myAwaitingRequests; - myAwaitingRequests = ContainerUtil.newArrayList(); + myAwaitingRequests = ContainerUtil.newLinkedList(); LOG.debug("Popped requests: " + requests); return requests; } } + @NotNull + public final List peekRequests() { + synchronized (LOCK) { + List requests = ContainerUtil.newArrayList(myAwaitingRequests); + LOG.debug("Peeked requests: " + requests); + return requests; + } + } + + public final void removeRequests(@NotNull List requests) { + synchronized (LOCK) { + myAwaitingRequests.removeAll(requests); + LOG.debug("Removed requests: " + requests); + } + } + + @Nullable + public final Request popRequest() { + synchronized (LOCK) { + if (myAwaitingRequests.isEmpty()) return null; + Request request = myAwaitingRequests.remove(0); + LOG.debug("Popped request: " + request); + return request; + } + } + /** * The underlying currently active task should use this method to inform that it has completed the execution.
    * If the result is not null, it is immediately passed to the result handler specified in the constructor. * Otherwise result handler is not called, the task just completes. + * After result handler is called, a new task is started if there are new requests awaiting in the queue. */ public final void taskCompleted(@Nullable Result result) { if (result != null) { @@ -99,12 +140,12 @@ public abstract class SingleTaskController { } synchronized (LOCK) { if (myAwaitingRequests.isEmpty()) { - myActive = false; + myRunningTask = null; LOG.debug("No more requests"); } else { - startNewBackgroundTask(); - LOG.debug("Restarted a bg task"); + myRunningTask = startNewBackgroundTask(); + LOG.debug("Restarted a bg task " + myRunningTask); } } } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/VcsLogRefresherImpl.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/VcsLogRefresherImpl.java index 3114e9728dd0..3ef1d4ab24ce 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/VcsLogRefresherImpl.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/VcsLogRefresherImpl.java @@ -26,7 +26,6 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Consumer; import com.intellij.util.NotNullFunction; import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.ui.UIUtil; import com.intellij.vcs.log.*; import com.intellij.vcs.log.data.index.VcsLogIndex; import com.intellij.vcs.log.graph.GraphCommit; @@ -82,19 +81,20 @@ public class VcsLogRefresherImpl implements VcsLogRefresher { mySingleTaskController = new SingleTaskController(dataPack -> { myDataPack = dataPack; dataPackUpdateHandler.consume(dataPack); - }) { + }, false) { + @NotNull @Override - protected void startNewBackgroundTask() { - VcsLogRefresherImpl.this.startNewBackgroundTask(new MyRefreshTask(myDataPack)); + protected ProgressIndicator startNewBackgroundTask() { + return VcsLogRefresherImpl.this.startNewBackgroundTask(new MyRefreshTask(myDataPack)); } }; } - protected void startNewBackgroundTask(@NotNull final Task.Backgroundable refreshTask) { - UIUtil.invokeLaterIfNeeded(() -> { - LOG.debug("Starting a background task..."); - ProgressManager.getInstance().runProcessWithProgressAsynchronously(refreshTask, myProgress.createProgressIndicator()); - }); + protected ProgressIndicator startNewBackgroundTask(@NotNull final Task.Backgroundable refreshTask) { + LOG.debug("Starting a background task..."); + ProgressIndicator indicator = myProgress.createProgressIndicator(); + ProgressManager.getInstance().runProcessWithProgressAsynchronously(refreshTask, indicator); + return indicator; } @NotNull diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPersistentIndex.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPersistentIndex.java index b7b453f6e484..6dc251b7ab34 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPersistentIndex.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPersistentIndex.java @@ -19,7 +19,10 @@ import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.*; +import com.intellij.openapi.progress.PerformInBackgroundOption; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Disposer; @@ -36,6 +39,7 @@ import com.intellij.util.io.*; import com.intellij.vcs.log.*; import com.intellij.vcs.log.data.*; import com.intellij.vcs.log.impl.FatalErrorHandler; +import com.intellij.vcs.log.impl.HeavyAwareExecutor; import com.intellij.vcs.log.ui.filter.VcsLogTextFilterImpl; import com.intellij.vcs.log.util.PersistentSet; import com.intellij.vcs.log.util.PersistentSetImpl; @@ -72,7 +76,7 @@ public class VcsLogPersistentIndex implements VcsLogIndex, Disposable { @Nullable private final IndexStorage myIndexStorage; @Nullable private final IndexDataGetter myDataGetter; - @NotNull private final SingleTaskController mySingleTaskController = new MySingleTaskController(); + @NotNull private final SingleTaskController mySingleTaskController; @NotNull private final Map myNumberOfTasks = ContainerUtil.newHashMap(); @NotNull private final List myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); @@ -91,6 +95,7 @@ public class VcsLogPersistentIndex implements VcsLogIndex, Disposable { myProviders = providers; myFatalErrorsConsumer = fatalErrorsConsumer; myRoots = ContainerUtil.newLinkedHashSet(); + mySingleTaskController = new MySingleTaskController(project); for (Map.Entry entry : providers.entrySet()) { if (VcsLogProperties.get(entry.getValue(), VcsLogProperties.SUPPORTS_INDEXING)) { @@ -133,6 +138,8 @@ public class VcsLogPersistentIndex implements VcsLogIndex, Disposable { @Override public synchronized void scheduleIndex(boolean full) { if (myCommitsToIndex.isEmpty() || myIndexStorage == null) return; + // for fresh index, wait for complete log to load and index everything in one command + if (myIndexStorage.isFresh() && !full) return; Map commitsToIndex = myCommitsToIndex; for (VirtualFile root : commitsToIndex.keySet()) { @@ -142,7 +149,12 @@ public class VcsLogPersistentIndex implements VcsLogIndex, Disposable { boolean isFull = full && myIndexStorage.isFresh(); if (isFull) LOG.debug("Index storage for project " + myProject.getName() + " is fresh, scheduling full reindex"); - mySingleTaskController.request(new IndexingRequest(commitsToIndex, isFull)); + for (VirtualFile root : commitsToIndex.keySet()) { + TIntHashSet commits = commitsToIndex.get(root); + if (!commits.isEmpty()) { + mySingleTaskController.request(new IndexingRequest(root, commits, isFull)); + } + } if (isFull) myIndexStorage.unmarkFresh(); } @@ -474,22 +486,30 @@ public class VcsLogPersistentIndex implements VcsLogIndex, Disposable { } private class MySingleTaskController extends SingleTaskController { - public MySingleTaskController() { - super(EmptyConsumer.getInstance()); + @NotNull private final HeavyAwareExecutor myHeavyAwareExecutor; + + public MySingleTaskController(@NotNull Project project) { + super(EmptyConsumer.getInstance(), false); + myHeavyAwareExecutor = new HeavyAwareExecutor(project, 50, 100, VcsLogPersistentIndex.this); } + @NotNull @Override - protected void startNewBackgroundTask() { + protected ProgressIndicator startNewBackgroundTask() { + ProgressIndicator indicator = myProgress.createProgressIndicator(false); ApplicationManager.getApplication().invokeLater(() -> { Task.Backgroundable task = new Task.Backgroundable(VcsLogPersistentIndex.this.myProject, "Indexing Commit Data", true, PerformInBackgroundOption.ALWAYS_BACKGROUND) { + @Override public void run(@NotNull ProgressIndicator indicator) { - List requests; - while (!(requests = popRequests()).isEmpty()) { - for (IndexingRequest request : requests) { + try { + + IndexingRequest request; + while ((request = popRequest()) != null) { try { request.run(indicator); + indicator.checkCanceled(); } catch (ProcessCanceledException reThrown) { throw reThrown; @@ -499,23 +519,29 @@ public class VcsLogPersistentIndex implements VcsLogIndex, Disposable { } } } - - taskCompleted(null); + finally { + taskCompleted(null); + } } }; - ProgressIndicator indicator = myProgress.createProgressIndicator(false); - ProgressManager.getInstance().runProcessWithProgressAsynchronously(task, indicator); + myHeavyAwareExecutor.executeOutOfHeavyOrPowerSave(task, indicator); }); + return indicator; } } private class IndexingRequest { private static final int BATCH_SIZE = 20000; private static final int FLUSHED_COMMITS_NUMBER = 15000; - private final Map myCommits; + @NotNull private final VirtualFile myRoot; + @NotNull private final TIntHashSet myCommits; private final boolean myFull; - public IndexingRequest(@NotNull Map commits, boolean full) { + public volatile int myNewIndexedCommits; + public volatile int myOldCommits; + + public IndexingRequest(@NotNull VirtualFile root, @NotNull TIntHashSet commits, boolean full) { + myRoot = root; myCommits = commits; myFull = full; } @@ -524,143 +550,115 @@ public class VcsLogPersistentIndex implements VcsLogIndex, Disposable { indicator.setIndeterminate(false); indicator.setFraction(0); - long time = System.currentTimeMillis(); + long startTime = System.currentTimeMillis(); - CommitsCounter counter = new CommitsCounter(indicator, myCommits.values().stream().mapToInt(TIntHashSet::size).sum()); - LOG.debug("Indexing " + counter.allCommits + " commits"); + LOG.debug("Indexing " + (myFull ? "full repository" : myCommits.size() + " commits") + " in " + myRoot.getName()); - for (VirtualFile root : myCommits.keySet()) { + try { try { if (myFull) { - indexAll(root, myCommits.get(root), counter); + indexAll(indicator); } else { - indexOneByOne(root, myCommits.get(root), counter); + IntStream commits = TroveUtil.stream(myCommits).filter(c -> { + if (isIndexed(c)) { + myOldCommits++; + return false; + } + return true; + }); + + indexOneByOne(commits, indicator); } } - finally { - myNumberOfTasks.get(root).decrementAndGet(); + catch (ProcessCanceledException e) { + scheduleReindex(); + throw e; } - - if (isIndexed(root)) { - myListeners.forEach(listener -> listener.indexingFinished(root)); + catch (VcsException e) { + LOG.error(e); + scheduleReindex(); } } + finally { + myNumberOfTasks.get(myRoot).decrementAndGet(); - LOG.debug(StopWatch.formatTime(System.currentTimeMillis() - time) + - " for indexing " + - counter.newIndexedCommits + - " new commits out of " + - counter.allCommits); - int leftCommits = counter.allCommits - counter.newIndexedCommits - counter.oldCommits; - if (leftCommits > 0) { - LOG.warn("Did not index " + leftCommits + " commits"); + if (isIndexed(myRoot)) { + myListeners.forEach(listener -> listener.indexingFinished(myRoot)); + } + + report(startTime); + + flush(); } } - private void indexOneByOne(@NotNull VirtualFile root, - @NotNull TIntHashSet commitsSet, - @NotNull CommitsCounter counter) { - IntStream commits = TroveUtil.stream(commitsSet).filter(c -> { - if (isIndexed(c)) { - counter.oldCommits++; - return false; - } + private void report(long startTime) { + String formattedTime = StopWatch.formatTime(System.currentTimeMillis() - startTime); + if (myFull) { + LOG.debug(formattedTime + + " for indexing " + + myNewIndexedCommits + " commits in " + myRoot.getName()); + } + else { + int leftCommits = myCommits.size() - myNewIndexedCommits - myOldCommits; + String leftCommitsMessage = (leftCommits > 0) ? ". " + leftCommits + " commits left" : ""; + + LOG.debug(formattedTime + + " for indexing " + + myNewIndexedCommits + + " new commits out of " + + myCommits.size() + " in " + myRoot.getName() + leftCommitsMessage); + } + } + + private void scheduleReindex() { + LOG.debug("Schedule reindexing of " + (myCommits.size() - myNewIndexedCommits - myOldCommits) + " commits in " + myRoot.getName()); + myCommits.forEach(value -> { + markForIndexing(value, myRoot); return true; }); - - indexOneByOne(root, counter, commits); + scheduleIndex(false); } - private void indexOneByOne(@NotNull VirtualFile root, - @NotNull CommitsCounter counter, - @NotNull IntStream commits) { + private void indexOneByOne(@NotNull IntStream commits, @NotNull ProgressIndicator indicator) throws VcsException { // We pass hashes to VcsLogProvider#readFullDetails in batches // in order to avoid allocating too much memory for these hashes // a batch of 20k will occupy ~2.4Mb TroveUtil.processBatches(commits, BATCH_SIZE, batch -> { - counter.indicator.checkCanceled(); + indicator.checkCanceled(); - if (indexOneByOne(root, batch)) { - counter.newIndexedCommits += batch.size(); - } + List hashes = TroveUtil.map(batch, value -> myStorage.getCommitId(value).getHash().asString()); + myProviders.get(myRoot).readFullDetails(myRoot, hashes, detail -> { + VcsLogPersistentIndex.this.storeDetail(detail); + myNewIndexedCommits++; + }, true); - counter.displayProgress(); + displayProgress(indicator); }); - - flush(); } - private boolean indexOneByOne(@NotNull VirtualFile root, @NotNull TIntHashSet commits) { - VcsLogProvider provider = myProviders.get(root); - try { - List hashes = TroveUtil.map(commits, value -> myStorage.getCommitId(value).getHash().asString()); - provider.readFullDetails(root, hashes, VcsLogPersistentIndex.this::storeDetail, true); - } - catch (VcsException e) { - LOG.error(e); - commits.forEach(value -> { - markForIndexing(value, root); - return true; - }); - return false; - } - return true; - } + public void indexAll(@NotNull ProgressIndicator indicator) throws VcsException { + displayProgress(indicator); - public void indexAll(@NotNull VirtualFile root, - @NotNull TIntHashSet commitsSet, - @NotNull CommitsCounter counter) { - TIntHashSet notIndexed = new TIntHashSet(); - TroveUtil.stream(commitsSet).forEach(c -> { - if (isIndexed(c)) { - counter.oldCommits++; - } - else { - notIndexed.add(c); - } + myProviders.get(myRoot).readAllFullDetails(myRoot, details -> { + storeDetail(details); + myNewIndexedCommits++; + + if (myNewIndexedCommits % FLUSHED_COMMITS_NUMBER == 0) flush(); + + indicator.checkCanceled(); + displayProgress(indicator); }); - counter.displayProgress(); - - try { - myProviders.get(root).readAllFullDetails(root, details -> { - int index = myStorage.getCommitIndex(details.getId(), details.getRoot()); - if (notIndexed.contains(index)) { - storeDetail(details); - counter.newIndexedCommits++; - - if (counter.newIndexedCommits % FLUSHED_COMMITS_NUMBER == 0) flush(); - } - - counter.indicator.checkCanceled(); - counter.displayProgress(); - }); - } - catch (VcsException e) { - LOG.error(e); - notIndexed.forEach(value -> { - markForIndexing(value, root); - return true; - }); - } - - flush(); - } - } - - private static class CommitsCounter { - @NotNull public final ProgressIndicator indicator; - public final int allCommits; - public volatile int newIndexedCommits; - public volatile int oldCommits; - - private CommitsCounter(@NotNull ProgressIndicator indicator, int commits) { - this.indicator = indicator; - this.allCommits = commits; } - public void displayProgress() { - indicator.setFraction(((double)newIndexedCommits + oldCommits) / allCommits); + public void displayProgress(@NotNull ProgressIndicator indicator) { + indicator.setFraction(((double)myNewIndexedCommits + myOldCommits) / myCommits.size()); + } + + @Override + public String toString() { + return "IndexingRequest of " + myCommits.size() + " commits in " + myRoot.getName() + (myFull ? " (full)" : ""); } } } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryFilterer.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryFilterer.java index 88ae5098569d..59603f5298f9 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryFilterer.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/history/FileHistoryFilterer.java @@ -16,8 +16,17 @@ package com.intellij.vcs.log.history; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vcs.AbstractVcs; import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.history.VcsAppendableHistoryPartnerAdapter; +import com.intellij.openapi.vcs.history.VcsFileRevision; +import com.intellij.openapi.vcs.history.VcsFileRevisionEx; +import com.intellij.openapi.vcs.history.VcsHistoryProvider; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Function; import com.intellij.util.ObjectUtils; @@ -40,6 +49,8 @@ import com.intellij.vcs.log.graph.utils.BfsUtil; import com.intellij.vcs.log.graph.utils.DfsUtil; import com.intellij.vcs.log.graph.utils.LinearGraphUtils; import com.intellij.vcs.log.graph.utils.impl.BitSetFlags; +import com.intellij.vcs.log.impl.HashImpl; +import com.intellij.vcs.log.util.StopWatch; import com.intellij.vcs.log.visible.CommitCountStage; import com.intellij.vcs.log.visible.VcsLogFilterer; import com.intellij.vcs.log.visible.VisiblePack; @@ -54,6 +65,7 @@ import static com.intellij.util.ObjectUtils.notNull; class FileHistoryFilterer extends VcsLogFilterer { private static final Logger LOG = Logger.getInstance(FileHistoryFilterer.class); + @NotNull private final Project myProject; @NotNull private final FilePath myFilePath; @NotNull private final IndexDataGetter myIndexDataGetter; @NotNull private final VirtualFile myRoot; @@ -61,30 +73,99 @@ class FileHistoryFilterer extends VcsLogFilterer { public FileHistoryFilterer(@NotNull VcsLogData logData, @NotNull FilePath filePath) { super(logData.getLogProviders(), logData.getStorage(), logData.getTopCommitsCache(), logData.getCommitDetailsGetter(), logData.getIndex()); + myProject = logData.getProject(); myFilePath = filePath; myIndexDataGetter = ObjectUtils.assertNotNull(myIndex.getDataGetter()); - myRoot = ObjectUtils.assertNotNull(VcsUtil.getVcsRootFor(logData.getProject(), myFilePath)); + myRoot = ObjectUtils.assertNotNull(VcsUtil.getVcsRootFor(myProject, myFilePath)); } @NotNull - protected VisiblePack createVisiblePack(@NotNull DataPack dataPack, - @NotNull PermanentGraph.SortType sortType, - @NotNull VcsLogFilterCollection filters, - @Nullable Set matchingHeads, - @NotNull FilterByDetailsResult filterResult) { - if (!(filterResult instanceof FilteredByFileResult) || filterResult.matchingCommits == null) { - return super.createVisiblePack(dataPack, sortType, filters, matchingHeads, filterResult); + @Override + protected Pair filter(@NotNull DataPack dataPack, + @NotNull PermanentGraph.SortType sortType, + @NotNull VcsLogFilterCollection filters, + @NotNull CommitCountStage commitCount) { + long start = System.currentTimeMillis(); + + checkDetailsFilter(filters); + + if (!myIndex.isIndexed(myRoot) && myFilePath.isDirectory()) { + return super.filter(dataPack, sortType, filters, commitCount); } - VisibleGraph visibleGraph = createVisibleGraph(dataPack, sortType, matchingHeads, filterResult.matchingCommits); + if (myIndex.isIndexed(myRoot)) { + VisiblePack visiblePack = filterWithIndex(dataPack, sortType, filters); + LOG.debug(StopWatch.formatTime(System.currentTimeMillis() - start) + " for computing history for " + myFilePath + " with index"); + checkNotEmpty(dataPack, visiblePack, true); + return Pair.create(visiblePack, commitCount); + } + + AbstractVcs vcs = ProjectLevelVcsManager.getInstance(myProject).getVcsFor(myRoot); + if (vcs != null) { + VcsHistoryProvider provider = vcs.getVcsHistoryProvider(); + if (provider != null) { + try { + VisiblePack visiblePack = filterWithProvider(provider, dataPack, sortType, filters); + LOG.debug(StopWatch.formatTime(System.currentTimeMillis() - start) + + " for computing history for " + + myFilePath + + " with history provider"); + checkNotEmpty(dataPack, visiblePack, false); + return Pair.create(visiblePack, commitCount); + } + catch (VcsException e) { + LOG.error(e); + return super.filter(dataPack, sortType, filters, commitCount); + } + } + } + + LOG.warn("Could not find vcs or history provider for file " + myFilePath); + return super.filter(dataPack, sortType, filters, commitCount); + } + + private void checkNotEmpty(@NotNull DataPack dataPack, @NotNull VisiblePack visiblePack, boolean withIndex) { + if (!dataPack.isFull()) { + LOG.debug("Data pack is not full while computing file history for " + myFilePath + "\n" + + "Found " + visiblePack.getVisibleGraph().getVisibleCommitCount() + " commits"); + } + else if (visiblePack.getVisibleGraph().getVisibleCommitCount() == 0) { + LOG.warn("Empty file history from " + (withIndex ? "index" : "provider") + " for " + myFilePath); + } + } + + @NotNull + private VisiblePack filterWithProvider(@NotNull VcsHistoryProvider provider, + @NotNull DataPack dataPack, + @NotNull PermanentGraph.SortType sortType, + @NotNull VcsLogFilterCollection filters) throws VcsException { + VcsAppendableHistoryPartnerAdapter partner = new VcsAppendableHistoryPartnerAdapter(); + provider.reportAppendableHistory(myFilePath, partner); + + Map pathsMap = ContainerUtil.newHashMap(); + for (VcsFileRevision revision : partner.getSession().getRevisionList()) { + int index = myStorage.getCommitIndex(HashImpl.build(revision.getRevisionNumber().asString()), myRoot); + pathsMap.put(index, ((VcsFileRevisionEx)revision).getPath()); + } + + VisibleGraph visibleGraph = createVisibleGraph(dataPack, sortType, null, pathsMap.keySet()); + return new FileHistoryVisiblePack(dataPack, visibleGraph, false, filters, pathsMap); + } + + @NotNull + private VisiblePack filterWithIndex(@NotNull DataPack dataPack, + @NotNull PermanentGraph.SortType sortType, + @NotNull VcsLogFilterCollection filters) { + Set matchingHeads = getMatchingHeads(dataPack.getRefsModel(), Collections.singleton(myRoot), filters); + IndexDataGetter.FileNamesData data = myIndexDataGetter.buildFileNamesData(myFilePath); + VisibleGraph visibleGraph = createVisibleGraph(dataPack, sortType, matchingHeads, data.getCommits()); - IndexDataGetter.FileNamesData namesData = ((FilteredByFileResult)filterResult).fileNamesData; Map pathsMap = null; if (visibleGraph.getVisibleCommitCount() > 0) { if (visibleGraph instanceof VisibleGraphImpl) { - int row = getCurrentRow(dataPack, visibleGraph, namesData); + int row = getCurrentRow(dataPack, visibleGraph, data); if (row >= 0) { - FileHistoryRefiner refiner = new FileHistoryRefiner((VisibleGraphImpl)visibleGraph, namesData); + FileHistoryRefiner refiner = new FileHistoryRefiner((VisibleGraphImpl)visibleGraph, data); if (refiner.refine(row, myFilePath)) { // creating a vg is the most expensive task, so trying to avoid that when unnecessary visibleGraph = createVisibleGraph(dataPack, sortType, matchingHeads, refiner.getPathsForCommits().keySet()); @@ -95,36 +176,21 @@ class FileHistoryFilterer extends VcsLogFilterer { } if (pathsMap == null) { - pathsMap = namesData.buildPathsMap(); + pathsMap = data.buildPathsMap(); } - return new FileHistoryVisiblePack(dataPack, visibleGraph, filterResult.canRequestMore, filters, pathsMap); + return new FileHistoryVisiblePack(dataPack, visibleGraph, false, filters, pathsMap); } - @NotNull - @Override - protected FilterByDetailsResult filterByDetails(@NotNull DataPack dataPack, - @NotNull VcsLogFilterCollection filters, - @NotNull CommitCountStage commitCount, - @NotNull Collection visibleRoots, - @Nullable Set matchingHeads) { + private void checkDetailsFilter(@NotNull VcsLogFilterCollection filters) { List detailsFilters = filters.getDetailsFilters(); + // checking our assumptions: + // we have one file filter here + LOG.assertTrue(detailsFilters.size() == 1); - if (myIndex.isIndexed(myRoot)) { - // checking our assumptions: - // we have one file filter here - - LOG.assertTrue(detailsFilters.size() == 1); - - VcsLogDetailsFilter filter = notNull(ContainerUtil.getFirstItem(detailsFilters)); - LOG.assertTrue(filter instanceof VcsLogStructureFilter); - LOG.assertTrue(((VcsLogStructureFilter)filter).getFiles().equals(Collections.singleton(myFilePath))); - - IndexDataGetter.FileNamesData data = myIndexDataGetter.buildFileNamesData(myFilePath); - return new FilteredByFileResult(data, data.getCommits(), false, commitCount); - } - - return super.filterByDetails(dataPack, filters, commitCount, visibleRoots, matchingHeads); + VcsLogDetailsFilter filter = notNull(ContainerUtil.getFirstItem(detailsFilters)); + LOG.assertTrue(filter instanceof VcsLogStructureFilter); + LOG.assertTrue(((VcsLogStructureFilter)filter).getFiles().equals(Collections.singleton(myFilePath))); } private int getCurrentRow(@NotNull DataPack pack, @@ -273,16 +339,4 @@ class FileHistoryFilterer extends VcsLogFilterer { myPaths.pop(); } } - - private static class FilteredByFileResult extends FilterByDetailsResult { - @NotNull public final IndexDataGetter.FileNamesData fileNamesData; - - public FilteredByFileResult(@NotNull IndexDataGetter.FileNamesData data, - @Nullable Set commits, - boolean more, - @NotNull CommitCountStage count) { - super(commits, more, count); - fileNamesData = data; - } - } } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/HeavyAwareExecutor.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/HeavyAwareExecutor.java index db7974c851a0..f22c0bdbce20 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/HeavyAwareExecutor.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/HeavyAwareExecutor.java @@ -16,13 +16,77 @@ package com.intellij.vcs.log.impl; import com.intellij.concurrency.JobScheduler; +import com.intellij.ide.PowerSaveMode; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.progress.impl.CoreProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.io.storage.HeavyProcessLatch; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; -class HeavyAwareExecutor { - static void executeOutOfHeavyProcessLater(@NotNull Runnable command, int delayMs) { +public class HeavyAwareExecutor implements Disposable { + @NotNull private final Project myProject; + @NotNull private final ExecutingHeavyOrPowerSaveListener myListener; + private final int myLongActivityDurationMs; + + /** + * Creates new HeavyAwareExecutor. + * + * @param project target project + * @param delayMs delay in milliseconds to execute the task after a heavy activity is finished + * @param longActivityDurationMs length of activity in milliseconds that cancels the task + */ + public HeavyAwareExecutor(@NotNull Project project, int delayMs, int longActivityDurationMs, @NotNull Disposable parent) { + myProject = project; + myLongActivityDurationMs = longActivityDurationMs; + myListener = new ExecutingHeavyOrPowerSaveListener(project, delayMs, this); + + Disposer.register(parent, this); + } + + /** + * Starts a task in background after heavy process is finished and there is no power save mode after. + * Task is not started until there some amount of time has passed since the last heavy activity. + * When a "long" heavy activity is started during task execution, task is cancelled. + * Task is also cancelled when user turns power save mode on. + * + * @param task task to execute + * @param indicator progress indicator for executing the task + */ + public void executeOutOfHeavyOrPowerSave(@NotNull Task.Backgroundable task, + @NotNull ProgressIndicator indicator) { + myListener.addTask(() -> { + Disposable disposable = Disposer.newDisposable(); + ((CoreProgressManager)ProgressManager.getInstance()).runProcessWithProgressAsynchronously(task, indicator, + () -> Disposer.dispose(disposable)); + + new CancellingOnHeavyOrPowerSaveListener(myProject, indicator, myLongActivityDurationMs, disposable); + } + ); + } + + @Override + public void dispose() { + } + + /** + * Starts a task out of heavy activity after a delay. + * + * @param command a task to start + * @param delayMs delay in milliseconds to wait after a heavy activity is finished + */ + public static void executeOutOfHeavyProcessLater(@NotNull Runnable command, int delayMs) { HeavyProcessLatch.INSTANCE.executeOutOfHeavyProcess(() -> JobScheduler.getScheduler().schedule(() -> { if (HeavyProcessLatch.INSTANCE.isRunning()) { executeOutOfHeavyProcessLater(command, delayMs); @@ -32,4 +96,87 @@ class HeavyAwareExecutor { } }, delayMs, TimeUnit.MILLISECONDS)); } + + private static class CancellingOnHeavyOrPowerSaveListener implements HeavyProcessLatch.HeavyProcessListener, PowerSaveMode.Listener { + @NotNull private final ProgressIndicator myIndicator; + private final int myLongActivityDurationMs; + + @Nullable private ScheduledFuture myFuture = null; + + public CancellingOnHeavyOrPowerSaveListener(@NotNull Project project,@NotNull ProgressIndicator indicator, int logActivityDurationMs, @NotNull Disposable disposable) { + myIndicator = indicator; + myLongActivityDurationMs = logActivityDurationMs; + + HeavyProcessLatch.INSTANCE.addListener(this, disposable); + project.getMessageBus().connect(disposable).subscribe(PowerSaveMode.TOPIC, this); + + scheduleCancel(); // in case some sneaky heavy process started before we managed to add a listener + powerSaveStateChanged(); // or if power save mode was suddenly turned on + } + + @Override + public void processStarted() { + scheduleCancel(); + } + + @Override + public void processFinished() { + doNotCancel(); + } + + @Override + public void powerSaveStateChanged() { + if (PowerSaveMode.isEnabled() && myIndicator.isRunning()) myIndicator.cancel(); + } + + private synchronized void scheduleCancel() { + if (HeavyProcessLatch.INSTANCE.isRunning() && myFuture == null) { + myFuture = JobScheduler.getScheduler().schedule(() -> { + if (HeavyProcessLatch.INSTANCE.isRunning() && myIndicator.isRunning()) myIndicator.cancel(); + }, myLongActivityDurationMs, TimeUnit.MILLISECONDS); + } + } + + private synchronized void doNotCancel() { + if (!HeavyProcessLatch.INSTANCE.isRunning() && myFuture != null) { + ScheduledFuture future = myFuture; + myFuture = null; + future.cancel(true); + } + } + } + + private static class ExecutingHeavyOrPowerSaveListener implements PowerSaveMode.Listener { + @NotNull private final AtomicReference> myTasksToRun = new AtomicReference<>(ContainerUtil.newArrayList()); + private final int myDelayMs; + + public ExecutingHeavyOrPowerSaveListener(@NotNull Project project, int delayMs, @NotNull Disposable parent) { + myDelayMs = delayMs; + project.getMessageBus().connect(parent).subscribe(PowerSaveMode.TOPIC, this); + } + + public void addTask(@NotNull Runnable task) { + myTasksToRun.getAndUpdate(tasks -> ContainerUtil.concat(tasks, Collections.singletonList(task))); + tryRun(); + } + + @Override + public void powerSaveStateChanged() { + tryRun(); + } + + private void tryRun() { + if (!PowerSaveMode.isEnabled()) { + HeavyProcessLatch.INSTANCE.executeOutOfHeavyProcess(() -> JobScheduler.getScheduler().schedule(() -> { + if (!HeavyProcessLatch.INSTANCE.isRunning() && !PowerSaveMode.isEnabled()) { + List tasks = myTasksToRun.getAndSet(ContainerUtil.newArrayList()); + tasks.forEach(Runnable::run); + } + else { + tryRun(); + } + }, myDelayMs, TimeUnit.MILLISECONDS)); + } + } + } } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsLogTabsWatcher.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsLogTabsWatcher.java index 707d8f7c9554..d10f83b501c2 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsLogTabsWatcher.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsLogTabsWatcher.java @@ -46,6 +46,7 @@ public class VcsLogTabsWatcher implements Disposable { @NotNull private final ToolWindowManagerEx myToolWindowManager; @NotNull private final MyRefreshPostponedEventsListener myPostponedEventsListener; @Nullable private ToolWindow myToolWindow; + private boolean myIsVisible; public VcsLogTabsWatcher(@NotNull Project project, @NotNull PostponableLogRefresher refresher, @NotNull Disposable parentDisposable) { myRefresher = refresher; @@ -80,6 +81,7 @@ public class VcsLogTabsWatcher implements Disposable { ToolWindow window = myToolWindowManager.getToolWindow(TOOLWINDOW_ID); if (window != null) { myToolWindow = window; + myIsVisible = myToolWindow.isVisible(); myToolWindow.getContentManager().addContentManagerListener(myPostponedEventsListener); } } @@ -170,7 +172,10 @@ public class VcsLogTabsWatcher implements Disposable { @Override public void stateChanged() { - selectionChanged(); + if (myToolWindow != null && myIsVisible != myToolWindow.isVisible()) { + myIsVisible = myToolWindow.isVisible(); + selectionChanged(); + } } @Override diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsProjectLog.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsProjectLog.java index 81a3db67cae0..48c896606962 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsProjectLog.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsProjectLog.java @@ -19,7 +19,6 @@ import com.intellij.ide.caches.CachesInvalidator; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationNamesInfo; -import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; @@ -29,7 +28,6 @@ import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vcs.ProjectLevelVcsManager; import com.intellij.openapi.vcs.VcsRoot; import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier; -import com.intellij.ui.GuiUtils; import com.intellij.util.messages.MessageBus; import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.messages.Topic; @@ -54,7 +52,6 @@ public class VcsProjectLog implements Disposable { @NotNull private final LazyVcsLogManager myLogManager = new LazyVcsLogManager(); private int myRecreatedLogCount = 0; - private volatile VcsLogUiImpl myUi; public VcsProjectLog(@NotNull Project project, @NotNull VcsLogTabsProperties uiProperties) { myProject = project; @@ -74,9 +71,9 @@ public class VcsProjectLog implements Disposable { return Arrays.asList(ProjectLevelVcsManager.getInstance(myProject).getAllVcsRoots()); } - @CalledInAwt + @CalledInAny void setMainUi(@NotNull VcsLogUiImpl ui) { - myUi = ui; + myLogManager.setLogUi(ui); } /** @@ -84,7 +81,7 @@ public class VcsProjectLog implements Disposable { */ @Nullable public VcsLogUiImpl getMainLogUi() { - return myUi; + return myLogManager.getLogUi(); } @Nullable @@ -94,15 +91,12 @@ public class VcsProjectLog implements Disposable { @CalledInAny private void recreateLog() { - GuiUtils.invokeLaterIfNeeded(() -> { + ApplicationManager.getApplication().executeOnPooledThread(() -> { disposeLog(); - - ApplicationManager.getApplication().executeOnPooledThread(() -> { - if (hasDvcsRoots()) { - createLog(); - } - }); - }, ModalityState.any()); + if (hasDvcsRoots()) { + createLog(); + } + }); } @CalledInAwt @@ -126,9 +120,8 @@ public class VcsProjectLog implements Disposable { recreateLog(); } - @CalledInAwt + @CalledInBackground private void disposeLog() { - myUi = null; myLogManager.drop(); } @@ -159,11 +152,12 @@ public class VcsProjectLog implements Disposable { @Override public void dispose() { - disposeLog(); + ApplicationManager.getApplication().executeOnPooledThread(this::disposeLog); } private class LazyVcsLogManager { @Nullable private VcsLogManager myValue; + @Nullable private VcsLogUiImpl myUi; @NotNull @CalledInBackground @@ -182,8 +176,9 @@ public class VcsProjectLog implements Disposable { return new VcsLogManager(myProject, myUiProperties, getVcsRoots(), false, VcsProjectLog.this::recreateOnError); } - @CalledInAwt + @CalledInBackground public synchronized void drop() { + myUi = null; if (myValue != null) { myMessageBus.syncPublisher(VCS_PROJECT_LOG_CHANGED).logDisposed(myValue); Disposer.dispose(myValue); @@ -195,6 +190,16 @@ public class VcsProjectLog implements Disposable { public synchronized VcsLogManager getCached() { return myValue; } + + public synchronized void setLogUi(@NotNull VcsLogUiImpl ui) { + myUi = ui; + } + + @Nullable + @CalledInAny + public synchronized VcsLogUiImpl getLogUi() { + return myUi; + } } public static class InitLogStartupActivity implements StartupActivity { diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/statistics/VcsLogFeaturesCollector.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/statistics/VcsLogFeaturesCollector.java index 5db760f30c6d..dc79132a86cd 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/statistics/VcsLogFeaturesCollector.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/statistics/VcsLogFeaturesCollector.java @@ -15,12 +15,12 @@ */ package com.intellij.vcs.log.statistics; -import com.intellij.internal.statistic.AbstractApplicationUsagesCollector; +import com.intellij.internal.statistic.AbstractProjectsUsagesCollector; import com.intellij.internal.statistic.CollectUsagesException; -import com.intellij.internal.statistic.StatisticsUtilKt; import com.intellij.internal.statistic.beans.ConvertUsagesUtil; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; +import com.intellij.internal.statistic.utils.StatisticsUtilKt; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.project.Project; import com.intellij.util.containers.ContainerUtil; @@ -38,7 +38,7 @@ import java.util.Set; import static com.intellij.vcs.log.impl.MainVcsLogUiProperties.*; import static com.intellij.vcs.log.ui.VcsLogUiImpl.LOG_HIGHLIGHTER_FACTORY_EP; -public class VcsLogFeaturesCollector extends AbstractApplicationUsagesCollector { +public class VcsLogFeaturesCollector extends AbstractProjectsUsagesCollector { public static final GroupDescriptor ID = GroupDescriptor.create("VCS Log Ui Settings"); @NotNull diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/statistics/VcsLogRepoSizeCollector.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/statistics/VcsLogRepoSizeCollector.java index 58f1b68f3967..e7182c35a122 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/statistics/VcsLogRepoSizeCollector.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/statistics/VcsLogRepoSizeCollector.java @@ -15,11 +15,11 @@ */ package com.intellij.vcs.log.statistics; -import com.intellij.internal.statistic.AbstractApplicationUsagesCollector; +import com.intellij.internal.statistic.AbstractProjectsUsagesCollector; import com.intellij.internal.statistic.CollectUsagesException; -import com.intellij.internal.statistic.StatisticsUtilKt; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; +import com.intellij.internal.statistic.utils.StatisticsUtilKt; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.VcsKey; import com.intellij.openapi.vfs.VirtualFile; @@ -39,7 +39,7 @@ import java.util.Set; import static java.util.Arrays.asList; @SuppressWarnings("StringToUpperCaseOrToLowerCaseWithoutLocale") -public class VcsLogRepoSizeCollector extends AbstractApplicationUsagesCollector { +public class VcsLogRepoSizeCollector extends AbstractProjectsUsagesCollector { public static final GroupDescriptor ID = GroupDescriptor.create("VCS Log 2"); diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/actions/FocusTextFilterAction.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/actions/FocusTextFilterAction.java index 60fb62518c7e..871109898b1a 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/actions/FocusTextFilterAction.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/actions/FocusTextFilterAction.java @@ -45,7 +45,7 @@ public class FocusTextFilterAction extends DumbAwareAction { Project project = e.getRequiredData(CommonDataKeys.PROJECT); MainFrame mainFrame = ((VcsLogUiImpl)e.getRequiredData(VcsLogDataKeys.VCS_LOG_UI)).getMainFrame(); - if (mainFrame.getTextFilter().getTextEditor().hasFocus()) { + if (IdeFocusManager.getInstance(project).getFocusedDescendantFor(mainFrame.getToolbar()) != null) { IdeFocusManager.getInstance(project).requestFocus(mainFrame.getGraphTable(), true); } else { diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/actions/ShowOtherBranchesAction.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/actions/ShowOtherBranchesAction.java index 5c19bf4e38db..543986483c1f 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/actions/ShowOtherBranchesAction.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/actions/ShowOtherBranchesAction.java @@ -15,9 +15,19 @@ */ package com.intellij.vcs.log.ui.actions; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsDataKeys; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.vcs.log.data.index.VcsLogIndex; import com.intellij.vcs.log.history.FileHistoryUiProperties; +import com.intellij.vcs.log.impl.VcsLogManager; import com.intellij.vcs.log.impl.VcsLogUiProperties; +import com.intellij.vcs.log.ui.VcsLogInternalDataKeys; +import com.intellij.vcsUtil.VcsUtil; import icons.VcsLogIcons; +import org.jetbrains.annotations.NotNull; public class ShowOtherBranchesAction extends BooleanPropertyToggleAction { @@ -29,4 +39,20 @@ public class ShowOtherBranchesAction extends BooleanPropertyToggleAction { protected VcsLogUiProperties.VcsLogUiProperty getProperty() { return FileHistoryUiProperties.SHOW_ALL_BRANCHES; } + + @Override + public void update(@NotNull AnActionEvent e) { + super.update(e); + + Project project = e.getProject(); + FilePath filePath = e.getData(VcsDataKeys.FILE_PATH); + VcsLogManager logManager = e.getData(VcsLogInternalDataKeys.LOG_MANAGER); + if (project != null && logManager != null && filePath != null) { + VcsLogIndex index = logManager.getDataManager().getIndex(); + VirtualFile root = VcsUtil.getVcsRootFor(project, filePath); + if (root != null && !index.isIndexed(root)) { + e.getPresentation().setEnabled(false); + } + } + } } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/actions/TextFieldWithProgress.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/actions/TextFieldWithProgress.java index 0f861b13809f..cb4549d83658 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/actions/TextFieldWithProgress.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/actions/TextFieldWithProgress.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -16,10 +16,10 @@ package com.intellij.vcs.log.ui.actions; import com.intellij.openapi.project.Project; -import com.intellij.ui.IdeBorderFactory; import com.intellij.util.textCompletion.TextCompletionProvider; import com.intellij.util.textCompletion.TextFieldWithCompletion; import com.intellij.util.ui.AsyncProcessIcon; +import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -33,7 +33,7 @@ public abstract class TextFieldWithProgress extends JPanel { public TextFieldWithProgress(@NotNull Project project, @NotNull TextCompletionProvider completionProvider) { super(new BorderLayout()); - setBorder(IdeBorderFactory.createEmptyBorder(3)); + setBorder(JBUI.Borders.empty(3)); myProgressIcon = new AsyncProcessIcon("Loading commits"); myTextField = new TextFieldWithCompletion(project, completionProvider, "", true, true, false) { @@ -52,7 +52,7 @@ public abstract class TextFieldWithProgress extends JPanel { return false; } }; - myTextField.setBorder(IdeBorderFactory.createEmptyBorder()); + myTextField.setBorder(JBUI.Borders.empty()); myProgressIcon.setOpaque(true); myProgressIcon.setBackground(myTextField.getBackground()); diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/DetailsPanel.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/DetailsPanel.java index 7ee0ef9e8d9d..edae523831ae 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/DetailsPanel.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/DetailsPanel.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -26,7 +26,6 @@ import com.intellij.openapi.ui.OnePixelDivider; import com.intellij.openapi.ui.VerticalFlowLayout; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.history.VcsHistoryUtil; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.SeparatorComponent; import com.intellij.ui.components.JBLabel; import com.intellij.ui.components.JBLoadingPanel; @@ -125,8 +124,8 @@ public class DetailsPanel extends JPanel implements EditorColorsListener { myMainContentPanel.setOpaque(false); myScrollPane.setViewportView(myMainContentPanel); - myScrollPane.setBorder(IdeBorderFactory.createEmptyBorder()); - myScrollPane.setViewportBorder(IdeBorderFactory.createEmptyBorder()); + myScrollPane.setBorder(JBUI.Borders.empty()); + myScrollPane.setViewportBorder(JBUI.Borders.empty()); myLoadingPanel = new JBLoadingPanel(new BorderLayout(), parent, ProgressWindow.DEFAULT_PROGRESS_DIALOG_POSTPONE_TIME_MILLIS) { @Override diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/table/VcsLogGraphTable.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/table/VcsLogGraphTable.java index e53965d17462..c6746f04bfdb 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/table/VcsLogGraphTable.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/table/VcsLogGraphTable.java @@ -95,7 +95,7 @@ public class VcsLogGraphTable extends TableWithProgress implements DataProvider, @NotNull private final AbstractVcsLogUi myUi; @NotNull private final VcsLogData myLogData; @NotNull private final MyDummyTableCellEditor myDummyEditor = new MyDummyTableCellEditor(); - @NotNull private final TableCellRenderer myDummyRenderer = new DefaultTableCellRenderer(); + @NotNull private final TableCellRenderer myDummyRenderer = new MyDefaultTableCellRenderer(); @NotNull private final GraphCommitCellRenderer myGraphCommitCellRenderer; @NotNull private final GraphTableController myController; @NotNull private final StringCellRenderer myStringCellRenderer; @@ -802,6 +802,17 @@ public class VcsLogGraphTable extends TableWithProgress implements DataProvider, } } + private static class MyDefaultTableCellRenderer extends DefaultTableCellRenderer { + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + component.setBackground(isSelected + ? table.hasFocus() ? UIUtil.getListSelectionBackground() : UIUtil.getListUnfocusedSelectionBackground() + : UIUtil.getListBackground()); + return component; + } + } + private class StringCellRenderer extends ColoredTableCellRenderer { @Override protected void customizeCellRenderer(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) { diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/util/TroveUtil.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/util/TroveUtil.java index d539c76a4bda..30b241d869d7 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/util/TroveUtil.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/util/TroveUtil.java @@ -16,14 +16,15 @@ package com.intellij.vcs.log.util; import com.intellij.openapi.util.Ref; -import com.intellij.util.Consumer; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.util.ThrowableConsumer; import com.intellij.util.containers.ContainerUtil; import gnu.trove.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.List; import java.util.Arrays; +import java.util.List; import java.util.Set; import java.util.function.IntFunction; import java.util.stream.Collectors; @@ -127,14 +128,21 @@ public class TroveUtil { return stream(set).mapToObj(function).collect(Collectors.toList()); } - public static void processBatches(@NotNull IntStream stream, int batchSize, @NotNull Consumer consumer) { + public static void processBatches(@NotNull IntStream stream, + int batchSize, + @NotNull ThrowableConsumer consumer) + throws VcsException { Ref batch = new Ref<>(new TIntHashSet()); + Ref exception = new Ref<>(); stream.forEach(commit -> { batch.get().add(commit); if (batch.get().size() >= batchSize) { try { consumer.consume(batch.get()); } + catch (VcsException e) { + exception.set(e); + } finally { batch.set(new TIntHashSet()); } @@ -144,5 +152,14 @@ public class TroveUtil { if (!batch.get().isEmpty()) { consumer.consume(batch.get()); } + + if (!exception.isNull()) throw exception.get(); + } + + @NotNull + public static TIntHashSet collect(@NotNull IntStream stream) { + TIntHashSet result = new TIntHashSet(); + stream.forEach(result::add); + return result; } } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/visible/VcsLogFilterer.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/visible/VcsLogFilterer.java index c1fc62a5399a..3966041fc981 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/visible/VcsLogFilterer.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/visible/VcsLogFilterer.java @@ -61,10 +61,10 @@ public class VcsLogFilterer { } @NotNull - Pair filter(@NotNull DataPack dataPack, - @NotNull PermanentGraph.SortType sortType, - @NotNull VcsLogFilterCollection filters, - @NotNull CommitCountStage commitCount) { + protected Pair filter(@NotNull DataPack dataPack, + @NotNull PermanentGraph.SortType sortType, + @NotNull VcsLogFilterCollection filters, + @NotNull CommitCountStage commitCount) { long start = System.currentTimeMillis(); VcsLogHashFilter hashFilter = filters.getHashFilter(); @@ -77,23 +77,13 @@ public class VcsLogFilterer { Set matchingHeads = getMatchingHeads(dataPack.getRefsModel(), visibleRoots, filters); FilterByDetailsResult filterResult = filterByDetails(dataPack, filters, commitCount, visibleRoots, matchingHeads); - VisiblePack visiblePack = - createVisiblePack(dataPack, sortType, filters, matchingHeads, filterResult); + VisibleGraph visibleGraph = createVisibleGraph(dataPack, sortType, matchingHeads, filterResult.matchingCommits); + VisiblePack visiblePack = new VisiblePack(dataPack, visibleGraph, filterResult.canRequestMore, filters); LOG.debug(StopWatch.formatTime(System.currentTimeMillis() - start) + " for filtering by " + filters); return Pair.create(visiblePack, filterResult.commitCount); } - @NotNull - protected VisiblePack createVisiblePack(@NotNull DataPack dataPack, - @NotNull PermanentGraph.SortType sortType, - @NotNull VcsLogFilterCollection filters, - @Nullable Set matchingHeads, - @NotNull FilterByDetailsResult filterResult) { - VisibleGraph visibleGraph = createVisibleGraph(dataPack, sortType, matchingHeads, filterResult.matchingCommits); - return new VisiblePack(dataPack, visibleGraph, filterResult.canRequestMore, filters); - } - @NotNull protected VisibleGraph createVisibleGraph(@NotNull DataPack dataPack, @NotNull PermanentGraph.SortType sortType, @@ -110,11 +100,11 @@ public class VcsLogFilterer { } @NotNull - protected FilterByDetailsResult filterByDetails(@NotNull DataPack dataPack, - @NotNull VcsLogFilterCollection filters, - @NotNull CommitCountStage commitCount, - @NotNull Collection visibleRoots, - @Nullable Set matchingHeads) { + private FilterByDetailsResult filterByDetails(@NotNull DataPack dataPack, + @NotNull VcsLogFilterCollection filters, + @NotNull CommitCountStage commitCount, + @NotNull Collection visibleRoots, + @Nullable Set matchingHeads) { List detailsFilters = filters.getDetailsFilters(); if (detailsFilters.isEmpty()) return new FilterByDetailsResult(null, false, commitCount); @@ -129,7 +119,8 @@ public class VcsLogFilterer { } } - FilterByDetailsResult filteredWithVcs = filterWithVcs(dataPack.getPermanentGraph(), filters, detailsFilters, matchingHeads, commitCount); + FilterByDetailsResult filteredWithVcs = + filterWithVcs(dataPack.getPermanentGraph(), filters, detailsFilters, matchingHeads, commitCount); Set filteredCommits; if (filteredWidthIndex == null) { @@ -190,9 +181,9 @@ public class VcsLogFilterer { } @Nullable - private Set getMatchingHeads(@NotNull VcsLogRefs refs, - @NotNull Collection roots, - @NotNull VcsLogFilterCollection filters) { + protected Set getMatchingHeads(@NotNull VcsLogRefs refs, + @NotNull Collection roots, + @NotNull VcsLogFilterCollection filters) { VcsLogBranchFilter branchFilter = filters.getBranchFilter(); VcsLogRootFilter rootFilter = filters.getRootFilter(); VcsLogStructureFilter structureFilter = filters.getStructureFilter(); diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/visible/VisiblePackRefresherImpl.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/visible/VisiblePackRefresherImpl.java index 348edfadf8c5..332a98e64056 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/visible/VisiblePackRefresherImpl.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/visible/VisiblePackRefresherImpl.java @@ -37,23 +37,19 @@ import com.intellij.vcs.log.impl.VcsLogFilterCollectionImpl.VcsLogFilterCollecti import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collections; import java.util.List; public class VisiblePackRefresherImpl implements VisiblePackRefresher, Disposable { private static final Logger LOG = Logger.getInstance(VisiblePackRefresherImpl.class); - @NotNull private final SingleTaskController myTaskController; + @NotNull private final SingleTaskController myTaskController; @NotNull private final VcsLogFilterer myVisiblePackBuilder; @NotNull private final VcsLogData myLogData; @NotNull private final VcsLogIndex.IndexingFinishedListener myIndexingFinishedListener; @NotNull private final List myVisiblePackChangeListeners = ContainerUtil.createLockFreeCopyOnWriteList(); - @NotNull private VcsLogFilterCollection myFilters; - @NotNull private PermanentGraph.SortType mySortType; - @NotNull private CommitCountStage myCommitCount = CommitCountStage.INITIAL; - @NotNull private List myRequestsToRun = ContainerUtil.newArrayList(); - @NotNull private volatile VisiblePack myVisiblePack = VisiblePack.EMPTY; - private volatile boolean myIsValid = true; + @NotNull private volatile State myState; public VisiblePackRefresherImpl(@NotNull Project project, @NotNull VcsLogData logData, @@ -61,22 +57,26 @@ public class VisiblePackRefresherImpl implements VisiblePackRefresher, Disposabl @NotNull VcsLogFilterer builder) { myLogData = logData; myVisiblePackBuilder = builder; - myFilters = new VcsLogFilterCollectionBuilder().build(); - mySortType = initialSortType; + myState = new State(initialSortType); - myTaskController = new SingleTaskController(visiblePack -> { - myVisiblePack = visiblePack; - for (VisiblePackChangeListener listener : myVisiblePackChangeListeners) { - listener.onVisiblePackChange(visiblePack); + myTaskController = new SingleTaskController(state -> { + boolean hasChanges = myState.getVisiblePack() != state.getVisiblePack(); + myState = state; + if (hasChanges) { + for (VisiblePackChangeListener listener : myVisiblePackChangeListeners) { + listener.onVisiblePackChange(state.getVisiblePack()); + } } - }) { + }, true) { + @NotNull @Override - protected void startNewBackgroundTask() { + protected ProgressIndicator startNewBackgroundTask() { + ProgressIndicator indicator = myLogData.getProgress().createProgressIndicator(); UIUtil.invokeLaterIfNeeded(() -> { MyTask task = new MyTask(project, "Applying filters..."); - ProgressManager.getInstance().runProcessWithProgressAsynchronously(task, - myLogData.getProgress().createProgressIndicator()); + ProgressManager.getInstance().runProcessWithProgressAsynchronously(task, indicator); }); + return indicator; } }; @@ -121,7 +121,7 @@ public class VisiblePackRefresherImpl implements VisiblePackRefresher, Disposabl @Override public boolean isValid() { - return myIsValid; + return myState.isValid(); } @Override @@ -137,13 +137,17 @@ public class VisiblePackRefresherImpl implements VisiblePackRefresher, Disposabl @Override public void run(@NotNull ProgressIndicator indicator) { - VisiblePack visiblePack = null; + State state = myState; List requests; - while (!(requests = myTaskController.popRequests()).isEmpty()) { + if (!(requests = myTaskController.peekRequests()).isEmpty()) { try { - visiblePack = getVisiblePack(visiblePack, requests); + state = computeState(state, requests); + myTaskController.removeRequests(requests); } catch (ProcessCanceledException reThrown) { + LOG.debug("Filtering cancelled"); + // need to start new bg task if cancelled + myTaskController.taskCompleted(null); throw reThrown; } catch (Throwable t) { @@ -151,13 +155,15 @@ public class VisiblePackRefresherImpl implements VisiblePackRefresher, Disposabl } } - // visible pack can be null (e.g. when filter is set during initialization) => we just remember filters set by user - myTaskController.taskCompleted(visiblePack); + List requestsToRun = ContainerUtil.newArrayList(); + if (state.getVisiblePack() != myState.getVisiblePack() && state.isValid()) { + requestsToRun.addAll(state.getRequestsToRun()); + state = state.withRequests(ContainerUtil.newArrayList()); + } - if (visiblePack != null && myIsValid) { - final List requestsToRun = myRequestsToRun; - myRequestsToRun = ContainerUtil.newArrayList(); + myTaskController.taskCompleted(state); + if (!requestsToRun.isEmpty()) { ApplicationManager.getApplication().invokeLater(() -> { for (MoreCommitsRequest request : requestsToRun) { request.onLoaded.run(); @@ -166,20 +172,20 @@ public class VisiblePackRefresherImpl implements VisiblePackRefresher, Disposabl } } - @Nullable - private VisiblePack getVisiblePack(@Nullable VisiblePack visiblePack, @NotNull List requests) { + @NotNull + private State computeState(@NotNull State state, @NotNull List requests) { ValidateRequest validateRequest = ContainerUtil.findLastInstance(requests, ValidateRequest.class); FilterRequest filterRequest = ContainerUtil.findLastInstance(requests, FilterRequest.class); SortTypeRequest sortTypeRequest = ContainerUtil.findLastInstance(requests, SortTypeRequest.class); List moreCommitsRequests = ContainerUtil.findAll(requests, MoreCommitsRequest.class); List indexingRequests = ContainerUtil.findAll(requests, IndexingFinishedRequest.class); - myRequestsToRun.addAll(moreCommitsRequests); + state = state.withRequests(ContainerUtil.concat(state.getRequestsToRun(), moreCommitsRequests)); if (filterRequest != null) { - myFilters = filterRequest.filters; + state = state.withFilters(filterRequest.filters); } if (sortTypeRequest != null) { - mySortType = sortTypeRequest.sortType; + state = state.withSortType(sortTypeRequest.sortType); } // On validate requests vs refresh requests. @@ -190,25 +196,24 @@ public class VisiblePackRefresherImpl implements VisiblePackRefresher, Disposabl // instances of ValidateRequest (also we get into this method only when there are some requests in the queue). // Refresh request does not carry inside any additional information since current DataPack is just taken from VcsLogDataManager. - if (!myIsValid) { + if (!state.isValid()) { if (validateRequest != null && validateRequest.validate) { - myIsValid = true; - return refresh(visiblePack, filterRequest, moreCommitsRequests); + state = state.withValid(true); + return refresh(state, filterRequest, moreCommitsRequests); } else { // validateRequest == null || !validateRequest.validate // remember filters - return visiblePack; + return state; } } else { if (validateRequest != null && !validateRequest.validate) { - myIsValid = false; + state = state.withValid(false); // invalidate - VisiblePack frozenVisiblePack = visiblePack == null ? myVisiblePack : visiblePack; if (filterRequest != null) { - frozenVisiblePack = refresh(visiblePack, filterRequest, moreCommitsRequests); + state = refresh(state, filterRequest, moreCommitsRequests); } - return new SnapshotVisiblePackBuilder(myLogData.getStorage()).build(frozenVisiblePack); + return state.withVisiblePack(new SnapshotVisiblePackBuilder(myLogData.getStorage()).build(state.getVisiblePack())); } Request nonValidateRequest = @@ -216,39 +221,138 @@ public class VisiblePackRefresherImpl implements VisiblePackRefresher, Disposabl // only doing something if there are some other requests or a relevant indexing request if (nonValidateRequest != null) { - return refresh(visiblePack, filterRequest, moreCommitsRequests); + return refresh(state, filterRequest, moreCommitsRequests); } else if (!indexingRequests.isEmpty()) { - if (myVisiblePackBuilder - .affectedByIndexingRoots(myFilters, ContainerUtil.map(indexingRequests, IndexingFinishedRequest::getRoot))) { - return refresh(visiblePack, filterRequest, moreCommitsRequests); + if (myVisiblePackBuilder.affectedByIndexingRoots(state.getFilters(), + ContainerUtil.map(indexingRequests, IndexingFinishedRequest::getRoot))) { + return refresh(state, filterRequest, moreCommitsRequests); } } - return visiblePack; + return state; } } - private VisiblePack refresh(@Nullable VisiblePack visiblePack, - @Nullable FilterRequest filterRequest, - @NotNull List moreCommitsRequests) { + @NotNull + private State refresh(@NotNull State state, + @Nullable FilterRequest filterRequest, + @NotNull List moreCommitsRequests) { DataPack dataPack = myLogData.getDataPack(); if (dataPack == DataPack.EMPTY) { // when filter is set during initialization, just remember filters - return visiblePack; + return state; } if (filterRequest != null) { // "more commits needed" has no effect if filter changes; it also can't come after filter change request - myCommitCount = CommitCountStage.INITIAL; + state = state.withCommitCount(CommitCountStage.INITIAL); } else if (!moreCommitsRequests.isEmpty()) { - myCommitCount = myCommitCount.next(); + state = state.withCommitCount(state.getCommitCount().next()); } - Pair pair = myVisiblePackBuilder.filter(dataPack, mySortType, myFilters, myCommitCount); - visiblePack = pair.first; - myCommitCount = pair.second; - return visiblePack; + Pair pair = myVisiblePackBuilder.filter(dataPack, state.getSortType(), state.getFilters(), + state.getCommitCount()); + return state.withVisiblePack(pair.first).withCommitCount(pair.second); + } + } + + private static class State { + @NotNull private final VcsLogFilterCollection myFilters; + @NotNull private final PermanentGraph.SortType mySortType; + @NotNull private final CommitCountStage myCommitCount; + @NotNull private final List myRequestsToRun; + @NotNull private final VisiblePack myVisiblePack; + private final boolean myIsValid; + + public State(@NotNull PermanentGraph.SortType sortType) { + this(new VcsLogFilterCollectionBuilder().build(), sortType, CommitCountStage.INITIAL, ContainerUtil.newArrayList(), VisiblePack.EMPTY, + true); + } + + public State(@NotNull VcsLogFilterCollection filters, + @NotNull PermanentGraph.SortType sortType, + @NotNull CommitCountStage commitCountStage, + @NotNull List requests, + @NotNull VisiblePack visiblePack, + boolean isValid) { + myFilters = filters; + mySortType = sortType; + myCommitCount = commitCountStage; + myRequestsToRun = Collections.unmodifiableList(requests); + myVisiblePack = visiblePack; + myIsValid = isValid; + } + + public boolean isValid() { + return myIsValid; + } + + @NotNull + public VisiblePack getVisiblePack() { + return myVisiblePack; + } + + @NotNull + public List getRequestsToRun() { + return myRequestsToRun; + } + + @NotNull + public VcsLogFilterCollection getFilters() { + return myFilters; + } + + @NotNull + public PermanentGraph.SortType getSortType() { + return mySortType; + } + + @NotNull + public CommitCountStage getCommitCount() { + return myCommitCount; + } + + @NotNull + public State withValid(boolean valid) { + return new State(myFilters, mySortType, myCommitCount, myRequestsToRun, myVisiblePack, valid); + } + + @NotNull + public State withVisiblePack(@NotNull VisiblePack visiblePack) { + return new State(myFilters, mySortType, myCommitCount, myRequestsToRun, visiblePack, myIsValid); + } + + @NotNull + public State withCommitCount(@NotNull CommitCountStage commitCount) { + return new State(myFilters, mySortType, commitCount, myRequestsToRun, myVisiblePack, myIsValid); + } + + @NotNull + public State withRequests(@NotNull List requests) { + return new State(myFilters, mySortType, myCommitCount, requests, myVisiblePack, myIsValid); + } + + @NotNull + public State withFilters(@NotNull VcsLogFilterCollection filters) { + return new State(filters, mySortType, myCommitCount, myRequestsToRun, myVisiblePack, myIsValid); + } + + @NotNull + public State withSortType(@NotNull PermanentGraph.SortType type) { + return new State(myFilters, type, myCommitCount, myRequestsToRun, myVisiblePack, myIsValid); + } + + @Override + public String toString() { + return "State{" + + "myFilters=" + myFilters + + ", mySortType=" + mySortType + + ", myCommitCount=" + myCommitCount + + ", myRequestsToRun=" + myRequestsToRun + + ", myVisiblePack=" + myVisiblePack + + ", myIsValid=" + myIsValid + + '}'; } } diff --git a/platform/vcs-log/impl/test/com/intellij/vcs/log/data/VcsLogJoinerTest.kt b/platform/vcs-log/impl/test/com/intellij/vcs/log/data/VcsLogJoinerTest.kt index 7109106d2e9d..e8e0d6f92a7d 100644 --- a/platform/vcs-log/impl/test/com/intellij/vcs/log/data/VcsLogJoinerTest.kt +++ b/platform/vcs-log/impl/test/com/intellij/vcs/log/data/VcsLogJoinerTest.kt @@ -27,7 +27,7 @@ import java.util.* class VcsLogJoinerTest { - class StringArrayBuilder() { + class StringArrayBuilder { val result = ArrayList() operator fun String.unaryPlus() = result.add(this) @@ -35,7 +35,7 @@ class VcsLogJoinerTest { operator fun Collection.unaryPlus() = result.addAll(this) } - class TestRunner() { + class TestRunner { private var fullLog: List? = null private var recentCommits: List? = null private var oldRefs: List? = null diff --git a/platform/vcs-log/impl/test/com/intellij/vcs/log/data/VcsLogRefresherTest.java b/platform/vcs-log/impl/test/com/intellij/vcs/log/data/VcsLogRefresherTest.java index a466f8d41a84..f13e73ecb1e8 100644 --- a/platform/vcs-log/impl/test/com/intellij/vcs/log/data/VcsLogRefresherTest.java +++ b/platform/vcs-log/impl/test/com/intellij/vcs/log/data/VcsLogRefresherTest.java @@ -16,6 +16,8 @@ package com.intellij.vcs.log.data; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.EmptyProgressIndicator; +import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.progress.impl.ProgressManagerImpl; @@ -214,10 +216,11 @@ public class VcsLogRefresherTest extends VcsPlatformTest { myLogData.getTopCommitsCache(), dataPackConsumer, FAILING_EXCEPTION_HANDLER, RECENT_COMMITS_COUNT ) { @Override - protected void startNewBackgroundTask(@NotNull final Task.Backgroundable refreshTask) { + protected ProgressIndicator startNewBackgroundTask(@NotNull final Task.Backgroundable refreshTask) { LOG.debug("Starting a background task..."); myStartedTasks.add(((ProgressManagerImpl)ProgressManager.getInstance()).runProcessWithProgressAsynchronously(refreshTask)); LOG.debug(myStartedTasks.size() + " started tasks"); + return new EmptyProgressIndicator(); } }; } diff --git a/platform/vcs-log/impl/test/com/intellij/vcs/log/impl/TestVcsLogProvider.java b/platform/vcs-log/impl/test/com/intellij/vcs/log/impl/TestVcsLogProvider.java index 33fcf06e6f88..2db06ffb152d 100644 --- a/platform/vcs-log/impl/test/com/intellij/vcs/log/impl/TestVcsLogProvider.java +++ b/platform/vcs-log/impl/test/com/intellij/vcs/log/impl/TestVcsLogProvider.java @@ -129,13 +129,6 @@ public class TestVcsLogProvider implements VcsLogProvider { throw new UnsupportedOperationException(); } - @Override - public void readFullDetails(@NotNull VirtualFile root, - @Nullable List hashes, - @NotNull Consumer commitConsumer) throws VcsException { - throw new UnsupportedOperationException(); - } - @Override public void readFullDetails(@NotNull VirtualFile root, @NotNull List hashes, diff --git a/platform/vcs-log/impl/test/com/intellij/vcs/log/visible/VisiblePackBuilderTest.kt b/platform/vcs-log/impl/test/com/intellij/vcs/log/visible/VisiblePackBuilderTest.kt index 190a90821e09..6106d1567085 100644 --- a/platform/vcs-log/impl/test/com/intellij/vcs/log/visible/VisiblePackBuilderTest.kt +++ b/platform/vcs-log/impl/test/com/intellij/vcs/log/visible/VisiblePackBuilderTest.kt @@ -216,12 +216,12 @@ class VisiblePackBuilderTest { operator fun GraphCommit.plus(name: String): GraphCommit { data[this] = Data(VcsUserImpl(name, name + "@example.com")) - return this; + return this } operator fun GraphCommit.plus(user: VcsUser?): GraphCommit { data[this] = Data(user) - return this; + return this } fun done() = Graph(commits, refs, data) diff --git a/platform/xdebugger-api/src/com/intellij/xdebugger/XDebugSession.java b/platform/xdebugger-api/src/com/intellij/xdebugger/XDebugSession.java index ef15da544be1..80205fc2c105 100644 --- a/platform/xdebugger-api/src/com/intellij/xdebugger/XDebugSession.java +++ b/platform/xdebugger-api/src/com/intellij/xdebugger/XDebugSession.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -93,6 +93,10 @@ public interface XDebugSession extends AbstractDebuggerSession { void setCurrentStackFrame(@NotNull XExecutionStack executionStack, @NotNull XStackFrame frame, boolean isTopFrame); + default void setCurrentStackFrame(@NotNull XExecutionStack executionStack, @NotNull XStackFrame frame) { + setCurrentStackFrame(executionStack, frame, frame.equals(executionStack.getTopFrame())); + } + /** * Call this method to setup custom icon and/or error message (it will be shown in tooltip) for breakpoint * diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebugSessionImpl.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebugSessionImpl.java index affa659cc3ed..5e27c96087b5 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebugSessionImpl.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebugSessionImpl.java @@ -258,6 +258,10 @@ public class XDebugSessionImpl implements XDebugSession { return myCurrentStackFrame; } + public XExecutionStack getCurrentExecutionStack() { + return myCurrentExecutionStack; + } + @Override public XSuspendContext getSuspendContext() { return mySuspendContext; diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/ui/BreakpointsDialog.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/ui/BreakpointsDialog.java index c90cb1358871..6f24e6743cb5 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/ui/BreakpointsDialog.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/ui/BreakpointsDialog.java @@ -34,6 +34,7 @@ import com.intellij.ui.popup.util.MasterController; import com.intellij.util.SingleAlarm; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashSet; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.tree.TreeUtil; import com.intellij.xdebugger.XDebuggerBundle; @@ -339,12 +340,12 @@ public class BreakpointsDialog extends DialogWrapper { } }). setToolbarPosition(ActionToolbarPosition.TOP). - setToolbarBorder(IdeBorderFactory.createEmptyBorder()); + setToolbarBorder(JBUI.Borders.empty()); myToggleRuleActions.forEach(decorator::addExtraAction); JPanel decoratedTree = decorator.createPanel(); - decoratedTree.setBorder(IdeBorderFactory.createEmptyBorder()); + decoratedTree.setBorder(JBUI.Borders.empty()); JScrollPane pane = UIUtil.getParentOfType(JScrollPane.class, tree); if (pane != null) pane.setBorder(IdeBorderFactory.createBorder()); diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/frame/XFramesView.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/frame/XFramesView.java index 060da221eea8..56157bbd8c98 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/frame/XFramesView.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/frame/XFramesView.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -16,12 +16,10 @@ package com.intellij.xdebugger.impl.frame; import com.intellij.ide.CommonActionsManager; -import com.intellij.openapi.actionSystem.ActionGroup; -import com.intellij.openapi.actionSystem.ActionManager; -import com.intellij.openapi.actionSystem.ActionPlaces; -import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.ComboBox; import com.intellij.openapi.util.text.StringUtil; @@ -34,6 +32,7 @@ import com.intellij.xdebugger.XDebugSession; import com.intellij.xdebugger.frame.XExecutionStack; import com.intellij.xdebugger.frame.XStackFrame; import com.intellij.xdebugger.frame.XSuspendContext; +import com.intellij.xdebugger.impl.XDebugSessionImpl; import com.intellij.xdebugger.impl.actions.XDebuggerActions; import gnu.trove.TObjectIntHashMap; import org.jetbrains.annotations.NotNull; @@ -57,6 +56,9 @@ import java.util.List; * @author nik */ public class XFramesView extends XDebugView { + private static final Logger LOG = Logger.getInstance(XFramesView.class); + public static final DataKey DATA_KEY = DataKey.create("XDEBUGGER_FRAMES_VIEW"); + private final JPanel myMainPanel; private final XDebuggerFramesList myFramesList; private final ComboBox myThreadComboBox; @@ -123,7 +125,7 @@ public class XFramesView extends XDebugView { XDebugSession session = getSession(e); if (session != null) { myRefresh = false; - updateFrames((XExecutionStack)item, session); + updateFrames((XExecutionStack)item, session, null); } } } @@ -142,12 +144,14 @@ public class XFramesView extends XDebugView { @Override public void addExecutionStack(@NotNull final List executionStacks, boolean last) { ApplicationManager.getApplication().invokeLater(() -> { - myThreadComboBox.removeItem(null); addExecutionStacks(executionStacks); - ComboPopup popup = myThreadComboBox.getPopup(); - if (popup != null && popup.isVisible()) { - popup.hide(); - popup.show(); + if (last) { + myThreadComboBox.removeItem(null); + ComboPopup popup = myThreadComboBox.getPopup(); + if (popup != null && popup.isVisible()) { + popup.hide(); + popup.show(); + } } }); } @@ -173,6 +177,14 @@ public class XFramesView extends XDebugView { myMainPanel.add(myThreadsPanel, BorderLayout.NORTH); } + public void selectFrame(XExecutionStack stack, XStackFrame frame) { + myThreadComboBox.setSelectedItem(stack); + + myLaterInvocator.offer(() -> + myFramesList.setSelectedValue(frame, true) + ); + } + private ActionToolbarImpl createToolbar() { final DefaultActionGroup framesGroup = new DefaultActionGroup(); @@ -202,10 +214,11 @@ public class XFramesView extends XDebugView { return; } + XExecutionStack currentExecutionStack = ((XDebugSessionImpl)session).getCurrentExecutionStack(); XStackFrame currentStackFrame = session.getCurrentStackFrame(); XSuspendContext suspendContext = session.getSuspendContext(); - if (event == SessionEvent.FRAME_CHANGED) { + if (event == SessionEvent.FRAME_CHANGED && Objects.equals(mySelectedStack, currentExecutionStack)) { ApplicationManager.getApplication().assertIsDispatchThread(); if (currentStackFrame != null) { myFramesList.setSelectedValue(currentStackFrame, true); @@ -240,10 +253,12 @@ public class XFramesView extends XDebugView { clear(); } + XExecutionStack activeExecutionStack = mySelectedStack != null ? mySelectedStack : currentExecutionStack; + addExecutionStacks(Collections.singletonList(activeExecutionStack)); + XExecutionStack[] executionStacks = suspendContext.getExecutionStacks(); addExecutionStacks(Arrays.asList(executionStacks)); - XExecutionStack activeExecutionStack = mySelectedStack != null ? mySelectedStack : suspendContext.getActiveExecutionStack(); myThreadComboBox.setSelectedItem(activeExecutionStack); myThreadsPanel.removeAll(); myThreadsPanel.add(myToolbar.getComponent(), BorderLayout.EAST); @@ -252,7 +267,7 @@ public class XFramesView extends XDebugView { myThreadsPanel.add(myThreadComboBox, BorderLayout.CENTER); } myToolbar.setAddSeparatorFirst(!invisible); - updateFrames(activeExecutionStack, session); + updateFrames(activeExecutionStack, session, event == SessionEvent.FRAME_CHANGED ? currentStackFrame : null); }); } @@ -274,7 +289,7 @@ public class XFramesView extends XDebugView { } } - private void updateFrames(final XExecutionStack executionStack, @NotNull XDebugSession session) { + private void updateFrames(XExecutionStack executionStack, @NotNull XDebugSession session, @Nullable XStackFrame frameToSelect) { if (mySelectedStack != null) { getOrCreateBuilder(mySelectedStack, session).stop(); } @@ -283,6 +298,7 @@ public class XFramesView extends XDebugView { if (executionStack != null) { mySelectedFrameIndex = myExecutionStacksWithSelection.get(executionStack); StackFramesListBuilder builder = getOrCreateBuilder(executionStack, session); + builder.setToSelect(frameToSelect != null ? frameToSelect : mySelectedFrameIndex); myListenersEnabled = false; builder.initModel(myFramesList.getModel()); myListenersEnabled = !builder.start(); @@ -319,6 +335,7 @@ public class XFramesView extends XDebugView { private volatile boolean myRunning; private boolean myAllFramesLoaded; private final XDebugSession mySession; + private Object myToSelect; private StackFramesListBuilder(final XExecutionStack executionStack, XDebugSession session) { myExecutionStack = executionStack; @@ -326,6 +343,10 @@ public class XFramesView extends XDebugView { myStackFrames = new ArrayList<>(); } + void setToSelect(Object toSelect) { + myToSelect = toSelect; + } + @Override public void addStackFrames(@NotNull final List stackFrames, final boolean last) { addStackFrames(stackFrames, null, last); @@ -340,13 +361,14 @@ public class XFramesView extends XDebugView { addFrameListElements(stackFrames, last); if (toSelect != null) { - int index = myStackFrames.indexOf(toSelect); - if (index != -1) mySelectedFrameIndex = index; + setToSelect(toSelect); } - selectCurrentFrame(); - + myNextFrameIndex += stackFrames.size(); myAllFramesLoaded = last; + + selectCurrentFrame(); + if (last) { if (myVisibleRect != null) { myFramesList.scrollRectToVisible(myVisibleRect); @@ -420,13 +442,25 @@ public class XFramesView extends XDebugView { } private void selectCurrentFrame() { - if (mySelectedStack != null && - myFramesList.getSelectedIndex() != mySelectedFrameIndex && - myFramesList.getElementCount() > mySelectedFrameIndex && - myFramesList.getModel().get(mySelectedFrameIndex) != null) { - myFramesList.setSelectedIndex(mySelectedFrameIndex); - processFrameSelection(mySession, false); - myListenersEnabled = true; + if (myToSelect instanceof XStackFrame) { + if (!Objects.equals(myFramesList.getSelectedValue(), myToSelect) && myFramesList.getModel().contains(myToSelect)) { + myFramesList.setSelectedValue(myToSelect, true); + processFrameSelection(mySession, false); + myListenersEnabled = true; + } + if (myAllFramesLoaded && myFramesList.getSelectedValue() == null) { + LOG.error("Frame was not found, " + myToSelect.getClass() + " must correctly override equals"); + } + } + else if (myToSelect instanceof Integer) { + int selectedFrameIndex = (int)myToSelect; + if (myFramesList.getSelectedIndex() != selectedFrameIndex && + myFramesList.getElementCount() > selectedFrameIndex && + myFramesList.getModel().get(selectedFrameIndex) != null) { + myFramesList.setSelectedIndex(selectedFrameIndex); + processFrameSelection(mySession, false); + myListenersEnabled = true; + } } } diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/XDebugSessionTab.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/XDebugSessionTab.java index 7b8b0154403a..6b2ed4fe67b1 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/XDebugSessionTab.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/XDebugSessionTab.java @@ -65,6 +65,7 @@ public class XDebugSessionTab extends DebuggerSessionTabBase { public static final DataKey TAB_KEY = DataKey.create("XDebugSessionTab"); private XWatchesViewImpl myWatchesView; + private XFramesView myFramesView; private boolean myWatchesInVariables = Registry.is("debugger.watches.in.variables"); private final LinkedHashMap myViews = new LinkedHashMap<>(); @@ -171,6 +172,9 @@ public class XDebugSessionTab extends DebuggerSessionTabBase { if (XWatchesView.DATA_KEY.is(dataId)) { return myWatchesView; } + else if (XFramesView.DATA_KEY.is(dataId)) { + return myFramesView; + } else if (TAB_KEY.is(dataId)) { return this; } @@ -220,9 +224,9 @@ public class XDebugSessionTab extends DebuggerSessionTabBase { @NotNull private Content createFramesContent() { - XFramesView framesView = new XFramesView(myProject); - registerView(DebuggerContentInfo.FRAME_CONTENT, framesView); - Content framesContent = myUi.createContent(DebuggerContentInfo.FRAME_CONTENT, framesView.getMainPanel(), + myFramesView = new XFramesView(myProject); + registerView(DebuggerContentInfo.FRAME_CONTENT, myFramesView); + Content framesContent = myUi.createContent(DebuggerContentInfo.FRAME_CONTENT, myFramesView.getMainPanel(), XDebuggerBundle.message("debugger.session.tab.frames.title"), AllIcons.Debugger.Frame, null); framesContent.setCloseable(false); return framesContent; diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/XDebuggerExpressionComboBox.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/XDebuggerExpressionComboBox.java index 934a9d87fde8..2e66ef823ebc 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/XDebuggerExpressionComboBox.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/XDebuggerExpressionComboBox.java @@ -17,6 +17,7 @@ package com.intellij.xdebugger.impl.ui; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.ComboBox; @@ -190,7 +191,8 @@ public class XDebuggerExpressionComboBox extends XDebuggerEditorBase { @Override protected void onEditorCreate(EditorEx editor) { editor.putUserData(DebuggerCopyPastePreprocessor.REMOVE_NEWLINES_ON_PASTE, true); - editor.getColorsScheme().setEditorFontSize(myComboBox.getFont().getSize()); + editor.getColorsScheme().setEditorFontSize( + Math.min(myComboBox.getFont().getSize(), EditorColorsManager.getInstance().getGlobalScheme().getEditorFontSize())); } }; myDelegate.getEditorComponent().setFontInheritedFromLAF(false); diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml index 842994427404..9e04b3d58980 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml @@ -9,6 +9,7 @@ + - @@ -2221,10 +2218,10 @@ bundle="com.siyeh.InspectionGadgetsBundle" key="runtime.exec.with.non.constant.string.display.name" groupBundle="messages.InspectionsBundle" groupKey="group.names.security.issues" enabledByDefault="false" level="WARNING" implementationClass="com.siyeh.ig.security.RuntimeExecWithNonConstantStringInspection"/> - + implementationClass="com.siyeh.ig.security.SerializableDeserializableClassInSecureContextInspection"/> #ref() s unconstructable.test.case.problem.descriptor=Test case #ref is not constructable by most test runners #loc deserializable.class.in.secure.context.problem.descriptor=Class #ref may be deserialized, compromising security #loc serializable.class.in.secure.context.problem.descriptor=Class #ref may be serialized, compromising security #loc +serializable.deserializable.class.in.secure.context.problem.descriptor=Class #ref may be serialized and deserialized, compromising security #loc cloneable.class.in.secure.context.problem.descriptor=Class #ref may be cloned, compromising security #loc +cloneable.class.in.secure.context.quickfix=Add 'clone()' method which always throws exception +remove.cloneable.quickfix=Remove 'Cloneable' from implements clause non.final.clone.problem.descriptor=Non-final #ref() method, compromising security #loc non.static.inner.class.in.secure.context.problem.descriptor=Non-'static' inner class #ref, compromising security #loc runtime.exec.with.non.constant.string.problem.descriptor=Call to Runtime.#ref() with non-constant argument #loc @@ -1703,8 +1705,9 @@ if.may.be.conditional.problem.descriptor=#ref could be replaced wit if.may.be.conditional.quickfix=Replace with conditional expression if.may.be.conditional.report.method.calls.option=Report if statements containing method calls redundant.string.format.call.display.name=Redundant call to 'String.format()' -redundant.string.format.call.problem.descriptor=Redundant call to #ref #loc +redundant.string.format.call.problem.descriptor=Redundant call to #ref() #loc redundant.string.format.call.quickfix=Remove redundant call to 'String.format()' +replace.printf.with.print.quickfix=Replace 'printf()' with 'print()' junit4.test.method.in.class.extending.junit3.testcase.display.name=JUnit 4 test method in class extending JUnit 3 TestCase junit4.test.method.in.class.extending.junit3.testcase.problem.descriptor=Method #ref() annotated with '@Test' inside class extending JUnit 3 TestCase #loc ignore.test.method.in.class.extending.junit3.testcase.problem.descriptor=JUnit 3 test method #ref() annotated with '@Ignore' won't be ignored #loc @@ -1847,7 +1850,7 @@ try.with.identical.catches.display.name=Identical 'catch' branches in 'try' stat try.with.identical.catches.problem.descriptor=''catch'' branch identical to ''{0}'' branch #loc if.can.be.switch.display.name='if' replaceable with 'switch' if.can.be.switch.problem.descriptor=#ref statement replaceable with 'switch' statement #loc -if.can.be.switch.quickfix=Replace with 'switch' +if.can.be.switch.quickfix=Replace 'if' with 'switch' if.can.be.switch.minimum.branch.option=Minimum number of branches: if.can.be.switch.int.option=Suggest switch on numbers if.can.be.switch.enum.option=Suggest switch on enums diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/BaseInspectionVisitor.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/BaseInspectionVisitor.java index a347b6c754f0..7b4dd6583d56 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/BaseInspectionVisitor.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/BaseInspectionVisitor.java @@ -43,27 +43,21 @@ public abstract class BaseInspectionVisitor extends JavaElementVisitor { return onTheFly; } - protected final void registerNewExpressionError( - @NotNull PsiNewExpression expression, Object... infos) { - final PsiJavaCodeReferenceElement classReference = - expression.getClassOrAnonymousClassReference(); + protected final void registerNewExpressionError(@NotNull PsiNewExpression expression, Object... infos) { + final PsiJavaCodeReferenceElement classReference = expression.getClassOrAnonymousClassReference(); if (classReference == null) { - registerError(expression, infos); - } - else { - registerError(classReference, infos); + return; } + registerError(classReference, infos); } protected final void registerMethodCallError(@NotNull PsiMethodCallExpression expression, @NonNls Object... infos) { final PsiReferenceExpression methodExpression = expression.getMethodExpression(); final PsiElement nameToken = methodExpression.getReferenceNameElement(); if (nameToken == null) { - registerError(expression, infos); - } - else { - registerError(nameToken, infos); + return; } + registerError(nameToken, infos); } protected final void registerStatementError(@NotNull PsiStatement statement, Object... infos) { @@ -77,8 +71,7 @@ public abstract class BaseInspectionVisitor extends JavaElementVisitor { } protected final void registerModuleError(@NotNull PsiJavaModule module, Object... infos) { - final PsiJavaModuleReferenceElement identifier = module.getNameIdentifier(); - registerError(identifier, infos); + registerError(module.getNameIdentifier(), infos); } protected final void registerClassError(@NotNull PsiClass aClass, Object... infos) { @@ -96,67 +89,40 @@ public abstract class BaseInspectionVisitor extends JavaElementVisitor { nameIdentifier = aClass.getNameIdentifier(); } if (nameIdentifier == null) { - registerError(aClass.getContainingFile(), infos); + return; } - else { - registerError(nameIdentifier, infos); - } - } - - protected final void registerMethodError(@NotNull PsiMethod method, - Object... infos) { - final PsiElement nameIdentifier = method.getNameIdentifier(); - if (nameIdentifier == null) { - final LocalQuickFix[] fixes = createAndInitFixes(infos); - final String description = inspection.buildErrorString(infos); - - final TextRange methodTextRange; - PsiCodeBlock body = method.getBody(); - if (body != null) { - methodTextRange = new TextRange(0, body.getStartOffsetInParent()); - } - else { - methodTextRange = new TextRange(0, method.getTextLength()); - } - - holder.registerProblem(method, methodTextRange, description, fixes); - } - else { - registerError(nameIdentifier, infos); - } - } - - protected final void registerVariableError(@NotNull PsiVariable variable, - Object... infos) { - final PsiElement nameIdentifier = variable.getNameIdentifier(); - if (nameIdentifier == null) { - registerError(variable, infos); - } - else { - registerError(nameIdentifier, infos); - } - } - - protected final void registerTypeParameterError( - @NotNull PsiTypeParameter typeParameter, Object... infos) { - final PsiElement nameIdentifier = typeParameter.getNameIdentifier(); - if (nameIdentifier == null) { - registerError(typeParameter, infos); - } - else { - registerError(nameIdentifier, infos); - } - } - - protected final void registerFieldError(@NotNull PsiField field, - Object... infos) { - final PsiElement nameIdentifier = field.getNameIdentifier(); registerError(nameIdentifier, infos); } - protected final void registerModifierError( - @NotNull String modifier, @NotNull PsiModifierListOwner parameter, - Object... infos) { + protected final void registerMethodError(@NotNull PsiMethod method, Object... infos) { + final PsiElement nameIdentifier = method.getNameIdentifier(); + if (nameIdentifier == null) { + return; + } + registerError(nameIdentifier, infos); + } + + protected final void registerVariableError(@NotNull PsiVariable variable, Object... infos) { + final PsiElement nameIdentifier = variable.getNameIdentifier(); + if (nameIdentifier == null) { + return; + } + registerError(nameIdentifier, infos); + } + + protected final void registerTypeParameterError(@NotNull PsiTypeParameter typeParameter, Object... infos) { + final PsiElement nameIdentifier = typeParameter.getNameIdentifier(); + if (nameIdentifier == null) { + return; + } + registerError(nameIdentifier, infos); + } + + protected final void registerFieldError(@NotNull PsiField field, Object... infos) { + registerError(field.getNameIdentifier(), infos); + } + + protected final void registerModifierError(@NotNull String modifier, @NotNull PsiModifierListOwner parameter, Object... infos) { final PsiModifierList modifiers = parameter.getModifierList(); if (modifiers == null) { return; @@ -170,8 +136,7 @@ public abstract class BaseInspectionVisitor extends JavaElementVisitor { } } - protected final void registerClassInitializerError( - @NotNull PsiClassInitializer initializer, Object... infos) { + protected final void registerClassInitializerError(@NotNull PsiClassInitializer initializer, Object... infos) { final PsiCodeBlock body = initializer.getBody(); final PsiJavaToken lBrace = body.getLBrace(); if (lBrace == null) { @@ -182,8 +147,7 @@ public abstract class BaseInspectionVisitor extends JavaElementVisitor { } } - protected final void registerError(@NotNull PsiElement location, - Object... infos) { + protected final void registerError(@NotNull PsiElement location, Object... infos) { registerError(location, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, infos); } diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/abstraction/BooleanParameterInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/abstraction/BooleanParameterInspection.java index 1621331169e7..2c88dfb2f0c0 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/abstraction/BooleanParameterInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/abstraction/BooleanParameterInspection.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -83,7 +83,7 @@ public class BooleanParameterInspection extends BaseInspection { return; } } - if (PropertyUtil.isSimpleSetter(method) || LibraryUtil.isOverrideOfLibraryMethod(method)) { + if (PropertyUtil.isSimplePropertySetter(method) || LibraryUtil.isOverrideOfLibraryMethod(method)) { return; } final PsiParameterList parameterList = method.getParameterList(); @@ -91,7 +91,7 @@ public class BooleanParameterInspection extends BaseInspection { int count = 0; for (PsiParameter parameter : parameters) { final PsiType type = parameter.getType(); - if (!PsiPrimitiveType.BOOLEAN.equals(type)) { + if (!PsiType.BOOLEAN.equals(type)) { continue; } count++; diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/MismatchedCollectionQueryUpdateInspectionBase.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/MismatchedCollectionQueryUpdateInspectionBase.java index 486e0c02f8b1..18bda9da216c 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/MismatchedCollectionQueryUpdateInspectionBase.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/MismatchedCollectionQueryUpdateInspectionBase.java @@ -200,7 +200,8 @@ public class MismatchedCollectionQueryUpdateInspectionBase extends BaseInspectio } final boolean written = collectionContentsAreUpdated(field, containingClass); final boolean read = collectionContentsAreQueried(field, containingClass); - if (read == written || UnusedSymbolUtil.isImplicitWrite(field)) { + if (read == written || UnusedSymbolUtil.isImplicitWrite(field) || UnusedSymbolUtil.isImplicitRead(field)) { + // Even implicit read of the mutable collection field may cause collection change return; } registerFieldError(field, Boolean.valueOf(written)); diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/ReturnNullInspectionBase.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/ReturnNullInspectionBase.java index 5fb10a5e6a43..5b23642ad47c 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/ReturnNullInspectionBase.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/ReturnNullInspectionBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2003-2016 Dave Griffith, Bas Leijdekkers + * Copyright 2003-2017 Dave Griffith, Bas Leijdekkers * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -203,7 +203,7 @@ public class ReturnNullInspectionBase extends BaseInspection { registerError(value, value); } } - else { + else if (!returnType.equalsToText("java.lang.Void")){ if (m_reportObjectMethods) { registerError(value, value); } diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/cloneable/CloneInNonCloneableClassInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/cloneable/CloneInNonCloneableClassInspection.java index a87d86d91a41..4befbe44a575 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/cloneable/CloneInNonCloneableClassInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/cloneable/CloneInNonCloneableClassInspection.java @@ -1,5 +1,5 @@ /* - * Copyright 2003-2015 Dave Griffith, Bas Leijdekkers + * Copyright 2003-2017 Dave Griffith, Bas Leijdekkers * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import com.siyeh.ig.BaseInspectionVisitor; import com.siyeh.ig.InspectionGadgetsFix; import com.siyeh.ig.fixes.DelegatingFixFactory; import com.siyeh.ig.psiutils.CloneUtils; +import com.siyeh.ig.psiutils.ControlFlowUtils; import org.jdom.Element; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -89,7 +90,7 @@ public class CloneInNonCloneableClassInspection extends BaseInspection { return; } final PsiClass containingClass = method.getContainingClass(); - if (CloneUtils.isCloneable(containingClass) || CloneUtils.onlyThrowsException(method)) { + if (CloneUtils.isCloneable(containingClass) || ControlFlowUtils.methodAlwaysThrowsException(method)) { return; } registerMethodError(method, containingClass); diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/cloneable/UseOfCloneInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/cloneable/UseOfCloneInspection.java index 879c98982b3b..544975a4d099 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/cloneable/UseOfCloneInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/cloneable/UseOfCloneInspection.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -21,6 +21,7 @@ import com.siyeh.InspectionGadgetsBundle; import com.siyeh.ig.BaseInspection; import com.siyeh.ig.BaseInspectionVisitor; import com.siyeh.ig.psiutils.CloneUtils; +import com.siyeh.ig.psiutils.ControlFlowUtils; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; @@ -100,7 +101,7 @@ public class UseOfCloneInspection extends BaseInspection { @Override public void visitMethod(PsiMethod method) { - if (!CloneUtils.isClone(method) || CloneUtils.onlyThrowsException(method)) { + if (!CloneUtils.isClone(method) || ControlFlowUtils.methodAlwaysThrowsException(method)) { return; } registerMethodError(method, method); diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/inheritance/RefusedBequestInspectionBase.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/inheritance/RefusedBequestInspectionBase.java index 8ac48d730680..bb6771942670 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/inheritance/RefusedBequestInspectionBase.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/inheritance/RefusedBequestInspectionBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2003-2016 Dave Griffith, Bas Leijdekkers + * Copyright 2003-2017 Dave Griffith, Bas Leijdekkers * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import com.siyeh.InspectionGadgetsBundle; import com.siyeh.ig.BaseInspection; import com.siyeh.ig.BaseInspectionVisitor; import com.siyeh.ig.psiutils.CloneUtils; +import com.siyeh.ig.psiutils.ControlFlowUtils; import com.siyeh.ig.psiutils.MethodCallUtils; import com.siyeh.ig.psiutils.MethodUtils; import com.siyeh.ig.ui.ExternalizableStringSet; @@ -134,12 +135,7 @@ public class RefusedBequestInspectionBase extends BaseInspection { return; } } - final PsiClass aClass = method.getContainingClass(); - if ((aClass != null && aClass.hasModifierProperty(PsiModifier.FINAL) || method.hasModifierProperty(PsiModifier.FINAL)) && - MethodUtils.isTrivial(method, true)) { - return; - } - if (MethodCallUtils.containsSuperMethodCall(method)) { + if (MethodCallUtils.containsSuperMethodCall(method) || ControlFlowUtils.methodAlwaysThrowsException(method)) { return; } registerMethodError(method); diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/junit/JUnitTestClassNamingConventionInspectionBase.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/junit/JUnitTestClassNamingConventionInspectionBase.java index 05727e23fb47..b76b701159b5 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/junit/JUnitTestClassNamingConventionInspectionBase.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/junit/JUnitTestClassNamingConventionInspectionBase.java @@ -23,6 +23,8 @@ import com.siyeh.ig.BaseInspectionVisitor; import com.siyeh.ig.naming.ConventionInspection; import org.jetbrains.annotations.NotNull; +import java.util.Set; + public class JUnitTestClassNamingConventionInspectionBase extends ConventionInspection { private static final int DEFAULT_MIN_LENGTH = 8; private static final int DEFAULT_MAX_LENGTH = 64; @@ -88,8 +90,8 @@ public class JUnitTestClassNamingConventionInspectionBase extends ConventionInsp return; } - final TestFramework framework = TestFrameworks.detectFramework(aClass); - if (framework == null || !framework.getName().startsWith("JUnit") || !framework.isTestClass(aClass)) { + final Set frameworks = TestFrameworks.detectApplicableFrameworks(aClass); + if (frameworks.stream().noneMatch(framework -> framework.getName().startsWith("JUnit") && framework.isTestClass(aClass))) { return; } diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/migration/CollectionsFieldAccessReplaceableByMethodCallInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/migration/CollectionsFieldAccessReplaceableByMethodCallInspection.java index 3b50ae36bd5c..20170af1942a 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/migration/CollectionsFieldAccessReplaceableByMethodCallInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/migration/CollectionsFieldAccessReplaceableByMethodCallInspection.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2014 Bas Leijdekkers + * Copyright 2000-2017 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. @@ -20,54 +20,50 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.util.PsiUtil; -import com.intellij.util.IncorrectOperationException; import com.siyeh.InspectionGadgetsBundle; import com.siyeh.ig.BaseInspection; import com.siyeh.ig.BaseInspectionVisitor; import com.siyeh.ig.InspectionGadgetsFix; import com.siyeh.ig.PsiReplacementUtil; +import com.siyeh.ig.psiutils.ComparisonUtils; import com.siyeh.ig.psiutils.ExpectedTypeUtils; +import com.siyeh.ig.psiutils.ParenthesesUtils; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class CollectionsFieldAccessReplaceableByMethodCallInspection - extends BaseInspection { +/** + * @author Bas Leijdekkers + */ +public class CollectionsFieldAccessReplaceableByMethodCallInspection extends BaseInspection { @Override @Nls @NotNull public String getDisplayName() { - return InspectionGadgetsBundle.message( - "collections.field.access.replaceable.by.method.call.display.name"); + return InspectionGadgetsBundle.message("collections.field.access.replaceable.by.method.call.display.name"); } @Override @NotNull protected String buildErrorString(Object... infos) { - return InspectionGadgetsBundle.message( - "collections.field.access.replaceable.by.method.call.problem.descriptor", - infos[1]); + return InspectionGadgetsBundle.message("collections.field.access.replaceable.by.method.call.problem.descriptor", infos[1]); } @Override @Nullable protected InspectionGadgetsFix buildFix(Object... infos) { - final PsiReferenceExpression expression = - (PsiReferenceExpression)infos[0]; - return new CollectionsFieldAccessReplaceableByMethodCallFix( - expression.getReferenceName()); + final PsiReferenceExpression expression = (PsiReferenceExpression)infos[0]; + return new CollectionsFieldAccessReplaceableByMethodCallFix(expression.getReferenceName()); } - private static class CollectionsFieldAccessReplaceableByMethodCallFix - extends InspectionGadgetsFix { + private static class CollectionsFieldAccessReplaceableByMethodCallFix extends InspectionGadgetsFix { private final String replacementText; - private CollectionsFieldAccessReplaceableByMethodCallFix( - String referenceName) { - replacementText = getCollectionsMethodCallText(referenceName); + CollectionsFieldAccessReplaceableByMethodCallFix(String referenceName) { + replacementText = getCollectionsMethodCallName(referenceName); } @NotNull @@ -79,21 +75,17 @@ public class CollectionsFieldAccessReplaceableByMethodCallInspection @Override @NotNull public String getName() { - return InspectionGadgetsBundle.message( - "collections.field.access.replaceable.by.method.call.quickfix", - replacementText); + return InspectionGadgetsBundle.message("collections.field.access.replaceable.by.method.call.quickfix", replacementText); } @NonNls - private static String getCollectionsMethodCallText( - PsiReferenceExpression referenceExpression) { + private static String getCollectionsMethodCallText(PsiReferenceExpression referenceExpression) { final String referenceName = referenceExpression.getReferenceName(); final PsiElement parent = referenceExpression.getParent(); if (!(parent instanceof PsiExpressionList)) { return getUntypedCollectionsMethodCallText(referenceName); } - final PsiType type = ExpectedTypeUtils.findExpectedType( - referenceExpression, false); + final PsiType type = ExpectedTypeUtils.findExpectedType(referenceExpression, false); if (!(type instanceof PsiClassType)) { return getUntypedCollectionsMethodCallText(referenceName); } @@ -101,16 +93,13 @@ public class CollectionsFieldAccessReplaceableByMethodCallInspection final PsiType[] parameterTypes = classType.getParameters(); boolean useTypeParameter = false; final String[] canonicalTexts = new String[parameterTypes.length]; - for (int i = 0, parameterTypesLength = parameterTypes.length; - i < parameterTypesLength; i++) { + for (int i = 0, parameterTypesLength = parameterTypes.length; i < parameterTypesLength; i++) { final PsiType parameterType = parameterTypes[i]; if (parameterType instanceof PsiWildcardType) { - final PsiWildcardType wildcardType = - (PsiWildcardType)parameterType; + final PsiWildcardType wildcardType = (PsiWildcardType)parameterType; final PsiType bound = wildcardType.getBound(); if (bound != null) { - if (!bound.equalsToText( - CommonClassNames.JAVA_LANG_OBJECT)) { + if (!bound.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) { useTypeParameter = true; } canonicalTexts[i] = bound.getCanonicalText(); @@ -120,31 +109,24 @@ public class CollectionsFieldAccessReplaceableByMethodCallInspection } } else { - if (!parameterType.equalsToText( - CommonClassNames.JAVA_LANG_OBJECT)) { + if (!parameterType.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) { useTypeParameter = true; } canonicalTexts[i] = parameterType.getCanonicalText(); } } - if (useTypeParameter) { - return "Collections.<" + StringUtil.join(canonicalTexts, ",") + - '>' + getCollectionsMethodCallText(referenceName); - } - else { - return getUntypedCollectionsMethodCallText(referenceName); - } + return useTypeParameter + ? "Collections.<" + StringUtil.join(canonicalTexts, ",") + '>' + getCollectionsMethodCallName(referenceName) + : getUntypedCollectionsMethodCallText(referenceName); } @NonNls - private static String getUntypedCollectionsMethodCallText( - String referenceName) { - return "Collections." + getCollectionsMethodCallText(referenceName); + private static String getUntypedCollectionsMethodCallText(String referenceName) { + return "Collections." + getCollectionsMethodCallName(referenceName); } @NonNls - private static String getCollectionsMethodCallText( - @NonNls String referenceName) { + private static String getCollectionsMethodCallName(@NonNls String referenceName) { if ("EMPTY_LIST".equals(referenceName)) { return "emptyList()"; } @@ -155,24 +137,19 @@ public class CollectionsFieldAccessReplaceableByMethodCallInspection return "emptySet()"; } else { - throw new AssertionError("unknown collections field name: " + - referenceName); + throw new AssertionError("unknown collections field name: " + referenceName); } } @Override - protected void doFix(Project project, ProblemDescriptor descriptor) - throws IncorrectOperationException { + protected void doFix(Project project, ProblemDescriptor descriptor) { final PsiElement element = descriptor.getPsiElement(); if (!(element instanceof PsiReferenceExpression)) { return; } - final PsiReferenceExpression referenceExpression = - (PsiReferenceExpression)element; - final String newMethodCallText = - getCollectionsMethodCallText(referenceExpression); - PsiReplacementUtil.replaceExpression(referenceExpression, - "java.util." + newMethodCallText); + final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)element; + final String newMethodCallText = getCollectionsMethodCallText(referenceExpression); + PsiReplacementUtil.replaceExpression(referenceExpression, "java.util." + newMethodCallText); } } @@ -186,12 +163,10 @@ public class CollectionsFieldAccessReplaceableByMethodCallInspection return PsiUtil.isLanguageLevel5OrHigher(file); } - private static class CollectionsFieldAccessReplaceableByMethodCallVisitor - extends BaseInspectionVisitor { + private static class CollectionsFieldAccessReplaceableByMethodCallVisitor extends BaseInspectionVisitor { @Override - public void visitReferenceExpression( - PsiReferenceExpression expression) { + public void visitReferenceExpression(PsiReferenceExpression expression) { super.visitReferenceExpression(expression); @NonNls final String name = expression.getReferenceName(); @NonNls final String replacement; @@ -220,6 +195,10 @@ public class CollectionsFieldAccessReplaceableByMethodCallInspection if (!CommonClassNames.JAVA_UTIL_COLLECTIONS.equals(qualifiedName)) { return; } + final PsiElement parent = ParenthesesUtils.getParentSkipParentheses(expression); + if (parent instanceof PsiExpression && ComparisonUtils.isEqualityComparison((PsiExpression)parent)) { + return; + } registerError(expression, expression, replacement); } } diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/migration/EqualsReplaceableByObjectsCallInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/migration/EqualsReplaceableByObjectsCallInspection.java index 71bec70f6aae..c8b2af900187 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/migration/EqualsReplaceableByObjectsCallInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/migration/EqualsReplaceableByObjectsCallInspection.java @@ -125,7 +125,7 @@ public class EqualsReplaceableByObjectsCallInspection extends BaseInspection { if (qualifierExpression instanceof PsiThisExpression || qualifierExpression instanceof PsiSuperExpression) { return; } - if (isNotNullLiteralOrConstant(qualifierExpression)) { + if (isNotNullExpressionOrConstant(qualifierExpression)) { return; } final PsiElement parentExpression = @@ -276,7 +276,7 @@ public class EqualsReplaceableByObjectsCallInspection extends BaseInspection { } } - private static boolean isNotNullLiteralOrConstant(PsiExpression expression) { + private static boolean isNotNullExpressionOrConstant(PsiExpression expression) { int preventEndlessLoop = 5; expression = ParenthesesUtils.stripParentheses(expression); while (expression instanceof PsiReferenceExpression) { @@ -288,11 +288,7 @@ public class EqualsReplaceableByObjectsCallInspection extends BaseInspection { expression instanceof PsiClassObjectAccessExpression) { return true; } - if (expression instanceof PsiLiteralExpression) { - final PsiType type = expression.getType(); - return type != null && !PsiType.NULL.equals(type); - } - return false; + return PsiUtil.isConstantExpression(expression); } @Nullable diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/migration/IfCanBeSwitchInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/migration/IfCanBeSwitchInspection.java index 446d533a1372..fe4087e67bb3 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/migration/IfCanBeSwitchInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/migration/IfCanBeSwitchInspection.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 Bas Leijdekkers + * Copyright 2011-2017 Bas Leijdekkers * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package com.siyeh.ig.migration; import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.InvalidDataException; @@ -29,10 +30,7 @@ import com.siyeh.InspectionGadgetsBundle; import com.siyeh.ig.BaseInspection; import com.siyeh.ig.BaseInspectionVisitor; import com.siyeh.ig.InspectionGadgetsFix; -import com.siyeh.ig.psiutils.ControlFlowUtils; -import com.siyeh.ig.psiutils.EquivalenceChecker; -import com.siyeh.ig.psiutils.ExpressionUtils; -import com.siyeh.ig.psiutils.SwitchUtils; +import com.siyeh.ig.psiutils.*; import com.siyeh.ig.psiutils.SwitchUtils.IfStatementBranch; import org.jdom.Element; import org.jetbrains.annotations.Nls; @@ -515,10 +513,17 @@ public class IfCanBeSwitchInspection extends BaseInspection { if (parent instanceof PsiIfStatement) { return; } - final PsiExpression switchExpression = SwitchUtils.getSwitchExpression(statement, minimumBranches, onlySuggestNullSafe, false); + final PsiExpression switchExpression = SwitchUtils.getSwitchExpression(statement, minimumBranches, false, true); if (switchExpression == null) { return; } + final ProblemHighlightType highlightType = shouldHighlight(switchExpression) + ? ProblemHighlightType.GENERIC_ERROR_OR_WARNING + : ProblemHighlightType.INFORMATION; + registerError(statement.getFirstChild(), highlightType, switchExpression); + } + + private boolean shouldHighlight(PsiExpression switchExpression) { final PsiType type = switchExpression.getType(); if (!suggestIntSwitches) { if (type instanceof PsiClassType) { @@ -526,21 +531,38 @@ public class IfCanBeSwitchInspection extends BaseInspection { type.equalsToText(CommonClassNames.JAVA_LANG_SHORT) || type.equalsToText(CommonClassNames.JAVA_LANG_BYTE) || type.equalsToText(CommonClassNames.JAVA_LANG_CHARACTER)) { - return; + return false; } } else if (PsiType.INT.equals(type) || PsiType.SHORT.equals(type) || PsiType.BYTE.equals(type) || PsiType.CHAR.equals(type)) { - return; + return false; } } - if (!suggestEnumSwitches && type instanceof PsiClassType) { + if (type instanceof PsiClassType) { final PsiClassType classType = (PsiClassType)type; final PsiClass aClass = classType.resolve(); - if (aClass == null || aClass.isEnum()) { - return; + if (aClass == null) { + return false; + } + if (!suggestEnumSwitches && aClass.isEnum()) { + return false; + } + if (CommonClassNames.JAVA_LANG_STRING.equals(aClass.getQualifiedName())) { + final PsiElement parent = ParenthesesUtils.getParentSkipParentheses(switchExpression); + if (parent instanceof PsiExpressionList && onlySuggestNullSafe && !ExpressionUtils.isAnnotatedNotNull(switchExpression)) { + final PsiElement grandParent = parent.getParent(); + if (grandParent instanceof PsiMethodCallExpression) { + final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)grandParent; + if ("equals".equals(methodCallExpression.getMethodExpression().getReferenceName())) { + // Objects.equals(switchExpression, other) or other.equals(switchExpression) + return false; + } + } + } + return !(parent instanceof PsiPolyadicExpression); // == expression } } - registerStatementError(statement, switchExpression); + return !SideEffectChecker.mayHaveSideEffects(switchExpression); } } } diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/performance/RedundantStringFormatCallInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/performance/RedundantStringFormatCallInspection.java index b73de0d927df..ad572a146779 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/performance/RedundantStringFormatCallInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/performance/RedundantStringFormatCallInspection.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2013 Bas Leijdekkers + * Copyright 2008-2017 Bas Leijdekkers * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import com.siyeh.InspectionGadgetsBundle; import com.siyeh.ig.BaseInspection; import com.siyeh.ig.BaseInspectionVisitor; import com.siyeh.ig.InspectionGadgetsFix; +import com.siyeh.ig.PsiReplacementUtil; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -44,7 +45,8 @@ public class RedundantStringFormatCallInspection extends BaseInspection { @Override protected InspectionGadgetsFix buildFix(Object... infos) { - return new RedundantStringFormatCallFix(); + final Boolean printf = (Boolean)infos[0]; + return printf.booleanValue() ? new ReplaceWithPrintFix() : new RedundantStringFormatCallFix(); } @Override @@ -52,13 +54,12 @@ public class RedundantStringFormatCallInspection extends BaseInspection { return true; } - private static class RedundantStringFormatCallFix extends InspectionGadgetsFix { - - @Override + private static class ReplaceWithPrintFix extends InspectionGadgetsFix { + @Nls @NotNull + @Override public String getFamilyName() { - return InspectionGadgetsBundle.message( - "redundant.string.format.call.quickfix"); + return InspectionGadgetsBundle.message("replace.printf.with.print.quickfix"); } @Override @@ -70,10 +71,34 @@ public class RedundantStringFormatCallInspection extends BaseInspection { return; } final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)grandParent; - final PsiExpressionList argumentList = methodCallExpression.getArgumentList(); - final PsiExpression[] arguments = argumentList.getExpressions(); - final PsiExpression lastArgument = arguments[arguments.length - 1]; - methodCallExpression.replace(lastArgument); + final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression(); + @NonNls final StringBuilder newExpression = new StringBuilder(); + final PsiExpression qualifierExpression = methodExpression.getQualifierExpression(); + if (qualifierExpression != null) { + newExpression.append(qualifierExpression.getText()).append('.'); + } + newExpression.append("print").append(methodCallExpression.getArgumentList().getText()); + PsiReplacementUtil.replaceExpression(methodCallExpression, newExpression.toString()); + } + } + + private static class RedundantStringFormatCallFix extends InspectionGadgetsFix { + + @Override + @NotNull + public String getFamilyName() { + return InspectionGadgetsBundle.message("redundant.string.format.call.quickfix"); + } + + @Override + protected void doFix(Project project, ProblemDescriptor descriptor) { + final PsiElement element = descriptor.getPsiElement().getParent().getParent(); + if (!(element instanceof PsiMethodCallExpression)) { + return; + } + final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)element; + final PsiExpression[] arguments = methodCallExpression.getArgumentList().getExpressions(); + methodCallExpression.replace(arguments[arguments.length - 1]); } } @@ -89,7 +114,14 @@ public class RedundantStringFormatCallInspection extends BaseInspection { super.visitMethodCallExpression(expression); final PsiReferenceExpression methodExpression = expression.getMethodExpression(); @NonNls final String methodName = methodExpression.getReferenceName(); - if (!"format".equals(methodName)) { + final boolean printf; + if ("format".equals(methodName)) { + printf = false; + } + else if ("printf".equals(methodName)) { + printf = true; + } + else { return; } final PsiExpressionList argumentList = expression.getArgumentList(); @@ -106,7 +138,12 @@ public class RedundantStringFormatCallInspection extends BaseInspection { return; } final String className = aClass.getQualifiedName(); - if (!CommonClassNames.JAVA_LANG_STRING.equals(className)) { + if (printf) { + if (!"java.io.PrintStream".equals(className)) { + return; + } + } + else if (!CommonClassNames.JAVA_LANG_STRING.equals(className)) { return; } final PsiExpression firstArgument = arguments[0]; @@ -116,7 +153,7 @@ public class RedundantStringFormatCallInspection extends BaseInspection { } if (firstType.equalsToText(CommonClassNames.JAVA_LANG_STRING)) { if (arguments.length == 1 && !containsPercentN(firstArgument)) { - registerMethodCallError(expression); + registerMethodCallError(expression, Boolean.valueOf(printf)); } } else if (firstType.equalsToText("java.util.Locale")) { @@ -131,7 +168,7 @@ public class RedundantStringFormatCallInspection extends BaseInspection { if (containsPercentN(secondArgument)) { return; } - registerMethodCallError(expression); + registerMethodCallError(expression, Boolean.valueOf(printf)); } } diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/BoolUtils.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/BoolUtils.java index 0007b2372553..4605e5ede4a7 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/BoolUtils.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/BoolUtils.java @@ -120,17 +120,24 @@ public class BoolUtils { return result.toString(); } if(tokenType.equals(JavaTokenType.ANDAND) || tokenType.equals(JavaTokenType.OROR)) { - String targetToken = tokenType.equals(JavaTokenType.ANDAND) ? "||" : "&&"; + final String targetToken; + final boolean needParenthesis; + if (tokenType.equals(JavaTokenType.ANDAND)) { + targetToken = "||"; + needParenthesis = ParenthesesUtils.OR_PRECEDENCE > precedence; + } + else { + targetToken = "&&"; + needParenthesis = ParenthesesUtils.AND_PRECEDENCE > precedence; + } Function replacer = child -> { if (child instanceof PsiExpression) { return getNegatedExpressionText((PsiExpression)child); } - if (child instanceof PsiJavaToken && ((PsiJavaToken)child).getTokenType().equals(tokenType)) { - return targetToken; - } - return child.getText(); + return child instanceof PsiJavaToken ? targetToken : child.getText(); }; - return StringUtil.join(polyadicExpression.getChildren(), replacer, ""); + final String join = StringUtil.join(polyadicExpression.getChildren(), replacer, ""); + return needParenthesis ? '(' + join + ')' : join; } } return '!' + ParenthesesUtils.getText(expression, ParenthesesUtils.PREFIX_PRECEDENCE); diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/CloneUtils.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/CloneUtils.java index 770a1ba4c596..c6651f445205 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/CloneUtils.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/CloneUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2003-2016 Dave Griffith, Bas Leijdekkers + * Copyright 2003-2017 Dave Griffith, Bas Leijdekkers * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,18 +59,6 @@ public class CloneUtils { } javaLangObject = null; } - return MethodUtils.methodMatches(method, null, javaLangObject, - HardcodedMethodConstants.CLONE, PsiType.EMPTY_ARRAY); - } - - public static boolean onlyThrowsException(@NotNull PsiMethod method) { - if (!method.hasModifierProperty(PsiModifier.FINAL)) { - final PsiClass aClass = method.getContainingClass(); - if (aClass == null || !aClass.hasModifierProperty(PsiModifier.FINAL)) { - return false; - } - } - final PsiStatement statement = ControlFlowUtils.getLastStatementInBlock(method.getBody()); - return statement instanceof PsiThrowStatement; + return MethodUtils.methodMatches(method, null, javaLangObject, HardcodedMethodConstants.CLONE, PsiType.EMPTY_ARRAY); } } diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/StreamApiUtil.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/StreamApiUtil.java new file mode 100644 index 000000000000..ca11685d59ca --- /dev/null +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/StreamApiUtil.java @@ -0,0 +1,83 @@ +/* + * Copyright 2000-2017 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.siyeh.ig.psiutils; + +import com.intellij.psi.*; +import com.intellij.psi.util.PsiUtil; +import org.jetbrains.annotations.Contract; + +/** + * @author Tagir Valeev + */ +public class StreamApiUtil { + @Contract("null -> null") + public static PsiType getStreamElementType(PsiType type) { + return getStreamElementType(type, true); + } + + @Contract("null, _ -> null") + public static PsiType getStreamElementType(PsiType type, boolean variableType) { + if(!(type instanceof PsiClassType)) return null; + PsiClass aClass = ((PsiClassType)type).resolve(); + if(com.intellij.psi.util.InheritanceUtil.isInheritor(aClass, false, CommonClassNames.JAVA_UTIL_STREAM_INT_STREAM)) { + return PsiType.INT; + } + if(com.intellij.psi.util.InheritanceUtil.isInheritor(aClass, false, CommonClassNames.JAVA_UTIL_STREAM_LONG_STREAM)) { + return PsiType.LONG; + } + if(com.intellij.psi.util.InheritanceUtil.isInheritor(aClass, false, CommonClassNames.JAVA_UTIL_STREAM_DOUBLE_STREAM)) { + return PsiType.DOUBLE; + } + if(!com.intellij.psi.util.InheritanceUtil.isInheritor(aClass, false, CommonClassNames.JAVA_UTIL_STREAM_STREAM)) { + return null; + } + PsiType streamType = PsiUtil.substituteTypeParameter(type, CommonClassNames.JAVA_UTIL_STREAM_STREAM, 0, false); + if (variableType) { + if (streamType instanceof PsiIntersectionType) { + return null; + } + streamType = GenericsUtil.getVariableTypeByExpressionType(streamType); + } + return streamType; + } + + public static boolean isNullOrEmptyStream(PsiExpression expression) { + if(ExpressionUtils.isNullLiteral(expression)) { + return true; + } + if (!(expression instanceof PsiMethodCallExpression)) return false; + PsiMethodCallExpression call = (PsiMethodCallExpression)expression; + String name = call.getMethodExpression().getReferenceName(); + if ((!"empty".equals(name) && !"of".equals(name)) || !(call.getArgumentList().getExpressions().length == 0)) { + return false; + } + PsiMethod method = call.resolveMethod(); + if (method == null || !method.hasModifierProperty(PsiModifier.STATIC)) return false; + PsiClass aClass = method.getContainingClass(); + if(aClass == null) return false; + String qualifiedName = aClass.getQualifiedName(); + return qualifiedName != null && qualifiedName.startsWith("java.util.stream."); + } + + @Contract("null -> false") + public static boolean isSupportedStreamElement(PsiType type) { + if(type == null) return false; + if(type instanceof PsiPrimitiveType) { + return type.equals(PsiType.INT) || type.equals(PsiType.LONG) || type.equals(PsiType.DOUBLE); + } + return true; + } +} diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/SwitchUtils.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/SwitchUtils.java index 8920ccd872df..e403c7101915 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/SwitchUtils.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/SwitchUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2003-2015 Dave Griffith, Bas Leijdekkers + * Copyright 2003-2017 Dave Griffith, Bas Leijdekkers * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -109,7 +109,7 @@ public class SwitchUtils { } private static boolean canBeSwitchExpression(PsiExpression expression, LanguageLevel languageLevel) { - if (expression == null || SideEffectChecker.mayHaveSideEffects(expression)) { + if (expression == null) { return false; } final PsiType type = expression.getType(); @@ -160,8 +160,8 @@ public class SwitchUtils { return determinePossibleSwitchExpressions(operands[0], languageLevel, nullSafe, stringEquality); } else if (operation.equals(JavaTokenType.EQEQ) && operands.length == 2) { - final PsiExpression lhs = operands[0]; - final PsiExpression rhs = operands[1]; + final PsiExpression lhs = ParenthesesUtils.stripParentheses(operands[0]); + final PsiExpression rhs = ParenthesesUtils.stripParentheses(operands[1]); if (canBeCaseLabel(lhs, languageLevel, stringEquality)) { return rhs; } diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/TestUtils.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/TestUtils.java index 1d324d2e38e6..c6cc3638a00d 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/TestUtils.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/TestUtils.java @@ -28,6 +28,8 @@ import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Set; + public class TestUtils { public static final String RUN_WITH = "org.junit.runner.RunWith"; @@ -64,8 +66,8 @@ public class TestUtils { if (method == null) return false; final PsiClass containingClass = method.getContainingClass(); if (containingClass == null) return false; - final TestFramework framework = TestFrameworks.detectFramework(containingClass); - return framework != null && framework.getName().startsWith("JUnit") && framework.isTestMethod(method, false); + final Set frameworks = TestFrameworks.detectApplicableFrameworks(containingClass); + return frameworks.stream().anyMatch(framework -> framework.getName().startsWith("JUnit") && framework.isTestMethod(method, false)); } public static boolean isRunnable(PsiMethod method) { diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/security/CloneableClassInSecureContextInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/security/CloneableClassInSecureContextInspection.java index e404aca6f60a..49dd1aeb114b 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/security/CloneableClassInSecureContextInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/security/CloneableClassInSecureContextInspection.java @@ -1,5 +1,5 @@ /* - * Copyright 2003-2012 Dave Griffith, Bas Leijdekkers + * Copyright 2003-2017 Dave Griffith, Bas Leijdekkers * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,15 +15,21 @@ */ package com.siyeh.ig.security; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiTypeParameter; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; import com.siyeh.InspectionGadgetsBundle; import com.siyeh.ig.BaseInspection; import com.siyeh.ig.BaseInspectionVisitor; +import com.siyeh.ig.InspectionGadgetsFix; +import com.siyeh.ig.psiutils.ClassUtils; import com.siyeh.ig.psiutils.CloneUtils; import com.siyeh.ig.psiutils.ControlFlowUtils; +import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; public class CloneableClassInSecureContextInspection extends BaseInspection { @@ -39,6 +45,83 @@ public class CloneableClassInSecureContextInspection extends BaseInspection { return InspectionGadgetsBundle.message("cloneable.class.in.secure.context.problem.descriptor"); } + @Override + protected boolean buildQuickFixesOnlyForOnTheFlyErrors() { + // the quickfixes below probably require some thought and shouldn't be applied blindly on many classes at once + return true; + } + + @Nullable + @Override + protected InspectionGadgetsFix buildFix(Object... infos) { + final PsiClass aClass = (PsiClass)infos[0]; + if (CloneUtils.isDirectlyCloneable(aClass)) { + return new RemoveCloneableFix(); + } + final boolean hasCloneMethod = Arrays.stream(aClass.findMethodsByName("clone", false)).anyMatch(CloneUtils::isClone); + if (hasCloneMethod) { + return null; + } + return new CreateExceptionCloneMethodFix(); + } + + private static class RemoveCloneableFix extends InspectionGadgetsFix { + @Nls + @NotNull + @Override + public String getFamilyName() { + return InspectionGadgetsBundle.message("remove.cloneable.quickfix"); + } + + @Override + protected void doFix(Project project, ProblemDescriptor descriptor) { + final PsiElement element = descriptor.getPsiElement().getParent(); + if (!(element instanceof PsiClass)) { + return; + } + final PsiClass aClass = (PsiClass)element; + final PsiReferenceList implementsList = aClass.getImplementsList(); + if (implementsList == null) { + return; + } + final PsiClass cloneableClass = ClassUtils.findClass(CommonClassNames.JAVA_LANG_CLONEABLE, element); + if (cloneableClass == null) { + return; + } + final PsiJavaCodeReferenceElement[] referenceElements = implementsList.getReferenceElements(); + for (PsiJavaCodeReferenceElement referenceElement : referenceElements) { + final PsiElement target = referenceElement.resolve(); + if (cloneableClass.equals(target)) { + referenceElement.delete(); + return; + } + } + } + } + + private static class CreateExceptionCloneMethodFix extends InspectionGadgetsFix { + @NotNull + @Override + public String getFamilyName() { + return InspectionGadgetsBundle.message("cloneable.class.in.secure.context.quickfix"); + } + + @Override + protected void doFix(Project project, ProblemDescriptor descriptor) { + final PsiElement element = descriptor.getPsiElement().getParent(); + if (!(element instanceof PsiClass)) { + return; + } + final PsiClass aClass = (PsiClass)element; + final String cloneMethod = + "public " + aClass.getName() + " clone() throws java.lang.CloneNotSupportedException{\n" + + "throw new java.lang.CloneNotSupportedException();\n" + + "}"; + final PsiMethod method = JavaPsiFacade.getElementFactory(project).createMethodFromText(cloneMethod, aClass); + aClass.add(method); + } + } + @Override public BaseInspectionVisitor buildVisitor() { return new CloneableClassInSecureContextVisitor(); @@ -54,15 +137,17 @@ public class CloneableClassInSecureContextInspection extends BaseInspection { if (!CloneUtils.isCloneable(aClass)) { return; } - final PsiMethod[] methods = aClass.getMethods(); - for (final PsiMethod method : methods) { - if (CloneUtils.isClone(method)) { - if (ControlFlowUtils.methodAlwaysThrowsException(method)) { - return; - } + for (final PsiMethod method : aClass.findMethodsByName("clone", true)) { + final PsiClass containingClass = method.getContainingClass(); + if (containingClass != null && CommonClassNames.JAVA_LANG_OBJECT.equals(containingClass.getQualifiedName())) { + // optimization + break; + } + if (CloneUtils.isClone(method) && ControlFlowUtils.methodAlwaysThrowsException((PsiMethod)method.getNavigationElement())) { + return; } } - registerClassError(aClass); + registerClassError(aClass, aClass); } } } \ No newline at end of file diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/security/DeserializableClassInSecureContextInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/security/DeserializableClassInSecureContextInspection.java deleted file mode 100644 index d53db7cd414d..000000000000 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/security/DeserializableClassInSecureContextInspection.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2003-2012 Dave Griffith, Bas Leijdekkers - * - * 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.siyeh.ig.security; - -import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiTypeParameter; -import com.intellij.psi.util.InheritanceUtil; -import com.siyeh.InspectionGadgetsBundle; -import com.siyeh.ig.BaseInspection; -import com.siyeh.ig.BaseInspectionVisitor; -import com.siyeh.ig.psiutils.ControlFlowUtils; -import com.siyeh.ig.psiutils.SerializationUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -public class DeserializableClassInSecureContextInspection extends BaseInspection { - - @SuppressWarnings("PublicField") - public boolean ignoreThrowable = false; - - @Override - @NotNull - public String getDisplayName() { - return InspectionGadgetsBundle.message("deserializable.class.in.secure.context.display.name"); - } - - @Override - @NotNull - protected String buildErrorString(Object... infos) { - return InspectionGadgetsBundle.message("deserializable.class.in.secure.context.problem.descriptor"); - } - - @Nullable - @Override - public JComponent createOptionsPanel() { - return new SingleCheckboxOptionsPanel( - InspectionGadgetsBundle.message("ignore.classes.extending.throwable.option"), this, "ignoreThrowable"); - } - - @Override - public BaseInspectionVisitor buildVisitor() { - return new DeserializableClassInSecureContextVisitor(); - } - - private class DeserializableClassInSecureContextVisitor extends BaseInspectionVisitor { - - @Override - public void visitClass(@NotNull PsiClass aClass) { - if (aClass.isInterface() || aClass.isAnnotationType() || aClass.isEnum()) { - return; - } - if (aClass instanceof PsiTypeParameter || !SerializationUtils.isSerializable(aClass)) { - return; - } - final PsiMethod[] methods = aClass.getMethods(); - for (final PsiMethod method : methods) { - if (!SerializationUtils.isReadObject(method)) { - continue; - } - if (ControlFlowUtils.methodAlwaysThrowsException(method)) { - return; - } - else { - break; - } - } - if (ignoreThrowable && InheritanceUtil.isInheritor(aClass, false, "java.lang.Throwable")) { - return; - } - registerClassError(aClass); - } - } - - @Override - public String getAlternativeID() { - return "serial"; - } -} \ No newline at end of file diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/security/SerializableClassInSecureContextInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/security/SerializableClassInSecureContextInspection.java deleted file mode 100644 index 8bd50a3702de..000000000000 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/security/SerializableClassInSecureContextInspection.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2003-2012 Dave Griffith, Bas Leijdekkers - * - * 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.siyeh.ig.security; - -import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiTypeParameter; -import com.intellij.psi.util.InheritanceUtil; -import com.siyeh.InspectionGadgetsBundle; -import com.siyeh.ig.BaseInspection; -import com.siyeh.ig.BaseInspectionVisitor; -import com.siyeh.ig.psiutils.ControlFlowUtils; -import com.siyeh.ig.psiutils.SerializationUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -public class SerializableClassInSecureContextInspection extends BaseInspection { - - @SuppressWarnings("PublicField") - public boolean ignoreThrowable = false; - - @Override - @NotNull - public String getDisplayName() { - return InspectionGadgetsBundle.message("serializable.class.in.secure.context.display.name"); - } - - @Override - @NotNull - protected String buildErrorString(Object... infos) { - return InspectionGadgetsBundle.message("serializable.class.in.secure.context.problem.descriptor"); - } - - @Nullable - @Override - public JComponent createOptionsPanel() { - return new SingleCheckboxOptionsPanel( - InspectionGadgetsBundle.message("ignore.classes.extending.throwable.option"), this, "ignoreThrowable"); - } - - @Override - public BaseInspectionVisitor buildVisitor() { - return new SerializableClassInSecureContextVisitor(); - } - - private class SerializableClassInSecureContextVisitor extends BaseInspectionVisitor { - - @Override - public void visitClass(@NotNull PsiClass aClass) { - if (aClass.isInterface() || aClass.isAnnotationType() || aClass.isEnum()) { - return; - } - if (aClass instanceof PsiTypeParameter || !SerializationUtils.isSerializable(aClass)) { - return; - } - final PsiMethod[] methods = aClass.findMethodsByName("writeObject", true); - for (final PsiMethod method : methods) { - if (!SerializationUtils.isWriteObject(method)) { - continue; - } - if (ControlFlowUtils.methodAlwaysThrowsException((PsiMethod)method.getNavigationElement())) { - return; - } - else { - break; - } - } - if (ignoreThrowable && InheritanceUtil.isInheritor(aClass, false, "java.lang.Throwable")) { - return; - } - registerClassError(aClass); - } - } - - @Override - public String getAlternativeID() { - return "serial"; - } -} \ No newline at end of file diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/security/SerializableClassInSecureContextMerger.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/security/SerializableClassInSecureContextMerger.java new file mode 100644 index 000000000000..fa066a4f3ab8 --- /dev/null +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/security/SerializableClassInSecureContextMerger.java @@ -0,0 +1,38 @@ +/* + * Copyright 2000-2017 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.siyeh.ig.security; + +import com.intellij.codeInspection.ex.InspectionElementsMerger; +import org.jetbrains.annotations.NotNull; + +/** + * @author Bas Leijdekkers + */ +public class SerializableClassInSecureContextMerger extends InspectionElementsMerger { + @Override + public String getMergedToolName() { + return "SerializableDeserializableClassInSecureContext"; + } + + @NotNull + @Override + public String[] getSourceToolNames() { + return new String[] { + "DeserializableClassInSecureContext", + "SerializableClassInSecureContext" + }; + } +} diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/security/SerializableDeserializableClassInSecureContextInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/security/SerializableDeserializableClassInSecureContextInspection.java new file mode 100644 index 000000000000..ad3f4735d272 --- /dev/null +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/security/SerializableDeserializableClassInSecureContextInspection.java @@ -0,0 +1,208 @@ +/* + * Copyright 2000-2017 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.siyeh.ig.security; + +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.psi.util.InheritanceUtil; +import com.intellij.psi.util.PsiTreeUtil; +import com.siyeh.InspectionGadgetsBundle; +import com.siyeh.ig.BaseInspection; +import com.siyeh.ig.BaseInspectionVisitor; +import com.siyeh.ig.InspectionGadgetsFix; +import com.siyeh.ig.psiutils.ControlFlowUtils; +import com.siyeh.ig.psiutils.SerializationUtils; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.Arrays; + +/** + * @author Bas Leijdekkers + */ +public class SerializableDeserializableClassInSecureContextInspection extends BaseInspection { + + @SuppressWarnings("PublicField") + public boolean ignoreThrowable = false; + + @Override + @NotNull + public String getDisplayName() { + return InspectionGadgetsBundle.message("serializable.class.in.secure.context.display.name"); + } + + @Override + @NotNull + protected String buildErrorString(Object... infos) { + final Boolean serializable = (Boolean)infos[0]; + final Boolean deserializable = (Boolean)infos[1]; + if (serializable.booleanValue()) { + return deserializable.booleanValue() + ? InspectionGadgetsBundle.message("serializable.deserializable.class.in.secure.context.problem.descriptor") + : InspectionGadgetsBundle.message("serializable.class.in.secure.context.problem.descriptor"); + } + else { + return InspectionGadgetsBundle.message("deserializable.class.in.secure.context.problem.descriptor"); + } + } + + @Nullable + @Override + protected InspectionGadgetsFix buildFix(Object... infos) { + final Boolean serializable = (Boolean)infos[0]; + final Boolean deserializable = (Boolean)infos[1]; + final PsiClass aClass = (PsiClass)infos[2]; + final boolean addReadObjectMethod = deserializable.booleanValue() && !hasOwnReadObjectMethod(aClass); + final boolean addWriteObjectMethod = serializable.booleanValue() && !hasOwnWriteObjectMethod(aClass); + if (!addReadObjectMethod && !addWriteObjectMethod) { + return null; + } + return new AddReadWriteObjectMethodsFix(addReadObjectMethod, addWriteObjectMethod); + } + + private static boolean hasOwnReadObjectMethod(PsiClass aClass) { + return Arrays.stream(aClass.findMethodsByName("readObject", false)).anyMatch(SerializationUtils::isReadObject); + } + + private static boolean hasOwnWriteObjectMethod(PsiClass aClass) { + return Arrays.stream(aClass.findMethodsByName("writeObject", false)).anyMatch(SerializationUtils::isWriteObject); + } + + private static class AddReadWriteObjectMethodsFix extends InspectionGadgetsFix { + + private final boolean myReadObject; + private final boolean myWriteObject; + + AddReadWriteObjectMethodsFix(boolean readObject, boolean writeObject) { + myReadObject = readObject; + myWriteObject = writeObject; + } + + @Nls + @NotNull + @Override + public String getName() { + if (myReadObject) { + return myWriteObject ? getFamilyName() : "Add 'readObject()' method which always throws exception"; + } + return "Add 'writeObject()' methods which always throws exception"; + } + + @Nls + @NotNull + @Override + public String getFamilyName() { + return "Add 'readObject()' and 'writeObject()' methods which always throw exception"; + } + + @Override + protected void doFix(Project project, ProblemDescriptor descriptor) { + final PsiElement element = descriptor.getPsiElement(); + final PsiClass containingClass = PsiTreeUtil.getParentOfType(element, PsiClass.class); + if (containingClass == null) { + return; + } + final String className = containingClass.getQualifiedName(); + final PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); + if (myReadObject) { + final PsiMethod readObjectMethod = factory.createMethodFromText( + "private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, java.lang.ClassNotFoundException {" + + " throw new java.io.NotSerializableException(\"" + className + "\");" + + "}", containingClass); + containingClass.add(readObjectMethod); + } + if (myWriteObject) { + final PsiMethod writeObjectMethod = factory.createMethodFromText( + "private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {" + + " throw new java.io.NotSerializableException(\"" + className + "\");" + + "}", containingClass); + containingClass.add(writeObjectMethod); + } + } + } + + @Nullable + @Override + public JComponent createOptionsPanel() { + return new SingleCheckboxOptionsPanel( + InspectionGadgetsBundle.message("ignore.classes.extending.throwable.option"), this, "ignoreThrowable"); + } + + @Override + public BaseInspectionVisitor buildVisitor() { + return new SerializableDeserializableClassInSecureContextVisitor(); + } + + private class SerializableDeserializableClassInSecureContextVisitor extends BaseInspectionVisitor { + + @Override + public void visitClass(@NotNull PsiClass aClass) { + if (aClass.isInterface() || aClass.isAnnotationType()) { + return; + } + if (aClass instanceof PsiTypeParameter || !SerializationUtils.isSerializable(aClass)) { + return; + } + if (ignoreThrowable && InheritanceUtil.isInheritor(aClass, false, "java.lang.Throwable")) { + return; + } + if (!hasSerializableState(aClass)) { + // doesn't matter, class has no state anyway. + return; + } + final boolean serializable = !hasWriteObjectMethodWhichAlwaysThrowsException(aClass); + final boolean deserializable = !hasReadObjectMethodWhichAlwaysThrowsException(aClass); + if (!serializable && !deserializable) { + return; + } + registerClassError(aClass, Boolean.valueOf(serializable), Boolean.valueOf(deserializable), aClass); + } + + private boolean hasSerializableState(PsiClass aClass) { + return Arrays.stream(aClass.getFields()) + .filter(f -> !f.hasModifierProperty(PsiModifier.STATIC)) + .filter(f -> !f.hasModifierProperty(PsiModifier.TRANSIENT)) + .anyMatch(f -> !(f instanceof PsiEnumConstant)); + } + + private boolean hasReadObjectMethodWhichAlwaysThrowsException(PsiClass aClass) { + for (final PsiMethod method : aClass.findMethodsByName("readObject", true)) { + if (SerializationUtils.isReadObject(method)) { + return ControlFlowUtils.methodAlwaysThrowsException((PsiMethod)method.getNavigationElement()); + } + } + return false; + } + + private boolean hasWriteObjectMethodWhichAlwaysThrowsException(PsiClass aClass) { + for (final PsiMethod method : aClass.findMethodsByName("writeObject", true)) { + if (SerializationUtils.isWriteObject(method)) { + return ControlFlowUtils.methodAlwaysThrowsException((PsiMethod)method.getNavigationElement()); + } + } + return false; + } + } + + @Override + public String getAlternativeID() { + return "serial"; + } +} \ No newline at end of file diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/threading/NonAtomicOperationOnVolatileFieldInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/threading/NonAtomicOperationOnVolatileFieldInspection.java index b78c815a763d..1649b80ee530 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/threading/NonAtomicOperationOnVolatileFieldInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/threading/NonAtomicOperationOnVolatileFieldInspection.java @@ -1,5 +1,5 @@ /* - * Copyright 2003-2010 Dave Griffith, Bas Leijdekkers + * Copyright 2003-2017 Dave Griffith, Bas Leijdekkers * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,26 +20,23 @@ import com.intellij.psi.tree.IElementType; import com.siyeh.InspectionGadgetsBundle; import com.siyeh.ig.BaseInspection; import com.siyeh.ig.BaseInspectionVisitor; +import com.siyeh.ig.psiutils.ParenthesesUtils; import com.siyeh.ig.psiutils.SynchronizationUtil; -import com.siyeh.ig.psiutils.VariableAccessUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class NonAtomicOperationOnVolatileFieldInspection - extends BaseInspection { +public class NonAtomicOperationOnVolatileFieldInspection extends BaseInspection { @Override @NotNull public String getDisplayName() { - return InspectionGadgetsBundle.message( - "non.atomic.operation.on.volatile.field.display.name"); + return InspectionGadgetsBundle.message("non.atomic.operation.on.volatile.field.display.name"); } @Override @NotNull protected String buildErrorString(Object... infos) { - return InspectionGadgetsBundle.message( - "non.atomic.operation.on.volatile.field.problem.descriptor"); + return InspectionGadgetsBundle.message("non.atomic.operation.on.volatile.field.problem.descriptor"); } @Override @@ -47,14 +44,12 @@ public class NonAtomicOperationOnVolatileFieldInspection return new NonAtomicOperationOnVolatileFieldVisitor(); } - private static class NonAtomicOperationOnVolatileFieldVisitor - extends BaseInspectionVisitor { + private static class NonAtomicOperationOnVolatileFieldVisitor extends BaseInspectionVisitor { @Override - public void visitAssignmentExpression( - @NotNull PsiAssignmentExpression expression) { + public void visitAssignmentExpression(@NotNull PsiAssignmentExpression expression) { super.visitAssignmentExpression(expression); - final PsiExpression rhs = expression.getRExpression(); + final PsiExpression rhs = ParenthesesUtils.stripParentheses(expression.getRExpression()); if (rhs == null) { return; } @@ -78,9 +73,23 @@ public class NonAtomicOperationOnVolatileFieldInspection registerError(lhs); return; } - if (VariableAccessUtils.variableIsUsed(volatileField, rhs)) { - registerError(lhs); + if (isOnTheFly() && !volatileField.hasModifierProperty(PsiModifier.PRIVATE)) { + return; } + rhs.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitReferenceExpression(PsiReferenceExpression reference) { + if (reference.isReferenceTo(volatileField) && isUnqualified(reference)) { + stopWalking(); + final PsiElement referenceNameElement = ((PsiJavaCodeReferenceElement)lhs).getReferenceNameElement(); + if (referenceNameElement != null) { + registerError(referenceNameElement); + } + return; + } + super.visitReferenceExpression(reference); + } + }); } @Override @@ -96,8 +105,7 @@ public class NonAtomicOperationOnVolatileFieldInspection if (operand == null) { return; } - final PsiField volatileField = - findNonSynchronizedVolatileField(operand); + final PsiField volatileField = findNonSynchronizedVolatileField(operand); if (volatileField == null) { return; } @@ -108,8 +116,7 @@ public class NonAtomicOperationOnVolatileFieldInspection public void visitPostfixExpression(PsiPostfixExpression expression) { super.visitPostfixExpression(expression); final PsiExpression operand = expression.getOperand(); - final PsiField volatileField = - findNonSynchronizedVolatileField(operand); + final PsiField volatileField = findNonSynchronizedVolatileField(operand); if (volatileField == null) { return; } @@ -117,13 +124,14 @@ public class NonAtomicOperationOnVolatileFieldInspection } @Nullable - private static PsiField findNonSynchronizedVolatileField( - PsiExpression expression) { + private static PsiField findNonSynchronizedVolatileField(PsiExpression expression) { if (!(expression instanceof PsiReferenceExpression)) { return null; } - final PsiReferenceExpression reference = - (PsiReferenceExpression)expression; + final PsiReferenceExpression reference = (PsiReferenceExpression)expression; + if (!isUnqualified(reference)) { + return null; + } final PsiElement referent = reference.resolve(); if (!(referent instanceof PsiField)) { return null; @@ -137,5 +145,13 @@ public class NonAtomicOperationOnVolatileFieldInspection } return field; } + + public static boolean isUnqualified(PsiReferenceExpression element) { + if (!element.isQualified()) { + return true; + } + final PsiExpression qualifierExpression = ParenthesesUtils.stripParentheses(element.getQualifierExpression()); + return qualifierExpression instanceof PsiThisExpression && ((PsiThisExpression)qualifierExpression).getQualifier() == null; + } } } \ No newline at end of file diff --git a/plugins/InspectionGadgets/src/com/siyeh/ig/asserttoif/AssertionCanBeIfInspection.java b/plugins/InspectionGadgets/src/com/siyeh/ig/asserttoif/AssertionCanBeIfInspection.java index 5c3e46886258..ed19c22f91cc 100644 --- a/plugins/InspectionGadgets/src/com/siyeh/ig/asserttoif/AssertionCanBeIfInspection.java +++ b/plugins/InspectionGadgets/src/com/siyeh/ig/asserttoif/AssertionCanBeIfInspection.java @@ -1,5 +1,5 @@ /* - * Copyright 2003-2013 Dave Griffith, Bas Leijdekkers + * Copyright 2003-2017 Dave Griffith, Bas Leijdekkers * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,9 +17,7 @@ package com.siyeh.ig.asserttoif; import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiAssertStatement; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiExpression; +import com.intellij.psi.*; import com.siyeh.InspectionGadgetsBundle; import com.siyeh.ig.BaseInspection; import com.siyeh.ig.BaseInspectionVisitor; @@ -56,24 +54,20 @@ public class AssertionCanBeIfInspection extends BaseInspection { return new AssertToIfFix(); } - private static void doFixImpl(@NotNull PsiElement element) { - final PsiAssertStatement assertStatement = (PsiAssertStatement)element; - @NonNls final StringBuilder newStatement = new StringBuilder(); - final PsiExpression condition = assertStatement.getAssertCondition(); - newStatement.append("if(").append(BoolUtils.getNegatedExpressionText(condition)).append(") throw new java.lang.AssertionError("); - final PsiExpression description = assertStatement.getAssertDescription(); - if (description != null) { - newStatement.append(description.getText()); - } - newStatement.append(");"); - PsiReplacementUtil.replaceStatement(assertStatement, newStatement.toString()); - } - private static class AssertToIfVisitor extends BaseInspectionVisitor { @Override public void visitAssertStatement(PsiAssertStatement assertStatement) { super.visitAssertStatement(assertStatement); - if (assertStatement.getAssertCondition() != null) { + if (assertStatement.getAssertCondition() == null) { + return; + } + if (assertStatement.getLastChild() instanceof PsiErrorElement) { + return; + } + if (isVisibleHighlight(assertStatement)) { + registerStatementError(assertStatement); + } + else { registerError(assertStatement); } } @@ -89,7 +83,18 @@ public class AssertionCanBeIfInspection extends BaseInspection { @Override protected void doFix(Project project, ProblemDescriptor descriptor) { - doFixImpl(descriptor.getPsiElement()); + final PsiElement element = descriptor.getPsiElement(); + final PsiAssertStatement assertStatement = + element instanceof PsiKeyword ? (PsiAssertStatement)element.getParent() : (PsiAssertStatement)element; + final PsiExpression condition = assertStatement.getAssertCondition(); + @NonNls final StringBuilder newStatement = + new StringBuilder("if(").append(BoolUtils.getNegatedExpressionText(condition)).append(") throw new java.lang.AssertionError("); + final PsiExpression description = assertStatement.getAssertDescription(); + if (description != null) { + newStatement.append(description.getText()); + } + newStatement.append(");"); + PsiReplacementUtil.replaceStatement(assertStatement, newStatement.toString()); } } } \ No newline at end of file diff --git a/plugins/InspectionGadgets/src/com/siyeh/ig/asserttoif/IfCanBeAssertionInspection.java b/plugins/InspectionGadgets/src/com/siyeh/ig/asserttoif/IfCanBeAssertionInspection.java index 062fa1cbc62e..02e6779c7b37 100644 --- a/plugins/InspectionGadgets/src/com/siyeh/ig/asserttoif/IfCanBeAssertionInspection.java +++ b/plugins/InspectionGadgets/src/com/siyeh/ig/asserttoif/IfCanBeAssertionInspection.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 Bas Leijdekkers + * Copyright 2010-2017 Bas Leijdekkers * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -56,50 +56,11 @@ public class IfCanBeAssertionInspection extends BaseInspection { return new IfToAssertionFix(); } - private static void doFixImpl(@NotNull PsiElement element) { - final PsiElement parent = element.getParent(); - if (!(parent instanceof PsiIfStatement)) { - return; - } - final PsiIfStatement ifStatement = (PsiIfStatement)parent; - final PsiExpression condition = ifStatement.getCondition(); - @NonNls final StringBuilder newStatementText = new StringBuilder("assert "); - newStatementText.append(BoolUtils.getNegatedExpressionText(condition)); - final PsiNewExpression newException = getThrownNewException(ifStatement); - final String message = getExceptionMessage(newException); - if (message != null) { - newStatementText.append(':').append(message); - } - newStatementText.append(';'); - PsiReplacementUtil.replaceStatement(ifStatement, newStatementText.toString()); - } - - private static String getExceptionMessage(PsiNewExpression newExpression) { - if (newExpression != null) { - final PsiExpressionList argumentList = newExpression.getArgumentList(); - if (argumentList != null) { - final PsiExpression[] arguments = argumentList.getExpressions(); - if (arguments.length >= 1) { - return arguments[0].getText(); - } - } - } - return null; - } - - private static PsiNewExpression getThrownNewException(PsiIfStatement ifStatement) { - if (ifStatement.getElseBranch() == null) { - final PsiStatement thenBranch = ifStatement.getThenBranch(); - return getThrownNewExceptionImpl(thenBranch); - } - return null; - } - - private static PsiNewExpression getThrownNewExceptionImpl(PsiElement element) { + static PsiNewExpression getThrownNewException(PsiElement element) { if (element instanceof PsiBlockStatement) { final PsiStatement[] statements = ((PsiBlockStatement)element).getCodeBlock().getStatements(); if (statements.length == 1) { - return getThrownNewExceptionImpl(statements[0]); + return getThrownNewException(statements[0]); } } else if (element instanceof PsiThrowStatement) { @@ -113,16 +74,14 @@ public class IfCanBeAssertionInspection extends BaseInspection { } private static class IfToAssertionVisitor extends BaseInspectionVisitor { + @Override - public void visitKeyword(PsiKeyword keyword) { - super.visitKeyword(keyword); - if (keyword.getTokenType() == JavaTokenType.IF_KEYWORD) { - final PsiElement parent = keyword.getParent(); - if (parent instanceof PsiIfStatement) { - if (getThrownNewException((PsiIfStatement)parent) != null) { - registerError(keyword); - } - } + public void visitIfStatement(PsiIfStatement statement) { + super.visitIfStatement(statement); + if (statement.getCondition() != null && + statement.getElseBranch() == null && + getThrownNewException(statement.getThenBranch()) != null) { + registerStatementError(statement); } } } @@ -137,7 +96,35 @@ public class IfCanBeAssertionInspection extends BaseInspection { @Override protected void doFix(Project project, ProblemDescriptor descriptor) { - doFixImpl(descriptor.getPsiElement()); + final PsiElement parent = descriptor.getPsiElement().getParent(); + if (!(parent instanceof PsiIfStatement)) { + return; + } + final PsiIfStatement ifStatement = (PsiIfStatement)parent; + @NonNls final StringBuilder newStatementText = new StringBuilder("assert "); + newStatementText.append(BoolUtils.getNegatedExpressionText(ifStatement.getCondition())); + final PsiNewExpression newException = getThrownNewException(ifStatement.getThenBranch()); + final String message = getExceptionMessage(newException); + if (message != null) { + newStatementText.append(':').append(message); + } + newStatementText.append(';'); + PsiReplacementUtil.replaceStatement(ifStatement, newStatementText.toString()); + } + + private static String getExceptionMessage(PsiNewExpression newExpression) { + if (newExpression == null) { + return null; + } + final PsiExpressionList argumentList = newExpression.getArgumentList(); + if (argumentList == null) { + return null; + } + final PsiExpression[] arguments = argumentList.getExpressions(); + if (arguments.length < 1) { + return null; + } + return arguments[0].getText(); } } } diff --git a/plugins/InspectionGadgets/src/com/siyeh/ig/maturity/SuppressionAnnotationInspection.java b/plugins/InspectionGadgets/src/com/siyeh/ig/maturity/SuppressionAnnotationInspection.java index 6aaa1439ce8e..78f81735ce49 100644 --- a/plugins/InspectionGadgets/src/com/siyeh/ig/maturity/SuppressionAnnotationInspection.java +++ b/plugins/InspectionGadgets/src/com/siyeh/ig/maturity/SuppressionAnnotationInspection.java @@ -99,6 +99,11 @@ public class SuppressionAnnotationInspection extends SuppressionAnnotationInspec ProjectInspectionProfileManager.getInstance(project).fireProfileChanged(); } + @Override + public boolean startInWriteAction() { + return false; + } + @NotNull @Override public String getName() { diff --git a/plugins/InspectionGadgets/src/com/siyeh/ig/psiutils/StreamApiUtil.java b/plugins/InspectionGadgets/src/com/siyeh/ig/psiutils/StreamApiUtil.java deleted file mode 100644 index c80b089d7c52..000000000000 --- a/plugins/InspectionGadgets/src/com/siyeh/ig/psiutils/StreamApiUtil.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright 2000-2017 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.siyeh.ig.psiutils; - -import com.intellij.codeInspection.util.OptionalUtil; -import com.intellij.psi.*; -import com.intellij.psi.util.PsiUtil; -import com.intellij.refactoring.util.RefactoringUtil; -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Objects; - -/** - * @author Tagir Valeev - */ -public class StreamApiUtil { - @Contract("null -> null") - public static PsiType getStreamElementType(PsiType type) { - return getStreamElementType(type, true); - } - - @Contract("null, _ -> null") - public static PsiType getStreamElementType(PsiType type, boolean variableType) { - if(!(type instanceof PsiClassType)) return null; - PsiClass aClass = ((PsiClassType)type).resolve(); - if(com.intellij.psi.util.InheritanceUtil.isInheritor(aClass, false, CommonClassNames.JAVA_UTIL_STREAM_INT_STREAM)) { - return PsiType.INT; - } - if(com.intellij.psi.util.InheritanceUtil.isInheritor(aClass, false, CommonClassNames.JAVA_UTIL_STREAM_LONG_STREAM)) { - return PsiType.LONG; - } - if(com.intellij.psi.util.InheritanceUtil.isInheritor(aClass, false, CommonClassNames.JAVA_UTIL_STREAM_DOUBLE_STREAM)) { - return PsiType.DOUBLE; - } - if(!com.intellij.psi.util.InheritanceUtil.isInheritor(aClass, false, CommonClassNames.JAVA_UTIL_STREAM_STREAM)) { - return null; - } - PsiType streamType = PsiUtil.substituteTypeParameter(type, CommonClassNames.JAVA_UTIL_STREAM_STREAM, 0, false); - if (variableType) { - if (streamType instanceof PsiIntersectionType) { - return null; - } - streamType = GenericsUtil.getVariableTypeByExpressionType(streamType); - } - return streamType; - } - - public static boolean isNullOrEmptyStream(PsiExpression expression) { - if(ExpressionUtils.isNullLiteral(expression)) { - return true; - } - if (!(expression instanceof PsiMethodCallExpression)) return false; - PsiMethodCallExpression call = (PsiMethodCallExpression)expression; - String name = call.getMethodExpression().getReferenceName(); - if ((!"empty".equals(name) && !"of".equals(name)) || !(call.getArgumentList().getExpressions().length == 0)) { - return false; - } - PsiMethod method = call.resolveMethod(); - if (method == null || !method.hasModifierProperty(PsiModifier.STATIC)) return false; - PsiClass aClass = method.getContainingClass(); - if(aClass == null) return false; - String qualifiedName = aClass.getQualifiedName(); - return qualifiedName != null && qualifiedName.startsWith("java.util.stream."); - } - - @Contract("null -> false") - public static boolean isSupportedStreamElement(PsiType type) { - if(type == null) return false; - if(type instanceof PsiPrimitiveType) { - return type.equals(PsiType.INT) || type.equals(PsiType.LONG) || type.equals(PsiType.DOUBLE); - } - return true; - } - - @NotNull - public static String generateMapOperation(PsiVariable variable, @Nullable PsiType outType, PsiElement mapper) { - String shortcutMappingMethod = getShortcutMappingMethod(variable, outType, mapper); - if (shortcutMappingMethod != null) return shortcutMappingMethod.isEmpty() ? "" : "." + shortcutMappingMethod + "()"; - PsiType inType = variable.getType(); - String operationName = getMapOperationName(inType, outType); - if(outType != null && mapper instanceof PsiArrayInitializerExpression) { - mapper = RefactoringUtil.convertInitializerToNormalExpression((PsiExpression)mapper, outType); - } - String typeArgument = mapper instanceof PsiExpression ? OptionalUtil.getMapTypeArgument((PsiExpression)mapper, outType) : ""; - return "." + typeArgument + operationName + - "(" + variable.getName() + "->" + mapper.getText() + ")"; - } - - /** - * Returns the shortcut mapping method name - * - * @param variable mapper input variable - * @param outType output type of the mapper - * @param mapper mapper code - * @return shortcut mapping name ("boxed", "asLongStream", "asDoubleStream") if applicable, empty string if it's - * ditto mapping (no mapping is necessary at all) and null if no shortcut is applicable for given mapper - */ - @Nullable - public static String getShortcutMappingMethod(PsiVariable variable, @Nullable PsiType outType, PsiElement mapper) { - if (!(mapper instanceof PsiExpression)) return null; - PsiExpression expression = PsiUtil.skipParenthesizedExprDown(((PsiExpression)mapper)); - PsiType inType = variable.getType(); - if (expression instanceof PsiTypeCastExpression && inType instanceof PsiPrimitiveType && Objects.equals(expression.getType(), outType)) { - expression = ((PsiTypeCastExpression)expression).getOperand(); - } - if (ExpressionUtils.isReferenceTo(expression, variable)) { - if (!(outType instanceof PsiPrimitiveType)) { - return inType instanceof PsiPrimitiveType ? "boxed" : ""; - } - if (outType.equals(inType)) { - return ""; - } - if (PsiType.LONG.equals(outType) && PsiType.INT.equals(inType)) { - return "asLongStream"; - } - if (PsiType.DOUBLE.equals(outType) && (PsiType.LONG.equals(inType) || PsiType.INT.equals(inType))) { - return "asDoubleStream"; - } - } - return null; - } - - @NotNull - public static String getMapOperationName(PsiType inType, @Nullable PsiType outType) { - if(outType instanceof PsiPrimitiveType) { - if(!outType.equals(inType)) { - if(PsiType.INT.equals(outType)) { - return "mapToInt"; - } else if(PsiType.LONG.equals(outType)) { - return "mapToLong"; - } else if(PsiType.DOUBLE.equals(outType)) { - return "mapToDouble"; - } - } - } else if(inType instanceof PsiPrimitiveType) { - return "mapToObj"; - } - return "map"; - } - - @Nullable - public static String getFlatMapOperationName(PsiType inType, PsiType outType) { - if (!(inType instanceof PsiPrimitiveType)) { - if (PsiType.INT.equals(outType)) { - return "flatMapToInt"; - } - else if (PsiType.LONG.equals(outType)) { - return "flatMapToLong"; - } - else if (PsiType.DOUBLE.equals(outType)) { - return "flatMapToDouble"; - } - } else if (!inType.equals(outType)) return null; - return "flatMap"; - } -} diff --git a/plugins/InspectionGadgets/src/inspectionDescriptions/DeserializableClassInSecureContext.html b/plugins/InspectionGadgets/src/inspectionDescriptions/DeserializableClassInSecureContext.html deleted file mode 100644 index 4bae34252061..000000000000 --- a/plugins/InspectionGadgets/src/inspectionDescriptions/DeserializableClassInSecureContext.html +++ /dev/null @@ -1,13 +0,0 @@ - - -Reports classes which may be deserialized. A class -may be deserialized if it supports the Serializable interface, -and its readObject() method is not defined to immediately -throw an error. Deserializable classes may be dangerous in code intended for secure use. - -

    -Use the checkbox below to ignore classes extending Throwable directly or indirectly. -

    - - - \ No newline at end of file diff --git a/plugins/InspectionGadgets/src/inspectionDescriptions/ExceptionPackage.html b/plugins/InspectionGadgets/src/inspectionDescriptions/ExceptionPackage.html index 82287849447a..b6d9cb1a3992 100644 --- a/plugins/InspectionGadgets/src/inspectionDescriptions/ExceptionPackage.html +++ b/plugins/InspectionGadgets/src/inspectionDescriptions/ExceptionPackage.html @@ -1,6 +1,8 @@ Report packages which contain only classes extending java.lang.Throwable, either directly or indirectly. +Although exceptions usually don't depend on other classes for their implementation, they are normally not used separately. +It is often a better design to locate exceptions in the same package as the classes that use them.

    Because this inspection requires global code analysis it is only available for Analyze|Inspect Code or Analyze|Run Inspection by Name and it will not report in the editor. diff --git a/plugins/InspectionGadgets/src/inspectionDescriptions/IfCanBeAssertion.html b/plugins/InspectionGadgets/src/inspectionDescriptions/IfCanBeAssertion.html index 2519f12a6c0e..75224616cdff 100644 --- a/plugins/InspectionGadgets/src/inspectionDescriptions/IfCanBeAssertion.html +++ b/plugins/InspectionGadgets/src/inspectionDescriptions/IfCanBeAssertion.html @@ -1,6 +1,6 @@ -This inspection reports if statements (with no else branch) throwing java.lang.Throwable.
    +Reports if statements (with no else branch) throwing java.lang.Throwable.
    For example:
    if (param == 2) throw new Exception(); diff --git a/plugins/InspectionGadgets/src/inspectionDescriptions/NonAtomicOperationOnVolatileField.html b/plugins/InspectionGadgets/src/inspectionDescriptions/NonAtomicOperationOnVolatileField.html index b1bdb1bbcba4..305f59c72c98 100644 --- a/plugins/InspectionGadgets/src/inspectionDescriptions/NonAtomicOperationOnVolatileField.html +++ b/plugins/InspectionGadgets/src/inspectionDescriptions/NonAtomicOperationOnVolatileField.html @@ -1,13 +1,11 @@ -Reports any non-atomic operations on volatile fields. Non-atomic -operations on volatile fields are operations where the volatile field is read and -the value is used to update the volatile field. It is possible for the value of the -field to change between the read and write, making the operation possibly invalid. -In such cases it is better to surround the operation with a synchronized block or -make use of one of the Atomic* or -Atomic*FieldUpdater classes -from the java.util.concurrent.atomic package. +Reports any non-atomic operations on volatile fields. Non-atomic +operations on volatile fields are operations where the field is read and +the value is used to update the field. It is possible for the value of the +field to change between the read and the write, possibly invalidating the operation. +The non-atomic operation can be avoided by surrounding it with a synchronized block or +by making use of one of the classes from the java.util.concurrent.atomic package.

    diff --git a/plugins/InspectionGadgets/src/inspectionDescriptions/RedundantStringFormatCall.html b/plugins/InspectionGadgets/src/inspectionDescriptions/RedundantStringFormatCall.html index 8e16d6353054..10f03d1d4fe3 100644 --- a/plugins/InspectionGadgets/src/inspectionDescriptions/RedundantStringFormatCall.html +++ b/plugins/InspectionGadgets/src/inspectionDescriptions/RedundantStringFormatCall.html @@ -1,8 +1,8 @@ -Reports any calls to String.format() where only a format string is +Reports any calls to String.format() or PrintStream.printf() where only a format string is provided, but no arguments. Such a call is unnecessary and can be replaced with just -the string. +the string or a call to PrintStream.print() respectively.

    diff --git a/plugins/InspectionGadgets/src/inspectionDescriptions/SerializableClassInSecureContext.html b/plugins/InspectionGadgets/src/inspectionDescriptions/SerializableClassInSecureContext.html deleted file mode 100644 index 9a3f69c9bd74..000000000000 --- a/plugins/InspectionGadgets/src/inspectionDescriptions/SerializableClassInSecureContext.html +++ /dev/null @@ -1,13 +0,0 @@ - - -Reports classes which may be serialized. A class -may be serialized if it supports the Serializable interface, -and its writeObject() method is not defined to immediately -throw an error. Serializable classes may be dangerous in code intended for secure use. - -

    -Use the checkbox below to ignore classes extending Throwable directly. -

    - - - \ No newline at end of file diff --git a/plugins/InspectionGadgets/src/inspectionDescriptions/SerializableDeserializableClassInSecureContext.html b/plugins/InspectionGadgets/src/inspectionDescriptions/SerializableDeserializableClassInSecureContext.html new file mode 100644 index 000000000000..7c33f091b5b0 --- /dev/null +++ b/plugins/InspectionGadgets/src/inspectionDescriptions/SerializableDeserializableClassInSecureContext.html @@ -0,0 +1,13 @@ + + +Reports classes which may be serialized or deserialized. A class +may be serialized if it supports the Serializable interface, +and its readObject() and writeObject() methods are not defined to always +throw an exception. Serializable classes may be dangerous in code intended for secure use. + +

    +Use the first checkbox below to ignore classes extending Throwable directly or indirectly. +However it may be more secure to add readObject() and writeObject() methods which always throw an exception instead. +

    New in 2017.3 + + \ No newline at end of file diff --git a/plugins/InspectionGadgets/test/com/siyeh/igfixes/asserttoif/assert_to_if/Incomplete.after.java b/plugins/InspectionGadgets/test/com/siyeh/igfixes/asserttoif/assert_to_if/Incomplete.after.java deleted file mode 100644 index 763148ba9a6b..000000000000 --- a/plugins/InspectionGadgets/test/com/siyeh/igfixes/asserttoif/assert_to_if/Incomplete.after.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.siyeh.ipp.asserttoif.assert_to_if; - -public class Incomplete { - - void x(Object o) { - if (o == null) throw new AssertionError(); - } -} diff --git a/plugins/InspectionGadgets/test/com/siyeh/igfixes/asserttoif/assert_to_if/Incomplete.java b/plugins/InspectionGadgets/test/com/siyeh/igfixes/asserttoif/assert_to_if/Incomplete.java index 9278a952ed52..ab2e7b81eab5 100644 --- a/plugins/InspectionGadgets/test/com/siyeh/igfixes/asserttoif/assert_to_if/Incomplete.java +++ b/plugins/InspectionGadgets/test/com/siyeh/igfixes/asserttoif/assert_to_if/Incomplete.java @@ -3,6 +3,6 @@ package com.siyeh.ipp.asserttoif.assert_to_if; public class Incomplete { void x(Object o) { - assert o != null : + assert o != null : } } diff --git a/plugins/InspectionGadgets/test/com/siyeh/igfixes/asserttoif/if_to_assert/NoCondition.java b/plugins/InspectionGadgets/test/com/siyeh/igfixes/asserttoif/if_to_assert/NoCondition.java new file mode 100644 index 000000000000..b06e622d9183 --- /dev/null +++ b/plugins/InspectionGadgets/test/com/siyeh/igfixes/asserttoif/if_to_assert/NoCondition.java @@ -0,0 +1,7 @@ +class NoCondition { + void m(Object o) { + if () { + throw new NullPointerException("wtf?"); + } + } +} \ No newline at end of file diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/switchtoif/replace_if_with_switch/Comments_after.java b/plugins/InspectionGadgets/test/com/siyeh/igfixes/migration/if_can_be_switch/Comments.after.java similarity index 100% rename from plugins/IntentionPowerPak/test/com/siyeh/ipp/switchtoif/replace_if_with_switch/Comments_after.java rename to plugins/InspectionGadgets/test/com/siyeh/igfixes/migration/if_can_be_switch/Comments.after.java diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/switchtoif/replace_if_with_switch/Comments.java b/plugins/InspectionGadgets/test/com/siyeh/igfixes/migration/if_can_be_switch/Comments.java similarity index 100% rename from plugins/IntentionPowerPak/test/com/siyeh/ipp/switchtoif/replace_if_with_switch/Comments.java rename to plugins/InspectionGadgets/test/com/siyeh/igfixes/migration/if_can_be_switch/Comments.java diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/switchtoif/replace_if_with_switch/Long.java b/plugins/InspectionGadgets/test/com/siyeh/igfixes/migration/if_can_be_switch/Long.java similarity index 100% rename from plugins/IntentionPowerPak/test/com/siyeh/ipp/switchtoif/replace_if_with_switch/Long.java rename to plugins/InspectionGadgets/test/com/siyeh/igfixes/migration/if_can_be_switch/Long.java diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/switchtoif/replace_if_with_switch/ObjectsEquals_after.java b/plugins/InspectionGadgets/test/com/siyeh/igfixes/migration/if_can_be_switch/ObjectsEquals.after.java similarity index 100% rename from plugins/IntentionPowerPak/test/com/siyeh/ipp/switchtoif/replace_if_with_switch/ObjectsEquals_after.java rename to plugins/InspectionGadgets/test/com/siyeh/igfixes/migration/if_can_be_switch/ObjectsEquals.after.java diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/switchtoif/replace_if_with_switch/ObjectsEquals.java b/plugins/InspectionGadgets/test/com/siyeh/igfixes/migration/if_can_be_switch/ObjectsEquals.java similarity index 100% rename from plugins/IntentionPowerPak/test/com/siyeh/ipp/switchtoif/replace_if_with_switch/ObjectsEquals.java rename to plugins/InspectionGadgets/test/com/siyeh/igfixes/migration/if_can_be_switch/ObjectsEquals.java diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/switchtoif/replace_if_with_switch/Polyadic_after.java b/plugins/InspectionGadgets/test/com/siyeh/igfixes/migration/if_can_be_switch/Polyadic.after.java similarity index 100% rename from plugins/IntentionPowerPak/test/com/siyeh/ipp/switchtoif/replace_if_with_switch/Polyadic_after.java rename to plugins/InspectionGadgets/test/com/siyeh/igfixes/migration/if_can_be_switch/Polyadic.after.java diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/switchtoif/replace_if_with_switch/Polyadic.java b/plugins/InspectionGadgets/test/com/siyeh/igfixes/migration/if_can_be_switch/Polyadic.java similarity index 100% rename from plugins/IntentionPowerPak/test/com/siyeh/ipp/switchtoif/replace_if_with_switch/Polyadic.java rename to plugins/InspectionGadgets/test/com/siyeh/igfixes/migration/if_can_be_switch/Polyadic.java diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/switchtoif/replace_if_with_switch/StringEquality_after.java b/plugins/InspectionGadgets/test/com/siyeh/igfixes/migration/if_can_be_switch/StringEquality.after.java similarity index 100% rename from plugins/IntentionPowerPak/test/com/siyeh/ipp/switchtoif/replace_if_with_switch/StringEquality_after.java rename to plugins/InspectionGadgets/test/com/siyeh/igfixes/migration/if_can_be_switch/StringEquality.after.java diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/switchtoif/replace_if_with_switch/StringEquality.java b/plugins/InspectionGadgets/test/com/siyeh/igfixes/migration/if_can_be_switch/StringEquality.java similarity index 100% rename from plugins/IntentionPowerPak/test/com/siyeh/ipp/switchtoif/replace_if_with_switch/StringEquality.java rename to plugins/InspectionGadgets/test/com/siyeh/igfixes/migration/if_can_be_switch/StringEquality.java diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/bugs/return_null/ReturnNull.java b/plugins/InspectionGadgets/test/com/siyeh/igtest/bugs/return_null/ReturnNull.java index e787d1f58dbb..4de5b396743d 100644 --- a/plugins/InspectionGadgets/test/com/siyeh/igtest/bugs/return_null/ReturnNull.java +++ b/plugins/InspectionGadgets/test/com/siyeh/igtest/bugs/return_null/ReturnNull.java @@ -12,4 +12,12 @@ public class ReturnNull { return null; } +} +interface A { + T m(); +} +class B implements A { + public Void m() { + return null; + } } \ No newline at end of file diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/classlayout/NonProtectedConstructorInAbstractClassInspection.java b/plugins/InspectionGadgets/test/com/siyeh/igtest/classlayout/NonProtectedConstructorInAbstractClassInspection.java deleted file mode 100644 index 8141d73a833f..000000000000 --- a/plugins/InspectionGadgets/test/com/siyeh/igtest/classlayout/NonProtectedConstructorInAbstractClassInspection.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.siyeh.igtest.classlayout; - -public abstract class NonProtectedConstructorInAbstractClassInspection -{ - public NonProtectedConstructorInAbstractClassInspection() - { - } - private NonProtectedConstructorInAbstractClassInspection(int foo) - { - } -} diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/classlayout/NonProtectedConstructorInAbstractClassInspection2.java b/plugins/InspectionGadgets/test/com/siyeh/igtest/classlayout/NonProtectedConstructorInAbstractClassInspection2.java deleted file mode 100644 index 1573ec53efbc..000000000000 --- a/plugins/InspectionGadgets/test/com/siyeh/igtest/classlayout/NonProtectedConstructorInAbstractClassInspection2.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.siyeh.igtest.classlayout; - -public abstract class NonProtectedConstructorInAbstractClassInspection2 { - protected NonProtectedConstructorInAbstractClassInspection2() { - this(2); - } - - private NonProtectedConstructorInAbstractClassInspection2(int foo) { - super(); - } -} diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/inheritance/non_protected_constructor_in_abstract_class/NonProtectedConstructorInAbstractClass.java b/plugins/InspectionGadgets/test/com/siyeh/igtest/inheritance/non_protected_constructor_in_abstract_class/NonProtectedConstructorInAbstractClass.java new file mode 100644 index 000000000000..65f514a727ae --- /dev/null +++ b/plugins/InspectionGadgets/test/com/siyeh/igtest/inheritance/non_protected_constructor_in_abstract_class/NonProtectedConstructorInAbstractClass.java @@ -0,0 +1,13 @@ +package com.siyeh.igtest.classlayout; + +public abstract class NonProtectedConstructorInAbstractClass +{ + public NonProtectedConstructorInAbstractClass() + { + } + private NonProtectedConstructorInAbstractClass(int foo) + { + } + + public void(); +} diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/inheritance/refused_bequest/CloneCallsSuperClone.java b/plugins/InspectionGadgets/test/com/siyeh/igtest/inheritance/refused_bequest/CloneCallsSuperClone.java index ebbe4d7b5b2f..3a6370d7c700 100644 --- a/plugins/InspectionGadgets/test/com/siyeh/igtest/inheritance/refused_bequest/CloneCallsSuperClone.java +++ b/plugins/InspectionGadgets/test/com/siyeh/igtest/inheritance/refused_bequest/CloneCallsSuperClone.java @@ -40,7 +40,7 @@ final class Two { } } class Three { - public Three clone() throws CloneNotSupportedException { + public Three clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } } diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/inheritance/refused_bequest/RefusedBequest.java b/plugins/InspectionGadgets/test/com/siyeh/igtest/inheritance/refused_bequest/RefusedBequest.java index 89d4bb385e57..187a0946b65d 100644 --- a/plugins/InspectionGadgets/test/com/siyeh/igtest/inheritance/refused_bequest/RefusedBequest.java +++ b/plugins/InspectionGadgets/test/com/siyeh/igtest/inheritance/refused_bequest/RefusedBequest.java @@ -40,4 +40,10 @@ class E extends C { Runnable r = super::setUp; r.run(); } +} +class Deny extends ArrayList { + @Override + public int size() { + throw new UnsupportedOperationException(); + } } \ No newline at end of file diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/j2me/simplifiable_if_statement/SimplifiableIfStatement.java b/plugins/InspectionGadgets/test/com/siyeh/igtest/j2me/simplifiable_if_statement/SimplifiableIfStatement.java index a9fc8622513f..1574392abfcc 100644 --- a/plugins/InspectionGadgets/test/com/siyeh/igtest/j2me/simplifiable_if_statement/SimplifiableIfStatement.java +++ b/plugins/InspectionGadgets/test/com/siyeh/igtest/j2me/simplifiable_if_statement/SimplifiableIfStatement.java @@ -105,4 +105,11 @@ public class SimplifiableIfStatement { } return i = true; } + + boolean test( boolean b1, boolean b2, boolean b3 ) + { + if (b1 && b2) + return false; + return b3; + } } \ No newline at end of file diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/migration/collections_field_access_replaceable_by_method_call/CollectionsFieldAccessReplaceableByMethodCall.java b/plugins/InspectionGadgets/test/com/siyeh/igtest/migration/collections_field_access_replaceable_by_method_call/CollectionsFieldAccessReplaceableByMethodCall.java index 57fe7def0409..6d87912d6ce3 100644 --- a/plugins/InspectionGadgets/test/com/siyeh/igtest/migration/collections_field_access_replaceable_by_method_call/CollectionsFieldAccessReplaceableByMethodCall.java +++ b/plugins/InspectionGadgets/test/com/siyeh/igtest/migration/collections_field_access_replaceable_by_method_call/CollectionsFieldAccessReplaceableByMethodCall.java @@ -1,4 +1,4 @@ -import java.util.Collections; +import java.util.*; class CollectionsFieldAccessReplaceableByMethodCall { @@ -8,4 +8,8 @@ class CollectionsFieldAccessReplaceableByMethodCall { System.out.println(Collections.EMPTY_SET); } + boolean test(Map map) { + return map == Collections.EMPTY_MAP; // don't report object comparison + } + } \ No newline at end of file diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/migration/equals_replaceable_by_objects_call/EqualsReplaceableByObjectsCall.java b/plugins/InspectionGadgets/test/com/siyeh/igtest/migration/equals_replaceable_by_objects_call/EqualsReplaceableByObjectsCall.java index 7a8537fc0356..c333c3672bde 100644 --- a/plugins/InspectionGadgets/test/com/siyeh/igtest/migration/equals_replaceable_by_objects_call/EqualsReplaceableByObjectsCall.java +++ b/plugins/InspectionGadgets/test/com/siyeh/igtest/migration/equals_replaceable_by_objects_call/EqualsReplaceableByObjectsCall.java @@ -90,6 +90,15 @@ class EqualsReplaceableByObjectsCall { return CONSTANT.equals(o); } + boolean constantLiteral(Object o) { + return ("a" + "b").equals(o); + } + + static final String CONSTANT_EXPR = CONSTANT + 1; + boolean constantExpr(Object o) { + return CONSTANT_EXPR.equals(o); + } + static final String NULL_CONSTANT = null; boolean nullConstant(Object o) { return NULL_CONSTANT.equals(o); diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/performance/redundant_string_format_call/RedundantStringFormatCall.java b/plugins/InspectionGadgets/test/com/siyeh/igtest/performance/redundant_string_format_call/RedundantStringFormatCall.java index 9591444bc4c6..63dd1632ec9a 100644 --- a/plugins/InspectionGadgets/test/com/siyeh/igtest/performance/redundant_string_format_call/RedundantStringFormatCall.java +++ b/plugins/InspectionGadgets/test/com/siyeh/igtest/performance/redundant_string_format_call/RedundantStringFormatCall.java @@ -7,14 +7,15 @@ import static java.lang.String.format; public class RedundantStringFormatCall { public static final String A = String.format("%n"); - String b = String.format("no parameters"); - String c = String.format("asdf%n" + - "asdf%n"); - String d = String.format("asdf" + "asdf" + "asdf%n"); - String e = format("test"); + String b = String.format("no parameters"); + String c = String.format("asdf%n" + + "asdf%n"); + String d = String.format("asdf" + "asdf" + "asdf%n"); + String e = format("test"); - void m() { - System.out.println(String.format("string contains %%n%n")); // ok - System.out.println(String.format(Locale.ENGLISH, "string contains %%n%n")); - } + void m() { + System.out.println(String.format("string contains %%n%n")); // ok + System.out.println(String.format(Locale.ENGLISH, "string contains %%n%n")); + System.out.printf("empty battery"); + } } \ No newline at end of file diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/performance/redundant_string_format_call/expected.xml b/plugins/InspectionGadgets/test/com/siyeh/igtest/performance/redundant_string_format_call/expected.xml deleted file mode 100644 index 6ed624731209..000000000000 --- a/plugins/InspectionGadgets/test/com/siyeh/igtest/performance/redundant_string_format_call/expected.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - RedundantStringFormatCall.java - 10 - Redundant call to 'String.format()' - Redundant call to <code>format</code> #loc - - - - RedundantStringFormatCall.java - 14 - Redundant call to 'String.format()' - Redundant call to <code>format</code> #loc - - \ No newline at end of file diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/security/cloneable_class_in_secure_context/CloneableClassInSecureContext.java b/plugins/InspectionGadgets/test/com/siyeh/igtest/security/cloneable_class_in_secure_context/CloneableClassInSecureContext.java index 699651d95680..9123b54fe5f4 100644 --- a/plugins/InspectionGadgets/test/com/siyeh/igtest/security/cloneable_class_in_secure_context/CloneableClassInSecureContext.java +++ b/plugins/InspectionGadgets/test/com/siyeh/igtest/security/cloneable_class_in_secure_context/CloneableClassInSecureContext.java @@ -1,6 +1,7 @@ package com.siyeh.igtest.security.cloneable_class_in_secure_context; -class CloneableClassInSecureContext implements Cloneable { +class CloneableClassInSecureContext implements Cloneable { } interface EventListener { -} \ No newline at end of file +} +class Sub extends CloneableClassInSecureContext {} \ No newline at end of file diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/security/cloneable_class_in_secure_context/expected.xml b/plugins/InspectionGadgets/test/com/siyeh/igtest/security/cloneable_class_in_secure_context/expected.xml deleted file mode 100644 index cf81b3189e69..000000000000 --- a/plugins/InspectionGadgets/test/com/siyeh/igtest/security/cloneable_class_in_secure_context/expected.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - CloneableClassInSecureContext.java - 3 - Cloneable class in secure context - Class <code>CloneableClassInSecureContext</code> may be cloned, compromising security #loc - - \ No newline at end of file diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/security/deserializable_class_in_secure_context/DeserializableClass.java b/plugins/InspectionGadgets/test/com/siyeh/igtest/security/deserializable_class_in_secure_context/DeserializableClass.java deleted file mode 100644 index 18020afd1ed9..000000000000 --- a/plugins/InspectionGadgets/test/com/siyeh/igtest/security/deserializable_class_in_secure_context/DeserializableClass.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.siyeh.igtest.security.deserializable_class_in_secure_context; - -import java.io.ObjectInputStream; -import java.io.Serializable; - - -public class DeserializableClass implements Serializable { - private void readObject(ObjectInputStream in) { - - } -} -class NonDeserializableClass implements Serializable { - private void readObject(ObjectInputStream in) { - throw new Error(); - } -} -interface Event extends Serializable { - -} -class EventListener { - -} -class MyException extends Exception {} \ No newline at end of file diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/security/deserializable_class_in_secure_context/expected.xml b/plugins/InspectionGadgets/test/com/siyeh/igtest/security/deserializable_class_in_secure_context/expected.xml deleted file mode 100644 index 39b9febe5aaf..000000000000 --- a/plugins/InspectionGadgets/test/com/siyeh/igtest/security/deserializable_class_in_secure_context/expected.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - DeserializableClass.java - 7 - Deserializable class in secure context - Class <code>DeserializableClass</code> may be deserialized, compromising security #loc - - \ No newline at end of file diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/security/serializable_class_in_secure_context/SerializableClass.java b/plugins/InspectionGadgets/test/com/siyeh/igtest/security/serializable_class_in_secure_context/SerializableClass.java deleted file mode 100644 index 607c2bd498e0..000000000000 --- a/plugins/InspectionGadgets/test/com/siyeh/igtest/security/serializable_class_in_secure_context/SerializableClass.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.siyeh.igtest.security.serializable_class_in_secure_context; - -import java.io.ObjectOutputStream; -import java.io.Serializable; - - -public class SerializableClass implements Serializable { - - public void writeObject(ObjectOutputStream out) { - - } -} -class NonSerializableClass implements Serializable { - public void writeObject(ObjectOutputStream out) { - throw new Error(); - } -} -interface Event extends Serializable { - -} -class EventListener { - -} -class MyException extends Exception {} \ No newline at end of file diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/security/serializable_class_in_secure_context/expected.xml b/plugins/InspectionGadgets/test/com/siyeh/igtest/security/serializable_class_in_secure_context/expected.xml deleted file mode 100644 index 438ef1f14ccf..000000000000 --- a/plugins/InspectionGadgets/test/com/siyeh/igtest/security/serializable_class_in_secure_context/expected.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - SerializableClass.java - 7 - Serializable class in secure context - Class <code>SerializableClass</code> may be serialized, compromising security #loc - - \ No newline at end of file diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/security/serializable_deserializable_class_in_secure_context/SerializableDeserializableClassInSecureContext.java b/plugins/InspectionGadgets/test/com/siyeh/igtest/security/serializable_deserializable_class_in_secure_context/SerializableDeserializableClassInSecureContext.java new file mode 100644 index 000000000000..5ce18a821b25 --- /dev/null +++ b/plugins/InspectionGadgets/test/com/siyeh/igtest/security/serializable_deserializable_class_in_secure_context/SerializableDeserializableClassInSecureContext.java @@ -0,0 +1,41 @@ +import java.io.*; + +public class SerializableDeserializableClassInSecureContext implements Serializable { + private int sensitive = 736326; +} +class ClassWithoutState implements Serializable { + static int i = 0; + transient int j = 0; +} +class DeserializableClass implements Serializable { + private int sensitive = 736326; + private void readObject(ObjectInputStream in) { + throw new Error(); + } +} +class NonDeserializableClass implements Serializable { + private int sensitive = 736326; + private void readObject(ObjectInputStream in) { + throw new Error(); + } + public void writeObject(ObjectOutputStream out) { + throw new Error(); + } +} +class SerializableClass implements Serializable { + private int sensitive = 736326; + public void writeObject(ObjectOutputStream out) { + throw new Error(); + } +} + +interface Event extends Serializable { + int sensitive = 736326; +} +class EventListener { + private int sensitive = 736326; +} +class MyException extends Exception {} +enum E { + A, B, C +} \ No newline at end of file diff --git a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/abstraction/BooleanParameterInspectionTest.java b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/abstraction/BooleanParameterInspectionTest.java index 0cefd2c7682c..7c048e8ceb49 100644 --- a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/abstraction/BooleanParameterInspectionTest.java +++ b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/abstraction/BooleanParameterInspectionTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -38,6 +38,12 @@ public class BooleanParameterInspectionTest extends LightInspectionTestCase { "}"); } + public void testSetter() { + doTest("class X {" + + " public void setMyProperty(final boolean myProperty) {}" + + "}"); + } + @Override protected LocalInspectionTool getInspection() { return new BooleanParameterInspection(); diff --git a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/fixes/asserttoif/AssertionCanBeIfInspectionTest.java b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/fixes/asserttoif/AssertionCanBeIfInspectionTest.java index badf0bd29e1f..cf63a97af583 100644 --- a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/fixes/asserttoif/AssertionCanBeIfInspectionTest.java +++ b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/fixes/asserttoif/AssertionCanBeIfInspectionTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -32,6 +32,6 @@ public class AssertionCanBeIfInspectionTest extends IGQuickFixesTestCase { myDefaultHint = InspectionGadgetsBundle.message("assert.can.be.if.quickfix"); } - public void testIncomplete() { doTest(); } + public void testIncomplete() { assertQuickfixNotAvailable(); } public void testMessage() { doTest(); } } diff --git a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/fixes/asserttoif/IfCanBeAssertionInspectionTest.java b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/fixes/asserttoif/IfCanBeAssertionInspectionTest.java index 5f195b9f6cd4..1900224016d0 100644 --- a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/fixes/asserttoif/IfCanBeAssertionInspectionTest.java +++ b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/fixes/asserttoif/IfCanBeAssertionInspectionTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -35,4 +35,5 @@ public class IfCanBeAssertionInspectionTest extends IGQuickFixesTestCase { public void testRandomThrowable() { doTest(); } public void testParentheses() { doTest(); } + public void testNoCondition() { assertQuickfixNotAvailable(); } } diff --git a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/fixes/migration/IfCanBeSwitchFixTest.java b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/fixes/migration/IfCanBeSwitchFixTest.java index bc14f28ee9c5..ab8cd3013003 100644 --- a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/fixes/migration/IfCanBeSwitchFixTest.java +++ b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/fixes/migration/IfCanBeSwitchFixTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -15,10 +15,15 @@ */ package com.siyeh.ig.fixes.migration; +import com.intellij.testFramework.IdeaTestUtil; +import com.intellij.testFramework.builders.JavaModuleFixtureBuilder; import com.siyeh.InspectionGadgetsBundle; import com.siyeh.ig.IGQuickFixesTestCase; import com.siyeh.ig.migration.IfCanBeSwitchInspection; +/** + * @author Bas Leijdekkers + */ public class IfCanBeSwitchFixTest extends IGQuickFixesTestCase { @Override public void setUp() throws Exception { @@ -30,7 +35,16 @@ public class IfCanBeSwitchFixTest extends IGQuickFixesTestCase { myDefaultHint = InspectionGadgetsBundle.message("if.can.be.switch.quickfix"); } - public void testComment() { - doTest(); + @Override + protected void tuneFixture(JavaModuleFixtureBuilder builder) throws Exception { + super.tuneFixture(builder); + builder.addJdk(IdeaTestUtil.getMockJdk18Path().getPath()); } + + public void testComment() { doTest();} + public void testComments() { doTest(); } + public void testLong() { assertQuickfixNotAvailable(); } + public void testPolyadic() { doTest(); } + public void testStringEquality() { doTest(); } + public void testObjectsEquals() { doTest();} } diff --git a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/inheritance/NonProtectedConstructorInAbstractClassInspectionTest.java b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/inheritance/NonProtectedConstructorInAbstractClassInspectionTest.java new file mode 100644 index 000000000000..82272202d5ec --- /dev/null +++ b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/inheritance/NonProtectedConstructorInAbstractClassInspectionTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2000-2017 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.siyeh.ig.inheritance; + +import com.intellij.codeInspection.InspectionProfileEntry; +import com.siyeh.ig.LightInspectionTestCase; +import org.jetbrains.annotations.Nullable; + +/** + * @author Bas Leijdekkers + */ +public class NonProtectedConstructorInAbstractClassInspectionTest extends LightInspectionTestCase { + + public void testNonProtectedConstructorInAbstractClass() { + doTest(); + } + + @Nullable + @Override + protected InspectionProfileEntry getInspection() { + return new NonProtectedConstructorInAbstractClassInspection(); + } +} \ No newline at end of file diff --git a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/migration/IfCanBeSwitchInspectionTest.java b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/migration/IfCanBeSwitchInspectionTest.java index a13e61bfd234..20ab9d8b8074 100644 --- a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/migration/IfCanBeSwitchInspectionTest.java +++ b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/migration/IfCanBeSwitchInspectionTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -16,15 +16,7 @@ package com.siyeh.ig.migration; import com.intellij.codeInspection.InspectionProfileEntry; -import com.intellij.codeInspection.ex.LocalInspectionToolWrapper; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.roots.LanguageLevelProjectExtension; -import com.intellij.pom.java.LanguageLevel; -import com.intellij.testFramework.IdeaTestUtil; -import com.intellij.testFramework.LightProjectDescriptor; -import com.siyeh.ig.IGInspectionTestCase; import com.siyeh.ig.LightInspectionTestCase; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class IfCanBeSwitchInspectionTest extends LightInspectionTestCase { diff --git a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/performance/RedundantStringFormatCallInspectionTest.java b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/performance/RedundantStringFormatCallInspectionTest.java index 2f0f59709ba1..be178133d7ae 100644 --- a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/performance/RedundantStringFormatCallInspectionTest.java +++ b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/performance/RedundantStringFormatCallInspectionTest.java @@ -1,12 +1,36 @@ +/* + * Copyright 2000-2017 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.siyeh.ig.performance; -import com.siyeh.ig.IGInspectionTestCase; +import com.intellij.codeInspection.InspectionProfileEntry; +import com.siyeh.ig.LightInspectionTestCase; +import org.jetbrains.annotations.Nullable; -public class RedundantStringFormatCallInspectionTest - extends IGInspectionTestCase { +/** + * @author Bas Leijdekkers + */ +public class RedundantStringFormatCallInspectionTest extends LightInspectionTestCase { - public void test() throws Exception { - doTest("com/siyeh/igtest/performance/redundant_string_format_call", - new RedundantStringFormatCallInspection()); + public void testRedundantStringFormatCall() { + doTest(); + } + + @Nullable + @Override + protected InspectionProfileEntry getInspection() { + return new RedundantStringFormatCallInspection(); } } \ No newline at end of file diff --git a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/security/CloneableClassInSecureContextInspectionTest.java b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/security/CloneableClassInSecureContextInspectionTest.java index 268c30009f62..f893bbb13c9f 100644 --- a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/security/CloneableClassInSecureContextInspectionTest.java +++ b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/security/CloneableClassInSecureContextInspectionTest.java @@ -1,10 +1,33 @@ +/* + * Copyright 2000-2017 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.siyeh.ig.security; -import com.siyeh.ig.IGInspectionTestCase; +import com.intellij.codeInspection.InspectionProfileEntry; +import com.siyeh.ig.LightInspectionTestCase; +import org.jetbrains.annotations.Nullable; -public class CloneableClassInSecureContextInspectionTest extends IGInspectionTestCase { +public class CloneableClassInSecureContextInspectionTest extends LightInspectionTestCase { - public void test() throws Exception { - doTest("com/siyeh/igtest/security/cloneable_class_in_secure_context", new CloneableClassInSecureContextInspection()); + public void testCloneableClassInSecureContext() { + doTest(); + } + + @Nullable + @Override + protected InspectionProfileEntry getInspection() { + return new CloneableClassInSecureContextInspection(); } } \ No newline at end of file diff --git a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/security/DeserializableClassInSecureContextInspectionTest.java b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/security/DeserializableClassInSecureContextInspectionTest.java deleted file mode 100644 index 307754f2b35f..000000000000 --- a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/security/DeserializableClassInSecureContextInspectionTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.siyeh.ig.security; - -import com.siyeh.ig.IGInspectionTestCase; - -public class DeserializableClassInSecureContextInspectionTest extends IGInspectionTestCase { - - public void test() throws Exception { - final DeserializableClassInSecureContextInspection tool = new DeserializableClassInSecureContextInspection(); - tool.ignoreThrowable = true; - doTest("com/siyeh/igtest/security/deserializable_class_in_secure_context", tool); - } -} \ No newline at end of file diff --git a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/security/SerializableClassInSecureContextInspectionTest.java b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/security/SerializableClassInSecureContextInspectionTest.java deleted file mode 100644 index 2af3cd9026ce..000000000000 --- a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/security/SerializableClassInSecureContextInspectionTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.siyeh.ig.security; - -import com.siyeh.ig.IGInspectionTestCase; - -public class SerializableClassInSecureContextInspectionTest extends IGInspectionTestCase { - - public void test() throws Exception { - final SerializableClassInSecureContextInspection tool = new SerializableClassInSecureContextInspection(); - tool.ignoreThrowable = true; - doTest("com/siyeh/igtest/security/serializable_class_in_secure_context", tool); - } -} \ No newline at end of file diff --git a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/security/SerializableDeserializableClassInSecureContextInspectionTest.java b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/security/SerializableDeserializableClassInSecureContextInspectionTest.java new file mode 100644 index 000000000000..cbc535269700 --- /dev/null +++ b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/security/SerializableDeserializableClassInSecureContextInspectionTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2017 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.siyeh.ig.security; + +import com.intellij.codeInspection.InspectionProfileEntry; +import com.siyeh.ig.LightInspectionTestCase; +import org.jetbrains.annotations.Nullable; + +/** + * @author Bas Leijdekkers + */ +public class SerializableDeserializableClassInSecureContextInspectionTest extends LightInspectionTestCase { + + public void testSerializableDeserializableClassInSecureContext() { + doTest(); + } + + @Nullable + @Override + protected InspectionProfileEntry getInspection() { + final SerializableDeserializableClassInSecureContextInspection inspection = + new SerializableDeserializableClassInSecureContextInspection(); + inspection.ignoreThrowable = true; + return inspection; + } +} \ No newline at end of file diff --git a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/threading/NonAtomicOperationOnVolatileFieldInspectionTest.java b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/threading/NonAtomicOperationOnVolatileFieldInspectionTest.java new file mode 100644 index 000000000000..d10174bff78b --- /dev/null +++ b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/threading/NonAtomicOperationOnVolatileFieldInspectionTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2000-2017 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.siyeh.ig.threading; + +import com.intellij.codeInspection.InspectionProfileEntry; +import com.siyeh.ig.LightInspectionTestCase; +import org.jetbrains.annotations.Nullable; + +/** + * @author Bas Leijdekkers + */ +@SuppressWarnings("NonAtomicOperationOnVolatileField") +public class NonAtomicOperationOnVolatileFieldInspectionTest extends LightInspectionTestCase { + + public void testWriteToDifferentInstance() { + doTest("class A {\n" + + " private volatile int v;\n" + + "\n" + + " A copy() {\n" + + " A a = new A();\n" + + " a.v = v;\n" + + " return a;\n" + + " }\n" + + "}"); + } + + public void testPostfix() { + doTest("class X {" + + " private volatile int v = 1;" + + " void m() {" + + " /*Non-atomic operation on volatile field 'v'*/v/**/++;" + + " }" + + "}"); + } + + public void testPrefix() { + doTest("class X {" + + " private volatile int v = 1;" + + " void m() {" + + " ++/*Non-atomic operation on volatile field 'v'*/v/**/;" + + " }" + + "}"); + } + + public void testSimple() { + doTest("class X {" + + " private volatile int v = 1;" + + " void m() {" + + " /*Non-atomic operation on volatile field 'v'*/v/**/ = 3 + v;" + + " }" + + "}"); + } + + public void testQualified() { + doTest("class X {" + + " private volatile int v = 1;" + + " void m() {" + + " (this)./*Non-atomic operation on volatile field 'v'*/v/**/ = 2 * (this).v;" + + " }" + + "}"); + } + + @Nullable + @Override + protected InspectionProfileEntry getInspection() { + return new NonAtomicOperationOnVolatileFieldInspection(); + } +} \ No newline at end of file diff --git a/plugins/IntentionPowerPak/src/META-INF/IntentionPowerPack.xml b/plugins/IntentionPowerPak/src/META-INF/IntentionPowerPack.xml index f6baf874ea50..a8b6c76df955 100644 --- a/plugins/IntentionPowerPak/src/META-INF/IntentionPowerPack.xml +++ b/plugins/IntentionPowerPak/src/META-INF/IntentionPowerPack.xml @@ -259,11 +259,6 @@ com.siyeh.IntentionPowerPackBundle intention.category.control.flow - - com.siyeh.ipp.switchtoif.ReplaceIfWithSwitchIntention - com.siyeh.IntentionPowerPackBundle - intention.category.control.flow - com.siyeh.ipp.trivialif.SimplifyIfElseIntention com.siyeh.IntentionPowerPackBundle diff --git a/plugins/IntentionPowerPak/src/com/siyeh/IntentionPowerPackBundle.properties b/plugins/IntentionPowerPak/src/com/siyeh/IntentionPowerPackBundle.properties index 2bbc06f742b4..245f8a402b0b 100644 --- a/plugins/IntentionPowerPak/src/com/siyeh/IntentionPowerPackBundle.properties +++ b/plugins/IntentionPowerPak/src/com/siyeh/IntentionPowerPackBundle.properties @@ -1,9 +1,7 @@ plugin.IntentionPowerPack.description=Adds over 80 new intention actions for IDEA. replace.fully.qualified.name.with.import.intention.name=Replace qualified name with 'import' replace.fully.qualified.name.with.import.intention.family.name=Replace Qualified Name with Import -replace.if.with.switch.intention.name=Replace 'if' with 'switch' extract.to.method.reference.intention.name=Extract to method reference -replace.if.with.switch.intention.family.name=Replace If with Switch merge.parallel.ifs.intention.name=Merge 'if's merge.parallel.ifs.intention.family.name=Merge Parallel Ifs replace.conditional.with.if.intention.name=Replace '?:' with 'if else' diff --git a/plugins/IntentionPowerPak/src/com/siyeh/ipp/comment/MoveCommentToSeparateLineIntention.java b/plugins/IntentionPowerPak/src/com/siyeh/ipp/comment/MoveCommentToSeparateLineIntention.java index 85bdc7cbb683..f98d0703bdc5 100644 --- a/plugins/IntentionPowerPak/src/com/siyeh/ipp/comment/MoveCommentToSeparateLineIntention.java +++ b/plugins/IntentionPowerPak/src/com/siyeh/ipp/comment/MoveCommentToSeparateLineIntention.java @@ -21,6 +21,7 @@ import com.intellij.openapi.editor.LogicalPosition; import com.intellij.openapi.editor.ScrollType; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; import com.siyeh.ipp.base.Intention; @@ -61,10 +62,11 @@ public class MoveCommentToSeparateLineIntention extends Intention { newline = text.substring(text.lastIndexOf('\n')); } final PsiElement prev = PsiTreeUtil.prevLeaf(comment); - final int deleteOffset = prev instanceof PsiWhiteSpace ? prev.getTextOffset() : comment.getTextOffset(); - document.deleteString(deleteOffset, comment.getTextOffset() + comment.getTextLength()); + final TextRange commentRange = comment.getTextRange(); + final int deleteOffset = prev instanceof PsiWhiteSpace ? prev.getTextRange().getStartOffset() : commentRange.getStartOffset(); + document.deleteString(deleteOffset, commentRange.getEndOffset()); - final int offset = anchor.getTextOffset(); + final int offset = anchor.getTextRange().getStartOffset(); document.insertString(offset, newline); document.insertString(offset, comment.getText()); scrollToVisible(project, offset); diff --git a/plugins/IntentionPowerPak/src/com/siyeh/ipp/conditional/FlipConditionalPredicate.java b/plugins/IntentionPowerPak/src/com/siyeh/ipp/conditional/FlipConditionalPredicate.java index eaf9ae83406b..122371ede525 100644 --- a/plugins/IntentionPowerPak/src/com/siyeh/ipp/conditional/FlipConditionalPredicate.java +++ b/plugins/IntentionPowerPak/src/com/siyeh/ipp/conditional/FlipConditionalPredicate.java @@ -1,5 +1,5 @@ /* - * Copyright 2003-2005 Dave Griffith + * Copyright 2003-2017 Dave Griffith, Bas Leijdekkers * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,10 @@ package com.siyeh.ipp.conditional; import com.intellij.psi.PsiConditionalExpression; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiErrorElement; +import com.intellij.psi.PsiExpression; +import com.intellij.psi.util.PsiTreeUtil; import com.siyeh.ipp.base.PsiElementPredicate; -import com.siyeh.ipp.psiutils.ErrorUtil; class FlipConditionalPredicate implements PsiElementPredicate { @@ -26,12 +28,10 @@ class FlipConditionalPredicate implements PsiElementPredicate { if (!(element instanceof PsiConditionalExpression)) { return false; } - final PsiConditionalExpression condition = - (PsiConditionalExpression)element; - if (condition.getThenExpression() == null || - condition.getElseExpression() == null) { - return false; - } - return !ErrorUtil.containsError(element); + final PsiConditionalExpression conditionalExpression = (PsiConditionalExpression)element; + final PsiExpression condition = conditionalExpression.getCondition(); + return !(PsiTreeUtil.getDeepestLast(condition) instanceof PsiErrorElement) && + conditionalExpression.getThenExpression() != null && + conditionalExpression.getElseExpression() != null; } } \ No newline at end of file diff --git a/plugins/IntentionPowerPak/src/com/siyeh/ipp/conditional/ReplaceConditionalWithIfPredicate.java b/plugins/IntentionPowerPak/src/com/siyeh/ipp/conditional/ReplaceConditionalWithIfPredicate.java index 597d95c422d9..ee1425b1ed1f 100644 --- a/plugins/IntentionPowerPak/src/com/siyeh/ipp/conditional/ReplaceConditionalWithIfPredicate.java +++ b/plugins/IntentionPowerPak/src/com/siyeh/ipp/conditional/ReplaceConditionalWithIfPredicate.java @@ -1,5 +1,5 @@ /* - * Copyright 2003-2012 Dave Griffith, Bas Leijdekkers + * Copyright 2003-2017 Dave Griffith, Bas Leijdekkers * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,8 @@ package com.siyeh.ipp.conditional; import com.intellij.psi.*; -import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.FileTypeUtils; +import com.intellij.psi.util.PsiTreeUtil; import com.siyeh.ipp.base.PsiElementPredicate; class ReplaceConditionalWithIfPredicate implements PsiElementPredicate { @@ -28,6 +28,10 @@ class ReplaceConditionalWithIfPredicate implements PsiElementPredicate { } final PsiConditionalExpression conditionalExpression = (PsiConditionalExpression)element; final PsiElement parent = conditionalExpression.getParent(); + final PsiExpression condition = conditionalExpression.getCondition(); + if (PsiTreeUtil.getDeepestLast(condition) instanceof PsiErrorElement) { + return false; + } if (parent instanceof PsiExpressionStatement) { return false; } diff --git a/plugins/IntentionPowerPak/src/com/siyeh/ipp/switchtoif/IfToSwitchPredicate.java b/plugins/IntentionPowerPak/src/com/siyeh/ipp/switchtoif/IfToSwitchPredicate.java deleted file mode 100644 index c7907c2861a4..000000000000 --- a/plugins/IntentionPowerPak/src/com/siyeh/ipp/switchtoif/IfToSwitchPredicate.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2003-2015 Dave Griffith, Bas Leijdekkers - * - * 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.siyeh.ipp.switchtoif; - -import com.intellij.psi.JavaTokenType; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiIfStatement; -import com.intellij.psi.PsiJavaToken; -import com.siyeh.ig.psiutils.SwitchUtils; -import com.siyeh.ipp.base.PsiElementPredicate; -import com.siyeh.ipp.psiutils.ErrorUtil; - -class IfToSwitchPredicate implements PsiElementPredicate { - - @Override - public boolean satisfiedBy(PsiElement element) { - if (!(element instanceof PsiJavaToken)) { - return false; - } - final PsiJavaToken token = (PsiJavaToken)element; - if (token.getTokenType() != JavaTokenType.IF_KEYWORD) { - return false; - } - final PsiElement parent = element.getParent(); - if (!(parent instanceof PsiIfStatement)) { - return false; - } - final PsiIfStatement statement = (PsiIfStatement)parent; - if (ErrorUtil.containsError(statement)) { - return false; - } - return SwitchUtils.getSwitchExpression(statement, 0, false, true) != null; - } -} \ No newline at end of file diff --git a/plugins/IntentionPowerPak/src/com/siyeh/ipp/switchtoif/ReplaceIfWithSwitchIntention.java b/plugins/IntentionPowerPak/src/com/siyeh/ipp/switchtoif/ReplaceIfWithSwitchIntention.java deleted file mode 100644 index f22419f6c4a9..000000000000 --- a/plugins/IntentionPowerPak/src/com/siyeh/ipp/switchtoif/ReplaceIfWithSwitchIntention.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2003-2015 Dave Griffith, Bas Leijdekkers - * - * 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.siyeh.ipp.switchtoif; - -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiIfStatement; -import com.siyeh.ig.migration.IfCanBeSwitchInspection; -import com.siyeh.ipp.base.Intention; -import com.siyeh.ipp.base.PsiElementPredicate; -import org.jetbrains.annotations.NotNull; - -public class ReplaceIfWithSwitchIntention extends Intention { - - @Override - @NotNull - public PsiElementPredicate getElementPredicate() { - return new IfToSwitchPredicate(); - } - - @Override - public void processIntention(@NotNull PsiElement element) { - final PsiElement parent = element.getParent(); - if (!(parent instanceof PsiIfStatement)) { - return; - } - final PsiIfStatement ifStatement = (PsiIfStatement)parent; - IfCanBeSwitchInspection.replaceIfWithSwitch(ifStatement); - } -} \ No newline at end of file diff --git a/plugins/IntentionPowerPak/src/com/siyeh/ipp/switchtoif/ReplaceSwitchWithIfIntention.java b/plugins/IntentionPowerPak/src/com/siyeh/ipp/switchtoif/ReplaceSwitchWithIfIntention.java index fba3bb404db5..1f7650e5fc4a 100644 --- a/plugins/IntentionPowerPak/src/com/siyeh/ipp/switchtoif/ReplaceSwitchWithIfIntention.java +++ b/plugins/IntentionPowerPak/src/com/siyeh/ipp/switchtoif/ReplaceSwitchWithIfIntention.java @@ -1,5 +1,5 @@ /* - * Copyright 2003-2011 Dave Griffith, Bas Leijdekkers + * Copyright 2003-2017 Dave Griffith, Bas Leijdekkers * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,9 @@ package com.siyeh.ipp.switchtoif; import com.intellij.codeInsight.daemon.impl.quickfix.ConvertSwitchToIfIntention; -import com.intellij.psi.*; -import com.intellij.util.IncorrectOperationException; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiJavaToken; +import com.intellij.psi.PsiSwitchStatement; import com.siyeh.ipp.base.Intention; import com.siyeh.ipp.base.PsiElementPredicate; import org.jetbrains.annotations.NotNull; @@ -30,11 +31,9 @@ public class ReplaceSwitchWithIfIntention extends Intention { } @Override - public void processIntention(@NotNull PsiElement element) - throws IncorrectOperationException { + public void processIntention(@NotNull PsiElement element) { final PsiJavaToken switchToken = (PsiJavaToken)element; - final PsiSwitchStatement switchStatement = - (PsiSwitchStatement)switchToken.getParent(); + final PsiSwitchStatement switchStatement = (PsiSwitchStatement)switchToken.getParent(); if (switchStatement == null) { return; } diff --git a/plugins/IntentionPowerPak/src/intentionDescriptions/ReplaceIfWithSwitchIntention/after.java.template b/plugins/IntentionPowerPak/src/intentionDescriptions/ReplaceIfWithSwitchIntention/after.java.template deleted file mode 100644 index ed928a5b1319..000000000000 --- a/plugins/IntentionPowerPak/src/intentionDescriptions/ReplaceIfWithSwitchIntention/after.java.template +++ /dev/null @@ -1,12 +0,0 @@ -public class X { - int f(int a) { - switch (a) { - case 0: - return 1; - case 9: - return 8; - default: - return 2; - } - } -} \ No newline at end of file diff --git a/plugins/IntentionPowerPak/src/intentionDescriptions/ReplaceIfWithSwitchIntention/before.java.template b/plugins/IntentionPowerPak/src/intentionDescriptions/ReplaceIfWithSwitchIntention/before.java.template deleted file mode 100644 index 4f2d2a7501b8..000000000000 --- a/plugins/IntentionPowerPak/src/intentionDescriptions/ReplaceIfWithSwitchIntention/before.java.template +++ /dev/null @@ -1,11 +0,0 @@ -public class X { - int f(int a) { - if (a == 0) { - return 1; - } else if (a == 9) { - return 8; - } else { - return 2; - } - } -} \ No newline at end of file diff --git a/plugins/IntentionPowerPak/src/intentionDescriptions/ReplaceIfWithSwitchIntention/description.html b/plugins/IntentionPowerPak/src/intentionDescriptions/ReplaceIfWithSwitchIntention/description.html deleted file mode 100644 index 0965a686f04a..000000000000 --- a/plugins/IntentionPowerPak/src/intentionDescriptions/ReplaceIfWithSwitchIntention/description.html +++ /dev/null @@ -1,6 +0,0 @@ - - -This intention replaces series of if-else -statements with an equivalent switch statement. - - diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/conditional/withIf/BrokenCode.java b/plugins/IntentionPowerPak/test/com/siyeh/ipp/conditional/withIf/BrokenCode.java new file mode 100644 index 000000000000..b32cafe3d911 --- /dev/null +++ b/plugins/IntentionPowerPak/test/com/siyeh/ipp/conditional/withIf/BrokenCode.java @@ -0,0 +1,4 @@ +class X { + + int a = foo(? 2 : 3); +} \ No newline at end of file diff --git a/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/conditional/ReplaceConditionalWithIfTest.java b/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/conditional/ReplaceConditionalWithIfTest.java index a7f0e2794c35..7a29b50e9f43 100644 --- a/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/conditional/ReplaceConditionalWithIfTest.java +++ b/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/conditional/ReplaceConditionalWithIfTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -23,24 +23,26 @@ import com.siyeh.ipp.IPPTestCase; */ public class ReplaceConditionalWithIfTest extends IPPTestCase { - public void testConditionalAsArgument() { doTest(); } - public void testComment() { doTest(); } - public void testParentheses() { doTest(); } - public void testConditionalInIf() { doTest(); } - public void testConditionalInBinaryExpression() { doTest(); } - public void testArrayInitializer() { doTest(); } - public void testInsideExprLambda() { doTest(); } - public void testInsideExprLambdaWithParams() { doTest(); } - public void testCastNeeded() { doTest(); } + public void testConditionalAsArgument() { doTest(); } + public void testComment() { doTest(); } + public void testParentheses() { doTest(); } + public void testConditionalInIf() { doTest(); } + public void testConditionalInBinaryExpression() { doTest(); } + public void testArrayInitializer() { doTest(); } + public void testInsideExprLambda() { doTest(); } + public void testInsideExprLambdaWithParams() { doTest(); } + public void testCastNeeded() { doTest(); } - @Override - protected String getIntentionName() { - return IntentionPowerPackBundle.message( - "replace.conditional.with.if.intention.name"); - } + public void testBrokenCode() { assertIntentionNotAvailable(); } - @Override - protected String getRelativePath() { - return "conditional/withIf"; - } + @Override + protected String getIntentionName() { + return IntentionPowerPackBundle.message( + "replace.conditional.with.if.intention.name"); + } + + @Override + protected String getRelativePath() { + return "conditional/withIf"; + } } diff --git a/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/switchtoif/ReplaceIfWithSwitchIntentionTest.java b/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/switchtoif/ReplaceIfWithSwitchIntentionTest.java deleted file mode 100644 index c1bfd02d34da..000000000000 --- a/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/switchtoif/ReplaceIfWithSwitchIntentionTest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2000-2015 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.siyeh.ipp.switchtoif; - -import com.siyeh.IntentionPowerPackBundle; -import com.siyeh.ipp.IPPTestCase; - -/** - * @see ReplaceIfWithSwitchIntention - */ -public class ReplaceIfWithSwitchIntentionTest extends IPPTestCase { - - public void testComments() { doTest(); } - public void testLong() { assertIntentionNotAvailable(); } - public void testPolyadic() { doTest(); } - public void testStringEquality() { doTest(); } - public void testObjectsEquals() { - myFixture.addClass("package java.util;" + - "public final class Objects {" + - " public static boolean equals(Object a, Object b) {\n" + - " return (a == b) || (a != null && a.equals(b));\n" + - " }" + - "}"); - doTest(); - } - - @Override - protected String getIntentionName() { - return IntentionPowerPackBundle.message("replace.if.with.switch.intention.name"); - } - - @Override - protected String getRelativePath() { - return "switchtoif/replace_if_with_switch"; - } -} diff --git a/plugins/ant/resources/icons/AntBuildXml.png b/plugins/ant/resources/icons/AntBuildXml.png index d7892abc8edc..6880c4bc250a 100644 Binary files a/plugins/ant/resources/icons/AntBuildXml.png and b/plugins/ant/resources/icons/AntBuildXml.png differ diff --git a/plugins/ant/resources/icons/AntBuildXml@2x.png b/plugins/ant/resources/icons/AntBuildXml@2x.png index 7a00eab722cb..a6770d10d97b 100644 Binary files a/plugins/ant/resources/icons/AntBuildXml@2x.png and b/plugins/ant/resources/icons/AntBuildXml@2x.png differ diff --git a/plugins/ant/resources/icons/AntBuildXml@2x_dark.png b/plugins/ant/resources/icons/AntBuildXml@2x_dark.png index 3880bc4a5bac..89854b17b92f 100644 Binary files a/plugins/ant/resources/icons/AntBuildXml@2x_dark.png and b/plugins/ant/resources/icons/AntBuildXml@2x_dark.png differ diff --git a/plugins/ant/resources/icons/AntBuildXml_dark.png b/plugins/ant/resources/icons/AntBuildXml_dark.png index 10a5c4a5a1a4..9aef7ba87e7a 100644 Binary files a/plugins/ant/resources/icons/AntBuildXml_dark.png and b/plugins/ant/resources/icons/AntBuildXml_dark.png differ diff --git a/plugins/ant/resources/icons/antInstallation.png b/plugins/ant/resources/icons/antInstallation.png index 6837da659ff0..d8d91abae37d 100644 Binary files a/plugins/ant/resources/icons/antInstallation.png and b/plugins/ant/resources/icons/antInstallation.png differ diff --git a/plugins/ant/resources/icons/antInstallation@2x.png b/plugins/ant/resources/icons/antInstallation@2x.png index 8c804ccd3b50..d91ad46c5293 100644 Binary files a/plugins/ant/resources/icons/antInstallation@2x.png and b/plugins/ant/resources/icons/antInstallation@2x.png differ diff --git a/plugins/ant/resources/icons/antInstallation@2x_dark.png b/plugins/ant/resources/icons/antInstallation@2x_dark.png index fe8790824186..954c24f1c4b2 100644 Binary files a/plugins/ant/resources/icons/antInstallation@2x_dark.png and b/plugins/ant/resources/icons/antInstallation@2x_dark.png differ diff --git a/plugins/ant/resources/icons/antInstallation_dark.png b/plugins/ant/resources/icons/antInstallation_dark.png index 0f780106c72b..264abeb48701 100644 Binary files a/plugins/ant/resources/icons/antInstallation_dark.png and b/plugins/ant/resources/icons/antInstallation_dark.png differ diff --git a/plugins/ant/resources/icons/build.png b/plugins/ant/resources/icons/build.png index 6837da659ff0..d8d91abae37d 100644 Binary files a/plugins/ant/resources/icons/build.png and b/plugins/ant/resources/icons/build.png differ diff --git a/plugins/ant/resources/icons/build@2x.png b/plugins/ant/resources/icons/build@2x.png index 8c804ccd3b50..d91ad46c5293 100644 Binary files a/plugins/ant/resources/icons/build@2x.png and b/plugins/ant/resources/icons/build@2x.png differ diff --git a/plugins/ant/resources/icons/build@2x_dark.png b/plugins/ant/resources/icons/build@2x_dark.png index fe8790824186..954c24f1c4b2 100644 Binary files a/plugins/ant/resources/icons/build@2x_dark.png and b/plugins/ant/resources/icons/build@2x_dark.png differ diff --git a/plugins/ant/resources/icons/build_dark.png b/plugins/ant/resources/icons/build_dark.png index 0f780106c72b..264abeb48701 100644 Binary files a/plugins/ant/resources/icons/build_dark.png and b/plugins/ant/resources/icons/build_dark.png differ diff --git a/plugins/ant/resources/icons/task.png b/plugins/ant/resources/icons/task.png index dada7ebd6963..ffdf75f9cd88 100644 Binary files a/plugins/ant/resources/icons/task.png and b/plugins/ant/resources/icons/task.png differ diff --git a/plugins/ant/resources/icons/task@2x.png b/plugins/ant/resources/icons/task@2x.png index 36ac2b123730..cae32c19c280 100644 Binary files a/plugins/ant/resources/icons/task@2x.png and b/plugins/ant/resources/icons/task@2x.png differ diff --git a/plugins/ant/src/com/intellij/lang/ant/config/actions/TargetActionStub.java b/plugins/ant/src/com/intellij/lang/ant/config/actions/TargetActionStub.java index eea6ff110aeb..79bd3455fcc5 100644 --- a/plugins/ant/src/com/intellij/lang/ant/config/actions/TargetActionStub.java +++ b/plugins/ant/src/com/intellij/lang/ant/config/actions/TargetActionStub.java @@ -65,7 +65,7 @@ public class TargetActionStub extends AnAction implements Disposable { } finally { invokeAction(e); - dispose(); + Disposer.dispose(this); } } diff --git a/plugins/commander/src/com/intellij/ide/commander/CommanderSelectInTarget.java b/plugins/commander/src/com/intellij/ide/commander/CommanderSelectInTarget.java index 521a1a22d5cf..04352086e10a 100644 --- a/plugins/commander/src/com/intellij/ide/commander/CommanderSelectInTarget.java +++ b/plugins/commander/src/com/intellij/ide/commander/CommanderSelectInTarget.java @@ -80,10 +80,6 @@ public final class CommanderSelectInTarget extends SelectInTargetPsiWrapper { } - protected boolean canWorkWithCustomObjects() { - return false; - } - public String getToolWindowId() { return ToolWindowId.COMMANDER; } diff --git a/plugins/coverage-common/src/com/intellij/coverage/CoverageExecutor.java b/plugins/coverage-common/src/com/intellij/coverage/CoverageExecutor.java index 1a38ba9ce3ee..bd44bd8fc73d 100644 --- a/plugins/coverage-common/src/com/intellij/coverage/CoverageExecutor.java +++ b/plugins/coverage-common/src/com/intellij/coverage/CoverageExecutor.java @@ -19,7 +19,7 @@ public class CoverageExecutor extends Executor { @Override public String getStartActionText(String configurationName) { - final String name = configurationName != null ? escapeMnemonicsInConfigurationName(StringUtil.first(configurationName, 30, true)) : null; + final String name = configurationName != null ? escapeMnemonicsInConfigurationName(shortenNameIfNeed(configurationName)) : null; return "Run" + (StringUtil.isEmpty(name) ? "" : " '" + name + "'") + " with Co_verage"; } diff --git a/plugins/devkit/devkit-tests.iml b/plugins/devkit/devkit-tests.iml deleted file mode 100644 index 5a284bae86f2..000000000000 --- a/plugins/devkit/devkit-tests.iml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/plugins/devkit/devkit.iml b/plugins/devkit/devkit.iml index c28b90e557bd..478f0ac93700 100644 --- a/plugins/devkit/devkit.iml +++ b/plugins/devkit/devkit.iml @@ -4,6 +4,7 @@ + @@ -42,5 +43,9 @@ + + + + \ No newline at end of file diff --git a/plugins/devkit/resources/META-INF/plugin.xml b/plugins/devkit/resources/META-INF/plugin.xml index aff372b04dee..051b4be3f2dd 100644 --- a/plugins/devkit/resources/META-INF/plugin.xml +++ b/plugins/devkit/resources/META-INF/plugin.xml @@ -117,6 +117,10 @@ groupKey="inspections.group.name" enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.idea.devkit.inspections.internal.UseDPIAwareInsetsInspection"/> + + diff --git a/plugins/devkit/resources/icons/sdk_closed.png b/plugins/devkit/resources/icons/sdk_closed.png index ab3fcabe6b07..4153fe2082b3 100644 Binary files a/plugins/devkit/resources/icons/sdk_closed.png and b/plugins/devkit/resources/icons/sdk_closed.png differ diff --git a/plugins/devkit/resources/icons/sdk_closed@2x.png b/plugins/devkit/resources/icons/sdk_closed@2x.png index 2c2a40a402e1..3147f0f47424 100644 Binary files a/plugins/devkit/resources/icons/sdk_closed@2x.png and b/plugins/devkit/resources/icons/sdk_closed@2x.png differ diff --git a/plugins/devkit/resources/inspectionDescriptions/UseDPIAwareBorders.html b/plugins/devkit/resources/inspectionDescriptions/UseDPIAwareBorders.html new file mode 100644 index 000000000000..6504843d36f0 --- /dev/null +++ b/plugins/devkit/resources/inspectionDescriptions/UseDPIAwareBorders.html @@ -0,0 +1,5 @@ + + +Use JBUI.Borders.empty for DPI-aware empty borders + + \ No newline at end of file diff --git a/plugins/devkit/resources/org/jetbrains/idea/devkit/DevKitBundle.properties b/plugins/devkit/resources/org/jetbrains/idea/devkit/DevKitBundle.properties index bcb11d87e0ef..fe46e8d3aecb 100644 --- a/plugins/devkit/resources/org/jetbrains/idea/devkit/DevKitBundle.properties +++ b/plugins/devkit/resources/org/jetbrains/idea/devkit/DevKitBundle.properties @@ -164,3 +164,6 @@ select.target.location.of.description=Select target location of {0} serialization.only.member.used.explicitly=Serialization-only member used explicitly implemented.at.runtime.dom=DOM Element implemented at runtime implemented.at.runtime.jamElement=JAM Element implemented at runtime + +line.marker.tooltip.extension.declaration={2} extension declaration in {4} +line.marker.tooltip.extension.point.declaration={2} extension point declaration in {4} diff --git a/plugins/devkit/src/inspections/StatefulEpInspection.java b/plugins/devkit/src/inspections/StatefulEpInspection.java index 3af76add1480..f226a8d74846 100644 --- a/plugins/devkit/src/inspections/StatefulEpInspection.java +++ b/plugins/devkit/src/inspections/StatefulEpInspection.java @@ -29,7 +29,7 @@ import com.intellij.psi.util.InheritanceUtil; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.idea.devkit.util.ExtensionPointLocator; +import org.jetbrains.idea.devkit.util.ExtensionLocator; import org.jetbrains.uast.UClass; import java.util.List; @@ -42,7 +42,7 @@ public class StatefulEpInspection extends DevKitUastInspectionBase { if (fields.length == 0) return super.checkClass(psiClass, manager, isOnTheFly); final boolean isQuickFix = InheritanceUtil.isInheritor(psiClass, LocalQuickFix.class.getCanonicalName()); - if (isQuickFix || ExtensionPointLocator.isRegisteredExtension(psiClass)) { + if (isQuickFix || ExtensionLocator.isRegisteredExtension(psiClass)) { final boolean isProjectComponent = InheritanceUtil.isInheritor(psiClass, ProjectComponent.class.getCanonicalName()); List result = ContainerUtil.newArrayList(); diff --git a/plugins/devkit/src/inspections/internal/UseDPIAwareEmptyBorderInspection.java b/plugins/devkit/src/inspections/internal/UseDPIAwareEmptyBorderInspection.java new file mode 100644 index 000000000000..8ef4ad3b0f5f --- /dev/null +++ b/plugins/devkit/src/inspections/internal/UseDPIAwareEmptyBorderInspection.java @@ -0,0 +1,101 @@ +/* + * Copyright 2000-2017 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.jetbrains.idea.devkit.inspections.internal; + +import com.intellij.codeInspection.InspectionManager; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.codeInspection.ProblemHighlightType; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.util.ui.JBEmptyBorder; +import com.intellij.util.ui.JBUI; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.devkit.inspections.DevKitInspectionBase; +import org.jetbrains.idea.devkit.inspections.quickfix.ConvertToJBBorderQuickFix; + +/** + * @author Konstantin Bulenkov + */ +public class UseDPIAwareEmptyBorderInspection extends DevKitInspectionBase { + @Override + public PsiElementVisitor buildInternalVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly) { + return new JavaElementVisitor() { + @Override + public void visitMethodCallExpression(PsiMethodCallExpression expression) { + final ProblemDescriptor descriptor = checkMethodCallCanBeSimplified(expression, holder.getManager(), isOnTheFly); + if (descriptor != null) { + holder.registerProblem(descriptor); + } + super.visitMethodCallExpression(expression); + } + + @Override + public void visitNewExpression(PsiNewExpression expression) { + final ProblemDescriptor descriptor = checkNewExpression(expression, holder.getManager(), isOnTheFly); + if (descriptor != null) { + holder.registerProblem(descriptor); + } + super.visitNewExpression(expression); + } + }; + } + + private static ProblemDescriptor checkMethodCallCanBeSimplified(PsiMethodCallExpression expression, + InspectionManager manager, + boolean isOnTheFly) { + String text = expression.getText(); + if (text.contains("empty(")) { + PsiType type = expression.getType(); + PsiType[] arguments = expression.getTypeArguments(); + if (type != null + && JBEmptyBorder.class.getName().equals(type.getCanonicalText()) + && ConvertToJBBorderQuickFix.canSimplify(expression)) { + + return manager.createProblemDescriptor(expression, "Simplify", new ConvertToJBBorderQuickFix("Simplify"), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, isOnTheFly); + + } + } + return null; + } + + @Nullable + private static ProblemDescriptor checkNewExpression(PsiNewExpression expression, InspectionManager manager, boolean isOnTheFly) { + final Project project = manager.getProject(); + final PsiType type = expression.getType(); + final PsiExpressionList arguments = expression.getArgumentList(); + if (type != null && arguments != null && type.equalsToText("javax.swing.border.EmptyBorder")) { + final JavaPsiFacade facade = JavaPsiFacade.getInstance(project); + final PsiClass jbuiClass = facade.findClass(JBUI.class.getName(), GlobalSearchScope.allScope(project)); + if (jbuiClass != null && facade.getResolveHelper().isAccessible(jbuiClass, expression, jbuiClass)) { + final PsiElement parent = expression.getParent(); + if (parent instanceof PsiExpressionList && parent.getParent() instanceof PsiNewExpression) { + final PsiType parentType = ((PsiNewExpression)parent.getParent()).getType(); + if (parentType == null || JBEmptyBorder.class.getName().equals(parentType.getCanonicalText())) return null; + } + if (arguments.getExpressions().length == 4) { + return manager.createProblemDescriptor(expression, "Replace with JBUI.Borders.empty(...)", new ConvertToJBBorderQuickFix(), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, isOnTheFly); + } + } + } + return null; + } + +} diff --git a/plugins/devkit/src/inspections/quickfix/ConvertToJBBorderQuickFix.java b/plugins/devkit/src/inspections/quickfix/ConvertToJBBorderQuickFix.java new file mode 100644 index 000000000000..bec504b60d10 --- /dev/null +++ b/plugins/devkit/src/inspections/quickfix/ConvertToJBBorderQuickFix.java @@ -0,0 +1,136 @@ +/* + * Copyright 2000-2017 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.jetbrains.idea.devkit.inspections.quickfix; + +import com.intellij.codeInspection.LocalQuickFixBase; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import com.intellij.psi.util.PsiUtilBase; +import com.intellij.util.ui.JBUI; +import org.jetbrains.annotations.NotNull; + +/** + * @author Konstantin Bulenkov + */ +public class ConvertToJBBorderQuickFix extends LocalQuickFixBase { + public ConvertToJBBorderQuickFix() { + super("Convert to JBUI.Borders.empty(...)"); + } + + public ConvertToJBBorderQuickFix(String text) { + super(text); + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + final PsiCall newExpression = (PsiCall)descriptor.getPsiElement(); + PsiExpressionList list = newExpression.getArgumentList(); + String text; + if (list != null && list.getExpressions().length == 4) { + String top = list.getExpressions()[0].getText(); + String left = list.getExpressions()[1].getText(); + String bottom = list.getExpressions()[2].getText(); + String right = list.getExpressions()[3].getText(); + + if (isZero(top, left, bottom, right)) { + text = "empty()"; + } + else if (isZero(left, bottom, right)) { + text = "emptyTop(" + top + ")"; + } + else if (isZero(top, bottom, right)) { + text = "emptyLeft(" + left + ")"; + } + else if (isZero(top, left, right)) { + text = "emptyBottom(" + bottom + ")"; + } + else if (isZero(top, left, bottom)) { + text = "emptyRight(" + right + ")"; + } + else if (top.equals(left) && left.equals(bottom) && bottom.equals(right) && right.equals(top)) { + text = "empty(" + top + ")"; + } + else if (top.equals(bottom) && right.equals(left)) { + text = String.format("empty(%s, %s)", top, left); + } + else { + text = String.format("empty(%s, %s, %s, %s)", top, left, bottom, right); + } + + text = JBUI.class.getName() + ".Borders." + text; + + final PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory(); + final PsiExpression expression = factory.createExpressionFromText(text, newExpression.getContext()); + final PsiElement newElement = newExpression.replace(expression); + final PsiElement el = JavaCodeStyleManager.getInstance(project).shortenClassReferences(newElement); + final int offset = el.getTextOffset() + el.getText().length() - 2; + final Editor editor = PsiUtilBase.findEditor(el); + if (editor != null) { + editor.getCaretModel().moveToOffset(offset); + } + } + } + + public static boolean canSimplify(PsiMethodCallExpression expression) { + PsiType[] types = expression.getArgumentList().getExpressionTypes(); + if (!(types.length == 1 || types.length == 2 || types.length == 4)) { + return false; + } + for (PsiType type : types) { + if (!PsiType.INT.equals(type)) { + return false; + } + } + + PsiExpression[] params = expression.getArgumentList().getExpressions(); + if (params.length == 1) { + return params[0].textMatches("0"); + } else if (params.length == 2) { + return areSame(params); + } else if (params.length == 4) { + if (areSame(params) || (areSame(params[0], params[2]) && areSame(params[1], params[3]))) { + return true; + } + int zeros = 0; + for (PsiExpression param : params) zeros += isZero(param.getText()) ? 1 : 0; + return zeros == 3; + } + return false; + } + + private static boolean areSame(PsiExpression... params) { + if (params.length < 2) return false; + + String gold = params[0].getText(); + for (int i = 1; i < params.length; i++) { + if (!params[i].textMatches(gold)) { + return false; + } + } + return true; + } + + private static boolean isZero(String... args) { + if (args.length == 0) return false; + for (String arg : args) { + if (!"0".equals(arg)) return false; + } + return true; + } +} \ No newline at end of file diff --git a/plugins/devkit/src/navigation/ExtensionDeclarationRelatedItemLineMarkerProvider.java b/plugins/devkit/src/navigation/ExtensionDeclarationRelatedItemLineMarkerProvider.java new file mode 100644 index 000000000000..6f1846cb39f3 --- /dev/null +++ b/plugins/devkit/src/navigation/ExtensionDeclarationRelatedItemLineMarkerProvider.java @@ -0,0 +1,54 @@ +/* + * Copyright 2000-2017 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.jetbrains.idea.devkit.navigation; + +import com.intellij.codeInsight.daemon.RelatedItemLineMarkerInfo; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiIdentifier; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.devkit.util.ExtensionCandidate; +import org.jetbrains.idea.devkit.util.ExtensionLocator; + +import java.util.Collection; +import java.util.List; + +public class ExtensionDeclarationRelatedItemLineMarkerProvider extends DevkitRelatedLineMarkerProviderBase { + + @Override + protected void collectNavigationMarkers(@NotNull PsiElement element, Collection result) { + if (element instanceof PsiClass) { + process((PsiClass)element, result); + } + } + + private static void process(PsiClass psiClass, Collection result) { + PsiIdentifier identifier = psiClass.getNameIdentifier(); + if (identifier == null) { + return; + } + + ExtensionLocator locator = new ExtensionLocator(psiClass); + List targets = locator.findCandidates(); + if (targets.isEmpty()) { + return; + } + + RelatedItemLineMarkerInfo info = + LineMarkerInfoHelper.createExtensionLineMarkerInfo(targets, identifier); + result.add(info); + } +} diff --git a/plugins/devkit/src/navigation/ExtensionPointDeclarationRelatedItemLineMarkerProvider.java b/plugins/devkit/src/navigation/ExtensionPointDeclarationRelatedItemLineMarkerProvider.java index 811415b93fe6..d32f8de63c77 100644 --- a/plugins/devkit/src/navigation/ExtensionPointDeclarationRelatedItemLineMarkerProvider.java +++ b/plugins/devkit/src/navigation/ExtensionPointDeclarationRelatedItemLineMarkerProvider.java @@ -16,16 +16,10 @@ package org.jetbrains.idea.devkit.navigation; import com.intellij.codeInsight.daemon.RelatedItemLineMarkerInfo; -import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder; -import com.intellij.icons.AllIcons; -import com.intellij.navigation.GotoRelatedItem; -import com.intellij.openapi.editor.markup.GutterIconRenderer; import com.intellij.openapi.extensions.ExtensionPointName; -import com.intellij.openapi.util.Condition; import com.intellij.psi.*; import com.intellij.psi.util.PsiTypesUtil; import com.intellij.psi.util.PsiUtil; -import com.intellij.util.NotNullFunction; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -33,17 +27,10 @@ import org.jetbrains.idea.devkit.util.ExtensionPointCandidate; import org.jetbrains.idea.devkit.util.ExtensionPointLocator; import java.util.Collection; -import java.util.Collections; import java.util.List; public class ExtensionPointDeclarationRelatedItemLineMarkerProvider extends DevkitRelatedLineMarkerProviderBase { - private static final NotNullFunction> CONVERTER = - candidate -> Collections.singleton(candidate.pointer.getElement()); - - private static final NotNullFunction> RELATED_ITEM_PROVIDER = - candidate -> GotoRelatedItem.createItems(Collections.singleton(candidate.pointer.getElement()), "DevKit"); - @Override protected void collectNavigationMarkers(@NotNull PsiElement element, Collection result) { if (element instanceof PsiField) { @@ -64,14 +51,12 @@ public class ExtensionPointDeclarationRelatedItemLineMarkerProvider extends Devk ExtensionPointLocator locator = new ExtensionPointLocator(epClass); List targets = ContainerUtil.filter(locator.findDirectCandidates(), candidate -> epName.equals(candidate.epName)); + if (targets.isEmpty()) { + return; + } - final RelatedItemLineMarkerInfo info = NavigationGutterIconBuilder - .create(AllIcons.Nodes.Plugin, CONVERTER, RELATED_ITEM_PROVIDER) - .setTargets(targets) - .setPopupTitle("Choose Extension Point") - .setTooltipText("Extension Point Declaration") - .setAlignment(GutterIconRenderer.Alignment.RIGHT) - .createLineMarkerInfo(psiField.getNameIdentifier()); + RelatedItemLineMarkerInfo info = + LineMarkerInfoHelper.createExtensionPointLineMarkerInfo(targets, psiField.getNameIdentifier()); result.add(info); } diff --git a/plugins/devkit/src/navigation/LineMarkerInfoHelper.java b/plugins/devkit/src/navigation/LineMarkerInfoHelper.java new file mode 100644 index 000000000000..0f52455ba950 --- /dev/null +++ b/plugins/devkit/src/navigation/LineMarkerInfoHelper.java @@ -0,0 +1,99 @@ +/* + * Copyright 2000-2017 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.jetbrains.idea.devkit.navigation; + +import com.intellij.codeInsight.daemon.RelatedItemLineMarkerInfo; +import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder; +import com.intellij.icons.AllIcons; +import com.intellij.navigation.GotoRelatedItem; +import com.intellij.openapi.editor.markup.GutterIconRenderer; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.xml.XmlTag; +import com.intellij.util.NotNullFunction; +import com.intellij.util.NullableFunction; +import org.jetbrains.idea.devkit.DevKitBundle; +import org.jetbrains.idea.devkit.util.PointableCandidate; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +class LineMarkerInfoHelper { + private static final NotNullFunction> CONVERTER = + candidate -> Collections.singleton(candidate.pointer.getElement()); + private static final NotNullFunction> RELATED_ITEM_PROVIDER = + candidate -> GotoRelatedItem.createItems(Collections.singleton(candidate.pointer.getElement()), "DevKit"); + + + private static final NullableFunction EXTENSION_NAMER = + createNamer("line.marker.tooltip.extension.declaration", XmlTag::getName); + + private static final NullableFunction EXTENSION_POINT_NAMER = + createNamer("line.marker.tooltip.extension.point.declaration", tag -> { + String name = tag.getAttributeValue("name"); + if (StringUtil.isEmpty(name)) { + // shouldn't happen, just for additional safety + name = "Extension Point"; + } + return name; + }); + + + private LineMarkerInfoHelper() { + } + + + public static RelatedItemLineMarkerInfo createExtensionLineMarkerInfo(List targets, + PsiElement element) { + return createPluginLineMarkerInfo(targets, element, "Choose Extension", EXTENSION_NAMER); + } + + public static RelatedItemLineMarkerInfo createExtensionPointLineMarkerInfo(List targets, + PsiElement element) { + return createPluginLineMarkerInfo(targets, element, "Choose Extension Point", EXTENSION_POINT_NAMER); + } + + private static RelatedItemLineMarkerInfo createPluginLineMarkerInfo(List targets, + PsiElement element, String popup, + NullableFunction namer) { + return NavigationGutterIconBuilder + .create(AllIcons.Nodes.Plugin, CONVERTER, RELATED_ITEM_PROVIDER) + .setTargets(targets) + .setPopupTitle(popup) + .setNamer(namer) + .setAlignment(GutterIconRenderer.Alignment.RIGHT) + .createLineMarkerInfo(element); + } + + private static NullableFunction createNamer(String tooltipPatternPropertyName, + NotNullFunction nameProvider) { + return target -> { + XmlTag tag = target.pointer.getElement(); + if (tag == null) { + // shouldn't happen + throw new NullPointerException("Null element for pointable candidate: " + target); + } + + PsiFile file = tag.getContainingFile(); + String path = file.getVirtualFile().getPath(); + return DevKitBundle.message(tooltipPatternPropertyName, + path, String.valueOf(tag.getTextRange().getStartOffset()), nameProvider.fun(tag), + path, file.getName()); + }; + } +} diff --git a/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java b/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java index 18f0e52ba3f2..d1773dac4504 100644 --- a/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java +++ b/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java @@ -181,51 +181,46 @@ public class TestDataGuessByExistingFilesUtil { final Collection fileNames = getAllFileNames(possibleFileName, gotoModel); for (String name : fileNames) { ProgressManager.checkCanceled(); - boolean currentNameProcessed = false; - final Object[] elements = gotoModel.getElementsByName(name, false, name); - for (Object element : elements) { - if (!(element instanceof PsiFile)) { - continue; - } - final VirtualFile file = ((PsiFile)element).getVirtualFile(); - if (file == null || fileIndex.isInSource(file) && !fileIndex.isUnderSourceRootOfType(file, JavaModuleSourceRootTypes.RESOURCES)) { - continue; - } - - final String filePath = file.getPath(); - if (!StringUtil.containsIgnoreCase(filePath, possibleFilePath) && !StringUtil.containsIgnoreCase(filePath, test)) { - continue; - } - final String fileName = PathUtil.getFileName(filePath).toLowerCase(); - int i = fileName.indexOf(possibleFileName.toLowerCase()); - // Skip files that doesn't contain target test name and files that contain digit after target test name fragment. - // Example: there are tests with names 'testEnter()' and 'testEnter2()' and we don't want test data file 'testEnter2' - // to be matched to the test 'testEnter()'. - if (i < 0 || (i + possibleFileName.length() < fileName.length()) - && Character.isDigit(fileName.charAt(i + possibleFileName.length()))) - { - continue; - } - - TestLocationDescriptor current = new TestLocationDescriptor(); - current.populate(possibleFileName, file); - if (!current.isComplete()) { - continue; - } - - currentNameProcessed = true; - if (descriptors.isEmpty() || (descriptors.iterator().next().dir.equals(current.dir) && !descriptors.contains(current))) { - descriptors.add(current); - continue; - } - if (moreRelevantPath(current, descriptors, psiClass)) { - descriptors.clear(); - descriptors.add(current); - } + final Object[] elements = gotoModel.getElementsByName(name, false, name); + for (Object element : elements) { + if (!(element instanceof PsiFile)) { + continue; } - if (currentNameProcessed) { - break; + final VirtualFile file = ((PsiFile)element).getVirtualFile(); + if (file == null || fileIndex.isInSource(file) && !fileIndex.isUnderSourceRootOfType(file, JavaModuleSourceRootTypes.RESOURCES)) { + continue; } + + final String filePath = file.getPath(); + if (!StringUtil.containsIgnoreCase(filePath, possibleFilePath) && !StringUtil.containsIgnoreCase(filePath, test)) { + continue; + } + final String fileName = PathUtil.getFileName(filePath).toLowerCase(); + int i = fileName.indexOf(possibleFileName.toLowerCase()); + // Skip files that doesn't contain target test name and files that contain digit after target test name fragment. + // Example: there are tests with names 'testEnter()' and 'testEnter2()' and we don't want test data file 'testEnter2' + // to be matched to the test 'testEnter()'. + if (i < 0 || (i + possibleFileName.length() < fileName.length()) + && Character.isDigit(fileName.charAt(i + possibleFileName.length()))) { + continue; + } + + TestLocationDescriptor current = new TestLocationDescriptor(); + current.populate(possibleFileName, file); + if (!current.isComplete()) { + continue; + } + + if (descriptors.isEmpty() || (descriptors.iterator().next().dir.equals(current.dir) && !descriptors.contains(current))) { + descriptors.add(current); + continue; + } + if (moreRelevantPath(current, descriptors, psiClass)) { + descriptors.clear(); + descriptors.add(current); + } + break; + } } return new TestDataDescriptor(descriptors, possibleFileName); } diff --git a/plugins/devkit/src/testAssistant/TestDataLineMarkerProvider.java b/plugins/devkit/src/testAssistant/TestDataLineMarkerProvider.java index ec1140261f1a..9f6744276e73 100644 --- a/plugins/devkit/src/testAssistant/TestDataLineMarkerProvider.java +++ b/plugins/devkit/src/testAssistant/TestDataLineMarkerProvider.java @@ -26,6 +26,7 @@ import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.util.PsiUtilCore; +import com.intellij.testFramework.TestDataPath; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.devkit.util.PsiUtil; @@ -39,7 +40,7 @@ import java.util.Collections; * @author yole */ public class TestDataLineMarkerProvider extends RunLineMarkerContributor { - public static final String TEST_DATA_PATH_ANNOTATION_QUALIFIED_NAME = "com.intellij.testFramework.TestDataPath"; + public static final String TEST_DATA_PATH_ANNOTATION_QUALIFIED_NAME = TestDataPath.class.getCanonicalName(); public static final String CONTENT_ROOT_VARIABLE = "$CONTENT_ROOT"; public static final String PROJECT_ROOT_VARIABLE = "$PROJECT_ROOT"; diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/beans/PatchedUsage.java b/plugins/devkit/src/util/ExtensionCandidate.java similarity index 52% rename from platform/platform-impl/src/com/intellij/internal/statistic/beans/PatchedUsage.java rename to plugins/devkit/src/util/ExtensionCandidate.java index f300bd74362f..ab1b2bd98f19 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/beans/PatchedUsage.java +++ b/plugins/devkit/src/util/ExtensionCandidate.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -13,27 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.internal.statistic.beans; +package org.jetbrains.idea.devkit.util; -public class PatchedUsage extends UsageDescriptor { +import com.intellij.psi.SmartPsiElementPointer; +import com.intellij.psi.xml.XmlTag; - public PatchedUsage(UsageDescriptor descriptor) { - super(descriptor.getKey(), descriptor.getValue()); - } - - public PatchedUsage(String key, int value) { - super(key, value); - } - - public int getDelta() { - return getValue(); - } - - public void addValue(int add) { - setValue(getValue() + add); - } - - public void subValue(int sub) { - setValue(getValue() - sub); +public class ExtensionCandidate extends PointableCandidate { + public ExtensionCandidate(SmartPsiElementPointer pointer) { + super(pointer); } } diff --git a/plugins/devkit/src/util/ExtensionLocator.java b/plugins/devkit/src/util/ExtensionLocator.java new file mode 100644 index 000000000000..e56046198932 --- /dev/null +++ b/plugins/devkit/src/util/ExtensionLocator.java @@ -0,0 +1,110 @@ +/* + * Copyright 2000-2017 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.jetbrains.idea.devkit.util; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.SmartPointerManager; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.PsiNonJavaFileReferenceProcessor; +import com.intellij.psi.search.PsiSearchHelper; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.xml.XmlTag; +import com.intellij.psi.xml.XmlToken; +import com.intellij.util.CommonProcessors; +import com.intellij.util.Processor; +import com.intellij.util.SmartList; +import com.intellij.util.xml.DomElement; +import com.intellij.util.xml.DomUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.devkit.dom.Extension; + +import java.util.Collections; +import java.util.List; + +public class ExtensionLocator { + private final PsiClass myPsiClass; + + public ExtensionLocator(PsiClass aClass) { + myPsiClass = aClass; + } + + @NotNull + public List findCandidates() { + String name = myPsiClass.getQualifiedName(); + if (name == null) { + return Collections.emptyList(); + } + + List result = new SmartList<>(); + processExtensionDeclarations(myPsiClass, new ReferenceProcessor(name, tag -> { + result.add(new ExtensionCandidate(SmartPointerManager.getInstance(tag.getProject()).createSmartPsiElementPointer(tag))); + return true; // continue processing + })); + return result; + } + + @SuppressWarnings("unchecked") // it's fine with Processor.FALSE + public static boolean isRegisteredExtension(@NotNull PsiClass psiClass) { + String name = psiClass.getQualifiedName(); + if (name == null) { + return false; + } + + CommonProcessors.FindFirstProcessor processor = new CommonProcessors.FindFirstProcessor<>(); + processExtensionDeclarations(psiClass, new ReferenceProcessor(name, processor)); + return processor.isFound(); + } + + private static void processExtensionDeclarations(PsiClass psiClass, PsiNonJavaFileReferenceProcessor referenceProcessor) { + String name = psiClass.getQualifiedName(); + if (name == null) return; + + Project project = psiClass.getProject(); + GlobalSearchScope scope = PluginRelatedLocatorsUtils.getCandidatesScope(project); + + PsiSearchHelper.SERVICE.getInstance(project).processUsagesInNonJavaFiles(name, referenceProcessor, scope); + } + + private static class ReferenceProcessor implements PsiNonJavaFileReferenceProcessor { + private final String myExtensionClassName; + private final Processor myExtensionTagHandler; + + private ReferenceProcessor(String name, Processor extensionTagHandler) { + myExtensionClassName = name; + myExtensionTagHandler = extensionTagHandler; + } + + @Override + public boolean process(PsiFile file, int startOffset, int endOffset) { + PsiElement element = file.findElementAt(startOffset); + String tokenText = element instanceof XmlToken ? element.getText() : null; + if (!StringUtil.equals(myExtensionClassName, tokenText)) return true; + + XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class); + if (tag == null) return true; + + DomElement dom = DomUtil.getDomElement(tag); + if (dom instanceof Extension && ((Extension)dom).getExtensionPoint() != null) { + return myExtensionTagHandler.process(tag); + } + return true; + } + } +} diff --git a/plugins/devkit/src/util/ExtensionPointCandidate.java b/plugins/devkit/src/util/ExtensionPointCandidate.java index 3ae694a0e7d0..87fee08af0ad 100644 --- a/plugins/devkit/src/util/ExtensionPointCandidate.java +++ b/plugins/devkit/src/util/ExtensionPointCandidate.java @@ -16,32 +16,32 @@ package org.jetbrains.idea.devkit.util; import com.intellij.psi.SmartPsiElementPointer; +import com.intellij.psi.xml.XmlTag; /** * @author yole */ -public class ExtensionPointCandidate { - public final SmartPsiElementPointer pointer; +public class ExtensionPointCandidate extends PointableCandidate { public final String epName; public final String attributeName; public final String tagName; public final String beanClassName; - public ExtensionPointCandidate(SmartPsiElementPointer pointer, + public ExtensionPointCandidate(SmartPsiElementPointer pointer, String epName, String attributeName, String tagName, String beanClassName) { - this.pointer = pointer; + super(pointer); this.epName = epName; this.attributeName = attributeName; this.tagName = tagName; this.beanClassName = beanClassName; } - public ExtensionPointCandidate(SmartPsiElementPointer pointer, + public ExtensionPointCandidate(SmartPsiElementPointer pointer, String epName) { - this.pointer = pointer; + super(pointer); this.epName = epName; this.attributeName = "implementation"; this.tagName = null; diff --git a/plugins/devkit/src/util/ExtensionPointLocator.java b/plugins/devkit/src/util/ExtensionPointLocator.java index c7832ea49afa..0ce5c5f8f1ea 100644 --- a/plugins/devkit/src/util/ExtensionPointLocator.java +++ b/plugins/devkit/src/util/ExtensionPointLocator.java @@ -16,25 +16,20 @@ package org.jetbrains.idea.devkit.util; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.*; +import com.intellij.psi.CommonClassNames; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.psi.SmartPointerManager; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.PsiSearchHelper; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.xml.XmlTag; -import com.intellij.psi.xml.XmlToken; import com.intellij.util.SmartList; import com.intellij.util.xml.DomElement; -import com.intellij.util.xml.DomService; import com.intellij.util.xml.DomUtil; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.idea.devkit.dom.Extension; import org.jetbrains.idea.devkit.dom.ExtensionPoint; -import org.jetbrains.idea.devkit.dom.IdeaPlugin; -import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -76,7 +71,7 @@ public class ExtensionPointLocator { if (name == null) return; Project project = psiClass.getProject(); - GlobalSearchScope scope = getCandidatesScope(project); + GlobalSearchScope scope = PluginRelatedLocatorsUtils.getCandidatesScope(project); PsiSearchHelper.SERVICE.getInstance(project).processUsagesInNonJavaFiles(name, (file, startOffset, endOffset) -> { PsiElement element = file.findElementAt(startOffset); processExtensionPointCandidate(element, list); @@ -84,36 +79,13 @@ public class ExtensionPointLocator { }, scope); } - @NotNull - private static GlobalSearchScope getCandidatesScope(@NotNull Project project) { - Collection candidates = DomService.getInstance().getDomFileCandidates(IdeaPlugin.class, project, GlobalSearchScope.allScope(project)); - return GlobalSearchScope.filesScope(project, candidates); - } - - public static boolean isRegisteredExtension(@NotNull PsiClass psiClass) { - String name = psiClass.getQualifiedName(); - if (name == null) return false; - - Project project = psiClass.getProject(); - GlobalSearchScope scope = getCandidatesScope(project); - return !PsiSearchHelper.SERVICE.getInstance(project).processUsagesInNonJavaFiles(name, (file, startOffset, endOffset) -> { - PsiElement at = file.findElementAt(startOffset); - String tokenText = at instanceof XmlToken ? at.getText() : null; - if (!StringUtil.equals(name, tokenText)) return true; - XmlTag tag = PsiTreeUtil.getParentOfType(at, XmlTag.class); - if (tag == null) return true; - DomElement dom = DomUtil.getDomElement(tag); - return !(dom instanceof Extension && ((Extension)dom).getExtensionPoint() != null); - }, scope); - } - private static void processExtensionPointCandidate(PsiElement element, List list) { XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class); if (tag == null) return; if ("extensionPoint".equals(tag.getName())) { String epName = getEPName(tag); if (epName != null) { - list.add(new ExtensionPointCandidate(createPointer(tag), epName)); + list.add(new ExtensionPointCandidate(SmartPointerManager.getInstance(tag.getProject()).createSmartPsiElementPointer(tag), epName)); } } else if ("with".equals(tag.getName())) { @@ -125,14 +97,11 @@ public class ExtensionPointLocator { String epName = getEPName(extensionPointTag); String beanClassName = extensionPointTag.getAttributeValue("beanClass"); if ((attrName == null && tagName == null) || epName == null) return; - list.add(new ExtensionPointCandidate(createPointer(extensionPointTag), epName, attrName, tagName, beanClassName)); + list.add(new ExtensionPointCandidate(SmartPointerManager.getInstance(extensionPointTag.getProject()) + .createSmartPsiElementPointer(extensionPointTag), epName, attrName, tagName, beanClassName)); } } - private static SmartPsiElementPointer createPointer(XmlTag extensionPointTag) { - return SmartPointerManager.getInstance(extensionPointTag.getProject()).createSmartPsiElementPointer(extensionPointTag); - } - @Nullable private static String getEPName(XmlTag tag) { final DomElement domElement = DomUtil.getDomElement(tag); diff --git a/plugins/devkit/src/util/PluginRelatedLocatorsUtils.java b/plugins/devkit/src/util/PluginRelatedLocatorsUtils.java new file mode 100644 index 000000000000..9c1366d6cde0 --- /dev/null +++ b/plugins/devkit/src/util/PluginRelatedLocatorsUtils.java @@ -0,0 +1,37 @@ +/* + * Copyright 2000-2017 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.jetbrains.idea.devkit.util; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.util.xml.DomService; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.devkit.dom.IdeaPlugin; + +import java.util.Collection; + +class PluginRelatedLocatorsUtils { + private PluginRelatedLocatorsUtils() { + } + + @NotNull + static GlobalSearchScope getCandidatesScope(@NotNull Project project) { + Collection candidates = DomService.getInstance() + .getDomFileCandidates(IdeaPlugin.class, project, GlobalSearchScope.allScope(project)); + return GlobalSearchScope.filesScope(project, candidates); + } +} diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/ideSettings/IdeSettingsDescriptors.java b/plugins/devkit/src/util/PointableCandidate.java similarity index 55% rename from platform/platform-impl/src/com/intellij/internal/statistic/ideSettings/IdeSettingsDescriptors.java rename to plugins/devkit/src/util/PointableCandidate.java index 893b04e55aa5..626e67c97a6d 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/ideSettings/IdeSettingsDescriptors.java +++ b/plugins/devkit/src/util/PointableCandidate.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -13,18 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.internal.statistic.ideSettings; +package org.jetbrains.idea.devkit.util; -import com.intellij.util.xmlb.annotations.AbstractCollection; -import com.intellij.util.xmlb.annotations.Property; +import com.intellij.psi.SmartPsiElementPointer; +import com.intellij.psi.xml.XmlTag; -public class IdeSettingsDescriptors { +public abstract class PointableCandidate { + public final SmartPsiElementPointer pointer; - @Property(surroundWithTag = false) - @AbstractCollection(surroundWithTag = false) - public IdeSettingsDescriptor[] myDescriptors; - - public IdeSettingsDescriptor[] getDescriptors() { - return myDescriptors; + protected PointableCandidate(SmartPsiElementPointer pointer) { + this.pointer = pointer; } } diff --git a/plugins/devkit/testData/guessByExistingFiles/SomeTest_AfterAndSame.java b/plugins/devkit/testData/guessByExistingFiles/SomeTest_AfterAndSame.java new file mode 100644 index 000000000000..8f75a764fe8b --- /dev/null +++ b/plugins/devkit/testData/guessByExistingFiles/SomeTest_AfterAndSame.java @@ -0,0 +1,6 @@ +import junit.framework.TestCase; + +public class SomeTest_BeforeAndSame extends TestCase { + public void testSomethingAS() { + } +} \ No newline at end of file diff --git a/plugins/devkit/testData/guessByExistingFiles/SomeTest_BeforeAndAfter.java b/plugins/devkit/testData/guessByExistingFiles/SomeTest_BeforeAndAfter.java new file mode 100644 index 000000000000..d8334e08733a --- /dev/null +++ b/plugins/devkit/testData/guessByExistingFiles/SomeTest_BeforeAndAfter.java @@ -0,0 +1,6 @@ +import junit.framework.TestCase; + +public class SomeTest_BeforeAndAfter extends TestCase { + public void testSomethingBA() { + } +} \ No newline at end of file diff --git a/plugins/devkit/testData/guessByExistingFiles/SomeTest_BeforeAndSame.java b/plugins/devkit/testData/guessByExistingFiles/SomeTest_BeforeAndSame.java new file mode 100644 index 000000000000..a138157e3605 --- /dev/null +++ b/plugins/devkit/testData/guessByExistingFiles/SomeTest_BeforeAndSame.java @@ -0,0 +1,6 @@ +import junit.framework.TestCase; + +public class SomeTest_BeforeAndSame extends TestCase { + public void testSomethingBS() { + } +} \ No newline at end of file diff --git a/plugins/devkit/testData/guessByExistingFiles/SomeTest_OnlySame.java b/plugins/devkit/testData/guessByExistingFiles/SomeTest_OnlySame.java new file mode 100644 index 000000000000..705a1f630296 --- /dev/null +++ b/plugins/devkit/testData/guessByExistingFiles/SomeTest_OnlySame.java @@ -0,0 +1,6 @@ +import junit.framework.TestCase; + +public class SomeTest_OnlySame extends TestCase { + public void testSomethingS() { + } +} \ No newline at end of file diff --git a/plugins/devkit/testData/guessByExistingFiles/somethingAS.java b/plugins/devkit/testData/guessByExistingFiles/somethingAS.java new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/plugins/devkit/testData/guessByExistingFiles/somethingAS_after.java b/plugins/devkit/testData/guessByExistingFiles/somethingAS_after.java new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/plugins/devkit/testData/guessByExistingFiles/somethingBA_after.java b/plugins/devkit/testData/guessByExistingFiles/somethingBA_after.java new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/plugins/devkit/testData/guessByExistingFiles/somethingBA_before.java b/plugins/devkit/testData/guessByExistingFiles/somethingBA_before.java new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/plugins/devkit/testData/guessByExistingFiles/somethingBS.java b/plugins/devkit/testData/guessByExistingFiles/somethingBS.java new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/plugins/devkit/testData/guessByExistingFiles/somethingBS_before.java b/plugins/devkit/testData/guessByExistingFiles/somethingBS_before.java new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/plugins/devkit/testData/guessByExistingFiles/somethingS.java b/plugins/devkit/testData/guessByExistingFiles/somethingS.java new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/plugins/devkit/testData/navigation/extensionDeclaration/MyExtension.java b/plugins/devkit/testData/navigation/extensionDeclaration/MyExtension.java new file mode 100644 index 000000000000..fa7422aa4f78 --- /dev/null +++ b/plugins/devkit/testData/navigation/extensionDeclaration/MyExtension.java @@ -0,0 +1,2 @@ +public class MyExtension { +} \ No newline at end of file diff --git a/plugins/devkit/testData/navigation/extensionDeclaration/MyInvalidExtension.java b/plugins/devkit/testData/navigation/extensionDeclaration/MyInvalidExtension.java new file mode 100644 index 000000000000..da97d5cfdd8c --- /dev/null +++ b/plugins/devkit/testData/navigation/extensionDeclaration/MyInvalidExtension.java @@ -0,0 +1,2 @@ +public class MyInvalidExtension { +} \ No newline at end of file diff --git a/plugins/devkit/testData/navigation/extensionDeclaration/plugin.xml b/plugins/devkit/testData/navigation/extensionDeclaration/plugin.xml new file mode 100644 index 000000000000..62ead8f5c93b --- /dev/null +++ b/plugins/devkit/testData/navigation/extensionDeclaration/plugin.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/devkit/testData/navigation/extensionPointDeclaration/plugin.xml b/plugins/devkit/testData/navigation/extensionPointDeclaration/plugin.xml index 6de1e0e4756a..22440c53674d 100644 --- a/plugins/devkit/testData/navigation/extensionPointDeclaration/plugin.xml +++ b/plugins/devkit/testData/navigation/extensionPointDeclaration/plugin.xml @@ -1,10 +1,8 @@ - - \ No newline at end of file diff --git a/plugins/devkit/testSources/navigation/ExtensionDeclarationRelatedItemLineMarkerProviderTest.java b/plugins/devkit/testSources/navigation/ExtensionDeclarationRelatedItemLineMarkerProviderTest.java new file mode 100644 index 000000000000..733444008f1a --- /dev/null +++ b/plugins/devkit/testSources/navigation/ExtensionDeclarationRelatedItemLineMarkerProviderTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2000-2017 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.jetbrains.idea.devkit.navigation; + +import com.intellij.codeInsight.daemon.GutterMark; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.application.PluginPathManager; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.psi.PsiFile; +import com.intellij.testFramework.TestDataPath; +import com.intellij.testFramework.builders.JavaModuleFixtureBuilder; +import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase; +import com.intellij.ui.components.JBList; +import com.intellij.util.PathUtil; + +@TestDataPath("$CONTENT_ROOT/testData/navigation/extensionDeclaration") +public class ExtensionDeclarationRelatedItemLineMarkerProviderTest extends JavaCodeInsightFixtureTestCase { + + @Override + protected String getBasePath() { + return PluginPathManager.getPluginHomePathRelative("devkit") + "/testData/navigation/extensionDeclaration"; + } + + @Override + protected void tuneFixture(JavaModuleFixtureBuilder moduleBuilder) throws Exception { + String extensionsJar = PathUtil.getJarPathForClass(ExtensionPointName.class); + moduleBuilder.addLibrary("extensions", extensionsJar); + String platformApiJar = PathUtil.getJarPathForClass(JBList.class); + moduleBuilder.addLibrary("platform-api", platformApiJar); + } + + public void testMyActionInvalid() { + myFixture.configureByFile("plugin.xml"); + GutterMark gutter = myFixture.findGutter("MyInvalidExtension.java"); + assertNull(gutter); + } + + public void testMyProjectService() { + PsiFile file = myFixture.configureByFile("plugin.xml"); + String path = file.getVirtualFile().getPath(); + int expectedTagPosition = file.getText().indexOf(""); + String expectedTooltip = "    myEp extension declaration in plugin.xml
    "; + + GutterMark gutter = myFixture.findGutter("MyExtension.java"); + DevKitGutterTargetsChecker.checkGutterTargets(gutter, expectedTooltip, AllIcons.Nodes.Plugin, "myEp"); + } +} diff --git a/plugins/devkit/testSources/navigation/ExtensionPointDeclarationRelatedItemLineMarkerProviderTest.java b/plugins/devkit/testSources/navigation/ExtensionPointDeclarationRelatedItemLineMarkerProviderTest.java index 6b21d8455152..1c6d13f47bff 100644 --- a/plugins/devkit/testSources/navigation/ExtensionPointDeclarationRelatedItemLineMarkerProviderTest.java +++ b/plugins/devkit/testSources/navigation/ExtensionPointDeclarationRelatedItemLineMarkerProviderTest.java @@ -19,6 +19,7 @@ import com.intellij.codeInsight.daemon.GutterMark; import com.intellij.icons.AllIcons; import com.intellij.openapi.application.PluginPathManager; import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.psi.PsiFile; import com.intellij.testFramework.TestDataPath; import com.intellij.testFramework.builders.JavaModuleFixtureBuilder; import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase; @@ -54,11 +55,14 @@ public class ExtensionPointDeclarationRelatedItemLineMarkerProviderTest extends } private void assertSingleEPDeclaration(String filePath) { - myFixture.configureByFile("plugin.xml"); // index + PsiFile file = myFixture.configureByFile("plugin.xml"); + String path = file.getVirtualFile().getPath(); + int expectedTagPosition = file.getText().indexOf(""); + String expectedTooltip = "    myStringEP extension point declaration in plugin.xml
    "; final GutterMark gutter = myFixture.findGutter(filePath); - DevKitGutterTargetsChecker.checkGutterTargets(gutter, "Extension Point Declaration", - AllIcons.Nodes.Plugin, - "extensionPoint"); + DevKitGutterTargetsChecker.checkGutterTargets(gutter, expectedTooltip, AllIcons.Nodes.Plugin, "extensionPoint"); } } \ No newline at end of file diff --git a/plugins/devkit/testSources/testAssistant/TestDataGuessByExistingFilesUtilTest.java b/plugins/devkit/testSources/testAssistant/TestDataGuessByExistingFilesUtilTest.java new file mode 100644 index 000000000000..b86cda0edcdc --- /dev/null +++ b/plugins/devkit/testSources/testAssistant/TestDataGuessByExistingFilesUtilTest.java @@ -0,0 +1,176 @@ +/* + * Copyright 2000-2017 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.jetbrains.idea.devkit.testAssistant; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.PluginPathManager; +import com.intellij.openapi.projectRoots.ex.JavaSdkUtil; +import com.intellij.openapi.util.ThrowableComputable; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.testFramework.PsiTestUtil; +import com.intellij.testFramework.TestDataPath; +import com.intellij.testFramework.builders.JavaModuleFixtureBuilder; +import org.jetbrains.jps.model.java.JavaResourceRootType; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +@TestDataPath("$CONTENT_ROOT/testData/guessByExistingFiles") +public class TestDataGuessByExistingFilesUtilTest extends TestDataPathTestCase { + private static final String RESOURCES_ROOT_NAME = "resources"; + + @Override + public void setUp() throws Exception { + super.setUp(); + VirtualFile resourcesDir = ApplicationManager.getApplication().runWriteAction(new ThrowableComputable() { + @Override + public VirtualFile compute() throws IOException { + return myContentRoot.createChildDirectory(TestDataGuessByExistingFilesUtilTest.this, RESOURCES_ROOT_NAME); + } + }); + PsiTestUtil.addSourceRoot(myFixture.getModule(), resourcesDir, JavaResourceRootType.RESOURCE); + } + + @Override + protected String getBasePath() { + return PluginPathManager.getPluginHomePathRelative("devkit") + "/testData/guessByExistingFiles"; + } + + @Override + protected void tuneFixture(JavaModuleFixtureBuilder moduleBuilder) throws Exception { + moduleBuilder.addLibrary("junit3", JavaSdkUtil.getJunit3JarPath()); + } + + + public void testGetTestName() { + String result = TestDataGuessByExistingFilesUtil.getTestName("testTestName"); + assertEquals("TestName", result); + } + + public void testCollectTestDataByExistingFilesBeforeAndAfter() throws IOException { + PsiMethod testMethod = getTestMethodWithBeforeAndAfterTestData(); + List result = TestDataGuessByExistingFilesUtil.collectTestDataByExistingFiles(testMethod); + verifyResultForBeforeAndAfter(result); + } + + public void testSuggestTestDataFilesBeforeAndAfter() { + PsiMethod testMethod = getTestMethodWithBeforeAndAfterTestData(); + List result = TestDataGuessByExistingFilesUtil.suggestTestDataFiles( + TestDataGuessByExistingFilesUtil.getTestName(testMethod.getName()), null, testMethod.getContainingClass()); + verifyResultForBeforeAndAfter(result); + } + + public void testCollectTestDataByExistingFilesBeforeAndSame() throws IOException { + PsiMethod testMethod = getTestMethodWithBeforeAndSameTestData(); + List result = TestDataGuessByExistingFilesUtil.collectTestDataByExistingFiles(testMethod); + verifyResultForBeforeAndSame(result); + } + + public void testSuggestTestDataFilesBeforeAndSame() { + PsiMethod testMethod = getTestMethodWithBeforeAndSameTestData(); + List result = TestDataGuessByExistingFilesUtil.suggestTestDataFiles( + TestDataGuessByExistingFilesUtil.getTestName(testMethod.getName()), null, testMethod.getContainingClass()); + verifyResultForBeforeAndSame(result); + } + + public void testCollectTestDataByExistingFilesAfterAndSame() throws IOException { + PsiMethod testMethod = getTestMethodWithAfterAndSameTestData(); + List result = TestDataGuessByExistingFilesUtil.collectTestDataByExistingFiles(testMethod); + verifyResultForAfterAndSame(result); + } + + public void testSuggestTestDataFilesAfterAndSame() { + PsiMethod testMethod = getTestMethodWithAfterAndSameTestData(); + List result = TestDataGuessByExistingFilesUtil.suggestTestDataFiles( + TestDataGuessByExistingFilesUtil.getTestName(testMethod.getName()), null, testMethod.getContainingClass()); + verifyResultForAfterAndSame(result); + } + + public void testCollectTestDataByExistingFilesOnlySame() throws IOException { + PsiMethod testMethod = getTestMethodWithOnlySameTestData(); + List result = TestDataGuessByExistingFilesUtil.collectTestDataByExistingFiles(testMethod); + verifyResultForOnlySame(result); + } + + public void testSuggestTestDataFilesOnlySame() { + PsiMethod testMethod = getTestMethodWithOnlySameTestData(); + List result = TestDataGuessByExistingFilesUtil.suggestTestDataFiles( + TestDataGuessByExistingFilesUtil.getTestName(testMethod.getName()), null, testMethod.getContainingClass()); + verifyResultForOnlySame(result); + } + + + private PsiMethod getTestMethodWithBeforeAndAfterTestData() { + return getTestMethod("SomeTest_BeforeAndAfter.java", "somethingBA_before.java", "somethingBA_after.java"); + } + + private PsiMethod getTestMethodWithBeforeAndSameTestData() { + return getTestMethod("SomeTest_BeforeAndSame.java", "somethingBS_before.java", "somethingBS.java"); + } + + private PsiMethod getTestMethodWithAfterAndSameTestData() { + return getTestMethod("SomeTest_AfterAndSame.java", "somethingAS_after.java", "somethingAS.java"); + } + + private PsiMethod getTestMethodWithOnlySameTestData() { + return getTestMethod("SomeTest_OnlySame.java", "somethingS.java"); + } + + private PsiMethod getTestMethod(String classFileName, String... testDataFiles) { + myFixture.configureByFiles(classFileName); + for (String file : testDataFiles) { + myFixture.copyFileToProject(file, RESOURCES_ROOT_NAME + "/" + file); + } + + PsiMethod testMethod = PsiTreeUtil.getParentOfType(myFixture.getElementAtCaret(), PsiMethod.class, false); + assertNotNull(testMethod); + return testMethod; + } + + + private static void verifyResultForBeforeAndAfter(List names) { + assertNotNull(names); + assertEquals(names.toString(), 2, names.size()); + Collections.sort(names); + assertTrue(names.get(0), names.get(0).endsWith("somethingBA_after.java")); + assertTrue(names.get(1), names.get(1).endsWith("somethingBA_before.java")); + } + + private static void verifyResultForBeforeAndSame(List names) { + assertNotNull(names); + assertEquals(names.toString(), 2, names.size()); + Collections.sort(names); + assertTrue(names.get(0), names.get(0).endsWith("somethingBS.java")); + assertTrue(names.get(1), names.get(1).endsWith("somethingBS_before.java")); + } + + private static void verifyResultForAfterAndSame(List names) { + assertNotNull(names); + assertEquals(names.toString(), 2, names.size()); + Collections.sort(names); + assertTrue(names.get(0), names.get(0).endsWith("somethingAS.java")); + assertTrue(names.get(1), names.get(1).endsWith("somethingAS_after.java")); + } + + private static void verifyResultForOnlySame(List names) { + assertNotNull(names); + assertEquals(names.toString(), 1, names.size()); + assertTrue(names.get(0), names.get(0).endsWith("somethingS.java")); + } +} diff --git a/plugins/editorconfig/src/org/editorconfig/settings/EditorConfigConfigurable.java b/plugins/editorconfig/src/org/editorconfig/settings/EditorConfigConfigurable.java index 54b511b78150..56213b54d636 100644 --- a/plugins/editorconfig/src/org/editorconfig/settings/EditorConfigConfigurable.java +++ b/plugins/editorconfig/src/org/editorconfig/settings/EditorConfigConfigurable.java @@ -12,6 +12,7 @@ import com.intellij.psi.codeStyle.CustomCodeStyleSettings; import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.components.JBCheckBox; import com.intellij.util.messages.MessageBus; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import org.editorconfig.Utils; import org.jetbrains.annotations.NotNull; @@ -37,7 +38,7 @@ public class EditorConfigConfigurable extends CodeStyleSettingsProvider implemen panel.add(myEnabled); final JLabel warning = new JLabel("EditorConfig may override the IDE code style settings"); warning.setFont(UIUtil.getLabelFont(UIUtil.FontSize.SMALL)); - warning.setBorder(IdeBorderFactory.createEmptyBorder(0, 20, 0, 0)); + warning.setBorder(JBUI.Borders.emptyLeft(20)); panel.add(warning); panel.setAlignmentY(Component.TOP_ALIGNMENT); result.add(panel); diff --git a/plugins/git4idea/src/git4idea/GitApplyChangesProcess.kt b/plugins/git4idea/src/git4idea/GitApplyChangesProcess.kt index bbaa04a2c27b..112c83fa1b15 100644 --- a/plugins/git4idea/src/git4idea/GitApplyChangesProcess.kt +++ b/plugins/git4idea/src/git4idea/GitApplyChangesProcess.kt @@ -34,7 +34,6 @@ import com.intellij.openapi.vcs.history.VcsRevisionNumber import com.intellij.openapi.vcs.merge.MergeDialogCustomizer import com.intellij.openapi.vcs.update.RefreshVFsSynchronously import com.intellij.openapi.vfs.VirtualFile -import com.intellij.util.ArrayUtil import com.intellij.util.text.UniqueNameGenerator import com.intellij.vcs.log.Hash import com.intellij.vcs.log.VcsFullCommitDetails @@ -278,8 +277,7 @@ class GitApplyChangesProcess(private val project: Project, return null } - val adjustedMessage = commitMessage.replace('\n', ' ').replace("[ ]{2,}".toRegex(), " ") - val changeListName = createNameForChangeList(adjustedMessage) + val changeListName = createNameForChangeList(commitMessage) val createdChangeList = (changeListManager as ChangeListManagerEx).addChangeList(changeListName, commitMessage, if (preserveCommitMetadata) commit else null) val actualChangeList = moveChanges(originalChanges, createdChangeList) @@ -313,7 +311,7 @@ class GitApplyChangesProcess(private val project: Project, } try { changeListManager.addChangeListListener(listener) - changeListManager.moveChangesTo(targetChangeList, *ArrayUtil.toObjectArray(localChanges, Change::class.java)) + changeListManager.moveChangesTo(targetChangeList, *localChanges.toTypedArray()) val success = moveChangesWaiter.await(100, TimeUnit.SECONDS) if (!success) { LOG.error("Couldn't await for changes move.") @@ -329,7 +327,11 @@ class GitApplyChangesProcess(private val project: Project, } } - private fun createNameForChangeList(proposedName: String): String { + private fun createNameForChangeList(commitMessage: String): String { + val proposedName = commitMessage.trim() + .substringBefore('\n') + .trim() + .replace("[ ]{2,}".toRegex(), " ") return UniqueNameGenerator.generateUniqueName(proposedName, "", "", "-", "", { changeListManager.findChangeList(it) == null }) } diff --git a/plugins/git4idea/src/git4idea/GitStatisticsCollector.kt b/plugins/git4idea/src/git4idea/GitStatisticsCollector.kt index 29c30c6f6f8b..b6d84cb0b75b 100644 --- a/plugins/git4idea/src/git4idea/GitStatisticsCollector.kt +++ b/plugins/git4idea/src/git4idea/GitStatisticsCollector.kt @@ -15,12 +15,12 @@ */ package git4idea -import com.intellij.internal.statistic.AbstractApplicationUsagesCollector +import com.intellij.internal.statistic.AbstractProjectsUsagesCollector import com.intellij.internal.statistic.UsageTrigger import com.intellij.internal.statistic.beans.GroupDescriptor import com.intellij.internal.statistic.beans.UsageDescriptor -import com.intellij.internal.statistic.getBooleanUsage -import com.intellij.internal.statistic.getCountingUsage +import com.intellij.internal.statistic.utils.getBooleanUsage +import com.intellij.internal.statistic.utils.getCountingUsage import com.intellij.openapi.project.Project import git4idea.config.GitVcsSettings @@ -28,7 +28,7 @@ fun reportUsage(key: String) { UsageTrigger.trigger(key) } -class GitStatisticsCollector : AbstractApplicationUsagesCollector() { +class GitStatisticsCollector : AbstractProjectsUsagesCollector() { private val ID = GroupDescriptor.create("Git") override fun getProjectUsages(project: Project): Set { diff --git a/plugins/git4idea/src/git4idea/GitUtil.java b/plugins/git4idea/src/git4idea/GitUtil.java index 239d8cfa0191..962af61b64d2 100644 --- a/plugins/git4idea/src/git4idea/GitUtil.java +++ b/plugins/git4idea/src/git4idea/GitUtil.java @@ -79,9 +79,12 @@ public class GitUtil { public static final String DOT_GIT = ".git"; + /** + * This comment char overrides the standard '#' and any other potentially defined by user via {@code core.commentChar}. + */ + public static final String COMMENT_CHAR = "\u0001"; + public static final String ORIGIN_HEAD = "origin/HEAD"; - public static final String GRAFTED = "grafted"; - public static final String REPLACED = "replaced"; public static final Function REPOSITORY_TO_ROOT = repository -> repository.getRoot(); diff --git a/plugins/git4idea/src/git4idea/GitVcs.java b/plugins/git4idea/src/git4idea/GitVcs.java index 038bf73a9f31..08fb7222be90 100644 --- a/plugins/git4idea/src/git4idea/GitVcs.java +++ b/plugins/git4idea/src/git4idea/GitVcs.java @@ -92,7 +92,7 @@ public class GitVcs extends AbstractVcs { public static final String NAME = "Git"; public static final String ID = "git"; - private static final Logger log = Logger.getInstance(GitVcs.class.getName()); + private static final Logger LOG = Logger.getInstance(GitVcs.class.getName()); private static final VcsKey ourKey = createKey(NAME); @Nullable private final ChangeProvider myChangeProvider; @@ -255,7 +255,7 @@ public class GitVcs extends AbstractVcs { return GitRevisionNumber.resolve(myProject, root, revision); } catch (VcsException e) { - log.info("Unexpected problem with resolving the git revision number: ", e); + LOG.info("Unexpected problem with resolving the git revision number: ", e); throw e; } } @@ -392,13 +392,13 @@ public class GitVcs extends AbstractVcs { final String executable = myAppSettings.getPathToGit(); try { myVersion = GitVersion.identifyVersion(executable); - if (! myVersion.isSupported()) { - log.info("Unsupported Git version: " + myVersion); + LOG.info("Git version: " + myVersion); + if (!myVersion.isSupported()) { final String SETTINGS_LINK = "settings"; final String UPDATE_LINK = "update"; String message = String.format("The configured version of Git is not supported: %s.
    " + "The minimal supported version is %s. Please update.", - myVersion, GitVersion.MIN); + myVersion.getPresentation(), GitVersion.MIN.getPresentation()); VcsNotifier.getInstance(myProject).notifyError("Unsupported Git version", message, new NotificationListener.Adapter() { @Override @@ -415,7 +415,9 @@ public class GitVcs extends AbstractVcs { } ); } - } catch (Exception e) { + } + catch (Exception e) { + LOG.warn(e); if (getExecutableValidator().checkExecutableAndNotifyIfNeeded()) { // check executable before notifying error final String reason = (e.getCause() != null ? e.getCause() : e).getMessage(); String message = GitBundle.message("vcs.unable.to.run.git", executable, reason); diff --git a/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java b/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java index 3c61fc997b7a..712787cbe52f 100644 --- a/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java +++ b/plugins/git4idea/src/git4idea/branch/GitSmartOperationDialog.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -19,8 +19,8 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationNamesInfo; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.components.JBLabel; +import com.intellij.util.ui.JBUI; import git4idea.DialogManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -90,7 +90,7 @@ public class GitSmartOperationDialog extends DialogWrapper { JBLabel description = new JBLabel("Your local changes to the following files would be overwritten by " + myOperationTitle + ".
    " + ApplicationNamesInfo.getInstance().getFullProductName() + " can stash the changes, " + myOperationTitle + " and unstash them after that."); - description.setBorder(IdeBorderFactory.createEmptyBorder(0, 0, 10, 0)); + description.setBorder(JBUI.Borders.emptyBottom(10)); return description; } diff --git a/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java b/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java index 38e6c1040299..1642628ba62a 100644 --- a/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java +++ b/plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java @@ -190,7 +190,7 @@ public class GitCheckinEnvironment implements CheckinEnvironment { VirtualFile root = repository.getRoot(); File messageFile; try { - messageFile = createMessageFile(root, message); + messageFile = createCommitMessageFile(myProject, root, message); } catch (IOException ex) { //noinspection ThrowableInstanceNeverThrown @@ -552,11 +552,12 @@ public class GitCheckinEnvironment implements CheckinEnvironment { * @return a file reference * @throws IOException if file cannot be created */ - private File createMessageFile(VirtualFile root, final String message) throws IOException { + @NotNull + public static File createCommitMessageFile(@NotNull Project project, @NotNull VirtualFile root, final String message) throws IOException { // filter comment lines File file = FileUtil.createTempFile(GIT_COMMIT_MSG_FILE_PREFIX, GIT_COMMIT_MSG_FILE_EXT); file.deleteOnExit(); - @NonNls String encoding = GitConfigUtil.getCommitEncoding(myProject, root); + @NonNls String encoding = GitConfigUtil.getCommitEncoding(project, root); Writer out = new OutputStreamWriter(new FileOutputStream(file), encoding); try { out.write(message); diff --git a/plugins/git4idea/src/git4idea/checkin/GitCheckinHandlerFactory.java b/plugins/git4idea/src/git4idea/checkin/GitCheckinHandlerFactory.java index 39fe6b3e4a3f..0d4880018814 100644 --- a/plugins/git4idea/src/git4idea/checkin/GitCheckinHandlerFactory.java +++ b/plugins/git4idea/src/git4idea/checkin/GitCheckinHandlerFactory.java @@ -189,11 +189,11 @@ public class GitCheckinHandlerFactory extends VcsCheckinHandlerFactory { GitVersion version = vcs.getVersion(); if (System.getenv("HOME") == null && GitVersionSpecialty.DOESNT_DEFINE_HOME_ENV_VAR.existsIn(version)) { Messages.showErrorDialog(project, - "You are using Git " + version + " which doesn't define %HOME% environment variable properly.\n" + - "Consider updating Git to a newer version " + - "or define %HOME% to point to the place where the global .gitconfig is stored \n" + - "(it is usually %USERPROFILE% or %HOMEDRIVE%%HOMEPATH%).", - "HOME Variable Is Not Defined"); + "You are using Git " + version.getPresentation() + " which doesn't define %HOME% environment variable properly.\n" + + "Consider updating Git to a newer version " + + "or define %HOME% to point to the place where the global .gitconfig is stored \n" + + "(it is usually %USERPROFILE% or %HOMEDRIVE%%HOMEPATH%).", + "HOME Variable Is Not Defined"); return ReturnResult.CANCEL; } diff --git a/plugins/git4idea/src/git4idea/commands/GitHandler.java b/plugins/git4idea/src/git4idea/commands/GitHandler.java index 159feda9c164..72592085bc0b 100644 --- a/plugins/git4idea/src/git4idea/commands/GitHandler.java +++ b/plugins/git4idea/src/git4idea/commands/GitHandler.java @@ -36,7 +36,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.EnvironmentUtil; import com.intellij.util.EventDispatcher; import com.intellij.util.ObjectUtils; -import com.intellij.util.Processor; +import com.intellij.util.ThrowableConsumer; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.io.URLUtil; import com.intellij.util.net.HttpConfigurable; @@ -91,7 +91,7 @@ public abstract class GitHandler { // the flag indicating that environment has been cleaned up, by default is true because there is nothing to clean private boolean myEnvironmentCleanedUp = true; private UUID myHttpHandler; - @Nullable private Processor myInputProcessor; // The processor for stdin + @Nullable private ThrowableConsumer myInputProcessor; // The processor for stdin // if true process might be cancelled // note that access is safe because it accessed in unsynchronized block only after process is started, and it does not change after that @@ -632,9 +632,12 @@ public abstract class GitHandler { checkStarted(); try { if (myInputProcessor != null && myProcess != null) { - myInputProcessor.process(myProcess.getOutputStream()); + myInputProcessor.consume(myProcess.getOutputStream()); } } + catch (IOException e) { + addError(new VcsException(e)); + } finally { waitForProcess(); } @@ -802,7 +805,7 @@ public abstract class GitHandler { * * @param inputProcessor the processor */ - public void setInputProcessor(@Nullable Processor inputProcessor) { + public void setInputProcessor(@Nullable ThrowableConsumer inputProcessor) { myInputProcessor = inputProcessor; } } diff --git a/plugins/git4idea/src/git4idea/commands/GitImpl.java b/plugins/git4idea/src/git4idea/commands/GitImpl.java index 90c7888d6fd1..2ee0b52388f4 100644 --- a/plugins/git4idea/src/git4idea/commands/GitImpl.java +++ b/plugins/git4idea/src/git4idea/commands/GitImpl.java @@ -46,7 +46,10 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import static git4idea.GitUtil.COMMENT_CHAR; +import static java.util.Arrays.asList; import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; /** * Easy-to-use wrapper of common native Git commands. @@ -58,6 +61,7 @@ import static java.util.Collections.singleton; public class GitImpl implements Git { private static final Logger LOG = Logger.getInstance(Git.class); + private static final List REBASE_CONFIG_PARAMS = singletonList("core.commentChar=" + COMMENT_CHAR); public GitImpl() { } @@ -573,7 +577,7 @@ public class GitImpl implements Git { @NotNull GitLineHandlerListener... listeners) { Project project = repository.getProject(); VirtualFile root = repository.getRoot(); - GitLineHandler handler = new GitLineHandler(project, root, GitCommand.REBASE); + GitLineHandler handler = new GitLineHandler(project, root, GitCommand.REBASE, REBASE_CONFIG_PARAMS); handler.addParameters(parameters.asCommandLineArguments()); addListeners(handler, listeners); @@ -614,7 +618,7 @@ public class GitImpl implements Git { @NotNull GitLineHandlerListener[] listeners) { Project project = repository.getProject(); VirtualFile root = repository.getRoot(); - GitLineHandler handler = new GitLineHandler(project, root, GitCommand.REBASE); + GitLineHandler handler = new GitLineHandler(project, root, GitCommand.REBASE, REBASE_CONFIG_PARAMS); handler.addParameters(rebaseMode.asCommandLineArgument()); addListeners(handler, listeners); return runWithEditor(handler, createEditor(project, root, handler, false)); @@ -688,7 +692,7 @@ public class GitImpl implements Git { } private static void addListeners(@NotNull GitLineHandler handler, @NotNull GitLineHandlerListener... listeners) { - addListeners(handler, Arrays.asList(listeners)); + addListeners(handler, asList(listeners)); } private static void addListeners(@NotNull GitLineHandler handler, @NotNull List listeners) { diff --git a/plugins/git4idea/src/git4idea/config/GitVcsPanel.java b/plugins/git4idea/src/git4idea/config/GitVcsPanel.java index 0df9bd773209..2670dc7ea424 100644 --- a/plugins/git4idea/src/git4idea/config/GitVcsPanel.java +++ b/plugins/git4idea/src/git4idea/config/GitVcsPanel.java @@ -133,10 +133,10 @@ public class GitVcsPanel { if (version.isSupported()) { Messages.showInfoMessage(myRootPanel, String.format("%s
    Git version is %s", GitBundle.getString("find.git.success.title"), - version.toString()), + version.getPresentation()), GitBundle.getString("find.git.success.title")); } else { - Messages.showWarningDialog(myRootPanel, GitBundle.message("find.git.unsupported.message", version.toString(), GitVersion.MIN), + Messages.showWarningDialog(myRootPanel, GitBundle.message("find.git.unsupported.message", version.getPresentation(), GitVersion.MIN.getPresentation()), GitBundle.getString("find.git.success.title")); } } diff --git a/plugins/git4idea/src/git4idea/config/GitVersion.java b/plugins/git4idea/src/git4idea/config/GitVersion.java index 5ae9170cd093..d345ef6a874a 100644 --- a/plugins/git4idea/src/git4idea/config/GitVersion.java +++ b/plugins/git4idea/src/git4idea/config/GitVersion.java @@ -81,11 +81,11 @@ public final class GitVersion implements Comparable { private final int myMinor; private final int myRevision; private final int myPatchLevel; - private final Type myType; + @NotNull private final Type myType; private final int myHashCode; - public GitVersion(int major, int minor, int revision, int patchLevel, Type type) { + public GitVersion(int major, int minor, int revision, int patchLevel, @NotNull Type type) { myMajor = major; myMinor = minor; myRevision = revision; @@ -105,7 +105,7 @@ public final class GitVersion implements Comparable { * Parses output of "git version" command. */ @NotNull - public static GitVersion parse(String output) throws ParseException { + public static GitVersion parse(@NotNull String output) throws ParseException { if (StringUtil.isEmptyOrSpaces(output)) { throw new ParseException("Empty git --version output: " + output, 0); } @@ -129,7 +129,7 @@ public final class GitVersion implements Comparable { // Utility method used in parsing - checks that the given capture group exists and captured something - then returns the captured value, // otherwise returns 0. - private static int getIntGroup(Matcher matcher, int group) { + private static int getIntGroup(@NotNull Matcher matcher, int group) { if (group > matcher.groupCount()+1) { return 0; } @@ -141,7 +141,7 @@ public final class GitVersion implements Comparable { } @NotNull - public static GitVersion identifyVersion(String gitExecutable) throws TimeoutException, ExecutionException, ParseException { + public static GitVersion identifyVersion(@NotNull String gitExecutable) throws TimeoutException, ExecutionException, ParseException { GeneralCommandLine commandLine = new GeneralCommandLine(); commandLine.setExePath(gitExecutable); commandLine.addParameter("--version"); @@ -184,7 +184,7 @@ public final class GitVersion implements Comparable { * Types are considered equal also if one of them is undefined. Otherwise they are compared. */ @Override - public boolean equals(final Object obj) { + public boolean equals(Object obj) { if (!(obj instanceof GitVersion)) { return false; } @@ -235,10 +235,16 @@ public final class GitVersion implements Comparable { return myPatchLevel - o.myPatchLevel; } + @NotNull + public String getPresentation() { + String presentation = myMajor + "." + myMinor + "." + myRevision; + if (myPatchLevel > 0) presentation += "." + myPatchLevel; + return presentation; + } + @Override public String toString() { - final String msysIndicator = (myType == Type.MSYS ? ".msysgit" : ""); - return myMajor + "." + myMinor + "." + myRevision + "." + myPatchLevel + msysIndicator; + return myMajor + "." + myMinor + "." + myRevision + "." + myPatchLevel + " (" + myType + ")"; } /** @@ -255,6 +261,7 @@ public final class GitVersion implements Comparable { return version != null && compareTo(version) >= 0; } + @NotNull public Type getType() { return myType; } diff --git a/plugins/git4idea/src/git4idea/history/GitLogOutputSplitter.java b/plugins/git4idea/src/git4idea/history/GitLogOutputSplitter.java index f8f33d3c0af0..fd9a18bb55ae 100644 --- a/plugins/git4idea/src/git4idea/history/GitLogOutputSplitter.java +++ b/plugins/git4idea/src/git4idea/history/GitLogOutputSplitter.java @@ -31,7 +31,7 @@ import org.jetbrains.annotations.Nullable; * and feeds them to the consumer. It does not store output in order to save memory. */ class GitLogOutputSplitter implements GitLineHandlerListener { - private static final int OUTPUT_CAPACITY_THRESHOLD = 20000000; + private static final int OUTPUT_CAPACITY_THRESHOLD = 5_000_000; @NotNull private final GitLineHandler myHandler; @NotNull private final Consumer myRecordConsumer; diff --git a/plugins/git4idea/src/git4idea/history/GitLogRecordCollector.java b/plugins/git4idea/src/git4idea/history/GitLogRecordCollector.java index 4ee81d2340ca..d49cc4fcf162 100644 --- a/plugins/git4idea/src/git4idea/history/GitLogRecordCollector.java +++ b/plugins/git4idea/src/git4idea/history/GitLogRecordCollector.java @@ -26,12 +26,10 @@ import com.intellij.util.containers.MultiMap; import git4idea.GitVcs; import git4idea.commands.GitCommand; import git4idea.commands.GitSimpleHandler; +import git4idea.util.GitUIUtil; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; import static com.intellij.util.ObjectUtils.notNull; import static git4idea.history.GitLogParser.GitLogOption.HASH; @@ -43,9 +41,12 @@ import static git4idea.history.GitLogParser.GitLogOption.TREE; */ abstract class GitLogRecordCollector implements Consumer { private static final Logger LOG = Logger.getInstance(GitLogRecordCollector.class); + private static final int STATUS_LINES_THRESHOLD = 20_000; @NotNull private final Project myProject; @NotNull private final VirtualFile myRoot; - @NotNull private final MultiMap myHashToRecord = MultiMap.createLinked(); + @NotNull private final MultiMap myHashToRecord = MultiMap.create(); + @NotNull private final MultiMap myHashToIncompleteRecords = MultiMap.create(); + private int myIncompleteStatusLinesCount = 0; protected GitLogRecordCollector(@NotNull Project project, @NotNull VirtualFile root) { myProject = project; @@ -61,12 +62,12 @@ abstract class GitLogRecordCollector implements Consumer { else { myHashToRecord.putValue(record.getHash(), record); if (parents.length == myHashToRecord.get(record.getHash()).size()) { - processCollectedRecords(); + processCollectedRecords(false); } } } - private void processCollectedRecords() { + private void processCollectedRecords(boolean processIncompleteRecords) { // there is a surprising (or not really surprising, depending how to look at it) problem with `-m` option // despite what is written in git-log documentation, it does not always output a record for each parent of a merge commit // if a merge commit has no changes with one of the parents, nothing is output for that parent @@ -80,62 +81,99 @@ abstract class GitLogRecordCollector implements Consumer { ArrayList records = ContainerUtil.newArrayList(notNull(myHashToRecord.get(hash))); GitLogRecord firstRecord = records.get(0); if (firstRecord.getParentsHashes().length != 0 && records.size() != firstRecord.getParentsHashes().length) { - if (!fillWithEmptyRecords(records)) continue; // skipping commit altogether on error + myHashToIncompleteRecords.put(hash, records); + records.forEach(r -> myIncompleteStatusLinesCount += r.getStatusInfos().size()); + } + else { + consume(records); } - consume(records); } myHashToRecord.clear(); + + // we want to avoid executing a lot of "git log" commands + // at the same time we do not want to waste too much memory on records + // so we process "incomplete" records when we accumulate too much of them in terms of status lines + // or at the very end + if (!myHashToIncompleteRecords.isEmpty() && (processIncompleteRecords || myIncompleteStatusLinesCount >= STATUS_LINES_THRESHOLD)) { + try { + Map hashToTreeMap = getHashToTreeMap(ContainerUtil.map(myHashToIncompleteRecords.entrySet(), + e -> ContainerUtil.getFirstItem(e.getValue()))); + for (String hash : myHashToIncompleteRecords.keySet()) { + ArrayList records = ContainerUtil.newArrayList(notNull(myHashToIncompleteRecords.get(hash))); + fillWithEmptyRecords(records, hashToTreeMap); + consume(records); + } + } + catch (VcsException e) { + LOG.error(e); + } + finally { + myHashToIncompleteRecords.clear(); + myIncompleteStatusLinesCount = 0; + // do not keep records on error + } + } } public void finish() { - processCollectedRecords(); + processCollectedRecords(true); } public abstract void consume(@NotNull List records); /* - * This method calculates tree hashes for a commit and its parents and places an empty record for parents that have same tree hash. + * This method calculates tree hashes for commits and their parents. */ - private boolean fillWithEmptyRecords(@NotNull List records) { + @NotNull + private Map getHashToTreeMap(@NotNull Collection records) throws VcsException { + Set hashes = ContainerUtil.newHashSet(); + + for (GitLogRecord r : records) { + hashes.add(r.getHash()); + hashes.addAll(Arrays.asList(r.getParentsHashes())); + } + + GitSimpleHandler handler = new GitSimpleHandler(myProject, myRoot, GitCommand.LOG); + GitLogParser parser = new GitLogParser(myProject, GitLogParser.NameStatus.NONE, HASH, TREE); + GitVcs vcs = notNull(GitVcs.getInstance(myProject)); + handler.setStdoutSuppressed(true); + handler.addParameters(parser.getPretty()); + handler.addParameters(GitLogUtil.getNoWalkParameter(vcs)); + handler.addParameters(GitLogUtil.STDIN); + handler.endOptions(); + + GitLogUtil.sendHashesToStdin(vcs, hashes, handler); + String output = handler.run(); + + if (!handler.errors().isEmpty()) { + throw new VcsException(GitUIUtil.stringifyErrors(handler.errors())); + } + + List hashAndTreeRecords = parser.parse(output); + return ContainerUtil.map2Map(hashAndTreeRecords, record -> Pair.create(record.getHash(), record.getTreeHash())); + } + + /* + * This method places an empty record for parents that have same tree hash. + */ + private static void fillWithEmptyRecords(@NotNull List records, @NotNull Map hashToTreeMap) { GitLogRecord firstRecord = records.get(0); String commit = firstRecord.getHash(); String[] parents = firstRecord.getParentsHashes(); - List hashes = ContainerUtil.newArrayList(parents); - hashes.add(commit); + String commitTreeHash = hashToTreeMap.get(commit); + LOG.assertTrue(commitTreeHash != null, "Could not get tree hash for commit " + commit); - GitSimpleHandler handler = new GitSimpleHandler(myProject, myRoot, GitCommand.LOG); - GitLogParser parser = new GitLogParser(myProject, GitLogParser.NameStatus.NONE, HASH, TREE); - handler.setStdoutSuppressed(true); - handler.addParameters(parser.getPretty()); - handler.addParameters(GitHistoryUtils.formHashParameters(notNull(GitVcs.getInstance(myProject)), hashes)); - handler.endOptions(); - - try { - String output = handler.run(); - - List hashAndTreeRecords = parser.parse(output); - Map hashToTreeMap = ContainerUtil.map2Map(hashAndTreeRecords, - record -> Pair.create(record.getHash(), record.getTreeHash())); - String commitTreeHash = hashToTreeMap.get(commit); - LOG.assertTrue(commitTreeHash != null, "Could not get tree hash for commit " + commit); - - for (int parentIndex = 0; parentIndex < parents.length; parentIndex++) { - String parent = parents[parentIndex]; - // sometimes a merge commit is identical to all its parents - // in this case, we get one empty record - String parentTreeHash = hashToTreeMap.get(parent); - LOG.assertTrue(parentTreeHash != null, "Could not get tree hash for commit " + parent); - if (parentTreeHash.equals(commitTreeHash) && records.size() < parents.length) { - records.add(parentIndex, new GitLogRecord(firstRecord.getOptions(), ContainerUtil.emptyList(), ContainerUtil.emptyList(), - firstRecord.isSupportsRawBody())); - } + for (int parentIndex = 0; parentIndex < parents.length; parentIndex++) { + String parent = parents[parentIndex]; + // sometimes a merge commit is identical to all its parents + // in this case, we get one empty record + String parentTreeHash = hashToTreeMap.get(parent); + LOG.assertTrue(parentTreeHash != null, "Could not get tree hash for commit " + parent); + if (parentTreeHash.equals(commitTreeHash) && records.size() < parents.length) { + records.add(parentIndex, new GitLogRecord(firstRecord.getOptions(), ContainerUtil.emptyList(), ContainerUtil.emptyList(), + firstRecord.isSupportsRawBody())); } } - catch (VcsException e) { - LOG.error(e); - return false; - } - return true; } } diff --git a/plugins/git4idea/src/git4idea/history/GitLogUtil.java b/plugins/git4idea/src/git4idea/history/GitLogUtil.java index a75050174392..8260417cb548 100644 --- a/plugins/git4idea/src/git4idea/history/GitLogUtil.java +++ b/plugins/git4idea/src/git4idea/history/GitLogUtil.java @@ -38,29 +38,30 @@ import git4idea.GitCommit; import git4idea.GitUtil; import git4idea.GitVcs; import git4idea.branch.GitBranchUtil; -import git4idea.commands.GitCommand; -import git4idea.commands.GitLineHandler; -import git4idea.commands.GitSimpleHandler; -import git4idea.commands.GitTextHandler; +import git4idea.commands.*; import git4idea.config.GitVersionSpecialty; import git4idea.log.GitLogProvider; import git4idea.log.GitRefManager; +import git4idea.util.GitUIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.IOException; import java.io.OutputStreamWriter; import java.util.*; import static com.intellij.util.ObjectUtils.notNull; +import static com.intellij.util.containers.ContainerUtil.getFirstItem; import static git4idea.history.GitLogParser.GitLogOption.*; public class GitLogUtil { private static final Logger LOG = Logger.getInstance(GitLogUtil.class); + public static final String GRAFTED = "grafted"; + public static final String REPLACED = "replaced"; /** * A parameter to {@code git log} which is equivalent to {@code --all}, but doesn't show the stuff from index or stash. */ public static final List LOG_ALL = Arrays.asList("HEAD", "--branches", "--remotes", "--tags"); + public static final String STDIN = "--stdin"; @NotNull public static List collectShortDetails(@NotNull Project project, @@ -145,7 +146,7 @@ public class GitLogUtil { @NotNull VcsLogObjectsFactory factory, @NotNull VirtualFile root) { return ContainerUtil.mapNotNull(refs, refName -> { - if (refName.equals(GitUtil.GRAFTED) || refName.equals(GitUtil.REPLACED)) return null; + if (refName.equals(GRAFTED) || refName.equals(REPLACED)) return null; VcsRefType type = GitRefManager.getRefType(refName); refName = GitBranchUtil.stripRefsPrefix(refName); return refName.equals(GitUtil.ORIGIN_HEAD) ? null : factory.createRef(hash, refName, type, root); @@ -254,6 +255,7 @@ public class GitLogUtil { GitLogRecordCollector recordCollector = new GitLogRecordCollector(project, root) { @Override public void consume(@NotNull List records) { + assertCorrectNumberOfRecords(records); commitConsumer.consume(createCommit(project, root, records, factory)); } }; @@ -261,6 +263,17 @@ public class GitLogUtil { recordCollector.finish(); } + public static void assertCorrectNumberOfRecords(@NotNull List records) { + GitLogRecord firstRecord = notNull(getFirstItem(records)); + String[] parents = firstRecord.getParentsHashes(); + LOG.assertTrue(parents.length == 0 || parents.length == records.size(), "Not enough records for commit " + + firstRecord.getHash() + + " expected " + + parents.length + + " records, but got " + + records.size()); + } + private static void readRecords(@NotNull Project project, @NotNull VirtualFile root, boolean withRefs, @@ -355,13 +368,23 @@ public class GitLogUtil { GitLogRecordCollector recordCollector = new GitLogRecordCollector(project, root) { @Override public void consume(@NotNull List records) { + assertCorrectNumberOfRecords(records); commitConsumer.consume(createCommit(project, root, records, factory)); } }; GitLineHandler handler = new GitLineHandler(project, root, GitCommand.LOG, createConfigParameters(true, fast)); + sendHashesToStdin(vcs, hashes, handler); + readRecordsFromHandler(project, root, false, true, recordCollector, handler, getNoWalkParameter(vcs), STDIN); + recordCollector.finish(); + + if (!handler.errors().isEmpty()) { + throw new VcsException(GitUIUtil.stringifyErrors(handler.errors())); + } + } + + public static void sendHashesToStdin(@NotNull GitVcs vcs, @NotNull Collection hashes, @NotNull GitHandler handler) { String separator = getSeparator(vcs); - Ref inputError = new Ref<>(); handler.setInputProcessor(stream -> { try (OutputStreamWriter writer = new OutputStreamWriter(stream, handler.getCharset())) { for (String hash : hashes) { @@ -371,18 +394,7 @@ public class GitLogUtil { writer.write(separator); writer.flush(); } - catch (IOException e) { - inputError.set(e); - } - return false; }); - - readRecordsFromHandler(project, root, false, true, recordCollector, handler, getNoWalkParameter(vcs), "--stdin"); - recordCollector.finish(); - - if (!inputError.isNull()) { - throw new VcsException(inputError.get()); - } } @NotNull diff --git a/plugins/git4idea/src/git4idea/push/GitPushResultNotification.java b/plugins/git4idea/src/git4idea/push/GitPushResultNotification.java index b0c8d0be48fa..c21333fff03b 100644 --- a/plugins/git4idea/src/git4idea/push/GitPushResultNotification.java +++ b/plugins/git4idea/src/git4idea/push/GitPushResultNotification.java @@ -104,7 +104,7 @@ class GitPushResultNotification extends Notification { if (tree != null) { tree.setBefore(pushResult.getBeforeUpdateLabel()); tree.setAfter(pushResult.getAfterUpdateLabel()); - notification.addAction(new ViewUpdateInfoNotification(project, tree, VIEW_FILES_UPDATED_DURING_THE_PUSH)); + notification.addAction(new ViewUpdateInfoNotification(project, tree, VIEW_FILES_UPDATED_DURING_THE_PUSH, notification)); } }); } diff --git a/plugins/git4idea/src/git4idea/rebase/GitInteractiveRebaseEditorHandler.java b/plugins/git4idea/src/git4idea/rebase/GitInteractiveRebaseEditorHandler.java index 1809be46c950..0bae12f3bd6c 100644 --- a/plugins/git4idea/src/git4idea/rebase/GitInteractiveRebaseEditorHandler.java +++ b/plugins/git4idea/src/git4idea/rebase/GitInteractiveRebaseEditorHandler.java @@ -23,7 +23,9 @@ import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VirtualFile; import git4idea.DialogManager; +import git4idea.GitUtil; import git4idea.config.GitConfigUtil; +import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -36,6 +38,7 @@ import java.util.UUID; import static com.intellij.CommonBundle.getCancelButtonText; import static com.intellij.CommonBundle.getOkButtonText; import static com.intellij.openapi.ui.Messages.getQuestionIcon; +import static com.intellij.openapi.util.text.StringUtil.splitByLinesKeepSeparators; import static git4idea.DialogManager.showOkCancelDialog; import static git4idea.rebase.GitRebaseEditorMain.ERROR_EXIT_CODE; @@ -92,10 +95,10 @@ public class GitInteractiveRebaseEditorHandler implements Closeable, GitRebaseEd } } - private boolean handleUnstructuredEditor(@NotNull String path) throws IOException { + protected boolean handleUnstructuredEditor(@NotNull String path) throws IOException { String encoding = GitConfigUtil.getCommitEncoding(myProject, myRoot); File file = new File(path); - String initialText = FileUtil.loadFile(file, encoding); + String initialText = ignoreComments(FileUtil.loadFile(file, encoding)); String newText = showUnstructuredEditor(initialText); if (newText == null) { @@ -107,6 +110,14 @@ public class GitInteractiveRebaseEditorHandler implements Closeable, GitRebaseEd } } + @NotNull + private static String ignoreComments(@NotNull String text) { + String[] lines = splitByLinesKeepSeparators(text); + return StreamEx.of(lines) + .filter(line -> !line.startsWith(GitUtil.COMMENT_CHAR)) + .joining(); + } + @Nullable private String showUnstructuredEditor(@NotNull String initialText) { Ref newText = Ref.create(); @@ -120,7 +131,7 @@ public class GitInteractiveRebaseEditorHandler implements Closeable, GitRebaseEd return newText.get(); } - private boolean handleInteractiveEditor(@NotNull String path) throws IOException { + protected boolean handleInteractiveEditor(@NotNull String path) throws IOException { GitInteractiveRebaseFile rebaseFile = new GitInteractiveRebaseFile(myProject, myRoot, path); try { List entries = rebaseFile.load(); diff --git a/plugins/git4idea/src/git4idea/rebase/GitInteractiveRebaseFile.java b/plugins/git4idea/src/git4idea/rebase/GitInteractiveRebaseFile.java index 6964fb73bb55..0fc923c1d9f4 100644 --- a/plugins/git4idea/src/git4idea/rebase/GitInteractiveRebaseFile.java +++ b/plugins/git4idea/src/git4idea/rebase/GitInteractiveRebaseFile.java @@ -20,6 +20,7 @@ import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.containers.ContainerUtil; +import git4idea.GitUtil; import git4idea.config.GitConfigUtil; import git4idea.util.StringScanner; import org.jetbrains.annotations.NonNls; @@ -48,7 +49,7 @@ class GitInteractiveRebaseFile { final StringScanner s = new StringScanner(FileUtil.loadFile(new File(myFile), encoding)); boolean noop = false; while (s.hasMoreData()) { - if (s.isEol() || s.startsWith('#')) { + if (s.isEol() || s.startsWith(GitUtil.COMMENT_CHAR)) { s.nextLine(); continue; } diff --git a/plugins/git4idea/src/git4idea/rebase/GitRebaser.java b/plugins/git4idea/src/git4idea/rebase/GitRebaser.java index e977bb8d22fb..e2d15c671f98 100644 --- a/plugins/git4idea/src/git4idea/rebase/GitRebaser.java +++ b/plugins/git4idea/src/git4idea/rebase/GitRebaser.java @@ -24,6 +24,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vcs.ProjectLevelVcsManager; import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.VcsNotifier; import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.openapi.vfs.VirtualFile; import git4idea.GitUtil; @@ -93,7 +94,7 @@ public class GitRebaser { myProgressIndicator.setText(oldText); return result.success() ? GitUpdateResult.SUCCESS : - handleRebaseFailure(rebaseHandler, root, rebaseConflictDetector, untrackedFilesDetector, localChangesDetector); + handleRebaseFailure(rebaseHandler, root, result, rebaseConflictDetector, untrackedFilesDetector, localChangesDetector); } catch (ProcessCanceledException pce) { if (onCancel != null) { @@ -323,6 +324,7 @@ public class GitRebaser { @NotNull public GitUpdateResult handleRebaseFailure(@NotNull GitLineHandler handler, @NotNull VirtualFile root, + @NotNull GitCommandResult result, @NotNull GitRebaseProblemDetector rebaseConflictDetector, @NotNull GitMessageWithFilesDetector untrackedWouldBeOverwrittenDetector, @NotNull GitLocalChangesWouldBeOverwrittenDetector localChangesDetector) { @@ -343,7 +345,7 @@ public class GitRebaser { } else { LOG.info("handleRebaseFailure error " + handler.errors()); - GitUIUtil.notifyImportantError(myProject, "Rebase error", GitUIUtil.stringifyErrors(handler.errors())); + VcsNotifier.getInstance(myProject).notifyError("Rebase error", result.getErrorOutputAsHtmlString()); return GitUpdateResult.ERROR; } } diff --git a/plugins/git4idea/src/git4idea/rebase/GitRewordOperation.kt b/plugins/git4idea/src/git4idea/rebase/GitRewordOperation.kt index 67a9552072eb..0d6c4abd6273 100644 --- a/plugins/git4idea/src/git4idea/rebase/GitRewordOperation.kt +++ b/plugins/git4idea/src/git4idea/rebase/GitRewordOperation.kt @@ -20,6 +20,7 @@ import com.intellij.notification.NotificationAction import com.intellij.notification.NotificationType import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.service import com.intellij.openapi.diagnostic.Attachment import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.progress.EmptyProgressIndicator @@ -35,7 +36,10 @@ import com.intellij.vcs.log.Hash import com.intellij.vcs.log.VcsCommitMetadata import git4idea.branch.GitBranchUtil import git4idea.branch.GitRebaseParams +import git4idea.checkin.GitCheckinEnvironment import git4idea.commands.Git +import git4idea.commands.GitCommand +import git4idea.commands.GitLineHandler import git4idea.config.GitConfigUtil import git4idea.history.GitLogUtil import git4idea.rebase.GitRebaseEntry.Action.pick @@ -43,6 +47,8 @@ import git4idea.rebase.GitRebaseEntry.Action.reword import git4idea.repo.GitRepository import git4idea.repo.GitRepositoryChangeListener import git4idea.reset.GitResetMode +import java.io.File +import java.io.IOException class GitRewordOperation(private val repository: GitRepository, private val commit: VcsCommitMetadata, @@ -60,6 +66,23 @@ class GitRewordOperation(private val repository: GitRepository, private var rewordedCommit: Hash? = null fun execute() { + var reworded = false + if (isLatestCommit()) { + reworded = rewordViaAmend() + } + if (!reworded) { + reworded = rewordViaRebase() + } + + if (reworded) { + headAfterReword = repository.currentRevision + rewordedCommit = findNewHashOfRewordedCommit(headAfterReword!!) + } + } + + private fun isLatestCommit() = commit.id.asString() == initialHeadPosition + + private fun rewordViaRebase(): Boolean { val rebaseEditor = GitAutomaticRebaseEditor(project, commit.root, entriesEditor = { list -> injectRewordAction(list) }, plainTextEditor = { editorText -> supplyNewMessage(editorText) }) @@ -67,9 +90,35 @@ class GitRewordOperation(private val repository: GitRepository, val params = GitRebaseParams.editCommits(commit.parents.first().asString(), rebaseEditor, true) val indicator = ProgressManager.getInstance().progressIndicator ?: EmptyProgressIndicator() val spec = GitRebaseSpec.forNewRebase(project, params, listOf(repository), indicator) - RewordProcess(spec).rebase() - headAfterReword = repository.currentRevision - rewordedCommit = findNewHashOfRewordedCommit(headAfterReword!!) + val rewordProcess = RewordProcess(spec) + rewordProcess.rebase() + return rewordProcess.succeeded + } + + private fun rewordViaAmend(): Boolean { + val handler = GitLineHandler(project, repository.root, GitCommand.COMMIT) + val messageFile: File + try { + messageFile = GitCheckinEnvironment.createCommitMessageFile(project, repository.root, newMessage) + } + catch(e: IOException) { + LOG.warn("Couldn't create message file", e) + return false + } + handler.addParameters("--amend") + handler.addParameters("-F", messageFile.absolutePath) + handler.addParameters("--only") // without any files: to amend only the message + + val result = Git.getInstance().runCommand(handler) + repository.update() + if (result.success()) { + notifySuccess() + return true + } + else { + LOG.warn("Couldn't reword via amend: " + result.errorOutputAsJoinedString) + return false + } } internal fun undo() { @@ -142,6 +191,34 @@ class GitRewordOperation(private val repository: GitRepository, return UndoPossibility.Possible } + private fun notifySuccess() { + val notification = STANDARD_NOTIFICATION.createNotification("Reworded Successfully", "", NotificationType.INFORMATION, null) + notification.addAction(object : NotificationAction("Undo") { + override fun actionPerformed(e: AnActionEvent, notification: Notification) { + notification.expire() + undoInBackground() + } + }) + + val connection = project.messageBus.connect() + notification.whenExpired { connection.disconnect() } + connection.subscribe(GitRepository.GIT_REPO_CHANGE, GitRepositoryChangeListener { + ApplicationManager.getApplication().executeOnPooledThread { + if (checkUndoPossibility(project) !is UndoPossibility.Possible) notification.expire() + } + }) + + notifier.notify(notification) + } + + private fun undoInBackground() { + ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Undoing Reword") { + override fun run(indicator: ProgressIndicator) { + undo() + } + }) + } + private sealed class UndoPossibility { object Possible : UndoPossibility() object HeadMoved : UndoPossibility() @@ -150,35 +227,14 @@ class GitRewordOperation(private val repository: GitRepository, } private inner class RewordProcess(spec: GitRebaseSpec) : GitRebaseProcess(project, spec, null) { + var succeeded = false + override fun notifySuccess(successful: MutableMap, skippedCommits: MultiMap) { - val notification = STANDARD_NOTIFICATION.createNotification("Reworded Successfully", "", NotificationType.INFORMATION, null) - notification.addAction(object : NotificationAction("Undo") { - override fun actionPerformed(e: AnActionEvent, notification: Notification) { - notification.expire() - undoInBackground() - } - }) - - val connection = project.messageBus.connect() - notification.whenExpired { connection.disconnect() } - connection.subscribe(GitRepository.GIT_REPO_CHANGE, GitRepositoryChangeListener { - ApplicationManager.getApplication().executeOnPooledThread { - if (checkUndoPossibility(project) !is UndoPossibility.Possible) notification.expire() - } - }) - - notifier.notify(notification) + notifySuccess() + succeeded = true } override fun shouldRefreshOnSuccess(successType: GitSuccessfulRebase.SuccessType) = false - - private fun undoInBackground() { - ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Undoing Reword") { - override fun run(indicator: ProgressIndicator) { - undo() - } - }) - } } } \ No newline at end of file diff --git a/plugins/git4idea/src/git4idea/repo/GitRepositoryUpdater.java b/plugins/git4idea/src/git4idea/repo/GitRepositoryUpdater.java index b3d00ed50365..f51aec657325 100644 --- a/plugins/git4idea/src/git4idea/repo/GitRepositoryUpdater.java +++ b/plugins/git4idea/src/git4idea/repo/GitRepositoryUpdater.java @@ -15,7 +15,6 @@ */ package git4idea.repo; -import com.intellij.dvcs.DvcsUtil; import com.intellij.openapi.Disposable; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.LocalFileSystem; @@ -35,6 +34,8 @@ import java.util.Collection; import java.util.List; import java.util.Set; +import static com.intellij.dvcs.DvcsUtil.ensureAllChildrenInVfs; + /** * Listens to .git service files changes and updates {@link GitRepository} when needed. */ @@ -43,12 +44,11 @@ final class GitRepositoryUpdater implements Disposable, BulkFileListener { @NotNull private final GitRepository myRepository; @NotNull private final GitRepositoryFiles myRepositoryFiles; @Nullable private final MessageBusConnection myMessageBusConnection; - @NotNull private final QueueProcessor myUpdateQueue; - @NotNull private final Object DUMMY_UPDATE_OBJECT = new Object(); + @NotNull private final QueueProcessor > myUpdateQueue; @Nullable private final VirtualFile myRemotesDir; @Nullable private final VirtualFile myHeadsDir; @Nullable private final VirtualFile myTagsDir; - @Nullable private final Set myWatchRequests; + @NotNull private final Set myWatchRequests; GitRepositoryUpdater(@NotNull GitRepository repository, @NotNull GitRepositoryFiles gitFiles) { myRepository = repository; @@ -62,7 +62,8 @@ final class GitRepositoryUpdater implements Disposable, BulkFileListener { myTagsDir = VcsUtil.getVirtualFile(myRepositoryFiles.getRefsTagsFile()); Project project = repository.getProject(); - myUpdateQueue = new QueueProcessor<>(new DvcsUtil.Updater(repository), project.getDisposed()); + myUpdateQueue = new QueueProcessor<>(events -> processEvents(events), project.getDisposed()); + if (!project.isDisposed()) { myMessageBusConnection = project.getMessageBus().connect(); myMessageBusConnection.subscribe(VirtualFileManager.VFS_CHANGES, this); @@ -82,6 +83,10 @@ final class GitRepositoryUpdater implements Disposable, BulkFileListener { @Override public void after(@NotNull List events) { + myUpdateQueue.add(events); + } + + private void processEvents(@NotNull List events) { // which files in .git were changed boolean configChanged = false; boolean headChanged = false; @@ -99,11 +104,11 @@ final class GitRepositoryUpdater implements Disposable, BulkFileListener { } else if (myRepositoryFiles.isBranchFile(filePath)) { // it is also possible, that a local branch with complex name ("folder/branch") was created => the folder also to be watched. branchFileChanged = true; - DvcsUtil.ensureAllChildrenInVfs(myHeadsDir); + ensureAllChildrenInVfs(myHeadsDir); } else if (myRepositoryFiles.isRemoteBranchFile(filePath)) { // it is possible, that a branch from a new remote was fetch => we need to add new remote folder to the VFS branchFileChanged = true; - DvcsUtil.ensureAllChildrenInVfs(myRemotesDir); + ensureAllChildrenInVfs(myRemotesDir); } else if (myRepositoryFiles.isPackedRefs(filePath)) { packedRefsChanged = true; } else if (myRepositoryFiles.isRebaseFile(filePath)) { @@ -111,13 +116,13 @@ final class GitRepositoryUpdater implements Disposable, BulkFileListener { } else if (myRepositoryFiles.isMergeFile(filePath)) { mergeFileChanged = true; } else if (myRepositoryFiles.isTagFile(filePath)) { - DvcsUtil.ensureAllChildrenInVfs(myTagsDir); + ensureAllChildrenInVfs(myTagsDir); tagChanged = true; } } if (headChanged || configChanged || branchFileChanged || packedRefsChanged || rebaseFileChanged || mergeFileChanged) { - myUpdateQueue.add(DUMMY_UPDATE_OBJECT); + myRepository.update(); } else if (tagChanged) { myRepository.getProject().getMessageBus().syncPublisher(GitRepository.GIT_REPO_CHANGE).repositoryChanged(myRepository); @@ -129,7 +134,7 @@ final class GitRepositoryUpdater implements Disposable, BulkFileListener { rootDir.getChildren(); } for (String path : myRepositoryFiles.getDirsToWatch()) { - DvcsUtil.ensureAllChildrenInVfs(LocalFileSystem.getInstance().refreshAndFindFileByPath(path)); + ensureAllChildrenInVfs(LocalFileSystem.getInstance().refreshAndFindFileByPath(path)); } } } diff --git a/plugins/git4idea/src/git4idea/repo/GitUntrackedFilesHolder.java b/plugins/git4idea/src/git4idea/repo/GitUntrackedFilesHolder.java index a723ad04522e..5fac8939793d 100644 --- a/plugins/git4idea/src/git4idea/repo/GitUntrackedFilesHolder.java +++ b/plugins/git4idea/src/git4idea/repo/GitUntrackedFilesHolder.java @@ -26,6 +26,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.openapi.vfs.newvfs.BulkFileListener; import com.intellij.openapi.vfs.newvfs.events.*; +import com.intellij.util.concurrency.QueueProcessor; import com.intellij.util.messages.MessageBusConnection; import git4idea.GitLocalBranch; import git4idea.GitUtil; @@ -79,8 +80,6 @@ import java.util.*; * This is done so, because the latter two variables are accessed from the AWT in after() and we don't want to lock the AWT long, * while myDefinitelyUntrackedFiles is modified along with native request to Git. *

    - * - * @author Kirill Likhodedov */ public class GitUntrackedFilesHolder implements Disposable, BulkFileListener { @@ -101,6 +100,8 @@ public class GitUntrackedFilesHolder implements Disposable, BulkFileListener { private final Object LOCK = new Object(); private final GitRepositoryManager myRepositoryManager; + private final QueueProcessor> myEventsProcessor; + GitUntrackedFilesHolder(@NotNull GitRepository repository, @NotNull GitRepositoryFiles gitFiles) { myProject = repository.getProject(); myRepository = repository; @@ -112,6 +113,8 @@ public class GitUntrackedFilesHolder implements Disposable, BulkFileListener { myRepositoryManager = GitUtil.getRepositoryManager(myProject); myRepositoryFiles = gitFiles; + + myEventsProcessor = new QueueProcessor<>(events -> processEvents(events), myProject.getDisposed()); } void setupVfsListener(@NotNull Project project) { @@ -234,6 +237,10 @@ public class GitUntrackedFilesHolder implements Disposable, BulkFileListener { @Override public void after(@NotNull List events) { + myEventsProcessor.add(events); + } + + private void processEvents(@NotNull List events) { boolean allChanged = false; Set filesToRefresh = new HashSet<>(); diff --git a/plugins/git4idea/tests/git4idea/merge/GitMergeProviderTestCase.kt b/plugins/git4idea/tests/git4idea/merge/GitMergeProviderTestCase.kt index a7a76120b402..9cf2b559464f 100644 --- a/plugins/git4idea/tests/git4idea/merge/GitMergeProviderTestCase.kt +++ b/plugins/git4idea/tests/git4idea/merge/GitMergeProviderTestCase.kt @@ -26,10 +26,7 @@ import com.intellij.util.LineSeparator import com.intellij.vcsUtil.VcsFileUtil import git4idea.branch.GitRebaseParams import git4idea.repo.GitRepository -import git4idea.test.GitPlatformTest -import git4idea.test.cd -import git4idea.test.git -import git4idea.test.mv +import git4idea.test.* import git4idea.util.GitFileUtils import java.io.File import java.io.FileNotFoundException @@ -96,12 +93,12 @@ abstract class GitMergeProviderTestCase : GitPlatformTest() { // private fun doRebaseInteractive(onto: String) { - myGit.setInteractiveRebaseEditor { + myGit.setInteractiveRebaseEditor (TestGitImpl.InteractiveRebaseEditor({ it.lines().mapIndexed { i, s -> if (i != 0) s else s.replace("pick", "reword") }.joinToString(LineSeparator.getSystemLineSeparator().separatorString) - } + }, null)) val rebaseParams = GitRebaseParams(null, null, "branch-$onto", true, false) myGit.rebase(repository, rebaseParams) } diff --git a/plugins/git4idea/tests/git4idea/rebase/GitRewordTest.kt b/plugins/git4idea/tests/git4idea/rebase/GitRewordTest.kt index 5bd8b96a52c3..387d0b0fa6ee 100644 --- a/plugins/git4idea/tests/git4idea/rebase/GitRewordTest.kt +++ b/plugins/git4idea/tests/git4idea/rebase/GitRewordTest.kt @@ -16,10 +16,7 @@ package git4idea.rebase import com.intellij.openapi.util.text.StringUtil -import git4idea.test.GitSingleRepoTest -import git4idea.test.assertLatestHistory -import git4idea.test.file -import git4idea.test.git +import git4idea.test.* class GitRewordTest : GitSingleRepoTest() { @@ -32,6 +29,22 @@ class GitRewordTest : GitSingleRepoTest() { assertEquals("Message reworded incorrectly", newMessage, git("log HEAD --no-walk --pretty=%B")) } + fun `test reword via amend doesn't touch the local changes`() { + val commit = file("a").create("initial").addCommit("Wrong message").details() + file("b").create("b").add() + + val newMessage = "Correct message" + GitRewordOperation(myRepo, commit, newMessage).execute() + + assertEquals("Message reworded incorrectly", newMessage, git("log HEAD --no-walk --pretty=%B")) + myRepo.assertStagedChanges { + added("b") + } + myRepo.assertCommitted { + added("a") + } + } + fun `test reword previous commit`() { val file = file("a").create("initial") val commit = file.addCommit("Wrong message").details() @@ -103,4 +116,20 @@ class GitRewordTest : GitSingleRepoTest() { assertTrue("Message reworded incorrectly. Expected:\n[$newMessage] Actual:\n[$actualMessage]", StringUtil.equalsIgnoreWhitespaces(newMessage, actualMessage)) } + + // IDEA-175443 + fun `test reword with hash symbol`() { + val commit = file("a").create("initial").addCommit("Wrong message").details() + + val newMessage = """ + Subject + + #body starting with a hash + """.trimIndent() + GitRewordOperation(myRepo, commit, newMessage).execute() + + val actualMessage = git("log HEAD --no-walk --pretty=%B") + assertTrue("Message reworded incorrectly. Expected:\n[$newMessage] Actual:\n[$actualMessage]", + StringUtil.equalsIgnoreWhitespaces(newMessage, actualMessage)) + } } \ No newline at end of file diff --git a/plugins/git4idea/tests/git4idea/rebase/GitSingleRepoRebaseTest.kt b/plugins/git4idea/tests/git4idea/rebase/GitSingleRepoRebaseTest.kt index b9f7815c84bd..989df88cbf6c 100644 --- a/plugins/git4idea/tests/git4idea/rebase/GitSingleRepoRebaseTest.kt +++ b/plugins/git4idea/tests/git4idea/rebase/GitSingleRepoRebaseTest.kt @@ -20,7 +20,10 @@ import com.intellij.openapi.progress.EmptyProgressIndicator import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.ui.DialogWrapper import com.intellij.openapi.ui.Messages +import com.intellij.openapi.vcs.ui.CommitMessage +import com.intellij.util.Functions import com.intellij.util.LineSeparator +import git4idea.GitUtil import git4idea.branch.GitBranchUiHandler import git4idea.branch.GitBranchWorker import git4idea.branch.GitRebaseParams @@ -29,6 +32,7 @@ import git4idea.test.* import org.mockito.Mockito import org.mockito.Mockito.`when` import kotlin.properties.Delegates +import kotlin.reflect.jvm.javaField import kotlin.test.assertFailsWith class GitSingleRepoRebaseTest : GitRebaseBaseTest() { @@ -409,12 +413,12 @@ class GitSingleRepoRebaseTest : GitRebaseBaseTest() { } - myGit.setInteractiveRebaseEditor { + myGit.setInteractiveRebaseEditor (TestGitImpl.InteractiveRebaseEditor({ it.lines().mapIndexed { i, s -> if (i != 0) s else s.replace("pick", "edit") }.joinToString(LineSeparator.getSystemLineSeparator().separatorString) - } + }, null)) rebaseInteractively() @@ -428,10 +432,68 @@ class GitSingleRepoRebaseTest : GitRebaseBaseTest() { assertNoRebaseInProgress(repo) } + // IDEA-140568 + fun `test git help comments are ignored when parsing interactive rebase`() { + makeCommit("initial.txt") + repo.update() + val initialMessage = "Wrong message" + val commit = file("a").create("initial").addCommit(initialMessage).details() + + git("config core.commentChar $") + + var receivedEntries: List? = null + val rebaseEditor = GitAutomaticRebaseEditor(project, commit.root, + entriesEditor = { list -> + receivedEntries = list + list + }, + plainTextEditor = { it }) + GitTestingRebaseProcess(myProject, GitRebaseParams.editCommits("HEAD^", rebaseEditor, false), repo).rebase() + + assertNotNull("Didn't get any rebase entries", receivedEntries) + assertEquals("Rebase entries parsed incorrectly", listOf(GitRebaseEntry.Action.pick), receivedEntries!!.map { it.action }) + } + + // IDEA-176455 + fun `test reword during interactive rebase writes commit message correctly`() { + makeCommit("initial.txt") + repo.update() + val initialMessage = "Wrong message" + file("a").create("initial").addCommit(initialMessage).details() + + val newMessage = """ + Subject + + #body starting with a hash + """.trimIndent() + + myGit.setInteractiveRebaseEditor (TestGitImpl.InteractiveRebaseEditor({ + it.lines().mapIndexed { i, s -> + if (i != 0) s + else s.replace("pick", "reword") + }.joinToString(LineSeparator.getSystemLineSeparator().separatorString) + }, null)) + + var receivedMessage : String? = null + myDialogManager.onDialog(GitRebaseUnstructuredEditor::class.java, { it -> + receivedMessage = it.text + val field = GitRebaseUnstructuredEditor::class.java.getDeclaredField("myTextEditor") + field.isAccessible = true + val commitMessage = field.get (it) as CommitMessage + commitMessage.setText(newMessage) + 0 + }) + + rebaseInteractively("HEAD^") + + assertEquals("Initial message is incorrect", initialMessage, receivedMessage) + assertEquals("Resulting message is incorrect", newMessage, git("log HEAD --no-walk --pretty=%B")) + } + fun `test cancel in interactive rebase should show no error notification`() { repo.`diverge feature and master`() - myDialogManager.onDialog(GitRebaseEditor::class.java) { DialogWrapper.CANCEL_EXIT_CODE } + myDialogManager.onDialog(GitRebaseEditor::class.java) { DialogWrapper.CANCEL_EXIT_CODE } assertFailsWith(ProcessCanceledException::class) { rebaseInteractively() } assertNoNotification() @@ -456,8 +518,8 @@ class GitSingleRepoRebaseTest : GitRebaseBaseTest() { repo.`assert feature not rebased on master`() } - private fun rebaseInteractively() { - GitTestingRebaseProcess(myProject, GitRebaseParams(null, null, "master", true, false), repo).rebase() + private fun rebaseInteractively(revision: String = "master") { + GitTestingRebaseProcess(myProject, GitRebaseParams(null, null, revision, true, false), repo).rebase() } fun `test checkout with rebase`() { diff --git a/plugins/git4idea/tests/git4idea/test/GitTestAssertions.kt b/plugins/git4idea/tests/git4idea/test/GitTestAssertions.kt index fec5848b45a1..940b185b00d4 100644 --- a/plugins/git4idea/tests/git4idea/test/GitTestAssertions.kt +++ b/plugins/git4idea/tests/git4idea/test/GitTestAssertions.kt @@ -16,10 +16,14 @@ package git4idea.test import com.intellij.openapi.vcs.FilePath +import com.intellij.openapi.vcs.FileStatus +import com.intellij.openapi.vcs.changes.Change import com.intellij.openapi.vfs.VirtualFile +import com.intellij.testFramework.PlatformTestCase import com.intellij.testFramework.PlatformTestCase.assertOrderedEquals import com.intellij.vcs.log.VcsCommitMetadata import com.intellij.vcsUtil.VcsUtil.getFilePath +import git4idea.changes.GitChangeUtils import git4idea.history.GitHistoryUtils import git4idea.history.GitLogUtil import git4idea.repo.GitRepository @@ -57,4 +61,65 @@ private fun GitRepository.assertLatestHistory(mapping: (VcsCommitMetadata) -> St .map(mapping) .subList(0, expectedMessages.size) assertOrderedEquals("History is incorrect", actualMessages, expectedMessages.asList()) +} + +fun GitRepository.assertStagedChanges(changes: ChangesBuilder.() -> Unit) { + val cb = ChangesBuilder() + cb.changes() + + val actualChanges = GitChangeUtils.getStagedChanges(this.project, this.root) + for (change in cb.changes) { + val found = actualChanges.find(change.matcher) + PlatformTestCase.assertNotNull("The change [$change] is not staged", found) + actualChanges.remove(found) + } + PlatformTestCase.assertTrue(actualChanges.isEmpty()) +} + +fun GitRepository.assertCommitted(changes: ChangesBuilder.() -> Unit) { + val cb = ChangesBuilder() + cb.changes() + + val actualChanges = GitHistoryUtils.history(this.project, this.root, "-1")[0].changes + for (change in cb.changes) { + val found = actualChanges.find(change.matcher) + PlatformTestCase.assertNotNull("The change [$change] wasn't committed", found) + actualChanges.remove(found) + } + PlatformTestCase.assertTrue(actualChanges.isEmpty()) +} + +class ChangesBuilder { + data class AChange(val type: FileStatus, val nameBefore: String?, val nameAfter: String, val matcher: (Change) -> Boolean) { + constructor(type: FileStatus, nameAfter: String, matcher: (Change) -> Boolean) : this(type, null, nameAfter, matcher) + + override fun toString(): String { + when (type) { + Change.Type.NEW -> return "A: $nameAfter" + Change.Type.DELETED -> return "D: $nameAfter" + Change.Type.MOVED -> return "M: $nameBefore -> $nameAfter" + else -> return "M: $nameAfter" + } + } + } + + val changes = linkedSetOf() + + fun added(name: String) { + PlatformTestCase.assertTrue(changes.add(AChange(FileStatus.ADDED, name) { + it.fileStatus == FileStatus.ADDED && it.beforeRevision == null && it.afterRevision!!.file.name == name + })) + } + + fun modified(name:String) { + PlatformTestCase.assertTrue(changes.add(AChange(FileStatus.MODIFIED, name) { + it.fileStatus == FileStatus.MODIFIED && it.beforeRevision!!.file.name == name && it.afterRevision!!.file.name == name + })) + } + + fun rename(from: String, to: String) { + PlatformTestCase.assertTrue(changes.add(AChange(FileStatus.MODIFIED, from, to) { + it.isRenamed && from == it.beforeRevision!!.file.name && to == it.afterRevision!!.file.name + })) + } } \ No newline at end of file diff --git a/plugins/git4idea/tests/git4idea/test/TestGit.kt b/plugins/git4idea/tests/git4idea/test/TestGit.kt index fbbcb79650b0..aeeb67315330 100644 --- a/plugins/git4idea/tests/git4idea/test/TestGit.kt +++ b/plugins/git4idea/tests/git4idea/test/TestGit.kt @@ -47,7 +47,10 @@ class TestGitImpl : GitImpl() { @Volatile private var myRebaseShouldFail: (GitRepository) -> Boolean = { false } @Volatile private var myPushHandler: (GitRepository) -> GitCommandResult? = { null } @Volatile private var myBranchDeleteHandler: (GitRepository) -> GitCommandResult? = { null } - @Volatile private var myInteractiveRebaseEditor: ((String) -> String)? = null + @Volatile private var interactiveRebaseEditor: InteractiveRebaseEditor? = null + + class InteractiveRebaseEditor(val entriesEditor: ((String) -> String)?, + val plainTextEditor: ((String) -> String)?) override fun push(repository: GitRepository, remote: GitRemote, @@ -95,20 +98,29 @@ class TestGitImpl : GitImpl() { override fun createEditor(project: Project, root: VirtualFile, handler: GitLineHandler, commitListAware: Boolean): GitInteractiveRebaseEditorHandler { - if (myInteractiveRebaseEditor == null) return super.createEditor(project, root, handler, commitListAware) + if (interactiveRebaseEditor == null) return super.createEditor(project, root, handler, commitListAware) val service = GitRebaseEditorService.getInstance() val editor = object: GitInteractiveRebaseEditorHandler(service, project, root) { - override fun editCommits(path: String): Int { + override fun handleUnstructuredEditor(path: String): Boolean { + val plainTextEditor = interactiveRebaseEditor!!.plainTextEditor + return if (plainTextEditor != null) handleEditor(path, plainTextEditor) else super.handleUnstructuredEditor(path) + } + + override fun handleInteractiveEditor(path: String): Boolean { + val entriesEditor = interactiveRebaseEditor!!.entriesEditor + return if (entriesEditor != null) handleEditor(path, entriesEditor) else super.handleInteractiveEditor(path) + } + + private fun handleEditor(path: String, editor: (String) -> String): Boolean { try { val file = File(path) - FileUtil.writeToFile(file, myInteractiveRebaseEditor!!(FileUtil.loadFile(file))) + FileUtil.writeToFile(file, editor(FileUtil.loadFile(file))) } catch(e: Exception) { LOG.error(e) - return 1 } - return 0 + return true } } service.configureHandler(handler, editor.handlerNo) @@ -140,14 +152,14 @@ class TestGitImpl : GitImpl() { myBranchDeleteHandler = branchDeleteHandler } - fun setInteractiveRebaseEditor(editor: (String) -> String) { - myInteractiveRebaseEditor = editor + fun setInteractiveRebaseEditor(editor: InteractiveRebaseEditor) { + interactiveRebaseEditor = editor } fun reset() { myRebaseShouldFail = { false } myPushHandler = { null } - myInteractiveRebaseEditor = null + interactiveRebaseEditor = null } private fun failOrCall(repository: GitRepository, delegate: () -> GitCommandResult): GitCommandResult { diff --git a/plugins/git4idea/tests/git4idea/tests/GitCommitTest.kt b/plugins/git4idea/tests/git4idea/tests/GitCommitTest.kt index 5bee825614eb..c114d115f4c3 100644 --- a/plugins/git4idea/tests/git4idea/tests/GitCommitTest.kt +++ b/plugins/git4idea/tests/git4idea/tests/GitCommitTest.kt @@ -17,16 +17,12 @@ package git4idea.tests import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.vcs.Executor.* -import com.intellij.openapi.vcs.FileStatus import com.intellij.openapi.vcs.VcsException import com.intellij.openapi.vcs.changes.Change import com.intellij.util.containers.ContainerUtil import git4idea.GitUtil -import git4idea.changes.GitChangeUtils import git4idea.checkin.GitCheckinEnvironment -import git4idea.history.GitHistoryUtils import git4idea.test.* -import git4idea.test.createFileStructure import java.io.File import java.util.* @@ -68,12 +64,12 @@ class GitCommitTest : GitSingleRepoTest() { fun `test commit case rename`() { generateCaseRename("a.java", "A.java") - val changes = assertChanges() { + val changes = assertChanges { rename("a.java", "A.java") } commit(changes) assertNoChanges() - assertCommitted() { + myRepo.assertCommitted { rename("a.java", "A.java") } } @@ -83,7 +79,7 @@ class GitCommitTest : GitSingleRepoTest() { touch("s.java") git("add s.java") - val changes = assertChanges() { + val changes = assertChanges { rename("a.java", "A.java") added("s.java") } @@ -91,7 +87,7 @@ class GitCommitTest : GitSingleRepoTest() { commit(changes) assertNoChanges() - assertCommitted() { + myRepo.assertCommitted { rename("a.java", "A.java") added("s.java") } @@ -102,7 +98,7 @@ class GitCommitTest : GitSingleRepoTest() { generateCaseRename("a.java", "A.java") echo("m.java", "unstaged") - val changes = assertChanges() { + val changes = assertChanges { rename("a.java", "A.java") modified("m.java") } @@ -110,7 +106,7 @@ class GitCommitTest : GitSingleRepoTest() { commit(changes) assertNoChanges() - assertCommitted() { + myRepo.assertCommitted { rename("a.java", "A.java") modified("m.java") } @@ -121,17 +117,17 @@ class GitCommitTest : GitSingleRepoTest() { generateCaseRename("a.java", "A.java") echo("m.java", "unstaged") - val changes = assertChanges() { + val changes = assertChanges { rename("a.java", "A.java") modified("m.java") } commit(listOf(changes[0])) - assertChanges() { + assertChanges { modified("m.java") } - assertCommitted() { + myRepo.assertCommitted { rename("a.java", "A.java") } } @@ -142,20 +138,20 @@ class GitCommitTest : GitSingleRepoTest() { echo("s.java", "staged") git("add s.java") - val changes = assertChanges() { + val changes = assertChanges { rename("a.java", "A.java") modified("s.java") } commit(listOf(changes[0])) - assertCommitted() { + myRepo.assertCommitted { rename("a.java", "A.java") } - assertChanges() { + assertChanges { modified("s.java") } - assertStagedChanges() { + myRepo.assertStagedChanges { modified("s.java") } } @@ -166,20 +162,20 @@ class GitCommitTest : GitSingleRepoTest() { generateCaseRename("a.java", "A.java") git("mv before.txt after.txt") - val changes = assertChanges() { + val changes = assertChanges { rename("a.java", "A.java") rename("before.txt", "after.txt") } commit(listOf(changes[0])) - assertCommitted() { + myRepo.assertCommitted { rename("a.java", "A.java") } - assertChanges() { + assertChanges { rename("before.txt", "after.txt") } - assertStagedChanges() { + myRepo.assertStagedChanges { rename("before.txt", "after.txt") } } @@ -192,7 +188,7 @@ class GitCommitTest : GitSingleRepoTest() { echo("m.java", "unstaged") git("add s.java m.java") - val changes = assertChanges() { + val changes = assertChanges { rename("a.java", "A.java") modified("s.java") modified("m.java") @@ -200,14 +196,14 @@ class GitCommitTest : GitSingleRepoTest() { commit(listOf(changes[0], changes[2])) - assertCommitted() { + myRepo.assertCommitted { rename("a.java", "A.java") modified("m.java") } - assertChanges() { + assertChanges { modified("s.java") } - assertStagedChanges() { + myRepo.assertStagedChanges { modified("s.java") } } @@ -219,20 +215,20 @@ class GitCommitTest : GitSingleRepoTest() { git("add c.java") overwrite("c.java", "unstaged") - val changes = assertChanges() { + val changes = assertChanges { rename("a.java", "A.java") modified("c.java") } commit(listOf(changes[0])) - assertCommitted() { + myRepo.assertCommitted { rename("a.java", "A.java") } - assertChanges() { + assertChanges { modified("c.java") } - assertStagedChanges() { + myRepo.assertStagedChanges { modified("c.java") } // this is intentional data loss: it is a rare case, while restoring both staged and unstaged part is not so easy, @@ -258,13 +254,13 @@ class GitCommitTest : GitSingleRepoTest() { val additionalContent = "non-staged content" append("A.java", additionalContent) - val changes = assertChanges() { + val changes = assertChanges { rename("a.java", "A.java") } commit(changes) - assertCommitted() { + myRepo.assertCommitted { rename("a.java", "A.java") } assertNoChanges() @@ -312,65 +308,4 @@ class GitCommitTest : GitSingleRepoTest() { assertTrue(actualChanges.isEmpty()) return allChanges } - - private fun assertStagedChanges(changes: ChangesBuilder.() -> Unit) { - val cb = ChangesBuilder() - cb.changes() - - val actualChanges = GitChangeUtils.getStagedChanges(myProject, myProjectRoot) - for (change in cb.changes) { - val found = actualChanges.find(change.matcher) - assertNotNull("The change [$change] is not staged", found) - actualChanges.remove(found) - } - assertTrue(actualChanges.isEmpty()) - } - - private fun assertCommitted(changes: ChangesBuilder.() -> Unit) { - val cb = ChangesBuilder() - cb.changes() - - val actualChanges = GitHistoryUtils.history(myProject, myProjectRoot, "-1")[0].changes - for (change in cb.changes) { - val found = actualChanges.find(change.matcher) - assertNotNull("The change [$change] wasn't committed", found) - actualChanges.remove(found) - } - assertTrue(actualChanges.isEmpty()) - } - - class ChangesBuilder { - data class AChange(val type: FileStatus, val nameBefore: String?, val nameAfter: String, val matcher: (Change) -> Boolean) { - constructor(type: FileStatus, nameAfter: String, matcher: (Change) -> Boolean) : this(type, null, nameAfter, matcher) - - override fun toString(): String { - when (type) { - Change.Type.NEW -> return "A: $nameAfter" - Change.Type.DELETED -> return "D: $nameAfter" - Change.Type.MOVED -> return "M: $nameBefore -> $nameAfter" - else -> return "M: $nameAfter" - } - } - } - - val changes = linkedSetOf() - - fun added(name: String) { - assertTrue(changes.add(AChange(FileStatus.ADDED, name) { - it.fileStatus == FileStatus.ADDED && it.beforeRevision == null && it.afterRevision!!.file.name == name - })) - } - - fun modified(name:String) { - assertTrue(changes.add(AChange(FileStatus.MODIFIED, name) { - it.fileStatus == FileStatus.MODIFIED && it.beforeRevision!!.file.name == name && it.afterRevision!!.file.name == name - })) - } - - fun rename(from: String, to: String) { - assertTrue(changes.add(AChange(FileStatus.MODIFIED, from, to) { - it.isRenamed && from == it.beforeRevision!!.file.name && to == it.afterRevision!!.file.name - })) - } - } } diff --git a/plugins/github/src/org/jetbrains/plugins/github/GithubRebaseAction.java b/plugins/github/src/org/jetbrains/plugins/github/GithubRebaseAction.java index b34f513feca4..6269996c8013 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/GithubRebaseAction.java +++ b/plugins/github/src/org/jetbrains/plugins/github/GithubRebaseAction.java @@ -267,7 +267,7 @@ public class GithubRebaseAction extends DumbAwareAction { GithubNotifications.showInfo(project, "Success", "Successfully rebased GitHub fork"); } else { - GitUpdateResult result = rebaser.handleRebaseFailure(handler, root, rebaseConflictDetector, + GitUpdateResult result = rebaser.handleRebaseFailure(handler, root, rebaseResult, rebaseConflictDetector, untrackedFilesDetector, localChangesDetector); if (result == GitUpdateResult.NOTHING_TO_UPDATE || result == GitUpdateResult.SUCCESS || diff --git a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCredentialsPanel.java b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCredentialsPanel.java index 155ae8951dc9..0a845423da16 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCredentialsPanel.java +++ b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCredentialsPanel.java @@ -59,6 +59,7 @@ public class GithubCredentialsPanel extends JPanel { super(new BorderLayout()); add(myPane, BorderLayout.CENTER); + mySignupTextField.setEditorKit(UIUtil.getHTMLEditorKit()); mySignupTextField.setText("Do not have an account at github.com? Sign up"); mySignupTextField.setBackground(UIUtil.TRANSPARENT_COLOR); mySignupTextField.setCursor(new Cursor(Cursor.HAND_CURSOR)); diff --git a/plugins/github/src/org/jetbrains/plugins/github/util/GithubUtil.java b/plugins/github/src/org/jetbrains/plugins/github/util/GithubUtil.java index 40cd228358d7..889749c15874 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/util/GithubUtil.java +++ b/plugins/github/src/org/jetbrains/plugins/github/util/GithubUtil.java @@ -387,7 +387,7 @@ public class GithubUtil { } if (!version.isSupported()) { - GithubNotifications.showWarningDialog(project, GitBundle.message("find.git.unsupported.message", version.toString(), GitVersion.MIN), + GithubNotifications.showWarningDialog(project, GitBundle.message("find.git.unsupported.message", version.getPresentation(), GitVersion.MIN.getPresentation()), GitBundle.getString("find.git.success.title")); return false; } diff --git a/plugins/gradle/gradle.iml b/plugins/gradle/gradle.iml index c7bf3730a4e0..ae7fc73ffe04 100644 --- a/plugins/gradle/gradle.iml +++ b/plugins/gradle/gradle.iml @@ -23,7 +23,6 @@ - @@ -80,7 +79,6 @@ - diff --git a/plugins/gradle/src/META-INF/plugin.xml b/plugins/gradle/src/META-INF/plugin.xml index 79e64951d150..78d783dc8328 100644 --- a/plugins/gradle/src/META-INF/plugin.xml +++ b/plugins/gradle/src/META-INF/plugin.xml @@ -39,7 +39,6 @@ com.intellij.modules.lang org.intellij.groovy - JUnit org.jetbrains.idea.maven i18n.GradleBundle diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/compiler/GradleBuildProcessParametersProvider.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/compiler/GradleBuildProcessParametersProvider.java index 806d49203553..9ecd53f87cad 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/compiler/GradleBuildProcessParametersProvider.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/compiler/GradleBuildProcessParametersProvider.java @@ -52,8 +52,8 @@ public class GradleBuildProcessParametersProvider extends BuildProcessParameters @NotNull public List getClassPath() { List result = ContainerUtil.newArrayList(); - addGradleClassPath(result); if (!GradleSettings.getInstance(myProject).getLinkedProjectsSettings().isEmpty()) { + addGradleClassPath(result); addOtherClassPath(result); } return result; diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/build/GradleApplicationEnvironmentProvider.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/build/GradleApplicationEnvironmentProvider.java index ea76d65ed794..5fb7680aaf8b 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/build/GradleApplicationEnvironmentProvider.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/build/GradleApplicationEnvironmentProvider.java @@ -98,7 +98,9 @@ public class GradleApplicationEnvironmentProvider implements GradleExecutionEnvi StringBuilder parametersString = new StringBuilder(); for (String parameter : params.getProgramParametersList().getParameters()) { - parametersString.append("args '").append(parameter).append("'\n"); + if (StringUtil.isEmpty(parameter)) continue; + String escaped = StringUtil.escapeChars(parameter, '\\', '"', '\''); + parametersString.append("args '").append(escaped).append("'\n"); } StringBuilder vmParametersString = new StringBuilder(); diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/PatternGradleConfigurationProducer.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/PatternGradleConfigurationProducer.java index 5d051d3280e1..bd7d8390dbac 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/PatternGradleConfigurationProducer.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/PatternGradleConfigurationProducer.java @@ -119,6 +119,10 @@ public class PatternGradleConfigurationProducer extends GradleTestRunConfigurati return false; } + public boolean isMultipleElementsSelected(ConfigurationContext context) { + return myBaseConfigurationProducer.isMultipleElementsSelected(context); + } + private static class GradlePatternBasedConfigurationProducer extends AbstractPatternBasedConfigurationProducer { public GradlePatternBasedConfigurationProducer(ConfigurationType configurationType) { super(configurationType); diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/TestClassGradleConfigurationProducer.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/TestClassGradleConfigurationProducer.java index d3a1c737558a..fbb10ba1e75b 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/TestClassGradleConfigurationProducer.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/TestClassGradleConfigurationProducer.java @@ -23,7 +23,6 @@ import com.intellij.execution.actions.ConfigurationFromContext; import com.intellij.execution.actions.RunConfigurationProducer; import com.intellij.execution.junit.InheritorChooser; import com.intellij.execution.junit.JUnitUtil; -import com.intellij.execution.junit.PatternConfigurationProducer; import com.intellij.openapi.externalSystem.ExternalSystemModulePropertyManager; import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfiguration; import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil; @@ -60,7 +59,7 @@ public class TestClassGradleConfigurationProducer extends GradleTestRunConfigura final Location contextLocation = context.getLocation(); assert contextLocation != null; - if (RunConfigurationProducer.getInstance(PatternConfigurationProducer.class).isMultipleElementsSelected(context)) { + if (RunConfigurationProducer.getInstance(PatternGradleConfigurationProducer.class).isMultipleElementsSelected(context)) { return false; } PsiClass testClass = getPsiClassForLocation(contextLocation); @@ -108,7 +107,7 @@ public class TestClassGradleConfigurationProducer extends GradleTestRunConfigura final Location contextLocation = context.getLocation(); assert contextLocation != null; - if (RunConfigurationProducer.getInstance(PatternConfigurationProducer.class).isMultipleElementsSelected(context)) { + if (RunConfigurationProducer.getInstance(PatternGradleConfigurationProducer.class).isMultipleElementsSelected(context)) { return false; } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/TestMethodGradleConfigurationProducer.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/TestMethodGradleConfigurationProducer.java index 611fd14ef4e2..6e94a6dfe2cc 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/TestMethodGradleConfigurationProducer.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/TestMethodGradleConfigurationProducer.java @@ -21,7 +21,6 @@ import com.intellij.execution.actions.ConfigurationContext; import com.intellij.execution.actions.ConfigurationFromContext; import com.intellij.execution.actions.RunConfigurationProducer; import com.intellij.execution.junit.InheritorChooser; -import com.intellij.execution.junit.PatternConfigurationProducer; import com.intellij.openapi.externalSystem.ExternalSystemModulePropertyManager; import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfiguration; import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil; @@ -55,7 +54,7 @@ public class TestMethodGradleConfigurationProducer extends GradleTestRunConfigur protected boolean doSetupConfigurationFromContext(ExternalSystemRunConfiguration configuration, ConfigurationContext context, Ref sourceElement) { - if (RunConfigurationProducer.getInstance(PatternConfigurationProducer.class).isMultipleElementsSelected(context)) { + if (RunConfigurationProducer.getInstance(PatternGradleConfigurationProducer.class).isMultipleElementsSelected(context)) { return false; } final Location contextLocation = context.getLocation(); @@ -83,7 +82,7 @@ public class TestMethodGradleConfigurationProducer extends GradleTestRunConfigur @Override protected boolean doIsConfigurationFromContext(ExternalSystemRunConfiguration configuration, ConfigurationContext context) { - if (RunConfigurationProducer.getInstance(PatternConfigurationProducer.class).isMultipleElementsSelected(context)) { + if (RunConfigurationProducer.getInstance(PatternGradleConfigurationProducer.class).isMultipleElementsSelected(context)) { return false; } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/projectView/GradleTreeStructureProvider.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/projectView/GradleTreeStructureProvider.java index 1f5986ee8d32..f7e11ff72c6a 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/projectView/GradleTreeStructureProvider.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/projectView/GradleTreeStructureProvider.java @@ -23,6 +23,7 @@ import com.intellij.ide.projectView.impl.nodes.*; import com.intellij.ide.util.treeView.AbstractTreeNode; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleGrouperKt; +import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.ProjectFileIndex; @@ -48,7 +49,7 @@ import static com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil.*; * @author Vladislav.Soroka * @since 2/4/2016 */ -public class GradleTreeStructureProvider implements TreeStructureProvider { +public class GradleTreeStructureProvider implements TreeStructureProvider, DumbAware { @NotNull @Override public Collection modify(@NotNull AbstractTreeNode parent, diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/BaseGradleProjectResolverExtension.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/BaseGradleProjectResolverExtension.java index 8baaa9000fb7..1fd57fa8a8a2 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/BaseGradleProjectResolverExtension.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/BaseGradleProjectResolverExtension.java @@ -170,6 +170,7 @@ public class BaseGradleProjectResolverExtension implements GradleProjectResolver final ModuleData mainModuleData = mainModuleNode.getData(); final String mainModuleConfigPath = mainModuleData.getLinkedExternalProjectPath(); final String mainModuleFileDirectoryPath = mainModuleData.getModuleFileDirectoryPath(); + final String jdkName = getJdkName(gradleModule); ExternalProject externalProject = resolverCtx.getExtraProject(gradleModule, ExternalProject.class); if (resolverCtx.isResolveModulePerSourceSet() && externalProject != null) { @@ -200,6 +201,7 @@ public class BaseGradleProjectResolverExtension implements GradleProjectResolver sourceSetData.setSourceCompatibility(sourceSet.getSourceCompatibility()); sourceSetData.setTargetCompatibility(sourceSet.getTargetCompatibility()); + sourceSetData.setSdkName(jdkName); final Set artifacts = ContainerUtil.newTroveSet(FileUtil.FILE_HASHING_STRATEGY); if ("main".equals(sourceSet.getName())) { @@ -245,6 +247,7 @@ public class BaseGradleProjectResolverExtension implements GradleProjectResolver mainModuleData.setTargetCompatibility(languageSettings.getTargetBytecodeVersion().toString()); } } + mainModuleData.setSdkName(jdkName); } catch (UnsupportedMethodException ignore) { // org.gradle.tooling.model.idea.IdeaModule.getJavaLanguageSettings method supported since Gradle 2.11 @@ -260,6 +263,16 @@ public class BaseGradleProjectResolverExtension implements GradleProjectResolver return mainModuleNode; } + @Nullable + private static String getJdkName(@NotNull IdeaModule gradleModule) { + try { + return gradleModule.getJdkName(); + } + catch (UnsupportedMethodException e) { + return null; + } + } + @Override public void populateModuleExtraModels(@NotNull IdeaModule gradleModule, @NotNull DataNode ideModule) { final BuildScriptClasspathModel buildScriptClasspathModel = resolverCtx.getExtraProject(gradleModule, BuildScriptClasspathModel.class); @@ -698,7 +711,7 @@ public class BaseGradleProjectResolverExtension implements GradleProjectResolver " if (task instanceof JavaForkOptions && (" + names + ".contains(task.name) || " + names + ".contains(task.path))) {", " def jvmArgs = task.jvmArgs.findAll{!it?.startsWith('-agentlib:jdwp') && !it?.startsWith('-Xrunjdwp')}", " jvmArgs << '" + jvmAgentSetup.trim().replace("\\", "\\\\") + '\'', - " task.jvmArgs jvmArgs", + " task.jvmArgs = jvmArgs", " }" + "}", }; diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/ui/GradleProjectCompositeSelectorDialog.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/ui/GradleProjectCompositeSelectorDialog.java index 0c0d5dc19665..ee17a21c3e41 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/ui/GradleProjectCompositeSelectorDialog.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/ui/GradleProjectCompositeSelectorDialog.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -28,6 +28,7 @@ import com.intellij.ui.*; import com.intellij.ui.components.JBLabel; import com.intellij.util.Consumer; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.tree.TreeUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -81,7 +82,7 @@ public class GradleProjectCompositeSelectorDialog extends DialogWrapper { addExtraAction(new SelectAllButton()). addExtraAction(new UnselectAllButton()). setToolbarPosition(ActionToolbarPosition.BOTTOM). - setToolbarBorder(IdeBorderFactory.createEmptyBorder()); + setToolbarBorder(JBUI.Borders.empty()); contentPanel.add(decorator.createPanel()); return mainPanel; } diff --git a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/importing/GradleMiscImportingTest.java b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/importing/GradleMiscImportingTest.java index 41d70108b755..97fcab7f0700 100644 --- a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/importing/GradleMiscImportingTest.java +++ b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/importing/GradleMiscImportingTest.java @@ -16,12 +16,16 @@ package org.jetbrains.plugins.gradle.importing; import com.intellij.compiler.CompilerConfiguration; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.projectRoots.ProjectJdkTable; +import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.LanguageLevelModuleExtensionImpl; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.TestModuleProperties; import com.intellij.pom.java.LanguageLevel; +import org.jetbrains.plugins.gradle.tooling.annotation.TargetVersions; import org.junit.Test; import org.junit.runners.Parameterized; @@ -120,6 +124,31 @@ public class GradleMiscImportingTest extends GradleImportingTestCase { } + @Test + @TargetVersions("3.4+") + public void testJdkName() throws Exception { + Sdk myJdk = createJdk("MyJDK"); + edt(() -> ApplicationManager.getApplication().runWriteAction(() -> ProjectJdkTable.getInstance().addJdk(myJdk))); + try { + importProject( + "apply plugin: 'java'\n" + + "apply plugin: 'idea'\n" + + "idea {\n" + + " module {\n" + + " jdkName = 'MyJDK'\n" + + " }\n" + + "}\n" + ); + + assertModules("project", "project_main", "project_test"); + assertTrue(getSdkForModule("project_main") == myJdk); + assertTrue(getSdkForModule("project_test") == myJdk); + + } finally { + edt(() -> ApplicationManager.getApplication().runWriteAction(() -> ProjectJdkTable.getInstance().removeJdk(myJdk))); + } + } + @Test public void testUnloadedModuleImport() throws Exception { importProject( @@ -141,4 +170,8 @@ public class GradleMiscImportingTest extends GradleImportingTestCase { private String getBytecodeTargetLevel(String moduleName) { return CompilerConfiguration.getInstance(myProject).getBytecodeTargetLevel(getModule(moduleName)); } + + private Sdk getSdkForModule(final String moduleName) { + return ModuleRootManager.getInstance(getModule(moduleName)).getSdk(); + } } diff --git a/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/util/DependencyResolverImpl.groovy b/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/util/DependencyResolverImpl.groovy index 2af733b27af4..32ccf0ffc140 100644 --- a/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/util/DependencyResolverImpl.groovy +++ b/plugins/gradle/tooling-extension-impl/src/org/jetbrains/plugins/gradle/tooling/util/DependencyResolverImpl.groovy @@ -251,7 +251,8 @@ class DependencyResolverImpl implements DependencyResolver { } } else { - if (checkCompileOnlyDeps && !originCompileConfiguration.containsAll(resolvedObjArray)) { + if (checkCompileOnlyDeps && !originCompileConfiguration.containsAll(resolvedObjArray) && + !runtimeConfiguration.containsAll(resolvedObjArray)) { ((AbstractExternalDependency)it).scope = providedScope } } diff --git a/plugins/groovy/groovy-psi/resources/icons/groovy/gant_16x16.png b/plugins/groovy/groovy-psi/resources/icons/groovy/gant_16x16.png index 9ee1c271abfe..5779fd93e736 100644 Binary files a/plugins/groovy/groovy-psi/resources/icons/groovy/gant_16x16.png and b/plugins/groovy/groovy-psi/resources/icons/groovy/gant_16x16.png differ diff --git a/plugins/groovy/groovy-psi/resources/icons/groovy/gant_16x16@2x.png b/plugins/groovy/groovy-psi/resources/icons/groovy/gant_16x16@2x.png index ec2db7e4f905..609412d097d1 100644 Binary files a/plugins/groovy/groovy-psi/resources/icons/groovy/gant_16x16@2x.png and b/plugins/groovy/groovy-psi/resources/icons/groovy/gant_16x16@2x.png differ diff --git a/plugins/groovy/groovy-psi/resources/icons/groovy/gant_16x16@2x_dark.png b/plugins/groovy/groovy-psi/resources/icons/groovy/gant_16x16@2x_dark.png index 640e7618e05f..98299830df7b 100644 Binary files a/plugins/groovy/groovy-psi/resources/icons/groovy/gant_16x16@2x_dark.png and b/plugins/groovy/groovy-psi/resources/icons/groovy/gant_16x16@2x_dark.png differ diff --git a/plugins/groovy/groovy-psi/resources/icons/groovy/gant_16x16_dark.png b/plugins/groovy/groovy-psi/resources/icons/groovy/gant_16x16_dark.png index f1e721acd9ee..12713cc0a8f1 100644 Binary files a/plugins/groovy/groovy-psi/resources/icons/groovy/gant_16x16_dark.png and b/plugins/groovy/groovy-psi/resources/icons/groovy/gant_16x16_dark.png differ diff --git a/plugins/groovy/groovy-psi/resources/icons/groovy/gant_sdk.png b/plugins/groovy/groovy-psi/resources/icons/groovy/gant_sdk.png index ccc8331285b0..44a1285422f6 100644 Binary files a/plugins/groovy/groovy-psi/resources/icons/groovy/gant_sdk.png and b/plugins/groovy/groovy-psi/resources/icons/groovy/gant_sdk.png differ diff --git a/plugins/groovy/groovy-psi/resources/icons/groovy/gant_sdk@2x.png b/plugins/groovy/groovy-psi/resources/icons/groovy/gant_sdk@2x.png index ac394d363984..b3cffe9ebb5a 100644 Binary files a/plugins/groovy/groovy-psi/resources/icons/groovy/gant_sdk@2x.png and b/plugins/groovy/groovy-psi/resources/icons/groovy/gant_sdk@2x.png differ diff --git a/plugins/groovy/groovy-psi/resources/icons/groovy/gant_sdk@2x_dark.png b/plugins/groovy/groovy-psi/resources/icons/groovy/gant_sdk@2x_dark.png index 9995e25a444a..92c400ea7e81 100644 Binary files a/plugins/groovy/groovy-psi/resources/icons/groovy/gant_sdk@2x_dark.png and b/plugins/groovy/groovy-psi/resources/icons/groovy/gant_sdk@2x_dark.png differ diff --git a/plugins/groovy/groovy-psi/resources/icons/groovy/gant_sdk_dark.png b/plugins/groovy/groovy-psi/resources/icons/groovy/gant_sdk_dark.png index d7acf2df7c06..901e79a2c8e3 100644 Binary files a/plugins/groovy/groovy-psi/resources/icons/groovy/gant_sdk_dark.png and b/plugins/groovy/groovy-psi/resources/icons/groovy/gant_sdk_dark.png differ diff --git a/plugins/groovy/groovy-psi/resources/icons/groovy/groovyFile.png b/plugins/groovy/groovy-psi/resources/icons/groovy/groovyFile.png index b826cc752c7e..7bf2e31b746b 100644 Binary files a/plugins/groovy/groovy-psi/resources/icons/groovy/groovyFile.png and b/plugins/groovy/groovy-psi/resources/icons/groovy/groovyFile.png differ diff --git a/plugins/groovy/groovy-psi/resources/icons/groovy/groovyFile@2x.png b/plugins/groovy/groovy-psi/resources/icons/groovy/groovyFile@2x.png index dd60a9048d5e..48ab6705ae0d 100644 Binary files a/plugins/groovy/groovy-psi/resources/icons/groovy/groovyFile@2x.png and b/plugins/groovy/groovy-psi/resources/icons/groovy/groovyFile@2x.png differ diff --git a/plugins/groovy/groovy-psi/resources/icons/groovy/groovy_outsideSources.png b/plugins/groovy/groovy-psi/resources/icons/groovy/groovy_outsideSources.png index be18b9e86019..7d20ddfea138 100644 Binary files a/plugins/groovy/groovy-psi/resources/icons/groovy/groovy_outsideSources.png and b/plugins/groovy/groovy-psi/resources/icons/groovy/groovy_outsideSources.png differ diff --git a/plugins/groovy/groovy-psi/resources/icons/groovy/groovy_outsideSources@2x.png b/plugins/groovy/groovy-psi/resources/icons/groovy/groovy_outsideSources@2x.png index 199dc2b8d9e2..996561f4b35b 100644 Binary files a/plugins/groovy/groovy-psi/resources/icons/groovy/groovy_outsideSources@2x.png and b/plugins/groovy/groovy-psi/resources/icons/groovy/groovy_outsideSources@2x.png differ diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/parser/GrCodeBlockElementType.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/parser/GrCodeBlockElementType.java index 63e67a3a3023..3d41e0524e4a 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/parser/GrCodeBlockElementType.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/parser/GrCodeBlockElementType.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -17,10 +17,9 @@ package org.jetbrains.plugins.groovy.lang.parser; import com.intellij.lang.ASTNode; import com.intellij.lang.Language; -import com.intellij.lexer.Lexer; +import com.intellij.lang.PsiBuilderUtil; import com.intellij.openapi.project.Project; import com.intellij.psi.tree.ICompositeElementType; -import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.IErrorCounterReparseableElementType; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.groovy.GroovyLanguage; @@ -49,24 +48,8 @@ public abstract class GrCodeBlockElementType extends IErrorCounterReparseableEle @Override public int getErrorsCount(final CharSequence seq, Language fileLanguage, final Project project) { - final Lexer lexer = new GroovyLexer(); - - lexer.start(seq); - if (lexer.getTokenType() != GroovyTokenTypes.mLCURLY) return FATAL_ERROR; - lexer.advance(); - int balance = 1; - while (true) { - IElementType type = lexer.getTokenType(); - if (type == null) break; - if (balance == 0) return FATAL_ERROR; - if (type == GroovyTokenTypes.mLCURLY) { - balance++; - } - else if (type == GroovyTokenTypes.mRCURLY) { - balance--; - } - lexer.advance(); - } - return balance; + return PsiBuilderUtil.hasProperBraceBalance(seq, new GroovyLexer(), GroovyTokenTypes.mLCURLY, GroovyTokenTypes.mRCURLY) + ? NO_ERRORS + : FATAL_ERROR; } } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/api/statements/typedef/members/GrMethod.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/api/statements/typedef/members/GrMethod.java index 6322deeb25c5..ca906f4a606f 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/api/statements/typedef/members/GrMethod.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/api/statements/typedef/members/GrMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -29,6 +29,7 @@ import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierL import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrParametersOwner; import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrCodeBlock; import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrOpenBlock; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter; import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameterList; import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.GrTopStatement; import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement; @@ -88,4 +89,8 @@ public interface GrMethod extends GrMembersDeclaration, GrNamedElement, PsiMetho @NotNull GrReflectedMethod[] getReflectedMethods(); + + @NotNull + @Override + GrParameter[] getParameters(); } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/processors/SubstitutorComputer.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/processors/SubstitutorComputer.java index 5167aa634659..150ee826d0d8 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/processors/SubstitutorComputer.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/resolve/processors/SubstitutorComputer.java @@ -260,7 +260,7 @@ public class SubstitutorComputer { if (closureParameters.length != 1) return null; PsiSubstitutor substitutor = myHelper.inferTypeArguments( - samClassTypeParameters, new PsiType[]{samReturnType}, closureParameters, LanguageLevel.JDK_1_7 + samClassTypeParameters, new PsiType[]{samReturnType}, closureParameters, LanguageLevel.JDK_1_8 ); if (substitutor.getSubstitutionMap().isEmpty()) return null; diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/changeSignature/GrChangeSignatureDialog.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/changeSignature/GrChangeSignatureDialog.java index 3c07572c1736..1f664d37a174 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/changeSignature/GrChangeSignatureDialog.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/refactoring/changeSignature/GrChangeSignatureDialog.java @@ -33,12 +33,12 @@ import com.intellij.refactoring.ui.CodeFragmentTableCellRenderer; import com.intellij.refactoring.ui.JavaCodeFragmentTableCellEditor; import com.intellij.refactoring.ui.VisibilityPanelBase; import com.intellij.refactoring.util.CanonicalTypes; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.ToolbarDecorator; import com.intellij.ui.table.JBTable; import com.intellij.ui.treeStructure.Tree; import com.intellij.util.Consumer; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.GroovyFileType; @@ -139,7 +139,7 @@ public class GrChangeSignatureDialog extends ChangeSignatureDialogBase names = suggestNames(); - return new NameSuggestionsField(names.toArray(new String[names.size()]), myProject, GroovyFileType.GROOVY_FILE_TYPE); + return new NameSuggestionsField(ArrayUtil.toStringArray(names), myProject, GroovyFileType.GROOVY_FILE_TYPE); } @Override diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/dsl/GroovyDslTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/dsl/GroovyDslTest.groovy index 5ec5f42bd642..ae663223aa2f 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/dsl/GroovyDslTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/dsl/GroovyDslTest.groovy @@ -170,7 +170,7 @@ class Foo { public class MyCategory { public static void foo(String s) {} }""") - def foo = category.getMethods()[0] + def foo = TestUtils.getMethods(category)[0] addGdsl(""" contributor([:]){category 'MyCategory'}""") myFixture.renameElement foo, "bar", false, false diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrGenericsInferringTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrGenericsInferringTest.groovy index 5b61830a13cd..b656674c4e3c 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrGenericsInferringTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/highlighting/GrGenericsInferringTest.groovy @@ -15,7 +15,18 @@ */ package org.jetbrains.plugins.groovy.lang.highlighting +import com.intellij.testFramework.LightProjectDescriptor +import org.jetbrains.annotations.NotNull +import org.jetbrains.plugins.groovy.GroovyLightProjectDescriptor + class GrGenericsInferringTest extends GrHighlightingTestBase { + + @Override + @NotNull + protected LightProjectDescriptor getProjectDescriptor() { + return GroovyLightProjectDescriptor.GROOVY_2_3 + } + void testMapExplicit() { testHighlighting ''' import groovy.transform.CompileStatic @@ -319,4 +330,69 @@ class Foo { } ''' } + + void testClosureToSAM() { + testHighlighting '''\ +import groovy.transform.CompileStatic + +interface SAM { + Out run(In argument) } + +@CompileStatic +class SomeClass2 { + static String join(T item, SAM f) { + return "" + } + + static void method() { + join(new SomeClass2(), { it.toString() }) + } +} + +''' + } + + void testClosureToSAMWildcard() { + testHighlighting '''\ +import groovy.transform.CompileStatic + +interface SAM { + Out run(In argument) +} + +@CompileStatic +class SomeClass2 { + static String join(T item, SAM f) { + return "" + } + + static void method() { + join(new SomeClass2(), { it.toString() }) + } +} +''' + } + + void testClosureToSAMGenericWildcard() { + testHighlighting '''\ + import groovy.transform.CompileStatic + + interface SAM { + Out run(In argument) + } + + @CompileStatic + class SomeClass2 { + static String join(List item, SAM f) { + return "" + } + + static void method() { + def list = [new SomeClass2()] + join(list, { it.toString() }) + } + } + ''' + } +} \ No newline at end of file diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/util/TestUtils.java b/plugins/groovy/test/org/jetbrains/plugins/groovy/util/TestUtils.java index 5126d9f7cede..c2b23f7bdf2a 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/util/TestUtils.java +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/util/TestUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -277,4 +277,8 @@ public abstract class TestUtils { Assert.assertEquals(sb.toString(), Arrays.asList(expectedUnresolved), actualUnresolved); } + public static PsiMethod[] getMethods(PsiClass aClass) { + //workaround for IDEA-148973: Groovy static compilation fails to compile calls of overriding methods with covariant type in interfaces + return aClass.getMethods(); + } } diff --git a/plugins/hg4idea/src/META-INF/plugin.xml b/plugins/hg4idea/src/META-INF/plugin.xml index 829544158aae..8ef76262ce1f 100644 --- a/plugins/hg4idea/src/META-INF/plugin.xml +++ b/plugins/hg4idea/src/META-INF/plugin.xml @@ -1,9 +1,9 @@ hg4idea - hg4idea + Mercurial Integration Mercurial version control system. + Allows working with Mercurial version control system. The following features are available:
    • Dedicated page under the Version Control node in the Settings/Preferences dialog.
    • diff --git a/plugins/java-i18n/src/com/intellij/codeInspection/duplicateStringLiteral/DuplicateStringLiteralInspection.java b/plugins/java-i18n/src/com/intellij/codeInspection/duplicateStringLiteral/DuplicateStringLiteralInspection.java index 01f7a9858e04..d4ac331cd8e5 100644 --- a/plugins/java-i18n/src/com/intellij/codeInspection/duplicateStringLiteral/DuplicateStringLiteralInspection.java +++ b/plugins/java-i18n/src/com/intellij/codeInspection/duplicateStringLiteral/DuplicateStringLiteralInspection.java @@ -424,10 +424,15 @@ public class DuplicateStringLiteralInspection extends BaseLocalInspectionTool { } private class NavigateToOccurrencesFix extends LocalQuickFixOnPsiElement { - public NavigateToOccurrencesFix(PsiLiteralExpression representative) { + NavigateToOccurrencesFix(PsiLiteralExpression representative) { super(representative); } + @Override + public boolean startInWriteAction() { + return false; + } + @Override public void invoke(@NotNull Project project, @NotNull PsiFile file, @NotNull PsiElement startElement, @NotNull PsiElement endElement) { if (!(startElement instanceof PsiLiteralExpression)) return; diff --git a/plugins/junit/src/com/intellij/execution/junit/JUnitConfiguration.java b/plugins/junit/src/com/intellij/execution/junit/JUnitConfiguration.java index b798cee43c4f..19fc5d863c7d 100644 --- a/plugins/junit/src/com/intellij/execution/junit/JUnitConfiguration.java +++ b/plugins/junit/src/com/intellij/execution/junit/JUnitConfiguration.java @@ -42,8 +42,11 @@ import com.intellij.openapi.util.WriteExternalException; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; +import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.ClassUtil; +import com.intellij.psi.util.TypeConversionUtil; import com.intellij.refactoring.listeners.RefactoringElementListener; +import com.intellij.rt.execution.junit.JUnitStarter; import com.intellij.rt.execution.junit.RepeatCount; import org.jdom.Element; import org.jetbrains.annotations.NonNls; @@ -520,6 +523,34 @@ public class JUnitConfiguration extends JavaTestConfigurationBase { return "j"; } + public String getPreferredRunner(final GlobalSearchScope globalSearchScope) { + Data data = getPersistentData(); + Project project = getProject(); + boolean isMethodConfiguration = TEST_METHOD.equals(data.TEST_OBJECT); + boolean isClassConfiguration = TEST_CLASS.equals(data.TEST_OBJECT); + final PsiClass psiClass = isMethodConfiguration || isClassConfiguration + ? JavaExecutionUtil.findMainClass(project, data.getMainClassName(), globalSearchScope) : null; + if (psiClass != null) { + if (JUnitUtil.isJUnit5TestClass(psiClass, false)) { + return JUnitStarter.JUNIT5_PARAMETER; + } + + if (isClassConfiguration || JUnitUtil.isJUnit4TestClass(psiClass)) { + return JUnitStarter.JUNIT4_PARAMETER; + } + + final String methodName = data.getMethodName(); + final PsiMethod[] methods = psiClass.findMethodsByName(methodName, true); + for (PsiMethod method : methods) { + if (JUnitUtil.isTestAnnotated(method)) { + return JUnitStarter.JUNIT4_PARAMETER; + } + } + return JUnitStarter.JUNIT3_PARAMETER; + } + return JUnitUtil.isJUnit5(globalSearchScope, project) ? JUnitStarter.JUNIT5_PARAMETER : null; + } + public static class Data implements Cloneable { public String PACKAGE_NAME; public String MAIN_CLASS_NAME; @@ -630,8 +661,11 @@ public class JUnitConfiguration extends JavaTestConfigurationBase { public static String getMethodPresentation(PsiMethod method) { if (method.getParameterList().getParametersCount() > 0 && MetaAnnotationUtil.isMetaAnnotated(method, JUnitUtil.TEST5_ANNOTATIONS)) { - return method.getName() + "(" + StringUtil.join(method.getParameterList().getParameters(), - param -> param.getType().accept(createSignatureVisitor()), + return method.getName() + "(" + StringUtil.join(method.getParameterList().getParameters(), + param -> { + PsiType type = TypeConversionUtil.erasure(param.getType()); + return type != null ? type.accept(createSignatureVisitor()) : ""; + }, ",") + ")"; } else { diff --git a/plugins/junit/src/com/intellij/execution/junit/TestClass.java b/plugins/junit/src/com/intellij/execution/junit/TestClass.java index 162690a34c44..bebb52345919 100644 --- a/plugins/junit/src/com/intellij/execution/junit/TestClass.java +++ b/plugins/junit/src/com/intellij/execution/junit/TestClass.java @@ -24,14 +24,10 @@ import com.intellij.execution.configurations.JavaRunConfigurationModule; import com.intellij.execution.configurations.RuntimeConfigurationException; import com.intellij.execution.configurations.RuntimeConfigurationWarning; import com.intellij.execution.runners.ExecutionEnvironment; -import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.psi.*; -import com.intellij.psi.search.GlobalSearchScope; import com.intellij.refactoring.listeners.RefactoringElementListener; -import com.intellij.rt.execution.junit.JUnitStarter; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; class TestClass extends TestObject { public TestClass(JUnitConfiguration configuration, ExecutionEnvironment environment) { @@ -46,20 +42,6 @@ class TestClass extends TestObject { return javaParameters; } - @Nullable - @Override - protected String getPreferredRunner(GlobalSearchScope globalSearchScope) { - Project project = getConfiguration().getProject(); - final PsiClass psiClass = JavaExecutionUtil.findMainClass(project, getConfiguration().getPersistentData().getMainClassName(), globalSearchScope); - if (psiClass != null) { - if (JUnitUtil.isJUnit5TestClass(psiClass, false)) { - return JUnitStarter.JUNIT5_PARAMETER; - } - return JUnitStarter.JUNIT4_PARAMETER; - } - return null; - } - @NotNull @Override protected String getForkMode() { diff --git a/plugins/junit/src/com/intellij/execution/junit/TestMethod.java b/plugins/junit/src/com/intellij/execution/junit/TestMethod.java index e6ced239d694..0eebfe995291 100644 --- a/plugins/junit/src/com/intellij/execution/junit/TestMethod.java +++ b/plugins/junit/src/com/intellij/execution/junit/TestMethod.java @@ -20,16 +20,12 @@ import com.intellij.codeInsight.AnnotationUtil; import com.intellij.execution.*; import com.intellij.execution.configurations.*; import com.intellij.execution.runners.ExecutionEnvironment; -import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.psi.*; -import com.intellij.psi.search.GlobalSearchScope; import com.intellij.refactoring.listeners.RefactoringElementAdapter; import com.intellij.refactoring.listeners.RefactoringElementListener; import com.intellij.refactoring.listeners.UndoRefactoringElementListener; -import com.intellij.rt.execution.junit.JUnitStarter; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; class TestMethod extends TestObject { public TestMethod(JUnitConfiguration configuration, ExecutionEnvironment environment) { @@ -48,32 +44,6 @@ class TestMethod extends TestObject { return super.createJavaParameters(); } - @Nullable - @Override - protected String getPreferredRunner(GlobalSearchScope globalSearchScope) { - Project project = getConfiguration().getProject(); - JUnitConfiguration.Data data = getConfiguration().getPersistentData(); - final PsiClass psiClass = JavaExecutionUtil.findMainClass(project, data.getMainClassName(), globalSearchScope); - if (psiClass == null) return null; - - if (JUnitUtil.isJUnit5TestClass(psiClass, false)) { - return JUnitStarter.JUNIT5_PARAMETER; - } - - if (JUnitUtil.isJUnit4TestClass(psiClass)) { - return JUnitStarter.JUNIT4_PARAMETER; - } - - final String methodName = data.getMethodName(); - final PsiMethod[] methods = psiClass.findMethodsByName(methodName, true); - for (PsiMethod method : methods) { - if (JUnitUtil.isTestAnnotated(method)) { - return JUnitStarter.JUNIT4_PARAMETER; - } - } - return JUnitStarter.JUNIT3_PARAMETER; - } - @Override public String suggestActionName() { return ProgramRunnerUtil.shortenName(getConfiguration().getPersistentData().METHOD_NAME, 2) + "()"; diff --git a/plugins/junit/src/com/intellij/execution/junit/TestMethods.java b/plugins/junit/src/com/intellij/execution/junit/TestMethods.java index 520908977ade..0f1bb8d5d86e 100644 --- a/plugins/junit/src/com/intellij/execution/junit/TestMethods.java +++ b/plugins/junit/src/com/intellij/execution/junit/TestMethods.java @@ -35,7 +35,6 @@ import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiMethod; import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.rt.execution.junit.JUnitStarter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -54,12 +53,6 @@ public class TestMethods extends TestMethod { myFailedTests = failedTests; } - @Nullable - @Override - protected String getPreferredRunner(GlobalSearchScope globalSearchScope) { - return JUnitUtil.isJUnit5(globalSearchScope, getConfiguration().getProject()) ? JUnitStarter.JUNIT5_PARAMETER : null; - } - @Override protected JavaParameters createJavaParameters() throws ExecutionException { final JavaParameters javaParameters = super.createDefaultJavaParameters(); diff --git a/plugins/junit/src/com/intellij/execution/junit/TestObject.java b/plugins/junit/src/com/intellij/execution/junit/TestObject.java index 7ccd869f33e6..fdace57fba90 100644 --- a/plugins/junit/src/com/intellij/execution/junit/TestObject.java +++ b/plugins/junit/src/com/intellij/execution/junit/TestObject.java @@ -169,7 +169,7 @@ public abstract class TestObject extends JavaTestFrameworkRunnableState { //junit 3 if (testCaseClass.isAssignableFrom(aClass)) { - return true; + return hasSingleConstructor(aClass); } else { //annotation @@ -248,7 +249,7 @@ public class TestPackage extends TestObject { for (Method method : aClass.getMethods()) { if (Modifier.isStatic(method.getModifiers()) && "suite".equals(method.getName()) || method.isAnnotationPresent(testClass)) { - return true; + return hasSingleConstructor(aClass); } } } @@ -261,4 +262,9 @@ public class TestPackage extends TestObject { return aClass -> false; } } + + private static boolean hasSingleConstructor(Class aClass) { + Constructor[] constructors = aClass.getConstructors(); + return constructors.length == 1 && constructors[0].getParameterTypes().length == 0; + } } diff --git a/plugins/junit/src/com/intellij/execution/junit/codeInsight/JUnit5MalformedRepeatedTestInspection.kt b/plugins/junit/src/com/intellij/execution/junit/codeInsight/JUnit5MalformedRepeatedTestInspection.kt index a1f34f293e80..66885f5850d7 100644 --- a/plugins/junit/src/com/intellij/execution/junit/codeInsight/JUnit5MalformedRepeatedTestInspection.kt +++ b/plugins/junit/src/com/intellij/execution/junit/codeInsight/JUnit5MalformedRepeatedTestInspection.kt @@ -31,6 +31,11 @@ import com.siyeh.ig.psiutils.ExpressionUtils import org.jetbrains.annotations.Nls class JUnit5MalformedRepeatedTestInspection : BaseJavaBatchLocalInspectionTool() { + object Annotations { + val NON_REPEATED_ANNOTATIONS = listOf(JUnitUtil.TEST5_ANNOTATION, + JUnitUtil.TEST5_FACTORY_ANNOTATION, + JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PARAMETERIZED_TEST) + } @Nls override fun getDisplayName(): String { @@ -66,9 +71,9 @@ class JUnit5MalformedRepeatedTestInspection : BaseJavaBatchLocalInspectionTool() else { val repetitionInfo = JavaPsiFacade.getInstance(holder.project).findClass(JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_REPETITION_INFO, file.resolveScope) val repetitionType = JavaPsiFacade.getElementFactory(holder.project).createType(repetitionInfo!!) - val repetitionInfoParam = method.parameterList.parameters.find { it.type.isAssignableFrom(repetitionType) } + val repetitionInfoParam = method.parameterList.parameters.find { it.type == repetitionType } if (repetitionInfoParam != null) { - if (MetaAnnotationUtil.isMetaAnnotated(method, JUnitUtil.TEST5_JUPITER_ANNOTATIONS)) { + if (MetaAnnotationUtil.isMetaAnnotated(method, Annotations.NON_REPEATED_ANNOTATIONS)) { holder.registerProblem(repetitionInfoParam.nameIdentifier ?: repetitionInfoParam, "RepetitionInfo is injected for @RepeatedTest only") } else { diff --git a/plugins/junit/src/com/intellij/execution/junit2/ui/properties/JUnitConsoleProperties.java b/plugins/junit/src/com/intellij/execution/junit2/ui/properties/JUnitConsoleProperties.java index a5f1bb411fe0..d2f5d85aec63 100644 --- a/plugins/junit/src/com/intellij/execution/junit2/ui/properties/JUnitConsoleProperties.java +++ b/plugins/junit/src/com/intellij/execution/junit2/ui/properties/JUnitConsoleProperties.java @@ -17,6 +17,7 @@ package com.intellij.execution.junit2.ui.properties; import com.intellij.execution.Executor; import com.intellij.execution.junit.JUnitConfiguration; +import com.intellij.execution.junit.TestObject; import com.intellij.execution.junit2.ui.actions.RerunFailedTestsAction; import com.intellij.execution.testframework.JavaAwareTestConsoleProperties; import com.intellij.execution.testframework.JavaTestLocator; @@ -27,6 +28,7 @@ import com.intellij.execution.testframework.sm.runner.SMTestLocator; import com.intellij.execution.ui.ConsoleView; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.rt.execution.junit.JUnitStarter; import com.intellij.rt.execution.junit.RepeatCount; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -38,6 +40,7 @@ public class JUnitConsoleProperties extends JavaAwareTestConsoleProperties stream() { return null; } @@ -117,6 +131,10 @@ class MethodSourceMalformed { "d"}) void testWithParams(Object s) { } + @ParameterizedTest + @MethodSource(value = {"unknown"}) + void unknownMethodSource(String s, int i) { } + String[] a() { return new String[]{"a", "b"}; } @@ -133,7 +151,4 @@ class MethodSourceMalformed { return new String[]{"a", "b"}; } - @ParameterizedTest - @MethodSource(value = {"unknown"}) - void unknownMethodSource(String s, int i) { } } \ No newline at end of file diff --git a/plugins/junit/testData/codeInsight/malformedRepeated/Malformed.java b/plugins/junit/testData/codeInsight/malformedRepeated/Malformed.java index 7a75fe770d4f..a25a3747841c 100644 --- a/plugins/junit/testData/codeInsight/malformedRepeated/Malformed.java +++ b/plugins/junit/testData/codeInsight/malformedRepeated/Malformed.java @@ -22,7 +22,7 @@ class WithRepeated { void beforeAllWithRepititionInfo(RepetitionInfo repetitionInfo) {} @BeforeEach - void config(RepetitionInfo testInfo) { } + void config(RepetitionInfo repetitionInfo) { } } class WithRepeatedAndTests { @@ -30,3 +30,34 @@ class WithRepeatedAndTests { @RepeatedTest(1) void repeatedTestAndTest() { } } + +class WithParameterized { + @ParameterizedTest + void testaccidentalRepetitionInfo(Object s, RepetitionInfo repetitionInfo) { } +} +class WithRepeatedAndCustomNames { + @RepeatedTest(value = 1, name = "{displayName} {currentRepetition}/{totalRepetitions}") + void repeatedTestWithCustomName() { } +} + +class WithRepeatedAndTestInfo { + @BeforeEach + void beforeEach(TestInfo testInfo, RepetitionInfo repetitionInfo) {} + + @RepeatedTest(1) + void repeatedTestWithTestInfo(TestInfo testInfo) { } + + @AfterEach + void afterEach(TestInfo testInfo, RepetitionInfo repetitionInfo) {} +} + +class WithRepeatedAndTestReporter { + @BeforeEach + void beforeEach(TestReporter testReporter, RepetitionInfo repetitionInfo) {} + + @RepeatedTest(1) + void repeatedTestWithTestInfo(TestReporter testReporter) { } + + @AfterEach + void afterEach(TestReporter testReporter, RepetitionInfo repetitionInfo) {} +} \ No newline at end of file diff --git a/plugins/junit/testData/codeInsight/malformedRepeated/PositiveRepetitions.java b/plugins/junit/testData/codeInsight/malformedRepeated/PositiveRepetitions.java index 478cf08a5ee5..4e3307df6355 100644 --- a/plugins/junit/testData/codeInsight/malformedRepeated/PositiveRepetitions.java +++ b/plugins/junit/testData/codeInsight/malformedRepeated/PositiveRepetitions.java @@ -2,5 +2,8 @@ import org.junit.jupiter.api.*; class WithRepeated { @RepeatedTest(-1) - void repeatedTestNoParams() { } + void repeatedTestNegative() { } + + @RepeatedTest(0) + void repeatedTestBoundaryZerro() { } } diff --git a/plugins/junit5_rt/src/com/intellij/junit5/JUnit5IdeaTestRunner.java b/plugins/junit5_rt/src/com/intellij/junit5/JUnit5IdeaTestRunner.java index 943fbbfbd264..a3da0467a6e0 100644 --- a/plugins/junit5_rt/src/com/intellij/junit5/JUnit5IdeaTestRunner.java +++ b/plugins/junit5_rt/src/com/intellij/junit5/JUnit5IdeaTestRunner.java @@ -37,7 +37,6 @@ public class JUnit5IdeaTestRunner implements IdeaTestRunner { myListeners = listeners; myListener = new JUnit5TestExecutionListener(); myLauncher = LauncherFactory.create(); - myLauncher.registerTestExecutionListeners(myListener); } @Override @@ -47,9 +46,11 @@ public class JUnit5IdeaTestRunner implements IdeaTestRunner { final String[] packageNameRef = new String[1]; final LauncherDiscoveryRequest discoveryRequest = JUnit5TestRunnerUtil.buildRequest(args, packageNameRef); myTestPlan = myLauncher.discover(discoveryRequest); + List listeners = new ArrayList<>(); + listeners.add(myListener); for (Object listenerClassName : myListeners) { final IDEAJUnitListener junitListener = (IDEAJUnitListener)Class.forName((String)listenerClassName).newInstance(); - myLauncher.registerTestExecutionListeners(new MyCustomListenerWrapper(junitListener)); + listeners.add(new MyCustomListenerWrapper(junitListener)); } if (sendTree) { do { @@ -58,7 +59,7 @@ public class JUnit5IdeaTestRunner implements IdeaTestRunner { while (--count > 0); } - myLauncher.execute(discoveryRequest); + myLauncher.execute(discoveryRequest, listeners.toArray(new TestExecutionListener[0])); return myListener.wasSuccessful() ? 0 : -1; } diff --git a/plugins/junit5_rt/src/com/intellij/junit5/JUnit5TestExecutionListener.java b/plugins/junit5_rt/src/com/intellij/junit5/JUnit5TestExecutionListener.java index b9ff3710e9c6..8fc95d30de67 100644 --- a/plugins/junit5_rt/src/com/intellij/junit5/JUnit5TestExecutionListener.java +++ b/plugins/junit5_rt/src/com/intellij/junit5/JUnit5TestExecutionListener.java @@ -38,6 +38,7 @@ import java.io.PrintStream; import java.io.PrintWriter; import java.io.StringWriter; import java.util.*; +import java.util.stream.Collectors; public class JUnit5TestExecutionListener implements TestExecutionListener { private static final String NO_LOCATION_HINT = ""; @@ -47,7 +48,7 @@ public class JUnit5TestExecutionListener implements TestExecutionListener { private long myCurrentTestStart; private int myFinishCount; private String myRootName; - private Set myRoots = new HashSet<>(); + private Set myRoots = new HashSet<>(); private boolean mySuccessful; public JUnit5TestExecutionListener() { @@ -110,9 +111,9 @@ public class JUnit5TestExecutionListener implements TestExecutionListener { testStarted(testIdentifier); myCurrentTestStart = System.currentTimeMillis(); } - else if (!myRoots.contains(testIdentifier)){ + else if (!myRoots.contains(testIdentifier.getUniqueId())){ myFinishCount = 0; - myPrintStream.println("##teamcity[testSuiteStarted" + idAndName(testIdentifier) + "]"); + myPrintStream.println("##teamcity[testSuiteStarted" + idAndName(testIdentifier) + getLocationHint(testIdentifier) + "]"); } } @@ -145,7 +146,7 @@ public class JUnit5TestExecutionListener implements TestExecutionListener { testFinished(testIdentifier, duration); myFinishCount++; } - else if (!myRoots.contains(testIdentifier)){ + else if (!myRoots.contains(testIdentifier.getUniqueId())){ String messageName = null; if (status == TestExecutionResult.Status.FAILED) { messageName = MapSerializerUtil.TEST_FAILED; @@ -155,9 +156,12 @@ public class JUnit5TestExecutionListener implements TestExecutionListener { } if (messageName != null) { if (status == TestExecutionResult.Status.FAILED) { - myPrintStream.println("\n##teamcity[testStarted name=\'" + JUnit4TestListener.CLASS_CONFIGURATION + "\' " + getLocationHint(testIdentifier) + "]"); - testFailure(JUnit4TestListener.CLASS_CONFIGURATION, JUnit4TestListener.CLASS_CONFIGURATION, messageName, throwableOptional, 0, reason, true); - myPrintStream.println("\n##teamcity[testFinished name=\'" + JUnit4TestListener.CLASS_CONFIGURATION + "\']"); + String parentId = getParentId(testIdentifier); + String nameAndId = " name=\'" + JUnit4TestListener.CLASS_CONFIGURATION + + "\' nodeId=\'" + escapeName(testIdentifier.getUniqueId()) + + "\' parentNodeId=\'" + parentId + "\' "; + testFailure(JUnit4TestListener.CLASS_CONFIGURATION, testIdentifier.getUniqueId(), parentId, messageName, throwableOptional, 0, reason, true); + myPrintStream.println("\n##teamcity[testFinished" + nameAndId + "]"); } final Set descendants = myTestPlan != null ? myTestPlan.getDescendants(testIdentifier) : Collections.emptySet(); @@ -192,11 +196,12 @@ public class JUnit5TestExecutionListener implements TestExecutionListener { long duration, String reason, boolean includeThrowable) { - testFailure(testIdentifier.getDisplayName(), testIdentifier.getUniqueId(), messageName, ex, duration, reason, includeThrowable); + testFailure(testIdentifier.getDisplayName(), testIdentifier.getUniqueId(), getParentId(testIdentifier), messageName, ex, duration, reason, includeThrowable); } private void testFailure(String methodName, String id, + String parentId, String messageName, Throwable ex, long duration, @@ -205,6 +210,8 @@ public class JUnit5TestExecutionListener implements TestExecutionListener { final Map attrs = new LinkedHashMap<>(); attrs.put("name", methodName); attrs.put("id", id); + attrs.put("nodeId", id); + attrs.put("parentNodeId", parentId); if (duration > 0) { attrs.put("duration", Long.toString(duration)); } @@ -216,7 +223,7 @@ public class JUnit5TestExecutionListener implements TestExecutionListener { ComparisonFailureData failureData = null; if (ex instanceof MultipleFailuresError && ((MultipleFailuresError)ex).hasFailures()) { for (Throwable assertionError : ((MultipleFailuresError)ex).getFailures()) { - testFailure(methodName, id, messageName, assertionError, duration, reason, false); + testFailure(methodName, id, parentId, messageName, assertionError, duration, reason, false); } } else if (ex instanceof AssertionFailedError && ((AssertionFailedError)ex).isActualDefined() && ((AssertionFailedError)ex).isExpectedDefined()) { @@ -256,8 +263,9 @@ public class JUnit5TestExecutionListener implements TestExecutionListener { public void sendTree(TestPlan testPlan, String rootName) { myTestPlan = testPlan; myRootName = rootName; - myRoots = testPlan.getRoots(); - for (TestIdentifier root : myRoots) { + Set roots = testPlan.getRoots(); + myRoots = roots.stream().map(identifier -> identifier.getUniqueId()).collect(Collectors.toSet()); + for (TestIdentifier root : roots) { assert root.isContainer(); for (TestIdentifier testIdentifier : testPlan.getChildren(root)) { sendTreeUnderRoot(testPlan, testIdentifier, new HashSet<>()); @@ -287,28 +295,39 @@ public class JUnit5TestExecutionListener implements TestExecutionListener { } } - private static String idAndName(TestIdentifier testIdentifier) { + private String idAndName(TestIdentifier testIdentifier) { return idAndName(testIdentifier, testIdentifier.getDisplayName()); } - private static String idAndName(TestIdentifier testIdentifier, String displayName) { - return " id=\'" + escapeName(testIdentifier.getUniqueId()) + "\' name=\'" + escapeName(displayName) + "\'"; + private String idAndName(TestIdentifier testIdentifier, String displayName) { + return " id=\'" + escapeName(testIdentifier.getUniqueId()) + + "\' name=\'" + escapeName(displayName) + + "\' nodeId=\'" + escapeName(testIdentifier.getUniqueId()) + + "\' parentNodeId=\'" + escapeName(getParentId(testIdentifier)) + "\'"; + } + + private String getParentId(TestIdentifier testIdentifier) { + String parentId = testIdentifier.getParentId().orElse("0"); + if (myRoots.contains(parentId)) { + parentId = "0"; + } + return parentId; } static String getLocationHint(TestIdentifier root) { return root.getSource() - .map(testSource -> getLocationHintValue(testSource, root.isTest())) + .map(testSource -> getLocationHintValue(testSource)) .filter(maybeLocationHintValue -> !NO_LOCATION_HINT_VALUE.equals(maybeLocationHintValue)) .map(locationHintValue -> "locationHint=\'" + locationHintValue + "\'") .orElse(NO_LOCATION_HINT); } - static String getLocationHintValue(TestSource testSource, boolean isTest) { + static String getLocationHintValue(TestSource testSource) { if (testSource instanceof CompositeTestSource) { CompositeTestSource compositeTestSource = ((CompositeTestSource)testSource); for (TestSource sourceFromComposite : compositeTestSource.getSources()) { - String locationHintValue = getLocationHintValue(sourceFromComposite, isTest); + String locationHintValue = getLocationHintValue(sourceFromComposite); if (!NO_LOCATION_HINT_VALUE.equals(locationHintValue)) { return locationHintValue; } @@ -327,12 +346,12 @@ public class JUnit5TestExecutionListener implements TestExecutionListener { if (testSource instanceof MethodSource) { MethodSource methodSource = (MethodSource)testSource; - return javaLocation(methodSource.getClassName(), methodSource.getMethodName(), isTest); + return javaLocation(methodSource.getClassName(), methodSource.getMethodName(), true); } if (testSource instanceof ClassSource) { String className = ((ClassSource)testSource).getClassName(); - return javaLocation(className, null, isTest); + return javaLocation(className, null, false); } return NO_LOCATION_HINT_VALUE; diff --git a/plugins/junit5_rt_tests/test/com/intellij/junit5/JUnit5AcceptanceTest.java b/plugins/junit5_rt_tests/test/com/intellij/junit5/JUnit5AcceptanceTest.java index eca0e5090761..4bb95694ab9f 100644 --- a/plugins/junit5_rt_tests/test/com/intellij/junit5/JUnit5AcceptanceTest.java +++ b/plugins/junit5_rt_tests/test/com/intellij/junit5/JUnit5AcceptanceTest.java @@ -77,15 +77,17 @@ class JUnit5AcceptanceTest extends JUnit5CodeInsightTest { @Test void methodPresentations() { doTest(() -> { - PsiClass aClass = myFixture.addClass("class MyTest {" + + myFixture.addClass("package a; public class TestInfo {}"); + PsiClass aClass = myFixture.addClass("class MyTest {" + " @org.junit.jupiter.api.Test void method() {}" + " @org.junit.jupiter.api.Test void method(a.TestInfo info) {}" + + " @org.junit.jupiter.api.Test void method(T info) {}" + " @org.junit.Test void method1() {}" + " @org.junit.Test void method1(a.TestInfo info) {}" + "}"); assertNotNull(aClass); - Stream expectedData = Arrays.stream(new String[]{"method", "method(a.TestInfo)", "method1", "method1"}); + Stream expectedData = Arrays.stream(new String[]{"method", "method(a.TestInfo)", "method(a.TestInfo)", "method1", "method1"}); StreamEx.of(aClass.getMethods()) .zipWith(expectedData) .forEach(e -> assertEquals(e.getValue(), JUnitConfiguration.Data.getMethodPresentation(e.getKey()))); diff --git a/plugins/junit5_rt_tests/test/com/intellij/junit5/JUnit5EventsTest.java b/plugins/junit5_rt_tests/test/com/intellij/junit5/JUnit5EventsTest.java index f932e5c95427..63de75cef5f0 100644 --- a/plugins/junit5_rt_tests/test/com/intellij/junit5/JUnit5EventsTest.java +++ b/plugins/junit5_rt_tests/test/com/intellij/junit5/JUnit5EventsTest.java @@ -83,15 +83,15 @@ class JUnit5EventsTest { String lineSeparator = MapSerializerUtil.escapeStr(System.getProperty("line.separator"), MapSerializerUtil.STD_ESCAPER); Assertions.assertEquals("##teamcity[enteredTheMatrix]\n" + "\n" + - "##teamcity[testStarted id='|[engine:engine|]' name='test1()' locationHint='java:test://com.intellij.junit5.JUnit5EventsTest$TestClass.test1']\n" + + "##teamcity[testStarted id='|[engine:engine|]' name='test1()' nodeId='|[engine:engine|]' parentNodeId='0' locationHint='java:test://com.intellij.junit5.JUnit5EventsTest$TestClass.test1']\n" + "\n" + - "##teamcity[testFailed name='test1()' id='|[engine:engine|]' details='' message='' expected='expected1' actual='actual1']\n" + + "##teamcity[testFailed name='test1()' id='|[engine:engine|]' nodeId='|[engine:engine|]' parentNodeId='0' details='' message='' expected='expected1' actual='actual1']\n" + "\n" + - "##teamcity[testFailed name='test1()' id='|[engine:engine|]' details='' message='' expected='expected2' actual='actual2']\n" + + "##teamcity[testFailed name='test1()' id='|[engine:engine|]' nodeId='|[engine:engine|]' parentNodeId='0' details='' message='' expected='expected2' actual='actual2']\n" + "\n" + - "##teamcity[testFailed name='test1()' id='|[engine:engine|]' details='TRACE' message='2 errors (2 failures)" + lineSeparator + "\tmessage1" + lineSeparator + "\tmessage2']\n" + + "##teamcity[testFailed name='test1()' id='|[engine:engine|]' nodeId='|[engine:engine|]' parentNodeId='0' details='TRACE' message='2 errors (2 failures)|r|n\tmessage1|r|n\tmessage2']\n" + "\n" + - "##teamcity[testFinished id='|[engine:engine|]' name='test1()']\n", StringUtil.convertLineSeparators(myBuf.toString())); + "##teamcity[testFinished id='|[engine:engine|]' name='test1()' nodeId='|[engine:engine|]' parentNodeId='0']\n", StringUtil.convertLineSeparators(myBuf.toString())); } @Test @@ -107,14 +107,14 @@ class JUnit5EventsTest { Assertions.assertEquals("##teamcity[enteredTheMatrix]\n" + "##teamcity[treeEnded]\n" + - "##teamcity[testSuiteStarted id='|[engine:engine1|]' name='brokenStream()']\n" + + "##teamcity[testSuiteStarted id='|[engine:engine1|]' name='brokenStream()' nodeId='|[engine:engine1|]' parentNodeId='0'locationHint='java:test://com.intellij.junit5.JUnit5EventsTest$TestClass.brokenStream']\n" + "\n" + - "##teamcity[testStarted name='Class Configuration' locationHint='java:suite://com.intellij.junit5.JUnit5EventsTest$TestClass.brokenStream']\n" + + "##teamcity[testStarted name='Class Configuration' nodeId='Class Configuration' parentNodeId='0' locationHint='java:test://com.intellij.junit5.JUnit5EventsTest$TestClass.brokenStream']\n" + "\n" + - "##teamcity[testFailed name='Class Configuration' id='Class Configuration' details='TRACE' error='true' message='']\n" + + "##teamcity[testFailed name='Class Configuration' id='Class Configuration' nodeId='Class Configuration' parentNodeId='0' details='TRACE' error='true' message='']\n" + "\n" + - "##teamcity[testFinished name='Class Configuration']\n" + - "##teamcity[testSuiteFinished id='|[engine:engine1|]' name='brokenStream()']\n", StringUtil.convertLineSeparators(myBuf.toString())); + "##teamcity[testFinished name='Class Configuration' nodeId='Class Configuration' parentNodeId='0' ]\n" + + "##teamcity[testSuiteFinished id='|[engine:engine1|]' name='brokenStream()' nodeId='|[engine:engine1|]' parentNodeId='0']\n", StringUtil.convertLineSeparators(myBuf.toString())); } private static class TestClass { diff --git a/plugins/junit5_rt_tests/test/com/intellij/junit5/JUnit5NavigationTest.java b/plugins/junit5_rt_tests/test/com/intellij/junit5/JUnit5NavigationTest.java index b9ab4ec32236..8f87127cc571 100644 --- a/plugins/junit5_rt_tests/test/com/intellij/junit5/JUnit5NavigationTest.java +++ b/plugins/junit5_rt_tests/test/com/intellij/junit5/JUnit5NavigationTest.java @@ -91,11 +91,6 @@ class JUnit5NavigationTest { void deriveSuiteOrTestFromDescription() { myTestSource = methodOrClassSource(); Assertions.assertTrue(locationHintValue().startsWith("java:suite:")); - - ConfigurableTestDescriptor descriptor = new ConfigurableTestDescriptor(myTestSource); - descriptor.isTest(true); - Assertions.assertTrue(locationHintValue(descriptor).startsWith("java:test:")); - } @Test @@ -140,7 +135,7 @@ class JUnit5NavigationTest { private static String locationHintValue(final ConfigurableTestDescriptor descriptor) { TestIdentifier testIdentifier = TestIdentifier.from(descriptor); - return JUnit5TestExecutionListener.getLocationHintValue(testIdentifier.getSource().orElseThrow(IllegalStateException::new), testIdentifier.isTest()); + return JUnit5TestExecutionListener.getLocationHintValue(testIdentifier.getSource().orElseThrow(IllegalStateException::new)); } private static ClassSource anySupportedSource() { diff --git a/plugins/junit5_rt_tests/test/com/intellij/junit5/JUnit5ParameterizedReferencesTest.java b/plugins/junit5_rt_tests/test/com/intellij/junit5/JUnit5ParameterizedReferencesTest.java index 26c398361108..51ef997b31ca 100644 --- a/plugins/junit5_rt_tests/test/com/intellij/junit5/JUnit5ParameterizedReferencesTest.java +++ b/plugins/junit5_rt_tests/test/com/intellij/junit5/JUnit5ParameterizedReferencesTest.java @@ -34,7 +34,7 @@ public class JUnit5ParameterizedReferencesTest extends JUnit5CodeInsightTest { "import org.junit.jupiter.params.ParameterizedTest;\n" + "import org.junit.jupiter.params.provider.MethodSource;\n" + "class ParameterizedTestsDemo {\n" + - " @MethodSource(names = {\"cde\", \"abc\"})\n" + + " @MethodSource(value = {\"cde\", \"abc\"})\n" + " void testWithProvider(String abc) {}\n" + " private static void abc() {}\n" + " private static void cde() {}\n" + diff --git a/plugins/maven/maven30-server-impl/src/org/jetbrains/idea/maven/server/Maven30ServerEmbedderImpl.java b/plugins/maven/maven30-server-impl/src/org/jetbrains/idea/maven/server/Maven30ServerEmbedderImpl.java index c5f6b660b79b..458704335070 100644 --- a/plugins/maven/maven30-server-impl/src/org/jetbrains/idea/maven/server/Maven30ServerEmbedderImpl.java +++ b/plugins/maven/maven30-server-impl/src/org/jetbrains/idea/maven/server/Maven30ServerEmbedderImpl.java @@ -531,10 +531,6 @@ public class Maven30ServerEmbedderImpl extends Maven3ServerEmbedder { myContainer.addComponent(getComponent(RepositoryMetadataManager.class, "ide"), RepositoryMetadataManager.class.getName()); myContainer.addComponent(getComponent(PluginDescriptorCache.class, "ide"), PluginDescriptorCache.class.getName()); ModelInterpolator modelInterpolator = getComponent(ModelInterpolator.class, "ide"); - if (!"3.0.5".equals(getMavenVersion()) && modelInterpolator instanceof StringSearchModelInterpolator) { - ((StringSearchModelInterpolator)modelInterpolator).setPathTranslator(getComponent(org.apache.maven.model.path.PathTranslator.class)); - ((StringSearchModelInterpolator)modelInterpolator).setUrlNormalizer(getComponent(UrlNormalizer.class)); - } myContainer.addComponent(modelInterpolator, ModelInterpolator.class.getName()); myContainer.addComponent(getComponent(org.apache.maven.project.interpolation.ModelInterpolator.class, "ide"), org.apache.maven.project.interpolation.ModelInterpolator.ROLE); @@ -544,6 +540,14 @@ public class Maven30ServerEmbedderImpl extends Maven3ServerEmbedder { DefaultModelBuilder defaultModelBuilder = (DefaultModelBuilder)getComponent(ModelBuilder.class); defaultModelBuilder.setModelValidator(modelValidator); defaultModelBuilder.setModelInterpolator(modelInterpolator); + + // IDEA-176117 - in older version of maven (3.0 - 3.0.3) "modelInterpolator" component is not fully initialized for some reason + // - "pathTranslator" and "urlNormalizer" fields are null, which causes NPE in paths interpolation process. + // Latest version (3.0.5) is the stable one so we expect "modelInterpolator" to be already valid in it. + if (!"3.0.5".equals(getMavenVersion()) && modelInterpolator instanceof StringSearchModelInterpolator) { + ((StringSearchModelInterpolator)modelInterpolator).setPathTranslator(getComponent(org.apache.maven.model.path.PathTranslator.class)); + ((StringSearchModelInterpolator)modelInterpolator).setUrlNormalizer(getComponent(UrlNormalizer.class)); + } } private void setConsoleAndIndicator(MavenServerConsole console, MavenServerProgressIndicator indicator) { diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/references/MavenModulePsiReference.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/references/MavenModulePsiReference.java index 201a8f97a3f6..f6c8b2203e33 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/references/MavenModulePsiReference.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/references/MavenModulePsiReference.java @@ -38,6 +38,7 @@ import org.jetbrains.idea.maven.model.MavenConstants; import org.jetbrains.idea.maven.model.MavenId; import org.jetbrains.idea.maven.utils.MavenUtil; +import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -143,8 +144,18 @@ public class MavenModulePsiReference extends MavenPsiReference implements LocalQ private VirtualFile createModulePom() throws IOException { VirtualFile baseDir = myVirtualFile.getParent(); String modulePath = PathUtil.getCanonicalPath(baseDir.getPath() + "/" + myText); + String pomFileName = MavenConstants.POM_XML; + + if (!new File(FileUtil.toSystemDependentName(modulePath)).isDirectory()) { + String fileName = PathUtil.getFileName(modulePath); + if (MavenUtil.isPomFileName(fileName) || MavenUtil.isPotentialPomFile(fileName)) { + modulePath = PathUtil.getParentPath(modulePath); + pomFileName = fileName; + } + } + VirtualFile moduleDir = VfsUtil.createDirectories(modulePath); - return moduleDir.createChildData(this, MavenConstants.POM_XML); + return moduleDir.createChildData(this, pomFileName); } } } diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/MavenProject.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/MavenProject.java index 3533ae4873ab..dc02a145c6d6 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/MavenProject.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/MavenProject.java @@ -224,13 +224,13 @@ public class MavenProject { private Map collectModulePathsAndNames(MavenModel mavenModel, String baseDir) { String basePath = baseDir + "/"; Map result = new LinkedHashMap<>(); - for (Map.Entry each : collectModulesRelativePathsAndNames(mavenModel).entrySet()) { + for (Map.Entry each : collectModulesRelativePathsAndNames(mavenModel, basePath).entrySet()) { result.put(new Path(basePath + each.getKey()).getPath(), each.getValue()); } return result; } - private Map collectModulesRelativePathsAndNames(MavenModel mavenModel) { + private Map collectModulesRelativePathsAndNames(MavenModel mavenModel, String basePath) { String extension = StringUtil.notNullize(myFile.getExtension()); LinkedHashMap result = new LinkedHashMap<>(); for (String name : mavenModel.getModules()) { @@ -246,6 +246,12 @@ public class MavenProject { if (!name.endsWith("/")) name += "/"; name += MavenConstants.POM_EXTENSION + '.' + extension; } + else { + String systemDependentName = FileUtil.toSystemDependentName(basePath + name); + if (new File(systemDependentName).isDirectory()) { + name += "/" + MavenConstants.POM_XML; + } + } result.put(name, originalName); } diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenJDOMUtil.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenJDOMUtil.java index 0c6f7e0fc848..7459de5ae9e5 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenJDOMUtil.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenJDOMUtil.java @@ -16,6 +16,7 @@ package org.jetbrains.idea.maven.utils; import com.intellij.openapi.application.AccessToken; +import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.util.JDOMUtil; import com.intellij.openapi.util.text.StringUtil; @@ -37,7 +38,11 @@ public class MavenJDOMUtil { public static Element read(final VirtualFile file, @Nullable final ErrorHandler handler) { String text; - AccessToken accessToken = ApplicationManager.getApplication().acquireReadActionLock(); + Application app = ApplicationManager.getApplication(); + if (app == null || app.isDisposeInProgress() || app.isDisposed()) { + return null; + } + AccessToken accessToken = app.acquireReadActionLock(); try { if (!file.isValid()) return null; diff --git a/plugins/maven/src/main/resources/images/childrenProjects.png b/plugins/maven/src/main/resources/images/childrenProjects.png index c5604a09e279..c7ff58e9676c 100644 Binary files a/plugins/maven/src/main/resources/images/childrenProjects.png and b/plugins/maven/src/main/resources/images/childrenProjects.png differ diff --git a/plugins/maven/src/main/resources/images/childrenProjects@2x.png b/plugins/maven/src/main/resources/images/childrenProjects@2x.png index 38e19e2a4196..81eb6fdfe904 100644 Binary files a/plugins/maven/src/main/resources/images/childrenProjects@2x.png and b/plugins/maven/src/main/resources/images/childrenProjects@2x.png differ diff --git a/plugins/maven/src/main/resources/images/childrenProjects@2x_dark.png b/plugins/maven/src/main/resources/images/childrenProjects@2x_dark.png index d12701b67b19..f0d168581733 100644 Binary files a/plugins/maven/src/main/resources/images/childrenProjects@2x_dark.png and b/plugins/maven/src/main/resources/images/childrenProjects@2x_dark.png differ diff --git a/plugins/maven/src/main/resources/images/childrenProjects_dark.png b/plugins/maven/src/main/resources/images/childrenProjects_dark.png index 8b3ed9488eec..3da8f4b60461 100644 Binary files a/plugins/maven/src/main/resources/images/childrenProjects_dark.png and b/plugins/maven/src/main/resources/images/childrenProjects_dark.png differ diff --git a/plugins/maven/src/main/resources/images/executeMavenGoal.png b/plugins/maven/src/main/resources/images/executeMavenGoal.png index 803a47688526..f13490e89855 100644 Binary files a/plugins/maven/src/main/resources/images/executeMavenGoal.png and b/plugins/maven/src/main/resources/images/executeMavenGoal.png differ diff --git a/plugins/maven/src/main/resources/images/executeMavenGoal@2x.png b/plugins/maven/src/main/resources/images/executeMavenGoal@2x.png index 70d791e46512..ff3b062057a4 100644 Binary files a/plugins/maven/src/main/resources/images/executeMavenGoal@2x.png and b/plugins/maven/src/main/resources/images/executeMavenGoal@2x.png differ diff --git a/plugins/maven/src/main/resources/images/mavenLogo.png b/plugins/maven/src/main/resources/images/mavenLogo.png index 25b3d9dae859..02ad4e3a4447 100644 Binary files a/plugins/maven/src/main/resources/images/mavenLogo.png and b/plugins/maven/src/main/resources/images/mavenLogo.png differ diff --git a/plugins/maven/src/main/resources/images/mavenLogo@2x.png b/plugins/maven/src/main/resources/images/mavenLogo@2x.png index 1a0ffd1098dd..342991d8201b 100644 Binary files a/plugins/maven/src/main/resources/images/mavenLogo@2x.png and b/plugins/maven/src/main/resources/images/mavenLogo@2x.png differ diff --git a/plugins/maven/src/main/resources/images/mavenPlugin.png b/plugins/maven/src/main/resources/images/mavenPlugin.png index 1aef9e55400d..bb135011bd98 100644 Binary files a/plugins/maven/src/main/resources/images/mavenPlugin.png and b/plugins/maven/src/main/resources/images/mavenPlugin.png differ diff --git a/plugins/maven/src/main/resources/images/mavenPlugin@2x.png b/plugins/maven/src/main/resources/images/mavenPlugin@2x.png index 9b0d5ef5e302..e5e3416ad353 100644 Binary files a/plugins/maven/src/main/resources/images/mavenPlugin@2x.png and b/plugins/maven/src/main/resources/images/mavenPlugin@2x.png differ diff --git a/plugins/maven/src/main/resources/images/mavenProject.png b/plugins/maven/src/main/resources/images/mavenProject.png index 8a5048772815..e83a6619c06a 100644 Binary files a/plugins/maven/src/main/resources/images/mavenProject.png and b/plugins/maven/src/main/resources/images/mavenProject.png differ diff --git a/plugins/maven/src/main/resources/images/mavenProject@2x.png b/plugins/maven/src/main/resources/images/mavenProject@2x.png index 7bd9444295d6..874da7fee09f 100644 Binary files a/plugins/maven/src/main/resources/images/mavenProject@2x.png and b/plugins/maven/src/main/resources/images/mavenProject@2x.png differ diff --git a/plugins/maven/src/main/resources/images/modulesClosed.png b/plugins/maven/src/main/resources/images/modulesClosed.png index 92d26833084d..dd934f4b65dc 100644 Binary files a/plugins/maven/src/main/resources/images/modulesClosed.png and b/plugins/maven/src/main/resources/images/modulesClosed.png differ diff --git a/plugins/maven/src/main/resources/images/modulesClosed@2x.png b/plugins/maven/src/main/resources/images/modulesClosed@2x.png index 41882d414729..0d51a5b6b367 100644 Binary files a/plugins/maven/src/main/resources/images/modulesClosed@2x.png and b/plugins/maven/src/main/resources/images/modulesClosed@2x.png differ diff --git a/plugins/maven/src/main/resources/images/offlineMode.png b/plugins/maven/src/main/resources/images/offlineMode.png index 11bae034687c..37310dcfb249 100644 Binary files a/plugins/maven/src/main/resources/images/offlineMode.png and b/plugins/maven/src/main/resources/images/offlineMode.png differ diff --git a/plugins/maven/src/main/resources/images/offlineMode@2x.png b/plugins/maven/src/main/resources/images/offlineMode@2x.png index a4f4c9a0de61..14f6279d5913 100644 Binary files a/plugins/maven/src/main/resources/images/offlineMode@2x.png and b/plugins/maven/src/main/resources/images/offlineMode@2x.png differ diff --git a/plugins/maven/src/main/resources/images/offlineMode@2x_dark.png b/plugins/maven/src/main/resources/images/offlineMode@2x_dark.png index 5693d198d505..126a09d1dd8d 100644 Binary files a/plugins/maven/src/main/resources/images/offlineMode@2x_dark.png and b/plugins/maven/src/main/resources/images/offlineMode@2x_dark.png differ diff --git a/plugins/maven/src/main/resources/images/offlineMode_dark.png b/plugins/maven/src/main/resources/images/offlineMode_dark.png index c1ee9b774596..77fd398ddb8c 100644 Binary files a/plugins/maven/src/main/resources/images/offlineMode_dark.png and b/plugins/maven/src/main/resources/images/offlineMode_dark.png differ diff --git a/plugins/maven/src/main/resources/images/parentProject.png b/plugins/maven/src/main/resources/images/parentProject.png index da93be6526c4..ad2060a35ce5 100644 Binary files a/plugins/maven/src/main/resources/images/parentProject.png and b/plugins/maven/src/main/resources/images/parentProject.png differ diff --git a/plugins/maven/src/main/resources/images/parentProject@2x.png b/plugins/maven/src/main/resources/images/parentProject@2x.png index db698551dd30..9b5b8452d837 100644 Binary files a/plugins/maven/src/main/resources/images/parentProject@2x.png and b/plugins/maven/src/main/resources/images/parentProject@2x.png differ diff --git a/plugins/maven/src/main/resources/images/phase.png b/plugins/maven/src/main/resources/images/phase.png index fae789db206d..a894e4c182d1 100644 Binary files a/plugins/maven/src/main/resources/images/phase.png and b/plugins/maven/src/main/resources/images/phase.png differ diff --git a/plugins/maven/src/main/resources/images/phase@2x.png b/plugins/maven/src/main/resources/images/phase@2x.png index 6d8ff6713857..fa27ef9f029b 100644 Binary files a/plugins/maven/src/main/resources/images/phase@2x.png and b/plugins/maven/src/main/resources/images/phase@2x.png differ diff --git a/plugins/maven/src/main/resources/images/phasesClosed.png b/plugins/maven/src/main/resources/images/phasesClosed.png index ceb667ddd877..5c6ec5b4fc16 100644 Binary files a/plugins/maven/src/main/resources/images/phasesClosed.png and b/plugins/maven/src/main/resources/images/phasesClosed.png differ diff --git a/plugins/maven/src/main/resources/images/phasesClosed@2x.png b/plugins/maven/src/main/resources/images/phasesClosed@2x.png index cd8a600527a5..9714772b1150 100644 Binary files a/plugins/maven/src/main/resources/images/phasesClosed@2x.png and b/plugins/maven/src/main/resources/images/phasesClosed@2x.png differ diff --git a/plugins/maven/src/main/resources/images/pluginGoal.png b/plugins/maven/src/main/resources/images/pluginGoal.png index fc3bdcee1aba..66cada048c1d 100644 Binary files a/plugins/maven/src/main/resources/images/pluginGoal.png and b/plugins/maven/src/main/resources/images/pluginGoal.png differ diff --git a/plugins/maven/src/main/resources/images/pluginGoal@2x.png b/plugins/maven/src/main/resources/images/pluginGoal@2x.png index 331938d12fe9..09b7a55bbbba 100644 Binary files a/plugins/maven/src/main/resources/images/pluginGoal@2x.png and b/plugins/maven/src/main/resources/images/pluginGoal@2x.png differ diff --git a/plugins/maven/src/main/resources/images/pluginGoal@2x_dark.png b/plugins/maven/src/main/resources/images/pluginGoal@2x_dark.png new file mode 100644 index 000000000000..ae4997304676 Binary files /dev/null and b/plugins/maven/src/main/resources/images/pluginGoal@2x_dark.png differ diff --git a/plugins/maven/src/main/resources/images/pluginGoal_dark.png b/plugins/maven/src/main/resources/images/pluginGoal_dark.png new file mode 100644 index 000000000000..76fe1bbfcccf Binary files /dev/null and b/plugins/maven/src/main/resources/images/pluginGoal_dark.png differ diff --git a/plugins/maven/src/main/resources/images/profilesClosed.png b/plugins/maven/src/main/resources/images/profilesClosed.png index 21cbde4b8e48..716997ebaa04 100644 Binary files a/plugins/maven/src/main/resources/images/profilesClosed.png and b/plugins/maven/src/main/resources/images/profilesClosed.png differ diff --git a/plugins/maven/src/main/resources/images/profilesClosed@2x.png b/plugins/maven/src/main/resources/images/profilesClosed@2x.png index 1c62a1bae019..d23e53643169 100644 Binary files a/plugins/maven/src/main/resources/images/profilesClosed@2x.png and b/plugins/maven/src/main/resources/images/profilesClosed@2x.png differ diff --git a/plugins/maven/src/main/resources/images/toolWindowMaven.png b/plugins/maven/src/main/resources/images/toolWindowMaven.png index beb6ba0ae815..d6885b74e0ed 100644 Binary files a/plugins/maven/src/main/resources/images/toolWindowMaven.png and b/plugins/maven/src/main/resources/images/toolWindowMaven.png differ diff --git a/plugins/maven/src/main/resources/images/toolWindowMaven@2x.png b/plugins/maven/src/main/resources/images/toolWindowMaven@2x.png index c570202a8b00..f853fa2beaaa 100644 Binary files a/plugins/maven/src/main/resources/images/toolWindowMaven@2x.png and b/plugins/maven/src/main/resources/images/toolWindowMaven@2x.png differ diff --git a/plugins/maven/src/main/resources/images/updateFolders.png b/plugins/maven/src/main/resources/images/updateFolders.png index 51715b3655be..0219eedc562b 100644 Binary files a/plugins/maven/src/main/resources/images/updateFolders.png and b/plugins/maven/src/main/resources/images/updateFolders.png differ diff --git a/plugins/maven/src/main/resources/images/updateFolders@2x.png b/plugins/maven/src/main/resources/images/updateFolders@2x.png index c043fca88db6..90e610cb145a 100644 Binary files a/plugins/maven/src/main/resources/images/updateFolders@2x.png and b/plugins/maven/src/main/resources/images/updateFolders@2x.png differ diff --git a/plugins/maven/src/test/java/org/jetbrains/idea/maven/dom/MavenModuleCompletionAndResolutionTest.java b/plugins/maven/src/test/java/org/jetbrains/idea/maven/dom/MavenModuleCompletionAndResolutionTest.java index 3a863c3fdce4..9b17c726040e 100644 --- a/plugins/maven/src/test/java/org/jetbrains/idea/maven/dom/MavenModuleCompletionAndResolutionTest.java +++ b/plugins/maven/src/test/java/org/jetbrains/idea/maven/dom/MavenModuleCompletionAndResolutionTest.java @@ -324,6 +324,81 @@ public class MavenModuleCompletionAndResolutionTest extends MavenDomWithIndicesT ""); } + public void testCreatePomQuickFixCustomPomFileName() throws Exception { + createProjectPom("test" + + "project" + + "1" + + "pom"); + importProject(); + + createProjectPom("test" + + "project" + + "1" + + "pom" + + + "" + + " subDir/newModule.xml" + + ""); + + IntentionAction i = getIntentionAtCaret(CREATE_MODULE_INTENTION); + assertNotNull(i); + + myFixture.launchAction(i); + + assertCreateModuleFixResult( + "subDir/newModule.xml", + "\n" + + "\n" + + " 4.0.0\n" + + "\n" + + " test\n" + + " subDir\n" + + " 1\n" + + "\n" + + " \n" + + ""); + } + + public void testCreatePomQuickFixInDotXmlFolder() throws Exception { + createProjectPom("test" + + "project" + + "1" + + "pom"); + importProject(); + + createProjectPom("test" + + "project" + + "1" + + "pom" + + + "" + + " subDir/newModule.xml" + + ""); + createProjectSubFile("subDir/newModule.xml/empty"); // ensure that "subDir/newModule.xml" exists as a directory + + IntentionAction i = getIntentionAtCaret(CREATE_MODULE_INTENTION); + assertNotNull(i); + + myFixture.launchAction(i); + + assertCreateModuleFixResult( + "subDir/newModule.xml/pom.xml", + "\n" + + "\n" + + " 4.0.0\n" + + "\n" + + " test\n" + + " newModule.xml\n" + + " 1\n" + + "\n" + + " \n" + + ""); + } + public void testCreatePomQuickFixTakesGroupAndVersionFromSuperParent() throws Throwable { createProjectPom("test" + "project" + diff --git a/plugins/maven/src/test/java/org/jetbrains/idea/maven/importing/CustomPomFileNameTest.java b/plugins/maven/src/test/java/org/jetbrains/idea/maven/importing/CustomPomFileNameTest.java index 0d8dae9beb93..d2b7a9c4cb37 100644 --- a/plugins/maven/src/test/java/org/jetbrains/idea/maven/importing/CustomPomFileNameTest.java +++ b/plugins/maven/src/test/java/org/jetbrains/idea/maven/importing/CustomPomFileNameTest.java @@ -42,6 +42,28 @@ public class CustomPomFileNameTest extends MavenDomTestCase { assertModules("project", "m1"); } + public void testFolderNameWithXmlExtension() throws Exception { + createProjectSubFile("customName.xml/pom.xml", createPomXml( + "m1" + + "1" + + + "" + + " test" + + " project" + + " 1" + + "")); + + importProject("test" + + "project" + + "1" + + + "" + + " customName.xml" + + ""); + + assertModules("project", "m1"); + } + public void testModuleCompletion() throws Exception { createProjectSubFile("m1/customPom.xml", createPomXml( "m1" + diff --git a/plugins/properties/properties-psi-api/resources/icons/xmlProperties.png b/plugins/properties/properties-psi-api/resources/icons/xmlProperties.png index d77e85478036..bfdfb64214d1 100644 Binary files a/plugins/properties/properties-psi-api/resources/icons/xmlProperties.png and b/plugins/properties/properties-psi-api/resources/icons/xmlProperties.png differ diff --git a/plugins/properties/properties-psi-api/resources/icons/xmlProperties@2x.png b/plugins/properties/properties-psi-api/resources/icons/xmlProperties@2x.png index a4de76d45ad4..7a9f51c72a10 100644 Binary files a/plugins/properties/properties-psi-api/resources/icons/xmlProperties@2x.png and b/plugins/properties/properties-psi-api/resources/icons/xmlProperties@2x.png differ diff --git a/plugins/settings-repository/src/upstreamEditor.kt b/plugins/settings-repository/src/upstreamEditor.kt index aecaab9e4634..15d4bf3935f4 100644 --- a/plugins/settings-repository/src/upstreamEditor.kt +++ b/plugins/settings-repository/src/upstreamEditor.kt @@ -15,7 +15,7 @@ */ package org.jetbrains.settingsRepository -import com.intellij.internal.statistic.ActionsCollector +import com.intellij.internal.statistic.customUsageCollectors.actions.ActionsCollector import com.intellij.notification.NotificationType import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.project.Project diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SelectBranchPopup.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SelectBranchPopup.java index 182656d3b79c..a9226af0be0c 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SelectBranchPopup.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SelectBranchPopup.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -16,15 +16,15 @@ package org.jetbrains.idea.svn.branchConfig; import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.popup.*; +import com.intellij.openapi.ui.popup.JBPopup; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.ui.popup.ListSeparator; +import com.intellij.openapi.ui.popup.PopupStep; import com.intellij.openapi.ui.popup.util.BaseListPopupStep; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.components.JBList; -import com.intellij.util.NullableFunction; -import com.intellij.util.continuation.ModalityIgnorantBackgroundableTask; import com.intellij.util.text.DateFormatUtil; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; @@ -42,11 +42,9 @@ import java.awt.*; import java.util.ArrayList; import java.util.List; +import static com.intellij.openapi.util.text.StringUtil.isEmptyOrSpaces; import static com.intellij.openapi.vfs.VfsUtilCore.virtualToIoFile; -/** - * @author yole - */ public class SelectBranchPopup { private final static String CONFIGURE_MESSAGE = SvnBundle.message("configure.branches.item"); @@ -57,47 +55,58 @@ public class SelectBranchPopup { void branchSelected(Project project, SvnBranchConfigurationNew configuration, String url, long revision); } - public static void show(Project project, VirtualFile file, BranchSelectedCallback callback, final String title) { + public static void show(@NotNull Project project, + @NotNull VirtualFile file, + @NotNull BranchSelectedCallback callback, + @Nullable String title) { show(project, file, callback, title, null); } - public static void show(Project project, VirtualFile file, BranchSelectedCallback callback, final String title, final Component component) { - final SvnFileUrlMapping urlMapping = SvnVcs.getInstance(project).getSvnFileUrlMapping(); - final SVNURL svnurl = urlMapping.getUrlForFile(virtualToIoFile(file)); - if (svnurl == null) { - return; - } - final RootUrlInfo rootUrlInfo = urlMapping.getWcRootForUrl(svnurl.toString()); - if (rootUrlInfo == null) { - return; - } + public static void show(@NotNull Project project, + @NotNull VirtualFile file, + @NotNull BranchSelectedCallback callback, + @Nullable String title, + @Nullable Component component) { + SvnFileUrlMapping urlMapping = SvnVcs.getInstance(project).getSvnFileUrlMapping(); + SVNURL svnurl = urlMapping.getUrlForFile(virtualToIoFile(file)); - // not vcs root but wc root is ok - showForBranchRoot(project, rootUrlInfo.getVirtualFile(), callback, title, component); + if (svnurl != null) { + RootUrlInfo rootUrlInfo = urlMapping.getWcRootForUrl(svnurl.toString()); + + if (rootUrlInfo != null) { + // not vcs root but wc root is ok + showForBranchRoot(project, rootUrlInfo.getVirtualFile(), callback, title, component); + } + } } - public static void showForBranchRoot(Project project, VirtualFile vcsRoot, BranchSelectedCallback callback, final String title) { + public static void showForBranchRoot(@NotNull Project project, + @NotNull VirtualFile vcsRoot, + @NotNull BranchSelectedCallback callback, + @Nullable String title) { showForBranchRoot(project, vcsRoot, callback, title, null); } - public static void showForBranchRoot(Project project, VirtualFile vcsRoot, BranchSelectedCallback callback, final String title, - final Component component) { - final SvnBranchConfigurationNew configuration = SvnBranchConfigurationManager.getInstance(project).get(vcsRoot); - final List items = new ArrayList<>(); - if (! StringUtil.isEmptyOrSpaces(configuration.getTrunkUrl())) { + public static void showForBranchRoot(@NotNull Project project, + @NotNull VirtualFile vcsRoot, + @NotNull BranchSelectedCallback callback, + @Nullable String title, + @Nullable Component component) { + SvnBranchConfigurationNew configuration = SvnBranchConfigurationManager.getInstance(project).get(vcsRoot); + List items = new ArrayList<>(); + + if (!isEmptyOrSpaces(configuration.getTrunkUrl())) { items.add(getTrunkString(configuration)); } - for (String url : configuration.getBranchUrls()) { - items.add(url); - } + items.addAll(configuration.getBranchUrls()); items.add(CONFIGURE_MESSAGE); BranchBasesPopupStep step = new BranchBasesPopupStep(project, vcsRoot, configuration, callback, items, title, component); - final ListPopup listPopup = JBPopupFactory.getInstance().createListPopup(step); - step.showPopupAt(listPopup); + step.showPopupAt(JBPopupFactory.getInstance().createListPopup(step)); } - - private static String getTrunkString(final SvnBranchConfigurationNew configuration) { + + @NotNull + private static String getTrunkString(@NotNull SvnBranchConfigurationNew configuration) { return configuration.getTrunkUrl() + " (trunk)"; } @@ -107,74 +116,63 @@ public class SelectBranchPopup { } private static class BranchBasesPopupStep extends BaseListPopupStep { - protected final Project myProject; - private final VirtualFile myVcsRoot; - private final SvnBranchConfigurationNew myConfiguration; - private final boolean myTopLevel; - private BranchSelectedCallback myCallback; - private final Component myComponent; + @NotNull private final Project myProject; + @NotNull private final VirtualFile myVcsRoot; + @NotNull private final SvnBranchConfigurationNew myConfiguration; + @NotNull private final BranchSelectedCallback myCallback; + @Nullable private final Component myComponent; + @NotNull private final String myTrunkString; private static final String REFRESH_MESSAGE = SvnBundle.message("refresh.branches.item"); - private String myTrunkString; - protected BranchBasesPopupStep(final Project project, - final VirtualFile vcsRoot, - final SvnBranchConfigurationNew configuration, - boolean topLevel, - final BranchSelectedCallback callback, - Component component) { + public BranchBasesPopupStep(@NotNull Project project, + @NotNull VirtualFile vcsRoot, + @NotNull SvnBranchConfigurationNew configuration, + @NotNull BranchSelectedCallback callback, + @NotNull List items, + @Nullable String title, + @Nullable Component component) { myProject = project; myVcsRoot = vcsRoot; myConfiguration = configuration; myTrunkString = getTrunkString(configuration); - myTopLevel = topLevel; myCallback = callback; myComponent = component; - } - - public BranchBasesPopupStep(final Project project, final VirtualFile vcsRoot, - final SvnBranchConfigurationNew configuration, - final BranchSelectedCallback callback, - final List items, - final String title, - Component component) { - this(project, vcsRoot, configuration, true, callback, component); init(title, items, null); } @Override - public ListSeparator getSeparatorAbove(final String value) { - return (CONFIGURE_MESSAGE.equals(value)) || - (REFRESH_MESSAGE.equals(value)) ? new ListSeparator("") : null; + public ListSeparator getSeparatorAbove(String value) { + return CONFIGURE_MESSAGE.equals(value) || REFRESH_MESSAGE.equals(value) ? new ListSeparator("") : null; } @NotNull @Override - public String getTextFor(final String value) { + public String getTextFor(@NotNull String value) { int pos = value.lastIndexOf('/'); if (pos < 0) { return value; } - if (myTopLevel && ((myConfiguration.getTrunkUrl() == null) || (! value.startsWith(myConfiguration.getTrunkUrl())))) { - return value.substring(pos+1) + "..."; + if (myConfiguration.getTrunkUrl() == null || !value.startsWith(myConfiguration.getTrunkUrl())) { + return value.substring(pos + 1) + "..."; } - return value.substring(pos+1); + return value.substring(pos + 1); } @Override - public boolean hasSubstep(final String selectedValue) { + public boolean hasSubstep(String selectedValue) { return false; } @Override - public PopupStep onChosen(final String selectedValue, final boolean finalChoice) { + public PopupStep onChosen(String selectedValue, boolean finalChoice) { if (CONFIGURE_MESSAGE.equals(selectedValue)) { return doFinalStep(() -> BranchConfigurationDialog.configureBranches(myProject, myVcsRoot)); } else if (myTrunkString.equals(selectedValue)) { return doFinalStep(() -> myCallback.branchSelected(myProject, myConfiguration, myConfiguration.getTrunkUrl(), -1)); } - else if (!myTopLevel || selectedValue.equals(myConfiguration.getTrunkUrl())) { + else if (selectedValue.equals(myConfiguration.getTrunkUrl())) { return doFinalStep(() -> myCallback.branchSelected(myProject, myConfiguration, selectedValue, -1)); } else { @@ -183,53 +181,40 @@ public class SelectBranchPopup { return FINAL_CHOICE; } - @Nullable - private void loadBranches(final String selectedBranchesHolder, final Runnable runnable) { - final ProgressManager pm = ProgressManager.getInstance(); - pm.run(new ModalityIgnorantBackgroundableTask(myProject, SvnBundle.message("compare.with.branch.progress.loading.branches"), true) { + private void loadBranches(@NotNull String selectedBranchesHolder, @NotNull Runnable runnable) { + new Task.Backgroundable(myProject, SvnBundle.message("compare.with.branch.progress.loading.branches"), true) { @Override - protected void doInAwtIfFail(@NotNull Exception e) { + public void onFinished() { runnable.run(); } @Override - protected void doInAwtIfCancel() { - runnable.run(); - } - - @Override - protected void doInAwtIfSuccess() { - runnable.run(); - } - - @Override - protected void runImpl(@NotNull ProgressIndicator indicator) { - final NewRootBunch manager = SvnBranchConfigurationManager.getInstance(myProject).getSvnBranchConfigManager(); + public void run(@NotNull ProgressIndicator indicator) { + NewRootBunch manager = SvnBranchConfigurationManager.getInstance(myProject).getSvnBranchConfigManager(); manager.reloadBranches(myVcsRoot, selectedBranchesHolder, InfoReliability.setByUser, false); } - }); + }.queue(); } - private void showBranchPopup(final String selectedValue) { + private void showBranchPopup(String selectedValue) { List branches = myConfiguration.getBranches(selectedValue); if (branches == null) { return; } - final Object[] items = new Object[branches.size() + 1]; + Object[] items = new Object[branches.size() + 1]; System.arraycopy(branches.toArray(), 0, items, 0, branches.size()); items[items.length - 1] = REFRESH_MESSAGE; - final JList branchList = new JBList(items); + JBList branchList = new JBList<>(items); branchList.setCellRenderer(new BranchRenderer()); - final JBPopup popup = JBPopupFactory.getInstance().createListPopupBuilder(branchList) + JBPopup popup = JBPopupFactory.getInstance().createListPopupBuilder(branchList) .setTitle(SVNPathUtil.tail(selectedValue)) .setResizable(true) - //.setDimensionServiceKey("Svn.CompareWithBranchPopup") .setItemChoosenCallback(() -> { if (REFRESH_MESSAGE.equals(branchList.getSelectedValue())) { - SwingUtilities.invokeLater(() -> loadBranches(selectedValue, () -> showBranchPopup(selectedValue))); + loadBranches(selectedValue, () -> showBranchPopup(selectedValue)); return; } SvnBranchItem item = (SvnBranchItem)branchList.getSelectedValue(); @@ -237,13 +222,12 @@ public class SelectBranchPopup { myCallback.branchSelected(myProject, myConfiguration, item.getUrl(), item.getRevision()); } }) - .setFilteringEnabled( - (NullableFunction)item -> item instanceof SvnBranchItem ? getBranchName((SvnBranchItem)item) : null) + .setFilteringEnabled(item -> item instanceof SvnBranchItem ? getBranchName((SvnBranchItem)item) : null) .createPopup(); showPopupAt(popup); } - public void showPopupAt(final JBPopup listPopup) { + public void showPopupAt(@NotNull JBPopup listPopup) { if (myComponent == null) { listPopup.showCenteredInCurrentWindow(myProject); } else { @@ -252,7 +236,7 @@ public class SelectBranchPopup { } } - private static class BranchRenderer extends JPanel implements ListCellRenderer { + private static class BranchRenderer extends JPanel implements ListCellRenderer { private final JLabel myUrlLabel = new JLabel(); private final JLabel myDateLabel = new JLabel(); @@ -269,14 +253,14 @@ public class SelectBranchPopup { public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { if (isSelected || cellHasFocus) { setBackground(UIUtil.getListSelectionBackground()); - final Color selectedForegroundColor = UIUtil.getListSelectionForeground(); + Color selectedForegroundColor = UIUtil.getListSelectionForeground(); myUrlLabel.setForeground(selectedForegroundColor); myDateLabel.setForeground(selectedForegroundColor); setForeground(selectedForegroundColor); } else { setBackground(UIUtil.getListBackground()); - final Color foregroundColor = UIUtil.getListForeground(); + Color foregroundColor = UIUtil.getListForeground(); myUrlLabel.setForeground(foregroundColor); myDateLabel.setForeground(UIUtil.getInactiveTextColor()); setForeground(foregroundColor); @@ -287,8 +271,8 @@ public class SelectBranchPopup { } else { SvnBranchItem item = (SvnBranchItem) value; myUrlLabel.setText(getBranchName(item)); - final long creationMillis = item.getCreationDateMillis(); - myDateLabel.setText((creationMillis > 0) ? DateFormatUtil.formatDate(creationMillis) : ""); + long creationMillis = item.getCreationDateMillis(); + myDateLabel.setText(creationMillis > 0 ? DateFormatUtil.formatDate(creationMillis) : ""); } return this; } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigurationManager.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigurationManager.java index 693e366105bc..2112414f1ad1 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigurationManager.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigurationManager.java @@ -70,7 +70,7 @@ public class SvnBranchConfigurationManager implements PersistentStateComponent { + if (!mySettings.isHideProperties()) { + mySplitter.setSecondComponent(myPropertiesViewer.getComponent()); + myNotificationPanel.setContent(null); + } + else { + mySplitter.setSecondComponent(null); + myNotificationPanel.setContent(createNotification()); + } + }); } @NotNull diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistoryProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistoryProvider.java index 9e39393be2ec..f094ef7b36b2 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistoryProvider.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistoryProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -32,11 +32,15 @@ import com.intellij.openapi.vcs.changes.ContentRevision; import com.intellij.openapi.vcs.changes.issueLinks.TableLinkMouseListener; import com.intellij.openapi.vcs.history.*; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.ui.*; +import com.intellij.ui.AnActionButton; +import com.intellij.ui.ColoredTableCellRenderer; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.ui.ToolbarDecorator; import com.intellij.util.Consumer; import com.intellij.util.PlatformIcons; import com.intellij.util.ThrowableConsumer; import com.intellij.util.ui.ColumnInfo; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.StatusText; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; @@ -155,7 +159,7 @@ public class SvnHistoryProvider }.initPosition() .addExtraAction(AnActionButton.fromAction(sourceAction)) .createPanel(); - fieldPanel.setBorder(IdeBorderFactory.createEmptyBorder()); + fieldPanel.setBorder(JBUI.Borders.empty()); addComp = fieldPanel; } else { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnMergeSourceDetails.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnMergeSourceDetails.java index 37b54cc62382..55c227eee864 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnMergeSourceDetails.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnMergeSourceDetails.java @@ -22,7 +22,6 @@ import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.MasterDetailsComponent; import com.intellij.openapi.ui.NamedConfigurable; import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.vcs.changes.committed.CommittedChangeListRenderer; import com.intellij.openapi.vcs.changes.ui.ChangeListViewerDialog; import com.intellij.openapi.vfs.VirtualFile; @@ -137,12 +136,11 @@ public class SvnMergeSourceDetails extends MasterDetailsComponent { } final String revisonNumber = revision.getRevisionNumber().asString(); - final Pair info = CommittedChangeListRenderer.getDescriptionOfChangeList(revision.getCommitMessage()); - String description = info.getFirst(); + String description = CommittedChangeListRenderer.getDescriptionOfChangeList(revision.getCommitMessage()); int width = metrics.stringWidth(description); int dotsWidth = metrics.stringWidth(ourDots); - boolean descriptionTruncated = info.getSecond(); - if ((descriptionTruncated && (ourMaxWidth - dotsWidth < width)) || (! descriptionTruncated) && (ourMaxWidth < width)) { + boolean descriptionTruncated = false; + if (ourMaxWidth < width) { description = CommittedChangeListRenderer.truncateDescription(description, metrics, ourMaxWidth - dotsWidth); descriptionTruncated = true; } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/statistics/SvnWorkingCopyFormatUsagesCollector.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/statistics/SvnWorkingCopyFormatUsagesCollector.java index 72dc4938b128..b54a9826bccc 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/statistics/SvnWorkingCopyFormatUsagesCollector.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/statistics/SvnWorkingCopyFormatUsagesCollector.java @@ -15,7 +15,7 @@ */ package org.jetbrains.idea.svn.statistics; -import com.intellij.internal.statistic.AbstractApplicationUsagesCollector; +import com.intellij.internal.statistic.AbstractProjectsUsagesCollector; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; import com.intellij.openapi.project.Project; @@ -31,7 +31,7 @@ import java.util.Set; /** * @author Konstantin Kolosovsky. */ -public class SvnWorkingCopyFormatUsagesCollector extends AbstractApplicationUsagesCollector { +public class SvnWorkingCopyFormatUsagesCollector extends AbstractProjectsUsagesCollector { private static final String GROUP_ID = "svn working copy format"; diff --git a/plugins/testng/src/com/theoryinpractice/testng/inspection/DependsOnGroupsInspection.java b/plugins/testng/src/com/theoryinpractice/testng/inspection/DependsOnGroupsInspection.java index 499b96829c60..810bcebbf981 100644 --- a/plugins/testng/src/com/theoryinpractice/testng/inspection/DependsOnGroupsInspection.java +++ b/plugins/testng/src/com/theoryinpractice/testng/inspection/DependsOnGroupsInspection.java @@ -181,5 +181,10 @@ public class DependsOnGroupsInspection extends BaseJavaLocalInspectionTool { } */ } + + @Override + public boolean startInWriteAction() { + return false; + } } } diff --git a/plugins/ui-designer-core/src/com/intellij/designer/designSurface/DesignerEditorPanel.java b/plugins/ui-designer-core/src/com/intellij/designer/designSurface/DesignerEditorPanel.java index 7fa81fdd22a4..b9b7a20be9d5 100644 --- a/plugins/ui-designer-core/src/com/intellij/designer/designSurface/DesignerEditorPanel.java +++ b/plugins/ui-designer-core/src/com/intellij/designer/designSurface/DesignerEditorPanel.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -61,6 +61,7 @@ import com.intellij.util.ThrowableRunnable; import com.intellij.util.containers.FixedHashMap; import com.intellij.util.containers.IntArrayList; import com.intellij.util.ui.AsyncProcessIcon; +import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -433,7 +434,7 @@ public abstract class DesignerEditorPanel extends JPanel } if (message.myAdditionalFixes != null && message.myAdditionalFixes.size() > 0) { JPanel fixesPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0)); - fixesPanel.setBorder(IdeBorderFactory.createEmptyBorder(3, 0, 10, 0)); + fixesPanel.setBorder(JBUI.Borders.empty(3, 0, 10, 0)); fixesPanel.setOpaque(false); fixesPanel.add(Box.createHorizontalStrut(icon.getIconWidth())); diff --git a/python/build/groovy/org/jetbrains/intellij/build/pycharm/PyCharmCommunityProperties.groovy b/python/build/groovy/org/jetbrains/intellij/build/pycharm/PyCharmCommunityProperties.groovy index 059e48c073c5..e7a701d402f0 100644 --- a/python/build/groovy/org/jetbrains/intellij/build/pycharm/PyCharmCommunityProperties.groovy +++ b/python/build/groovy/org/jetbrains/intellij/build/pycharm/PyCharmCommunityProperties.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -78,7 +78,12 @@ class PyCharmCommunityProperties extends PyCharmPropertiesBase { return new LinuxDistributionCustomizer() { { iconPngPath = "$projectHome/python/resources/PyCharmCore128.png" + //snapName = "pycharm-community" + //snapDescription = + // "Python IDE for professional developers. Save time while PyCharm takes care of the routine. " + // "Focus on bigger things and embrace the keyboard-centric approach to get the most of PyCharm’s many productivity features." } + @Override String getRootDirectoryName(ApplicationInfoProperties applicationInfo, String buildNumber) { "pycharm-community-${applicationInfo.isEAP ? buildNumber : applicationInfo.fullVersion}" diff --git a/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCoursesPanel.java b/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCoursesPanel.java index 7aa34423d129..d4e07dc28905 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCoursesPanel.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCoursesPanel.java @@ -84,11 +84,11 @@ public class EduCoursesPanel extends JPanel { GuiUtils.replaceJSplitPaneWithIDEASplitter(mySplitPaneRoot, true); mySplitPane.setDividerLocation(0.5); mySplitPane.setResizeWeight(0.5); - myCourseNameLabel.setBorder(IdeBorderFactory.createEmptyBorder(20, 10, 5, 10)); + myCourseNameLabel.setBorder(JBUI.Borders.empty(20, 10, 5, 10)); Font labelFont = UIUtil.getLabelFont(); myCourseNameLabel.setFont(new Font(labelFont.getName(), Font.BOLD, JBUI.scaleFontSize(18.0f))); - myTagsPanel.setBorder(IdeBorderFactory.createEmptyBorder(0, 10, 0, 10)); - myDescriptionTextArea.setBorder(IdeBorderFactory.createEmptyBorder(20, 10, 10, 10)); + myTagsPanel.setBorder(JBUI.Borders.empty(0, 10)); + myDescriptionTextArea.setBorder(JBUI.Borders.empty(20, 10, 10, 10)); myDescriptionTextArea.setEditorKit(UIUtil.getHTMLEditorKit()); myDescriptionTextArea.setEditable(false); myDescriptionTextArea.setPreferredSize(JBUI.size(myCoursePanel.getPreferredSize())); @@ -97,7 +97,7 @@ public class EduCoursesPanel extends JPanel { myCourses = getCourses(); updateModel(myCourses, null); myErrorLabel.setVisible(false); - myErrorLabel.setBorder(IdeBorderFactory.createEmptyBorder(20, 10, 0, 0)); + myErrorLabel.setBorder(JBUI.Borders.empty(20, 10, 0, 0)); ListCellRendererWrapper renderer = new ListCellRendererWrapper() { @Override @@ -124,7 +124,7 @@ public class EduCoursesPanel extends JPanel { public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { Component component = renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); if (component instanceof JLabel) { - ((JLabel)component).setBorder(IdeBorderFactory.createEmptyBorder(5, 0, 5, 0)); + ((JLabel)component).setBorder(JBUI.Borders.empty(5, 0)); } return component; } @@ -133,12 +133,7 @@ public class EduCoursesPanel extends JPanel { myCoursesList.addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { - Course selectedCourse = myCoursesList.getSelectedValue(); - if (selectedCourse == null) { - notifyListeners(false); - return; - } - updateCourseInfoPanel(selectedCourse); + processSelectionChanged(); } }); DefaultActionGroup group = new DefaultActionGroup(new AnAction("Import Course", "import local course", AllIcons.ToolbarDecorator.Import) { @@ -181,7 +176,7 @@ public class EduCoursesPanel extends JPanel { myCourseListPanel.setBorder(JBUI.Borders.customLine(OnePixelDivider.BACKGROUND, 1, 1, 1, 1)); HideableDecorator decorator = new HideableDecorator(myAdvancedSettingsPlaceholder, "Advanced Settings", false); decorator.setContentComponent(myAdvancedSettings); - myAdvancedSettings.setBorder(IdeBorderFactory.createEmptyBorder(0, IdeBorderFactory.TITLED_BORDER_INDENT, 5, 0)); + myAdvancedSettings.setBorder(JBUI.Borders.empty(0, IdeBorderFactory.TITLED_BORDER_INDENT, 5, 0)); myCoursesList.setBackground(LIST_COLOR); myDescriptionTextArea.setBackground(UIUtil.getPanelBackground()); myAdvancedSettingsPlaceholder.setVisible(false); @@ -219,7 +214,16 @@ public class EduCoursesPanel extends JPanel { } } }); - updateCourseInfoPanel(myCoursesList.getSelectedValue()); + + processSelectionChanged(); + } + + private void processSelectionChanged() { + Course selectedCourse = myCoursesList.getSelectedValue(); + notifyListeners(canStartCourse(selectedCourse)); + if (selectedCourse != null) { + updateCourseInfoPanel(selectedCourse); + } } private void updateCourseInfoPanel(Course selectedCourse) { @@ -230,10 +234,6 @@ public class EduCoursesPanel extends JPanel { myErrorLabel.setText( UIUtil.toHtml("Log in to Stepik " + (selectedCourse.isAdaptive() ? "to start adaptive course" : "to see more courses"))); myErrorLabel.setForeground((selectedCourse.isAdaptive() ? MessageType.ERROR : MessageType.WARNING).getTitleForeground()); - notifyListeners(!selectedCourse.isAdaptive()); - } - else { - notifyListeners(true); } } @@ -311,7 +311,7 @@ public class EduCoursesPanel extends JPanel { } private static JLabel createTagLabel(String tagText) { - Border emptyBorder = IdeBorderFactory.createEmptyBorder(3, 5, 3, 5); + Border emptyBorder = JBUI.Borders.empty(3, 5); JBLabel label = new JBLabel(tagText); label.setOpaque(true); label.setBorder(emptyBorder); @@ -424,6 +424,7 @@ public class EduCoursesPanel extends JPanel { public void addCourseValidationListener(CourseValidationListener listener) { myListeners.add(listener); + listener.validationStatusChanged(canStartCourse(myCoursesList.getSelectedValue())); } private void notifyListeners(boolean canStartCourse) { @@ -432,6 +433,18 @@ public class EduCoursesPanel extends JPanel { } } + private static boolean canStartCourse(Course selectedCourse) { + if (selectedCourse == null) { + return false; + } + + if (isLoggedIn()) { + return true; + } + + return !selectedCourse.isAdaptive(); + } + public interface CourseValidationListener { void validationStatusChanged(boolean canStartCourse); } diff --git a/python/educational-core/src/com/jetbrains/edu/learning/statistics/EduUsagesCollector.java b/python/educational-core/src/com/jetbrains/edu/learning/statistics/EduUsagesCollector.java index f6c0960b9036..0399965e65d6 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/statistics/EduUsagesCollector.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/statistics/EduUsagesCollector.java @@ -20,51 +20,53 @@ import com.intellij.internal.statistic.UsagesCollector; import com.intellij.internal.statistic.beans.GroupDescriptor; import com.intellij.internal.statistic.beans.UsageDescriptor; import com.intellij.openapi.components.ServiceManager; -import com.intellij.util.containers.FactoryMap; import com.intellij.util.containers.hash.HashSet; +import gnu.trove.TObjectIntHashMap; import org.jetbrains.annotations.NotNull; -import java.util.Map; import java.util.Set; public class EduUsagesCollector extends UsagesCollector { private static final String GROUP_ID = "educational"; - private final Map myUsageDescriptors = FactoryMap.createMap(key -> new UsageDescriptor(key, 0)); + private final TObjectIntHashMap myUsageDescriptors = new TObjectIntHashMap<>(); public static void projectTypeCreated(@NotNull String projectTypeId) { - ServiceManager.getService(EduUsagesCollector.class).myUsageDescriptors.get("project.created." + projectTypeId).advance(); + advanceKey("project.created." + projectTypeId); } public static void projectTypeOpened(@NotNull String projectTypeId) { - ServiceManager.getService(EduUsagesCollector.class).myUsageDescriptors.get("project.opened." + projectTypeId).advance(); + advanceKey("project.opened." + projectTypeId); } public static void taskChecked() { - ServiceManager.getService(EduUsagesCollector.class).myUsageDescriptors.get("checkTask.").advance(); + advanceKey("checkTask."); } public static void hintShown() { - ServiceManager.getService(EduUsagesCollector.class).myUsageDescriptors.get("showHint.").advance(); + advanceKey("showHint."); } public static void taskNavigation() { - ServiceManager.getService(EduUsagesCollector.class).myUsageDescriptors.get("navigateToTask.").advance(); + advanceKey("navigateToTask."); } public static void courseUploaded() { - ServiceManager.getService(EduUsagesCollector.class).myUsageDescriptors.get("uploadCourse.").advance(); + advanceKey("uploadCourse."); } public static void createdCourseArchive() { - ServiceManager.getService(EduUsagesCollector.class).myUsageDescriptors.get("courseArchive.").advance(); + advanceKey("courseArchive."); } @NotNull @Override public Set getUsages() throws CollectUsagesException { HashSet descriptors = new HashSet<>(); - descriptors.addAll(myUsageDescriptors.values()); + myUsageDescriptors.forEachEntry((key, value) -> { + descriptors.add(new UsageDescriptor(key, value)); + return true; + }); myUsageDescriptors.clear(); return descriptors; } @@ -75,4 +77,10 @@ public class EduUsagesCollector extends UsagesCollector { public GroupDescriptor getGroupId() { return GroupDescriptor.create(GROUP_ID); } + + private static void advanceKey(@NotNull String key) { + TObjectIntHashMap descriptors = ServiceManager.getService(EduUsagesCollector.class).myUsageDescriptors; + int oldValue = descriptors.get(key); + descriptors.put(key, oldValue + 1); + } } diff --git a/python/educational-python/build/customInstallActions.nsi b/python/educational-python/build/customInstallActions.nsi index e44f7ace7a89..3b30dc394521 100644 --- a/python/educational-python/build/customInstallActions.nsi +++ b/python/educational-python/build/customInstallActions.nsi @@ -14,7 +14,7 @@ getPythonFileInfo: IfErrors cantOpenFile StrCmp $R0 ${PYTHON_VERSIONS} getPythonInfo cantOpenFile: - MessageBox MB_OK|MB_ICONEXCLAMATION "python.txt is not exist. Python will not be downloaded." + MessageBox MB_OK|MB_ICONEXCLAMATION "python.txt is not exist. Python will not be downloaded." /SD IDOK removePythonChoice: !insertmacro INSTALLOPTIONS_WRITE "Desktop.ini" "Field 6" "Flags" "DISABLED" !insertmacro INSTALLOPTIONS_WRITE "Desktop.ini" "Field 7" "Flags" "DISABLED" @@ -47,7 +47,7 @@ Function customInstallActions ; info about 2 and 3 version of python StrCmp $R0 ${PYTHON_VERSIONS} getPythonInfo cantOpenFile: - MessageBox MB_OK|MB_ICONEXCLAMATION "python.txt is invalid. Python will not be downloaded." + MessageBox MB_OK|MB_ICONEXCLAMATION "python.txt is invalid. Python will not be downloaded." /SD IDOK goto skip_python_download getPythonInfo: Call getPythonInfo @@ -84,7 +84,7 @@ get_python: ${If} $0 == "OK" ExecDos::exec '$R7"$INSTDIR\python\python_$R8" $R9' ${Else} - MessageBox MB_OK|MB_ICONEXCLAMATION "The download is failed: $0" + MessageBox MB_OK|MB_ICONEXCLAMATION "The download is failed: $0" /SD IDOK ${EndIf} python_exists: skip_python_download: @@ -121,7 +121,7 @@ getPythonInfo: ${StrTok} $R1 $4 " " "2" "1" goto done cantOpenFile: - MessageBox MB_OK|MB_ICONEXCLAMATION "python.txt is not exist. Python will not be downloaded." + MessageBox MB_OK|MB_ICONEXCLAMATION "python.txt is not exist. Python will not be downloaded." /SD IDOK StrCpy $0 "Error" done: FunctionEnd diff --git a/python/educational-python/resources/icons/com/jetbrains/edu/student.png b/python/educational-python/resources/icons/com/jetbrains/edu/student.png index 64fb2e44adf8..fdbc396340ca 100644 Binary files a/python/educational-python/resources/icons/com/jetbrains/edu/student.png and b/python/educational-python/resources/icons/com/jetbrains/edu/student.png differ diff --git a/python/educational-python/resources/icons/com/jetbrains/edu/student@2x.png b/python/educational-python/resources/icons/com/jetbrains/edu/student@2x.png index d4c06ae0b75b..82d8f440584e 100644 Binary files a/python/educational-python/resources/icons/com/jetbrains/edu/student@2x.png and b/python/educational-python/resources/icons/com/jetbrains/edu/student@2x.png differ diff --git a/python/educational-python/resources/icons/com/jetbrains/edu/student@2x_dark.png b/python/educational-python/resources/icons/com/jetbrains/edu/student@2x_dark.png index 711cae10fad6..a7ca5d9581fe 100644 Binary files a/python/educational-python/resources/icons/com/jetbrains/edu/student@2x_dark.png and b/python/educational-python/resources/icons/com/jetbrains/edu/student@2x_dark.png differ diff --git a/python/educational-python/resources/icons/com/jetbrains/edu/studentHover.png b/python/educational-python/resources/icons/com/jetbrains/edu/studentHover.png index 48bf5c6665eb..64a0ff68766f 100644 Binary files a/python/educational-python/resources/icons/com/jetbrains/edu/studentHover.png and b/python/educational-python/resources/icons/com/jetbrains/edu/studentHover.png differ diff --git a/python/educational-python/resources/icons/com/jetbrains/edu/studentHover@2x.png b/python/educational-python/resources/icons/com/jetbrains/edu/studentHover@2x.png index ad45e3a1d67c..b60140a4bbd8 100644 Binary files a/python/educational-python/resources/icons/com/jetbrains/edu/studentHover@2x.png and b/python/educational-python/resources/icons/com/jetbrains/edu/studentHover@2x.png differ diff --git a/python/educational-python/resources/icons/com/jetbrains/edu/studentHover@2x_dark.png b/python/educational-python/resources/icons/com/jetbrains/edu/studentHover@2x_dark.png index de5d65ecb72a..4d8207a2656d 100644 Binary files a/python/educational-python/resources/icons/com/jetbrains/edu/studentHover@2x_dark.png and b/python/educational-python/resources/icons/com/jetbrains/edu/studentHover@2x_dark.png differ diff --git a/python/educational-python/resources/icons/com/jetbrains/edu/studentHover_dark.png b/python/educational-python/resources/icons/com/jetbrains/edu/studentHover_dark.png index e0ed7796e598..b5385bad9a50 100644 Binary files a/python/educational-python/resources/icons/com/jetbrains/edu/studentHover_dark.png and b/python/educational-python/resources/icons/com/jetbrains/edu/studentHover_dark.png differ diff --git a/python/educational-python/resources/icons/com/jetbrains/edu/student_dark.png b/python/educational-python/resources/icons/com/jetbrains/edu/student_dark.png index 1ef084e59f30..4efad501f167 100644 Binary files a/python/educational-python/resources/icons/com/jetbrains/edu/student_dark.png and b/python/educational-python/resources/icons/com/jetbrains/edu/student_dark.png differ diff --git a/python/educational-python/resources/icons/com/jetbrains/edu/teacher.png b/python/educational-python/resources/icons/com/jetbrains/edu/teacher.png index 05469ef496ef..28da147975cb 100644 Binary files a/python/educational-python/resources/icons/com/jetbrains/edu/teacher.png and b/python/educational-python/resources/icons/com/jetbrains/edu/teacher.png differ diff --git a/python/educational-python/resources/icons/com/jetbrains/edu/teacher@2x.png b/python/educational-python/resources/icons/com/jetbrains/edu/teacher@2x.png index a8baaa3609e0..394058469d47 100644 Binary files a/python/educational-python/resources/icons/com/jetbrains/edu/teacher@2x.png and b/python/educational-python/resources/icons/com/jetbrains/edu/teacher@2x.png differ diff --git a/python/educational-python/resources/icons/com/jetbrains/edu/teacher@2x_dark.png b/python/educational-python/resources/icons/com/jetbrains/edu/teacher@2x_dark.png index 42849b4e98af..cb19d80c3212 100644 Binary files a/python/educational-python/resources/icons/com/jetbrains/edu/teacher@2x_dark.png and b/python/educational-python/resources/icons/com/jetbrains/edu/teacher@2x_dark.png differ diff --git a/python/educational-python/resources/icons/com/jetbrains/edu/teacherHover.png b/python/educational-python/resources/icons/com/jetbrains/edu/teacherHover.png index 61de62256a82..f69ee7b81849 100644 Binary files a/python/educational-python/resources/icons/com/jetbrains/edu/teacherHover.png and b/python/educational-python/resources/icons/com/jetbrains/edu/teacherHover.png differ diff --git a/python/educational-python/resources/icons/com/jetbrains/edu/teacherHover@2x.png b/python/educational-python/resources/icons/com/jetbrains/edu/teacherHover@2x.png index 2f6bad0f8c01..2ee14a703210 100644 Binary files a/python/educational-python/resources/icons/com/jetbrains/edu/teacherHover@2x.png and b/python/educational-python/resources/icons/com/jetbrains/edu/teacherHover@2x.png differ diff --git a/python/educational-python/resources/icons/com/jetbrains/edu/teacherHover@2x_dark.png b/python/educational-python/resources/icons/com/jetbrains/edu/teacherHover@2x_dark.png index 579c036d4240..ce43e8255fc0 100644 Binary files a/python/educational-python/resources/icons/com/jetbrains/edu/teacherHover@2x_dark.png and b/python/educational-python/resources/icons/com/jetbrains/edu/teacherHover@2x_dark.png differ diff --git a/python/educational-python/resources/icons/com/jetbrains/edu/teacherHover_dark.png b/python/educational-python/resources/icons/com/jetbrains/edu/teacherHover_dark.png index 3ca4cc31cbb8..549874805389 100644 Binary files a/python/educational-python/resources/icons/com/jetbrains/edu/teacherHover_dark.png and b/python/educational-python/resources/icons/com/jetbrains/edu/teacherHover_dark.png differ diff --git a/python/educational-python/resources/icons/com/jetbrains/edu/teacher_dark.png b/python/educational-python/resources/icons/com/jetbrains/edu/teacher_dark.png index e35aeb7b2921..71aeb7440711 100644 Binary files a/python/educational-python/resources/icons/com/jetbrains/edu/teacher_dark.png and b/python/educational-python/resources/icons/com/jetbrains/edu/teacher_dark.png differ diff --git a/python/educational-python/resources/idea/PyCharmEduApplicationInfo.xml b/python/educational-python/resources/idea/PyCharmEduApplicationInfo.xml index d4cd6eba98c6..98e6a88c1114 100644 --- a/python/educational-python/resources/idea/PyCharmEduApplicationInfo.xml +++ b/python/educational-python/resources/idea/PyCharmEduApplicationInfo.xml @@ -4,9 +4,9 @@ - - + + diff --git a/python/educational-python/resources/pycharm_edu_about.png b/python/educational-python/resources/pycharm_edu_about.png index d70a6109632e..1a0aed5f5db6 100644 Binary files a/python/educational-python/resources/pycharm_edu_about.png and b/python/educational-python/resources/pycharm_edu_about.png differ diff --git a/python/educational-python/resources/pycharm_edu_about@2x.png b/python/educational-python/resources/pycharm_edu_about@2x.png index afc894234411..f7c32affebbc 100644 Binary files a/python/educational-python/resources/pycharm_edu_about@2x.png and b/python/educational-python/resources/pycharm_edu_about@2x.png differ diff --git a/python/educational-python/resources/pycharm_edu_logo.png b/python/educational-python/resources/pycharm_edu_logo.png index 551af6e61fa3..981544f1400d 100644 Binary files a/python/educational-python/resources/pycharm_edu_logo.png and b/python/educational-python/resources/pycharm_edu_logo.png differ diff --git a/python/educational-python/resources/pycharm_edu_logo@2x.png b/python/educational-python/resources/pycharm_edu_logo@2x.png index 8d06c38c3e18..0740350cea71 100644 Binary files a/python/educational-python/resources/pycharm_edu_logo@2x.png and b/python/educational-python/resources/pycharm_edu_logo@2x.png differ diff --git a/python/educational-python/src/com/jetbrains/python/edu/CustomizeEduStepPanel.java b/python/educational-python/src/com/jetbrains/python/edu/CustomizeEduStepPanel.java index b2f1749914da..5c8f2bcedf10 100644 --- a/python/educational-python/src/com/jetbrains/python/edu/CustomizeEduStepPanel.java +++ b/python/educational-python/src/com/jetbrains/python/edu/CustomizeEduStepPanel.java @@ -2,6 +2,7 @@ package com.jetbrains.python.edu; import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.ui.VerticalFlowLayout; +import com.intellij.util.ui.JBUI; import icons.PythonEducationalIcons; import javax.swing.*; @@ -17,7 +18,7 @@ public class CustomizeEduStepPanel extends JPanel { final FlowLayout layout = new FlowLayout(); layout.setHgap(20); setLayout(layout); - int iconSize = 180; + int iconSize = JBUI.scale(180); final JPanel studentPanel = new JPanel(new VerticalFlowLayout()); final JLabel teacher = new JLabel(PythonEducationalIcons.Teacher); myStudent = new JLabel(PythonEducationalIcons.StudentHover); diff --git a/python/helpers/generator3.py b/python/helpers/generator3.py index 322c66ccb43d..de526b14cb50 100644 --- a/python/helpers/generator3.py +++ b/python/helpers/generator3.py @@ -1,7 +1,6 @@ # encoding: utf-8 import atexit import zipfile -import shutil # TODO: Move all CLR-specific functions to clr_tools @@ -95,7 +94,8 @@ def is_skipped_module(path, f): def is_module(d, root): return (os.path.exists(os.path.join(root, d, "__init__.py")) or os.path.exists(os.path.join(root, d, "__init__.pyc")) or - os.path.exists(os.path.join(root, d, "__init__.pyo"))) + os.path.exists(os.path.join(root, d, "__init__.pyo")) or + is_valid_implicit_namespace_package_name(d)) def walk_python_path(path): @@ -154,7 +154,7 @@ def list_binaries(paths): return list(res.values()) -def list_sources(paths, target_path): +def list_sources(paths): #noinspection PyBroadException try: for path in paths: @@ -162,29 +162,14 @@ def list_sources(paths, target_path): path = os.path.normpath(path) - target_dir_path = '' - extra_info = '' - if path.endswith('.egg') and os.path.isfile(path): - if target_path is not None: - extra_info = '\t' + os.path.basename(path) - say("%s\t%s\t%d%s", path, path, os.path.getsize(path), extra_info) - else: - target_dir_path = compute_path_hash(path) - if target_path is not None: - extra_info = '\t' + target_dir_path + say("%s\t%s\t%d", path, path, os.path.getsize(path)) for root, files in walk_python_path(path): for name in files: - if name.endswith('.py'): + if name.endswith('.py') or name.endswith('-nspkg.pth'): file_path = os.path.join(root, name) - if target_path is not None: - relpath = os.path.relpath(root, path) - folder_path = os.path.join(target_path, target_dir_path, relpath) - if not os.path.exists(folder_path): - os.makedirs(folder_path) - shutil.copyfile(file_path, os.path.join(folder_path, name)) - say("%s\t%s\t%d%s", os.path.normpath(file_path), path, os.path.getsize(file_path), extra_info) + say("%s\t%s\t%d", os.path.normpath(file_path), path, os.path.getsize(file_path)) say('END') sys.stdout.flush() except: @@ -193,13 +178,6 @@ def list_sources(paths, target_path): traceback.print_exc() sys.exit(1) -def compute_path_hash(path): - # computes hash string of provided path - h = 0 - for c in path: - h = (31 * h + ord(c)) & 0xFFFFFFFF - h = ((h + 0x80000000) & 0xFFFFFFFF) - 0x80000000 - return str(h) #noinspection PyBroadException def zip_sources(zip_path): @@ -366,7 +344,7 @@ if __name__ == "__main__": from getopt import getopt helptext = get_help_text() - opts, args = getopt(sys.argv[1:], "d:hbqxvc:ps:C:LiSz") + opts, args = getopt(sys.argv[1:], "d:hbqxvc:ps:LiSz") opts = dict(opts) quiet = '-q' in opts @@ -410,7 +388,7 @@ if __name__ == "__main__": report("Expected no args with -S, got %d args", len(args)) sys.exit(1) say(VERSION) - list_sources(sys.path, opts.get('-C', None)) + list_sources(sys.path) sys.exit(0) if "-z" in opts: diff --git a/python/helpers/pycharm/__jb.for_twisted/twisted/plugins/README.txt b/python/helpers/pycharm/__jb.for_twisted/twisted/plugins/README.txt new file mode 100644 index 000000000000..1456368d1d62 --- /dev/null +++ b/python/helpers/pycharm/__jb.for_twisted/twisted/plugins/README.txt @@ -0,0 +1,5 @@ +Plugin to support twisted trial. + +From: http://twistedmatrix.com/documents/13.1.0/core/howto/plugin.html + if a directory which has been added to sys.path (typically by adding it to the PYTHONPATH environment variable) contains a directory + named twisted/plugins/, each .py file in that directory will be loaded as a source of plugins. \ No newline at end of file diff --git a/python/helpers/pycharm/__jb.for_twisted/twisted/plugins/teamcity_plugin.py b/python/helpers/pycharm/__jb.for_twisted/twisted/plugins/teamcity_plugin.py new file mode 100644 index 000000000000..709c523da9c8 --- /dev/null +++ b/python/helpers/pycharm/__jb.for_twisted/twisted/plugins/teamcity_plugin.py @@ -0,0 +1,37 @@ +import sys +from teamcity.unittestpy import TeamcityTestResult +from twisted.trial.reporter import Reporter +from twisted.python.failure import Failure +from twisted.plugins.twisted_trial import _Reporter + + +class FailureWrapper(Failure): + + def __getitem__(self, key): + return self.value[key] + + +class TeamcityReporter(TeamcityTestResult, Reporter): + + def __init__(self, + stream=sys.stdout, + tbformat='default', + realtime=False, + publisher=None): + TeamcityTestResult.__init__(self) + Reporter.__init__(self, + stream=stream, + tbformat=tbformat, + realtime=realtime, + publisher=publisher) + + def addError(self, test, failure, *k): + super(TeamcityReporter, self).addError(test, FailureWrapper(failure), *k) + + +Teamcity = _Reporter("Teamcity Reporter", + "twisted.plugins.teamcity_plugin", + description="teamcity messages", + longOpt="teamcity", + shortOpt="teamcity", + klass="TeamcityReporter") diff --git a/python/helpers/pycharm/_jb_runner_tools.py b/python/helpers/pycharm/_jb_runner_tools.py index 0b3089241f72..9588590009f8 100644 --- a/python/helpers/pycharm/_jb_runner_tools.py +++ b/python/helpers/pycharm/_jb_runner_tools.py @@ -18,7 +18,7 @@ if teamcity_presence_env_var not in os.environ: # anything sent to stdout/stderr goes to IDE directly, not after test is over like it is done by default. # out and err are not in sync, so output may go to wrong test JB_DISABLE_BUFFERING = "JB_DISABLE_BUFFERING" in os.environ - +PROJECT_DIR = os.getcwd() def _parse_parametrized(part): """ @@ -179,7 +179,7 @@ class NewTeamcityServiceMessages(_old_service_messages): loc = possible_location.find("(") if loc > 0: possible_location = possible_location[:loc].strip() - properties["locationHint"] = "python<{0}>://{1}".format(os.getcwd(), possible_location) + properties["locationHint"] = "python<{0}>://{1}".format(PROJECT_DIR, possible_location) except KeyError: # If message does not have name, then it is not test # Simply pass it @@ -399,4 +399,4 @@ def jb_doc_args(framework_name, args): Runner encouraged to report its arguments to user with aid of this function """ - print("Launching {0} with arguments {1} in {2}\n".format(framework_name, " ".join(args), os.getcwd())) + print("Launching {0} with arguments {1} in {2}\n".format(framework_name, " ".join(args), PROJECT_DIR)) diff --git a/python/helpers/pycharm/_jb_trialtest_runner.py b/python/helpers/pycharm/_jb_trialtest_runner.py new file mode 100644 index 000000000000..6d1b7f56bd09 --- /dev/null +++ b/python/helpers/pycharm/_jb_trialtest_runner.py @@ -0,0 +1,29 @@ +# coding=utf-8 +import os +from pprint import pprint + +from _jb_runner_tools import jb_start_tests, jb_doc_args +from twisted.scripts import trial +import sys + + +if __name__ == '__main__': + # This folder should be in sys.path because teamcity twisted plugin is there + sys.path.append(os.path.join(os.path.dirname(__file__), "__jb.for_twisted")) + + path, targets, additional_args = jb_start_tests() + + sys.path.append(os.getcwd()) # Current dir must be in sys.path according to trial docs + + sys.argv.append("--reporter=teamcity") + sys.argv += additional_args + + if path: + assert os.path.exists(path), path + " does not exist" + # assert os.path.isfile(path), path + " is folder. Provide its name as python target (dot separated)" + sys.argv.append(os.path.normpath(path)) + elif targets: + sys.argv += targets + + jb_doc_args("trial", sys.argv[1:]) + trial.run() diff --git a/python/helpers/pycharm/teamcity/README.txt b/python/helpers/pycharm/teamcity/README.txt index 9e24110cfb9e..d33277f12f70 100644 --- a/python/helpers/pycharm/teamcity/README.txt +++ b/python/helpers/pycharm/teamcity/README.txt @@ -1,4 +1,7 @@ Test runners wraps python framework and provides teamcity protocol. They are are distributed separately. Copy latest version from https://github.com/JetBrains/teamcity-messages.git/teamcity -See https://confluence.jetbrains.com/display/~link/PyCharm+test+runners+protocol \ No newline at end of file +See https://confluence.jetbrains.com/display/~link/PyCharm+test+runners+protocol + +When updating, place "twisted.plugins" one level up because "twisted" should be in sys.path. +See twisted/plugins/README.txt \ No newline at end of file diff --git a/python/helpers/pycharm_generator_utils/util_methods.py b/python/helpers/pycharm_generator_utils/util_methods.py index 42451811389c..f5dca1e41e5a 100644 --- a/python/helpers/pycharm_generator_utils/util_methods.py +++ b/python/helpers/pycharm_generator_utils/util_methods.py @@ -704,3 +704,28 @@ def build_output_name(dirname, qualified_name): os.makedirs(dirname) return fname + + +def is_valid_implicit_namespace_package_name(s): + """ + Checks whether provided string could represent implicit namespace package name. + :param s: string to check + :return: True if provided string could represent implicit namespace package name and False otherwise + """ + return isidentifier(s) and not keyword.iskeyword(s) + + +def isidentifier(s): + """ + Checks whether provided string complies Python identifier syntax requirements. + :param s: string to check + :return: True if provided string comply Python identifier syntax requirements and False otherwise + """ + if version[0] >= 3: + return s.isidentifier() + else: + # quick test on provided string to comply major Python identifier syntax requirements + return (s and + not s[:1].isdigit() and + "-" not in s and + " " not in s) diff --git a/python/helpers/pycodestyle.py b/python/helpers/pycodestyle.py index 70263968d7c5..5d8c2ac1cffd 100755 --- a/python/helpers/pycodestyle.py +++ b/python/helpers/pycodestyle.py @@ -48,33 +48,35 @@ W warnings """ from __future__ import with_statement -import os -import sys -import re -import time import inspect import keyword +import os +import re +import sys +import time import tokenize import warnings -from optparse import OptionParser + from fnmatch import fnmatch +from optparse import OptionParser + try: from configparser import RawConfigParser from io import TextIOWrapper except ImportError: from ConfigParser import RawConfigParser -__version__ = '2.0.0' +__version__ = '2.3.1' DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__,.tox' DEFAULT_IGNORE = 'E121,E123,E126,E226,E24,E704,W503' try: if sys.platform == 'win32': - USER_CONFIG = os.path.expanduser(r'~\.pep8') + USER_CONFIG = os.path.expanduser(r'~\.pycodestyle') else: USER_CONFIG = os.path.join( os.getenv('XDG_CONFIG_HOME') or os.path.expanduser('~/.config'), - 'pep8' + 'pycodestyle' ) except ImportError: USER_CONFIG = None @@ -119,6 +121,20 @@ KEYWORD_REGEX = re.compile(r'(\s*)\b(?:%s)\b(\s*)' % r'|'.join(KEYWORDS)) OPERATOR_REGEX = re.compile(r'(?:[^,\s])(\s*)(?:[-+*/|!<=>%&^]+)(\s*)') LAMBDA_REGEX = re.compile(r'\blambda\b') HUNK_REGEX = re.compile(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$') +STARTSWITH_DEF_REGEX = re.compile(r'^(async\s+def|def)') +STARTSWITH_TOP_LEVEL_REGEX = re.compile(r'^(async\s+def\s+|def\s+|class\s+|@)') +STARTSWITH_INDENT_STATEMENT_REGEX = re.compile( + r'^\s*({0})'.format('|'.join(s.replace(' ', '\s+') for s in ( + 'def', 'async def', + 'for', 'async for', + 'if', 'elif', 'else', + 'try', 'except', 'finally', + 'with', 'async with', + 'class', + 'while', + ))) +) +DUNDER_REGEX = re.compile(r'^__([^\s]+)__ = ') # Work around Python < 2.6 behaviour, which does not generate NL after # a comment which is on a line by itself. @@ -197,7 +213,7 @@ def trailing_blank_lines(physical_line, lines, line_number, total_lines): return len(physical_line), "W292 no newline at end of file" -def maximum_line_length(physical_line, max_line_length, multiline): +def maximum_line_length(physical_line, max_line_length, multiline, noqa): r"""Limit all lines to a maximum of 79 characters. There are still many devices around that are limited to 80 character @@ -211,7 +227,7 @@ def maximum_line_length(physical_line, max_line_length, multiline): """ line = physical_line.rstrip() length = len(line) - if length > max_line_length and not noqa(line): + if length > max_line_length and not noqa: # Special case for long URLs in multi-line docstrings or comments, # but still report the error when the 72 first chars are whitespaces. chunks = line.split() @@ -236,7 +252,9 @@ def maximum_line_length(physical_line, max_line_length, multiline): def blank_lines(logical_line, blank_lines, indent_level, line_number, - blank_before, previous_logical, previous_indent_level): + blank_before, previous_logical, + previous_unindented_logical_line, previous_indent_level, + lines): r"""Separate top-level function and class definitions with two blank lines. Method definitions inside a class are separated by a single blank line. @@ -248,13 +266,19 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, Use blank lines in functions, sparingly, to indicate logical sections. Okay: def a():\n pass\n\n\ndef b():\n pass + Okay: def a():\n pass\n\n\nasync def b():\n pass Okay: def a():\n pass\n\n\n# Foo\n# Bar\n\ndef b():\n pass + Okay: default = 1\nfoo = 1 + Okay: classify = 1\nfoo = 1 E301: class Foo:\n b = 0\n def bar():\n pass E302: def a():\n pass\n\ndef b(n):\n pass + E302: def a():\n pass\n\nasync def b(n):\n pass E303: def a():\n pass\n\n\n\ndef b(n):\n pass E303: def a():\n\n\n\n pass E304: @decorator\n\ndef a():\n pass + E305: def a():\n pass\na() + E306: def a():\n def b():\n pass\n def c():\n pass """ if line_number < 3 and not previous_logical: return # Don't expect blank lines before the first line @@ -263,13 +287,30 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, yield 0, "E304 blank lines found after function decorator" elif blank_lines > 2 or (indent_level and blank_lines == 2): yield 0, "E303 too many blank lines (%d)" % blank_lines - elif logical_line.startswith(('def ', 'class ', '@')): + elif STARTSWITH_TOP_LEVEL_REGEX.match(logical_line): if indent_level: if not (blank_before or previous_indent_level < indent_level or DOCSTRING_REGEX.match(previous_logical)): - yield 0, "E301 expected 1 blank line, found 0" + ancestor_level = indent_level + nested = False + # Search backwards for a def ancestor or tree root (top level). + for line in lines[line_number - 2::-1]: + if line.strip() and expand_indent(line) < ancestor_level: + ancestor_level = expand_indent(line) + nested = line.lstrip().startswith('def ') + if nested or ancestor_level == 0: + break + if nested: + yield 0, "E306 expected 1 blank line before a " \ + "nested definition, found 0" + else: + yield 0, "E301 expected 1 blank line, found 0" elif blank_before != 2: yield 0, "E302 expected 2 blank lines, found %d" % blank_before + elif (logical_line and not indent_level and blank_before != 2 and + previous_unindented_logical_line.startswith(('def ', 'class '))): + yield 0, "E305 expected 2 blank lines after " \ + "class or function definition, found %d" % blank_before def extraneous_whitespace(logical_line): @@ -787,7 +828,7 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): no_space = False prev_end = None annotated_func_arg = False - in_def = logical_line.startswith(('def', 'async def')) + in_def = bool(STARTSWITH_DEF_REGEX.match(logical_line)) message = "E251 unexpected spaces around keyword / parameter equals" for token_type, text, start, end, line in tokens: if token_type == tokenize.NL: @@ -886,8 +927,10 @@ def module_imports_on_top_of_file( Okay: # this is a comment\nimport os Okay: '''this is a module docstring'''\nimport os Okay: r'''this is a module docstring'''\nimport os - Okay: try:\n import x\nexcept:\n pass\nelse:\n pass\nimport y - Okay: try:\n import x\nexcept:\n pass\nfinally:\n pass\nimport y + Okay: + try:\n\timport x\nexcept ImportError:\n\tpass\nelse:\n\tpass\nimport y + Okay: + try:\n\timport x\nexcept ImportError:\n\tpass\nfinally:\n\tpass\nimport y E402: a=1\nimport os E402: 'One string'\n"Two string"\nimport os E402: a=1\nfrom sys import x @@ -913,6 +956,8 @@ def module_imports_on_top_of_file( if line.startswith('import ') or line.startswith('from '): if checker_state.get('seen_non_imports', False): yield 0, "E402 module level import not at top of file" + elif re.match(DUNDER_REGEX, line): + return elif any(line.startswith(kw) for kw in allowed_try_keywords): # Allow try, except, else, finally keywords intermixed with imports in # order to support conditional importing @@ -958,22 +1003,25 @@ def compound_statements(logical_line): line = logical_line last_char = len(line) - 1 found = line.find(':') + prev_found = 0 + counts = dict((char, 0) for char in '{}[]()') while -1 < found < last_char: - before = line[:found] - if ((before.count('{') <= before.count('}') and # {'a': 1} (dict) - before.count('[') <= before.count(']') and # [1:2] (slice) - before.count('(') <= before.count(')'))): # (annotation) - lambda_kw = LAMBDA_REGEX.search(before) + update_counts(line[prev_found:found], counts) + if ((counts['{'] <= counts['}'] and # {'a': 1} (dict) + counts['['] <= counts[']'] and # [1:2] (slice) + counts['('] <= counts[')'])): # (annotation) + lambda_kw = LAMBDA_REGEX.search(line, 0, found) if lambda_kw: before = line[:lambda_kw.start()].rstrip() if before[-1:] == '=' and isidentifier(before[:-1].strip()): yield 0, ("E731 do not assign a lambda expression, use a " "def") break - if before.startswith('def '): + if STARTSWITH_DEF_REGEX.match(line): yield 0, "E704 multiple statements on one line (def)" - else: + elif STARTSWITH_INDENT_STATEMENT_REGEX.match(line): yield found, "E701 multiple statements on one line (colon)" + prev_found = found found = line.find(':', found + 1) found = line.find(';') while -1 < found: @@ -1150,6 +1198,74 @@ def comparison_type(logical_line, noqa): yield match.start(), "E721 do not compare types, use 'isinstance()'" +def bare_except(logical_line, noqa): + r"""When catching exceptions, mention specific exceptions whenever possible. + + Okay: except Exception: + Okay: except BaseException: + E722: except: + """ + if noqa: + return + + regex = re.compile(r"except\s*:") + match = regex.match(logical_line) + if match: + yield match.start(), "E722 do not use bare except'" + + +def ambiguous_identifier(logical_line, tokens): + r"""Never use the characters 'l', 'O', or 'I' as variable names. + + In some fonts, these characters are indistinguishable from the numerals + one and zero. When tempted to use 'l', use 'L' instead. + + Okay: L = 0 + Okay: o = 123 + Okay: i = 42 + E741: l = 0 + E741: O = 123 + E741: I = 42 + + Variables can be bound in several other contexts, including class and + function definitions, 'global' and 'nonlocal' statements, exception + handlers, and 'with' statements. + + Okay: except AttributeError as o: + Okay: with lock as L: + E741: except AttributeError as O: + E741: with lock as l: + E741: global I + E741: nonlocal l + E742: class I(object): + E743: def l(x): + """ + idents_to_avoid = ('l', 'O', 'I') + prev_type, prev_text, prev_start, prev_end, __ = tokens[0] + for token_type, text, start, end, line in tokens[1:]: + ident = pos = None + # identifiers on the lhs of an assignment operator + if token_type == tokenize.OP and '=' in text: + if prev_text in idents_to_avoid: + ident = prev_text + pos = prev_start + # identifiers bound to a value with 'as', 'global', or 'nonlocal' + if prev_text in ('as', 'global', 'nonlocal'): + if text in idents_to_avoid: + ident = text + pos = start + if prev_text == 'class': + if text in idents_to_avoid: + yield start, "E742 ambiguous class definition '%s'" % text + if prev_text == 'def': + if text in idents_to_avoid: + yield start, "E743 ambiguous function definition '%s'" % text + if ident: + yield pos, "E741 ambiguous variable name '%s'" % ident + prev_text = text + prev_start = start + + def python_3000_has_key(logical_line, noqa): r"""The {}.has_key() method is removed in Python 3: use the 'in' operator. @@ -1219,7 +1335,7 @@ else: with open(filename, 'rb') as f: (coding, lines) = tokenize.detect_encoding(f.readline) f = TextIOWrapper(f, coding, line_buffering=True) - return [l.decode(coding) for l in lines] + f.readlines() + return [line.decode(coding) for line in lines] + f.readlines() except (LookupError, SyntaxError, UnicodeError): # Fall back if file encoding is improperly declared with open(filename, encoding='latin-1') as f: @@ -1333,8 +1449,18 @@ def filename_match(filename, patterns, default=True): return any(fnmatch(filename, pattern) for pattern in patterns) +def update_counts(s, counts): + r"""Adds one to the counts of each appearance of characters in s, + for characters in counts""" + for char in s: + if char in counts: + counts[char] += 1 + + def _is_eol_token(token): return token[0] in NEWLINE or token[4][token[3][1]:].lstrip() == '\\\n' + + if COMMENT_WITH_NL: def _is_eol_token(token, _eol_token=_is_eol_token): return _eol_token(token) or (token[0] == tokenize.COMMENT and @@ -1384,6 +1510,8 @@ def init_checks_registry(): mod = inspect.getmodule(register_check) for (name, function) in inspect.getmembers(mod, inspect.isfunction): register_check(function) + + init_checks_registry() @@ -1431,6 +1559,7 @@ class Checker(object): self.lines[0] = self.lines[0][3:] self.report = report or options.report self.report_error = self.report.error + self.noqa = False def report_invalid_syntax(self): """Check if the syntax is valid.""" @@ -1541,6 +1670,8 @@ class Checker(object): if self.logical_line: self.previous_indent_level = self.indent_level self.previous_logical = self.logical_line + if not self.indent_level: + self.previous_unindented_logical_line = self.logical_line self.blank_lines = 0 self.tokens = [] @@ -1565,6 +1696,7 @@ class Checker(object): for token in tokengen: if token[2][0] > self.total_lines: return + self.noqa = token[4] and noqa(token[4]) self.maybe_check_physical(token) yield token except (SyntaxError, tokenize.TokenError): @@ -1610,6 +1742,7 @@ class Checker(object): self.indent_char = None self.indent_level = self.previous_indent_level = 0 self.previous_logical = '' + self.previous_unindented_logical_line = '' self.tokens = [] self.blank_lines = self.blank_before = 0 parens = 0 @@ -1952,7 +2085,7 @@ class StyleGuide(object): return sorted(checks) -def get_parser(prog='pep8', version=__version__): +def get_parser(prog='pycodestyle', version=__version__): """Create the parser for the program.""" parser = OptionParser(prog=prog, version=version, usage="%prog [options] input ...") @@ -2020,7 +2153,7 @@ def read_config(options, args, arglist, parser): If a config file is specified on the command line with the "--config" option, then only it is used for configuration. - Otherwise, the user configuration (~/.config/pep8) and any local + Otherwise, the user configuration (~/.config/pycodestyle) and any local configurations in the current directory or above will be merged together (in that order) using the read method of ConfigParser. """ @@ -2049,8 +2182,14 @@ def read_config(options, args, arglist, parser): print('cli configuration: %s' % cli_conf) config.read(cli_conf) - pep8_section = parser.prog - if config.has_section(pep8_section): + pycodestyle_section = None + if config.has_section(parser.prog): + pycodestyle_section = parser.prog + elif config.has_section('pep8'): + pycodestyle_section = 'pep8' # Deprecated + warnings.warn('[pep8] section is deprecated. Use [pycodestyle].') + + if pycodestyle_section: option_list = dict([(o.dest, o.type or o.action) for o in parser.option_list]) @@ -2058,23 +2197,23 @@ def read_config(options, args, arglist, parser): (new_options, __) = parser.parse_args([]) # Second, parse the configuration - for opt in config.options(pep8_section): + for opt in config.options(pycodestyle_section): if opt.replace('_', '-') not in parser.config_options: print(" unknown option '%s' ignored" % opt) continue if options.verbose > 1: - print(" %s = %s" % (opt, config.get(pep8_section, opt))) + print(" %s = %s" % (opt, + config.get(pycodestyle_section, opt))) normalized_opt = opt.replace('-', '_') opt_type = option_list[normalized_opt] if opt_type in ('int', 'count'): - value = config.getint(pep8_section, opt) - elif opt_type == 'string': - value = config.get(pep8_section, opt) + value = config.getint(pycodestyle_section, opt) + elif opt_type in ('store_true', 'store_false'): + value = config.getboolean(pycodestyle_section, opt) + else: + value = config.get(pycodestyle_section, opt) if normalized_opt == 'exclude': value = normalize_paths(value, local_dir) - else: - assert opt_type in ('store_true', 'store_false') - value = config.getboolean(pep8_section, opt) setattr(new_options, normalized_opt, value) # Third, overwrite with the command-line options @@ -2088,7 +2227,7 @@ def process_options(arglist=None, parse_argv=False, config_file=None, """Process options passed either via arglist or via command line args. Passing in the ``config_file`` parameter allows other tools, such as flake8 - to specify their own options to be processed in pep8. + to specify their own options to be processed in pycodestyle. """ if not parser: parser = get_parser() diff --git a/python/helpers/pydev/_pydev_bundle/pydev_ipython_console_011.py b/python/helpers/pydev/_pydev_bundle/pydev_ipython_console_011.py index 309da1989720..d9d4d63233ca 100644 --- a/python/helpers/pydev/_pydev_bundle/pydev_ipython_console_011.py +++ b/python/helpers/pydev/_pydev_bundle/pydev_ipython_console_011.py @@ -335,7 +335,8 @@ class _PyDevFrontEnd: ns = self.ipython.user_ns for key in dict_keys(self.ipython.user_ns): - locals[key] = ns[key] + if key not in locals: + locals[key] = ns[key] self.ipython.user_global_ns.clear() self.ipython.user_global_ns.update(globals) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py b/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py index 07d72ea81e94..5d75fe1ab472 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py @@ -657,7 +657,7 @@ class NetCommandFactory: except: return self.make_error_message(0, get_exception_traceback_str()) - def make_thread_suspend_str(self, thread_id, frame, stop_reason, message): + def make_thread_suspend_str(self, thread_id, frame, stop_reason, message, suspend_type="trace"): """ @@ -672,7 +672,7 @@ class NetCommandFactory: if message: message = make_valid_xml_value(message) - append('' % (thread_id, stop_reason, message)) + append('' % (thread_id, stop_reason, message, suspend_type)) curr_frame = frame try: @@ -719,9 +719,9 @@ class NetCommandFactory: append("") return ''.join(cmd_text_list) - def make_thread_suspend_message(self, thread_id, frame, stop_reason, message): + def make_thread_suspend_message(self, thread_id, frame, stop_reason, message, suspend_type): try: - return NetCommand(CMD_THREAD_SUSPEND, 0, self.make_thread_suspend_str(thread_id, frame, stop_reason, message)) + return NetCommand(CMD_THREAD_SUSPEND, 0, self.make_thread_suspend_str(thread_id, frame, stop_reason, message, suspend_type)) except: return self.make_error_message(0, get_exception_traceback_str()) diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_utils.py b/python/helpers/pydev/_pydevd_bundle/pydevd_utils.py index a5a2958d4a27..e51c07b248a8 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_utils.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_utils.py @@ -182,7 +182,7 @@ def not_in_project_roots(filename, filename_to_not_in_scope_cache={}): filename = os.path.abspath(filename) filename = os.path.normcase(filename) for root in project_roots: - if filename.startswith(root): + if len(root) > 0 and filename.startswith(root): filename_to_not_in_scope_cache[original_filename] = False break else: # for else (only called if the break wasn't reached). diff --git a/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_tracing.py b/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_tracing.py index e26e186a8934..62610b8cc160 100644 --- a/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_tracing.py +++ b/python/helpers/pydev/_pydevd_frame_eval/pydevd_frame_tracing.py @@ -78,7 +78,7 @@ def _pydev_stop_at_break(): if breakpoint and handle_breakpoint(frame, t, debugger, breakpoint): pydev_log.debug("Suspending at breakpoint in file: {} on line {}".format(frame.f_code.co_filename, line)) debugger.set_suspend(t, CMD_SET_BREAK) - debugger.do_wait_suspend(t, frame, 'line', None) + debugger.do_wait_suspend(t, frame, 'line', None, "frame_eval") t.additional_info.is_tracing = False diff --git a/python/helpers/pydev/pydevconsole.py b/python/helpers/pydev/pydevconsole.py index 15873b932fd0..daa01e3a5611 100644 --- a/python/helpers/pydev/pydevconsole.py +++ b/python/helpers/pydev/pydevconsole.py @@ -351,12 +351,15 @@ def start_console_server(host, port, interpreter): return server -def start_server(host, port, client_port): +def start_server(host, port, client_port, client_host = None): + if not client_host: + client_host = host + #replace exit (see comments on method) #note that this does not work in jython!!! (sys method can't be replaced). sys.exit = do_exit - interpreter = InterpreterInterface(host, client_port, threading.currentThread()) + interpreter = InterpreterInterface(client_host, client_port, threading.currentThread()) start_new_thread(start_console_server,(host, port, interpreter)) @@ -530,4 +533,10 @@ if __name__ == '__main__': client_port = p - pydevconsole.start_server(pydev_localhost.get_localhost(), int(port), int(client_port)) + if len(sys.argv) > 4: + host = sys.argv[3] + client_host = sys.argv[4] + else: + host = client_host = pydev_localhost.get_localhost() + + pydevconsole.start_server(host, int(port), int(client_port), client_host) diff --git a/python/helpers/pydev/pydevd.py b/python/helpers/pydev/pydevd.py index 45e1b0b9ff38..4deb0f1e42bc 100644 --- a/python/helpers/pydev/pydevd.py +++ b/python/helpers/pydev/pydevd.py @@ -725,7 +725,7 @@ class PyDB: self.writer.add_command(cmd) - def do_wait_suspend(self, thread, frame, event, arg): #@UnusedVariable + def do_wait_suspend(self, thread, frame, event, arg, suspend_type="trace"): #@UnusedVariable """ busy waits until the thread state changes to RUN it expects thread's state as attributes of the thread. Upon running, processes any outstanding Stepping commands. @@ -734,7 +734,7 @@ class PyDB: message = thread.additional_info.pydev_message - cmd = self.cmd_factory.make_thread_suspend_message(get_thread_id(thread), frame, thread.stop_reason, message) + cmd = self.cmd_factory.make_thread_suspend_message(get_thread_id(thread), frame, thread.stop_reason, message, suspend_type) self.writer.add_command(cmd) CustomFramesContainer.custom_frames_lock.acquire() # @UndefinedVariable @@ -745,7 +745,7 @@ class PyDB: if custom_frame.thread_id == thread.ident: # print >> sys.stderr, 'Frame created: ', frame_id self.writer.add_command(self.cmd_factory.make_custom_frame_created_message(frame_id, custom_frame.name)) - self.writer.add_command(self.cmd_factory.make_thread_suspend_message(frame_id, custom_frame.frame, CMD_THREAD_SUSPEND, "")) + self.writer.add_command(self.cmd_factory.make_thread_suspend_message(frame_id, custom_frame.frame, CMD_THREAD_SUSPEND, "", suspend_type)) from_this_thread.append(frame_id) @@ -807,7 +807,7 @@ class PyDB: stop = True if stop: info.pydev_state = STATE_SUSPEND - self.do_wait_suspend(thread, frame, event, arg) + self.do_wait_suspend(thread, frame, event, arg, "trace") return @@ -857,7 +857,7 @@ class PyDB: try: add_exception_to_frame(frame, exception) self.set_suspend(thread, CMD_ADD_EXCEPTION_BREAK) - self.do_wait_suspend(thread, frame, 'exception', None) + self.do_wait_suspend(thread, frame, 'exception', None, "trace") except: pydev_log.error("We've got an error while stopping in post-mortem: %s\n"%sys.exc_info()[0]) finally: @@ -896,15 +896,18 @@ class PyDB: def prepare_to_run(self): ''' Shared code to prepare debugging by installing traces and registering threads ''' + if self.signature_factory is not None or self.thread_analyser is not None: + # we need all data to be sent to IDE even after program finishes + CheckOutputThread(self).start() + # turn off frame evaluation for concurrency visualization + self.frame_eval_func = None + self.patch_threads() pydevd_tracing.SetTrace(self.trace_dispatch, self.frame_eval_func, self.dummy_trace_dispatch) # There is no need to set tracing function if frame evaluation is available. Moreover, there is no need to patch thread # functions, because frame evaluation function is set to all threads by default. PyDBCommandThread(self).start() - if self.signature_factory is not None or self.thread_analyser is not None: - # we need all data to be sent to IDE even after program finishes - CheckOutputThread(self).start() if show_tracing_warning or show_frame_eval_warning: cmd = self.cmd_factory.make_show_cython_warning_message() diff --git a/python/helpers/pydev/tests_pydevd_python/debugger_unittest.py b/python/helpers/pydev/tests_pydevd_python/debugger_unittest.py index 9e1c42ea0929..8faf21f196bc 100644 --- a/python/helpers/pydev/tests_pydevd_python/debugger_unittest.py +++ b/python/helpers/pydev/tests_pydevd_python/debugger_unittest.py @@ -399,7 +399,10 @@ class AbstractWriterThread(threading.Thread): thread_id = splitted[3] return thread_id - def wait_for_breakpoint_hit(self, reason='111', get_line=False, get_name=False): + def wait_for_breakpoint_hit(self, *args, **kwargs): + return self.wait_for_breakpoint_hit_with_suspend_type(*args, **kwargs)[:-1] + + def wait_for_breakpoint_hit_with_suspend_type(self, reason='111', get_line=False, get_name=False): ''' 108 is over 109 is return @@ -419,25 +422,26 @@ class AbstractWriterThread(threading.Thread): # we have something like None: ... def __call__(self, *args: Any, **kwargs: Any) -> Any: ... class InstanceType: diff --git a/python/helpers/typeshed/stdlib/3/types.pyi b/python/helpers/typeshed/stdlib/3/types.pyi index 084a1b46ecac..ae923a200e4b 100644 --- a/python/helpers/typeshed/stdlib/3/types.pyi +++ b/python/helpers/typeshed/stdlib/3/types.pyi @@ -107,6 +107,7 @@ class CoroutineType: class MethodType: __func__ = ... # type: FunctionType __self__ = ... # type: object + def __init__(self, func: Callable, obj: object) -> None: ... def __call__(self, *args: Any, **kwargs: Any) -> Any: ... class BuiltinFunctionType: __self__ = ... # type: Union[object, ModuleType] diff --git a/python/ide/src/com/jetbrains/python/PyDirectoryIndexExcludePolicy.java b/python/ide/src/com/jetbrains/python/PyDirectoryIndexExcludePolicy.java index 01dfbae6970b..a11c075b3ffd 100644 --- a/python/ide/src/com/jetbrains/python/PyDirectoryIndexExcludePolicy.java +++ b/python/ide/src/com/jetbrains/python/PyDirectoryIndexExcludePolicy.java @@ -17,23 +17,31 @@ package com.jetbrains.python; import com.google.common.collect.Lists; import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.ModuleRootModel; +import com.intellij.openapi.roots.OrderRootType; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.roots.impl.DirectoryIndexExcludePolicy; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.pointers.VirtualFilePointer; +import com.intellij.util.Function; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * @author traff */ public class PyDirectoryIndexExcludePolicy implements DirectoryIndexExcludePolicy { - + private final static String[] SITE_PACKAGES = new String[]{"site-packages", "dist-packages"}; + private final Project myProject; - public PyDirectoryIndexExcludePolicy(@NotNull Project project) { + public PyDirectoryIndexExcludePolicy(@NotNull Project project) { myProject = project; } @@ -41,21 +49,44 @@ public class PyDirectoryIndexExcludePolicy implements DirectoryIndexExcludePolic @Override public VirtualFile[] getExcludeRootsForProject() { List result = Lists.newArrayList(); - for (VirtualFile root: ProjectRootManager.getInstance(myProject).getContentRoots()) { + for (VirtualFile root : ProjectRootManager.getInstance(myProject).getContentRoots()) { VirtualFile file = root.findChild(".tox"); if (file != null) { result.add(file); } } - + return result.toArray(new VirtualFile[result.size()]); } + @Nullable + @Override + public Function> getExcludeSdkRootsStrategy() { + return sdk -> { + List result = Lists.newLinkedList(); + + if (sdk != null) { + Set roots = new HashSet<>(Arrays.asList(sdk.getRootProvider().getFiles(OrderRootType.CLASSES))); + + for (VirtualFile dir : sdk.getRootProvider().getFiles(OrderRootType.CLASSES)) { + for (String name : SITE_PACKAGES) { + VirtualFile sitePackages = dir.findChild(name); + if (sitePackages != null && !roots.contains(sitePackages)) { + result.add(sitePackages); + } + } + } + } + + return result; + }; + } + @NotNull @Override public VirtualFilePointer[] getExcludeRootsForModule(@NotNull ModuleRootModel rootModel) { - - + + return VirtualFilePointer.EMPTY_ARRAY; } } diff --git a/python/ipnb/ipnb.iml b/python/ipnb/ipnb.iml index b351c473c80d..2772f724e418 100644 --- a/python/ipnb/ipnb.iml +++ b/python/ipnb/ipnb.iml @@ -27,5 +27,14 @@ + + + + + + + + + \ No newline at end of file diff --git a/python/ipnb/lib/commons-lang-2.6.jar b/python/ipnb/lib/commons-lang-2.6.jar new file mode 100644 index 000000000000..98467d3a653e Binary files /dev/null and b/python/ipnb/lib/commons-lang-2.6.jar differ diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/IpnbEditorUtil.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/IpnbEditorUtil.java index 4ce045caa923..13f41e30a1b2 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/IpnbEditorUtil.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/IpnbEditorUtil.java @@ -52,7 +52,7 @@ public class IpnbEditorUtil { public static Editor createPythonCodeEditor(@NotNull final Project project, @NotNull final IpnbCodeSourcePanel codeSourcePanel) { final EditorFactory editorFactory = EditorFactory.getInstance(); assert editorFactory != null; - final String text = codeSourcePanel.getCell().getSourceAsString().trim(); + final String text = codeSourcePanel.getCell().getSourceAsString(); final Module module = ProjectRootManagerEx.getInstanceEx(project).getFileIndex() .getModuleForFile(codeSourcePanel.getIpnbCodePanel().getFileEditor().getVirtualFile()); final IpnbPyFragment fragment = new IpnbPyFragment(project, text, true, codeSourcePanel); diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/IpnbEditablePanel.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/IpnbEditablePanel.java index 9d664c1686e1..58b1b4aeefe0 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/IpnbEditablePanel.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/editor/panels/IpnbEditablePanel.java @@ -275,6 +275,9 @@ public abstract class IpnbEditablePanel { private void setOutputStateInCell(boolean isCollapsed) { final Map metadata = myCell.getMetadata(); - metadata.put("collapsed", isCollapsed); + if (!metadata.containsKey(COLLAPSED_METADATA) && !isCollapsed) { + return; + } + + metadata.put(COLLAPSED_METADATA, isCollapsed); + } private JPanel createToggleBar(OnePixelSplitter splitter) { @@ -284,6 +289,9 @@ public class IpnbCodePanel extends IpnbEditablePanel { public void updateCellSource() { final Document document = myCodeSourcePanel.getEditor().getDocument(); final String text = document.getText(); + if (StringUtil.isEmpty(text) && myCell.getSource().isEmpty()) { + return; + } myCell.setSource(Arrays.asList(StringUtil.splitByLinesKeepSeparators(text))); } @@ -329,7 +337,9 @@ public class IpnbCodePanel extends IpnbEditablePanel { } if (replacementContent != null) { - myCell.setSource(Arrays.asList(StringUtil.splitByLinesKeepSeparators(replacementContent))); + if (replacementContent.isEmpty() || myCell.getSource().isEmpty()) { + myCell.setSource(Arrays.asList(StringUtil.splitByLinesKeepSeparators(replacementContent))); + } String prompt = IpnbEditorUtil.prompt(null, IpnbEditorUtil.PromptType.In); myCell.setPromptNumber(null); myPromptLabel.setText(prompt); diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/format/IpnbFile.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/format/IpnbFile.java index f328de5c9ff0..4785d281f828 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/format/IpnbFile.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/format/IpnbFile.java @@ -9,12 +9,14 @@ import java.util.Map; public class IpnbFile { private final Map myMetadata; private final int myNbformat; + private final int myNbFormatMinor; private final List myCells; @NotNull private final String myPath; - public IpnbFile(Map metadata, int nbformat, List cells, @NotNull String path) { + public IpnbFile(Map metadata, int nbformat, int nbFormatMinor, List cells, @NotNull String path) { myMetadata = metadata; myNbformat = nbformat; + myNbFormatMinor = nbFormatMinor; myCells = cells; myPath = path; } @@ -44,6 +46,10 @@ public class IpnbFile { return myNbformat; } + public int getNbFormatMinor() { + return myNbFormatMinor; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -52,6 +58,7 @@ public class IpnbFile { IpnbFile file = (IpnbFile)o; if (myNbformat != file.myNbformat) return false; + if (myNbFormatMinor != file.myNbFormatMinor) return false; if (myMetadata != null ? !myMetadata.equals(file.myMetadata) : file.myMetadata != null) return false; if (myCells != null ? !myCells.equals(file.myCells) : file.myCells != null) return false; if (!myPath.equals(file.myPath)) return false; @@ -63,8 +70,9 @@ public class IpnbFile { public int hashCode() { int result = myMetadata != null ? myMetadata.hashCode() : 0; result = 31 * result + myNbformat; + result = 31 * result + myNbFormatMinor; result = 31 * result + (myCells != null ? myCells.hashCode() : 0); - result = 31 * result + (myPath.hashCode()); + result = 31 * result + myPath.hashCode(); return result; } } \ No newline at end of file diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/format/IpnbParser.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/format/IpnbParser.java index ebb3bfd43295..e47a13d4c4e9 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/format/IpnbParser.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/format/IpnbParser.java @@ -3,6 +3,8 @@ package org.jetbrains.plugins.ipnb.format; import com.google.common.collect.Lists; import com.google.gson.*; import com.google.gson.annotations.SerializedName; +import com.google.gson.internal.LinkedTreeMap; +import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonWriter; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; @@ -18,6 +20,7 @@ import com.intellij.util.text.VersionComparatorUtil; import com.jetbrains.python.packaging.PyPackage; import com.jetbrains.python.packaging.PyPackageUtil; import com.jetbrains.python.sdk.PythonSdkType; +import org.apache.commons.lang.math.NumberUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.ipnb.editor.panels.IpnbEditablePanel; @@ -28,10 +31,7 @@ import org.jetbrains.plugins.ipnb.format.cells.output.*; import java.io.*; import java.lang.reflect.Type; import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; public class IpnbParser { private static final Logger LOG = Logger.getInstance(IpnbParser.class); @@ -43,8 +43,10 @@ public class IpnbParser { @NotNull private static Gson initGson() { final GsonBuilder builder = - new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().registerTypeAdapter(IpnbCellRaw.class, new RawCellAdapter()) - .registerTypeAdapter(IpnbFileRaw.class, new FileAdapter()).registerTypeAdapter(CellOutputRaw.class, new OutputsAdapter()) + new GsonBuilder().setPrettyPrinting().disableHtmlEscaping() + .registerTypeAdapter(IpnbCellRaw.class, new RawCellAdapter()) + .registerTypeAdapter(IpnbFileRaw.class, new FileAdapter()) + .registerTypeAdapter(CellOutputRaw.class, new OutputsAdapter()) .registerTypeAdapter(OutputDataRaw.class, new OutputDataAdapter()) .registerTypeAdapter(CellOutputRaw.class, new CellOutputDeserializer()) .registerTypeAdapter(OutputDataRaw.class, new OutputDataDeserializer()) @@ -60,10 +62,10 @@ public class IpnbParser { IpnbFileRaw rawFile = gson.fromJson(fileText.toString(), IpnbFileRaw.class); if (rawFile == null) { int nbformat = isIpythonNewFormat(virtualFile) ? 4 : 3; - return new IpnbFile(new HashMap<>(), nbformat, Lists.newArrayList(), path); + return new IpnbFile(new HashMap<>(), nbformat, 0, Lists.newArrayList(), path); } List cells = new ArrayList<>(); - final IpnbWorksheet[] worksheets = rawFile.worksheets; + final List worksheets = rawFile.worksheets; if (worksheets == null) { for (IpnbCellRaw rawCell : rawFile.cells) { cells.add(rawCell.createCell(validateSource(rawCell))); @@ -78,7 +80,7 @@ public class IpnbParser { } } showValidationMessage(); - return new IpnbFile(rawFile.metadata, rawFile.nbformat, cells, path); + return new IpnbFile(rawFile.metadata, rawFile.nbformat, rawFile.nbformat_minor, cells, path); } private static boolean validateSource(IpnbCellRaw cell) { @@ -139,6 +141,7 @@ public class IpnbParser { } final IpnbFileRaw fileRaw = new IpnbFileRaw(); + fileRaw.nbformat_minor = ipnbFile.getNbFormatMinor(); fileRaw.metadata = ipnbFile.getMetadata(); if (ipnbFile.getNbformat() == 4) { for (IpnbCell cell : ipnbFile.getCells()) { @@ -151,7 +154,7 @@ public class IpnbParser { for (IpnbCell cell : ipnbFile.getCells()) { worksheet.cells.add(IpnbCellRaw.fromCell(cell, ipnbFile.getNbformat())); } - fileRaw.worksheets = new IpnbWorksheet[]{worksheet}; + fileRaw.worksheets = Collections.singletonList(worksheet); } final StringWriter stringWriter = new StringWriter(); final JsonWriter writer = new JsonWriter(stringWriter); @@ -188,7 +191,7 @@ public class IpnbParser { @SuppressWarnings("unused") public static class IpnbFileRaw { - IpnbWorksheet[] worksheets; + List worksheets; List cells = new ArrayList<>(); Map metadata = new HashMap<>(); int nbformat = 4; @@ -517,7 +520,7 @@ public class IpnbParser { } } - static class FileAdapter implements JsonSerializer { + static class FileAdapter implements JsonSerializer, JsonDeserializer { @Override public JsonElement serialize(IpnbFileRaw fileRaw, Type typeOfSrc, JsonSerializationContext context) { final JsonObject jsonObject = new JsonObject(); @@ -526,7 +529,7 @@ public class IpnbParser { jsonObject.add("worksheets", worksheets); } if (fileRaw.cells != null) { - final JsonElement cells = gson.toJsonTree(fileRaw.cells); + final JsonElement cells = gson.toJsonTree(fileRaw.cells, new TypeToken>(){}.getType()); jsonObject.add("cells", cells); } final JsonElement metadata = gson.toJsonTree(fileRaw.metadata); @@ -537,8 +540,73 @@ public class IpnbParser { return jsonObject; } + + @Override + public IpnbFileRaw deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject object = json.getAsJsonObject(); + IpnbFileRaw fileRaw = new IpnbFileRaw(); + + JsonElement worksheets = object.get("worksheets"); + if (worksheets != null) { + fileRaw.worksheets = gson.fromJson(worksheets, new TypeToken>(){}.getType()); + } + + JsonElement cellsElement = object.get("cells"); + if (cellsElement != null) { + fileRaw.cells = gson.fromJson(cellsElement, new TypeToken>(){}.getType()); + } + + JsonElement metadataElement = object.get("metadata"); + if (metadataElement != null) { + LinkedTreeMap metadataMap = gson.fromJson(metadataElement, new TypeToken>(){}.getType()); + + JsonElement kernelInfo = metadataElement.getAsJsonObject().get("kernel_info"); + if (kernelInfo != null) { + metadataMap.put("kernel_info", gson.fromJson(kernelInfo, new TypeToken>() {}.getType())); + } + + JsonElement languageInfo = metadataElement.getAsJsonObject().get("language_info"); + if (languageInfo != null) { + LinkedTreeMap languageInfoMap = gson.fromJson(languageInfo, new TypeToken>() {}.getType()); + JsonElement codemirrorMode = languageInfo.getAsJsonObject().get("codemirror_mode"); + if (codemirrorMode != null) { + LinkedTreeMap codemirrorModeMap = gson.fromJson(codemirrorMode, new TypeToken>() {}.getType()); + if (codemirrorModeMap.containsKey("version")) { + + String version = (String)codemirrorModeMap.get("version"); + if (NumberUtils.isNumber(version)) { + try { + codemirrorModeMap.put("version", Integer.parseInt(version)); + } + catch (NumberFormatException e) { + // added this to obtain backward compatibility as previously we parsed "version" as double. + codemirrorModeMap.put("version", (int) Double.parseDouble(version)); + } + } + } + languageInfoMap.put("codemirror_mode", codemirrorModeMap); + } + metadataMap.put("language_info", languageInfoMap); + } + + fileRaw.metadata = metadataMap; + } + + JsonElement nbformat = object.get("nbformat"); + if (nbformat != null) { + fileRaw.nbformat = nbformat.getAsInt(); + } + + JsonElement nbformatMinor = object.get("nbformat_minor"); + if (nbformatMinor != null) { + fileRaw.nbformat_minor = nbformatMinor.getAsInt(); + } + + return fileRaw; + } } + static class CellRawDeserializer implements JsonDeserializer { @Override diff --git a/python/psi-api/src/com/jetbrains/python/PyNames.java b/python/psi-api/src/com/jetbrains/python/PyNames.java index 5c84eec859de..b15191866e47 100644 --- a/python/psi-api/src/com/jetbrains/python/PyNames.java +++ b/python/psi-api/src/com/jetbrains/python/PyNames.java @@ -210,6 +210,7 @@ public class PyNames { public static final String NOSE_TEST = "nose"; public static final String PY_TEST = "pytest"; + public static final String TRIAL_TEST = "trial"; public static final String TEST_CASE = "TestCase"; @@ -582,6 +583,41 @@ public class PyNames { return name != null && (name.matches("__r[a-z]+__") || CONTAINS.equals(name)); } + public static boolean isRightOperatorName(@Nullable String referencedName, @Nullable String calleeName) { + if (isRightOperatorName(calleeName)) return true; + + return referencedName != null && calleeName != null && calleeName.equals(leftToRightComparisonOperatorName(referencedName)); + } + + @Nullable + public static String leftToRightOperatorName(@Nullable String name) { + if (name == null) return null; + + final String rightComparisonOperatorName = leftToRightComparisonOperatorName(name); + if (rightComparisonOperatorName != null) return rightComparisonOperatorName; + + return name.replaceFirst("__([a-z]+)__", "__r$1__"); + } + + @Nullable + private static String leftToRightComparisonOperatorName(@NotNull String name) { + switch (name) { + case "__lt__": + return "__gt__"; + case "__gt__": + return "__lt__"; + case "__ge__": + return "__le__"; + case "__le__": + return "__ge__"; + case "__eq__": + case "__ne__": + return name; + default: + return null; + } + } + /** * Available in Python 3 and Python 2 starting from 2.6. *

      diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyBinaryExpression.java b/python/psi-api/src/com/jetbrains/python/psi/PyBinaryExpression.java index eb5d9fb7eb26..6b3ba29419f0 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/PyBinaryExpression.java +++ b/python/psi-api/src/com/jetbrains/python/psi/PyBinaryExpression.java @@ -31,15 +31,17 @@ public interface PyBinaryExpression extends PyQualifiedExpression, PyCallSiteExp @Nullable @Override default PyExpression getReceiver(@Nullable PyCallable resolvedCallee) { - final boolean isRight = resolvedCallee != null && PyNames.isRightOperatorName(resolvedCallee.getName()); - return isRight ? getRightExpression() : getLeftExpression(); + return isRightOperator(resolvedCallee) ? getRightExpression() : getLeftExpression(); } @NotNull @Override default List getArguments(@Nullable PyCallable resolvedCallee) { - final boolean isRight = resolvedCallee != null && PyNames.isRightOperatorName(resolvedCallee.getName()); - return Collections.singletonList(isRight ? getLeftExpression() : getRightExpression()); + return Collections.singletonList(isRightOperator(resolvedCallee) ? getLeftExpression() : getRightExpression()); + } + + default boolean isRightOperator(@Nullable PyCallable resolvedCallee) { + return resolvedCallee != null && PyNames.isRightOperatorName(getReferencedName(), resolvedCallee.getName()); } PyExpression getLeftExpression(); diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyQualifiedExpression.java b/python/psi-api/src/com/jetbrains/python/psi/PyQualifiedExpression.java index 20de49dd203b..63433bf455b7 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/PyQualifiedExpression.java +++ b/python/psi-api/src/com/jetbrains/python/psi/PyQualifiedExpression.java @@ -36,7 +36,29 @@ public interface PyQualifiedExpression extends PyExpression { boolean isQualified(); /** - * Returns the qualified name for the expression if all the qualifiers are qualified expressions. + * Returns the qualified name for the expression. + *

      + * If it has no qualifier, the result is the same as {@code QualifiedName.fromDottedString(getReferencedName())}. + * Otherwise, the qualified name is built of {@code getReferencedName()} followed (to the left) by the components + * of the qualifier if it consists only of reference expressions. In any other case, the result is null. + *

      + * Note that it means that for pseudo-qualified operations that map to "magic" methods, the trailing component of + * the result is the corresponding "dunder" name: {@code __add__}, {@code __neg__}, etc. + *

      + * Examples: + *

        + *
      • {@code foo -> foo}
      • + *
      • {@code foo.bar -> foo.bar}
      • + *
      • {@code foo[0] -> foo.__getitem__}
      • + *
      • {@code foo[0].bar -> null}
      • + *
      • {@code foo[0][1] -> null}
      • + *
      • {@code foo().bar -> null}
      • + *
      • {@code -foo -> foo.__neg__}
      • + *
      • {@code foo + bar -> foo.__add__}
      • + *
      • {@code foo + bar + baz -> null}
      • + *
      + * + * @see #getReferencedName() */ @Nullable QualifiedName asQualifiedName(); diff --git a/python/psi-api/src/com/jetbrains/python/psi/impl/PyPsiUtils.java b/python/psi-api/src/com/jetbrains/python/psi/impl/PyPsiUtils.java index 1459765160aa..371a37eb599e 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/impl/PyPsiUtils.java +++ b/python/psi-api/src/com/jetbrains/python/psi/impl/PyPsiUtils.java @@ -30,6 +30,7 @@ import com.intellij.psi.tree.TokenSet; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtilCore; import com.intellij.psi.util.QualifiedName; +import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.psi.*; @@ -549,13 +550,13 @@ public class PyPsiUtils { return expr instanceof PyQualifiedExpression ? ((PyQualifiedExpression)expr).asQualifiedName() : null; } - @Nullable + @NotNull public static PyExpression getFirstQualifier(@NotNull PyQualifiedExpression expr) { - final List expressions = unwindQualifiers(expr); - if (!expressions.isEmpty()) { - return expressions.get(0); + final PyExpression qualifier = expr.getQualifier(); + if (qualifier instanceof PyQualifiedExpression) { + return getFirstQualifier((PyQualifiedExpression)qualifier); } - return null; + return expr; } @NotNull @@ -575,32 +576,26 @@ public class PyPsiUtils { @Nullable protected static QualifiedName asQualifiedName(@NotNull PyQualifiedExpression expr) { - return fromReferenceChain(unwindQualifiers(expr)); - } - - @NotNull - private static List unwindQualifiers(@NotNull final PyQualifiedExpression expr) { - final List path = new LinkedList<>(); - PyQualifiedExpression e = expr; - while (e != null) { - path.add(0, e); - final PyExpression q = e.getQualifier(); - e = q instanceof PyQualifiedExpression ? (PyQualifiedExpression)q : null; + final List path = new LinkedList<>(); + final String firstName = expr.getReferencedName(); + if (firstName == null) { + return null; } - return path; - } - - @Nullable - private static QualifiedName fromReferenceChain(@NotNull List components) { - final List componentNames = new ArrayList<>(components.size()); - for (PyExpression component : components) { - final String refName = (component instanceof PyQualifiedExpression) ? ((PyQualifiedExpression)component).getReferencedName() : null; - if (refName == null) { + path.add(firstName); + PyExpression qualifier = expr.getQualifier(); + while (qualifier != null) { + final PyReferenceExpression qualifierReference = ObjectUtils.tryCast(qualifier, PyReferenceExpression.class); + if (qualifierReference == null) { return null; } - componentNames.add(refName); + final String qualifierName = qualifierReference.getReferencedName(); + if (qualifierName == null) { + return null; + } + path.add(0, qualifierName); + qualifier = qualifierReference.getQualifier(); } - return QualifiedName.fromComponents(componentNames); + return QualifiedName.fromComponents(path); } /** diff --git a/python/pydevSrc/com/jetbrains/python/console/pydev/PydevXmlRpcClient.kt b/python/pydevSrc/com/jetbrains/python/console/pydev/PydevXmlRpcClient.kt index 6a01ff303beb..ad0c36c987fd 100644 --- a/python/pydevSrc/com/jetbrains/python/console/pydev/PydevXmlRpcClient.kt +++ b/python/pydevSrc/com/jetbrains/python/console/pydev/PydevXmlRpcClient.kt @@ -4,11 +4,9 @@ import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.progress.ProgressManager import com.intellij.util.net.NetUtils import org.apache.xmlrpc.* - import java.net.MalformedURLException import java.net.URL -import java.util.Arrays -import java.util.Vector +import java.util.* import java.util.concurrent.TimeUnit /** @@ -22,7 +20,7 @@ class PydevXmlRpcClient * Constructor (see fields description) */ @Throws(MalformedURLException::class) -constructor(private val process: Process, port: Int) : IPydevXmlRpcClient { +constructor(private val process: Process, hostname: String?, port: Int) : IPydevXmlRpcClient { /** @@ -33,9 +31,10 @@ constructor(private val process: Process, port: Int) : IPydevXmlRpcClient { init { XmlRpc.setDefaultInputEncoding("UTF8") //even though it uses UTF anyway - impl = XmlRpcClientLite(NetUtils.getLocalHostString(), port) + impl = XmlRpcClientLite(hostname ?: NetUtils.getLocalHostString(), port) } + constructor(process: Process, port: Int): this(process = process, hostname = null, port = port) override fun execute(command: String, args: Array): Any { return execute(command, args, TIME_LIMIT) diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ClientModeMultiProcessDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ClientModeMultiProcessDebugger.java index 4779af6f8c0b..90da38407159 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ClientModeMultiProcessDebugger.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ClientModeMultiProcessDebugger.java @@ -291,9 +291,9 @@ public class ClientModeMultiProcessDebugger implements ProcessDebugger { } @Override - public void execute(@NotNull AbstractCommand command, boolean waitForResult) { + public void execute(@NotNull AbstractCommand command) { for (ProcessDebugger d : allDebuggers()) { - d.execute(command, waitForResult); + d.execute(command); } } @@ -388,14 +388,14 @@ public class ClientModeMultiProcessDebugger implements ProcessDebugger { @Override public void addExceptionBreakpoint(ExceptionBreakpointCommandFactory factory) { for (RemoteDebugger d : allDebuggers()) { - d.execute(factory.createAddCommand(d), false); + d.execute(factory.createAddCommand(d)); } } @Override public void removeExceptionBreakpoint(ExceptionBreakpointCommandFactory factory) { for (RemoteDebugger d : allDebuggers()) { - d.execute(factory.createRemoveCommand(d), false); + d.execute(factory.createRemoveCommand(d)); } } diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java index c21ed9ba3d45..22dcafdcb75d 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java @@ -326,9 +326,9 @@ public class MultiProcessDebugger implements ProcessDebugger { @Override - public void execute(@NotNull AbstractCommand command, boolean waitForResult) { + public void execute(@NotNull AbstractCommand command) { for (ProcessDebugger d : allDebuggers()) { - d.execute(command, waitForResult); + d.execute(command); } } @@ -522,14 +522,14 @@ public class MultiProcessDebugger implements ProcessDebugger { @Override public void addExceptionBreakpoint(ExceptionBreakpointCommandFactory factory) { for (RemoteDebugger d : allDebuggers()) { - d.execute(factory.createAddCommand(d), false); + d.execute(factory.createAddCommand(d)); } } @Override public void removeExceptionBreakpoint(ExceptionBreakpointCommandFactory factory) { for (RemoteDebugger d : allDebuggers()) { - d.execute(factory.createRemoveCommand(d), false); + d.execute(factory.createRemoveCommand(d)); } } diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java index d6f1369c356c..186dd53a569d 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java @@ -46,7 +46,7 @@ public interface ProcessDebugger { Collection getThreads(); - void execute(@NotNull AbstractCommand command, boolean waitForResult); + void execute(@NotNull AbstractCommand command); void suspendAllThreads(); diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java index b36e88ce8a65..010d369d9645 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java @@ -322,7 +322,7 @@ public class RemoteDebugger implements ProcessDebugger { } @Override - public void execute(@NotNull final AbstractCommand command, boolean waitForResult) { + public void execute(@NotNull final AbstractCommand command) { CountDownLatch myLatch = new CountDownLatch(1); ApplicationManager.getApplication().executeOnPooledThread(() -> { if (command instanceof ResumeOrStepCommand) { @@ -338,7 +338,7 @@ public class RemoteDebugger implements ProcessDebugger { LOG.error(e); } }); - if (waitForResult) { + if (command.isResponseExpected()) { // Note: do not wait for result from UI thread try { myLatch.await(RESPONSE_TIMEOUT, TimeUnit.MILLISECONDS); @@ -364,7 +364,7 @@ public class RemoteDebugger implements ProcessDebugger { @Override public void suspendThread(String threadId) { final SuspendCommand command = new SuspendCommand(this, threadId); - execute(command, false); + execute(command); } @Override @@ -388,20 +388,20 @@ public class RemoteDebugger implements ProcessDebugger { @Override public void smartStepInto(String threadId, String functionName) { final SmartStepIntoCommand command = new SmartStepIntoCommand(this, threadId, functionName); - execute(command, false); + execute(command); } @Override public void resumeOrStep(String threadId, ResumeOrStepCommand.Mode mode) { final ResumeOrStepCommand command = new ResumeOrStepCommand(this, threadId, mode); - execute(command, false); + execute(command); } @Override public void setTempBreakpoint(@NotNull String type, @NotNull String file, int line) { final SetBreakpointCommand command = new SetBreakpointCommand(this, type, file, line); - execute(command, true); // set temp. breakpoint + execute(command); // set temp. breakpoint myTempBreakpoints.put(Pair.create(file, line), type); } @@ -410,7 +410,7 @@ public class RemoteDebugger implements ProcessDebugger { String type = myTempBreakpoints.get(Pair.create(file, line)); if (type != null) { final RemoveBreakpointCommand command = new RemoveBreakpointCommand(this, type, file, line); - execute(command, false); // remove temp. breakpoint + execute(command); // remove temp. breakpoint } else { LOG.error("Temp breakpoint not found for " + file + ":" + line); @@ -431,7 +431,7 @@ public class RemoteDebugger implements ProcessDebugger { logExpression, funcName, policy); - execute(command, false); + execute(command); } @@ -439,13 +439,13 @@ public class RemoteDebugger implements ProcessDebugger { public void removeBreakpoint(@NotNull String typeId, @NotNull String file, int line) { final RemoveBreakpointCommand command = new RemoveBreakpointCommand(this, typeId, file, line); - execute(command, false); + execute(command); } @Override public void setShowReturnValues(boolean isShowReturnValues) { final ShowReturnValuesCommand command = new ShowReturnValuesCommand(this, isShowReturnValues); - execute(command, false); + execute(command); } // for DebuggerReader only @@ -628,25 +628,25 @@ public class RemoteDebugger implements ProcessDebugger { @Override public List getCompletions(String threadId, String frameId, String prefix) { final GetCompletionsCommand command = new GetCompletionsCommand(this, threadId, frameId, prefix); - execute(command, true); + execute(command); return command.getCompletions(); } @Override public String getDescription(String threadId, String frameId, String cmd) { final GetDescriptionCommand command = new GetDescriptionCommand(this, threadId, frameId, cmd); - execute(command, true); + execute(command); return command.getResult(); } @Override public void addExceptionBreakpoint(ExceptionBreakpointCommandFactory factory) { - execute(factory.createAddCommand(this), false); + execute(factory.createAddCommand(this)); } @Override public void removeExceptionBreakpoint(ExceptionBreakpointCommandFactory factory) { - execute(factory.createRemoveCommand(this), false); + execute(factory.createRemoveCommand(this)); } @Override diff --git a/python/python-community-ide-resources/resources/idea/PyCharmCoreApplicationInfo.xml b/python/python-community-ide-resources/resources/idea/PyCharmCoreApplicationInfo.xml index 6acf8f4597cd..7184297f721c 100644 --- a/python/python-community-ide-resources/resources/idea/PyCharmCoreApplicationInfo.xml +++ b/python/python-community-ide-resources/resources/idea/PyCharmCoreApplicationInfo.xml @@ -1,7 +1,7 @@ - + diff --git a/python/resources/fileTemplates/internal/Setup Script.py.ft b/python/resources/fileTemplates/internal/Setup Script.py.ft index cb9cb9b36e0b..c351cc60052d 100644 --- a/python/resources/fileTemplates/internal/Setup Script.py.ft +++ b/python/resources/fileTemplates/internal/Setup Script.py.ft @@ -1,4 +1,4 @@ -from distutils.core import setup +$Import setup( name='$Package_name', diff --git a/python/setup-test-environment/build.gradle b/python/setup-test-environment/build.gradle index 398dae824960..cfb2e2624b35 100644 --- a/python/setup-test-environment/build.gradle +++ b/python/setup-test-environment/build.gradle @@ -12,8 +12,8 @@ envs { packages = ["pip", "setuptools"] _64Bits = true - conda "django19", "2.7", ["django==1.9", "tox", "nose", "pytest", "behave", "lettuce>=0.2.22", "unittest2", "teamcity-messages"], true - textfile "django19/tags.txt", "python2.7\ndjango\nnose\npytest\nbehave\nlettuce\npackaging\ntox\nunittest2" + conda "django19", "2.7", ["django==1.9", "tox", "nose", "pytest", "Twisted", "behave", "lettuce>=0.2.22", "unittest2", "teamcity-messages"], true + textfile "django19/tags.txt", "python2.7\ndjango\nnose\npytest\nbehave\nlettuce\npackaging\ntox\nunittest2\ntwisted" conda "django110", "3.4", ["django==1.10", "django-nose"], false textfile "django110/tags.txt", "python3.4\ndjango\nskeletons\ndjango-nose" diff --git a/python/src/META-INF/python-core-common.xml b/python/src/META-INF/python-core-common.xml index d6a5efc832c5..bd98166afb02 100644 --- a/python/src/META-INF/python-core-common.xml +++ b/python/src/META-INF/python-core-common.xml @@ -101,6 +101,8 @@ + + + @@ -792,12 +795,6 @@ - - - - consumer, Editor editor) { + @NotNull final Consumer consumer, @Nullable Editor editor) { Collection consoles = getConsoles(project); ExecutionHelper @@ -187,7 +193,9 @@ public class PyExecuteSelectionAction extends AnAction { ContentManager contentManager = currentSession.getUI().getContentManager(); Content content = contentManager.findContent("Console"); contentManager.setSelectedContent(content); - IdeFocusManager.findInstance().requestFocus(editor.getContentComponent(), true); + if (editor != null) { + IdeFocusManager.findInstance().requestFocus(editor.getContentComponent(), true); + } } } else { @@ -230,8 +238,12 @@ public class PyExecuteSelectionAction extends AnAction { return processHandler != null && !processHandler.isProcessTerminated(); } - private static void findCodeExecutor(AnActionEvent e, Consumer consumer, Editor editor, Project project, Module module) { - if (project != null && editor != null) { + private static void findCodeExecutor(@NotNull AnActionEvent e, + @NotNull Consumer consumer, + @Nullable Editor editor, + @Nullable Project project, + @Nullable Module module) { + if (project != null) { if (canFindConsole(e)) { selectConsole(e.getDataContext(), project, consumer, editor); } @@ -285,7 +297,7 @@ public class PyExecuteSelectionAction extends AnAction { } } - private static void executeInConsole(@NotNull PyCodeExecutor codeExecutor, @NotNull String text, Editor editor) { + private static void executeInConsole(@NotNull PyCodeExecutor codeExecutor, @Nullable String text, Editor editor) { codeExecutor.executeCode(text, editor); } } diff --git a/python/src/com/jetbrains/python/codeInsight/controlflow/InstructionTypeCallback.java b/python/src/com/jetbrains/python/codeInsight/controlflow/InstructionTypeCallback.java index 75858924cf57..69bca8261f3a 100644 --- a/python/src/com/jetbrains/python/codeInsight/controlflow/InstructionTypeCallback.java +++ b/python/src/com/jetbrains/python/codeInsight/controlflow/InstructionTypeCallback.java @@ -15,6 +15,7 @@ */ package com.jetbrains.python.codeInsight.controlflow; +import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiElement; import com.jetbrains.python.psi.types.PyType; import com.jetbrains.python.psi.types.TypeEvalContext; @@ -25,5 +26,5 @@ import org.jetbrains.annotations.Nullable; */ public interface InstructionTypeCallback { @Nullable - PyType getType(TypeEvalContext context, @Nullable PsiElement anchor); + Ref getType(TypeEvalContext context, @Nullable PsiElement anchor); } diff --git a/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java b/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java index da1716e952a1..87b13eaacd34 100644 --- a/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java +++ b/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java @@ -23,6 +23,7 @@ import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiNamedElement; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.QualifiedName; import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.ParamHelper; @@ -218,19 +219,17 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor { @Override public void visitPyTargetExpression(final PyTargetExpression node) { - final PsiElement[] children = node.getChildren(); - // Case of non qualified reference - if (children.length == 0) { - final ReadWriteInstruction.ACCESS access = node.getParent() instanceof PySliceExpression - ? ReadWriteInstruction.ACCESS.READ : ReadWriteInstruction.ACCESS.WRITE; - final ReadWriteInstruction instruction = ReadWriteInstruction.newInstruction(myBuilder, node, node.getName(), access); + final QualifiedName qName = node.asQualifiedName(); + if (qName != null) { + final ReadWriteInstruction instruction = ReadWriteInstruction.newInstruction(myBuilder, node, qName.toString(), + ReadWriteInstruction.ACCESS.WRITE); myBuilder.addNode(instruction); myBuilder.checkPending(instruction); } - else { - for (PsiElement child : children) { - child.accept(this); - } + + final PyExpression qualifier = node.getQualifier(); + if (qualifier != null) { + qualifier.accept(this); } } @@ -365,13 +364,6 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor { }); myBuilder.addPendingEdge(node, myBuilder.prevInstruction); } - final Ref pendingInScopeEdges = Ref.create(false); - myBuilder.processPending((pendingScope, instruction) -> { - if (pendingScope != null && PsiTreeUtil.isAncestor(node, pendingScope, false)) { - pendingInScopeEdges.set(true); - } - myBuilder.addPendingEdge(pendingScope, instruction); - }); final PyTypeAssertionEvaluator negativeAssertionEvaluator = new PyTypeAssertionEvaluator(false); final PyExpression ifCondition = ifPart.getCondition(); // TODO: Add support for 'elif' @@ -386,15 +378,11 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor { InstructionBuilder.addAssertInstructions(myBuilder, negativeAssertionEvaluator); elseBranch.accept(this); myBuilder.addPendingEdge(node, myBuilder.prevInstruction); - } else { - if (!pendingInScopeEdges.get()) { - myBuilder.prevInstruction = lastBranchingPoint; - InstructionBuilder.addAssertInstructions(myBuilder, negativeAssertionEvaluator); - myBuilder.addPendingEdge(node, myBuilder.prevInstruction); - } - else { - myBuilder.addPendingEdge(node, lastBranchingPoint); - } + } + else { + myBuilder.prevInstruction = lastBranchingPoint; + InstructionBuilder.addAssertInstructions(myBuilder, negativeAssertionEvaluator); + myBuilder.addPendingEdge(node, myBuilder.prevInstruction); } } diff --git a/python/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java b/python/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java index c26aceb635ac..064a55dcfaac 100644 --- a/python/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java +++ b/python/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java @@ -15,6 +15,7 @@ */ package com.jetbrains.python.codeInsight.controlflow; +import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiElement; import com.jetbrains.python.PyNames; import com.jetbrains.python.PyTokenTypes; @@ -149,27 +150,27 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor { } @Nullable - private static PyType createAssertionType(@Nullable PyType initial, - @Nullable PyType suggested, - boolean positive, - boolean transformToDefinition, - @NotNull TypeEvalContext context) { + private static Ref createAssertionType(@Nullable PyType initial, + @Nullable PyType suggested, + boolean positive, + boolean transformToDefinition, + @NotNull TypeEvalContext context) { final PyType transformedType = transformTypeFromAssertion(suggested, transformToDefinition); if (positive) { if (!(initial instanceof PyUnionType) && !PyTypeChecker.isUnknown(initial, context) && PyTypeChecker.match(transformedType, initial, context)) { - return initial; + return Ref.create(initial); } - return transformedType; + return Ref.create(transformedType); } else if (initial instanceof PyUnionType) { - return ((PyUnionType)initial).exclude(transformedType, context); + return Ref.create(((PyUnionType)initial).exclude(transformedType, context)); } - else if (PyTypeChecker.match(transformedType, initial, context)) { + else if (initial != null && PyTypeChecker.match(transformedType, initial, context)) { return null; } - return initial; + return Ref.create(initial); } @Nullable @@ -196,7 +197,7 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor { @NotNull Function suggestedType) { final InstructionTypeCallback typeCallback = new InstructionTypeCallback() { @Override - public PyType getType(TypeEvalContext context, @Nullable PsiElement anchor) { + public Ref getType(TypeEvalContext context, @Nullable PsiElement anchor) { return createAssertionType(context.getType(target), suggestedType.apply(context), positive, transformToDefinition, context); } }; @@ -223,7 +224,7 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor { static class Assertion { private final PyReferenceExpression element; - private InstructionTypeCallback myFunction; + private final InstructionTypeCallback myFunction; Assertion(PyReferenceExpression element, InstructionTypeCallback getType) { this.element = element; diff --git a/python/src/com/jetbrains/python/codeInsight/controlflow/ReadWriteInstruction.java b/python/src/com/jetbrains/python/codeInsight/controlflow/ReadWriteInstruction.java index e7c2d05a2bea..9ddb14c6454a 100644 --- a/python/src/com/jetbrains/python/codeInsight/controlflow/ReadWriteInstruction.java +++ b/python/src/com/jetbrains/python/codeInsight/controlflow/ReadWriteInstruction.java @@ -17,6 +17,7 @@ package com.jetbrains.python.codeInsight.controlflow; import com.intellij.codeInsight.controlflow.ControlFlowBuilder; import com.intellij.codeInsight.controlflow.impl.InstructionImpl; +import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiElement; import com.jetbrains.python.psi.PyElement; import com.jetbrains.python.psi.PyExpression; @@ -29,8 +30,8 @@ public class ReadWriteInstruction extends InstructionImpl { final InstructionTypeCallback EXPR_TYPE = new InstructionTypeCallback() { @Nullable @Override - public PyType getType(TypeEvalContext context, @Nullable PsiElement anchor) { - return myElement instanceof PyExpression ? context.getType((PyExpression)myElement) : null; + public Ref getType(TypeEvalContext context, @Nullable PsiElement anchor) { + return Ref.create(myElement instanceof PyExpression ? context.getType((PyExpression)myElement) : null); } }; @@ -120,7 +121,7 @@ public class ReadWriteInstruction extends InstructionImpl { } @Nullable - public PyType getType(TypeEvalContext context, @Nullable PsiElement anchor) { + public Ref getType(TypeEvalContext context, @Nullable PsiElement anchor) { return myGetType.getType(context, anchor); } diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleType.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleType.java index c4791958b6d0..4f5ec30c6f70 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleType.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleType.java @@ -104,7 +104,7 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType @Nullable @Override public PyType getCallType(@NotNull TypeEvalContext context, @NotNull PyCallSiteExpression callSite) { - if (myDefinitionLevel == DefinitionLevel.AS_SUPERCLASS) { + if (myDefinitionLevel == DefinitionLevel.NT_FUNCTION) { return new PyNamedTupleType(myClass, myDeclaration, myName, myFields, DefinitionLevel.NEW_TYPE); } else if (myDefinitionLevel == DefinitionLevel.NEW_TYPE) { @@ -135,6 +135,23 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType return "PyNamedTupleType: " + myName; } + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + + final PyNamedTupleType type = (PyNamedTupleType)o; + return Objects.equals(myName, type.myName) && + Objects.equals(myFields.keySet(), type.myFields.keySet()) && + myDefinitionLevel == type.myDefinitionLevel; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), myName, myFields.keySet(), myDefinitionLevel); + } + @NotNull @Override public Set getMemberNames(boolean inherited, @NotNull TypeEvalContext context) { @@ -155,7 +172,7 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType @Override public boolean isCallable() { - return myDefinitionLevel == DefinitionLevel.NEW_TYPE; + return myDefinitionLevel != DefinitionLevel.INSTANCE; } @Nullable @@ -173,7 +190,7 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType public enum DefinitionLevel { - AS_SUPERCLASS, + NT_FUNCTION, // type for collections.namedtuple and typing.NamedTuple.__init__ NEW_TYPE, INSTANCE } diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibOverridingTypeProvider.kt b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibOverridingTypeProvider.kt index 90b7b71ad7b6..9adf2f7f30ed 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibOverridingTypeProvider.kt +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibOverridingTypeProvider.kt @@ -16,9 +16,6 @@ package com.jetbrains.python.codeInsight.stdlib import com.intellij.psi.PsiElement -import com.jetbrains.python.PyNames -import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider -import com.jetbrains.python.psi.PyFunction import com.jetbrains.python.psi.impl.PyOverridingTypeProvider import com.jetbrains.python.psi.types.PyType import com.jetbrains.python.psi.types.PyTypeProviderBase @@ -27,12 +24,6 @@ import com.jetbrains.python.psi.types.TypeEvalContext class PyStdlibOverridingTypeProvider : PyTypeProviderBase(), PyOverridingTypeProvider { override fun getReferenceType(referenceTarget: PsiElement, context: TypeEvalContext, anchor: PsiElement?): PyType? { - return if (isTypingNamedTupleInit(referenceTarget)) PyStdlibTypeProvider.getNamedTupleType(referenceTarget, context, anchor) else null - } - - private fun isTypingNamedTupleInit(referenceTarget: PsiElement): Boolean { - return referenceTarget is PyFunction && - PyNames.INIT == referenceTarget.name && - PyTypingTypeProvider.NAMEDTUPLE == referenceTarget.containingClass?.qualifiedName + return PyStdlibTypeProvider.getNamedTupleTypeForResolvedCallee(referenceTarget, context, anchor) } } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java index f7711088b50b..0030bf4bf71a 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java @@ -40,7 +40,6 @@ import com.jetbrains.python.psi.resolve.PyResolveImportUtil; import com.jetbrains.python.psi.stubs.PyNamedTupleStub; import com.jetbrains.python.psi.stubs.PyTargetExpressionStub; import com.jetbrains.python.psi.types.*; -import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -77,7 +76,7 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { if (type != null) { return type; } - type = getNamedTupleType(referenceTarget, context, anchor); + type = getNamedTupleTypeForResolvedCallee(referenceTarget, context, anchor); if (type != null) { return type; } @@ -101,12 +100,14 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { } } - final PyCallableType typeFromNTInheritorInitializing = Optional - .ofNullable(getTypeFromCollectionsNTInheritorInitializing(referenceExpression, context)) - .orElseGet(() -> getTypeFromTypingNTInheritorInitializing(referenceExpression, context)); + final PyType fieldTypeForTypingNTTarget = getFieldTypeForTypingNTTarget(referenceExpression, context); + if (fieldTypeForTypingNTTarget != null) { + return fieldTypeForTypingNTTarget; + } - if (typeFromNTInheritorInitializing != null) { - return typeFromNTInheritorInitializing; + final PyCallableType namedTupleTypeForCallee = getNamedTupleTypeForCallee(referenceExpression, context); + if (namedTupleTypeForCallee != null) { + return namedTupleTypeForCallee; } return null; @@ -122,6 +123,21 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { return null; } + @Nullable + static PyNamedTupleType getNamedTupleTypeForResolvedCallee(@NotNull PsiElement referenceTarget, + @NotNull TypeEvalContext context, + @Nullable PsiElement anchor) { + if (referenceTarget instanceof PyFunction && anchor instanceof PyCallExpression) { + return getNamedTupleFunctionType((PyFunction)referenceTarget, context, (PyCallExpression)anchor); + } + + if (referenceTarget instanceof PyTargetExpression) { + return getNamedTupleTypeForTarget((PyTargetExpression)referenceTarget, context); + } + + return null; + } + @Nullable private static PyType getEnumType(@NotNull PsiElement referenceTarget, @NotNull TypeEvalContext context, @Nullable PsiElement anchor) { @@ -173,29 +189,25 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { } @Nullable - private static PyCallableType getTypeFromCollectionsNTInheritorInitializing(@NotNull PyReferenceExpression referenceExpression, - @NotNull TypeEvalContext context) { - if (PyCallExpressionNavigator.getPyCallExpressionByCallee(referenceExpression) == null) { - return null; + private static PyType getFieldTypeForTypingNTTarget(@NotNull PyReferenceExpression referenceExpression, + @NotNull TypeEvalContext context) { + final PyExpression qualifier = referenceExpression.getQualifier(); + if (qualifier != null) { + final PyType qualifierType = context.getType(qualifier); + if (qualifierType instanceof PyNamedTupleType) { + final Map fields = ((PyNamedTupleType)qualifierType).getFields(); + final PyNamedTupleType.FieldTypeAndDefaultValue typeAndDefaultValue = fields.get(referenceExpression.getName()); + if (typeAndDefaultValue != null) { + return typeAndDefaultValue.getType(); + } + } } - - final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context); - final ResolveResult[] resolveResults = referenceExpression.getReference(resolveContext).multiResolve(false); - - return StreamEx - .of(PyUtil.filterTopPriorityResults(resolveResults)) - .select(PyTypedElement.class) - .map(context::getType) - .select(PyClassLikeType.class) - .flatCollection(type -> type.getAncestorTypes(context)) - .select(PyNamedTupleType.class) - .findFirst() - .orElse(null); + return null; } @Nullable - private static PyCallableType getTypeFromTypingNTInheritorInitializing(@NotNull PyReferenceExpression referenceExpression, - @NotNull TypeEvalContext context) { + private static PyNamedTupleType getNamedTupleTypeForCallee(@NotNull PyReferenceExpression referenceExpression, + @NotNull TypeEvalContext context) { if (PyCallExpressionNavigator.getPyCallExpressionByCallee(referenceExpression) == null) { return null; } @@ -203,11 +215,35 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context); final ResolveResult[] resolveResults = referenceExpression.getReference(resolveContext).multiResolve(false); - return StreamEx - .of(PyUtil.filterTopPriorityResults(resolveResults)) - .map(element -> getNamedTupleType(element, context, referenceExpression)) - .findFirst(Objects::nonNull) - .orElse(null); + for (PsiElement element : PyUtil.filterTopPriorityResults(resolveResults)) { + if (element instanceof PyTargetExpression) { + final PyNamedTupleType result = getNamedTupleTypeForTarget((PyTargetExpression)element, context); + if (result != null) { + return result; + } + } + + if (element instanceof PyClass) { + final PyNamedTupleType result = getNamedTupleTypeForTypingNTInheritorAsCallee((PyClass)element, context); + if (result != null) { + return result; + } + } + + if (element instanceof PyTypedElement) { + final PyType type = context.getType((PyTypedElement)element); + if (type instanceof PyClassLikeType) { + final List superClassTypes = ((PyClassLikeType)type).getSuperClassTypes(context); + + final PyClassLikeType superNTType = ContainerUtil.find(superClassTypes, PyNamedTupleType.class::isInstance); + if (superNTType != null) { + return as(superNTType, PyNamedTupleType.class); + } + } + } + } + + return null; } @Nullable @@ -336,76 +372,88 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { } @Nullable - static PyNamedTupleType getNamedTupleType(@NotNull PsiElement referenceTarget, - @NotNull TypeEvalContext context, - @Nullable PsiElement anchor) { - if (referenceTarget instanceof PyTargetExpression) { - final PyTargetExpression target = (PyTargetExpression)referenceTarget; - final PyTargetExpressionStub stub = target.getStub(); + private static PyNamedTupleType getNamedTupleFunctionType(@NotNull PyFunction function, + @NotNull TypeEvalContext context, + @NotNull PyCallExpression call) { + if (PyNames.COLLECTIONS_NAMEDTUPLE.equals(function.getQualifiedName())) { + return getNamedTupleTypeFromAST(call, context, PyNamedTupleType.DefinitionLevel.NT_FUNCTION); + } - if (stub != null) { - return getNamedTupleTypeFromStub(target, - stub.getCustomStub(PyNamedTupleStub.class), - context, - PyNamedTupleType.DefinitionLevel.NEW_TYPE); - } - else { - return getNamedTupleTypeFromAST(target, context, PyNamedTupleType.DefinitionLevel.NEW_TYPE); + if (PyUtil.isInit(function)) { + final PyClass cls = function.getContainingClass(); + if (cls != null && PyTypingTypeProvider.NAMEDTUPLE.equals(cls.getQualifiedName())) { + return getNamedTupleTypeFromAST(call, context, PyNamedTupleType.DefinitionLevel.NT_FUNCTION); } } - else if (referenceTarget instanceof PyFunction && anchor instanceof PyCallExpression) { - return getNamedTupleTypeFromAST((PyCallExpression)anchor, context, PyNamedTupleType.DefinitionLevel.AS_SUPERCLASS); + + return null; + } + + @Nullable + private static PyNamedTupleType getNamedTupleTypeForTarget(@NotNull PyTargetExpression target, @NotNull TypeEvalContext context) { + final PyTargetExpressionStub stub = target.getStub(); + + if (stub != null) { + return getNamedTupleTypeFromStub(target, + stub.getCustomStub(PyNamedTupleStub.class), + context, + PyNamedTupleType.DefinitionLevel.NEW_TYPE); } - else if (referenceTarget instanceof PyClass) { - final PyClass cls = (PyClass)referenceTarget; + else { + return getNamedTupleTypeFromAST(target, context, PyNamedTupleType.DefinitionLevel.NEW_TYPE); + } + } - final Condition isTypingNT = - type -> - type != null && - !(type instanceof PyNamedTupleType) && - PyTypingTypeProvider.NAMEDTUPLE.equals(type.getClassQName()); + @Nullable + private static PyNamedTupleType getNamedTupleTypeForTypingNTInheritorAsCallee(@NotNull PyClass cls, @NotNull TypeEvalContext context) { + final Condition isTypingNT = + type -> + type != null && + !(type instanceof PyNamedTupleType) && + PyTypingTypeProvider.NAMEDTUPLE.equals(type.getClassQName()); - if (ContainerUtil.exists(cls.getSuperClassTypes(context), isTypingNT)) { - final String name = cls.getName(); - if (name != null) { - final PsiElement typingNT = PyResolveImportUtil.resolveTopLevelMember(QualifiedName.fromDottedString(PyTypingTypeProvider.NAMEDTUPLE), - PyResolveImportUtil.fromFoothold(cls)); + if (ContainerUtil.exists(cls.getSuperClassTypes(context), isTypingNT)) { + final String name = cls.getName(); + if (name != null) { + final PsiElement typingNT = + PyResolveImportUtil.resolveTopLevelMember(QualifiedName.fromDottedString(PyTypingTypeProvider.NAMEDTUPLE), + PyResolveImportUtil.fromFoothold(cls)); - final PyClass tupleClass = as(typingNT, PyClass.class); - if (tupleClass != null) { - final Set fields = new TreeSet<>(Comparator.comparingInt(PyTargetExpression::getTextOffset)); + final PyClass tupleClass = as(typingNT, PyClass.class); + if (tupleClass != null) { + final Set fields = new TreeSet<>(Comparator.comparingInt(PyTargetExpression::getTextOffset)); - cls.processClassLevelDeclarations( - new BaseScopeProcessor() { - @Override - public boolean execute(@NotNull PsiElement element, @NotNull ResolveState substitutor) { - if (element instanceof PyTargetExpression) { - final PyTargetExpression target = (PyTargetExpression)element; - if (target.getAnnotation() != null) { - fields.add(target); - } + cls.processClassLevelDeclarations( + new BaseScopeProcessor() { + @Override + public boolean execute(@NotNull PsiElement element, @NotNull ResolveState substitutor) { + if (element instanceof PyTargetExpression) { + final PyTargetExpression target = (PyTargetExpression)element; + if (target.getAnnotation() != null) { + fields.add(target); } - - return true; } + + return true; } - ); + } + ); - final Collector> toNTFields = - Collectors.toMap(PyTargetExpression::getName, - field -> new PyNamedTupleType.FieldTypeAndDefaultValue(context.getType(field), field.findAssignedValue()), - (v1, v2) -> v2, - LinkedHashMap::new); + final Collector> toNTFields = + Collectors.toMap(PyTargetExpression::getName, + field -> new PyNamedTupleType.FieldTypeAndDefaultValue(context.getType(field), field.findAssignedValue()), + (v1, v2) -> v2, + LinkedHashMap::new); - return new PyNamedTupleType(tupleClass, - cls, - name, - fields.stream().collect(toNTFields), - PyNamedTupleType.DefinitionLevel.NEW_TYPE); - } + return new PyNamedTupleType(tupleClass, + cls, + name, + fields.stream().collect(toNTFields), + PyNamedTupleType.DefinitionLevel.NEW_TYPE); } } } + return null; } diff --git a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java index fffbb5f84fce..52fef4f65ce5 100644 --- a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java @@ -885,8 +885,7 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { private static List tryResolvingOnStubs(@NotNull PyReferenceExpression expression, @NotNull TypeEvalContext context) { - // PyPsiUtils.asQualifiedName() also takes into account subscription and prefix expressions - final QualifiedName qualifiedName = turnPlainReferenceExpressionIntoQualifiedName(expression); + final QualifiedName qualifiedName = expression.asQualifiedName(); final PyFile pyFile = as(FileContextUtil.getContextFile(expression), PyFile.class); if (pyFile != null && qualifiedName != null) { @@ -895,33 +894,6 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { return Collections.singletonList(expression); } - /** - * Return the qualified name containing all names in the given (possibly qualified) reference expression. - * If any of the qualifiers is not a reference expression, returns null. - *

      - * For instance, for the expression "foo.bar.baz" it returns the qualified name "foo.bar.baz", - * but for "foo[0].bar.baz" it will return null. - *

      - * If you need to take into account such implicit "magical" names, use {@link PyPsiUtils#asQualifiedName(PyExpression)} - * or {@link PyQualifiedExpression#asQualifiedName()}. - * @param expression - */ - @Nullable - public static QualifiedName turnPlainReferenceExpressionIntoQualifiedName(@NotNull PyReferenceExpression expression) { - final List components = new ArrayList<>(); - PyExpression remaining = expression; - while (remaining != null) { - final PyReferenceExpression remainingReference = as(remaining, PyReferenceExpression.class); - if (remainingReference == null) { - return null; - } - components.add(remainingReference.getReferencedName()); - remaining = remainingReference.getQualifier(); - } - Collections.reverse(components); - return QualifiedName.fromComponents(components); - } - private static boolean isBuiltinPathLike(@Nullable PsiElement element) { return element instanceof PyClass && PyBuiltinCache.getInstance(element).isBuiltin(element) && diff --git a/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java b/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java index 1d15e12d04a9..560fc44d5a67 100644 --- a/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java +++ b/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java @@ -134,7 +134,7 @@ public class PyIntegratedToolsConfigurable implements SearchableConfigurable { if (PyTestFrameworkService.getSdkReadableNameByFramework(framework).equals(selectedItem)) { if (!VFSTestFrameworkListener.getInstance().isTestFrameworkInstalled(sdk, framework)) { return new ValidationResult(PyBundle.message("runcfg.testing.no.test.framework", framework), - createQuickFix(sdk, facetErrorPanel, PyNames.PY_TEST)); + createQuickFix(sdk, facetErrorPanel, PyTestFrameworkService.getPackageByFramework(framework))); } } } diff --git a/python/src/com/jetbrains/python/console/PyCodeExecutor.java b/python/src/com/jetbrains/python/console/PyCodeExecutor.java index 069a16167137..e0d60b3d51aa 100644 --- a/python/src/com/jetbrains/python/console/PyCodeExecutor.java +++ b/python/src/com/jetbrains/python/console/PyCodeExecutor.java @@ -16,12 +16,11 @@ package com.jetbrains.python.console; import com.intellij.openapi.editor.Editor; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * @author traff */ public interface PyCodeExecutor { - void executeCode(@NotNull String code, @Nullable Editor e); + void executeCode(@Nullable String code, @Nullable Editor e); } diff --git a/python/src/com/jetbrains/python/console/PyConsoleUtil.java b/python/src/com/jetbrains/python/console/PyConsoleUtil.java index 51a0992df309..5d7a87f62f94 100644 --- a/python/src/com/jetbrains/python/console/PyConsoleUtil.java +++ b/python/src/com/jetbrains/python/console/PyConsoleUtil.java @@ -18,6 +18,7 @@ package com.jetbrains.python.console; import com.google.common.base.CharMatcher; import com.intellij.codeInsight.lookup.LookupManager; import com.intellij.execution.console.LanguageConsoleView; +import com.intellij.execution.process.ProcessOutputTypes; import com.intellij.execution.ui.ConsoleViewContentType; import com.intellij.openapi.actionSystem.ActionManager; import com.intellij.openapi.actionSystem.AnAction; @@ -25,15 +26,21 @@ import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.IdeActions; import com.intellij.openapi.actionSystem.ex.ActionUtil; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.ex.DocumentEx; +import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.IJSwingUtilities; import com.jetbrains.python.console.parsing.PythonConsoleData; +import com.jetbrains.python.console.pydev.ConsoleCommunication; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.awt.event.InputEvent; import java.awt.event.KeyEvent; /** @@ -192,6 +199,41 @@ public class PyConsoleUtil { runCompletions.getTemplatePresentation().setVisible(false); return runCompletions; } + + public static AnAction createInterruptAction(PythonConsoleView consoleView) { + AnAction anAction = new AnAction() { + @Override + public void actionPerformed(final AnActionEvent e) { + ConsoleCommunication consoleCommunication = consoleView.getExecuteActionHandler().getConsoleCommunication(); + if (consoleCommunication.isExecuting() || consoleCommunication.isWaitingForInput()) { + consoleView.print("^C", ProcessOutputTypes.SYSTEM); + consoleCommunication.interrupt(); + } + else { + DocumentEx document = consoleView.getConsoleEditor().getDocument(); + if (document.getTextLength() != 0) { + ApplicationManager.getApplication().runWriteAction(() -> + CommandProcessor + .getInstance() + .runUndoTransparentAction(() -> document.deleteString(0, document + .getLineEndOffset(document.getLineCount() - 1)))); + } + } + } + + @Override + public void update(final AnActionEvent e) { + EditorEx consoleEditor = consoleView.getConsoleEditor(); + boolean enabled = IJSwingUtilities.hasFocus(consoleEditor.getComponent()) && !consoleEditor.getSelectionModel().hasSelection(); + e.getPresentation().setEnabled(enabled); + } + }; + + anAction + .registerCustomShortcutSet(KeyEvent.VK_C, InputEvent.CTRL_MASK, consoleView.getConsoleEditor().getComponent()); + anAction.getTemplatePresentation().setVisible(false); + return anAction; + } } diff --git a/python/src/com/jetbrains/python/console/PyOpenDebugConsoleAction.java b/python/src/com/jetbrains/python/console/PyOpenDebugConsoleAction.java deleted file mode 100644 index 4cf9a7321871..000000000000 --- a/python/src/com/jetbrains/python/console/PyOpenDebugConsoleAction.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2000-2014 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.jetbrains.python.console; - -import com.intellij.execution.ExecutionHelper; -import com.intellij.execution.process.ProcessHandler; -import com.intellij.execution.ui.RunContentDescriptor; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.util.Consumer; -import com.intellij.util.NotNullFunction; -import icons.PythonIcons; -import org.jetbrains.annotations.NotNull; - -import java.util.Collection; - -/** - * @author traff - */ -public class PyOpenDebugConsoleAction extends AnAction implements DumbAware { - - public PyOpenDebugConsoleAction() { - super(); - getTemplatePresentation().setIcon(PythonIcons.Python.Debug.CommandLine); - } - - @Override - public void update(final AnActionEvent e) { - e.getPresentation().setVisible(false); - e.getPresentation().setEnabled(true); - final Project project = e.getData(CommonDataKeys.PROJECT); - if (project != null) { - e.getPresentation().setVisible(getConsoles(project).size() > 0); - } - } - - @Override - public void actionPerformed(final AnActionEvent e) { - final Project project = e.getData(CommonDataKeys.PROJECT); - if (project != null) { - selectRunningProcess(e.getDataContext(), project, view -> { - view.enableConsole(false); - IdeFocusManager.getInstance(project).requestFocus(view.getPydevConsoleView().getComponent(), true); - }); - } - } - - - private static void selectRunningProcess(@NotNull DataContext dataContext, @NotNull Project project, - final Consumer consumer) { - Collection consoles = getConsoles(project); - - ExecutionHelper - .selectContentDescriptor(dataContext, project, consoles, "Select running python process", descriptor -> { - if (descriptor != null && descriptor.getExecutionConsole() instanceof PythonDebugLanguageConsoleView) { - consumer.consume((PythonDebugLanguageConsoleView)descriptor.getExecutionConsole()); - } - }); - } - - private static Collection getConsoles(Project project) { - return ExecutionHelper.findRunningConsole(project, - dom -> dom.getExecutionConsole() instanceof PythonDebugLanguageConsoleView && isAlive(dom)); - } - - private static boolean isAlive(RunContentDescriptor dom) { - ProcessHandler processHandler = dom.getProcessHandler(); - return processHandler != null && !processHandler.isProcessTerminated(); - } -} diff --git a/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java b/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java index 8ca8de1b2534..ca7a1072a0f8 100644 --- a/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java +++ b/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java @@ -44,7 +44,6 @@ import org.apache.xmlrpc.XmlRpcHandler; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.IOException; import java.net.MalformedURLException; import java.util.*; @@ -116,6 +115,10 @@ public class PydevConsoleCommunication extends AbstractConsoleCommunication impl * @throws MalformedURLException */ public PydevConsoleCommunication(Project project, int port, Process process, int clientPort) throws Exception { + this(project, null, port, process, clientPort); + } + + public PydevConsoleCommunication(Project project, String host, int port, Process process, int clientPort) throws Exception { super(project); //start the server that'll handle input requests @@ -124,7 +127,7 @@ public class PydevConsoleCommunication extends AbstractConsoleCommunication impl myWebServer.addHandler("$default", this); this.myWebServer.start(); - this.myClient = new PydevXmlRpcClient(process, port); + this.myClient = new PydevXmlRpcClient(process, host, port); } public boolean handshake() throws XmlRpcException { @@ -668,19 +671,6 @@ public class PydevConsoleCommunication extends AbstractConsoleCommunication impl super(port); } - @Override - public synchronized void shutdown() { - try { - if (serverSocket != null) { - serverSocket.close(); - } - } - catch (IOException e) { - //pass - } - super.shutdown(); - } - public boolean waitForTerminate() { if (listener != null) { return new WaitFor(10000) { diff --git a/python/src/com/jetbrains/python/console/PydevConsoleRunner.java b/python/src/com/jetbrains/python/console/PydevConsoleRunner.java index 666ae365757a..ad90029ab1c2 100644 --- a/python/src/com/jetbrains/python/console/PydevConsoleRunner.java +++ b/python/src/com/jetbrains/python/console/PydevConsoleRunner.java @@ -62,24 +62,31 @@ public interface PydevConsoleRunner { @Nullable - static PyRemotePathMapper getPathMapper(@NotNull Project project, Sdk sdk, PyConsoleOptions.PyConsoleSettings consoleSettings) { + static PyRemotePathMapper getPathMapper(@NotNull Project project, + Sdk sdk, + PyConsoleOptions.PyConsoleSettings consoleSettings) { if (PySdkUtil.isRemote(sdk)) { PythonRemoteInterpreterManager instance = PythonRemoteInterpreterManager.getInstance(); if (instance != null) { - //noinspection ConstantConditions - PyRemotePathMapper remotePathMapper = - instance.setupMappings(project, (PyRemoteSdkAdditionalDataBase)sdk.getSdkAdditionalData(), null); - - PathMappingSettings mappingSettings = consoleSettings.getMappingSettings(); - - remotePathMapper.addAll(mappingSettings.getPathMappings(), PyRemotePathMapper.PyPathMappingType.USER_DEFINED); - - return remotePathMapper; + PyRemoteSdkAdditionalDataBase remoteSdkAdditionalData = (PyRemoteSdkAdditionalDataBase)sdk.getSdkAdditionalData(); + return getPathMapper(project, consoleSettings, instance, remoteSdkAdditionalData); } } return null; } + @NotNull + static PyRemotePathMapper getPathMapper(@NotNull Project project, + PyConsoleOptions.PyConsoleSettings consoleSettings, + PythonRemoteInterpreterManager instance, + PyRemoteSdkAdditionalDataBase remoteSdkAdditionalData) { + //noinspection ConstantConditions + PyRemotePathMapper remotePathMapper = instance.setupMappings(project, remoteSdkAdditionalData, null); + PathMappingSettings mappingSettings = consoleSettings.getMappingSettings(); + remotePathMapper.addAll(mappingSettings.getPathMappings(), PyRemotePathMapper.PyPathMappingType.USER_DEFINED); + return remotePathMapper; + } + @NotNull static Pair findPythonSdkAndModule(@NotNull Project project, @Nullable Module contextModule) { Sdk sdk = null; diff --git a/python/src/com/jetbrains/python/console/PydevConsoleRunnerImpl.java b/python/src/com/jetbrains/python/console/PydevConsoleRunnerImpl.java index c95df0b2eb9c..aea689b404fb 100644 --- a/python/src/com/jetbrains/python/console/PydevConsoleRunnerImpl.java +++ b/python/src/com/jetbrains/python/console/PydevConsoleRunnerImpl.java @@ -44,15 +44,12 @@ import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.application.TransactionGuard; -import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Caret; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.actionSystem.EditorAction; import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler; import com.intellij.openapi.editor.actions.SplitLineAction; -import com.intellij.openapi.editor.ex.DocumentEx; -import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.progress.ProgressIndicator; @@ -72,14 +69,16 @@ import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.ToolWindow; import com.intellij.psi.PsiFile; -import com.intellij.remote.RemoteProcess; -import com.intellij.remote.Tunnelable; +import com.intellij.remote.*; import com.intellij.testFramework.LightVirtualFile; import com.intellij.ui.GuiUtils; import com.intellij.ui.JBColor; import com.intellij.ui.SideBorder; import com.intellij.ui.content.Content; -import com.intellij.util.*; +import com.intellij.util.ArrayUtil; +import com.intellij.util.Consumer; +import com.intellij.util.PathMappingSettings; +import com.intellij.util.TimeoutUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.net.NetUtils; import com.intellij.util.ui.MessageCategory; @@ -106,8 +105,6 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; -import java.awt.event.InputEvent; -import java.awt.event.KeyEvent; import java.io.File; import java.io.IOException; import java.net.ServerSocket; @@ -214,7 +211,7 @@ public class PydevConsoleRunnerImpl implements PydevConsoleRunner { actions.add(0, createRerunAction()); - actions.add(createInterruptAction()); + actions.add(PyConsoleUtil.createInterruptAction(myConsoleView)); actions.add(PyConsoleUtil.createTabCompletionAction(myConsoleView)); actions.add(createSplitLineAction()); @@ -340,7 +337,7 @@ public class PydevConsoleRunnerImpl implements PydevConsoleRunner { return DefaultRunExecutor.getRunExecutorInstance(); } - private static int[] findAvailablePorts(Project project, PyConsoleType consoleType) { + public static int[] findAvailablePorts(Project project, PyConsoleType consoleType) { final int[] ports; try { // File "pydev/console/pydevconsole.py", line 223, in @@ -399,8 +396,31 @@ public class PydevConsoleRunnerImpl implements PydevConsoleRunner { PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance(); if (manager != null) { UsageTrigger.trigger(CONSOLE_FEATURE + ".remote"); - return createRemoteConsoleProcess(manager, myGeneralCommandLine.getParametersList().getArray(), - myGeneralCommandLine.getEnvironment(), myGeneralCommandLine.getWorkDirectory()); + + PyRemoteSdkAdditionalDataBase data = (PyRemoteSdkAdditionalDataBase)mySdk.getSdkAdditionalData(); + CredentialsType connectionType = data.getRemoteConnectionType(); + + if (connectionType == CredentialsType.SSH_HOST || + connectionType == CredentialsType.WEB_DEPLOYMENT || + connectionType == CredentialsType.VAGRANT) { + return createRemoteConsoleProcess(manager, + myGeneralCommandLine.getParametersList().getArray(), + myGeneralCommandLine.getEnvironment(), + myGeneralCommandLine.getWorkDirectory()); + } + + RemoteConsoleProcessData remoteConsoleProcessData = + PythonConsoleRemoteProcessCreatorKt.createRemoteConsoleProcess(manager, myGeneralCommandLine.getParametersList().getArray(), + myGeneralCommandLine.getEnvironment(), + myGeneralCommandLine.getWorkDirectory(), + PydevConsoleRunner.getPathMapper(myProject, mySdk, myConsoleSettings), + myProject, data, getRunnerFileFromHelpers()); + + myRemoteProcessHandlerBase = remoteConsoleProcessData.getRemoteProcessHandlerBase(); + myCommandLine = myRemoteProcessHandlerBase.getCommandLine(); + myPydevConsoleCommunication = remoteConsoleProcessData.getPydevConsoleCommunication(); + + return myRemoteProcessHandlerBase.getProcess(); } throw new PythonRemoteInterpreterManager.PyRemoteInterpreterExecutionException(); } @@ -508,7 +528,7 @@ public class PydevConsoleRunnerImpl implements PydevConsoleRunner { DOCKER_CONTAINER_PROJECT_PATH))); } - private static Couple getRemotePortsFromProcess(RemoteProcess process) throws ExecutionException { + public static Couple getRemotePortsFromProcess(RemoteProcess process) throws ExecutionException { Scanner s = new Scanner(process.getInputStream()); return Couple.of(readInt(s, process), readInt(s, process)); @@ -699,38 +719,6 @@ public class PydevConsoleRunnerImpl implements PydevConsoleRunner { return new RestartAction(this); } - private AnAction createInterruptAction() { - AnAction anAction = new AnAction() { - @Override - public void actionPerformed(final AnActionEvent e) { - if (myPydevConsoleCommunication.isExecuting() || myPydevConsoleCommunication.isWaitingForInput()) { - myConsoleView.print("^C", ProcessOutputTypes.SYSTEM); - myPydevConsoleCommunication.interrupt(); - } else{ - DocumentEx document = myConsoleView.getConsoleEditor().getDocument(); - if (!(document.getTextLength() == 0)) { - ApplicationManager.getApplication().runWriteAction(() -> - CommandProcessor - .getInstance() - .runUndoTransparentAction(() -> document.deleteString(0, document.getLineEndOffset(document.getLineCount() - 1)))); - } - - } - } - - @Override - public void update(final AnActionEvent e) { - EditorEx consoleEditor = myConsoleView.getConsoleEditor(); - boolean enabled = IJSwingUtilities.hasFocus(consoleEditor.getComponent()) && !consoleEditor.getSelectionModel().hasSelection(); - e.getPresentation().setEnabled(enabled); - } - }; - anAction - .registerCustomShortcutSet(KeyEvent.VK_C, InputEvent.CTRL_MASK, myConsoleView.getConsoleEditor().getComponent()); - anAction.getTemplatePresentation().setVisible(false); - return anAction; - } - private void enableConsoleExecuteAction() { myConsoleExecuteActionHandler.setEnabled(true); } diff --git a/python/src/com/jetbrains/python/console/PydevRemoteConsoleCommunication.java b/python/src/com/jetbrains/python/console/PydevRemoteConsoleCommunication.java index 9e4264fc22f5..ee2e54764d4a 100644 --- a/python/src/com/jetbrains/python/console/PydevRemoteConsoleCommunication.java +++ b/python/src/com/jetbrains/python/console/PydevRemoteConsoleCommunication.java @@ -34,4 +34,8 @@ public class PydevRemoteConsoleCommunication extends PydevConsoleCommunication { throws Exception { super(project, port, process, clientPort); } + + public PydevRemoteConsoleCommunication(Project project, int port, Process process, int clientPort, String clientHost) throws Exception { + super(project, clientHost, port, process, clientPort); + } } diff --git a/python/src/com/jetbrains/python/console/PythonConsoleRemoteProcessCreator.kt b/python/src/com/jetbrains/python/console/PythonConsoleRemoteProcessCreator.kt new file mode 100644 index 000000000000..2085dd21b6e0 --- /dev/null +++ b/python/src/com/jetbrains/python/console/PythonConsoleRemoteProcessCreator.kt @@ -0,0 +1,99 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.console + +import com.intellij.execution.ExecutionException +import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Ref +import com.intellij.remote.CredentialsType +import com.intellij.remote.ext.CredentialsCase +import com.jetbrains.python.remote.PyRemotePathMapper +import com.jetbrains.python.remote.PyRemoteProcessHandlerBase +import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase +import com.jetbrains.python.remote.PythonRemoteInterpreterManager +import java.io.File + +interface PythonConsoleRemoteProcessCreator { + val credentialsType: CredentialsType + + @Throws(ExecutionException::class) + fun createRemoteConsoleProcess(manager: PythonRemoteInterpreterManager, + command: Array, + env: Map, + workDirectory: File, + pathMapper: PyRemotePathMapper, + project: Project, + data: PyRemoteSdkAdditionalDataBase, + runnerFileFromHelpers: String, + credentials: T): RemoteConsoleProcessData + + companion object { + val EP_NAME = ExtensionPointName.create>("Pythonid.remoteConsoleProcessCreator") + } +} + +data class RemoteConsoleProcessData(val remoteProcessHandlerBase: PyRemoteProcessHandlerBase, + val pydevConsoleCommunication: PydevRemoteConsoleCommunication) + +@Throws(ExecutionException::class) +fun createRemoteConsoleProcess(manager: PythonRemoteInterpreterManager, + command: Array, + env: Map, + workDirectory: File, + pathMapper: PyRemotePathMapper, + project: Project, + data: PyRemoteSdkAdditionalDataBase, + runnerFileFromHelpers: String): RemoteConsoleProcessData { + val extensions = PythonConsoleRemoteProcessCreator.EP_NAME.extensions + val result = Ref.create() + val exception = Ref.create() + + val collectedCases = extensions.map { + object : CredentialsCase { + override fun getType(): CredentialsType = it.credentialsType + + override fun process(credentials: Any) { + try { + val remoteConsoleProcess = it.createRemoteConsoleProcess(manager = manager, + command = command, + env = env, + workDirectory = workDirectory, + pathMapper = pathMapper, + project = project, + data = data, + runnerFileFromHelpers = runnerFileFromHelpers, + credentials = credentials) + result.set(remoteConsoleProcess) + } + catch(e: ExecutionException) { + exception.set(e) + } + } + } as CredentialsCase + } + + data.switchOnConnectionType(*collectedCases.toTypedArray()) + if (!exception.isNull) { + throw exception.get() + } + else if (!result.isNull) { + return result.get() + } + else { + throw ExecutionException("Python console for ${data.remoteConnectionType.name} interpreter is not supported") + } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/console/PythonConsoleView.java b/python/src/com/jetbrains/python/console/PythonConsoleView.java index eeef188f4ac8..20c63f8b1b84 100644 --- a/python/src/com/jetbrains/python/console/PythonConsoleView.java +++ b/python/src/com/jetbrains/python/console/PythonConsoleView.java @@ -181,37 +181,42 @@ public class PythonConsoleView extends LanguageConsoleImpl implements Observable } @Override - public void executeCode(final @NotNull String code, @Nullable final Editor editor) { + public void executeCode(final @Nullable String code, @Nullable final Editor editor) { myInitialized.doWhenDone( - () -> - ProgressManager.getInstance().run(new Task.Backgroundable(null, "Executing Code in Console...", false) { - @Override - public void run(@NotNull final ProgressIndicator indicator) { - long time = System.currentTimeMillis(); - while (!myExecuteActionHandler.isEnabled() || !myExecuteActionHandler.canExecuteNow()) { - if (indicator.isCanceled()) { - break; - } - if (System.currentTimeMillis() - time > 1000) { - if (editor != null) { - UIUtil.invokeLaterIfNeeded( - () -> HintManager.getInstance() - .showErrorHint(editor, myExecuteActionHandler.getCantExecuteMessage())); + () -> { + if (code != null) { + ProgressManager.getInstance().run(new Task.Backgroundable(null, "Executing Code in Console...", false) { + @Override + public void run(@NotNull final ProgressIndicator indicator) { + long time = System.currentTimeMillis(); + while (!myExecuteActionHandler.isEnabled() || !myExecuteActionHandler.canExecuteNow()) { + if (indicator.isCanceled()) { + break; } - return; + if (System.currentTimeMillis() - time > 1000) { + if (editor != null) { + UIUtil.invokeLaterIfNeeded( + () -> HintManager.getInstance() + .showErrorHint(editor, myExecuteActionHandler.getCantExecuteMessage())); + } + return; + } + TimeoutUtil.sleep(300); + } + if (!indicator.isCanceled()) { + executeInConsole(code); } - TimeoutUtil.sleep(300); } - if (!indicator.isCanceled()) { - executeInConsole(code); - } - } - }) + }); + } else { + requestFocus(); + } + } ); } - public void executeInConsole(final String code) { + public void executeInConsole(@NotNull final String code) { TransactionGuard.submitTransaction(this, () -> { final String codeToExecute = code.endsWith("\n") || myExecuteActionHandler.checkSingleLine(code) ? code : code + "\n"; DocumentEx document = getConsoleEditor().getDocument(); diff --git a/python/src/com/jetbrains/python/console/PythonDebugLanguageConsoleView.java b/python/src/com/jetbrains/python/console/PythonDebugLanguageConsoleView.java index 565b957e3418..35f5f7b0678d 100644 --- a/python/src/com/jetbrains/python/console/PythonDebugLanguageConsoleView.java +++ b/python/src/com/jetbrains/python/console/PythonDebugLanguageConsoleView.java @@ -51,6 +51,7 @@ public class PythonDebugLanguageConsoleView extends DuplexConsoleView actions = ContainerUtil.newArrayList(PyConsoleUtil.createTabCompletionAction(getPydevConsoleView())); + actions.add(PyConsoleUtil.createInterruptAction(getPydevConsoleView())); AbstractConsoleRunnerWithHistory.registerActionShortcuts(actions, getPydevConsoleView().getEditor().getComponent()); } @@ -59,9 +60,13 @@ public class PythonDebugLanguageConsoleView extends DuplexConsoleView getPydevConsoleView().requestFocus()); + } } @NotNull diff --git a/python/src/com/jetbrains/python/console/RunPythonConsoleAction.java b/python/src/com/jetbrains/python/console/RunPythonConsoleAction.java index 0e5d682198dd..2020ffd1710e 100644 --- a/python/src/com/jetbrains/python/console/RunPythonConsoleAction.java +++ b/python/src/com/jetbrains/python/console/RunPythonConsoleAction.java @@ -24,6 +24,7 @@ import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.util.Pair; +import com.jetbrains.python.actions.PyExecuteSelectionAction; import icons.PythonIcons; /** @@ -50,7 +51,6 @@ public class RunPythonConsoleAction extends AnAction implements DumbAware { } public void actionPerformed(final AnActionEvent e) { - PydevConsoleRunner runner = PythonConsoleRunnerFactory.getInstance().createConsoleRunner(e.getData(CommonDataKeys.PROJECT), e.getData(LangDataKeys.MODULE)); - runner.open(); + PyExecuteSelectionAction.showConsoleAndExecuteCode(e, null); } } diff --git a/python/src/com/jetbrains/python/documentation/docstrings/PyDocstringGenerator.java b/python/src/com/jetbrains/python/documentation/docstrings/PyDocstringGenerator.java index 4630e240e2d5..75bc9219c0df 100644 --- a/python/src/com/jetbrains/python/documentation/docstrings/PyDocstringGenerator.java +++ b/python/src/com/jetbrains/python/documentation/docstrings/PyDocstringGenerator.java @@ -19,16 +19,13 @@ import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; -import com.intellij.codeInsight.CodeInsightUtilCore; import com.intellij.codeInsight.template.*; -import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.util.PsiTreeUtil; @@ -351,12 +348,14 @@ public class PyDocstringGenerator { return; } builder.replaceRange(substring.getTextRange(), getDefaultType(getParamToEdit())); - Template template = ((TemplateBuilderImpl)builder).buildInlineTemplate(); + Template template = PyUtil.updateDocumentUnblockedAndCommitted(myDocStringOwner, document -> { + return ((TemplateBuilderImpl)builder).buildInlineTemplate(); + }); final VirtualFile virtualFile = myDocStringOwner.getContainingFile().getVirtualFile(); if (virtualFile == null) return; final Project project = myDocStringOwner.getProject(); final Editor targetEditor = PsiUtilBase.findEditor(myDocStringOwner); - if (targetEditor != null) { + if (targetEditor != null && template != null) { targetEditor.getCaretModel().moveToOffset(docStringExpression.getTextOffset()); TemplateManager.getInstance(project).startTemplate(targetEditor, template); } @@ -529,43 +528,26 @@ public class PyDocstringGenerator { } final PyStatementList statements = container.getStatementList(); final String indentation = PyIndentUtil.getElementIndent(statements); - final Document document = PsiDocumentManager.getInstance(project).getDocument(myDocStringOwner.getContainingFile()); - if (document != null) { + PyUtil.updateDocumentUnblockedAndCommitted(myDocStringOwner, document -> { final PsiElement beforeStatements = statements.getPrevSibling(); - final boolean onSameLine = !(beforeStatements instanceof PsiWhiteSpace) || !beforeStatements.textContains('\n'); - if (onSameLine || statements.getStatements().length == 0) { - String replacementWithLineBreaks = "\n" + indentation + replacementText; - if (statements.getStatements().length > 0) { - replacementWithLineBreaks += "\n" + indentation; - } - final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); - documentManager.doPostponedOperationsAndUnblockDocument(document); - final TextRange range = beforeStatements.getTextRange(); - try { - if (beforeStatements instanceof PsiWhiteSpace) { - if (statements.getStatements().length > 0) { - document.replaceString(range.getStartOffset(), range.getEndOffset(), replacementWithLineBreaks); - } - else { - // preserve original spacing, since it probably separates function and other declarations - document.insertString(range.getStartOffset(), replacementWithLineBreaks); - } - } - else { - document.insertString(range.getEndOffset(), replacementWithLineBreaks); - } - } - finally { - documentManager.commitDocument(document); - } + String replacementWithLineBreaks = "\n" + indentation + replacementText; + if (statements.getStatements().length > 0) { + replacementWithLineBreaks += "\n" + indentation; + } + final TextRange range = beforeStatements.getTextRange(); + if (!(beforeStatements instanceof PsiWhiteSpace)) { + document.insertString(range.getEndOffset(), replacementWithLineBreaks); + } + else if (statements.getStatements().length == 0 && beforeStatements.textContains('\n')) { + // preserve original spacing, since it probably separates function and other declarations + document.insertString(range.getStartOffset(), replacementWithLineBreaks); } else { - statements.addBefore(replacement, statements.getStatements()[0]); + document.replaceString(range.getStartOffset(), range.getEndOffset(), replacementWithLineBreaks); } - } + }); } - myDocStringOwner = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(myDocStringOwner); return myDocStringOwner; } diff --git a/python/src/com/jetbrains/python/findUsages/PyClassFindUsagesHandler.java b/python/src/com/jetbrains/python/findUsages/PyClassFindUsagesHandler.java index bde1b0851bbd..c1f44e00d4ef 100644 --- a/python/src/com/jetbrains/python/findUsages/PyClassFindUsagesHandler.java +++ b/python/src/com/jetbrains/python/findUsages/PyClassFindUsagesHandler.java @@ -15,7 +15,6 @@ */ package com.jetbrains.python.findUsages; -import com.intellij.find.findUsages.FindUsagesHandler; import com.intellij.psi.PsiElement; import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.PyClass; @@ -28,7 +27,7 @@ import java.util.Collections; /** * @author yole */ -public class PyClassFindUsagesHandler extends FindUsagesHandler { +public class PyClassFindUsagesHandler extends PyFindUsagesHandler { private final PyClass myClass; public PyClassFindUsagesHandler(@NotNull PyClass psiElement) { diff --git a/python/src/com/jetbrains/python/findUsages/PyFindUsagesHandler.java b/python/src/com/jetbrains/python/findUsages/PyFindUsagesHandler.java new file mode 100644 index 000000000000..b452efeaa988 --- /dev/null +++ b/python/src/com/jetbrains/python/findUsages/PyFindUsagesHandler.java @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.findUsages; + +import com.intellij.find.findUsages.FindUsagesHandler; +import com.intellij.find.findUsages.FindUsagesOptions; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Yuli Fiterman + */ +public abstract class PyFindUsagesHandler extends FindUsagesHandler { + protected PyFindUsagesHandler(@NotNull PsiElement psiElement) { + super(psiElement); + } + + @NotNull + @Override + public FindUsagesOptions getFindUsagesOptions(@Nullable DataContext dataContext) { + PyFindUsagesOptions sharedOpts = PyFindUsagesOptions.getInstance(getProject()); + return !isSearchForTextOccurrencesAvailable(getPsiElement(), false) ? (PyFindUsagesOptions)sharedOpts.clone() : sharedOpts; + } +} diff --git a/python/src/com/jetbrains/python/findUsages/PyFindUsagesOptions.java b/python/src/com/jetbrains/python/findUsages/PyFindUsagesOptions.java new file mode 100644 index 000000000000..6fb5f5029bac --- /dev/null +++ b/python/src/com/jetbrains/python/findUsages/PyFindUsagesOptions.java @@ -0,0 +1,60 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.findUsages; + +import com.intellij.find.findUsages.FindUsagesHandler; +import com.intellij.find.findUsages.FindUsagesOptions; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.components.*; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Yuli Fiterman + */ +@State(name = "PyFindUsagesOptions", storages = @Storage(StoragePathMacros.WORKSPACE_FILE)) +public class PyFindUsagesOptions extends FindUsagesOptions implements PersistentStateComponent { + + + public PyFindUsagesOptions(@NotNull Project project) { + super(project); + isUsages = true; + isSearchForTextOccurrences = false; + } + + public static PyFindUsagesOptions getInstance(Project project) { + return ServiceManager.getService(project, PyFindUsagesOptions.class); + } + + + @Nullable + @Override + public State getState() { + State s = new State(); + s.isSearchForTextOccurrences = isSearchForTextOccurrences; + return s; + } + + @Override + public void loadState(State state) { + isSearchForTextOccurrences = state.isSearchForTextOccurrences; + } + + static class State { + public boolean isSearchForTextOccurrences; + } +} diff --git a/python/src/com/jetbrains/python/findUsages/PyFunctionFindUsagesHandler.java b/python/src/com/jetbrains/python/findUsages/PyFunctionFindUsagesHandler.java index 4a337cc55789..8edc8e8e3822 100644 --- a/python/src/com/jetbrains/python/findUsages/PyFunctionFindUsagesHandler.java +++ b/python/src/com/jetbrains/python/findUsages/PyFunctionFindUsagesHandler.java @@ -15,7 +15,6 @@ */ package com.jetbrains.python.findUsages; -import com.intellij.find.findUsages.FindUsagesHandler; import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; @@ -24,7 +23,7 @@ import java.util.List; /** * @author yole */ -public class PyFunctionFindUsagesHandler extends FindUsagesHandler { +public class PyFunctionFindUsagesHandler extends PyFindUsagesHandler { private final List myAllElements; public PyFunctionFindUsagesHandler(@NotNull PsiElement psiElement) { diff --git a/python/src/com/jetbrains/python/findUsages/PyModuleFindUsagesHandler.java b/python/src/com/jetbrains/python/findUsages/PyModuleFindUsagesHandler.java index 6817b1abaac6..771c64429dff 100644 --- a/python/src/com/jetbrains/python/findUsages/PyModuleFindUsagesHandler.java +++ b/python/src/com/jetbrains/python/findUsages/PyModuleFindUsagesHandler.java @@ -39,7 +39,7 @@ import java.util.List; /** * @author yole */ -public class PyModuleFindUsagesHandler extends FindUsagesHandler { +public class PyModuleFindUsagesHandler extends PyFindUsagesHandler { private final PsiFileSystemItem myElement; protected PyModuleFindUsagesHandler(@NotNull PsiFileSystemItem file) { diff --git a/python/src/com/jetbrains/python/findUsages/PyTargetExpressionFindUsagesHandler.java b/python/src/com/jetbrains/python/findUsages/PyTargetExpressionFindUsagesHandler.java index 5956973ed7fd..d4bc6f15e46c 100644 --- a/python/src/com/jetbrains/python/findUsages/PyTargetExpressionFindUsagesHandler.java +++ b/python/src/com/jetbrains/python/findUsages/PyTargetExpressionFindUsagesHandler.java @@ -7,7 +7,7 @@ import org.jetbrains.annotations.NotNull; /** * @author Mikhail Golubev */ -public class PyTargetExpressionFindUsagesHandler extends FindUsagesHandler { +public class PyTargetExpressionFindUsagesHandler extends PyFindUsagesHandler { public PyTargetExpressionFindUsagesHandler(@NotNull PyTargetExpression psiElement) { super(psiElement); } diff --git a/python/src/com/jetbrains/python/formatter/PyBlock.java b/python/src/com/jetbrains/python/formatter/PyBlock.java index aef895ed9062..8e70a14dfe21 100644 --- a/python/src/com/jetbrains/python/formatter/PyBlock.java +++ b/python/src/com/jetbrains/python/formatter/PyBlock.java @@ -90,18 +90,18 @@ public class PyBlock implements ASTBlock { private List mySubBlocks = null; private Map mySubBlockByNode = null; private final boolean myEmptySequence; - + // Shared among multiple children sub-blocks private Alignment myChildAlignment = null; private Alignment myDictAlignment = null; private Wrap myDictWrapping = null; private Wrap myFromImportWrapping = null; - public PyBlock(@Nullable PyBlock parent, - @NotNull ASTNode node, - @Nullable Alignment alignment, - @NotNull Indent indent, - @Nullable Wrap wrap, + public PyBlock(@Nullable PyBlock parent, + @NotNull ASTNode node, + @Nullable Alignment alignment, + @NotNull Indent indent, + @Nullable Wrap wrap, @NotNull PyBlockContext context) { myParent = parent; myAlignment = alignment; @@ -189,7 +189,7 @@ public class PyBlock implements ASTBlock { Wrap childWrap = null; Indent childIndent = Indent.getNoneIndent(); Alignment childAlignment = null; - + final PyCodeStyleSettings settings = myContext.getPySettings(); if (parentType == PyElementTypes.BINARY_EXPRESSION && !isInControlStatement()) { @@ -260,7 +260,7 @@ public class PyBlock implements ASTBlock { } } - + if (parentType == PyElementTypes.LIST_LITERAL_EXPRESSION || parentType == PyElementTypes.LIST_COMP_EXPRESSION) { if ((childType == PyTokenTypes.RBRACKET && !settings.HANG_CLOSING_BRACKETS) || childType == PyTokenTypes.LBRACKET) { childIndent = Indent.getNoneIndent(); @@ -617,7 +617,7 @@ public class PyBlock implements ASTBlock { myContext.getMode() == FormattingMode.ADJUST_INDENT) { return true; } - return !hasHangingIndent(myNode.getPsi()) && !(myNode.getElementType() == PyElementTypes.DICT_LITERAL_EXPRESSION && + return !hasHangingIndent(myNode.getPsi()) && !(myNode.getElementType() == PyElementTypes.DICT_LITERAL_EXPRESSION && myContext.getPySettings().DICT_NEW_LINE_AFTER_LEFT_BRACE); } if (myNode.getElementType() == PyElementTypes.ARGUMENT_LIST) { @@ -719,6 +719,8 @@ public class PyBlock implements ASTBlock { @Override @Nullable public Spacing getSpacing(@Nullable Block child1, @NotNull Block child2) { + final CommonCodeStyleSettings settings = myContext.getSettings(); + final PyCodeStyleSettings pySettings = myContext.getPySettings(); if (child1 instanceof ASTBlock && child2 instanceof ASTBlock) { final ASTNode node1 = ((ASTBlock)child1).getNode(); ASTNode node2 = ((ASTBlock)child2).getNode(); @@ -726,6 +728,29 @@ public class PyBlock implements ASTBlock { final PsiElement psi1 = node1.getPsi(); PsiElement psi2 = node2.getPsi(); + + if (psi2 instanceof PyStatementList) { + // Quite odd getSpacing() doesn't get called with child1=null for the first statement + // in the statement list of a class, yet it does get called for the preceding colon and + // the statement list itself. Hence we have to handle blank lines around methods here in + // addition to SpacingBuilder. + if (myNode.getElementType() == PyElementTypes.CLASS_DECLARATION) { + final PyStatement[] statements = ((PyStatementList)psi2).getStatements(); + if (statements.length > 0 && statements[0] instanceof PyFunction) { + return getBlankLinesForOption(settings.BLANK_LINES_AROUND_METHOD); + } + } + if (childType1 == PyTokenTypes.COLON && needLineBreakInStatement()) { + return Spacing.createSpacing(0, 0, 1, true, settings.KEEP_BLANK_LINES_IN_CODE); + } + } + + // pycodestyle.py enforces at most 2 blank lines only between comments directly + // at the top-level of a file, not inside if, try/except, etc. + if (psi1 instanceof PsiComment && myNode.getPsi() instanceof PsiFile) { + return Spacing.createSpacing(0, 0, 1, true, 2); + } + // skip not inline comments to handles blank lines between various declarations if (psi2 instanceof PsiComment && hasLineBreaksBeforeInSameParent(node2, 1)) { final PsiElement nonCommentAfter = PyPsiUtils.getNextNonCommentSibling(psi2, true); @@ -737,7 +762,6 @@ public class PyBlock implements ASTBlock { final IElementType childType2 = psi2.getNode().getElementType(); //noinspection ConstantConditions child2 = getSubBlockByNode(node2); - final CommonCodeStyleSettings settings = myContext.getSettings(); if ((childType1 == PyTokenTypes.EQ || childType2 == PyTokenTypes.EQ)) { final PyNamedParameter namedParameter = as(myNode.getPsi(), PyNamedParameter.class); @@ -745,19 +769,6 @@ public class PyBlock implements ASTBlock { return Spacing.createSpacing(1, 1, 0, settings.KEEP_LINE_BREAKS, settings.KEEP_BLANK_LINES_IN_CODE); } } - - if (childType1 == PyTokenTypes.COLON && psi2 instanceof PyStatementList) { - if (needLineBreakInStatement()) { - return Spacing.createSpacing(0, 0, 1, true, settings.KEEP_BLANK_LINES_IN_CODE); - } - } - - if ((PyElementTypes.CLASS_OR_FUNCTION.contains(childType1) && STATEMENT_OR_DECLARATION.contains(childType2)) || - STATEMENT_OR_DECLARATION.contains(childType1) && PyElementTypes.CLASS_OR_FUNCTION.contains(childType2)) { - if (PyUtil.isTopLevel(psi1)) { - return getBlankLinesForOption(myContext.getPySettings().BLANK_LINES_AROUND_TOP_LEVEL_CLASSES_FUNCTIONS); - } - } if (psi1 instanceof PyImportStatementBase) { if (psi2 instanceof PyImportStatementBase) { @@ -765,7 +776,7 @@ public class PyBlock implements ASTBlock { final Boolean rightImportIsGroupStart = psi2.getCopyableUserData(IMPORT_GROUP_BEGIN); // Cleanup user data, it's no longer needed psi1.putCopyableUserData(IMPORT_GROUP_BEGIN, null); - // Don't remove IMPORT_GROUP_BEGIN from the element psi2 yet, because spacing is constructed pairwise: + // Don't remove IMPORT_GROUP_BEGIN from the element psi2 yet, because spacing is constructed pairwise: // it might be needed on the next iteration. //psi2.putCopyableUserData(IMPORT_GROUP_BEGIN, null); if (rightImportIsGroupStart != null) { @@ -780,17 +791,26 @@ public class PyBlock implements ASTBlock { } if (psi2 instanceof PyStatement && !(psi2 instanceof PyImportStatementBase)) { if (PyUtil.isTopLevel(psi1)) { + // If there is any function or class after a top-level import, it should be top-level as well + if (PyElementTypes.CLASS_OR_FUNCTION.contains(childType2)) { + return getBlankLinesForOption(Math.max(settings.BLANK_LINES_AFTER_IMPORTS, + pySettings.BLANK_LINES_AROUND_TOP_LEVEL_CLASSES_FUNCTIONS)); + } return getBlankLinesForOption(settings.BLANK_LINES_AFTER_IMPORTS); } else { - return getBlankLinesForOption(myContext.getPySettings().BLANK_LINES_AFTER_LOCAL_IMPORTS); + return getBlankLinesForOption(pySettings.BLANK_LINES_AFTER_LOCAL_IMPORTS); } } } - if (psi2 instanceof PsiComment && !hasLineBreaksBeforeInSameParent(psi2.getNode(), 1) && myContext.getPySettings().SPACE_BEFORE_NUMBER_SIGN) { - return Spacing.createSpacing(2, 0, 0, false, 0); + if ((PyElementTypes.CLASS_OR_FUNCTION.contains(childType1) && STATEMENT_OR_DECLARATION.contains(childType2)) || + STATEMENT_OR_DECLARATION.contains(childType1) && PyElementTypes.CLASS_OR_FUNCTION.contains(childType2)) { + if (PyUtil.isTopLevel(psi1)) { + return getBlankLinesForOption(pySettings.BLANK_LINES_AROUND_TOP_LEVEL_CLASSES_FUNCTIONS); + } } + } return myContext.getSpacingBuilder().getSpacing(this, child1, child2); } @@ -871,7 +891,7 @@ public class PyBlock implements ASTBlock { // delegation sometimes causes NPEs in formatter core, so we calculate the // correct indent manually. if (statementListsBelow > 0) { // was 1... strange - @SuppressWarnings("ConstantConditions") + @SuppressWarnings("ConstantConditions") final int indent = myContext.getSettings().getIndentOptions().INDENT_SIZE; return new ChildAttributes(Indent.getSpaceIndent(indent * statementListsBelow), null); } @@ -952,7 +972,7 @@ public class PyBlock implements ASTBlock { } } else if (lastChild != null && PyElementTypes.LIST_LIKE_EXPRESSIONS.contains(lastChild.getElementType())) { - // handle pressing enter at the end of a list literal when there's no closing paren or bracket + // handle pressing enter at the end of a list literal when there's no closing paren or bracket final ASTNode lastLastChild = lastChild.getLastChildNode(); if (lastLastChild != null && lastLastChild.getPsi() instanceof PsiErrorElement) { // we're at a place like this: [foo, ... bar, diff --git a/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java b/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java index 511cca5aafed..3f493e310055 100644 --- a/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java +++ b/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java @@ -162,6 +162,9 @@ public class PyLanguageCodeStyleSettingsProvider extends LanguageCodeStyleSettin indentOptions.INDENT_SIZE = 4; defaultSettings.ALIGN_MULTILINE_PARAMETERS_IN_CALLS = true; defaultSettings.KEEP_BLANK_LINES_IN_DECLARATIONS = 1; + // Don't set it to 2 -- this setting is used implicitly in a lot of methods related to spacing, + // e.g. in SpacingBuilder#blankLines(), and can lead to unexpected side-effects in formatter's + // behavior defaultSettings.KEEP_BLANK_LINES_IN_CODE = 1; return defaultSettings; } diff --git a/python/src/com/jetbrains/python/formatter/PythonFormattingModelBuilder.java b/python/src/com/jetbrains/python/formatter/PythonFormattingModelBuilder.java index 970bb3380f45..14851b412397 100644 --- a/python/src/com/jetbrains/python/formatter/PythonFormattingModelBuilder.java +++ b/python/src/com/jetbrains/python/formatter/PythonFormattingModelBuilder.java @@ -77,13 +77,12 @@ public class PythonFormattingModelBuilder implements FormattingModelBuilderEx, C final CommonCodeStyleSettings commonSettings = settings.getCommonSettings(PythonLanguage.getInstance()); return new SpacingBuilder(commonSettings) - .between(CLASS_DECLARATION, STATEMENT_OR_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_CLASS) - .between(STATEMENT_OR_DECLARATION, CLASS_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_CLASS) - .between(FUNCTION_DECLARATION, STATEMENT_OR_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_METHOD) - .between(STATEMENT_OR_DECLARATION, FUNCTION_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_METHOD) - .after(FUNCTION_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_METHOD) - .after(CLASS_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_CLASS) - // Remove excess blank lines between imports (at most one is allowed). + .before(END_OF_LINE_COMMENT).spacing(2, 0, 0, commonSettings.KEEP_LINE_BREAKS, commonSettings.KEEP_BLANK_LINES_IN_CODE) + .after(END_OF_LINE_COMMENT).spacing(0, 0, 1, commonSettings.KEEP_LINE_BREAKS, commonSettings.KEEP_BLANK_LINES_IN_CODE) + // Top-level definitions are supposed to be handled in PyBlock#getSpacing + .around(CLASS_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_CLASS) + .around(FUNCTION_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_METHOD) + // Remove excess blank lines between imports (at most one is allowed). // Note that ImportOptimizer gets rid of them anyway. // Empty lines between import groups are handles in PyBlock#getSpacing .between(IMPORT_STATEMENTS, IMPORT_STATEMENTS).spacing(0, Integer.MAX_VALUE, 1, false, 1) @@ -116,12 +115,12 @@ public class PythonFormattingModelBuilder implements FormattingModelBuilderEx, C .before(COLON).spaceIf(pySettings.SPACE_BEFORE_PY_COLON) .afterInside(LPAR, FROM_IMPORT_STATEMENT).spaces(0, pySettings.FROM_IMPORT_NEW_LINE_AFTER_LEFT_PARENTHESIS) - .betweenInside(COMMA, RPAR, FROM_IMPORT_STATEMENT).spaceIf(commonSettings.SPACE_AFTER_COMMA, + .betweenInside(COMMA, RPAR, FROM_IMPORT_STATEMENT).spaceIf(commonSettings.SPACE_AFTER_COMMA, pySettings.FROM_IMPORT_NEW_LINE_BEFORE_RIGHT_PARENTHESIS) .beforeInside(RPAR, FROM_IMPORT_STATEMENT).spaces(0, pySettings.FROM_IMPORT_NEW_LINE_BEFORE_RIGHT_PARENTHESIS) .after(COMMA).spaceIf(commonSettings.SPACE_AFTER_COMMA) .before(COMMA).spaceIf(commonSettings.SPACE_BEFORE_COMMA) - .between(FROM_KEYWORD, DOT).spaces(1) + .after(FROM_KEYWORD).spaces(1) .between(DOT, IMPORT_KEYWORD).spaces(1) .around(DOT).spaces(0) .aroundInside(AT, DECORATOR_CALL).none() @@ -157,9 +156,10 @@ public class PythonFormattingModelBuilder implements FormattingModelBuilderEx, C // should be all keywords? private static final TokenSet SINGLE_SPACE_KEYWORDS = TokenSet.create(IN_KEYWORD, AND_KEYWORD, OR_KEYWORD, IS_KEYWORD, - IF_KEYWORD, ELIF_KEYWORD, FOR_KEYWORD, RETURN_KEYWORD, RAISE_KEYWORD, + IF_KEYWORD, ELIF_KEYWORD, ELSE_KEYWORD, + FOR_KEYWORD, RETURN_KEYWORD, RAISE_KEYWORD, ASSERT_KEYWORD, CLASS_KEYWORD, DEF_KEYWORD, DEL_KEYWORD, - EXEC_KEYWORD, GLOBAL_KEYWORD, IMPORT_KEYWORD, LAMBDA_KEYWORD, + EXEC_KEYWORD, GLOBAL_KEYWORD, NONLOCAL_KEYWORD, IMPORT_KEYWORD, LAMBDA_KEYWORD, NOT_KEYWORD, WHILE_KEYWORD, YIELD_KEYWORD); private static TokenSet allButLambda() { diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyStaticCallHierarchyUtil.java b/python/src/com/jetbrains/python/hierarchy/call/PyStaticCallHierarchyUtil.java index 3e061a0e84d8..e5f3276b5ed0 100644 --- a/python/src/com/jetbrains/python/hierarchy/call/PyStaticCallHierarchyUtil.java +++ b/python/src/com/jetbrains/python/hierarchy/call/PyStaticCallHierarchyUtil.java @@ -66,9 +66,8 @@ public class PyStaticCallHierarchyUtil { public void visitPyCallExpression(PyCallExpression node) { super.visitPyCallExpression(node); - final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(element.getProject(), null); StreamEx - .of(node.multiResolveCalleeFunction(PyResolveContext.defaultContext().withTypeEvalContext(typeEvalContext))) + .of(node.multiResolveCalleeFunction(PyResolveContext.defaultContext())) .select(PyFunction.class) .forEach(callees::add); } diff --git a/python/src/com/jetbrains/python/highlighting/PyRainbowVisitor.kt b/python/src/com/jetbrains/python/highlighting/PyRainbowVisitor.kt index 3f7ab5a2f368..a65651449e27 100644 --- a/python/src/com/jetbrains/python/highlighting/PyRainbowVisitor.kt +++ b/python/src/com/jetbrains/python/highlighting/PyRainbowVisitor.kt @@ -25,7 +25,6 @@ import com.jetbrains.python.codeInsight.controlflow.ScopeOwner import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil import com.jetbrains.python.psi.* import com.jetbrains.python.psi.resolve.PyResolveContext -import com.jetbrains.python.psi.types.TypeEvalContext class PyRainbowVisitor : RainbowVisitor() { @@ -90,9 +89,7 @@ class PyRainbowVisitor : RainbowVisitor() { val parent = targetExpression.parent if (parent is PyGlobalStatement) return targetExpression.containingFile if (parent is PyNonlocalStatement) { - val typeEvalContext = TypeEvalContext.codeAnalysis(targetExpression.project, targetExpression.containingFile) - val resolveContext = PyResolveContext.defaultContext().withTypeEvalContext(typeEvalContext) - val outerResolved = targetExpression.getReference(resolveContext).resolve() + val outerResolved = targetExpression.reference.resolve() return if (outerResolved is PyTargetExpression) getTargetContext(outerResolved) else null } diff --git a/python/src/com/jetbrains/python/inspections/PyInitNewSignatureInspection.java b/python/src/com/jetbrains/python/inspections/PyInitNewSignatureInspection.java index 4a497b14893d..64f39161ef7d 100644 --- a/python/src/com/jetbrains/python/inspections/PyInitNewSignatureInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyInitNewSignatureInspection.java @@ -16,6 +16,7 @@ package com.jetbrains.python.inspections; import com.intellij.codeInspection.LocalInspectionToolSession; +import com.intellij.codeInspection.LocalQuickFix; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.openapi.extensions.Extensions; import com.intellij.psi.PsiElementVisitor; @@ -25,11 +26,15 @@ import com.jetbrains.python.PyNames; import com.jetbrains.python.inspections.quickfix.PyChangeSignatureQuickFix; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyFunction; +import com.jetbrains.python.psi.PyParameterList; import com.jetbrains.python.psi.PyUtil; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collections; +import java.util.List; + /** * Detect and report incompatibilities between __new__ and __init__ signatures. * @@ -64,34 +69,52 @@ public class PyInitNewSignatureInspection extends PyInspection { final PyClass cls = node.getContainingClass(); if (cls == null || !cls.isNewStyleClass(myTypeEvalContext)) return; - final PyFunction complementaryMethod = findComplementaryMethod(cls, node); - if (complementaryMethod == null) return; + final List complementaryMethods = findComplementaryMethods(cls, node); - if (!PyUtil.isSignatureCompatibleTo(complementaryMethod, node, myTypeEvalContext) && - !PyUtil.isSignatureCompatibleTo(node, complementaryMethod, myTypeEvalContext) && - node.getContainingFile() == cls.getContainingFile()) { - registerProblem(node.getParameterList(), PyBundle.message(PyNames.NEW.equals(node.getName()) ? "INSP.new.incompatible.to.init" - : "INSP.init.incompatible.to.new"), - PyChangeSignatureQuickFix.forMismatchingMethods(node, complementaryMethod)); + for (PyFunction complementaryMethod : complementaryMethods) { + if (PyUtil.isSignatureCompatibleTo(complementaryMethod, node, myTypeEvalContext) || + PyUtil.isSignatureCompatibleTo(node, complementaryMethod, myTypeEvalContext)) { + return; + } + } + + if (complementaryMethods.size() == 1) { + registerIncompatibilityProblem(node, PyChangeSignatureQuickFix.forMismatchingMethods(node, complementaryMethods.get(0))); + } + else if (!complementaryMethods.isEmpty()) { + registerIncompatibilityProblem(node, null); } } - @Nullable - private PyFunction findComplementaryMethod(@NotNull PyClass cls, @NotNull PyFunction original) { + @NotNull + private List findComplementaryMethods(@NotNull PyClass cls, @NotNull PyFunction original) { final String complementaryName = PyNames.NEW.equals(original.getName()) ? PyNames.INIT : PyNames.NEW; - final PyFunction complementaryMethod = cls.findMethodByName(complementaryName, true, myTypeEvalContext); + final List complementaryMethods = cls.multiFindMethodByName(complementaryName, true, myTypeEvalContext); - if (complementaryMethod == null) return null; + for (PyFunction complementaryMethod : complementaryMethods) { + final PyClass complementaryMethodClass = complementaryMethod.getContainingClass(); - final PyClass complementaryMethodClass = complementaryMethod.getContainingClass(); - if (complementaryMethodClass == null || - PyUtil.isObjectClass(complementaryMethodClass) || - ContainerUtil.exists(Extensions.getExtensions(PyInspectionExtension.EP_NAME), - extension -> extension.ignoreInitNewSignatures(original, complementaryMethod))) { - return null; + if (complementaryMethodClass == null || + PyUtil.isObjectClass(complementaryMethodClass) || + ContainerUtil.exists(Extensions.getExtensions(PyInspectionExtension.EP_NAME), + extension -> extension.ignoreInitNewSignatures(original, complementaryMethod))) { + return Collections.emptyList(); + } } - return complementaryMethod; + return complementaryMethods; + } + + private void registerIncompatibilityProblem(@NotNull PyFunction function, @Nullable LocalQuickFix quickFix) { + final PyParameterList parameterList = function.getParameterList(); + final String message = PyBundle.message(PyNames.NEW.equals(function.getName()) ? "INSP.new.incompatible.to.init" + : "INSP.init.incompatible.to.new"); + if (quickFix != null) { + registerProblem(parameterList, message, quickFix); + } + else { + registerProblem(parameterList, message); + } } } } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/inspections/PyNamedTupleInspection.kt b/python/src/com/jetbrains/python/inspections/PyNamedTupleInspection.kt index 306a77c3e2cf..8ed567ec7332 100644 --- a/python/src/com/jetbrains/python/inspections/PyNamedTupleInspection.kt +++ b/python/src/com/jetbrains/python/inspections/PyNamedTupleInspection.kt @@ -23,10 +23,11 @@ import com.intellij.psi.PsiElementVisitor import com.intellij.psi.ResolveState import com.intellij.psi.scope.BaseScopeProcessor import com.jetbrains.python.codeInsight.stdlib.PyNamedTupleType +import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider import com.jetbrains.python.psi.LanguageLevel import com.jetbrains.python.psi.PyClass import com.jetbrains.python.psi.PyTargetExpression -import com.jetbrains.python.psi.impl.PyReferenceExpressionImpl +import com.jetbrains.python.psi.types.PyClassLikeType import java.util.* import kotlin.comparisons.compareBy @@ -41,9 +42,7 @@ class PyNamedTupleInspection : PyInspection() { override fun visitPyClass(node: PyClass?) { super.visitPyClass(node) - if (node != null && - LanguageLevel.forElement(node).isAtLeast(LanguageLevel.PYTHON36) && - PyReferenceExpressionImpl.getReferenceTypeFromProviders(node, myTypeEvalContext, node) is PyNamedTupleType) { + if (node != null && LanguageLevel.forElement(node).isAtLeast(LanguageLevel.PYTHON36) && isTypingNTInheritor(node)) { val fieldsProcessor = FieldsProcessor() node.processClassLevelDeclarations(fieldsProcessor) @@ -54,6 +53,13 @@ class PyNamedTupleInspection : PyInspection() { } } + private fun isTypingNTInheritor(cls: PyClass): Boolean { + val isTypingNT: (PyClassLikeType?) -> Boolean = + { it != null && !(it is PyNamedTupleType) && PyTypingTypeProvider.NAMEDTUPLE == it.classQName } + + return cls.getSuperClassTypes(myTypeEvalContext).find(isTypingNT) != null + } + private fun registerErrorOnTargetsAboveBound(bound: PyTargetExpression?, targets: TreeSet, message: String) { diff --git a/python/src/com/jetbrains/python/inspections/PyRedeclarationInspection.java b/python/src/com/jetbrains/python/inspections/PyRedeclarationInspection.java index 7f4f1d93f58d..e5e421990a68 100644 --- a/python/src/com/jetbrains/python/inspections/PyRedeclarationInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyRedeclarationInspection.java @@ -71,7 +71,8 @@ public class PyRedeclarationInspection extends PyInspection { @Override public void visitPyFunction(final PyFunction node) { - if (!isDecorated(node)) { + if (!PyKnownDecoratorUtil.hasUnknownDecorator(node, myTypeEvalContext) && + !PyKnownDecoratorUtil.hasRedeclarationDecorator(node, myTypeEvalContext)) { processElement(node); } } diff --git a/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspectionProblemRegistrar.java b/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspectionProblemRegistrar.java index af5148424f9c..eff591d759ab 100644 --- a/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspectionProblemRegistrar.java +++ b/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspectionProblemRegistrar.java @@ -22,7 +22,6 @@ import com.intellij.psi.PsiElement; import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; import com.intellij.xml.util.XmlStringUtil; -import com.jetbrains.python.PyNames; import com.jetbrains.python.documentation.PythonDocumentationProvider; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.types.PyClassLikeType; @@ -126,10 +125,7 @@ class PyTypeCheckerInspectionProblemRegistrar { @NotNull List calleesResults, @NotNull TypeEvalContext context) { final Predicate isRightOperatorResults = - calleeResults -> { - final PyCallable callable = calleeResults.getCallable(); - return callable != null && PyNames.isRightOperatorName(callable.getName()); - }; + calleeResults -> binaryExpression.isRightOperator(calleeResults.getCallable()); final boolean allCalleesAreRightOperators = calleesResults.stream().allMatch(isRightOperatorResults); diff --git a/python/src/com/jetbrains/python/inspections/PyUnusedLocalInspectionVisitor.java b/python/src/com/jetbrains/python/inspections/PyUnusedLocalInspectionVisitor.java index 98b86605cea5..4464bbff9203 100644 --- a/python/src/com/jetbrains/python/inspections/PyUnusedLocalInspectionVisitor.java +++ b/python/src/com/jetbrains/python/inspections/PyUnusedLocalInspectionVisitor.java @@ -95,10 +95,10 @@ public class PyUnusedLocalInspectionVisitor extends PyInspectionVisitor { } private void processScope(final ScopeOwner owner) { - if (owner.getContainingFile() instanceof PyExpressionCodeFragment || callsLocals(owner)) { + if (owner.getContainingFile() instanceof PyExpressionCodeFragment) { return; } - if (!(owner instanceof PyClass)) { + if (!(owner instanceof PyClass) && !callsLocals(owner)) { collectAllWrites(owner); } collectUsedReads(owner); @@ -160,6 +160,9 @@ public class PyUnusedLocalInspectionVisitor extends PyInspectionVisitor { if (name == null || PyNames.UNDERSCORE.equals(name) || scope.isGlobal(name) || scope.isNonlocal(name)) { continue; } + if (element instanceof PyTargetExpression && ((PyTargetExpression)element).isQualified()) { + continue; + } // Ignore underscore-prefixed parameters if (name.startsWith(PyNames.UNDERSCORE) && element instanceof PyParameter) { continue; diff --git a/python/src/com/jetbrains/python/inspections/quickfix/CompatibilityPrintCallQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/CompatibilityPrintCallQuickFix.java index 41cb9786150f..df235ba5fe3e 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/CompatibilityPrintCallQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/CompatibilityPrintCallQuickFix.java @@ -21,7 +21,6 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.util.Function; import com.jetbrains.python.PyBundle; import com.jetbrains.python.psi.*; import org.jetbrains.annotations.NotNull; @@ -51,7 +50,7 @@ public class CompatibilityPrintCallQuickFix implements LocalQuickFix { stringBuilder.append(StringUtil.join(target, o -> o.getText(), ", ")); } stringBuilder.append(")"); - expression.replace(elementGenerator.createFromText(LanguageLevel.forElement(expression), PyExpression.class, + expression.replace(elementGenerator.createFromText(LanguageLevel.forElement(expression), PyElement.class, stringBuilder.toString())); final PyFromImportStatement statement = elementGenerator.createFromText(LanguageLevel.forElement(expression), PyFromImportStatement.class, diff --git a/python/src/com/jetbrains/python/inspections/quickfix/ReplaceBuiltinsQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/ReplaceBuiltinsQuickFix.java index fb2151a1d69b..df5743f1f2a6 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/ReplaceBuiltinsQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/ReplaceBuiltinsQuickFix.java @@ -44,10 +44,10 @@ public class ReplaceBuiltinsQuickFix implements LocalQuickFix { PyReferenceExpression importReference = importElement.getImportReferenceExpression(); if (importReference != null) { if ("__builtin__".equals(importReference.getName())) { - importReference.replace(elementGenerator.createFromText(LanguageLevel.getDefault(), PyReferenceExpression.class, "builtins")); + importReference.replace(elementGenerator.createExpressionFromText(LanguageLevel.getDefault(), "builtins")); } if ("builtins".equals(importReference.getName())) { - importReference.replace(elementGenerator.createFromText(LanguageLevel.getDefault(), PyReferenceExpression.class, "__builtin__")); + importReference.replace(elementGenerator.createExpressionFromText(LanguageLevel.getDefault(), "__builtin__")); } } } diff --git a/python/src/com/jetbrains/python/inspections/quickfix/StatementEffectFunctionCallQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/StatementEffectFunctionCallQuickFix.java index 4129fb30a8dc..71ec2d1cd564 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/StatementEffectFunctionCallQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/StatementEffectFunctionCallQuickFix.java @@ -62,7 +62,7 @@ public class StatementEffectFunctionCallQuickFix implements LocalQuickFix { final PsiElement next = getNextElement(expression); if (next == null) { stringBuilder.append(")"); - expression.replace(elementGenerator.createFromText(LanguageLevel.forElement(expression), PyExpression.class, + expression.replace(elementGenerator.createFromText(LanguageLevel.forElement(expression), PyElement.class, stringBuilder.toString())); return; } @@ -100,7 +100,7 @@ public class StatementEffectFunctionCallQuickFix implements LocalQuickFix { if (commentText != null) { stringBuilder.append(commentText); } - expression.replace(elementGenerator.createFromText(LanguageLevel.forElement(expression), PyExpression.class, + expression.replace(elementGenerator.createFromText(LanguageLevel.forElement(expression), PyStatement.class, stringBuilder.toString())); } @@ -140,7 +140,7 @@ public class StatementEffectFunctionCallQuickFix implements LocalQuickFix { if (commentText != null) { stringBuilder.append(commentText); } - expression.replace(elementGenerator.createFromText(LanguageLevel.forElement(expression), PyExpression.class, + expression.replace(elementGenerator.createFromText(LanguageLevel.forElement(expression), PyStatement.class, stringBuilder.toString())); } diff --git a/python/src/com/jetbrains/python/packaging/PyCondaPackageService.java b/python/src/com/jetbrains/python/packaging/PyCondaPackageService.java index 6aee3d8bdc23..709d8ff974bc 100644 --- a/python/src/com/jetbrains/python/packaging/PyCondaPackageService.java +++ b/python/src/com/jetbrains/python/packaging/PyCondaPackageService.java @@ -28,7 +28,6 @@ import com.intellij.util.containers.ContainerUtil; import com.intellij.util.text.VersionComparatorUtil; import com.intellij.util.xmlb.XmlSerializerUtil; import com.jetbrains.python.PythonHelpersLocator; -import com.jetbrains.python.packaging.ui.PyCondaManagementService; import com.jetbrains.python.sdk.PySdkUtil; import com.jetbrains.python.sdk.PythonSdkType; import com.jetbrains.python.sdk.flavors.VirtualEnvSdkFlavor; @@ -40,7 +39,7 @@ import java.util.*; @State(name = "PyCondaPackageService", storages = @Storage(value="conda_packages.xml", roamingType = RoamingType.DISABLED)) public class PyCondaPackageService implements PersistentStateComponent { - private static final Logger LOG = Logger.getInstance(PyCondaManagementService.class); + private static final Logger LOG = Logger.getInstance(PyCondaPackageService.class); public Map CONDA_PACKAGES = ContainerUtil.newConcurrentMap(); public Map> PACKAGES_TO_RELEASES = new HashMap<>(); public Set CONDA_CHANNELS = ContainerUtil.newConcurrentSet(); @@ -157,12 +156,15 @@ public class PyCondaPackageService implements PersistentStateComponent packages = PyPackageManager.getInstance(sdk).getPackages(); + return packages != null && PyPackageUtil.findPackage(packages, "setuptools") != null; + } + private static String getPackageList(DataContext dataContext) { final Module module = LangDataKeys.MODULE.getData(dataContext); if (module != null) { diff --git a/python/src/com/jetbrains/python/packaging/setupPy/SetupTaskIntrospector.java b/python/src/com/jetbrains/python/packaging/setupPy/SetupTaskIntrospector.java index cba2ead1573e..e7e4c2656b8d 100644 --- a/python/src/com/jetbrains/python/packaging/setupPy/SetupTaskIntrospector.java +++ b/python/src/com/jetbrains/python/packaging/setupPy/SetupTaskIntrospector.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -29,6 +29,7 @@ import com.jetbrains.python.packaging.PyPackageUtil; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyPsiUtils; import com.jetbrains.python.psi.resolve.PyResolveContext; +import com.jetbrains.python.psi.resolve.PyResolveImportUtil; import com.jetbrains.python.psi.stubs.PyClassNameIndex; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -78,35 +79,53 @@ public class SetupTaskIntrospector { return getTaskList(module, setupPy != null && usesSetuptools(setupPy)); } - private static List getTaskList(Module module, boolean setuptools) { - final String name = (setuptools ? "setuptools" : "distutils") + ".command.install.install"; - final Map> cache = setuptools ? ourSetuptoolsTaskCache : ourDistutilsTaskCache; - final PyClass installClass = PyClassNameIndex.findClass(name, module.getProject()); - if (installClass != null) { - final PsiDirectory distutilsCommandDir = installClass.getContainingFile().getParent(); - if (distutilsCommandDir != null) { - final String path = distutilsCommandDir.getVirtualFile().getPath(); + @NotNull + private static List getTaskList(@NotNull Module module, boolean setuptools) { + final QualifiedName name = QualifiedName.fromDottedString((setuptools ? "setuptools" : "distutils") + ".command.install.install"); + final PsiElement install = PyResolveImportUtil.resolveTopLevelMember(name, PyResolveImportUtil.fromModule(module)); + + if (install instanceof PyClass) { + final PsiDirectory commandDir = install.getContainingFile().getParent(); + if (commandDir != null) { + final Map> cache = setuptools ? ourSetuptoolsTaskCache : ourDistutilsTaskCache; + final String path = commandDir.getVirtualFile().getPath(); + List tasks = cache.get(path); if (tasks == null) { - tasks = collectTasks(distutilsCommandDir, setuptools); + tasks = collectTasks(module, commandDir, setuptools); cache.put(path, tasks); } return tasks; } } + return Collections.emptyList(); } private static final Set SKIP_NAMES = ImmutableSet.of(PyNames.INIT_DOT_PY, "alias.py", "setopt.py", "savecfg.py"); - private static List collectTasks(PsiDirectory dir, boolean setuptools) { - List result = new ArrayList<>(); - for (PsiFile commandFile : dir.getFiles()) { + @NotNull + private static List collectTasks(@NotNull Module module, @NotNull PsiDirectory commandDir, boolean setuptools) { + final List result = new ArrayList<>(); + for (PsiFile commandFile : commandDir.getFiles()) { if (commandFile instanceof PyFile && !SKIP_NAMES.contains(commandFile.getName())) { final String taskName = FileUtil.getNameWithoutExtension(commandFile.getName()); result.add(createTaskFromFile((PyFile)commandFile, taskName, setuptools)); } } + + if (setuptools) { + final QualifiedName name = QualifiedName.fromComponents("wheel", "bdist_wheel", "bdist_wheel"); + final PsiElement bdistWheel = PyResolveImportUtil.resolveTopLevelMember(name, PyResolveImportUtil.fromModule(module)); + + if (bdistWheel instanceof PyClass) { + final PsiFile file = bdistWheel.getContainingFile(); + if (file instanceof PyFile) { + result.add(createTaskFromFile((PyFile)file, "bdist_wheel", true)); + } + } + } + return result; } diff --git a/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java b/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java index 096a4c435a39..169355f939c6 100644 --- a/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java +++ b/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java @@ -234,6 +234,10 @@ public class PyKnownDecoratorUtil { return ContainerUtil.exists(getKnownDecorators(function, context), GENERATOR_BASED_COROUTINE_DECORATORS::contains); } + public static boolean hasRedeclarationDecorator(@NotNull PyFunction function, @NotNull TypeEvalContext context) { + return getKnownDecorators(function, context).contains(TYPING_OVERLOAD); + } + private static boolean allDecoratorsAreKnown(@NotNull PyDecoratable element, @NotNull List decorators) { final PyDecoratorList decoratorList = element.getDecoratorList(); return decoratorList == null ? decorators.isEmpty() : decoratorList.getDecorators().length == decorators.size(); diff --git a/python/src/com/jetbrains/python/psi/PyUtil.java b/python/src/com/jetbrains/python/psi/PyUtil.java index a78d150b1fdf..e7d5045e207b 100644 --- a/python/src/com/jetbrains/python/psi/PyUtil.java +++ b/python/src/com/jetbrains/python/psi/PyUtil.java @@ -920,22 +920,37 @@ public class PyUtil { } /** - * Retrieve the document from {@link PsiDocumentManager} using the anchor PSI element and pass it to the consumer function - * first releasing it from pending PSI modifications it with {@link PsiDocumentManager#doPostponedOperationsAndUnblockDocument(Document)} - * and then committing in try/finally block, so that subsequent operations over the PSI can be performed. + * Retrieves the document from {@link PsiDocumentManager} using the anchor PSI element and, if it's not null, + * passes it to the consumer function. + *

      + * The document is first released from pending PSI operations and then committed after the function has been applied + * in a {@code try/finally} block, so that subsequent operations on PSI could be performed. + * + * @see PsiDocumentManager#doPostponedOperationsAndUnblockDocument(Document) + * @see PsiDocumentManager#commitDocument(Document) + * @see #updateDocumentUnblockedAndCommitted(PsiElement, Function) */ public static void updateDocumentUnblockedAndCommitted(@NotNull PsiElement anchor, @NotNull Consumer consumer) { + updateDocumentUnblockedAndCommitted(anchor, document -> { + consumer.consume(document); + return null; + }); + } + + @Nullable + public static T updateDocumentUnblockedAndCommitted(@NotNull PsiElement anchor, @NotNull Function func) { final PsiDocumentManager manager = PsiDocumentManager.getInstance(anchor.getProject()); final Document document = manager.getDocument(anchor.getContainingFile()); if (document != null) { manager.doPostponedOperationsAndUnblockDocument(document); try { - consumer.consume(document); + return func.fun(document); } finally { manager.commitDocument(document); } } + return null; } @Nullable @@ -1639,17 +1654,9 @@ public class PyUtil { if (hasKeywordContainer(parameters)) { n++; } - if (callable.asMethod() != null) { + if (isFirstParameterSpecial(callable, parameters)) { n++; } - else { - if (parameters.size() > 0) { - final PyCallableParameter first = parameters.get(0); - if (PyNames.CANONICAL_SELF.equals(first.getName())) { - n++; - } - } - } return n; } @@ -1671,6 +1678,16 @@ public class PyUtil { return false; } + private static boolean isFirstParameterSpecial(@NotNull PyCallable callable, @NotNull List parameters) { + final PyFunction method = callable.asMethod(); + if (method != null) { + return PyNames.NEW.equals(method.getName()) || method.getModifier() != STATICMETHOD; + } else { + final PyCallableParameter first = ContainerUtil.getFirstItem(parameters); + return first != null && PyNames.CANONICAL_SELF.equals(first.getName()); + } + } + public static boolean isInit(@NotNull final PyFunction function) { return PyNames.INIT.equals(function.getName()); } diff --git a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java index 4703a00ec7d1..d520c8386f05 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java +++ b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java @@ -584,6 +584,10 @@ public class PyCallExpressionHelper { if (providedType instanceof PyCallableType) { return Ref.create(((PyCallableType)providedType).getCallType(context, call)); } + final Ref propertyCallType = getPropertyCallType(call, target, context); + if (propertyCallType != null) { + return propertyCallType; + } if (target instanceof PyCallable) { final PyCallable callable = (PyCallable)target; return Ref.create(callable.getCallType(context, call)); @@ -591,6 +595,26 @@ public class PyCallExpressionHelper { return null; } + @Nullable + private static Ref getPropertyCallType(@NotNull PyCallExpression call, + @NotNull PsiElement target, + @NotNull TypeEvalContext context) { + if (target instanceof PyCallable && target instanceof PyPossibleClassMember) { + final PyClass containingClass = ((PyPossibleClassMember)target).getContainingClass(); + if (containingClass != null) { + final PyCallable callable = (PyCallable)target; + final Property property = containingClass.findPropertyByCallable(callable); + if (property != null) { + final PyType propertyType = property.getType(call.getReceiver(callable), context); + if (propertyType instanceof PyCallableType) { + return Ref.create(((PyCallableType)propertyType).getCallType(context, call)); + } + } + } + } + return null; + } + @NotNull private static Maybe getSuperCallType(@NotNull PyCallExpression call, TypeEvalContext context) { final PyExpression callee = call.getCallee(); diff --git a/python/src/com/jetbrains/python/psi/impl/PyDecoratorListImpl.java b/python/src/com/jetbrains/python/psi/impl/PyDecoratorListImpl.java index 52ddd66fa67d..3c0e1e4312c2 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyDecoratorListImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyDecoratorListImpl.java @@ -16,6 +16,7 @@ package com.jetbrains.python.psi.impl; import com.intellij.lang.ASTNode; +import com.intellij.psi.PsiElement; import com.intellij.psi.util.QualifiedName; import com.jetbrains.python.PyElementTypes; import com.jetbrains.python.psi.PyDecorator; @@ -60,4 +61,14 @@ public class PyDecoratorListImpl extends PyBaseElementImpl } return null; } + + @Override + public void deleteChildInternal(@NotNull ASTNode child) { + final PsiElement childPsi = child.getPsi(); + if (childPsi instanceof PyDecorator && getDecorators().length == 1) { + delete(); + return; + } + super.deleteChildInternal(child); + } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java b/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java index 29225aba8798..e1602e665767 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java @@ -404,12 +404,12 @@ public class PyElementGeneratorImpl extends PyElementGenerator { if (ret == null) { throw new IllegalArgumentException("Can't find element matching path " + Arrays.toString(path) + " in text '" + text + "'"); } - try { + if (aClass.isInstance(ret)) { //noinspection unchecked return (T)ret; } - catch (ClassCastException e) { - throw new IllegalArgumentException("Can't create an expression of type " + aClass + " from text '" + text + "'"); + else { + throw new IllegalArgumentException("Can't create an element of type " + aClass + " from text '" + text + "', got " + ret.getClass() + " instead"); } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java index 1fbadf144c6f..976161e54c95 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java @@ -398,8 +398,7 @@ public class PyNamedParameterImpl extends PyBaseElementImpl members = new ArrayList<>(); - if (context.maySwitchToAST(this)) { + final PsiFile realFile = FileContextUtil.getContextFile(this); + if (!(getContainingFile() instanceof PyExpressionCodeFragment) || (realFile != null && context.maySwitchToAST(realFile))) { for (PsiElement target : PyUtil.multiResolveTopPriority(getReference(resolveContext))) { if (target == this || target == null) { continue; @@ -502,15 +504,23 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere final PyElement element = augAssignment != null ? augAssignment : anchor; try { final List defs = PyDefUseUtil.getLatestDefs(scopeOwner, name, element, true, false); - if (!defs.isEmpty()) { - final ReadWriteInstruction firstInstruction = PyUtil.as(defs.get(0), ReadWriteInstruction.class); - PyType type = firstInstruction != null ? firstInstruction.getType(context, anchor) : null; - for (int i = 1; i < defs.size(); i++) { - final ReadWriteInstruction instruction = PyUtil.as(defs.get(i), ReadWriteInstruction.class); - type = PyUnionType.union(type, instruction != null ? instruction.getType(context, anchor) : null); - } - return type; - } + // null means empty set of possible types, Ref(null) means Any + final @Nullable Ref combinedType = StreamEx.of(defs) + .select(ReadWriteInstruction.class) + .map(instr -> instr.getType(context, anchor)) + // don't use foldLeft(BiFunction) here, as it doesn't support null results + .foldLeft(null, (accType, defType) -> { + if (defType == null) { + return accType; + } + else if (accType == null) { + return defType; + } + else { + return Ref.create(PyUnionType.union(accType.get(), defType.get())); + } + }); + return Ref.deref(combinedType); } catch (PyDefUseUtil.InstructionNotFoundException ignored) { } diff --git a/python/src/com/jetbrains/python/psi/impl/references/PyOperatorReference.java b/python/src/com/jetbrains/python/psi/impl/references/PyOperatorReference.java index 8cc85013c903..dd38e6864541 100644 --- a/python/src/com/jetbrains/python/psi/impl/references/PyOperatorReference.java +++ b/python/src/com/jetbrains/python/psi/impl/references/PyOperatorReference.java @@ -30,6 +30,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import static com.jetbrains.python.psi.PyUtil.as; @@ -45,26 +46,25 @@ public class PyOperatorReference extends PyReferenceImpl { @NotNull @Override protected List resolveInner() { - List res = new ArrayList<>(); if (myElement instanceof PyBinaryExpression) { final PyBinaryExpression expr = (PyBinaryExpression)myElement; final String name = expr.getReferencedName(); if (PyNames.CONTAINS.equals(name)) { - res = resolveMember(expr.getRightExpression(), name); + return resolveMember(expr.getRightExpression(), name); } else { - resolveLeftAndRightOperators(res, expr, name); + return resolveLeftAndRightOperators(expr, name); } } else if (myElement instanceof PySubscriptionExpression) { final PySubscriptionExpression expr = (PySubscriptionExpression)myElement; - res = resolveMember(expr.getOperand(), expr.getReferencedName()); + return resolveMember(expr.getOperand(), expr.getReferencedName()); } else if (myElement instanceof PyPrefixExpression) { final PyPrefixExpression expr = (PyPrefixExpression)myElement; - res = resolveMember(expr.getOperand(), expr.getReferencedName()); + return resolveMember(expr.getOperand(), expr.getReferencedName()); } - return res; + return Collections.emptyList(); } @Override @@ -117,16 +117,15 @@ public class PyOperatorReference extends PyReferenceImpl { return leftOperand; } - private static String leftToRightOperatorName(String name) { - return name.replaceFirst("__([a-z]+)__", "__r$1__"); - } + @NotNull + private List resolveLeftAndRightOperators(@NotNull PyBinaryExpression expr, @Nullable String name) { + final List result = new ArrayList<>(); - private void resolveLeftAndRightOperators(List res, PyBinaryExpression expr, String name) { final TypeEvalContext typeEvalContext = myContext.getTypeEvalContext(); typeEvalContext.trace("Trying to resolve left operator"); typeEvalContext.traceIndent(); try { - res.addAll(resolveMember(getBinaryOperatorReceiver(expr), name)); + result.addAll(resolveMember(getBinaryOperatorReceiver(expr), name)); } finally { typeEvalContext.traceUnindent(); @@ -134,11 +133,13 @@ public class PyOperatorReference extends PyReferenceImpl { typeEvalContext.trace("Trying to resolve right operator"); typeEvalContext.traceIndent(); try { - res.addAll(resolveMember(expr.getRightExpression(), leftToRightOperatorName(name))); + result.addAll(resolveMember(expr.getRightExpression(), PyNames.leftToRightOperatorName(name))); } finally { typeEvalContext.traceUnindent(); } + + return result; } @NotNull diff --git a/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java b/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java index 24e60535cb81..a1310c70f74d 100644 --- a/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java +++ b/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java @@ -282,27 +282,26 @@ public class PyQualifiedReference extends PyReferenceImpl { if (qualifier instanceof PyQualifiedExpression) { final PyQualifiedExpression qualifierExpression = (PyQualifiedExpression)qualifier; final QualifiedName qualifiedName = qualifierExpression.asQualifiedName(); - if (qualifiedName == null) { - return variants.toArray(); - } - final Collection attrs = collectAssignedAttributes(qualifiedName, qualifier); - for (PyExpression ex : attrs) { - final String name = ex.getName(); - if (name != null && name.endsWith(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED)) { - continue; - } - if (ex instanceof PsiNamedElement && qualifierType instanceof PyClassType && name != null) { - variants.add(LookupElementBuilder.createWithSmartPointer(name, ex) - .withTypeText(qualifierType.getName()) - .withIcon(PlatformIcons.FIELD_ICON)); - } - if (ex instanceof PyReferenceExpression) { - PyReferenceExpression refExpr = (PyReferenceExpression)ex; - namesAlready.add(refExpr.getReferencedName()); - } - else if (ex instanceof PyTargetExpression) { - PyTargetExpression targetExpr = (PyTargetExpression)ex; - namesAlready.add(targetExpr.getName()); + if (qualifiedName != null) { + final Collection attrs = collectAssignedAttributes(qualifiedName, qualifier); + for (PyExpression ex : attrs) { + final String name = ex.getName(); + if (name != null && name.endsWith(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED)) { + continue; + } + if (ex instanceof PsiNamedElement && qualifierType instanceof PyClassType && name != null) { + variants.add(LookupElementBuilder.createWithSmartPointer(name, ex) + .withTypeText(qualifierType.getName()) + .withIcon(PlatformIcons.FIELD_ICON)); + } + if (ex instanceof PyReferenceExpression) { + PyReferenceExpression refExpr = (PyReferenceExpression)ex; + namesAlready.add(refExpr.getReferencedName()); + } + else if (ex instanceof PyTargetExpression) { + PyTargetExpression targetExpr = (PyTargetExpression)ex; + namesAlready.add(targetExpr.getName()); + } } } Collections.addAll(variants, qualifierType.getCompletionVariants(element.getName(), element, ctx)); diff --git a/python/src/com/jetbrains/python/psi/impl/stubs/PyTypingAliasStubType.java b/python/src/com/jetbrains/python/psi/impl/stubs/PyTypingAliasStubType.java index 76a97e223d78..cc707d1cd8ff 100644 --- a/python/src/com/jetbrains/python/psi/impl/stubs/PyTypingAliasStubType.java +++ b/python/src/com/jetbrains/python/psi/impl/stubs/PyTypingAliasStubType.java @@ -21,7 +21,6 @@ import com.intellij.psi.tree.TokenSet; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.io.StringRef; import com.jetbrains.python.PyElementTypes; -import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.stubs.PyTargetExpressionStub; import com.jetbrains.python.psi.stubs.PyTargetExpressionStub.InitializerType; @@ -122,7 +121,7 @@ public class PyTypingAliasStubType extends CustomTargetExpressionStubType { @Override protected boolean applyNotNull(@NotNull final PyTargetExpression input) { - final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(input.getProject(), null); - final PyResolveContext context = PyResolveContext.noImplicits().withTypeEvalContext(typeEvalContext); - return input.getReference(context).resolve() instanceof PyTargetExpression; + return input.getReference().resolve() instanceof PyTargetExpression; } } } diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/PropertiesManager.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/PropertiesManager.java index 26ac0985db43..84b0566885ec 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/PropertiesManager.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/PropertiesManager.java @@ -20,8 +20,6 @@ import com.intellij.psi.PsiReference; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.containers.MultiMap; import com.jetbrains.python.psi.*; -import com.jetbrains.python.psi.resolve.PyResolveContext; -import com.jetbrains.python.psi.types.TypeEvalContext; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -187,14 +185,7 @@ class PropertiesManager extends MembersManager { @Override public void visitPyExpression(final PyExpression node) { - final PsiReference reference; - if (node instanceof PyReferenceOwner) { - final TypeEvalContext context = TypeEvalContext.userInitiated(node.getProject(), null); - reference = ((PyReferenceOwner)node).getReference(PyResolveContext.noImplicits().withTypeEvalContext(context)); - } - else { - reference = node.getReference(); - } + final PsiReference reference = node.getReference(); if (reference == null) { return; } diff --git a/python/src/com/jetbrains/python/run/AbstractPythonRunConfiguration.java b/python/src/com/jetbrains/python/run/AbstractPythonRunConfiguration.java index 629e8928acde..ea42a0c21870 100644 --- a/python/src/com/jetbrains/python/run/AbstractPythonRunConfiguration.java +++ b/python/src/com/jetbrains/python/run/AbstractPythonRunConfiguration.java @@ -22,6 +22,7 @@ import com.intellij.execution.ExecutionException; import com.intellij.execution.configuration.AbstractRunConfiguration; import com.intellij.execution.configuration.EnvironmentVariablesComponent; import com.intellij.execution.configurations.*; +import com.intellij.execution.testframework.sm.runner.GeneralIdBasedToSMTRunnerEventsConvertor; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.module.ModuleType; @@ -87,7 +88,10 @@ public abstract class AbstractPythonRunConfiguration testRunnerRef = new Ref<>(); + ApplicationManager.getApplication().runReadAction(() -> testRunnerRef.set(detectTestRunnerFromSetupPy(module))); + + String testRunner = testRunnerRef.get(); + assert testRunner != null: "detectTestRunnerFromSetupPy can't return null"; if (!testRunner.isEmpty()) { LOG.debug("Test runner '" + testRunner + "' was discovered from setup.py in the module '" + module.getModuleFilePath() + "'"); } diff --git a/python/src/com/jetbrains/python/testing/PyTestFrameworkService.java b/python/src/com/jetbrains/python/testing/PyTestFrameworkService.java index 8923addf408f..6ad4e772ca38 100644 --- a/python/src/com/jetbrains/python/testing/PyTestFrameworkService.java +++ b/python/src/com/jetbrains/python/testing/PyTestFrameworkService.java @@ -38,8 +38,9 @@ public class PyTestFrameworkService implements PersistentStateComponent SDK_TO_PYTEST = new HashMap<>(); public Map SDK_TO_NOSETEST = new HashMap<>(); + public Map SDK_TO_TRIALTEST = new HashMap<>(); - private static final String[] FRAMEWORK_NAMES = {PyNames.PY_TEST, PyNames.NOSE_TEST}; + private static final String[] FRAMEWORK_NAMES = {PyNames.PY_TEST, PyNames.NOSE_TEST, PyNames.TRIAL_TEST}; @Override public PyTestFrameworkService getState() { @@ -62,6 +63,17 @@ public class PyTestFrameworkService implements PersistentStateComponent(newConfig: T) : JDOMExternalizable { - private val configManager: LegacyConfigurationManager<*, *> + private val configManager: LegacyConfigurationManager<*, *>? private val project = newConfig.project /** @@ -168,12 +168,17 @@ class PyTestLegacyConfigurationAdapter(newCo configManager = LegacyConfigurationManagerUnit(newConfig) } else -> { - throw IllegalAccessException("Unknown config: $newConfig") + configManager = null } } } override fun readExternal(element: Element) { + val configManager = configManager + if (configManager == null) { + containsLegacyInformation = false + return + } val legacyConfig = configManager.legacyConfig if (legacyConfig is RunConfiguration) { (legacyConfig as RunConfiguration).readExternal(element) @@ -190,7 +195,7 @@ class PyTestLegacyConfigurationAdapter(newCo override fun writeExternal(element: Element) { if (containsLegacyInformation ?: return) { - val legacyConfig = configManager.legacyConfig + val legacyConfig = configManager?.legacyConfig if (legacyConfig is RunConfiguration) { (legacyConfig as RunConfiguration).writeExternal(element) } @@ -203,7 +208,7 @@ class PyTestLegacyConfigurationAdapter(newCo fun copyFromLegacyIfNeeded() { assert(project.isInitialized, { "Initialized project required" }) if (containsLegacyInformation ?: return && !(legacyInformationCopiedToNew ?: false) && SwingUtilities.isEventDispatchThread()) { - configManager.copyFromLegacy() + configManager?.copyFromLegacy() legacyInformationCopiedToNew = true } } diff --git a/python/src/com/jetbrains/python/testing/PyTestsShared.kt b/python/src/com/jetbrains/python/testing/PyTestsShared.kt index 21bb04e5b6bb..09164b1009da 100644 --- a/python/src/com/jetbrains/python/testing/PyTestsShared.kt +++ b/python/src/com/jetbrains/python/testing/PyTestsShared.kt @@ -76,7 +76,8 @@ import com.jetbrains.reflection.getProperties val factories: Array = arrayOf( PyUnitTestFactory, PyTestFactory, - PyNoseTestFactory) + PyNoseTestFactory, + PyTrialTestFactory) internal fun getAdditionalArgumentsPropertyName() = com.jetbrains.python.testing.PyAbstractTestConfiguration::additionalArguments.name @@ -146,6 +147,14 @@ object PyTestsLocator : SMTestLocator { return listOf() } val matcher = PATH_URL.matcher(protocol) + if (! matcher.matches()) { + // special case: setup.py runner uses unittest configuration but different (old) protocol + // delegate to old protocol locator until setup.py moved to separate configuration + val oldLocation = PythonUnitTestTestIdUrlProvider.INSTANCE.getLocation(protocol, path, project, scope) + if (oldLocation.isNotEmpty()) { + return oldLocation + } + } val folder = if (matcher.matches()) { LocalFileSystem.getInstance().findFileByPath(matcher.group(1)) @@ -471,7 +480,7 @@ abstract class PyAbstractTestConfiguration(project: Project, abstract fun isFrameworkInstalled(): Boolean - override fun isTestBased() = true + override fun isIdTestBased() = true private fun getPythonTestSpecByLocation(location: Location<*>): List { diff --git a/python/src/com/jetbrains/python/testing/PyTrialTest.kt b/python/src/com/jetbrains/python/testing/PyTrialTest.kt new file mode 100644 index 000000000000..927cada336a3 --- /dev/null +++ b/python/src/com/jetbrains/python/testing/PyTrialTest.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.testing + +import com.intellij.execution.Executor +import com.intellij.execution.configurations.RunProfileState +import com.intellij.execution.runners.ExecutionEnvironment +import com.intellij.openapi.options.SettingsEditor +import com.intellij.openapi.project.Project +import com.jetbrains.python.PyNames +import com.jetbrains.python.PythonHelper + +/** + * Py.test runner + */ + +class PyTrialTestSettingsEditor(configuration: PyAbstractTestConfiguration) : + PyAbstractTestSettingsEditor(PyTestSharedForm.create(configuration)) + +class PyTrialTestExecutionEnvironment(configuration: PyTrialTestConfiguration, environment: ExecutionEnvironment) : + PyTestExecutionEnvironment(configuration, environment) { + override fun getRunner() = PythonHelper.TRIAL +} + + +class PyTrialTestConfiguration(project: Project, factory: PyTrialTestFactory) + : PyAbstractTestConfiguration(project, factory, PyTestFrameworkService.getSdkReadableNameByFramework(PyNames.TRIAL_TEST)) { + + + override fun getState(executor: Executor, environment: ExecutionEnvironment): RunProfileState? = + PyTrialTestExecutionEnvironment(this, environment) + + override fun createConfigurationEditor(): SettingsEditor = + PyTrialTestSettingsEditor(this) + + override fun shouldSeparateTargetPath() = false + + override fun isFrameworkInstalled() = VFSTestFrameworkListener.getInstance().isTestFrameworkInstalled(sdk, PyNames.TRIAL_TEST) + +} + + +object PyTrialTestFactory : PyAbstractTestFactory() { + override fun createTemplateConfiguration(project: Project) = PyTrialTestConfiguration(project, this) + + override fun getName(): String = PyTestFrameworkService.getSdkReadableNameByFramework(PyNames.TRIAL_TEST) +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/testing/PyUnitTest.kt b/python/src/com/jetbrains/python/testing/PyUnitTest.kt index 8b4245172561..ac3816f2429c 100644 --- a/python/src/com/jetbrains/python/testing/PyUnitTest.kt +++ b/python/src/com/jetbrains/python/testing/PyUnitTest.kt @@ -24,8 +24,8 @@ import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.openapi.options.SettingsEditor import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.LocalFileSystem +import com.jetbrains.python.PyNames import com.jetbrains.python.PythonHelper -import com.jetbrains.python.testing.PythonTestConfigurationsModel /** * unittest @@ -39,12 +39,22 @@ class PyUnitTestSettingsEditor(configuration: PyAbstractTestConfiguration) : class PyUnitTestExecutionEnvironment(configuration: PyUnitTestConfiguration, environment: ExecutionEnvironment) : PyTestExecutionEnvironment(configuration, environment) { - override fun getRunner() = PythonHelper.UNITTEST + + override fun getRunner() = + // different runner is used for setup.py + if (configuration.isSetupPyBased()) { + PythonHelper.SETUPPY + } + else { + PythonHelper.UNITTEST + } + } class PyUnitTestConfiguration(project: Project, factory: PyUnitTestFactory) : - PyAbstractTestConfiguration(project, factory, PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME) { // Bare functions not supported in unittest: classes only + PyAbstractTestConfiguration(project, factory, + PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME) { // Bare functions not supported in unittest: classes only @ConfigField var pattern: String? = null @@ -67,6 +77,17 @@ class PyUnitTestConfiguration(project: Project, factory: PyUnitTestFactory) : } + /** + * @return configuration should use runner for setup.py + */ + internal fun isSetupPyBased(): Boolean { + val setupPy = target.targetType == TestTargetType.PATH && target.target.endsWith(PyNames.SETUP_DOT_PY) + return setupPy + } + + // setup.py runner is not id-based + override fun isIdTestBased() = !isSetupPyBased() + override fun checkConfiguration() { super.checkConfiguration() if (target.targetType == TestTargetType.PATH && target.target.endsWith(".py") && !pattern.isNullOrEmpty()) { diff --git a/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java b/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java index 4ad20b232368..c1dbffcb8b51 100644 --- a/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java +++ b/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java @@ -86,7 +86,7 @@ public abstract class PythonTestCommandLineStateBase null") - private Boolean checkTestFrameworkInstalled(@Nullable Sdk sdk, @NotNull String testPackageName) { - return checkTestFrameworksInstalled(sdk, testPackageName).get(testPackageName); + private Boolean checkTestFrameworkInstalled(@Nullable Sdk sdk, @NotNull String testFrameworkName) { + return checkTestFrameworksInstalled(sdk, testFrameworkName).get(testFrameworkName); } @NotNull - private Map checkTestFrameworksInstalled(@Nullable Sdk sdk, @NotNull String... testPackageNames) { + private Map checkTestFrameworksInstalled(@Nullable Sdk sdk, @NotNull String... testFrameworkNames) { final Map result = new HashMap<>(); if (sdk == null || StringUtil.isEmptyOrSpaces(sdk.getHomePath())) { LOG.info("Searching test runner in empty sdk"); @@ -151,8 +151,9 @@ public class VFSTestFrameworkListener { if (refreshed) { final List packages = manager.getPackages(); if (packages != null) { - for (String name : testPackageNames) { - result.put(name, PyPackageUtil.findPackage(packages, name) != null); + for (final String frameworkName : testFrameworkNames) { + final String packageName = PyTestFrameworkService.getPackageByFramework(frameworkName); + result.put(frameworkName, PyPackageUtil.findPackage(packages, packageName) != null); } } } diff --git a/python/src/com/jetbrains/python/testing/doctest/PythonDocTestCommandLineState.java b/python/src/com/jetbrains/python/testing/doctest/PythonDocTestCommandLineState.java index fbd9a356e893..4fc336154816 100644 --- a/python/src/com/jetbrains/python/testing/doctest/PythonDocTestCommandLineState.java +++ b/python/src/com/jetbrains/python/testing/doctest/PythonDocTestCommandLineState.java @@ -52,7 +52,7 @@ public class PythonDocTestCommandLineState extends PythonTestCommandLineStateBas specs.add(myConfig.getScriptName() + "::" + myConfig.getClassName() + "::" + myConfig.getMethodName()); break; case TEST_FOLDER: - if (!myConfig.getPattern().isEmpty()) + if (myConfig.usePattern() && !myConfig.getPattern().isEmpty()) specs.add(myConfig.getFolderName() + "/" + ";" + myConfig.getPattern()); else specs.add(myConfig.getFolderName() + "/"); diff --git a/python/testData/codeInsight/controlflow/implicitnegativetypeassertionafterif.py b/python/testData/codeInsight/controlflow/implicitnegativetypeassertionafterif.py new file mode 100644 index 000000000000..560fa098578b --- /dev/null +++ b/python/testData/codeInsight/controlflow/implicitnegativetypeassertionafterif.py @@ -0,0 +1,3 @@ +if xs is None: + xs = [1, 2, 3] +print(xs) \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/implicitnegativetypeassertionafterif.txt b/python/testData/codeInsight/controlflow/implicitnegativetypeassertionafterif.txt new file mode 100644 index 000000000000..e4f1a6beb5f0 --- /dev/null +++ b/python/testData/codeInsight/controlflow/implicitnegativetypeassertionafterif.txt @@ -0,0 +1,12 @@ +0(1) element: null +1(2) element: PyIfStatement +2(3) READ ACCESS: xs +3(4,8) READ ACCESS: None +4(5) element: PyStatementList. Condition: xs is None:true +5(6) ASSERTTYPE ACCESS: xs +6(7) element: PyAssignmentStatement +7(9) WRITE ACCESS: xs +8(9) ASSERTTYPE ACCESS: xs +9(10) element: PyPrintStatement +10(11) READ ACCESS: xs +11() element: null \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/implicitnegativetypeassertionaftertwonestedif.py b/python/testData/codeInsight/controlflow/implicitnegativetypeassertionaftertwonestedif.py new file mode 100644 index 000000000000..8eeb463d6cef --- /dev/null +++ b/python/testData/codeInsight/controlflow/implicitnegativetypeassertionaftertwonestedif.py @@ -0,0 +1,5 @@ +if not isinstance(x, str): + if x is None: + print(x) + print(x) +print(x) \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/implicitnegativetypeassertionaftertwonestedif.txt b/python/testData/codeInsight/controlflow/implicitnegativetypeassertionaftertwonestedif.txt new file mode 100644 index 000000000000..2243367e6343 --- /dev/null +++ b/python/testData/codeInsight/controlflow/implicitnegativetypeassertionaftertwonestedif.txt @@ -0,0 +1,21 @@ +0(1) element: null +1(2) element: PyIfStatement +2(3) READ ACCESS: isinstance +3(4) READ ACCESS: x +4(5,17) READ ACCESS: str +5(6) element: PyStatementList. Condition: not isinstance(x, str):true +6(7) ASSERTTYPE ACCESS: x +7(8) element: PyIfStatement +8(9) READ ACCESS: x +9(10,14) READ ACCESS: None +10(11) element: PyStatementList. Condition: x is None:true +11(12) ASSERTTYPE ACCESS: x +12(13) element: PyPrintStatement +13(15) READ ACCESS: x +14(15) ASSERTTYPE ACCESS: x +15(16) element: PyPrintStatement +16(18) READ ACCESS: x +17(18) ASSERTTYPE ACCESS: x +18(19) element: PyPrintStatement +19(20) READ ACCESS: x +20() element: null \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/qualifiedselfreference.txt b/python/testData/codeInsight/controlflow/qualifiedselfreference.txt index 6d1bbf4a4fff..75774510d320 100644 --- a/python/testData/codeInsight/controlflow/qualifiedselfreference.txt +++ b/python/testData/codeInsight/controlflow/qualifiedselfreference.txt @@ -2,5 +2,6 @@ 1(2) WRITE ACCESS: self 2(3) WRITE ACCESS: args 3(4) element: PyAssignmentStatement -4(5) READ ACCESS: self -5() element: null \ No newline at end of file +4(5) WRITE ACCESS: self.args +5(6) READ ACCESS: self +6() element: null \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/self.txt b/python/testData/codeInsight/controlflow/self.txt index f3a672dba772..abf00f37997c 100644 --- a/python/testData/codeInsight/controlflow/self.txt +++ b/python/testData/codeInsight/controlflow/self.txt @@ -1,7 +1,8 @@ 0(1) element: null 1(2) WRITE ACCESS: self 2(3) element: PyAssignmentStatement -3(4) READ ACCESS: self -4(5) element: PyPrintStatement -5(6) READ ACCESS: self -6() element: null \ No newline at end of file +3(4) WRITE ACCESS: self.foo +4(5) READ ACCESS: self +5(6) element: PyPrintStatement +6(7) READ ACCESS: self +7() element: null \ No newline at end of file diff --git a/python/testData/codeInsight/controlflow/tryfinally.txt b/python/testData/codeInsight/controlflow/tryfinally.txt index 3409584f677d..3a694cd2e856 100644 --- a/python/testData/codeInsight/controlflow/tryfinally.txt +++ b/python/testData/codeInsight/controlflow/tryfinally.txt @@ -6,23 +6,25 @@ 5(6,9) element: PyTryPart 6(7,9) element: PyAssignmentStatement 7(8,9) READ ACCESS: open -8(9,17) WRITE ACCESS: status +8(9,18) WRITE ACCESS: status 9(10) element: PyFinallyPart 10(11) element: PyIfStatement 11(12) READ ACCESS: status -12(13,27) READ ACCESS: None +12(13,17) READ ACCESS: None 13(14) element: PyStatementList. Condition: status is not None:true 14(15) ASSERTTYPE ACCESS: status 15(16) element: PyPrintStatement -16(27) READ ACCESS: status -17(18) element: PyFinallyPart -18(19) element: PyIfStatement -19(20) READ ACCESS: status -20(21,25) READ ACCESS: None -21(22) element: PyStatementList. Condition: status is not None:true -22(23) ASSERTTYPE ACCESS: status -23(24) element: PyPrintStatement -24(25) READ ACCESS: status -25(26) element: PyExpressionStatement -26(27) READ ACCESS: status -27() element: null \ No newline at end of file +16(29) READ ACCESS: status +17(29) ASSERTTYPE ACCESS: status +18(19) element: PyFinallyPart +19(20) element: PyIfStatement +20(21) READ ACCESS: status +21(22,26) READ ACCESS: None +22(23) element: PyStatementList. Condition: status is not None:true +23(24) ASSERTTYPE ACCESS: status +24(25) element: PyPrintStatement +25(27) READ ACCESS: status +26(27) ASSERTTYPE ACCESS: status +27(28) element: PyExpressionStatement +28(29) READ ACCESS: status +29() element: null \ No newline at end of file diff --git a/python/testData/completion/incompleteQualifiedNameClashesWithLocalVariable.py b/python/testData/completion/incompleteQualifiedNameClashesWithLocalVariable.py new file mode 100644 index 000000000000..8eca21942d9d --- /dev/null +++ b/python/testData/completion/incompleteQualifiedNameClashesWithLocalVariable.py @@ -0,0 +1,7 @@ +class MyClass: + foo = 'spam' + + +def f(foo): + foo.illegal = 42 + MyClass().foo. diff --git a/python/testData/create_tests/create_tst.expected.py b/python/testData/create_tests/create_tst.expected.py index b09ba847eef6..172a9a3c6ffb 100644 --- a/python/testData/create_tests/create_tst.expected.py +++ b/python/testData/create_tests/create_tst.expected.py @@ -2,6 +2,7 @@ from unittest import TestCase class Spam(TestCase): + def eggs(self): self.fail() diff --git a/python/testData/formatter/alignmentOfClosingBraceInDictLiteralWhenNoHangingIndent_after.py b/python/testData/formatter/alignmentOfClosingBraceInDictLiteralWhenNoHangingIndent_after.py index 044070106f87..5c7cd915fe1d 100644 --- a/python/testData/formatter/alignmentOfClosingBraceInDictLiteralWhenNoHangingIndent_after.py +++ b/python/testData/formatter/alignmentOfClosingBraceInDictLiteralWhenNoHangingIndent_after.py @@ -1,4 +1,5 @@ class Checkpoints(webapp2.RequestHandler): + def get(self): self.response.write(json.dumps({"meta": {"code": 400, "errorType": "paramError", diff --git a/python/testData/formatter/beforeTopLevelClass_after.py b/python/testData/formatter/beforeTopLevelClass_after.py index 6b331a7cd1b5..299757e2e9ba 100644 --- a/python/testData/formatter/beforeTopLevelClass_after.py +++ b/python/testData/formatter/beforeTopLevelClass_after.py @@ -2,5 +2,6 @@ from unittest import TestCase class MyTest(TestCase): + def test_pass(self): self.assertEqual(1 + 1, 2) diff --git a/python/testData/formatter/blankLineAroundClasses_after.py b/python/testData/formatter/blankLineAroundClasses_after.py index d7f3fc0ca04a..b00290ebd5d5 100644 --- a/python/testData/formatter/blankLineAroundClasses_after.py +++ b/python/testData/formatter/blankLineAroundClasses_after.py @@ -1,4 +1,5 @@ class Adjunct: + def apply(self, right, arg): pass diff --git a/python/testData/formatter/blankLineBetweenMethods_after.py b/python/testData/formatter/blankLineBetweenMethods_after.py index 54cae234d6c3..9cfa2e27b90d 100644 --- a/python/testData/formatter/blankLineBetweenMethods_after.py +++ b/python/testData/formatter/blankLineBetweenMethods_after.py @@ -1,4 +1,5 @@ class C: + def foo(self): pass diff --git a/python/testData/formatter/blankLinesAfterTopLevelImportsBeforeClass.py b/python/testData/formatter/blankLinesAfterTopLevelImportsBeforeClass.py new file mode 100644 index 000000000000..a3e7a06a928e --- /dev/null +++ b/python/testData/formatter/blankLinesAfterTopLevelImportsBeforeClass.py @@ -0,0 +1,4 @@ +import sys + +class C: + x = sys.path \ No newline at end of file diff --git a/python/testData/formatter/blankLinesAfterTopLevelImportsBeforeClassWithPrecedingComments.py b/python/testData/formatter/blankLinesAfterTopLevelImportsBeforeClassWithPrecedingComments.py new file mode 100644 index 000000000000..364c9db92e3a --- /dev/null +++ b/python/testData/formatter/blankLinesAfterTopLevelImportsBeforeClassWithPrecedingComments.py @@ -0,0 +1,8 @@ +import sys + +# some comment + +# for +# the class +class C: + x = sys.path diff --git a/python/testData/formatter/blankLinesAfterTopLevelImportsBeforeClassWithPrecedingComments_after.py b/python/testData/formatter/blankLinesAfterTopLevelImportsBeforeClassWithPrecedingComments_after.py new file mode 100644 index 000000000000..9d1ace6cc74e --- /dev/null +++ b/python/testData/formatter/blankLinesAfterTopLevelImportsBeforeClassWithPrecedingComments_after.py @@ -0,0 +1,12 @@ +import sys + + + + + +# some comment + +# for +# the class +class C: + x = sys.path diff --git a/python/testData/formatter/blankLinesAfterTopLevelImportsBeforeClass_after.py b/python/testData/formatter/blankLinesAfterTopLevelImportsBeforeClass_after.py new file mode 100644 index 000000000000..d520b506fffe --- /dev/null +++ b/python/testData/formatter/blankLinesAfterTopLevelImportsBeforeClass_after.py @@ -0,0 +1,8 @@ +import sys + + + + + +class C: + x = sys.path diff --git a/python/testData/formatter/blankLinesAroundFirstMethod.py b/python/testData/formatter/blankLinesAroundFirstMethod.py new file mode 100644 index 000000000000..c3e73409aeb9 --- /dev/null +++ b/python/testData/formatter/blankLinesAroundFirstMethod.py @@ -0,0 +1,65 @@ +class C1: + # comment 1 + + # comment 2 + def __init__(self): + pass + + +class C2: + # comment 2 + def __init__(self): + pass + + +class C3: + def __init__(self): + pass + + +class C4: + """Docstring.""" + # comment 1 + + # comment 2 + def __init__(self): + pass + + +class C5: + """Docstring.""" + # comment 2 + def __init__(self): + pass + + +class C6: + """Docstring.""" + def __init__(self): + pass + + +class C7: + attr = 42 + # comment 1 + + # comment 2 + def __init__(self): + pass + + +class C8: + attr = 42 + # comment 2 + def __init__(self): + pass + + +class C9: + attr = 42 + def __init__(self): + pass + +class C10: # comment before statement list + def __init__(self): + pass diff --git a/python/testData/formatter/blankLinesAroundFirstMethod_after.py b/python/testData/formatter/blankLinesAroundFirstMethod_after.py new file mode 100644 index 000000000000..a905f49887e4 --- /dev/null +++ b/python/testData/formatter/blankLinesAroundFirstMethod_after.py @@ -0,0 +1,76 @@ +class C1: + + # comment 1 + + # comment 2 + def __init__(self): + pass + + +class C2: + + # comment 2 + def __init__(self): + pass + + +class C3: + + def __init__(self): + pass + + +class C4: + """Docstring.""" + + # comment 1 + + # comment 2 + def __init__(self): + pass + + +class C5: + """Docstring.""" + + # comment 2 + def __init__(self): + pass + + +class C6: + """Docstring.""" + + def __init__(self): + pass + + +class C7: + attr = 42 + + # comment 1 + + # comment 2 + def __init__(self): + pass + + +class C8: + attr = 42 + + # comment 2 + def __init__(self): + pass + + +class C9: + attr = 42 + + def __init__(self): + pass + + +class C10: # comment before statement list + + def __init__(self): + pass diff --git a/python/testData/formatter/commentBetweenClasses_after.py b/python/testData/formatter/commentBetweenClasses_after.py index e97c76b04590..9a82642ffadc 100644 --- a/python/testData/formatter/commentBetweenClasses_after.py +++ b/python/testData/formatter/commentBetweenClasses_after.py @@ -1,4 +1,5 @@ class T1(object): + def m1(self): pass @@ -6,5 +7,6 @@ class T1(object): # comment about T2 class T2(object): + def m2(self): pass diff --git a/python/testData/formatter/commentsSpacing.py b/python/testData/formatter/commentsSpacing.py new file mode 100644 index 000000000000..781e7e4aecbc --- /dev/null +++ b/python/testData/formatter/commentsSpacing.py @@ -0,0 +1,35 @@ +# line comment 1 +# line comment 2 + + +# line comment 3 + +def f(): + # inner line comment 1 + # inner line comment 2 + + + # inner line comment 3 + + + x = 42 # trailing comment 1 + +# line comment 4 + + +def g(): + pass + +if True: + # semi top-level comment 1 + + + + # semi top-level comment 2 + + + # semi top-level comment 3 + + + class C: + pass \ No newline at end of file diff --git a/python/testData/formatter/commentsSpacing_after.py b/python/testData/formatter/commentsSpacing_after.py new file mode 100644 index 000000000000..97bc420ec9a2 --- /dev/null +++ b/python/testData/formatter/commentsSpacing_after.py @@ -0,0 +1,31 @@ +# line comment 1 +# line comment 2 + + +# line comment 3 + +def f(): + # inner line comment 1 + # inner line comment 2 + + # inner line comment 3 + + x = 42 # trailing comment 1 + + +# line comment 4 + + +def g(): + pass + + +if True: + # semi top-level comment 1 + + # semi top-level comment 2 + + # semi top-level comment 3 + + class C: + pass diff --git a/python/testData/formatter/extraBlankLinesBetweenMethodsAndAtTheEnd_after.py b/python/testData/formatter/extraBlankLinesBetweenMethodsAndAtTheEnd_after.py index 959949462bea..f45624cc4a67 100644 --- a/python/testData/formatter/extraBlankLinesBetweenMethodsAndAtTheEnd_after.py +++ b/python/testData/formatter/extraBlankLinesBetweenMethodsAndAtTheEnd_after.py @@ -1,4 +1,5 @@ class Foo(object): + def wrong_blank_lines(self): pass diff --git a/python/testData/formatter/spacesAfterFromInYieldFrom.py b/python/testData/formatter/spacesAfterFromInYieldFrom.py new file mode 100644 index 000000000000..dab0f7a4224e --- /dev/null +++ b/python/testData/formatter/spacesAfterFromInYieldFrom.py @@ -0,0 +1,6 @@ +def g(): + yield 42 + + +def f(): + yield from g() \ No newline at end of file diff --git a/python/testData/formatter/spacesAfterFromInYieldFrom_after.py b/python/testData/formatter/spacesAfterFromInYieldFrom_after.py new file mode 100644 index 000000000000..a5287e7dc00a --- /dev/null +++ b/python/testData/formatter/spacesAfterFromInYieldFrom_after.py @@ -0,0 +1,6 @@ +def g(): + yield 42 + + +def f(): + yield from g() diff --git a/python/testData/formatter/spacesAfterNonlocal.py b/python/testData/formatter/spacesAfterNonlocal.py new file mode 100644 index 000000000000..d5dda4fd2ec8 --- /dev/null +++ b/python/testData/formatter/spacesAfterNonlocal.py @@ -0,0 +1,7 @@ +def f(y): + x = 42 + + def g(): + nonlocal x, y + x = 'spam' + y += 1 \ No newline at end of file diff --git a/python/testData/formatter/spacesAfterNonlocal_after.py b/python/testData/formatter/spacesAfterNonlocal_after.py new file mode 100644 index 000000000000..d0ef9e968170 --- /dev/null +++ b/python/testData/formatter/spacesAfterNonlocal_after.py @@ -0,0 +1,7 @@ +def f(y): + x = 42 + + def g(): + nonlocal x, y + x = 'spam' + y += 1 diff --git a/python/testData/formatter/spacesAroundElseInConditionalExpression.py b/python/testData/formatter/spacesAroundElseInConditionalExpression.py new file mode 100644 index 000000000000..1bc9aeb5aecd --- /dev/null +++ b/python/testData/formatter/spacesAroundElseInConditionalExpression.py @@ -0,0 +1 @@ +x = 'spam' if True else 42 \ No newline at end of file diff --git a/python/testData/formatter/spacesAroundElseInConditionalExpression_after.py b/python/testData/formatter/spacesAroundElseInConditionalExpression_after.py new file mode 100644 index 000000000000..9697882a8529 --- /dev/null +++ b/python/testData/formatter/spacesAroundElseInConditionalExpression_after.py @@ -0,0 +1 @@ +x = 'spam' if True else 42 diff --git a/python/testData/formatter/spacesBeforeFromImportSource.py b/python/testData/formatter/spacesBeforeFromImportSource.py new file mode 100644 index 000000000000..2ccdcff66014 --- /dev/null +++ b/python/testData/formatter/spacesBeforeFromImportSource.py @@ -0,0 +1,3 @@ +from sys import path + +print(sys.path) \ No newline at end of file diff --git a/python/testData/formatter/spacesBeforeFromImportSource_after.py b/python/testData/formatter/spacesBeforeFromImportSource_after.py new file mode 100644 index 000000000000..40d3f4875fbb --- /dev/null +++ b/python/testData/formatter/spacesBeforeFromImportSource_after.py @@ -0,0 +1,3 @@ +from sys import path + +print(sys.path) diff --git a/python/testData/inspections/PyInitNewSignatureInspection/test.py b/python/testData/inspections/PyInitNewSignatureInspection/test.py index aef17b94e2da..49d725d98c87 100644 --- a/python/testData/inspections/PyInitNewSignatureInspection/test.py +++ b/python/testData/inspections/PyInitNewSignatureInspection/test.py @@ -40,4 +40,10 @@ class Planet(enum.Enum): EARTH = (5.976e+24, 6.37814e6) def __init__(self, mass, radius): # OK + pass + + +# PY-25171 +class Meta(type): + def __new__(mcls, name, bases, namespace): pass \ No newline at end of file diff --git a/python/testData/inspections/PyMethodOverridingInspection/OverriddingAbstractStaticMethodWithExpandedArguments.py b/python/testData/inspections/PyMethodOverridingInspection/OverriddingAbstractStaticMethodWithExpandedArguments.py new file mode 100644 index 000000000000..81dce1c0ae7e --- /dev/null +++ b/python/testData/inspections/PyMethodOverridingInspection/OverriddingAbstractStaticMethodWithExpandedArguments.py @@ -0,0 +1,14 @@ +from abc import ABC, abstractmethod + + +class Abstract(ABC): + @staticmethod + @abstractmethod + def bar(*args, **kwargs): + pass + + +class Child(Abstract): + @staticmethod + def bar(arg_one, arg_two, arg_three): + pass \ No newline at end of file diff --git a/python/testData/inspections/PyRedeclarationInspection/classMethodRedeclaresAnotherClassMethod.py b/python/testData/inspections/PyRedeclarationInspection/classMethodRedeclaresAnotherClassMethod.py new file mode 100644 index 000000000000..0167125e97b6 --- /dev/null +++ b/python/testData/inspections/PyRedeclarationInspection/classMethodRedeclaresAnotherClassMethod.py @@ -0,0 +1,8 @@ +class TestClass: + @classmethod + def foo(cls): + print(0) + + @classmethod + def foo(cls): + print(1) \ No newline at end of file diff --git a/python/testData/inspections/PyRedeclarationInspection/classMethodRedeclaresInstanceMethod.py b/python/testData/inspections/PyRedeclarationInspection/classMethodRedeclaresInstanceMethod.py new file mode 100644 index 000000000000..62efb08d73e0 --- /dev/null +++ b/python/testData/inspections/PyRedeclarationInspection/classMethodRedeclaresInstanceMethod.py @@ -0,0 +1,7 @@ +class TestClass: + def foo(self): + print(0) + + @classmethod + def foo(cls): + print(1) \ No newline at end of file diff --git a/python/testData/inspections/PyRedeclarationInspection/staticMethodRedeclaresAnotherStaticMethod.py b/python/testData/inspections/PyRedeclarationInspection/staticMethodRedeclaresAnotherStaticMethod.py new file mode 100644 index 000000000000..6855cd08b3b0 --- /dev/null +++ b/python/testData/inspections/PyRedeclarationInspection/staticMethodRedeclaresAnotherStaticMethod.py @@ -0,0 +1,8 @@ +class TestClass: + @staticmethod + def foo(): + print(0) + + @staticmethod + def foo(): + print(1) \ No newline at end of file diff --git a/python/testData/inspections/PyRedeclarationInspection/staticMethodRedeclaresInstanceMethod.py b/python/testData/inspections/PyRedeclarationInspection/staticMethodRedeclaresInstanceMethod.py new file mode 100644 index 000000000000..a23709494411 --- /dev/null +++ b/python/testData/inspections/PyRedeclarationInspection/staticMethodRedeclaresInstanceMethod.py @@ -0,0 +1,7 @@ +class TestClass: + def foo(self): + print(0) + + @staticmethod + def foo(): + print(1) \ No newline at end of file diff --git a/python/testData/inspections/PyTypeCheckerInspection/ChainedComparisons.py b/python/testData/inspections/PyTypeCheckerInspection/ChainedComparisons.py index 85210e86c5cd..45422150a6a6 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/ChainedComparisons.py +++ b/python/testData/inspections/PyTypeCheckerInspection/ChainedComparisons.py @@ -3,7 +3,7 @@ print(('a' < 'b') < 'c'< print((1, 1) < (1, 2) < (1, 3) < (1, 4)) print(((1, 1) < (1, 2)) < (1, 3)) print(1.0 < 4.5 < 9.3 < 10.0) -print((1.0 < 4.5) < 9.3) +print((1.0 < 4.5) < 9.3) from datetime import datetime d1 = datetime.now() diff --git a/python/testData/inspections/PyTypeCheckerInspection/ComparingFloatAndInt.py b/python/testData/inspections/PyTypeCheckerInspection/ComparingFloatAndInt.py new file mode 100644 index 000000000000..6ce69ca83028 --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/ComparingFloatAndInt.py @@ -0,0 +1,5 @@ +result = 0 > 0.0 +result2 = 0 < 0.0 + +result3 = 0.0 > 0 +result4 = 0.0 < 0 \ No newline at end of file diff --git a/python/testData/inspections/PyTypeCheckerInspection/ComparisonOperators.py b/python/testData/inspections/PyTypeCheckerInspection/ComparisonOperators.py index 29f333cb2a72..2d64733b86cb 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/ComparisonOperators.py +++ b/python/testData/inspections/PyTypeCheckerInspection/ComparisonOperators.py @@ -13,7 +13,7 @@ def test(): f(o == o) f(o >= o) f('foo' > 'bar') - f(c < 1) + f(c < 1) f(c > 1) f(c == 1) f(c in [1, 2, 3]) diff --git a/python/testData/inspections/PyTypeCheckerInspection/IterateOverDictValueWhenItsTypeIsUnion.py b/python/testData/inspections/PyTypeCheckerInspection/IterateOverDictValueWhenItsTypeIsUnion.py new file mode 100644 index 000000000000..7f786045c9d1 --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/IterateOverDictValueWhenItsTypeIsUnion.py @@ -0,0 +1,7 @@ +KWARGS = { + "do_stuff": True, + "little_list": ['WORLD_RET_BP_IMPALA_AB.Control', 'WORLD_RET_BP_IMPALA_AB.Impala_WS'], +} + +for element in KWARGS["little_list"]: + print(element) \ No newline at end of file diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/innerClassAsNamedTupleDefinitionMember.py b/python/testData/inspections/PyUnresolvedReferencesInspection/innerClassAsNamedTupleDefinitionMember.py new file mode 100644 index 000000000000..290c27bb1288 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/innerClassAsNamedTupleDefinitionMember.py @@ -0,0 +1,15 @@ +from typing import NamedTuple + + +class User(NamedTuple): + class PrivLvl: + ADMIN = 1 + OPERATOR = 2 + + name: str + level: PrivLvl = PrivLvl.ADMIN + +User.PrivLvl + +admin = User('MrRobot') +admin.PrivLvl \ No newline at end of file diff --git a/python/testData/inspections/PyUnusedLocalInspection/callingLocalsLeadsToUnusedParameter.py b/python/testData/inspections/PyUnusedLocalInspection/callingLocalsLeadsToUnusedParameter.py new file mode 100644 index 000000000000..a29bc66fe4bf --- /dev/null +++ b/python/testData/inspections/PyUnusedLocalInspection/callingLocalsLeadsToUnusedParameter.py @@ -0,0 +1,5 @@ +def outer(arg_one): + def inner(): + print(locals()) + print(arg_one) + return inner \ No newline at end of file diff --git a/python/testData/inspections/TransformClassicClass_after.py b/python/testData/inspections/TransformClassicClass_after.py index 70402ca7f12c..19a4bb306c8b 100644 --- a/python/testData/inspections/TransformClassicClass_after.py +++ b/python/testData/inspections/TransformClassicClass_after.py @@ -1,4 +1,5 @@ class A(object): + def foo(self): pass diff --git a/python/testData/intentions/googleDocStubCustomIndent.py b/python/testData/intentions/googleDocStubCustomIndent.py index c57c31a3f729..05e4a892f70c 100644 --- a/python/testData/intentions/googleDocStubCustomIndent.py +++ b/python/testData/intentions/googleDocStubCustomIndent.py @@ -1,2 +1,3 @@ def f(x, y): - return 42 \ No newline at end of file + z = x ** y + return z \ No newline at end of file diff --git a/python/testData/intentions/googleDocStubCustomIndent_after.py b/python/testData/intentions/googleDocStubCustomIndent_after.py index 1321e8b867df..7d89b9a2db5e 100644 --- a/python/testData/intentions/googleDocStubCustomIndent_after.py +++ b/python/testData/intentions/googleDocStubCustomIndent_after.py @@ -1,11 +1,12 @@ def f(x, y): - """ + """ - Args: - x: - y: + Args: + x: + y: - Returns: + Returns: - """ - return 42 \ No newline at end of file + """ + z = x ** y + return z \ No newline at end of file diff --git a/python/testData/intentions/returnTypeInDocStringDifferentIndentationSize.py b/python/testData/intentions/returnTypeInDocStringDifferentIndentationSize.py index d89af49bb95f..617037563d7b 100644 --- a/python/testData/intentions/returnTypeInDocStringDifferentIndentationSize.py +++ b/python/testData/intentions/returnTypeInDocStringDifferentIndentationSize.py @@ -1,2 +1,3 @@ def func(x): + print(x) return 1 \ No newline at end of file diff --git a/python/testData/intentions/returnTypeInDocStringDifferentIndentationSize_after.py b/python/testData/intentions/returnTypeInDocStringDifferentIndentationSize_after.py index c1a700fa3dac..ff44cdfc808d 100644 --- a/python/testData/intentions/returnTypeInDocStringDifferentIndentationSize_after.py +++ b/python/testData/intentions/returnTypeInDocStringDifferentIndentationSize_after.py @@ -1,6 +1,7 @@ def func(x): - """ - - :rtype: object - """ - return 1 \ No newline at end of file + """ + + :rtype: object + """ + print(x) + return 1 \ No newline at end of file diff --git a/python/testData/intentions/typeInDocStringDifferentIndentationSize.py b/python/testData/intentions/typeInDocStringDifferentIndentationSize.py index eb6ed54d624c..69c05f3df918 100644 --- a/python/testData/intentions/typeInDocStringDifferentIndentationSize.py +++ b/python/testData/intentions/typeInDocStringDifferentIndentationSize.py @@ -1,2 +1,3 @@ def func(x): + print(x) return 1 \ No newline at end of file diff --git a/python/testData/intentions/typeInDocStringDifferentIndentationSize_after.py b/python/testData/intentions/typeInDocStringDifferentIndentationSize_after.py index de5fc24db1ae..528b9112af52 100644 --- a/python/testData/intentions/typeInDocStringDifferentIndentationSize_after.py +++ b/python/testData/intentions/typeInDocStringDifferentIndentationSize_after.py @@ -1,6 +1,7 @@ def func(x): - """ - - :type x: object - """ - return 1 \ No newline at end of file + """ + + :type x: object + """ + print(x) + return 1 \ No newline at end of file diff --git a/python/testData/override/indent_after.py b/python/testData/override/indent_after.py index 807c1d5498f1..f51ffeff5a0a 100644 --- a/python/testData/override/indent_after.py +++ b/python/testData/override/indent_after.py @@ -2,5 +2,6 @@ class Dialog: def validate(self): pass class B(Dialog): + def validate(self): Dialog.validate(self) \ No newline at end of file diff --git a/python/testData/quickFixes/PyAddMethodQuickFixTest/addMethodReplacePass_after.py b/python/testData/quickFixes/PyAddMethodQuickFixTest/addMethodReplacePass_after.py index 3082267d4d02..595eb86ded71 100644 --- a/python/testData/quickFixes/PyAddMethodQuickFixTest/addMethodReplacePass_after.py +++ b/python/testData/quickFixes/PyAddMethodQuickFixTest/addMethodReplacePass_after.py @@ -1,4 +1,5 @@ class A: + def y(self): pass diff --git a/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/firstMethod_after.py b/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/firstMethod_after.py index 01298185c05c..fcd2fa9f867e 100644 --- a/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/firstMethod_after.py +++ b/python/testData/quickFixes/PyMakeFunctionFromMethodQuickFixTest/firstMethod_after.py @@ -6,6 +6,7 @@ def f(x): class Child(Base): + def __init__(self): super(Child, self).__init__() diff --git a/python/testData/refactoring/extractsuperclass/fieldsNpe.after.py b/python/testData/refactoring/extractsuperclass/fieldsNpe.after.py index 94167c7fa6bb..9a08604d360b 100644 --- a/python/testData/refactoring/extractsuperclass/fieldsNpe.after.py +++ b/python/testData/refactoring/extractsuperclass/fieldsNpe.after.py @@ -1,4 +1,5 @@ class Ancestor(object): + def __init__(self, a, b): self.a = a self.b = b @@ -8,5 +9,6 @@ class Ancestor(object): class Basic(Ancestor): + def func2(self): return self.func1() \ No newline at end of file diff --git a/python/testData/refactoring/extractsuperclass/importMultiFile/dest_module.after.py b/python/testData/refactoring/extractsuperclass/importMultiFile/dest_module.after.py index eba5f32d8456..b717c28ecdcf 100644 --- a/python/testData/refactoring/extractsuperclass/importMultiFile/dest_module.after.py +++ b/python/testData/refactoring/extractsuperclass/importMultiFile/dest_module.after.py @@ -3,6 +3,7 @@ from shared_module import module_function as my_function, ModuleClass class NewParent(object): + def do_useful_stuff(self): i = shared_module.MODULE_CONTANT my_function() diff --git a/python/testData/refactoring/extractsuperclass/instanceNotDeclaredInInit.after.py b/python/testData/refactoring/extractsuperclass/instanceNotDeclaredInInit.after.py index ef3511dc8c66..25fd7a3d8c68 100644 --- a/python/testData/refactoring/extractsuperclass/instanceNotDeclaredInInit.after.py +++ b/python/testData/refactoring/extractsuperclass/instanceNotDeclaredInInit.after.py @@ -1,4 +1,5 @@ class Parent(object): + def __init__(self): self.eggs = 12 diff --git a/python/testData/refactoring/extractsuperclass/moveAndMakeAbstractImportExistsPy3/dest_module.after.py b/python/testData/refactoring/extractsuperclass/moveAndMakeAbstractImportExistsPy3/dest_module.after.py index ddce4a86100f..946e299b39e6 100644 --- a/python/testData/refactoring/extractsuperclass/moveAndMakeAbstractImportExistsPy3/dest_module.after.py +++ b/python/testData/refactoring/extractsuperclass/moveAndMakeAbstractImportExistsPy3/dest_module.after.py @@ -7,6 +7,7 @@ abstractmethod() class NewParent(metaclass=ABCMeta): + @classmethod @abstractmethod def foo_method(cls): diff --git a/python/testData/refactoring/extractsuperclass/multifile/target.append.py b/python/testData/refactoring/extractsuperclass/multifile/target.append.py index 81722c98acac..681607f25d50 100644 --- a/python/testData/refactoring/extractsuperclass/multifile/target.append.py +++ b/python/testData/refactoring/extractsuperclass/multifile/target.append.py @@ -3,5 +3,6 @@ A = 1 class Suppa: + def foo(self): print "bar" diff --git a/python/testData/refactoring/extractsuperclass/multifile/target.new.py b/python/testData/refactoring/extractsuperclass/multifile/target.new.py index db6d3b7efa91..20c10a93ab7b 100644 --- a/python/testData/refactoring/extractsuperclass/multifile/target.new.py +++ b/python/testData/refactoring/extractsuperclass/multifile/target.new.py @@ -1,3 +1,4 @@ class Suppa: + def foo(self): print "bar" diff --git a/python/testData/refactoring/extractsuperclass/properties.after.py b/python/testData/refactoring/extractsuperclass/properties.after.py index c414f00ca149..14a336c41f66 100644 --- a/python/testData/refactoring/extractsuperclass/properties.after.py +++ b/python/testData/refactoring/extractsuperclass/properties.after.py @@ -20,6 +20,7 @@ class ToClass(object): class FromClass(ToClass): + def __init__(self): pass diff --git a/python/testData/refactoring/extractsuperclass/py3ParentHasObject.after.py b/python/testData/refactoring/extractsuperclass/py3ParentHasObject.after.py index dd03d9a179c9..23b744155397 100644 --- a/python/testData/refactoring/extractsuperclass/py3ParentHasObject.after.py +++ b/python/testData/refactoring/extractsuperclass/py3ParentHasObject.after.py @@ -1,4 +1,5 @@ class Parent(object): + def spam(self): pass diff --git a/python/testData/refactoring/extractsuperclass/simple.after.py b/python/testData/refactoring/extractsuperclass/simple.after.py index 38248501dc4a..86b15163bcae 100644 --- a/python/testData/refactoring/extractsuperclass/simple.after.py +++ b/python/testData/refactoring/extractsuperclass/simple.after.py @@ -1,4 +1,5 @@ class Suppa: + def foo(self): print "bar" diff --git a/python/testData/refactoring/extractsuperclass/withSuper.after.py b/python/testData/refactoring/extractsuperclass/withSuper.after.py index 5f346b590cad..5831a7ab776a 100644 --- a/python/testData/refactoring/extractsuperclass/withSuper.after.py +++ b/python/testData/refactoring/extractsuperclass/withSuper.after.py @@ -1,4 +1,5 @@ class Suppa(object): + def foo(self): print "bar" diff --git a/python/testData/refactoring/pullup/fieldMove/Class.after.py b/python/testData/refactoring/pullup/fieldMove/Class.after.py index da34ab3a2897..e717954de1e0 100644 --- a/python/testData/refactoring/pullup/fieldMove/Class.after.py +++ b/python/testData/refactoring/pullup/fieldMove/Class.after.py @@ -2,5 +2,6 @@ from SuperClass import SuperClass class AnyClass(SuperClass): + def __init__(self): super().__init__() diff --git a/python/testData/testRunner/env/doc/subfolder/__init__.py b/python/testData/testRunner/env/doc/subfolder/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/testRunner/env/unit/__init__.py b/python/testData/testRunner/env/unit/__init__.py deleted file mode 100644 index 7c7597a4f4fb..000000000000 --- a/python/testData/testRunner/env/unit/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'ktisha' diff --git a/python/testData/typeshed/stdlib/2/types_test.py b/python/testData/typeshed/stdlib/2/types_test.py new file mode 100644 index 000000000000..b751c1294cda --- /dev/null +++ b/python/testData/typeshed/stdlib/2/types_test.py @@ -0,0 +1,11 @@ +def test_methodtype(): + import types + + + class A: + def foo(self): + return "ok" + + + assert types.UnboundMethodType(A.foo, A())() == "ok" + assert types.MethodType(A.foo, A())() == "ok" \ No newline at end of file diff --git a/python/testData/typeshed/stdlib/3/types_test.py b/python/testData/typeshed/stdlib/3/types_test.py new file mode 100644 index 000000000000..f86ce7a10d07 --- /dev/null +++ b/python/testData/typeshed/stdlib/3/types_test.py @@ -0,0 +1,10 @@ +def test_methodtype(): + import types + + + class A: + def foo(self): + return "ok" + + + assert types.MethodType(A.foo, A())() == "ok" \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/env/PyAbstractTestProcessRunner.java b/python/testSrc/com/jetbrains/env/PyAbstractTestProcessRunner.java index 1e1d855ad2c6..7a2a6a8785f4 100644 --- a/python/testSrc/com/jetbrains/env/PyAbstractTestProcessRunner.java +++ b/python/testSrc/com/jetbrains/env/PyAbstractTestProcessRunner.java @@ -104,6 +104,16 @@ public class PyAbstractTestProcessRunner tags) { + unittests(script, tags, false); + } + + private void unittests(final String script, @Nullable Set tags, boolean isSkipAllowed) { runPythonTest(new PyProcessWithConsoleTestTask("/helpers/pydev", SdkCreationType.SDK_PACKAGES_ONLY) { @NotNull @@ -114,7 +124,12 @@ public class PythonDebuggerTest extends PyEnvTestCase { @NotNull final String stdout, @NotNull final String stderr, @NotNull final String all) { - runner.assertAllTestsPassed(); + if (isSkipAllowed) { + runner.assertNoFailures(); + } + else { + runner.assertAllTestsPassed(); + } } @NotNull diff --git a/python/testSrc/com/jetbrains/env/python/testing/PyTestsFunctionBasedRunner.java b/python/testSrc/com/jetbrains/env/python/testing/PyTestsFunctionBasedRunner.java new file mode 100644 index 000000000000..a0f8d02c3b6d --- /dev/null +++ b/python/testSrc/com/jetbrains/env/python/testing/PyTestsFunctionBasedRunner.java @@ -0,0 +1,56 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.env.python.testing; + +import com.intellij.execution.testframework.AbstractTestProxy; +import com.intellij.openapi.application.ReadAction; +import com.intellij.util.ThrowableRunnable; +import com.jetbrains.env.PyProcessWithConsoleTestTask; +import com.jetbrains.env.ut.PyScriptTestProcessRunner; +import com.jetbrains.python.sdkTools.SdkCreationType; +import org.jetbrains.annotations.NotNull; + +/** + * Checks each method by name + */ +abstract class PyTestsFunctionBasedRunner> extends PyProcessWithConsoleTestTask { + @NotNull + protected final String[] myFunctionsToCheck; + + protected PyTestsFunctionBasedRunner(@NotNull final String... functionsToCheck) { + super("/testRunner/env/testsInFolder", SdkCreationType.EMPTY_SDK); + assert functionsToCheck.length > 0 : "Provide functions"; + myFunctionsToCheck = functionsToCheck.clone(); + } + + @Override + protected final void checkTestResults(@NotNull final T runner, + @NotNull final String stdout, + @NotNull final String stderr, + @NotNull final String all) { + for (final String functionName : myFunctionsToCheck) { + ReadAction.run((ThrowableRunnable)() -> { + final AbstractTestProxy method = runner.findTestByName(functionName); + checkMethod(method, functionName); + }); + } + } + + /** + * Called for each method + */ + protected abstract void checkMethod(@NotNull final AbstractTestProxy method, @NotNull final String functionName); +} diff --git a/python/testSrc/com/jetbrains/env/python/testing/PyTestsInSubFolderRunner.java b/python/testSrc/com/jetbrains/env/python/testing/PyTestsInSubFolderRunner.java new file mode 100644 index 000000000000..0f521c2b5e43 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/python/testing/PyTestsInSubFolderRunner.java @@ -0,0 +1,53 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.env.python.testing; + +import com.intellij.execution.Location; +import com.intellij.execution.testframework.AbstractTestProxy; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiNamedElement; +import com.intellij.psi.search.GlobalSearchScope; +import com.jetbrains.env.ut.PyScriptTestProcessRunner; +import com.jetbrains.python.psi.PyFunction; +import org.hamcrest.Matchers; +import org.jetbrains.annotations.NotNull; +import org.junit.Assert; + +/** + * Checks tests are resolved when launched from subfolder + */ +abstract class PyTestsInSubFolderRunner> extends PyTestsFunctionBasedRunner { + + /** + * @param functionsToCheck name of functions that should be found in test tree and resolved + */ + PyTestsInSubFolderRunner(@NotNull final String... functionsToCheck) { + super(functionsToCheck); + } + + + @Override + protected void checkMethod(@NotNull final AbstractTestProxy method, @NotNull final String functionName) { + + final Location methodLocation = method.getLocation(getProject(), GlobalSearchScope.moduleScope(myFixture.getModule())); + + Assert.assertNotNull("Failed to resolve method location " + method, methodLocation); + final PsiElement methodPsiElement = methodLocation.getPsiElement(); + Assert.assertNotNull("Failed to get PSI for method location", methodPsiElement); + Assert.assertThat("Wrong test returned", methodPsiElement, Matchers.instanceOf(PyFunction.class)); + Assert.assertEquals("Wrong method name", functionName, ((PsiNamedElement)methodPsiElement).getName()); + } +} diff --git a/python/testSrc/com/jetbrains/env/python/testing/PyTestsOutputRunner.java b/python/testSrc/com/jetbrains/env/python/testing/PyTestsOutputRunner.java new file mode 100644 index 000000000000..7225a1d213b2 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/python/testing/PyTestsOutputRunner.java @@ -0,0 +1,51 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.env.python.testing; + +import com.intellij.execution.testframework.AbstractTestProxy; +import com.intellij.execution.testframework.sm.runner.ui.MockPrinter; +import com.jetbrains.env.ut.PyScriptTestProcessRunner; +import org.hamcrest.Matchers; +import org.jetbrains.annotations.NotNull; +import org.junit.Assert; + +/** + * Checks test output is correct + */ +abstract class PyTestsOutputRunner> extends PyTestsFunctionBasedRunner { + + PyTestsOutputRunner(@NotNull final String... functionsToCheck) { + super(functionsToCheck); + } + + @Override + protected void checkMethod(@NotNull final AbstractTestProxy method, @NotNull final String functionName) { + if (functionName.endsWith("test_metheggs")) { + Assert.assertThat("Method output is broken", + MockPrinter.fillPrinter(method).getStdOut().trim(), Matchers.containsString("I am method")); + } + else if (functionName.endsWith("test_funeggs")) { + Assert.assertThat("Function output is broken", + MockPrinter.fillPrinter(method).getStdOut().trim(), Matchers.containsString("I am function")); + } + else if (functionName.endsWith("test_first") || functionName.endsWith("test_second")) { + // No output expected + } + else { + throw new AssertionError("Unknown function " + functionName); + } + } +} diff --git a/python/testSrc/com/jetbrains/env/python/testing/PyUnitTestLikeProcessWithConsoleTestTask.kt b/python/testSrc/com/jetbrains/env/python/testing/PyUnitTestLikeProcessWithConsoleTestTask.kt new file mode 100644 index 000000000000..2ec7e246239c --- /dev/null +++ b/python/testSrc/com/jetbrains/env/python/testing/PyUnitTestLikeProcessWithConsoleTestTask.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.env.python.testing + +import com.google.common.collect.Sets +import com.jetbrains.env.PyProcessWithConsoleTestTask +import com.jetbrains.env.ut.PyScriptTestProcessRunner +import com.jetbrains.env.ut.PyUnitTestProcessRunner +import com.jetbrains.python.sdkTools.SdkCreationType +import java.util.function.Function + +/** + * [PyProcessWithConsoleTestTask] to be used with python unittest and trial. It saves you from boilerplate + * by setting working folder and creating [PyUnitTestProcessRunner] + + * @author Ilya.Kazakevich + */ +internal abstract class PyUnitTestLikeProcessWithConsoleTestTask> @JvmOverloads constructor(relativePathToTestData: String, + val myScriptName: String, + val myRerunFailedTests: Int = 0, + protected val processRunnerCreator: Function) : + PyProcessWithConsoleTestTask(relativePathToTestData, SdkCreationType.SDK_PACKAGES_ONLY) { + + override fun getTagsToCover(): Set = Sets.newHashSet("python2.6", "python2.7", "python3.5", "python3.6", "jython", "pypy", + "IronPython") + + + @Throws(Exception::class) + override fun createProcessRunner(): T = + processRunnerCreator.apply(TestRunnerConfig(myScriptName, myRerunFailedTests)) +} + +data class TestRunnerConfig(val scriptName: String, val rerunFailedTests: Int) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/env/python/testing/PyUnitTestProcessWithConsoleTestTask.java b/python/testSrc/com/jetbrains/env/python/testing/PyUnitTestProcessWithConsoleTestTask.java deleted file mode 100644 index 5467db48f69b..000000000000 --- a/python/testSrc/com/jetbrains/env/python/testing/PyUnitTestProcessWithConsoleTestTask.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2000-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.jetbrains.env.python.testing; - -import com.google.common.collect.Sets; -import com.intellij.execution.Location; -import com.intellij.execution.testframework.AbstractTestProxy; -import com.intellij.execution.testframework.sm.runner.ui.MockPrinter; -import com.intellij.openapi.application.ReadAction; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiNamedElement; -import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.util.ThrowableRunnable; -import com.jetbrains.env.PyProcessWithConsoleTestTask; -import com.jetbrains.env.ut.PyScriptTestProcessRunner; -import com.jetbrains.env.ut.PyUnitTestProcessRunner; -import com.jetbrains.python.psi.PyFunction; -import com.jetbrains.python.sdkTools.SdkCreationType; -import org.hamcrest.Matchers; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.junit.Assert; - -import java.util.Set; - -/** - * {@link PyProcessWithConsoleTestTask} to be used with python unittest. It saves you from boilerplate - * by setting working folder and creating {@link PyUnitTestProcessRunner} - * - * @author Ilya.Kazakevich - */ -abstract class PyUnitTestProcessWithConsoleTestTask extends PyProcessWithConsoleTestTask { - @NotNull - protected final String myScriptName; - private final int myRerunFailedTests; - - PyUnitTestProcessWithConsoleTestTask(@NotNull final String relativePathToTestData, @NotNull final String scriptName) { - this(relativePathToTestData, scriptName, 0); - } - PyUnitTestProcessWithConsoleTestTask(@NotNull final String relativePathToTestData, - @NotNull final String scriptName, - final int rerunFailedTests) { - super(relativePathToTestData, SdkCreationType.SDK_PACKAGES_ONLY); - myScriptName = scriptName; - myRerunFailedTests= rerunFailedTests; - } - - @Nullable - @Override - public Set getTagsToCover() { - return Sets.newHashSet("python2.6", "python2.7", "python3.5", "python3.6", "jython", "pypy", "IronPython"); - } - - @NotNull - @Override - protected PyUnitTestProcessRunner createProcessRunner() throws Exception { - return new PyUnitTestProcessRunner(myScriptName, myRerunFailedTests); - } - - - /** - * Checks each method by name - */ - abstract static class PyTestsFunctionBasedRunner> extends PyProcessWithConsoleTestTask { - @NotNull - protected final String[] myFunctionsToCheck; - - protected PyTestsFunctionBasedRunner(@NotNull final String... functionsToCheck) { - super("/testRunner/env/testsInFolder", SdkCreationType.EMPTY_SDK); - assert functionsToCheck.length > 0 : "Provide functions"; - myFunctionsToCheck = functionsToCheck.clone(); - } - - @Override - protected final void checkTestResults(@NotNull final T runner, - @NotNull final String stdout, - @NotNull final String stderr, - @NotNull final String all) { - for (final String functionName : myFunctionsToCheck) { - ReadAction.run((ThrowableRunnable)() -> { - final AbstractTestProxy method = runner.findTestByName(functionName); - checkMethod(method, functionName); - }); - } - } - - /** - * Called for each method - */ - protected abstract void checkMethod(@NotNull final AbstractTestProxy method, @NotNull final String functionName); - } - - /** - * Checks tests are resolved when launched from subfolder - */ - abstract static class PyTestsInSubFolderRunner> extends PyTestsFunctionBasedRunner { - - /** - * @param functionsToCheck name of functions that should be found in test tree and resolved - */ - PyTestsInSubFolderRunner(@NotNull final String... functionsToCheck) { - super(functionsToCheck); - } - - - @Override - protected void checkMethod(@NotNull final AbstractTestProxy method, @NotNull final String functionName) { - - final Location methodLocation = method.getLocation(getProject(), GlobalSearchScope.moduleScope(myFixture.getModule())); - - Assert.assertNotNull("Failed to resolve method location " + method, methodLocation); - final PsiElement methodPsiElement = methodLocation.getPsiElement(); - Assert.assertNotNull("Failed to get PSI for method location", methodPsiElement); - Assert.assertThat("Wrong test returned", methodPsiElement, Matchers.instanceOf(PyFunction.class)); - Assert.assertEquals("Wrong method name", functionName, ((PsiNamedElement)methodPsiElement).getName()); - } - } - - /** - * Checks test output is correct - */ - abstract static class PyTestsOutputRunner> extends PyTestsFunctionBasedRunner { - - PyTestsOutputRunner(@NotNull final String... functionsToCheck) { - super(functionsToCheck); - } - - @Override - protected void checkMethod(@NotNull final AbstractTestProxy method, @NotNull final String functionName) { - if (functionName.endsWith("test_metheggs")) { - Assert.assertThat("Method output is broken", - MockPrinter.fillPrinter(method).getStdOut().trim(), Matchers.containsString("I am method")); - } - else if (functionName.endsWith("test_funeggs")) { - Assert.assertThat("Function output is broken", - MockPrinter.fillPrinter(method).getStdOut().trim(), Matchers.containsString("I am function")); - } - else if (functionName.endsWith("test_first") || functionName.endsWith("test_second")) { - // No output expected - } - else { - throw new AssertionError("Unknown function " + functionName); - } - } - } -} diff --git a/python/testSrc/com/jetbrains/env/python/testing/PythonDocTestingTest.java b/python/testSrc/com/jetbrains/env/python/testing/PythonDocTestingTest.java index 4b257814ad81..976f109e95fd 100644 --- a/python/testSrc/com/jetbrains/env/python/testing/PythonDocTestingTest.java +++ b/python/testSrc/com/jetbrains/env/python/testing/PythonDocTestingTest.java @@ -4,12 +4,15 @@ import com.jetbrains.env.PyEnvTestCase; import com.jetbrains.env.PyProcessWithConsoleTestTask; import com.jetbrains.env.ut.PyDocTestProcessRunner; import com.jetbrains.python.sdkTools.SdkCreationType; +import com.jetbrains.python.testing.AbstractPythonLegacyTestRunConfiguration; import com.jetbrains.python.testing.doctest.PythonDocTestRunConfiguration; import org.hamcrest.Matchers; import org.jetbrains.annotations.NotNull; import org.junit.Assert; import org.junit.Test; +import java.io.IOException; + import static org.junit.Assert.assertEquals; /** @@ -18,13 +21,40 @@ import static org.junit.Assert.assertEquals; public final class PythonDocTestingTest extends PyEnvTestCase { - @Test public void testConfigurationProducer() throws Exception { runPythonTest( new CreateConfigurationByFileTask<>(null, PythonDocTestRunConfiguration.class, "doctest_test.py")); } + // ensure no pattern provided if checkbox disabled + @Test + public void testNoPatternIfDisabled() { + runPythonTest(new PyProcessWithConsoleTestTask("/testRunner/env/doc", SdkCreationType.EMPTY_SDK) { + @NotNull + @Override + protected PyDocTestProcessRunner createProcessRunner() throws Exception { + return new PyDocTestProcessRunner("subfolder", 0) { + @Override + protected void configurationCreatedAndWillLaunch(@NotNull PythonDocTestRunConfiguration configuration) throws IOException { + super.configurationCreatedAndWillLaunch(configuration); + configuration.setTestType(AbstractPythonLegacyTestRunConfiguration.TestType.TEST_FOLDER); + configuration.setPattern("ABC123"); + configuration.usePattern(false); + } + }; + } + + @Override + protected void checkTestResults(@NotNull final PyDocTestProcessRunner runner, + @NotNull final String stdout, + @NotNull final String stderr, + @NotNull final String all) { + Assert.assertThat("Pattern used while it should not", all, Matchers.not(Matchers.containsString("ABC123"))); + } + }); + } + @Test public void testUTRunner() { runPythonTest(new PyProcessWithConsoleTestTask("/testRunner/env/doc", SdkCreationType.EMPTY_SDK) { diff --git a/python/testSrc/com/jetbrains/env/python/testing/PythonNoseTestingTest.java b/python/testSrc/com/jetbrains/env/python/testing/PythonNoseTestingTest.java index 937341fb3edd..3199bb7591a1 100644 --- a/python/testSrc/com/jetbrains/env/python/testing/PythonNoseTestingTest.java +++ b/python/testSrc/com/jetbrains/env/python/testing/PythonNoseTestingTest.java @@ -119,8 +119,8 @@ public final class PythonNoseTestingTest extends PyEnvTestCase { @Test public void testTestsInSubFolderResolvable() throws Exception { runPythonTest( - new PyUnitTestProcessWithConsoleTestTask.PyTestsInSubFolderRunner("test_metheggs", "test_funeggs", - "test_first") { + new PyTestsInSubFolderRunner("test_metheggs", "test_funeggs", + "test_first") { @NotNull @Override protected PyNoseTestProcessRunner createProcessRunner() throws Exception { @@ -141,7 +141,7 @@ public final class PythonNoseTestingTest extends PyEnvTestCase { @Test public void testOutput() throws Exception { runPythonTest( - new PyUnitTestProcessWithConsoleTestTask.PyTestsOutputRunner("test_metheggs", "test_funeggs", "test_first") { + new PyTestsOutputRunner("test_metheggs", "test_funeggs", "test_first") { @NotNull @Override protected PyNoseTestProcessRunner createProcessRunner() throws Exception { diff --git a/python/testSrc/com/jetbrains/env/python/testing/PythonPyTestingTest.java b/python/testSrc/com/jetbrains/env/python/testing/PythonPyTestingTest.java index 13f3f383889b..c717600ade38 100644 --- a/python/testSrc/com/jetbrains/env/python/testing/PythonPyTestingTest.java +++ b/python/testSrc/com/jetbrains/env/python/testing/PythonPyTestingTest.java @@ -292,8 +292,8 @@ public final class PythonPyTestingTest extends PyEnvTestCase { @Test public void testTestsInSubFolderResolvable() throws Exception { runPythonTest( - new PyUnitTestProcessWithConsoleTestTask.PyTestsInSubFolderRunner("test_metheggs", "test_funeggs", - "test_first") { + new PyTestsInSubFolderRunner("test_metheggs", "test_funeggs", + "test_first") { @NotNull @Override protected PyTestTestProcessRunner createProcessRunner() throws Exception { @@ -314,7 +314,7 @@ public final class PythonPyTestingTest extends PyEnvTestCase { @Test public void testOutput() throws Exception { runPythonTest( - new PyUnitTestProcessWithConsoleTestTask.PyTestsOutputRunner("test_metheggs", "test_funeggs", "test_first") { + new PyTestsOutputRunner("test_metheggs", "test_funeggs", "test_first") { @NotNull @Override protected PyTestTestProcessRunner createProcessRunner() throws Exception { diff --git a/python/testSrc/com/jetbrains/env/python/testing/PythonTrialTest.kt b/python/testSrc/com/jetbrains/env/python/testing/PythonTrialTest.kt new file mode 100644 index 000000000000..99e194ae42e5 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/python/testing/PythonTrialTest.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.env.python.testing + +import com.jetbrains.env.EnvTestTagsRequired +import com.jetbrains.env.ut.PyScriptTestProcessRunner +import com.jetbrains.python.testing.PyTrialTestConfiguration +import com.jetbrains.python.testing.PyTrialTestFactory + +// Twisted trial test case +@EnvTestTagsRequired(tags = arrayOf("twisted")) +internal class PythonTrialTest : PythonUnitTestingLikeTest() { + override fun createTestRunner(config: TestRunnerConfig) = PyTrialTestProcessRunner(config.scriptName, config.rerunFailedTests) +} + + +class PyTrialTestProcessRunner(scriptName: String, + timesToRerunFailedTests: Int) : PyScriptTestProcessRunner( + PyTrialTestFactory, PyTrialTestConfiguration::class.java, scriptName, timesToRerunFailedTests) diff --git a/python/testSrc/com/jetbrains/env/python/testing/PythonUnitTestingLikeTest.java b/python/testSrc/com/jetbrains/env/python/testing/PythonUnitTestingLikeTest.java new file mode 100644 index 000000000000..fa396e3016b3 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/python/testing/PythonUnitTestingLikeTest.java @@ -0,0 +1,388 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.env.python.testing; + +import com.intellij.execution.ExecutionException; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.testFramework.EdtTestUtil; +import com.intellij.testFramework.fixtures.CodeInsightTestFixture; +import com.jetbrains.env.PyEnvTestCase; +import com.jetbrains.env.ut.PyScriptTestProcessRunner; +import com.jetbrains.env.ut.PyUnitTestProcessRunner; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.psi.LanguageLevel; +import org.hamcrest.Matchers; +import org.jetbrains.annotations.NotNull; +import org.junit.Assert; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import static com.jetbrains.env.ut.PyScriptTestProcessRunner.TEST_TARGET_PREFIX; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertEquals; + +/** + * Parent of unittest and trial test. + * All tests here should run with trial and unit + */ +public abstract class PythonUnitTestingLikeTest> extends PyEnvTestCase { + /** + * Ensure "[" in test does not break output + */ + @Test + public void testEscaping() throws Exception { + runPythonTest(new PyUnitTestLikeProcessWithConsoleTestTask("/testRunner/env/unit", "test_escaping.py", this::createTestRunner) { + + + @Override + protected void checkTestResults(@NotNull final T runner, + @NotNull final String stdout, + @NotNull final String stderr, + @NotNull final String all) { + assertEquals(1, runner.getAllTestsCount()); + assertEquals(0, runner.getPassedTestsCount()); + assertEquals(1, runner.getFailedTestsCount()); + } + }); + } + + abstract T createTestRunner(@NotNull final TestRunnerConfig config); + + /** + * Ensure that sys.path[0] is script folder, not helpers folder + */ + @Test + public void testSysPath() throws Exception { + runPythonTest(new PyUnitTestLikeProcessWithConsoleTestTask("testRunner/env/unit/sysPath", "test_sample.py", this::createTestRunner) { + + @NotNull + @Override + protected T createProcessRunner() throws Exception { + return getProcessRunnerCreator().apply(new TestRunnerConfig(toFullPath(getMyScriptName()), 0)); + } + + @Override + protected void checkTestResults(@NotNull final T runner, + @NotNull final String stdout, + @NotNull final String stderr, + @NotNull final String all) { + Assert.assertEquals(runner.getFormattedTestTree(), 1, runner.getAllTestsCount()); + myFixture.getTempDirFixture().getFile("sysPath"); + + final VirtualFile folderWithScript = myFixture.getTempDirFixture().getFile("."); + assert folderWithScript != null : "No folder for script " + getMyScriptName(); + Assert.assertThat("sys.path[0] should point to folder with test, while it does not", stdout, + Matchers.containsString(String.format("path[0]=%s", new File(folderWithScript.getPath()).getAbsolutePath()))); + } + }); + } + + @Test + public void testUTRunner() { + runPythonTest(new PyUnitTestLikeProcessWithConsoleTestTask("/testRunner/env/unit", "test1.py", this::createTestRunner) { + + + @Override + protected void checkTestResults(@NotNull final T runner, + @NotNull final String stdout, + @NotNull final String stderr, + @NotNull final String all) { + assertEquals(runner.getFormattedTestTree(), 2, runner.getAllTestsCount()); + assertEquals(runner.getFormattedTestTree(), 2, runner.getPassedTestsCount()); + runner.assertAllTestsPassed(); + } + }); + } + + /** + * tests with docstrings are reported as "test.name (text)" by unittest. + */ + @Test + public void testWithDocString() throws Exception { + + runPythonTest( + new PyUnitTestLikeProcessWithConsoleTestTask("testRunner/env/unit/withDocString", "test_test.py", this::createTestRunner) { + + @NotNull + @Override + protected T createProcessRunner() throws Exception { + return getProcessRunnerCreator().apply(new TestRunnerConfig(toFullPath(getMyScriptName()), 1)); + } + + @Override + protected void checkTestResults(@NotNull final T runner, + @NotNull final String stdout, + @NotNull final String stderr, + @NotNull final String all) { + if (runner.getCurrentRerunStep() == 0) { + assertEquals("test with docstring produced bad tree", "Test tree:\n" + + "[root]\n" + + ".test_test\n" + + "..SomeTestCase\n" + + "...testSomething (Only with docstring test is parsed with extra space)(+)\n" + + "...testSomethingBad (Fail)(-)\n", runner.getFormattedTestTree()); + } + else { + assertEquals("test with docstring failed to rerun", + "Test tree:\n" + + "[root]\n" + + ".test_test\n" + + "..SomeTestCase\n" + + "...testSomethingBad (Fail)(-)\n", runner.getFormattedTestTree()); + } + } + }); + } + + @Test + public void testClass() { + runPythonTest(new PyUnitTestLikeProcessWithConsoleTestTask("/testRunner/env/unit", + TEST_TARGET_PREFIX + "test_file.GoodTest", this::createTestRunner) { + + @Override + protected void checkTestResults(@NotNull final T runner, + @NotNull final String stdout, + @NotNull final String stderr, + @NotNull final String all) { + assertEquals(1, runner.getAllTestsCount()); + assertEquals(1, runner.getPassedTestsCount()); + } + }); + } + + @Test + public void testUTRunner2() { + runPythonTest(new PyUnitTestLikeProcessWithConsoleTestTask("/testRunner/env/unit", "test2.py", this::createTestRunner) { + + @Override + protected void checkTestResults(@NotNull final T runner, + @NotNull final String stdout, + @NotNull final String stderr, + @NotNull final String all) { + assertEquals(runner.getFormattedTestTree(), 3, runner.getAllTestsCount()); + assertEquals(runner.getFormattedTestTree(), 1, runner.getPassedTestsCount()); + assertEquals(runner.getFormattedTestTree(), 2, runner.getFailedTestsCount()); + } + }); + } + + + /** + * Ensures file references are highlighted for python traceback + */ + @Test + public void testUnitTestFileReferences() { + final String fileName = "reference_tests.py"; + runPythonTest(new PyUnitTestLikeProcessWithConsoleTestTask("/testRunner/env/unit", fileName, this::createTestRunner) { + + @Override + protected void checkTestResults(@NotNull final T runner, + @NotNull final String stdout, + @NotNull final String stderr, + @NotNull final String all) { + final List fileNames = runner.getHighlightedStringsInConsole().getSecond(); + Assert.assertTrue(String.format("Not enough highlighted entries(%s) in the following output: %s", + StringUtil.join(fileNames, ","), + runner.getAllConsoleText()), + fileNames.size() >= 3); + // UnitTest highlights file name + Assert.assertThat("Bad line highlighted", fileNames, hasItem(endsWith(fileName))); + } + }); + } + + /** + * Ensures that skipped and erroneous tests do not lead to suite ignorance + */ + @Test + public void testUTSkippedAndIgnored() { + runPythonTest( + new PyUnitTestLikeProcessWithConsoleTestTask("/testRunner/env/unit", "test_with_skips_and_errors.py", this::createTestRunner) { + + @Override + public boolean isLanguageLevelSupported(@NotNull final LanguageLevel level) { + // This test requires unittest to have decorator "test" that does not exists in 2.6 + return level.compareTo(LanguageLevel.PYTHON26) > 0; + } + + @Override + protected void checkTestResults(@NotNull final T runner, + @NotNull final String stdout, + @NotNull final String stderr, + @NotNull final String all) { + assertEquals(4, runner.getAllTestsCount()); + assertEquals(2, runner.getPassedTestsCount()); + assertEquals(2, runner.getFailedTestsCount()); + Assert.assertFalse("Suite is not finished", runner.getTestProxy().isInterrupted()); + } + }); + } + + + @Test + public void testDependent() { + runPythonTest( + new PyUnitTestLikeProcessWithConsoleTestTask("/testRunner/env/unit", "dependentTests/test_my_class.py", this::createTestRunner) { + + @Override + protected void checkTestResults(@NotNull final T runner, + @NotNull final String stdout, + @NotNull final String stderr, + @NotNull final String all) { + assertEquals(1, runner.getAllTestsCount()); + assertEquals(1, runner.getPassedTestsCount()); + } + }); + } + + /** + * Ensures that python target pointing to module works correctly + */ + @Test + public void testRunModuleAsFile() throws Exception { + runPythonTest(new RunModuleAsFileTask() { + @NotNull + @Override + protected PyUnitTestProcessRunner createProcessRunner() throws Exception { + return new PyUnitTestProcessRunner(TARGET, 0); + } + }); + } + + /** + * Ensure rerun test works even if test is declared in parent + * See https://github.com/JetBrains/teamcity-messages/issues/117 + */ + @Test + public void testRerunDerivedClass() throws Exception { + runPythonTest(new PyUnitTestLikeProcessWithConsoleTestTask("/testRunner/env/unit", "rerun_derived.py", this::createTestRunner) { + @Override + protected void checkTestResults(@NotNull final T runner, + @NotNull final String stdout, + @NotNull final String stderr, + @NotNull final String all) { + Assert.assertThat("Wrong number of failed tests", runner.getFailedTestsCount(), equalTo(1)); + final int expectedNumberOfTests = (runner.getCurrentRerunStep() == 0 ? 2 : 1); + Assert.assertThat("Wrong number tests", runner.getAllTestsCount(), equalTo(expectedNumberOfTests)); + if (runner.getCurrentRerunStep() == 1) { + // Make sure derived tests are launched, not abstract + Assert.assertEquals("Wrong tests after rerun", + "Test tree:\n" + + "[root]\n" + + ".rerun_derived\n" + + "..TestDerived\n" + + "...test_a(-)\n", runner.getFormattedTestTree()); + } + } + + @NotNull + @Override + protected T createProcessRunner() throws Exception { + return getProcessRunnerCreator().apply(new TestRunnerConfig(getMyScriptName(), 2)); + } + }); + } + + + // Ensures setup/teardown does not break anything + @Test + public void testSetupTearDown() throws Exception { + runPythonTest(new SetupTearDownTestTask() { + @NotNull + @Override + protected PyUnitTestProcessRunner createProcessRunner() throws Exception { + return new PyUnitTestProcessRunner("test_test.py", 1); + } + }); + } + + + @Test + public void testFolder() { + runPythonTest(new PyUnitTestLikeProcessWithConsoleTestTask("/testRunner/env/unit", "test_folder/", this::createTestRunner) { + + @Override + protected void checkTestResults(@NotNull final T runner, + @NotNull final String stdout, + @NotNull final String stderr, + @NotNull final String all) { + assertEquals(5, runner.getAllTestsCount()); + assertEquals(3, runner.getPassedTestsCount()); + } + }); + } + + /** + * Run tests, delete file and click "rerun" should throw exception and display error since test ids do not point to correct PSI + * from that moment + */ + @Test + public void testCantRerun() throws Exception { + startMessagesCapture(); + + runPythonTest( + new PyUnitTestLikeProcessWithConsoleTestTask("/testRunner/env/unit", "test_with_skips_and_errors.py", this::createTestRunner) { + + @Override + protected void checkTestResults(@NotNull final T runner, + @NotNull final String stdout, + @NotNull final String stderr, + @NotNull final String all) { + assert runner.getFailedTestsCount() > 0 : "We need failed tests to test broken rerun"; + + startMessagesCapture(); + + EdtTestUtil.runInEdtAndWait(() -> { + deleteAllTestFiles(myFixture); + runner.rerunFailedTests(); + }); + + final List throwables = getCapturesMessages().first; + Assert.assertThat("Exception shall be thrown", throwables, not(emptyCollectionOf(Throwable.class))); + final Throwable exception = throwables.get(0); + Assert.assertThat("ExecutionException should be thrown", exception, instanceOf(ExecutionException.class)); + Assert.assertThat("Wrong text", exception.getMessage(), equalTo(PyBundle.message("runcfg.tests.cant_rerun"))); + Assert.assertThat("No messages displayed for exception", getCapturesMessages().second, not(emptyCollectionOf(String.class))); + + + stopMessageCapture(); + } + }); + } + + /** + * Deletes all files in temp. folder + */ + private static void deleteAllTestFiles(@NotNull final CodeInsightTestFixture fixture) { + ApplicationManager.getApplication().runWriteAction(() -> { + final VirtualFile testRoot = fixture.getTempDirFixture().getFile("."); + assert testRoot != null : "No temp path?"; + try { + for (final VirtualFile child : testRoot.getChildren()) { + child.delete(null); + } + } + catch (final IOException e) { + throw new AssertionError(String.format("Failed to delete files in %s : %s", testRoot, e)); + } + }); + } +} diff --git a/python/testSrc/com/jetbrains/env/python/testing/PythonUnitTestingTest.java b/python/testSrc/com/jetbrains/env/python/testing/PythonUnitTestingTest.java index ca86062fc540..3a0f42e7220f 100644 --- a/python/testSrc/com/jetbrains/env/python/testing/PythonUnitTestingTest.java +++ b/python/testSrc/com/jetbrains/env/python/testing/PythonUnitTestingTest.java @@ -15,19 +15,12 @@ */ package com.jetbrains.env.python.testing; -import com.intellij.execution.ExecutionException; import com.intellij.execution.configurations.RuntimeConfigurationWarning; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; -import com.intellij.testFramework.EdtTestUtil; -import com.intellij.testFramework.fixtures.CodeInsightTestFixture; import com.jetbrains.env.EnvTestTagsRequired; -import com.jetbrains.env.PyEnvTestCase; import com.jetbrains.env.ut.PyUnitTestProcessRunner; -import com.jetbrains.python.PyBundle; +import com.jetbrains.python.PythonHelper; import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.sdk.InvalidSdkException; @@ -35,26 +28,72 @@ import com.jetbrains.python.testing.PyUnitTestConfiguration; import com.jetbrains.python.testing.PyUnitTestFactory; import com.jetbrains.python.testing.PythonTestConfigurationsModel; import com.jetbrains.python.testing.TestTargetType; -import org.hamcrest.Matchers; import org.jetbrains.annotations.NotNull; import org.junit.Assert; import org.junit.Test; -import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.List; import static com.jetbrains.env.ut.PyScriptTestProcessRunner.TEST_TARGET_PREFIX; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.endsWith; import static org.junit.Assert.assertEquals; /** * @author traff */ -public final class PythonUnitTestingTest extends PyEnvTestCase { +public final class PythonUnitTestingTest extends PythonUnitTestingLikeTest { + @Override + PyUnitTestProcessRunner createTestRunner(@NotNull final TestRunnerConfig config) { + return new PyUnitTestProcessRunner(config.getScriptName(), config.getRerunFailedTests()); + } + + @Test + public void testSetupPyRunner() throws Exception { + // We need to make sure setup.py is called using different runner + runPythonTest(new PyUnitTestProcessWithConsoleTestTask("testRunner/env/unit/failFast", "setup.py") { + + @Override + protected void checkTestResults(@NotNull final PyUnitTestProcessRunner runner, + @NotNull final String stdout, + @NotNull final String stderr, + @NotNull final String all) { + Assert.assertThat("Wrong runner used", all, containsString(PythonHelper.SETUPPY.asParamString())); + } + }); + } + + @Test + public void testRenameClass() throws Exception { + runPythonTest( + new CreateConfigurationByFileTask.CreateConfigurationTestAndRenameClassTask<>( + PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME, + PyUnitTestConfiguration.class)); + } + + @Test(expected = RuntimeConfigurationWarning.class) + public void testValidation() throws Exception { + + final CreateConfigurationTestTask.PyConfigurationCreationTask task = + new CreateConfigurationTestTask.PyConfigurationCreationTask() { + @NotNull + @Override + protected PyUnitTestFactory createFactory() { + return PyUnitTestFactory.INSTANCE; + } + }; + runPythonTest(task); + final PyUnitTestConfiguration configuration = task.getConfiguration(); + configuration.setPattern("foo"); + configuration.getTarget().setTargetType(TestTargetType.PATH); + configuration.getTarget().setTarget("foo.py"); + configuration.checkConfiguration(); + } + /** * tests failfast as example of argument */ @@ -66,7 +105,7 @@ public final class PythonUnitTestingTest extends PyEnvTestCase { @NotNull @Override protected PyUnitTestProcessRunner createProcessRunner() throws Exception { - return new PyUnitTestProcessRunner(toFullPath(myScriptName), 1) { + return new PyUnitTestProcessRunner(toFullPath(getMyScriptName()), 1) { @Override protected void configurationCreatedAndWillLaunch(@NotNull final PyUnitTestConfiguration configuration) throws IOException { super.configurationCreatedAndWillLaunch(configuration); @@ -92,7 +131,7 @@ public final class PythonUnitTestingTest extends PyEnvTestCase { /** - * check non-ascii (127+) chars are supported in skip messaged + * check non-ascii (127+) chars are supported in skip messaged */ @Test public void testNonAsciiMessage() throws Exception { @@ -109,56 +148,16 @@ public final class PythonUnitTestingTest extends PyEnvTestCase { runner.getFormattedTestTree(); assertEquals("Skipped test with non-ascii message broke tree", "Test tree:\n" + - "[root]\n" + - ".test_test\n" + - "..TestCase\n" + - "...test(~)\n", runner.getFormattedTestTree()); + "[root]\n" + + ".test_test\n" + + "..TestCase\n" + + "...test(~)\n", runner.getFormattedTestTree()); Assert.assertThat("non-ascii char broken in output", stdout, containsString("ошибка")); } }); } - /** - * tests with docstrings are reported as "test.name (text)" by unittest. - */ - @Test - public void testWithDocString() throws Exception { - - runPythonTest(new PyUnitTestProcessWithConsoleTestTask("testRunner/env/unit/withDocString", "test_test.py") { - - @NotNull - @Override - protected PyUnitTestProcessRunner createProcessRunner() throws Exception { - return new PyUnitTestProcessRunner(toFullPath(myScriptName), 1); - } - - @Override - protected void checkTestResults(@NotNull final PyUnitTestProcessRunner runner, - @NotNull final String stdout, - @NotNull final String stderr, - @NotNull final String all) { - if (runner.getCurrentRerunStep() == 0) { - assertEquals("test with docstring produced bad tree", "Test tree:\n" + - "[root]\n" + - ".test_test\n" + - "..SomeTestCase\n" + - "...testSomething (Only with docstring test is parsed with extra space)(+)\n" + - "...testSomethingBad (Fail)(-)\n", runner.getFormattedTestTree()); - } - else { - assertEquals("test with docstring failed to rerun", - "Test tree:\n" + - "[root]\n" + - ".test_test\n" + - "..SomeTestCase\n" + - "...testSomethingBad (Fail)(-)\n", runner.getFormattedTestTree()); - } - } - }); - } - - // Ensure failed and error subtests work @Test @EnvTestTagsRequired(tags = "python3") @@ -168,7 +167,7 @@ public final class PythonUnitTestingTest extends PyEnvTestCase { @NotNull @Override protected PyUnitTestProcessRunner createProcessRunner() throws Exception { - return new PyUnitTestProcessRunner(toFullPath(myScriptName), 1); + return new PyUnitTestProcessRunner(toFullPath(getMyScriptName()), 1); } @Override @@ -198,7 +197,7 @@ public final class PythonUnitTestingTest extends PyEnvTestCase { @NotNull @Override protected PyUnitTestProcessRunner createProcessRunner() throws Exception { - return new PyUnitTestProcessRunner(toFullPath(myScriptName), 1); + return new PyUnitTestProcessRunner(toFullPath(getMyScriptName()), 1); } @Override @@ -233,7 +232,7 @@ public final class PythonUnitTestingTest extends PyEnvTestCase { @NotNull @Override protected PyUnitTestProcessRunner createProcessRunner() throws Exception { - return new PyUnitTestProcessRunner(toFullPath(myScriptName), 1); + return new PyUnitTestProcessRunner(toFullPath(getMyScriptName()), 1); } @Override @@ -261,18 +260,6 @@ public final class PythonUnitTestingTest extends PyEnvTestCase { }); } - // Ensures setup/teardown does not break anything - @Test - public void testSetupTearDown() throws Exception { - runPythonTest(new SetupTearDownTestTask() { - @NotNull - @Override - protected PyUnitTestProcessRunner createProcessRunner() throws Exception { - return new PyUnitTestProcessRunner("test_test.py", 1); - } - }); - } - /** * Raising SkipTest on class setup should not lead to KeyError @@ -287,43 +274,14 @@ public final class PythonUnitTestingTest extends PyEnvTestCase { @NotNull final String stdout, @NotNull final String stderr, @NotNull final String all) { - Assert.assertEquals("Output tree broken for skipped exception thrown in setup method" ,"Test tree:\n" + - "[root]\n" + - ".test_test\n" + - "..TestSimple\n" + - "...setUpClass(~)\n" + - "..TestSubSimple\n" + - "...setUpClass(~)\n", runner.getFormattedTestTree()); - } - }); - } - - - /** - * Ensure that sys.path[0] is script folder, not helpers folder - */ - @Test - public void testSysPath() throws Exception { - runPythonTest(new PyUnitTestProcessWithConsoleTestTask("testRunner/env/unit/sysPath", "test_sample.py") { - - @NotNull - @Override - protected PyUnitTestProcessRunner createProcessRunner() throws Exception { - return new PyUnitTestProcessRunner(toFullPath(myScriptName), 0); - } - - @Override - protected void checkTestResults(@NotNull final PyUnitTestProcessRunner runner, - @NotNull final String stdout, - @NotNull final String stderr, - @NotNull final String all) { - Assert.assertEquals(runner.getFormattedTestTree(), 1, runner.getAllTestsCount()); - myFixture.getTempDirFixture().getFile("sysPath"); - - final VirtualFile folderWithScript = myFixture.getTempDirFixture().getFile("."); - assert folderWithScript != null : "No folder for script " + myScriptName; - Assert.assertThat("sys.path[0] should point to folder with test, while it does not", stdout, - Matchers.containsString(String.format("path[0]=%s", new File(folderWithScript.getPath()).getAbsolutePath()))); + Assert.assertEquals("Output tree broken for skipped exception thrown in setup method", "Test tree:\n" + + "[root]\n" + + ".test_test\n" + + "..TestSimple\n" + + "...setUpClass(~)\n" + + "..TestSubSimple\n" + + "...setUpClass(~)\n", + runner.getFormattedTestTree()); } }); } @@ -340,7 +298,7 @@ public final class PythonUnitTestingTest extends PyEnvTestCase { @NotNull @Override protected PyUnitTestProcessRunner createProcessRunner() throws Exception { - return new PyUnitTestProcessRunner(toFullPath(myScriptName), 1); + return new PyUnitTestProcessRunner(toFullPath(getMyScriptName()), 1); } @Override @@ -374,7 +332,7 @@ public final class PythonUnitTestingTest extends PyEnvTestCase { @Override protected PyUnitTestProcessRunner createProcessRunner() throws Exception { // Full pass is required because it is folder - return new PyUnitTestProcessRunner(toFullPath(myScriptName), 2) { + return new PyUnitTestProcessRunner(toFullPath(getMyScriptName()), 2) { @Override protected void configurationCreatedAndWillLaunch(@NotNull final PyUnitTestConfiguration configuration) throws IOException { @@ -399,20 +357,6 @@ public final class PythonUnitTestingTest extends PyEnvTestCase { }); } - /** - * Ensures that python target pointing to module works correctly - */ - @Test - public void testRunModuleAsFile() throws Exception { - runPythonTest(new RunModuleAsFileTask() { - @NotNull - @Override - protected PyUnitTestProcessRunner createProcessRunner() throws Exception { - return new PyUnitTestProcessRunner(TARGET, 0); - } - }); - } - @Test public void testRerunSubfolder() throws Exception { runPythonTest(new RerunSubfolderTask(1) { @@ -442,7 +386,7 @@ public final class PythonUnitTestingTest extends PyEnvTestCase { @Override protected PyUnitTestProcessRunner createProcessRunner() throws Exception { // Full pass is required because it is folder - return new PyUnitTestProcessRunner(toFullPath(myScriptName), 2) { + return new PyUnitTestProcessRunner(toFullPath(getMyScriptName()), 2) { @Override protected void configurationCreatedAndWillLaunch(@NotNull PyUnitTestConfiguration configuration) throws IOException { super.configurationCreatedAndWillLaunch(configuration); @@ -510,20 +454,181 @@ public final class PythonUnitTestingTest extends PyEnvTestCase { }); } + // PY-24407 @Test - public void testMultipleCases() throws Exception { + public void testWorkingDirectoryDependsOnRelativeImport() throws Exception { + runPythonTest(new CreateConfigurationTestTask(PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME, + PyUnitTestConfiguration.class) { + @NotNull + @Override + protected List getPsiElementsToRightClickOn() { + myFixture.configureByFile("testRelativeImport/src/tests/test_no_relative.py"); + final PyFunction noRelativeImportFun = myFixture.findElementByText("test_no_relative", PyFunction.class); + assert noRelativeImportFun != null; + + myFixture.configureByFile("testRelativeImport/src/tests/test_relative.py"); + final PyFunction relativeImportFun = myFixture.findElementByText("test_relative", PyFunction.class); + assert relativeImportFun != null; + + + return Arrays.asList(relativeImportFun, noRelativeImportFun); + } + + @Override + protected void checkConfiguration(@NotNull PyUnitTestConfiguration configuration, @NotNull PsiElement elementToRightClickOn) { + super.checkConfiguration(configuration, elementToRightClickOn); + configuration.getWorkingDirectorySafe(); + + final PyFunction function = (PyFunction)elementToRightClickOn; + if (function.getName().equals("test_relative")) { + Assert.assertThat("Wrong dir for relative import", configuration.getWorkingDirectory(), endsWith("testRelativeImport")); + assertEquals("Bad target", "src.tests.test_relative.ModuleTest.test_relative", configuration.getTarget().getTarget()); + } + else if (function.getName().equals("test_no_relative")) { + Assert + .assertThat("Wrong dir for non relative import", configuration.getWorkingDirectory(), endsWith("testRelativeImport/src/tests")); + assertEquals("Bad target", "test_no_relative.ModuleTest.test_no_relative", configuration.getTarget().getTarget()); + } + else { + throw new AssertionError("Unexpected function " + function.getName()); + } + } + }); + } + + /** + * Checks tests are resolved when launched from subfolder + */ + @Test + public void testTestsInSubFolderResolvable() throws Exception { runPythonTest( - new CreateConfigurationMultipleCasesTask(PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME, - PyUnitTestConfiguration.class){ + new PyTestsInSubFolderRunner("test_metheggs", "test_first") { + @NotNull @Override - protected boolean configurationShouldBeProducedForElement(@NotNull final PsiElement element) { - // test_functions.py does not conttain any TestCase and can't be launched with unittest - final PsiFile file = element.getContainingFile(); - return file == null || ! file.getName().endsWith("test_functions.py"); + protected PyUnitTestProcessRunner createProcessRunner() throws Exception { + return new PyUnitTestProcessRunner(toFullPath("tests"), 0) { + @Override + protected void configurationCreatedAndWillLaunch(@NotNull PyUnitTestConfiguration configuration) throws IOException { + super.configurationCreatedAndWillLaunch(configuration); + configuration.setWorkingDirectory(getWorkingFolderForScript()); + } + }; } }); } + /** + * Ensures test output works + */ + @Test + public void testOutput() throws Exception { + runPythonTest( + new PyTestsOutputRunner("test_metheggs", "test_first") { + @NotNull + @Override + protected PyUnitTestProcessRunner createProcessRunner() throws Exception { + return new PyUnitTestProcessRunner(toFullPath("tests"), 0) { + @Override + protected void configurationCreatedAndWillLaunch(@NotNull PyUnitTestConfiguration configuration) throws IOException { + super.configurationCreatedAndWillLaunch(configuration); + configuration.setWorkingDirectory(getWorkingFolderForScript()); + } + }; + } + }); + } + + + /** + * Checks Load test protocol + */ + @Test + public void testLoadProtocol() throws Exception { + runPythonTest(new PyUnitTestProcessWithConsoleTestTask("/testRunner/env/unit", "test_load_protocol.py") { + @Override + public boolean isLanguageLevelSupported(@NotNull final LanguageLevel level) { + // "load_protocol" does not exist before 2.7 + return level.compareTo(LanguageLevel.PYTHON26) > 0; + } + + @Override + protected void checkTestResults(@NotNull final PyUnitTestProcessRunner runner, + @NotNull final String stdout, + @NotNull final String stderr, + @NotNull final String all) { + assertEquals("bad num of passed tests: unittest load protocol failed to find tests?", 3, runner.getPassedTestsCount()); + runner.assertAllTestsPassed(); + } + }); + } + + /** + * Ensures pattern is supported + */ + @Test + public void testUTRunnerByPattern() { + runPythonTest( + new PyUnitTestProcessWithConsoleTestTask("/testRunner/env/unit", PyUnitTestProcessRunner.TEST_PATTERN_PREFIX + "*pattern.py") { + + + @Override + protected void checkTestResults(@NotNull final PyUnitTestProcessRunner runner, + @NotNull final String stdout, + @NotNull final String stderr, + @NotNull final String all) { + assertEquals(runner.getFormattedTestTree(), 4, runner.getAllTestsCount()); + assertEquals(runner.getFormattedTestTree(), 2, runner.getPassedTestsCount()); + assertEquals(runner.getFormattedTestTree(), 2, runner.getFailedTestsCount()); + } + }); + } + + @Test + public void testMethod() { + runPythonTest(new PyUnitTestProcessWithConsoleTestTask("/testRunner/env/unit", + TEST_TARGET_PREFIX + + "test_file.GoodTest.test_passes") { + + @Override + protected void checkTestResults(@NotNull final PyUnitTestProcessRunner runner, + @NotNull final String stdout, + @NotNull final String stderr, + @NotNull final String all) { + assertEquals(1, runner.getAllTestsCount()); + assertEquals(1, runner.getPassedTestsCount()); + } + }); + } + + @Test + public void testRelativeImports() { + runPythonTest(new PyUnitTestProcessWithConsoleTestTask("/testRunner/env/unit/relativeImports", + PyUnitTestProcessRunner.TEST_PATTERN_PREFIX + "test_imps.py") { + @Override + protected void checkTestResults(@NotNull final PyUnitTestProcessRunner runner, + @NotNull final String stdout, + @NotNull final String stderr, + @NotNull final String all) { + assertEquals(runner.getFormattedTestTree(), 1, runner.getAllTestsCount()); + assertEquals(runner.getFormattedTestTree(), 1, runner.getPassedTestsCount()); + } + }); + } + + + @Test + public void testConfigurationProducerOnDirectory() throws Exception { + runPythonTest( + new CreateConfigurationByFileTask.CreateConfigurationTestAndRenameFolderTask<>(PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME, + PyUnitTestConfiguration.class)); + } + + + @Test + public void testConfigurationProducer() throws Exception { + runPythonTest(new CreateConfigurationByFileTask<>(PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME, PyUnitTestConfiguration.class)); + } + /** * Ensures newly created configuration inherits working dir from default if set */ @@ -554,438 +659,31 @@ public final class PythonUnitTestingTest extends PyEnvTestCase { ); } - // PY-24407 @Test - public void testWorkingDirectoryDependsOnRelativeImport() throws Exception { - runPythonTest(new CreateConfigurationTestTask(PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME, PyUnitTestConfiguration.class){ - @NotNull - @Override - protected List getPsiElementsToRightClickOn() { - myFixture.configureByFile("testRelativeImport/src/tests/test_no_relative.py"); - final PyFunction noRelativeImportFun = myFixture.findElementByText("test_no_relative", PyFunction.class); - assert noRelativeImportFun != null; - - myFixture.configureByFile("testRelativeImport/src/tests/test_relative.py"); - final PyFunction relativeImportFun = myFixture.findElementByText("test_relative", PyFunction.class); - assert relativeImportFun != null; - - - return Arrays.asList(relativeImportFun, noRelativeImportFun); - } - - @Override - protected void checkConfiguration(@NotNull PyUnitTestConfiguration configuration, @NotNull PsiElement elementToRightClickOn) { - super.checkConfiguration(configuration, elementToRightClickOn); - configuration.getWorkingDirectorySafe(); - - final PyFunction function = (PyFunction)elementToRightClickOn; - if (function.getName().equals("test_relative")) { - Assert.assertThat("Wrong dir for relative import", configuration.getWorkingDirectory(), endsWith("testRelativeImport")); - assertEquals("Bad target", "src.tests.test_relative.ModuleTest.test_relative", configuration.getTarget().getTarget()); - } else if (function.getName().equals("test_no_relative")) { - Assert.assertThat("Wrong dir for non relative import", configuration.getWorkingDirectory(), endsWith("testRelativeImport/src/tests")); - assertEquals("Bad target", "test_no_relative.ModuleTest.test_no_relative", configuration.getTarget().getTarget()); - } else { - throw new AssertionError("Unexpected function " + function.getName()); - } - } - }); - } - - @Test - public void testConfigurationProducer() throws Exception { - runPythonTest(new CreateConfigurationByFileTask<>(PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME, PyUnitTestConfiguration.class)); - } - - /** - * Checks tests are resolved when launched from subfolder - */ - @Test - public void testTestsInSubFolderResolvable() throws Exception { + public void testMultipleCases() throws Exception { runPythonTest( - new PyUnitTestProcessWithConsoleTestTask.PyTestsInSubFolderRunner("test_metheggs", "test_first") { - @NotNull + new CreateConfigurationMultipleCasesTask(PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME, + PyUnitTestConfiguration.class) { @Override - protected PyUnitTestProcessRunner createProcessRunner() throws Exception { - return new PyUnitTestProcessRunner(toFullPath("tests"), 0) { - @Override - protected void configurationCreatedAndWillLaunch(@NotNull PyUnitTestConfiguration configuration) throws IOException { - super.configurationCreatedAndWillLaunch(configuration); - configuration.setWorkingDirectory(getWorkingFolderForScript()); - } - }; - } - }); - } - - /** - * Ensures test output works - */ - @Test - public void testOutput() throws Exception { - runPythonTest( - new PyUnitTestProcessWithConsoleTestTask.PyTestsOutputRunner("test_metheggs", "test_first") { - @NotNull - @Override - protected PyUnitTestProcessRunner createProcessRunner() throws Exception { - return new PyUnitTestProcessRunner(toFullPath("tests"), 0) { - @Override - protected void configurationCreatedAndWillLaunch(@NotNull PyUnitTestConfiguration configuration) throws IOException { - super.configurationCreatedAndWillLaunch(configuration); - configuration.setWorkingDirectory(getWorkingFolderForScript()); - } - }; + protected boolean configurationShouldBeProducedForElement(@NotNull final PsiElement element) { + // test_functions.py does not conttain any TestCase and can't be launched with unittest + final PsiFile file = element.getContainingFile(); + return file == null || !file.getName().endsWith("test_functions.py"); } }); } - @Test(expected = RuntimeConfigurationWarning.class) - public void testValidation() throws Exception { + private abstract class PyUnitTestProcessWithConsoleTestTask extends PyUnitTestLikeProcessWithConsoleTestTask { + public PyUnitTestProcessWithConsoleTestTask(@NotNull String relativePathToTestData, + @NotNull String scriptName) { + super(relativePathToTestData, scriptName, PythonUnitTestingTest.this::createTestRunner); + } - final CreateConfigurationTestTask.PyConfigurationCreationTask task = - new CreateConfigurationTestTask.PyConfigurationCreationTask() { - @NotNull - @Override - protected PyUnitTestFactory createFactory() { - return PyUnitTestFactory.INSTANCE; - } - }; - runPythonTest(task); - final PyUnitTestConfiguration configuration = task.getConfiguration(); - configuration.setPattern("foo"); - configuration.getTarget().setTargetType(TestTargetType.PATH); - configuration.getTarget().setTarget("foo.py"); - configuration.checkConfiguration(); - } - - - @Test - public void testConfigurationProducerOnDirectory() throws Exception { - runPythonTest( - new CreateConfigurationByFileTask.CreateConfigurationTestAndRenameFolderTask<>(PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME, - PyUnitTestConfiguration.class)); - } - - @Test - public void testRenameClass() throws Exception { - runPythonTest( - new CreateConfigurationByFileTask.CreateConfigurationTestAndRenameClassTask<>( - PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME, - PyUnitTestConfiguration.class)); - } - - @Test - public void testUTRunner() { - runPythonTest(new PyUnitTestProcessWithConsoleTestTask("/testRunner/env/unit", "test1.py") { - - - @Override - protected void checkTestResults(@NotNull final PyUnitTestProcessRunner runner, - @NotNull final String stdout, - @NotNull final String stderr, - @NotNull final String all) { - assertEquals(runner.getFormattedTestTree(), 2, runner.getAllTestsCount()); - assertEquals(runner.getFormattedTestTree(), 2, runner.getPassedTestsCount()); - runner.assertAllTestsPassed(); - } - }); - } - - /** - * Checks Load test protocol - */ - @Test - public void testLoadProtocol() throws Exception { - runPythonTest(new PyUnitTestProcessWithConsoleTestTask("/testRunner/env/unit", "test_load_protocol.py") { - @Override - public boolean isLanguageLevelSupported(@NotNull final LanguageLevel level) { - // "load_protocol" does not exist before 2.7 - return level.compareTo(LanguageLevel.PYTHON26) > 0; - } - - @Override - protected void checkTestResults(@NotNull final PyUnitTestProcessRunner runner, - @NotNull final String stdout, - @NotNull final String stderr, - @NotNull final String all) { - assertEquals("bad num of passed tests: unittest load protocol failed to find tests?", 3, runner.getPassedTestsCount()); - runner.assertAllTestsPassed(); - } - }); - } - - /** - * Ensure rerun test works even if test is declared in parent - * See https://github.com/JetBrains/teamcity-messages/issues/117 - */ - @Test - public void testRerunDerivedClass() throws Exception { - runPythonTest(new PyUnitTestProcessWithConsoleTestTask("/testRunner/env/unit", "rerun_derived.py") { - @Override - protected void checkTestResults(@NotNull final PyUnitTestProcessRunner runner, - @NotNull final String stdout, - @NotNull final String stderr, - @NotNull final String all) { - Assert.assertThat("Wrong number of failed tests", runner.getFailedTestsCount(), equalTo(1)); - final int expectedNumberOfTests = (runner.getCurrentRerunStep() == 0 ? 2 : 1); - Assert.assertThat("Wrong number tests", runner.getAllTestsCount(), equalTo(expectedNumberOfTests)); - if (runner.getCurrentRerunStep() == 1) { - // Make sure derived tests are launched, not abstract - Assert.assertEquals("Wrong tests after rerun", - "Test tree:\n" + - "[root]\n" + - ".rerun_derived\n" + - "..TestDerived\n" + - "...test_a(-)\n", runner.getFormattedTestTree()); - } - } - - @NotNull - @Override - protected PyUnitTestProcessRunner createProcessRunner() throws Exception { - return new PyUnitTestProcessRunner(myScriptName, 2); - } - }); - } - - /** - * Run tests, delete file and click "rerun" should throw exception and display error since test ids do not point to correct PSI - * from that moment - */ - @Test - public void testCantRerun() throws Exception { - startMessagesCapture(); - - runPythonTest(new PyUnitTestProcessWithConsoleTestTask("/testRunner/env/unit", "test_with_skips_and_errors.py") { - - @Override - protected void checkTestResults(@NotNull final PyUnitTestProcessRunner runner, - @NotNull final String stdout, - @NotNull final String stderr, - @NotNull final String all) { - assert runner.getFailedTestsCount() > 0 : "We need failed tests to test broken rerun"; - - startMessagesCapture(); - - EdtTestUtil.runInEdtAndWait(() -> { - deleteAllTestFiles(myFixture); - runner.rerunFailedTests(); - }); - - final List throwables = getCapturesMessages().first; - Assert.assertThat("Exception shall be thrown", throwables, not(emptyCollectionOf(Throwable.class))); - final Throwable exception = throwables.get(0); - Assert.assertThat("ExecutionException should be thrown", exception, instanceOf(ExecutionException.class)); - Assert.assertThat("Wrong text", exception.getMessage(), equalTo(PyBundle.message("runcfg.tests.cant_rerun"))); - Assert.assertThat("No messages displayed for exception", getCapturesMessages().second, not(emptyCollectionOf(String.class))); - - - stopMessageCapture(); - } - }); - } - - /** - * Ensures that skipped and erroneous tests do not lead to suite ignorance - */ - @Test - public void testUTSkippedAndIgnored() { - runPythonTest(new PyUnitTestProcessWithConsoleTestTask("/testRunner/env/unit", "test_with_skips_and_errors.py") { - - @Override - public boolean isLanguageLevelSupported(@NotNull final LanguageLevel level) { - // This test requires unittest to have decorator "test" that does not exists in 2.6 - return level.compareTo(LanguageLevel.PYTHON26) > 0; - } - - @Override - protected void checkTestResults(@NotNull final PyUnitTestProcessRunner runner, - @NotNull final String stdout, - @NotNull final String stderr, - @NotNull final String all) { - assertEquals(4, runner.getAllTestsCount()); - assertEquals(2, runner.getPassedTestsCount()); - assertEquals(2, runner.getFailedTestsCount()); - Assert.assertFalse("Suite is not finished", runner.getTestProxy().isInterrupted()); - } - }); - } - - @Test - public void testUTRunner2() { - runPythonTest(new PyUnitTestProcessWithConsoleTestTask("/testRunner/env/unit", "test2.py") { - - @Override - protected void checkTestResults(@NotNull final PyUnitTestProcessRunner runner, - @NotNull final String stdout, - @NotNull final String stderr, - @NotNull final String all) { - assertEquals(runner.getFormattedTestTree(), 3, runner.getAllTestsCount()); - assertEquals(runner.getFormattedTestTree(), 1, runner.getPassedTestsCount()); - assertEquals(runner.getFormattedTestTree(), 2, runner.getFailedTestsCount()); - } - }); - } - - /** - * Ensures pattern is supported - */ - @Test - public void testUTRunnerByPattern() { - runPythonTest( - new PyUnitTestProcessWithConsoleTestTask("/testRunner/env/unit", PyUnitTestProcessRunner.TEST_PATTERN_PREFIX + "*pattern.py") { - - - @Override - protected void checkTestResults(@NotNull final PyUnitTestProcessRunner runner, - @NotNull final String stdout, - @NotNull final String stderr, - @NotNull final String all) { - assertEquals(runner.getFormattedTestTree(), 4, runner.getAllTestsCount()); - assertEquals(runner.getFormattedTestTree(), 2, runner.getPassedTestsCount()); - assertEquals(runner.getFormattedTestTree(), 2, runner.getFailedTestsCount()); - } - }); - } - - /** - * Ensure "[" in test does not break output - */ - @Test - public void testEscaping() throws Exception { - runPythonTest(new PyUnitTestProcessWithConsoleTestTask("/testRunner/env/unit", "test_escaping.py") { - - - @Override - protected void checkTestResults(@NotNull final PyUnitTestProcessRunner runner, - @NotNull final String stdout, - @NotNull final String stderr, - @NotNull final String all) { - assertEquals(1, runner.getAllTestsCount()); - assertEquals(0, runner.getPassedTestsCount()); - assertEquals(1, runner.getFailedTestsCount()); - } - }); - } - - @Test - public void testClass() { - runPythonTest(new PyUnitTestProcessWithConsoleTestTask("/testRunner/env/unit", - TEST_TARGET_PREFIX + "test_file.GoodTest") { - - @Override - protected void checkTestResults(@NotNull final PyUnitTestProcessRunner runner, - @NotNull final String stdout, - @NotNull final String stderr, - @NotNull final String all) { - assertEquals(1, runner.getAllTestsCount()); - assertEquals(1, runner.getPassedTestsCount()); - } - }); - } - - @Test - public void testMethod() { - runPythonTest(new PyUnitTestProcessWithConsoleTestTask("/testRunner/env/unit", - TEST_TARGET_PREFIX + - "test_file.GoodTest.test_passes") { - - @Override - protected void checkTestResults(@NotNull final PyUnitTestProcessRunner runner, - @NotNull final String stdout, - @NotNull final String stderr, - @NotNull final String all) { - assertEquals(1, runner.getAllTestsCount()); - assertEquals(1, runner.getPassedTestsCount()); - } - }); - } - - @Test - public void testFolder() { - runPythonTest(new PyUnitTestProcessWithConsoleTestTask("/testRunner/env/unit", "test_folder/") { - - @Override - protected void checkTestResults(@NotNull final PyUnitTestProcessRunner runner, - @NotNull final String stdout, - @NotNull final String stderr, - @NotNull final String all) { - assertEquals(5, runner.getAllTestsCount()); - assertEquals(3, runner.getPassedTestsCount()); - } - }); - } - - /** - * Ensures file references are highlighted for python traceback - */ - @Test - public void testUnitTestFileReferences() { - final String fileName = "reference_tests.py"; - runPythonTest(new PyUnitTestProcessWithConsoleTestTask("/testRunner/env/unit", fileName) { - - @Override - protected void checkTestResults(@NotNull final PyUnitTestProcessRunner runner, - @NotNull final String stdout, - @NotNull final String stderr, - @NotNull final String all) { - final List fileNames = runner.getHighlightedStringsInConsole().getSecond(); - Assert.assertTrue(String.format("Not enough highlighted entries(%s) in the following output: %s", - StringUtil.join(fileNames, ","), - runner.getAllConsoleText()), - fileNames.size() >= 3); - // UnitTest highlights file name - Assert.assertThat("Bad line highlighted", fileNames, hasItem(endsWith(fileName))); - } - }); - } - - @Test - public void testDependent() { - runPythonTest(new PyUnitTestProcessWithConsoleTestTask("/testRunner/env/unit", "dependentTests/test_my_class.py") { - - @Override - protected void checkTestResults(@NotNull final PyUnitTestProcessRunner runner, - @NotNull final String stdout, - @NotNull final String stderr, - @NotNull final String all) { - assertEquals(1, runner.getAllTestsCount()); - assertEquals(1, runner.getPassedTestsCount()); - } - }); - } - - @Test - public void testRelativeImports() { - runPythonTest(new PyUnitTestProcessWithConsoleTestTask("/testRunner/env/unit/relativeImports", - PyUnitTestProcessRunner.TEST_PATTERN_PREFIX + "test_imps.py") { - @Override - protected void checkTestResults(@NotNull final PyUnitTestProcessRunner runner, - @NotNull final String stdout, - @NotNull final String stderr, - @NotNull final String all) { - assertEquals(runner.getFormattedTestTree(), 1, runner.getAllTestsCount()); - assertEquals(runner.getFormattedTestTree(), 1, runner.getPassedTestsCount()); - } - }); - } - - /** - * Deletes all files in temp. folder - */ - private static void deleteAllTestFiles(@NotNull final CodeInsightTestFixture fixture) { - ApplicationManager.getApplication().runWriteAction(() -> { - final VirtualFile testRoot = fixture.getTempDirFixture().getFile("."); - assert testRoot != null : "No temp path?"; - try { - for (final VirtualFile child : testRoot.getChildren()) { - child.delete(null); - } - } - catch (final IOException e) { - throw new AssertionError(String.format("Failed to delete files in %s : %s", testRoot, e)); - } - }); + public PyUnitTestProcessWithConsoleTestTask(@NotNull String relativePathToTestData, + @NotNull String scriptName, + int rerunFailedTests) { + super(relativePathToTestData, scriptName, rerunFailedTests, PythonUnitTestingTest.this::createTestRunner); + } } } diff --git a/python/testSrc/com/jetbrains/python/PyControlFlowBuilderTest.java b/python/testSrc/com/jetbrains/python/PyControlFlowBuilderTest.java index eae31579f153..90a46a31a4a5 100644 --- a/python/testSrc/com/jetbrains/python/PyControlFlowBuilderTest.java +++ b/python/testSrc/com/jetbrains/python/PyControlFlowBuilderTest.java @@ -16,21 +16,15 @@ package com.jetbrains.python; import com.intellij.codeInsight.controlflow.ControlFlow; -import com.intellij.codeInsight.controlflow.Instruction; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.testFramework.UsefulTestCase; import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache; import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; import com.jetbrains.python.fixtures.LightMarkedTestCase; -import com.jetbrains.python.fixtures.PyTestCase; import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyFile; import com.jetbrains.python.psi.PyFunction; -import junit.framework.Assert; - -import java.io.IOException; /** * @author oleg @@ -244,6 +238,16 @@ public class PyControlFlowBuilderTest extends LightMarkedTestCase { runWithLanguageLevel(LanguageLevel.PYTHON36, this::doTest); } + // PY-21175 + public void testImplicitNegativeTypeAssertionAfterIf() { + doTest(); + } + + // PY-21175 + public void testImplicitNegativeTypeAssertionAfterTwoNestedIf() { + doTest(); + } + private void doTestFirstStatement() { final String testName = getTestName(false).toLowerCase(); configureByFile(testName + ".py"); @@ -253,18 +257,7 @@ public class PyControlFlowBuilderTest extends LightMarkedTestCase { } private static void check(final String fullPath, final ControlFlow flow) { - final StringBuffer buffer = new StringBuffer(); - final Instruction[] instructions = flow.getInstructions(); - for (Instruction instruction : instructions) { - buffer.append(instruction).append("\n"); - } - final VirtualFile vFile = PyTestCase.getVirtualFileByName(fullPath); - try { - final String fileText = StringUtil.convertLineSeparators(VfsUtil.loadText(vFile), "\n"); - Assert.assertEquals(fileText.trim(), buffer.toString().trim()); - } - catch (IOException e) { - throw new RuntimeException(e); - } + final String actualCFG = StringUtil.join(flow.getInstructions(), Object::toString, "\n"); + UsefulTestCase.assertSameLinesWithFile(fullPath, actualCFG, true); } } diff --git a/python/testSrc/com/jetbrains/python/PyFormatterTest.java b/python/testSrc/com/jetbrains/python/PyFormatterTest.java index ee074e068ba2..8ad3b04247fe 100644 --- a/python/testSrc/com/jetbrains/python/PyFormatterTest.java +++ b/python/testSrc/com/jetbrains/python/PyFormatterTest.java @@ -799,6 +799,47 @@ public class PyFormatterTest extends PyTestCase { doTest(); } + // PY-21931 + public void testSpacesAroundElseInConditionalExpression() { + doTest(); + } + + // PY-20970 + public void testSpacesAfterNonlocal() { + runWithLanguageLevel(LanguageLevel.PYTHON30, this::doTest); + } + + // PY-21515 + public void testSpacesBeforeFromImportSource() { + doTest(); + } + + public void testSpacesAfterFromInYieldFrom() { + runWithLanguageLevel(LanguageLevel.PYTHON33, this::doTest); + } + + // PY-24220 + public void testBlankLinesAfterTopLevelImportsBeforeClass() { + getCommonCodeStyleSettings().BLANK_LINES_AFTER_IMPORTS = 5; + doTest(); + } + + // PY-24220 + public void testBlankLinesAfterTopLevelImportsBeforeClassWithPrecedingComments() { + getCommonCodeStyleSettings().BLANK_LINES_AFTER_IMPORTS = 5; + doTest(); + } + + // PY-25356 + public void testCommentsSpacing() { + doTest(); + } + + // PY-19705 + public void testBlankLinesAroundFirstMethod() { + doTest(); + } + public void testVariableAnnotations() { runWithLanguageLevel(LanguageLevel.PYTHON36, this::doTest); } diff --git a/python/testSrc/com/jetbrains/python/PyMethodOverridingInspectionTest.java b/python/testSrc/com/jetbrains/python/PyMethodOverridingInspectionTest.java index 7d90b9388050..5b55e07676cb 100644 --- a/python/testSrc/com/jetbrains/python/PyMethodOverridingInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/PyMethodOverridingInspectionTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -84,6 +84,11 @@ public class PyMethodOverridingInspectionTest extends PyTestCase { doTest(); } + // PY-23513 + public void testOverriddingAbstractStaticMethodWithExpandedArguments() { + doTest(); + } + private void doTest() { myFixture.configureByFile(TEST_DIRECTORY + getTestName(false) + ".py"); myFixture.enableInspections(PyMethodOverridingInspection.class); diff --git a/python/testSrc/com/jetbrains/python/PyMiscellaneousPsiOperationsTest.java b/python/testSrc/com/jetbrains/python/PyMiscellaneousPsiOperationsTest.java new file mode 100644 index 000000000000..fa869e766d4d --- /dev/null +++ b/python/testSrc/com/jetbrains/python/PyMiscellaneousPsiOperationsTest.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python; + +import com.intellij.psi.util.QualifiedName; +import com.jetbrains.python.fixtures.PyTestCase; +import com.jetbrains.python.psi.LanguageLevel; +import com.jetbrains.python.psi.PyElementGenerator; +import com.jetbrains.python.psi.PyQualifiedExpression; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Mikhail Golubev + */ +public class PyMiscellaneousPsiOperationsTest extends PyTestCase { + + public void testQualifiedNameExtraction() { + checkAsQualifiedNameResult("foo.bar.baz", QualifiedName.fromDottedString("foo.bar.baz")); + checkAsQualifiedNameResult("foo().bar.baz", null); + checkAsQualifiedNameResult("foo.bar[0]", QualifiedName.fromDottedString("foo.bar.__getitem__")); + checkAsQualifiedNameResult("foo[0].bar", null); + checkAsQualifiedNameResult("foo[0][0]", null); + checkAsQualifiedNameResult("-foo.bar", QualifiedName.fromDottedString("foo.bar.__neg__")); + checkAsQualifiedNameResult("foo + bar", QualifiedName.fromDottedString("foo.__add__")); + checkAsQualifiedNameResult("foo + bar + baz", null); + checkAsQualifiedNameResult("foo.bar + baz", QualifiedName.fromDottedString("foo.bar.__add__")); + checkAsQualifiedNameResult("-foo + bar", null); + } + + private void checkAsQualifiedNameResult(@NotNull String expression, @Nullable QualifiedName expectedQualifiedName) { + final PyElementGenerator generator = PyElementGenerator.getInstance(myFixture.getProject()); + final PyQualifiedExpression expr = (PyQualifiedExpression)generator.createExpressionFromText(LanguageLevel.PYTHON27, expression); + assertEquals(expectedQualifiedName, expr.asQualifiedName()); + } +} diff --git a/python/testSrc/com/jetbrains/python/PyStubsTest.java b/python/testSrc/com/jetbrains/python/PyStubsTest.java index 7b6592933a74..698ed521044d 100644 --- a/python/testSrc/com/jetbrains/python/PyStubsTest.java +++ b/python/testSrc/com/jetbrains/python/PyStubsTest.java @@ -606,7 +606,7 @@ public class PyStubsTest extends PyTestCase { final PyType typeFromStub = TypeEvalContext.codeInsightFallback(myFixture.getProject()).getType(attribute); doTestNamedTuple(expectedName, expectedFieldsNames, expectedFieldsTypes, typeFromStub); - //assertNotParsed(file); remove comment after PY-18816 will be fixed + assertNotParsed(file); final FileASTNode astNode = file.getNode(); assertNotNull(astNode); diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index 284a50ef2112..2d807e1c6b75 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -16,11 +16,13 @@ package com.jetbrains.python; import com.google.common.collect.ImmutableList; +import com.jetbrains.python.codeInsight.stdlib.PyNamedTupleType; import com.jetbrains.python.documentation.docstrings.DocStringFormat; import com.jetbrains.python.fixtures.PyTestCase; import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.psi.PyExpression; import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher; +import com.jetbrains.python.psi.types.PyClassLikeType; import com.jetbrains.python.psi.types.PyClassType; import com.jetbrains.python.psi.types.PyType; import com.jetbrains.python.psi.types.TypeEvalContext; @@ -1992,6 +1994,249 @@ public class PyTypeTest extends PyTestCase { " expr = param")); } + // PY-21175 + public void testNoneTypeFilteredOutByConditionalAssignment() { + doTest("List[int]", + "xs = None\n" + + "if xs is None:\n" + + " xs = [1, 2, 3]\n" + + "expr = xs\n"); + } + + // PY-21175 + public void testAnyAddedByConditionalDefinition() { + doTest("Union[str, Any]", + "def f(x, y):\n" + + " if x:\n" + + " var = y\n" + + " else:\n" + + " var = 'foo'\n" + + " expr = var"); + } + + // PY-21626 + public void testNestedConflictingIsNoneChecksInitialAny() { + doTest("Optional[Any]", + "def f(x):\n" + + " if x is None:\n" + + " if x is not None:\n" + + " pass\n" + + " expr = x"); + } + + // PY-21626 + public void testNestedConflictingIsNoneChecksInitialKnown() { + doTest("Optional[str]", + "x = 'foo'\n" + + "if x is None:\n" + + " if x is not None:\n" + + " pass\n" + + "expr = x"); + } + + // PY-21175 + public void testLazyAttributeInitialization() { + doTest("int", + "class C:\n" + + " def __init__(self):\n" + + " self.attr = None\n" + + " \n" + + " def m(self):\n" + + " if self.attr is None:\n" + + " self.attr = 42\n" + + " expr = self.attr"); + } + + // PY-21175 + public void testAssignmentToAttributeOfCallResultWithNameOfLocalVariable() { + doTest("int", + "def f(g):\n" + + " x = 42\n" + + " if True:\n" + + " g().x = 'foo'\n" + + " expr = x"); + } + + public void testTypingNTInheritor() { + runWithLanguageLevel( + LanguageLevel.PYTHON36, + () -> { + final PyExpression definition = parseExpr("from typing import NamedTuple\n" + + "class User(NamedTuple):\n" + + " name: str\n" + + " level: int = 0\n" + + "expr = User"); + + for (TypeEvalContext context : getTypeEvalContexts(definition)) { + final PyType type = context.getType(definition); + assertInstanceOf(type, PyClassType.class); + + final List superClassTypes = ((PyClassType)type).getSuperClassTypes(context); + assertEquals(1, superClassTypes.size()); + + assertInstanceOf(superClassTypes.get(0), PyClassType.class); + } + + final PyExpression instance = parseExpr("from typing import NamedTuple\n" + + "class User(NamedTuple):\n" + + " name: str\n" + + " level: int = 0\n" + + "expr = User(\"name\")"); + + for (TypeEvalContext context : getTypeEvalContexts(instance)) { + final PyType type = context.getType(instance); + assertInstanceOf(type, PyClassType.class); + + final List superClassTypes = ((PyClassType)type).getSuperClassTypes(context); + assertEquals(1, superClassTypes.size()); + + assertInstanceOf(superClassTypes.get(0), PyClassType.class); + } + } + ); + } + + public void testTypingNTTarget() { + runWithLanguageLevel( + LanguageLevel.PYTHON36, + () -> { + final PyExpression definition = parseExpr("from typing import NamedTuple\n" + + "User = NamedTuple(\"User\", name=str, level=int)\n" + + "expr = User"); + + for (TypeEvalContext context : getTypeEvalContexts(definition)) { + assertInstanceOf(context.getType(definition), PyNamedTupleType.class); + } + + final PyExpression instance = parseExpr("from typing import NamedTuple\n" + + "User = NamedTuple(\"User\", name=str, level=int)\n" + + "expr = User(\"name\")"); + + for (TypeEvalContext context : getTypeEvalContexts(instance)) { + assertInstanceOf(context.getType(instance), PyNamedTupleType.class); + } + } + ); + } + + public void testCollectionsNTInheritor() { + runWithLanguageLevel( + LanguageLevel.PYTHON36, + () -> { + final PyExpression definition = parseExpr("from collections import namedtuple\n" + + "class User(namedtuple(\"User\", \"name level\")):\n" + + " pass\n" + + "expr = User"); + + for (TypeEvalContext context : getTypeEvalContexts(definition)) { + final PyType type = context.getType(definition); + assertInstanceOf(type, PyClassType.class); + + final List superClassTypes = ((PyClassType)type).getSuperClassTypes(context); + assertEquals(1, superClassTypes.size()); + + assertInstanceOf(superClassTypes.get(0), PyNamedTupleType.class); + } + + final PyExpression instance = parseExpr("from collections import namedtuple\n" + + "class User(namedtuple(\"User\", \"name level\")):\n" + + " pass\n" + + "expr = User('MrRobot')"); + + for (TypeEvalContext context : getTypeEvalContexts(instance)) { + final PyType type = context.getType(instance); + assertInstanceOf(type, PyClassType.class); + + final List superClassTypes = ((PyClassType)type).getSuperClassTypes(context); + assertEquals(1, superClassTypes.size()); + + assertInstanceOf(superClassTypes.get(0), PyNamedTupleType.class); + } + } + ); + } + + public void testCollectionsNTTarget() { + runWithLanguageLevel( + LanguageLevel.PYTHON36, + () -> { + final PyExpression definition = parseExpr("from collections import namedtuple\n" + + "User = namedtuple(\"User\", \"name level\")\n" + + "expr = User"); + + for (TypeEvalContext context : getTypeEvalContexts(definition)) { + assertInstanceOf(context.getType(definition), PyNamedTupleType.class); + } + + final PyExpression instance = parseExpr("from collections import namedtuple\n" + + "User = namedtuple(\"User\", \"name level\")\n" + + "expr = User('MrRobot')"); + + for (TypeEvalContext context : getTypeEvalContexts(instance)) { + assertInstanceOf(context.getType(instance), PyNamedTupleType.class); + } + } + ); + } + + // PY-25157 + public void testFunctionWithDifferentNamedTuplesAsParameterAndReturnTypes() { + runWithLanguageLevel( + LanguageLevel.PYTHON35, + () -> doTest("(a: MyType1) -> MyType2", + "from collections import namedtuple\n" + + "MyType1 = namedtuple('MyType1', 'x y')\n" + + "MyType2 = namedtuple('MyType2', 'x y')\n" + + "def foo(a: MyType1) -> MyType2:\n" + + " pass\n" + + "expr = foo") + ); + } + + // PY-25346 + public void testTypingNTInheritorField() { + runWithLanguageLevel( + LanguageLevel.PYTHON36, + () -> { + doTest("int", + "from typing import NamedTuple\n" + + "class User(NamedTuple):\n" + + " name: str\n" + + " level: int = 0\n" + + "expr = User(\"name\").level"); + } + ); + } + + // PY-25346 + public void testTypingNTTargetField() { + runWithLanguageLevel( + LanguageLevel.PYTHON36, + () -> { + doTest("int", + "from typing import NamedTuple\n" + + "User = NamedTuple(\"User\", name=str, level=int)\n" + + "expr = User(\"name\").level"); + } + ); + } + + // PY-18791 + public void testCallOnProperty() { + runWithLanguageLevel( + LanguageLevel.PYTHON35, + () -> doTest("Iterator[int]", + "from typing import Iterator, Callable\n" + + "class Foo:\n" + + " def iterate(self) -> Iterator[int]:\n" + + " pass\n" + + " @property\n" + + " def foo(self) -> Callable[[], Iterator[int]]:\n" + + " return self.iterate\n" + + "expr = Foo().foo()") + ); + } + private static List getTypeEvalContexts(@NotNull PyExpression element) { return ImmutableList.of(TypeEvalContext.codeAnalysis(element.getProject(), element.getContainingFile()).withTracing(), TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile()).withTracing()); diff --git a/python/testSrc/com/jetbrains/python/PyTypingTest.java b/python/testSrc/com/jetbrains/python/PyTypingTest.java index fc168974c6ef..d9fc1aba9db3 100644 --- a/python/testSrc/com/jetbrains/python/PyTypingTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypingTest.java @@ -974,6 +974,17 @@ public class PyTypingTest extends PyTestCase { " expr: Alias = g()"); } + // TODO same test for variable type comments + // PY-18816 + public void testLocalTypeAliasInFunctionTypeComment() { + doTest("int", + "def func():\n" + + " Alias = int\n" + + " def g(x):\n" + + " # type: (Alias) -> None\n" + + " expr = x\n"); + } + private void doTestNoInjectedText(@NotNull String text) { myFixture.configureByText(PythonFileType.INSTANCE, text); final InjectedLanguageManager languageManager = InjectedLanguageManager.getInstance(myFixture.getProject()); diff --git a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java index b0d65c4af5a8..18da51cbdcd8 100644 --- a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java +++ b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java @@ -1203,6 +1203,12 @@ public class PythonCompletionTest extends PyTestCase { assertContainsElements(variants, "List", "Union", "Optional"); } + public void testIncompleteQualifiedNameClashesWithLocalVariable() { + final List variants = doTestByFile(); + assertContainsElements(variants, "upper", "split", "__len__"); + assertDoesntContain(variants, "illegal"); + } + @Override protected String getTestDataPath() { return super.getTestDataPath() + "/completion"; diff --git a/python/testSrc/com/jetbrains/python/inspections/PyRedeclarationInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyRedeclarationInspectionTest.java index 259c27d8b845..138fd54dee72 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyRedeclarationInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyRedeclarationInspectionTest.java @@ -104,6 +104,26 @@ public class PyRedeclarationInspectionTest extends PyTestCase { doTest(); } + // PY-23552 + public void testStaticMethodRedeclaresInstanceMethod() { + doTest(); + } + + // PY-23552 + public void testClassMethodRedeclaresInstanceMethod() { + doTest(); + } + + // PY-23552 + public void testStaticMethodRedeclaresAnotherStaticMethod() { + doTest(); + } + + // PY-23552 + public void testClassMethodRedeclaresAnotherClassMethod() { + doTest(); + } + private void doTest() { final String path = "inspections/PyRedeclarationInspection/" + getTestName(true) + ".py"; diff --git a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java index caec7d0948be..93b5f3fe010f 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java @@ -431,4 +431,14 @@ public class PyTypeCheckerInspectionTest extends PyTestCase { public void testDunderInitAnnotatedAsNonNone() { runWithLanguageLevel(LanguageLevel.PYTHON35, this::doTest); } + + // PY-23367 + public void testComparingFloatAndInt() { + doTest(); + } + + // PY-25120 + public void testIterateOverDictValueWhenItsTypeIsUnion() { + doTest(); + } } diff --git a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java index 10a88d2360e7..9d96366f576a 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java @@ -613,6 +613,11 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase { doTest(); } + // PY-25118 + public void testInnerClassAsNamedTupleDefinitionMember() { + runWithLanguageLevel(LanguageLevel.PYTHON36, this::doTest); + } + @NotNull @Override protected Class getInspectionClass() { diff --git a/python/testSrc/com/jetbrains/python/inspections/PyUnusedLocalInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyUnusedLocalInspectionTest.java index 030625d5060e..d1a0fe293e60 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyUnusedLocalInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyUnusedLocalInspectionTest.java @@ -91,6 +91,11 @@ public class PyUnusedLocalInspectionTest extends PyTestCase { doTest(); } + // PY-20655 + public void testCallingLocalsLeadsToUnusedParameter() { + doTest(); + } + private void doTest() { final String path = "inspections/PyUnusedLocalInspection/" + getTestName(true) + ".py"; diff --git a/resources-en/src/inspectionDescriptions/Java9RedundantRequiresStatement.html b/resources-en/src/inspectionDescriptions/Java9RedundantRequiresStatement.html index d453bfbee8ae..22d09bb47e16 100644 --- a/resources-en/src/inspectionDescriptions/Java9RedundantRequiresStatement.html +++ b/resources-en/src/inspectionDescriptions/Java9RedundantRequiresStatement.html @@ -3,8 +3,8 @@ The inspection detects a situation where Java 9 module A requires Java 9 module B, but A doesn't use any package from B. Also, requiring the module 'java.base' is redundant.

      - The fix deletes the 'requires' statement for such redundant dependencies. - If the deleted dependency re-exported modules that are actually used, the fix adds 'requires' statements for these modules. + The fix deletes the 'requires' directive for such redundant dependencies. + If the deleted dependency re-exported modules that are actually used, the fix adds 'requires' directive for these modules.

      New in 2017.1 diff --git a/resources-en/src/inspectionDescriptions/JavaRequiresAutoModule.html b/resources-en/src/inspectionDescriptions/JavaRequiresAutoModule.html index 40f809eb400c..614afb3f445a 100644 --- a/resources-en/src/inspectionDescriptions/JavaRequiresAutoModule.html +++ b/resources-en/src/inspectionDescriptions/JavaRequiresAutoModule.html @@ -1,6 +1,6 @@ -The inspection warns about use of automatic modules in the "requires" clauses. +The inspection warns about use of automatic modules in the "requires" directive. Corresponds to -Xlint:requires-automatic and -Xlint:requires-transitive-automatic Javac options. \ No newline at end of file diff --git a/resources-en/src/messages/QuickFixBundle.properties b/resources-en/src/messages/QuickFixBundle.properties index 35feb3de8ced..244377848d64 100644 --- a/resources-en/src/messages/QuickFixBundle.properties +++ b/resources-en/src/messages/QuickFixBundle.properties @@ -306,8 +306,8 @@ extract.side.effects.convert.to.if=Convert to 'if' statement extract.side.effects=Extract side {0, choice, 1#effect|2#effects} extract.side.effects.family.name=Delete statement extracting side effects -module.info.add.requires.family.name=Add 'requires' statement to module-info.java -module.info.add.requires.name=Add ''requires {0}'' statement to module-info.java +module.info.add.requires.family.name=Add 'requires' directive to module-info.java +module.info.add.requires.name=Add ''requires {0}'' directive to module-info.java collection.to.array.text=Apply conversion ''.toArray({0})'' collection.to.array.family.name=Apply conversion '.toArray()' @@ -319,7 +319,8 @@ wrap.with.java.io.file.text=Wrap using 'new File()' wrap.with.java.io.file.parameter.single.text=Wrap parameter using 'new File()' wrap.with.java.io.file.parameter.multiple.text=Wrap {0, choice, 1#1st|2#2nd|3#3rd|4#{0,number}th} parameter using ''new File()'' -java.9.merge.module.statements.fix.family.name=Merge with other ''{0}'' statement -java.9.merge.module.statements.fix.name=Merge with other ''{0} {1}'' statement +java.9.merge.module.statements.fix.family.name=Merge with other ''{0}'' directive +java.9.merge.module.statements.fix.name=Merge with other ''{0} {1}'' directive + model.create.constructor.quickfix.message=Create constructor ''{0}'' model.create.constructor.quickfix.message.family.name=Create constructor \ No newline at end of file diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index a525032f87f5..2d08d89ebf62 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -241,6 +241,7 @@ + @@ -363,6 +364,7 @@ + @@ -475,6 +477,8 @@ serviceImplementation="com.intellij.ide.util.TreeClassChooserFactoryImpl"/> + + + + diff --git a/slowCheck/src/slowCheck/CheckerSettings.java b/slowCheck/src/slowCheck/CheckerSettings.java deleted file mode 100644 index c6e7c802116a..000000000000 --- a/slowCheck/src/slowCheck/CheckerSettings.java +++ /dev/null @@ -1,28 +0,0 @@ -package slowCheck; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * @author peter - */ -public class CheckerSettings { - public static final CheckerSettings DEFAULT_SETTINGS = new CheckerSettings(100, null); - final int iterationCount; - @Nullable final Long randomSeed; - - private CheckerSettings(int iterationCount, @Nullable Long randomSeed) { - this.iterationCount = iterationCount; - this.randomSeed = randomSeed; - } - - @NotNull - public CheckerSettings withSeed(long randomSeed) { - return new CheckerSettings(iterationCount, randomSeed); - } - - @NotNull - public CheckerSettings withIterationCount(int iterationCount) { - return new CheckerSettings(iterationCount, randomSeed); - } -} diff --git a/slowCheck/src/slowCheck/GeneratorException.java b/slowCheck/src/slowCheck/GeneratorException.java deleted file mode 100644 index 6cc9ea143a50..000000000000 --- a/slowCheck/src/slowCheck/GeneratorException.java +++ /dev/null @@ -1,11 +0,0 @@ -package slowCheck; - -/** - * @author peter - */ -public class GeneratorException extends RuntimeException { - - GeneratorException(long seed, Throwable cause) { - super("Exception while generating data, seed=" + seed, cause); - } -} diff --git a/slowCheck/src/slowCheck/PropertyChecker.java b/slowCheck/src/slowCheck/PropertyChecker.java deleted file mode 100644 index f7824ec804a0..000000000000 --- a/slowCheck/src/slowCheck/PropertyChecker.java +++ /dev/null @@ -1,198 +0,0 @@ -package slowCheck; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.HashSet; -import java.util.Random; -import java.util.Set; -import java.util.function.Predicate; - -/** - * @author peter - */ -public class PropertyChecker { - private static final Predicate DATA_IS_DIFFERENT = new Predicate() { - @Override - public boolean test(Object o) { - return false; - } - - @Override - public String toString() { - return ": cannot generate enough sufficiently different values"; - } - }; - private Generator generator; - private Predicate property; - private final Set generatedHashes = new HashSet<>(); - private final long seed; - private final Random random; - private final StatusNotifier notifier; - - private PropertyChecker(Generator generator, Predicate property, CheckerSettings settings) { - this.generator = generator; - this.property = property; - - Long seed = settings.randomSeed; - if (seed == null) { - seed = new Random().nextLong(); - } - this.seed = seed; - random = new Random(seed); - notifier = new StatusNotifier(settings.iterationCount, this.seed); - } - - public static void forAll(Generator gen, Predicate property) { - forAll(CheckerSettings.DEFAULT_SETTINGS, gen, property); - } - - public static void forAll(CheckerSettings settings, Generator gen, Predicate property) { - new PropertyChecker<>(gen, property, settings).forAll(settings.iterationCount); - } - - private void forAll(int iterationCount) { - for (int i = 1; i <= iterationCount; i++) { - notifier.iterationStarted(i); - - CounterExampleImpl example = findCounterExample(i); - if (example != null) { - notifier.counterExampleFound(); - PropertyFailureImpl failure = new PropertyFailureImpl(example, i); - throw new PropertyFalsified(seed, failure, () -> new ReplayDataStructure(failure.getMinimalCounterexample().data, failure.sizeHint)); - } - } - } - - @Nullable - private CounterExampleImpl findCounterExample(int sizeHint) { - for (int i = 0; i < 100; i++) { - StructureNode node = new StructureNode(); - T value; - try { - value = generator.generateUnstructured(new GenerativeDataStructure(random, node, sizeHint)); - } - catch (Throwable e) { - throw new GeneratorException(seed, e); - } - if (!generatedHashes.add(node.hashCode())) continue; - - return CounterExampleImpl.checkProperty(property, value, node); - } - throw new CannotSatisfyCondition(DATA_IS_DIFFERENT); - } - - private class PropertyFailureImpl implements PropertyFailure { - private final CounterExampleImpl initial; - private CounterExampleImpl minimized; - private int totalSteps; - private int successfulSteps; - private int sizeHint; - private Throwable stoppingReason; - - PropertyFailureImpl(@NotNull CounterExampleImpl initial, int sizeHint) { - this.initial = initial; - this.minimized = initial; - this.sizeHint = sizeHint; - try { - shrink(); - } - catch (Throwable e) { - stoppingReason = e; - } - } - - @NotNull - @Override - public CounterExampleImpl getFirstCounterExample() { - return initial; - } - - @NotNull - @Override - public CounterExampleImpl getMinimalCounterexample() { - return minimized; - } - - @Nullable - @Override - public Throwable getStoppingReason() { - return stoppingReason; - } - - @Override - public int getTotalMinimizationExampleCount() { - return totalSteps; - } - - @Override - public int getMinimizationStageCount() { - return successfulSteps; - } - - private void shrink() { - ShrinkRunner shrinkRunner = new ShrinkRunner(); - while (true) { - CounterExampleImpl shrank = shrinkRunner.findShrink(minimized.data, node -> { - if (!generatedHashes.add(node.hashCode())) return null; - - notifier.shrinkAttempt(this); - - try { - T value = generator.generateUnstructured(new ReplayDataStructure(node, sizeHint)); - totalSteps++; - return CounterExampleImpl.checkProperty(property, value, node); - } - catch (CannotRestoreValue e) { - return null; - } - }); - if (shrank != null) { - minimized = shrank; - successfulSteps++; - } else { - break; - } - } - } - - - } -} - -class CounterExampleImpl implements PropertyFailure.CounterExample { - final StructureNode data; - private final T value; - @Nullable private final Throwable exception; - - private CounterExampleImpl(StructureNode data, T value, @Nullable Throwable exception) { - this.data = data; - this.value = value; - this.exception = exception; - } - - @Override - public T getExampleValue() { - return value; - } - - @Nullable - @Override - public Throwable getExceptionCause() { - return exception; - } - - static CounterExampleImpl checkProperty(Predicate property, T value, StructureNode node) { - try { - if (!property.test(value)) { - return new CounterExampleImpl<>(node, value, null); - } - } - catch (Throwable e) { - return new CounterExampleImpl<>(node, value, e); - } - return null; - } - -} - diff --git a/slowCheck/src/slowCheck/RemoveListRange.java b/slowCheck/src/slowCheck/RemoveListRange.java deleted file mode 100644 index 7ad5d219fa94..000000000000 --- a/slowCheck/src/slowCheck/RemoveListRange.java +++ /dev/null @@ -1,50 +0,0 @@ -package slowCheck; - -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -class RemoveListRange implements Shrink.ElementaryShrink { - private final int start; - private final int end; - - RemoveListRange(int start, int end) { - this.start = start; - this.end = end; - } - - @NotNull - @Override - public StructureElement shrinkNode(@NotNull StructureElement source) { - List original = ((StructureNode)source).children; - - int newSize = original.size() - (end - start) - 1; - - List list = new ArrayList<>(newSize + 1); - list.add(new IntData(newSize, IntDistribution.uniform(0, newSize))); - list.addAll(original.subList(1, start + 1)); - list.addAll(original.subList(end + 1, original.size())); - - return new StructureNode(list); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof RemoveListRange)) return false; - RemoveListRange range = (RemoveListRange)o; - return start == range.start && end == range.end; - } - - @Override - public int hashCode() { - return Objects.hash(start, end); - } - - @Override - public String toString() { - return "RemoveListRange{" + + start + ", " + end + '}'; - } -} diff --git a/slowCheck/src/slowCheck/ShrinkAction.java b/slowCheck/src/slowCheck/ShrinkAction.java deleted file mode 100644 index d486304395c5..000000000000 --- a/slowCheck/src/slowCheck/ShrinkAction.java +++ /dev/null @@ -1,78 +0,0 @@ -package slowCheck; - -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * @author peter - */ -class ShrinkAction { - final StructurePath path; - final Shrink shrink; - - ShrinkAction(@NotNull StructurePath path, @NotNull Shrink shrink) { - this.path = path; - this.shrink = shrink; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ShrinkAction)) return false; - ShrinkAction action = (ShrinkAction)o; - return Objects.equals(path, action.path) && - Objects.equals(shrink, action.shrink); - } - - @Override - public int hashCode() { - return Objects.hash(path, shrink); - } - - @Override - public String toString() { - return "ShrinkAction{" + - "path=" + path + - ", shrink=" + shrink + - '}'; - } -} - -interface Shrink { - - interface ElementaryShrink extends Shrink { - @NotNull - StructureElement shrinkNode(@NotNull StructureElement source); - } - - ShrinkChildren SHRINK_ALL_CHILDREN = new ShrinkChildren(true); - ShrinkChildren SHRINK_LIST_ELEMENTS = new ShrinkChildren(false); - - class ShrinkChildren implements Shrink { - private final boolean myShrinkInts; - - private ShrinkChildren(boolean shrinkInts) { - myShrinkInts = shrinkInts; - } - - @NotNull - public List expandShrinks(@NotNull StructureNode source) { - List result = new ArrayList<>(); - for (int i = 0; i < source.children.size(); i++) { - StructureElement child = source.children.get(i); - if (child instanceof IntData && !myShrinkInts) continue; - - for (Shrink shrink : child.shrink()) { - result.add(new ShrinkAction(new StructurePath.ChildPath(StructurePath.EMPTY, i), shrink)); - } - } - return result; - } - - } - -} - diff --git a/slowCheck/src/slowCheck/ShrinkRunner.java b/slowCheck/src/slowCheck/ShrinkRunner.java deleted file mode 100644 index b9a3621313e9..000000000000 --- a/slowCheck/src/slowCheck/ShrinkRunner.java +++ /dev/null @@ -1,51 +0,0 @@ -package slowCheck; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.*; -import java.util.function.Function; - -/** - * @author peter - */ -class ShrinkRunner { - private final Set unsuccessful = new HashSet<>(); - - @Nullable - T findShrink(@NotNull StructureNode element, Function shrinkProcessor) { - List postponed = new ArrayList<>(); - - LinkedList queue = new LinkedList<>(); - for (Shrink shrink : element.shrink()) { - queue.add(new ShrinkAction(StructurePath.EMPTY, shrink)); - } - - while (true) { - if (queue.isEmpty() && postponed != null) { - queue.addAll(postponed); - postponed = null; - } - - ShrinkAction action = queue.poll(); - if (action == null) return null; - - if (action.shrink instanceof Shrink.ElementaryShrink) { - if (postponed != null && !unsuccessful.add(action)) { - postponed.add(action); - continue; - } - - StructureNode shrank = action.path.applyShrink(element, (Shrink.ElementaryShrink)action.shrink); - T result = shrinkProcessor.apply(shrank); - if (result != null) { - unsuccessful.remove(action); - return result; - } - } else { - queue.addAll(0, action.path.expandShrinks(element, (Shrink.ShrinkChildren)action.shrink)); - } - } - } - -} diff --git a/slowCheck/src/slowCheck/StructureNode.java b/slowCheck/src/slowCheck/StructureNode.java deleted file mode 100644 index baeb67d79b75..000000000000 --- a/slowCheck/src/slowCheck/StructureNode.java +++ /dev/null @@ -1,132 +0,0 @@ -package slowCheck; - -import org.jetbrains.annotations.NotNull; - -import java.util.*; -import java.util.stream.Collectors; - -/** - * @author peter - */ -interface StructureElement { - Collection shrink(); -} - -class StructureNode implements StructureElement { - final List children; - boolean shrinkProhibited; - - StructureNode() { - this(new ArrayList<>()); - } - - StructureNode(List children) { - this.children = children; - } - - Iterator childrenIterator() { - return children.iterator(); - } - - void addChild(StructureElement child) { - children.add(child); - } - - StructureNode subStructure() { - StructureNode e = new StructureNode(); - addChild(e); - return e; - } - - void removeLastChild(StructureNode node) { - if (children.isEmpty() || children.get(children.size() - 1) != node) { - throw new IllegalStateException("Last sub-structure changed"); - } - children.remove(children.size() - 1); - } - - @Override - public List shrink() { - if (shrinkProhibited) return Collections.emptyList(); - - return isList() ? shrinkList(children.size() - 1) : Collections.singletonList(Shrink.SHRINK_ALL_CHILDREN); - } - - private static List shrinkList(int size) { - List result = new ArrayList<>(); - if (size > 4) { - result.add(new RemoveListRange(size / 2, size)); - result.add(new RemoveListRange(0, size / 2)); - } - for (int i = 0; i < size; i++) { - result.add(new RemoveListRange(i, i + 1)); - } - result.add(Shrink.SHRINK_LIST_ELEMENTS); - return result; - } - - private boolean isList() { - if (!children.isEmpty() && - children.get(0) instanceof IntData && ((IntData)children.get(0)).value == children.size() - 1) { - for (int i = 1; i < children.size(); i++) { - if (!(children.get(i) instanceof StructureNode)) return false; - } - return true; - } - return false; - } - - @Override - public int hashCode() { - return children.hashCode(); - } - - @Override - public String toString() { - return "(" + children.stream().map(Object::toString).collect(Collectors.joining(", ")) + ")"; - } - -} - -class IntData implements StructureElement, Shrink.ElementaryShrink { - final int value; - final IntDistribution distribution; - - IntData(int value, IntDistribution distribution) { - this.value = value; - this.distribution = distribution; - } - - @Override - public Collection shrink() { - if (value == 0) return Collections.emptyList(); - - Set builder = new LinkedHashSet<>(); - if (distribution.isValidValue(0)) { - builder.add(new IntData(0, distribution)); - } - if (value < 0 && distribution.isValidValue(-value)) { - builder.add(new IntData(-value, distribution)); - } - if (distribution.isValidValue(value / 2)) { - builder.add(new IntData(value / 2, distribution)); - } - return builder; - } - - @NotNull - @Override - public StructureElement shrinkNode(@NotNull StructureElement source) { - return this; - } - - @Override - public String toString() { - return String.valueOf(value); - } - - @Override - public int hashCode() { - return value; - } -} \ No newline at end of file diff --git a/slowCheck/src/slowCheck/StructurePath.java b/slowCheck/src/slowCheck/StructurePath.java deleted file mode 100644 index ce493bb54a13..000000000000 --- a/slowCheck/src/slowCheck/StructurePath.java +++ /dev/null @@ -1,86 +0,0 @@ -package slowCheck; - -import org.jetbrains.annotations.NotNull; - -import java.util.*; -import java.util.stream.Collectors; - -abstract class StructurePath { - static final StructurePath EMPTY = new StructurePath() {}; - - static class ChildPath extends StructurePath { - final StructurePath parent; - final int childIndex; - - ChildPath(StructurePath parent, int childIndex) { - this.parent = parent; - this.childIndex = childIndex; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ChildPath)) return false; - ChildPath path = (ChildPath)o; - return childIndex == path.childIndex && Objects.equals(parent, path.parent); - } - - @Override - public int hashCode() { - return Objects.hash(parent, childIndex); - } - } - - @Override - public String toString() { - return getIndicesFromRoot().toString(); - } - - List getIndicesFromRoot() { - List result = new ArrayList<>(); - StructurePath each = this; - while (each instanceof ChildPath) { - result.add(((ChildPath)each).childIndex); - each = ((ChildPath)each).parent; - } - Collections.reverse(result); - return result; - } - - @NotNull - StructureNode applyShrink(@NotNull StructureNode source, @NotNull Shrink.ElementaryShrink shrink) { - List indicesFromRoot = getIndicesFromRoot(); - return (StructureNode)new Object() { - private StructureElement expandShrinks(StructureElement source, int depth) { - if (depth == indicesFromRoot.size()) { - return shrink.shrinkNode(source); - } - int index = indicesFromRoot.get(depth); - List children = new ArrayList<>(((StructureNode)source).children); - children.set(index, expandShrinks(children.get(index), depth + 1)); - return new StructureNode(children); - } - }.expandShrinks(source, 0); - } - - private StructureElement findNode(StructureElement element) { - for (Integer index : getIndicesFromRoot()) { - element = ((StructureNode)element).children.get(index); - } - return element; - } - - @NotNull - Collection expandShrinks(@NotNull StructureNode source, @NotNull Shrink.ShrinkChildren shrinkChildren) { - Collection actions = shrinkChildren.expandShrinks((StructureNode)findNode(source)); - return actions.stream().map(a -> new ShrinkAction(prependPath(a.path), a.shrink)).collect(Collectors.toList()); - } - - private StructurePath prependPath(StructurePath path) { - StructurePath result = this; - for (Integer integer : path.getIndicesFromRoot()) { - result = new ChildPath(result, integer); - } - return result; - } -} \ No newline at end of file diff --git a/slowCheck/test/slowCheck/ExceptionTest.java b/slowCheck/test/slowCheck/ExceptionTest.java deleted file mode 100644 index 7e55e749890f..000000000000 --- a/slowCheck/test/slowCheck/ExceptionTest.java +++ /dev/null @@ -1,86 +0,0 @@ -package slowCheck; - -import static slowCheck.Generator.*; - -/** - * @author peter - */ -public class ExceptionTest extends PropertyCheckerTestCase { - - public void testFailureReasonUnchanged() { - try { - PropertyChecker.forAll(ourTestSettings, integers(), i -> { - throw new AssertionError("fail"); - }); - fail(); - } - catch (PropertyFalsified e) { - assertFalse(e.getMessage().contains(PropertyFalsified.FAILURE_REASON_HAS_CHANGED_DURING_MINIMIZATION)); - } - } - - public void testFailureReasonChangedExceptionClass() { - try { - PropertyChecker.forAll(ourTestSettings, integers(), i -> { - throw (i == 0 ? new RuntimeException("fail") : new IllegalArgumentException("fail")); - }); - fail(); - } - catch (PropertyFalsified e) { - assertTrue(e.getMessage().contains(PropertyFalsified.FAILURE_REASON_HAS_CHANGED_DURING_MINIMIZATION)); - } - } - - public void testFailureReasonChangedExceptionTrace() { - try { - PropertyChecker.forAll(ourTestSettings, integers(), i -> { - if (i == 0) { - throw new AssertionError("fail"); - } - else { - throw new AssertionError("fail2"); - } - }); - fail(); - } - catch (PropertyFalsified e) { - assertTrue(e.getMessage().contains(PropertyFalsified.FAILURE_REASON_HAS_CHANGED_DURING_MINIMIZATION)); - } - } - - public void testExceptionWhileGeneratingValue() { - try { - PropertyChecker.forAll(ourTestSettings, from(data -> { - throw new AssertionError("fail"); - }), i -> true); - fail(); - } - catch (GeneratorException ignore) { - } - } - - public void testExceptionWhileShrinkingValue() { - try { - PropertyChecker.forAll(ourTestSettings, listsOf(integers()).suchThat(l -> { - if (l.size() == 1 && l.get(0) == 0) throw new RuntimeException("my exception"); - return true; - }), l -> l.stream().allMatch(i -> i > 0)); - fail(); - } - catch (PropertyFalsified e) { - assertEquals("my exception", e.getFailure().getStoppingReason().getMessage()); - assertTrue(StatusNotifier.printStackTrace(e).contains("my exception")); - } - } - - public void testUnsatisfiableSuchThat() { - try { - PropertyChecker.forAll(integers(-1, 1).suchThat(i -> i > 2), i -> i == 0); - fail(); - } - catch (GeneratorException e) { - assertTrue(e.getCause() instanceof CannotSatisfyCondition); - } - } - -} diff --git a/slowCheck/test/slowCheck/PropertyCheckerTestCase.java b/slowCheck/test/slowCheck/PropertyCheckerTestCase.java deleted file mode 100644 index 75e4f7d35564..000000000000 --- a/slowCheck/test/slowCheck/PropertyCheckerTestCase.java +++ /dev/null @@ -1,32 +0,0 @@ -package slowCheck; - -import junit.framework.TestCase; - -import java.util.function.Predicate; - -/** - * @author peter - */ -abstract class PropertyCheckerTestCase extends TestCase { - protected static final CheckerSettings ourTestSettings = CheckerSettings.DEFAULT_SETTINGS.withSeed(0); - - protected PropertyFailure checkFalsified(Generator generator, Predicate predicate, int minimizationSteps) { - try { - PropertyChecker.forAll(ourTestSettings, generator, predicate); - throw new AssertionError("Can't falsify " + getName()); - } - catch (PropertyFalsified e) { - //noinspection unchecked - PropertyFailure failure = (PropertyFailure)e.getFailure(); - - System.out.println(" " + getName()); - System.out.println("Value: " + e.getBreakingValue()); - System.out.println("Data: " + e.getData()); - assertEquals(minimizationSteps, failure.getTotalMinimizationExampleCount()); - assertEquals(e.getBreakingValue(), generator.generateUnstructured(e.getData())); - - return failure; - } - } - -} diff --git a/spellchecker/src/com/intellij/spellchecker/jetbrains.dic b/spellchecker/src/com/intellij/spellchecker/jetbrains.dic index 4328d3f58cea..64d2cfb12588 100644 --- a/spellchecker/src/com/intellij/spellchecker/jetbrains.dic +++ b/spellchecker/src/com/intellij/spellchecker/jetbrains.dic @@ -216,6 +216,8 @@ geometrycollection getters giud gists +github +gitlab globals google grapheme diff --git a/spellchecker/src/com/intellij/spellchecker/settings/SpellCheckerSettingsPane.java b/spellchecker/src/com/intellij/spellchecker/settings/SpellCheckerSettingsPane.java index 6544c02b3811..0ab3d0163613 100644 --- a/spellchecker/src/com/intellij/spellchecker/settings/SpellCheckerSettingsPane.java +++ b/spellchecker/src/com/intellij/spellchecker/settings/SpellCheckerSettingsPane.java @@ -21,6 +21,7 @@ import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.options.ex.Settings; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; import com.intellij.profile.codeInspection.ui.ErrorsConfigurable; @@ -34,7 +35,6 @@ import com.intellij.ui.AddDeleteListPanel; import com.intellij.ui.HyperlinkLabel; import com.intellij.ui.OptionalChooserComponent; import com.intellij.ui.PathsChooserComponent; -import com.intellij.util.Consumer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -229,7 +229,7 @@ public class SpellCheckerSettingsPane implements Disposable { public void dispose() { if (wordsPanel != null) { - wordsPanel.dispose(); + Disposer.dispose(wordsPanel); } } diff --git a/xml/dom-impl/src/com/intellij/codeInsight/navigation/NavigationGutterIconRenderer.java b/xml/dom-impl/src/com/intellij/codeInsight/navigation/NavigationGutterIconRenderer.java index d3682d34b0a6..9ba538fa626c 100644 --- a/xml/dom-impl/src/com/intellij/codeInsight/navigation/NavigationGutterIconRenderer.java +++ b/xml/dom-impl/src/com/intellij/codeInsight/navigation/NavigationGutterIconRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -31,11 +31,11 @@ import com.intellij.openapi.util.NotNullLazyValue; import com.intellij.psi.PsiElement; import com.intellij.psi.SmartPsiElementPointer; import com.intellij.psi.util.PsiUtilCore; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.awt.RelativePoint; import com.intellij.util.NullableFunction; import com.intellij.util.PsiNavigateUtil; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -119,7 +119,7 @@ public abstract class NavigationGutterIconRenderer extends GutterIconRenderer if (myEmptyText != null) { if (event != null) { final JComponent label = HintUtil.createErrorLabel(myEmptyText); - label.setBorder(IdeBorderFactory.createEmptyBorder(2, 7, 2, 7)); + label.setBorder(JBUI.Borders.empty(2, 7, 2, 7)); JBPopupFactory.getInstance().createBalloonBuilder(label) .setFadeoutTime(3000) .setFillColor(HintUtil.getErrorColor()) diff --git a/xml/impl/src/com/intellij/codeInsight/completion/HtmlCompletionContributor.java b/xml/impl/src/com/intellij/codeInsight/completion/HtmlCompletionContributor.java index 27c1d1eee6f0..043ecefa020a 100644 --- a/xml/impl/src/com/intellij/codeInsight/completion/HtmlCompletionContributor.java +++ b/xml/impl/src/com/intellij/codeInsight/completion/HtmlCompletionContributor.java @@ -17,6 +17,8 @@ package com.intellij.codeInsight.completion; import com.intellij.codeInsight.lookup.LookupElementBuilder; import com.intellij.lang.xhtml.XHTMLLanguage; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.DumbService; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.patterns.XmlPatterns; @@ -43,7 +45,7 @@ import java.util.List; import static com.intellij.html.impl.util.MicrodataUtil.*; import static com.intellij.patterns.PlatformPatterns.psiElement; -public class HtmlCompletionContributor extends CompletionContributor { +public class HtmlCompletionContributor extends CompletionContributor implements DumbAware { public HtmlCompletionContributor() { extend(CompletionType.BASIC, psiElement().inside(XmlPatterns.xmlAttributeValue()), new CompletionProvider() { @Override @@ -96,7 +98,7 @@ public class HtmlCompletionContributor extends CompletionContributor { } else if ("rel".equals(name) || "rev".equals(name)) { return new String[]{"alternate", "author", "bookmark", "help", "icon", "license", "next", "nofollow", - "noreferrer", "prefetch", "prev", "search", "stylesheet", "tag", "start", "contents", "index", + "noreferrer", "noopener", "prefetch", "prev", "search", "stylesheet", "tag", "start", "contents", "index", "glossary", "copyright", "chapter", "section", "subsection", "appendix", "script", "import", "apple-touch-icon", "apple-touch-icon-precomposed", "apple-touch-startup-image"}; } @@ -125,7 +127,7 @@ public class HtmlCompletionContributor extends CompletionContributor { names[i] = charSets[i].toString(); } return names; - } else if ("itemprop".equals(name)) { + } else if ("itemprop".equals(name) && !DumbService.isDumb(attribute.getProject())) { XmlTag scopeTag = findScopeTag(tag); return scopeTag != null ? findItemProperties(scopeTag) : ArrayUtil.EMPTY_STRING_ARRAY; } diff --git a/xml/impl/src/com/intellij/codeInsight/completion/XmlLocationCompletionContributor.java b/xml/impl/src/com/intellij/codeInsight/completion/XmlLocationCompletionContributor.java index 8ad47041d631..cc7b8ceb2679 100644 --- a/xml/impl/src/com/intellij/codeInsight/completion/XmlLocationCompletionContributor.java +++ b/xml/impl/src/com/intellij/codeInsight/completion/XmlLocationCompletionContributor.java @@ -19,16 +19,17 @@ import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementBuilder; import com.intellij.javaee.ExternalResourceManagerEx; import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.util.Condition; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiReference; import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference; import com.intellij.psi.impl.source.resolve.reference.impl.providers.DependentNSReference; import com.intellij.psi.impl.source.resolve.reference.impl.providers.URLReference; -import com.intellij.psi.xml.*; +import com.intellij.psi.xml.XmlAttribute; +import com.intellij.psi.xml.XmlDocument; +import com.intellij.psi.xml.XmlFile; +import com.intellij.psi.xml.XmlTag; import com.intellij.util.ArrayUtil; import com.intellij.util.Function; -import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import com.intellij.xml.XmlNamespaceHelper; @@ -55,10 +56,12 @@ public class XmlLocationCompletionContributor extends CompletionContributor { if (((URLReference)reference).isSchemaLocation()) { Object[] objects = completeSchemaLocation(reference.getElement()); result.addAllElements(ContainerUtil.map(objects, MAPPING)); + if (objects.length > 0) result.stopHere(); return; } Object[] objects = completeNamespace(reference.getElement()); result.addAllElements(ContainerUtil.map(objects, MAPPING)); + if (objects.length > 0) result.stopHere(); return; } if (reference instanceof PsiMultiReference) reference = ((PsiMultiReference)reference).getReferences()[0]; diff --git a/xml/impl/src/com/intellij/codeInsight/daemon/impl/quickfix/IgnoreExtResourceAction.java b/xml/impl/src/com/intellij/codeInsight/daemon/impl/quickfix/IgnoreExtResourceAction.java index 88d3df92e855..3e02cf8f5be8 100644 --- a/xml/impl/src/com/intellij/codeInsight/daemon/impl/quickfix/IgnoreExtResourceAction.java +++ b/xml/impl/src/com/intellij/codeInsight/daemon/impl/quickfix/IgnoreExtResourceAction.java @@ -16,11 +16,12 @@ package com.intellij.codeInsight.daemon.impl.quickfix; import com.intellij.javaee.ExternalResourceManagerEx; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.psi.PsiFile; import com.intellij.openapi.editor.Editor; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * @author mike @@ -31,8 +32,14 @@ public class IgnoreExtResourceAction extends BaseExtResourceAction { return "ignore.external.resource.text"; } + @Nullable + @Override + public PsiElement getElementToMakeWritable(@NotNull PsiFile currentFile) { + return null; + } + @Override protected void doInvoke(@NotNull final PsiFile file, final int offset, @NotNull final String uri, final Editor editor) throws IncorrectOperationException { - ApplicationManager.getApplication().runWriteAction(() -> ExternalResourceManagerEx.getInstanceEx().addIgnoredResource(uri)); + ExternalResourceManagerEx.getInstanceEx().addIgnoredResource(uri); } } diff --git a/xml/impl/src/com/intellij/codeInsight/editorActions/XmlTagNameSynchronizer.java b/xml/impl/src/com/intellij/codeInsight/editorActions/XmlTagNameSynchronizer.java index 42afb44a650f..8ffee9af55cc 100644 --- a/xml/impl/src/com/intellij/codeInsight/editorActions/XmlTagNameSynchronizer.java +++ b/xml/impl/src/com/intellij/codeInsight/editorActions/XmlTagNameSynchronizer.java @@ -299,7 +299,8 @@ public class XmlTagNameSynchronizer implements NamedComponent, CommandListener { final RangeMarker leader = couple.first; final RangeMarker support = couple.second; final String name = document.getText(new TextRange(leader.getStartOffset(), leader.getEndOffset())); - if (!name.equals(document.getText(new TextRange(support.getStartOffset(), support.getEndOffset())))) { + if (document.getTextLength() >= support.getEndOffset() && + !name.equals(document.getText(new TextRange(support.getStartOffset(), support.getEndOffset())))) { document.replaceString(support.getStartOffset(), support.getEndOffset(), name); } } diff --git a/xml/impl/src/com/intellij/codeInsight/template/emmet/EmmetAbbreviationBalloon.java b/xml/impl/src/com/intellij/codeInsight/template/emmet/EmmetAbbreviationBalloon.java index 7ebbbdd6efb6..fa8ce0867808 100644 --- a/xml/impl/src/com/intellij/codeInsight/template/emmet/EmmetAbbreviationBalloon.java +++ b/xml/impl/src/com/intellij/codeInsight/template/emmet/EmmetAbbreviationBalloon.java @@ -91,7 +91,7 @@ public class EmmetAbbreviationBalloon { field.setHistorySize(10); JBLabel label = new JBLabel(AllIcons.General.ContextHelp); - label.setBorder(IdeBorderFactory.createEmptyBorder(0, 3, 0, 1)); + label.setBorder(JBUI.Borders.empty(0, 3, 0, 1)); IdeTooltipManager.getInstance().setCustomTooltip(label, new ContextHelpTooltip(label, myDocumentation)); panel.add(field, BorderLayout.CENTER); diff --git a/xml/impl/src/com/intellij/codeInsight/template/emmet/EmmetParser.java b/xml/impl/src/com/intellij/codeInsight/template/emmet/EmmetParser.java index e793f6e6fda2..0ddb4d95bd86 100644 --- a/xml/impl/src/com/intellij/codeInsight/template/emmet/EmmetParser.java +++ b/xml/impl/src/com/intellij/codeInsight/template/emmet/EmmetParser.java @@ -202,7 +202,7 @@ public abstract class EmmetParser { protected boolean setTemplate(final TemplateToken token, TemplateImpl template) { if (template == null) { - template = myGenerator.createTemplateByKey(token.getKey()); + template = myGenerator.createTemplateByKey(token.getKey(), token.isForceSingleTag()); } if (template == null) { return false; diff --git a/xml/impl/src/com/intellij/codeInsight/template/emmet/EmmetPreviewHint.java b/xml/impl/src/com/intellij/codeInsight/template/emmet/EmmetPreviewHint.java index 327131a129ff..da08637f7106 100644 --- a/xml/impl/src/com/intellij/codeInsight/template/emmet/EmmetPreviewHint.java +++ b/xml/impl/src/com/intellij/codeInsight/template/emmet/EmmetPreviewHint.java @@ -35,7 +35,6 @@ import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; import com.intellij.ui.HintHint; -import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.LightweightHint; import com.intellij.ui.components.JBPanel; import com.intellij.util.Alarm; @@ -159,7 +158,7 @@ public class EmmetPreviewHint extends LightweightHint implements Disposable { settings.setAdditionalPageAtBottom(false); settings.setCaretRowShown(false); previewEditor.setCaretEnabled(false); - previewEditor.setBorder(IdeBorderFactory.createEmptyBorder()); + previewEditor.setBorder(JBUI.Borders.empty()); JBPanel panel = new JBPanel(new BorderLayout()) { @NotNull diff --git a/xml/impl/src/com/intellij/codeInsight/template/emmet/XmlEmmetParser.java b/xml/impl/src/com/intellij/codeInsight/template/emmet/XmlEmmetParser.java index e2dd5d0eec24..78b06c0cab24 100644 --- a/xml/impl/src/com/intellij/codeInsight/template/emmet/XmlEmmetParser.java +++ b/xml/impl/src/com/intellij/codeInsight/template/emmet/XmlEmmetParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -37,7 +37,10 @@ import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -170,17 +173,22 @@ public class XmlEmmetParser extends EmmetParser { return null; } + boolean forceSingleTag = false; TemplateImpl template = myCallback.findApplicableTemplate(templateKey); + if (template == null && StringUtil.endsWithChar(templateKey, '/')) { + forceSingleTag = true; + templateKey = StringUtil.trimEnd(templateKey, '/'); + } if (template == null && !ZenCodingUtil.isXML11ValidQName(templateKey) && !StringUtil.containsChar(templateKey, '$')) { return null; } - final Map attributes = parseSelectors(); + Map attributes = parseSelectors(); if (mustHaveSelector && attributes.isEmpty()) { return null; } - final TemplateToken templateToken = new TemplateToken(templateKey, attributes); + TemplateToken templateToken = new TemplateToken(templateKey, attributes, forceSingleTag); if (!setTemplate(templateToken, template)) { return null; } @@ -310,7 +318,7 @@ public class XmlEmmetParser extends EmmetParser { if (token == ZenCodingTokens.OPENING_SQ_BRACKET) { advance(); final List> attrList = parseAttributeList(); - if (attrList == null || getToken() != ZenCodingTokens.CLOSING_SQ_BRACKET) { + if (getToken() != ZenCodingTokens.CLOSING_SQ_BRACKET) { return null; } advance(); @@ -336,7 +344,7 @@ public class XmlEmmetParser extends EmmetParser { return HtmlUtil.CLASS_ATTRIBUTE_NAME; } - @Nullable + @NotNull private List> parseAttributeList() { final List> result = new ArrayList<>(); while (true) { diff --git a/xml/impl/src/com/intellij/codeInsight/template/emmet/generators/XmlZenCodingGenerator.java b/xml/impl/src/com/intellij/codeInsight/template/emmet/generators/XmlZenCodingGenerator.java index 32544986430f..d7be76da1ad2 100644 --- a/xml/impl/src/com/intellij/codeInsight/template/emmet/generators/XmlZenCodingGenerator.java +++ b/xml/impl/src/com/intellij/codeInsight/template/emmet/generators/XmlZenCodingGenerator.java @@ -54,10 +54,10 @@ public abstract class XmlZenCodingGenerator extends ZenCodingGenerator { } @Override - public TemplateImpl createTemplateByKey(@NotNull String key) { + public TemplateImpl createTemplateByKey(@NotNull String key, boolean forceSingleTag) { StringBuilder builder = new StringBuilder("<"); builder.append(key).append('>'); - if (!HtmlUtil.isSingleHtmlTag(key)) { + if (!forceSingleTag && !HtmlUtil.isSingleHtmlTag(key)) { builder.append("$END$'); } return new TemplateImpl("", builder.toString(), ""); diff --git a/xml/impl/src/com/intellij/codeInsight/template/emmet/generators/ZenCodingGenerator.java b/xml/impl/src/com/intellij/codeInsight/template/emmet/generators/ZenCodingGenerator.java index 2321c94ec6ee..467536caea26 100644 --- a/xml/impl/src/com/intellij/codeInsight/template/emmet/generators/ZenCodingGenerator.java +++ b/xml/impl/src/com/intellij/codeInsight/template/emmet/generators/ZenCodingGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -42,7 +42,7 @@ public abstract class ZenCodingGenerator { public abstract TemplateImpl generateTemplate(@NotNull TemplateToken token, boolean hasChildren, @NotNull PsiElement context); @Nullable - public TemplateImpl createTemplateByKey(@NotNull String key) { + public TemplateImpl createTemplateByKey(@NotNull String key, boolean forceSingleTag) { return null; } diff --git a/xml/impl/src/com/intellij/codeInsight/template/emmet/nodes/TemplateNode.java b/xml/impl/src/com/intellij/codeInsight/template/emmet/nodes/TemplateNode.java index e278643b47ff..e694f70e6960 100644 --- a/xml/impl/src/com/intellij/codeInsight/template/emmet/nodes/TemplateNode.java +++ b/xml/impl/src/com/intellij/codeInsight/template/emmet/nodes/TemplateNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -56,8 +56,8 @@ public class TemplateNode extends ZenCodingNode { String templateKey = templateToken.getKey(); if (myGenerator != null && StringUtil.containsChar(templateKey, '$') && callback.findApplicableTemplate(templateKey) == null) { String newTemplateKey = ZenCodingUtil.replaceMarkers(templateKey, numberInIteration, totalIterations, surroundedText); - TemplateToken newTemplateToken = new TemplateToken(newTemplateKey, templateToken.getAttributes()); - TemplateImpl template = myGenerator.createTemplateByKey(newTemplateKey); + TemplateToken newTemplateToken = new TemplateToken(newTemplateKey, templateToken.getAttributes(), templateToken.isForceSingleTag()); + TemplateImpl template = myGenerator.createTemplateByKey(newTemplateKey, newTemplateToken.isForceSingleTag()); if (template != null) { template.setDeactivated(true); newTemplateToken.setTemplate(template, callback); diff --git a/xml/impl/src/com/intellij/codeInsight/template/emmet/tokens/TemplateToken.java b/xml/impl/src/com/intellij/codeInsight/template/emmet/tokens/TemplateToken.java index 5b52083fa588..ce4d2c758dfd 100644 --- a/xml/impl/src/com/intellij/codeInsight/template/emmet/tokens/TemplateToken.java +++ b/xml/impl/src/com/intellij/codeInsight/template/emmet/tokens/TemplateToken.java @@ -40,15 +40,21 @@ public class TemplateToken extends ZenCodingToken { @NotNull private final String myKey; private TemplateImpl myTemplate; @NotNull private final Map myAttributes; - private PsiFile myFile; + private final boolean myForceSingleTag; + private PsiFile myFile; public TemplateToken(@NotNull String key) { this(key, Collections.emptyMap()); } public TemplateToken(@NotNull String key, @NotNull Map attribute2value) { + this(key, attribute2value, false); + } + + public TemplateToken(@NotNull String key, @NotNull Map attribute2value, boolean forceSingleTag) { myKey = key; myAttributes = attribute2value; + myForceSingleTag = forceSingleTag; } @NotNull @@ -59,7 +65,7 @@ public class TemplateToken extends ZenCodingToken { public PsiFile getFile() { return myFile; } - + public String getTemplateText() { return myFile.getText(); } @@ -67,7 +73,11 @@ public class TemplateToken extends ZenCodingToken { private void setFile(PsiFile file) { myFile = file; } - + + public boolean isForceSingleTag() { + return myForceSingleTag; + } + @Nullable public XmlTag getXmlTag() { return PsiTreeUtil.findChildOfType(myFile, XmlTag.class); diff --git a/xml/impl/src/com/intellij/xml/actions/validate/StdErrorReporter.java b/xml/impl/src/com/intellij/xml/actions/validate/StdErrorReporter.java index 108d5df7d730..6b77912f835e 100644 --- a/xml/impl/src/com/intellij/xml/actions/validate/StdErrorReporter.java +++ b/xml/impl/src/com/intellij/xml/actions/validate/StdErrorReporter.java @@ -21,6 +21,7 @@ import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Key; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.ToolWindowId; @@ -160,7 +161,7 @@ public class StdErrorReporter extends ErrorReporter { myMessageView.getContentManager().removeContentManagerListener(this); NewErrorTreeViewPanel errorTreeView = eventContent.getUserData(KEY); if (errorTreeView != null) { - errorTreeView.dispose(); + Disposer.dispose(errorTreeView); } eventContent.putUserData(KEY, null); } diff --git a/xml/tests/src/com/intellij/codeInsight/daemon/XmlHighlightingTest.java b/xml/tests/src/com/intellij/codeInsight/daemon/XmlHighlightingTest.java index 6140f8848fec..86e0ff63e1bf 100644 --- a/xml/tests/src/com/intellij/codeInsight/daemon/XmlHighlightingTest.java +++ b/xml/tests/src/com/intellij/codeInsight/daemon/XmlHighlightingTest.java @@ -67,6 +67,7 @@ import com.intellij.util.containers.ContainerUtil; import com.intellij.xml.XmlAttributeDescriptor; import com.intellij.xml.XmlBundle; import com.intellij.xml.XmlElementDescriptor; +import com.intellij.xml.actions.validate.ValidateXmlActionHandler; import com.intellij.xml.impl.schema.XmlElementDescriptorImpl; import com.intellij.xml.util.*; import gnu.trove.THashSet; @@ -2108,6 +2109,15 @@ public class XmlHighlightingTest extends DaemonAnalyzerTestCase { doDoTest(true, false); } + public void testMaxOccurLimitValidation() throws Exception { + configureByFiles(null, BASE_PATH + "MaxOccurLimit.xml", BASE_PATH + "MaxOccurLimit.xsd"); + assertTrue(doHighlighting().stream().anyMatch(info -> info.getSeverity() == HighlightSeverity.ERROR)); + + configureByFiles(null, BASE_PATH + "MaxOccurLimit.xml", BASE_PATH + "MaxOccurLimit.xsd"); + System.setProperty(ValidateXmlActionHandler.JDK_XML_MAX_OCCUR_LIMIT, "10000"); + assertFalse(doHighlighting().stream().anyMatch(info -> info.getSeverity() == HighlightSeverity.ERROR)); + } + public void testTheSameElement() throws Exception { doTest( new VirtualFile[] { diff --git a/xml/tests/src/com/intellij/codeInsight/daemon/XmlInspectionsTest.java b/xml/tests/src/com/intellij/codeInsight/daemon/XmlInspectionsTest.java index 726715aa85bb..8a9729f3c430 100644 --- a/xml/tests/src/com/intellij/codeInsight/daemon/XmlInspectionsTest.java +++ b/xml/tests/src/com/intellij/codeInsight/daemon/XmlInspectionsTest.java @@ -57,4 +57,10 @@ public class XmlInspectionsTest extends LightPlatformCodeInsightFixtureTestCase "\n"); myFixture.checkHighlighting(); } + + public void testDefaultAttributeInHtml() { + myFixture.enableInspections(new XmlDefaultAttributeValueInspection()); + myFixture.configureByText(HtmlFileType.INSTANCE, ""); + myFixture.checkHighlighting(); + } } diff --git a/xml/tests/testData/xml/MaxOccurLimit.xml b/xml/tests/testData/xml/MaxOccurLimit.xml new file mode 100644 index 000000000000..75946813de07 --- /dev/null +++ b/xml/tests/testData/xml/MaxOccurLimit.xml @@ -0,0 +1,9 @@ + + + + + TYPE1 + + + diff --git a/xml/tests/testData/xml/MaxOccurLimit.xsd b/xml/tests/testData/xml/MaxOccurLimit.xsd new file mode 100644 index 000000000000..efe8d1dccdae --- /dev/null +++ b/xml/tests/testData/xml/MaxOccurLimit.xsd @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xml/xml-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/XmlDefaultAttributeValueInspection.java b/xml/xml-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/XmlDefaultAttributeValueInspection.java index 1303d84d410a..1834edc70e0c 100644 --- a/xml/xml-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/XmlDefaultAttributeValueInspection.java +++ b/xml/xml-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/XmlDefaultAttributeValueInspection.java @@ -21,6 +21,7 @@ import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; import com.intellij.psi.XmlElementVisitor; +import com.intellij.psi.html.HtmlTag; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlAttributeValue; @@ -40,30 +41,33 @@ public class XmlDefaultAttributeValueInspection extends XmlSuppressableInspectio @Override public void visitXmlAttributeValue(XmlAttributeValue value) { PsiElement parent = value.getParent(); - if (parent instanceof XmlAttribute) { - XmlAttributeDescriptor descriptor = ((XmlAttribute)parent).getDescriptor(); - if (descriptor != null) { - String defaultValue = descriptor.getDefaultValue(); - if (defaultValue != null && defaultValue.equals(value.getValue())) { - holder.registerProblem(value, "Redundant default attribute value assignment", ProblemHighlightType.LIKE_UNUSED_SYMBOL, - new LocalQuickFix() { - @Nls - @NotNull - @Override - public String getFamilyName() { - return XmlErrorMessages.message("remove.attribute.quickfix.family"); - } + if (!(parent instanceof XmlAttribute)) { + return; + } + if (parent.getParent() instanceof HtmlTag && "input".equals(((HtmlTag)parent.getParent()).getName())) return; + XmlAttributeDescriptor descriptor = ((XmlAttribute)parent).getDescriptor(); + if (descriptor == null) { + return; + } + String defaultValue = descriptor.getDefaultValue(); + if (defaultValue != null && defaultValue.equals(value.getValue())) { + holder.registerProblem(value, "Redundant default attribute value assignment", ProblemHighlightType.LIKE_UNUSED_SYMBOL, + new LocalQuickFix() { + @Nls + @NotNull + @Override + public String getFamilyName() { + return XmlErrorMessages.message("remove.attribute.quickfix.family"); + } - @Override - public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { - XmlAttribute attribute = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), XmlAttribute.class); - if (attribute != null) { - attribute.delete(); - } - } - }); - } - } + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + XmlAttribute attribute = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), XmlAttribute.class); + if (attribute != null) { + attribute.delete(); + } + } + }); } } }; diff --git a/xml/xml-analysis-impl/src/com/intellij/codeInspection/htmlInspections/AddCustomHtmlElementIntentionAction.java b/xml/xml-analysis-impl/src/com/intellij/codeInspection/htmlInspections/AddCustomHtmlElementIntentionAction.java index 17da72b30a45..832291546f12 100644 --- a/xml/xml-analysis-impl/src/com/intellij/codeInspection/htmlInspections/AddCustomHtmlElementIntentionAction.java +++ b/xml/xml-analysis-impl/src/com/intellij/codeInspection/htmlInspections/AddCustomHtmlElementIntentionAction.java @@ -56,4 +56,9 @@ public class AddCustomHtmlElementIntentionAction implements LocalQuickFix { InspectionProfile profile = InspectionProjectProfileManager.getInstance(project).getCurrentProfile(); profile.modifyToolSettings(myInspectionKey, element, tool -> tool.addEntry(myName)); } + + @Override + public boolean startInWriteAction() { + return false; + } } diff --git a/xml/xml-analysis-impl/src/com/intellij/codeInspection/htmlInspections/RemoveExtraClosingTagIntentionAction.java b/xml/xml-analysis-impl/src/com/intellij/codeInspection/htmlInspections/RemoveExtraClosingTagIntentionAction.java index 757d090038e9..e1e806b1a108 100644 --- a/xml/xml-analysis-impl/src/com/intellij/codeInspection/htmlInspections/RemoveExtraClosingTagIntentionAction.java +++ b/xml/xml-analysis-impl/src/com/intellij/codeInspection/htmlInspections/RemoveExtraClosingTagIntentionAction.java @@ -34,6 +34,8 @@ import com.intellij.psi.xml.XmlToken; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NotNull; +import java.util.Objects; + /** * @author spleaner */ @@ -53,18 +55,14 @@ public class RemoveExtraClosingTagIntentionAction implements LocalQuickFix, Inte @Override public boolean isAvailable(@NotNull final Project project, final Editor editor, final PsiFile file) { - return true; + PsiElement psiElement = file.findElementAt(editor.getCaretModel().getOffset()); + return psiElement instanceof XmlToken && + (psiElement.getParent() instanceof XmlTag || psiElement.getParent() instanceof PsiErrorElement); } @Override public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException { - final int offset = editor.getCaretModel().getOffset(); - final PsiElement psiElement = file.findElementAt(offset); - if (psiElement == null || !psiElement.isValid() || !(psiElement instanceof XmlToken)) { - return; - } - - doFix(psiElement); + doFix(Objects.requireNonNull(file.findElementAt(editor.getCaretModel().getOffset())).getParent()); } @Override @@ -72,11 +70,7 @@ public class RemoveExtraClosingTagIntentionAction implements LocalQuickFix, Inte return true; } - private static void doFix(@NotNull final PsiElement element) throws IncorrectOperationException { - final XmlToken endNameToken = (XmlToken)element; - final PsiElement tagElement = endNameToken.getParent(); - if (!(tagElement instanceof XmlTag) && !(tagElement instanceof PsiErrorElement)) return; - + private static void doFix(@NotNull PsiElement tagElement) throws IncorrectOperationException { if (tagElement instanceof PsiErrorElement) { tagElement.delete(); } @@ -85,7 +79,7 @@ public class RemoveExtraClosingTagIntentionAction implements LocalQuickFix, Inte if (astNode != null) { final ASTNode endTagStart = XmlChildRole.CLOSING_TAG_START_FINDER.findChild(astNode); if (endTagStart != null) { - final Document document = PsiDocumentManager.getInstance(element.getProject()).getDocument(tagElement.getContainingFile()); + final Document document = PsiDocumentManager.getInstance(tagElement.getProject()).getDocument(tagElement.getContainingFile()); if (document != null) { document.deleteString(endTagStart.getStartOffset(), tagElement.getLastChild().getTextRange().getEndOffset()); } @@ -99,6 +93,6 @@ public class RemoveExtraClosingTagIntentionAction implements LocalQuickFix, Inte final PsiElement element = descriptor.getPsiElement(); if (!(element instanceof XmlToken)) return; - doFix(element); + doFix(element.getParent()); } } diff --git a/xml/xml-psi-impl/src/com/intellij/xml/actions/validate/ValidateXmlActionHandler.java b/xml/xml-psi-impl/src/com/intellij/xml/actions/validate/ValidateXmlActionHandler.java index 0e98e6d82663..663d2817b4e4 100644 --- a/xml/xml-psi-impl/src/com/intellij/xml/actions/validate/ValidateXmlActionHandler.java +++ b/xml/xml-psi-impl/src/com/intellij/xml/actions/validate/ValidateXmlActionHandler.java @@ -32,11 +32,15 @@ import org.apache.xerces.impl.XMLEntityManager; import org.apache.xerces.impl.XercesAccessor; import org.apache.xerces.jaxp.JAXPConstants; import org.apache.xerces.jaxp.SAXParserFactoryImpl; +import org.apache.xerces.util.SecurityManager; import org.apache.xerces.util.XMLGrammarPoolImpl; import org.apache.xerces.xni.grammars.XMLGrammarPool; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.Nullable; -import org.xml.sax.*; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXParseException; import org.xml.sax.helpers.DefaultHandler; import javax.xml.XMLConstants; @@ -47,6 +51,8 @@ import java.io.StringReader; import java.util.Arrays; import java.util.Map; +import static com.sun.org.apache.xerces.internal.impl.Constants.SECURITY_MANAGER; + /** * @author Mike */ @@ -62,6 +68,7 @@ public class ValidateXmlActionHandler { private static final Key DEPENDENT_FILES_KEY = Key.create("GrammarPoolFilesKey"); private static final Key KNOWN_NAMESPACES_KEY = Key.create("KnownNamespacesKey"); private static final Key> ENTITIES_KEY = Key.create("EntityManagerKey"); + public static final String JDK_XML_MAX_OCCUR_LIMIT = "jdk.xml.maxOccurLimit"; private Project myProject; private XmlFile myFile; @@ -240,6 +247,12 @@ public class ValidateXmlActionHandler { } catch (Exception ignore) { } + String property = System.getProperty(JDK_XML_MAX_OCCUR_LIMIT); + if (property != null) { + SecurityManager securityManager = (SecurityManager)parser.getProperty(SECURITY_MANAGER); + securityManager.setMaxOccurNodeLimit(Integer.parseInt(property)); + } + if (schemaChecking) { // when dtd checking schema refs could not be validated @see http://marc.theaimsgroup.com/?l=xerces-j-user&m=112504202423704&w=2 XMLGrammarPool grammarPool = getGrammarPool(myFile, myForceChecking); configureEntityManager(myFile, parser);