diff --git a/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceServiceImpl.java b/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceServiceImpl.java index bba107fcd03f..248ca7b7c154 100644 --- a/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceServiceImpl.java +++ b/java/compiler/impl/src/com/intellij/compiler/backwardRefs/CompilerReferenceServiceImpl.java @@ -178,6 +178,9 @@ public class CompilerReferenceServiceImpl extends CompilerReferenceService imple PsiModificationTracker.MODIFICATION_COUNT, this)); } + catch (ProcessCanceledException e) { + throw e; + } catch (Exception e) { LOG.error("an exception during scope without code references calculation", e); return null; @@ -211,7 +214,6 @@ public class CompilerReferenceServiceImpl extends CompilerReferenceService imple if (!isServiceEnabledFor(aClass) || searchScope == LibraryScopeCache.getInstance(myProject).getLibrariesOnlyScope()) return null; try { - Map candidatesPerFile = ReadAction.compute(() -> { if (myProject.isDisposed()) throw new ProcessCanceledException(); return CachedValuesManager.getCachedValue(aClass, () -> CachedValueProvider.Result.create( @@ -234,6 +236,9 @@ public class CompilerReferenceServiceImpl extends CompilerReferenceService imple } return new CompilerHierarchyInfoImpl(candidatesPerFile, aClass, dirtyScope, searchScope, myProject, searchFileType, searchType); } + catch (ProcessCanceledException e) { + throw e; + } catch (Exception e) { LOG.error("an exception during hierarchy calculation", e); return null; 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 178906b69735..5350cc191dd8 100644 --- a/java/debugger/impl/src/com/intellij/debugger/actions/CustomizeContextViewAction.java +++ b/java/debugger/impl/src/com/intellij/debugger/actions/CustomizeContextViewAction.java @@ -20,14 +20,12 @@ import com.intellij.debugger.engine.JavaDebugProcess; import com.intellij.debugger.settings.JavaDebuggerSettings; import com.intellij.debugger.settings.NodeRendererSettings; import com.intellij.idea.ActionsBundle; -import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.options.TabbedConfigurable; import com.intellij.openapi.options.ex.SingleConfigurableEditor; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Disposer; import com.intellij.util.ui.JBUI; import com.intellij.xdebugger.XDebugSession; import com.intellij.xdebugger.XDebuggerManager; @@ -49,8 +47,7 @@ public class CustomizeContextViewAction extends XDebuggerTreeActionBase { @Override protected void perform(XValueNodeImpl node, @NotNull String nodeName, AnActionEvent e) { final Project project = e.getProject(); - Disposable disposable = Disposer.newDisposable(); - final MyTabbedConfigurable configurable = new MyTabbedConfigurable(disposable); + final MyTabbedConfigurable configurable = new MyTabbedConfigurable(); SingleConfigurableEditor editor = new SingleConfigurableEditor(project, configurable) { @Override protected void doOKAction() { @@ -66,15 +63,10 @@ public class CustomizeContextViewAction extends XDebuggerTreeActionBase { super.doCancelAction(); } }; - Disposer.register(editor.getDisposable(), disposable); editor.show(); } private static class MyTabbedConfigurable extends TabbedConfigurable { - public MyTabbedConfigurable(@NotNull Disposable parent) { - super(parent); - } - @Override protected List createConfigurables() { return JavaDebuggerSettings.createDataViewsConfigurable(); diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/RequestHint.java b/java/debugger/impl/src/com/intellij/debugger/engine/RequestHint.java index fbf01ff46d5e..5c8863562c36 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/RequestHint.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/RequestHint.java @@ -25,7 +25,6 @@ import com.intellij.debugger.SourcePosition; import com.intellij.debugger.engine.evaluation.EvaluateException; import com.intellij.debugger.engine.jdi.StackFrameProxy; import com.intellij.debugger.impl.DebuggerUtilsEx; -import com.intellij.debugger.impl.PositionUtil; import com.intellij.debugger.jdi.StackFrameProxyImpl; import com.intellij.debugger.jdi.ThreadReferenceProxyImpl; import com.intellij.debugger.settings.DebuggerSettings; @@ -156,7 +155,7 @@ public class RequestHint { return myMethodFilter instanceof BreakpointStepMethodFilter || myTargetMethodMatched; } - private boolean isTheSameFrame(SuspendContextImpl context) { + protected boolean isTheSameFrame(SuspendContextImpl context) { if (mySteppedOut) return false; final ThreadReferenceProxyImpl contextThread = context.getThread(); if (contextThread != null) { @@ -188,6 +187,10 @@ public class RequestHint { return STOP; } + protected boolean isSteppedOut() { + return mySteppedOut; + } + public int getNextStepDepth(final SuspendContextImpl context) { try { final StackFrameProxyImpl frameProxy = context.getFrameProxy(); diff --git a/java/debugger/impl/src/com/intellij/debugger/jdi/VirtualMachineProxyImpl.java b/java/debugger/impl/src/com/intellij/debugger/jdi/VirtualMachineProxyImpl.java index b2af9fd6a7e6..73ff7069af3e 100644 --- a/java/debugger/impl/src/com/intellij/debugger/jdi/VirtualMachineProxyImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/jdi/VirtualMachineProxyImpl.java @@ -131,7 +131,17 @@ public class VirtualMachineProxyImpl implements JdiTimer, VirtualMachineProxy { myAllClassesByName = new MultiMap<>(); allClasses().forEach(t -> myAllClassesByName.putValue(t.signature(), t)); } - return (List)myAllClassesByName.get(signature); + Collection res = myAllClassesByName.get(signature); + // TODO: Below is extra logging for IDEA-164040, remove when fixed + if (res.isEmpty()) { + List types = myVirtualMachine.classesByName(s); + if (!types.isEmpty()) { + LOG.error("Obsolete classes cache does not contain " + s + ", while the real vm does"); + clearCaches(); + } + return types; + } + return (List)res; } else { return myVirtualMachine.classesByName(s); diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/BreakpointManager.java b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/BreakpointManager.java index 092538a3d789..c50ad2d05ffb 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/BreakpointManager.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/BreakpointManager.java @@ -245,7 +245,7 @@ public class BreakpointManager { return null; } - @NotNull + @Nullable public ExceptionBreakpoint addExceptionBreakpoint(@NotNull final String exceptionClassName, final String packageName) { ApplicationManager.getApplication().assertIsDispatchThread(); final JavaExceptionBreakpointType type = XDebuggerUtil.getInstance().findBreakpointType(JavaExceptionBreakpointType.class); diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/MethodBreakpoint.java b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/MethodBreakpoint.java index 509e119d1086..edb693357da6 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/MethodBreakpoint.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/MethodBreakpoint.java @@ -47,7 +47,6 @@ import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.JDOMExternalizerUtil; import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.registry.Registry; import com.intellij.psi.*; import com.intellij.util.StringBuilderSpinAllocator; import com.intellij.util.containers.ContainerUtil; @@ -150,14 +149,17 @@ public class MethodBreakpoint extends BreakpointWithHighlighter indicatorRef = new AtomicReference<>(); - ApplicationManager.getApplication().invokeAndWait(() -> indicatorRef.set(new ProgressWindowWithNotification(true, myProject))); + ApplicationManager.getApplication() + .invokeAndWait(() -> indicatorRef.set(new ProgressWindowWithNotification(true, false, myProject, "Cancel emulation"))); ProgressIndicator indicator = indicatorRef.get(); ProgressManager.getInstance().executeProcessUnderProgress( () -> processPreparedSubTypes(baseType, subType -> createRequestForPreparedClassEmulated(debugProcess, subType, false), indicator), indicator); if (indicator.isCanceled()) { - ApplicationManager.getApplication().invokeLater( - () -> DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().removeBreakpoint(this)); + ApplicationManager.getApplication().invokeLater(() -> { + getProperties().EMULATED = false; + fireBreakpointChanged(); + }); } } @@ -493,7 +495,7 @@ public class MethodBreakpoint extends BreakpointWithHighlighter breakpoint) { - myEmulatedCheckBox.setVisible( - Registry.is("debugger.emulate.method.breakpoints") && breakpoint.getType() instanceof JavaMethodBreakpointType); + myEmulatedCheckBox.setVisible(breakpoint.getType() instanceof JavaMethodBreakpointType); myEmulatedCheckBox.setSelected(breakpoint.getProperties().EMULATED); myWatchEntryCheckBox.setSelected(breakpoint.getProperties().WATCH_ENTRY); diff --git a/java/execution/impl/src/com/intellij/execution/junit/JUnitUtil.java b/java/execution/impl/src/com/intellij/execution/junit/JUnitUtil.java index de0c115aa717..31ab17b7598d 100644 --- a/java/execution/impl/src/com/intellij/execution/junit/JUnitUtil.java +++ b/java/execution/impl/src/com/intellij/execution/junit/JUnitUtil.java @@ -107,15 +107,19 @@ public class JUnitUtil { } public static boolean isTestMethod(final Location location, boolean checkAbstract, boolean checkRunWith) { + return isTestMethod(location, checkAbstract, checkRunWith, true); + } + + public static boolean isTestMethod(final Location location, boolean checkAbstract, boolean checkRunWith, boolean checkClass) { final PsiMethod psiMethod = location.getPsiElement(); final PsiClass aClass = location instanceof MethodLocation ? ((MethodLocation)location).getContainingClass() : psiMethod.getContainingClass(); - if (aClass == null || !isTestClass(aClass, checkAbstract, true)) return false; + if (checkClass && (aClass == null || !isTestClass(aClass, checkAbstract, true))) return false; if (isTestAnnotated(psiMethod)) return true; if (psiMethod.isConstructor()) return false; if (!psiMethod.hasModifierProperty(PsiModifier.PUBLIC)) return false; if (psiMethod.hasModifierProperty(PsiModifier.ABSTRACT)) return false; if (AnnotationUtil.isAnnotated(psiMethod, CONFIGURATIONS_ANNOTATION_NAME, false)) return false; - if (checkRunWith) { + if (checkClass && checkRunWith) { PsiAnnotation annotation = AnnotationUtil.findAnnotation(aClass, RUN_WITH); if (annotation != null) { return !isParameterized(annotation); @@ -124,8 +128,11 @@ public class JUnitUtil { if (psiMethod.getParameterList().getParametersCount() > 0) return false; if (psiMethod.hasModifierProperty(PsiModifier.STATIC)) return false; if (!psiMethod.getName().startsWith("test")) return false; - PsiClass testCaseClass = getTestCaseClassOrNull(location); - return testCaseClass != null && psiMethod.getContainingClass().isInheritor(testCaseClass, true) && PsiType.VOID.equals(psiMethod.getReturnType()); + if (checkClass) { + PsiClass testCaseClass = getTestCaseClassOrNull(location); + if (testCaseClass == null || !psiMethod.getContainingClass().isInheritor(testCaseClass, true)) return false; + } + return PsiType.VOID.equals(psiMethod.getReturnType()); } public static boolean isTestCaseInheritor(final PsiClass aClass) { diff --git a/java/execution/impl/src/com/intellij/execution/remote/RemoteConfiguration.java b/java/execution/impl/src/com/intellij/execution/remote/RemoteConfiguration.java index 7054fdb3b2eb..8460555c30bf 100644 --- a/java/execution/impl/src/com/intellij/execution/remote/RemoteConfiguration.java +++ b/java/execution/impl/src/com/intellij/execution/remote/RemoteConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * 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. @@ -43,7 +43,6 @@ import java.util.Collection; public class RemoteConfiguration extends ModuleBasedConfiguration implements RunConfigurationWithSuppressedDefaultRunAction, RemoteRunProfile { - @Override public void writeExternal(final Element element) throws WriteExternalException { super.writeExternal(element); @@ -100,6 +99,4 @@ public class RemoteConfiguration extends ModuleBasedConfiguration getValidModules() { return getAllModules(); } - - } diff --git a/java/idea-ui/src/com/intellij/facet/impl/ui/libraries/LibraryOptionsPanel.java b/java/idea-ui/src/com/intellij/facet/impl/ui/libraries/LibraryOptionsPanel.java index f70ed4717890..daf4a64dfbdb 100644 --- a/java/idea-ui/src/com/intellij/facet/impl/ui/libraries/LibraryOptionsPanel.java +++ b/java/idea-ui/src/com/intellij/facet/impl/ui/libraries/LibraryOptionsPanel.java @@ -26,8 +26,6 @@ import com.intellij.openapi.Disposable; import com.intellij.openapi.application.Result; import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.roots.OrderRootType; @@ -291,7 +289,7 @@ public class LibraryOptionsPanel implements Disposable { }); myConfigureButton.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, () -> doConfigure()); + doConfigure(); } }); updateState(); diff --git a/java/idea-ui/src/com/intellij/framework/addSupport/impl/AddSupportForSingleFrameworkDialog.java b/java/idea-ui/src/com/intellij/framework/addSupport/impl/AddSupportForSingleFrameworkDialog.java index 86775b8a4efb..479944a97345 100644 --- a/java/idea-ui/src/com/intellij/framework/addSupport/impl/AddSupportForSingleFrameworkDialog.java +++ b/java/idea-ui/src/com/intellij/framework/addSupport/impl/AddSupportForSingleFrameworkDialog.java @@ -28,8 +28,6 @@ import com.intellij.ide.util.newProjectWizard.impl.FrameworkSupportModelBase; import com.intellij.openapi.application.Result; import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.ProjectBundle; import com.intellij.openapi.roots.*; import com.intellij.openapi.roots.libraries.Library; @@ -40,7 +38,6 @@ import com.intellij.openapi.roots.ui.configuration.projectRoot.LibrariesContaine import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.Ref; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; @@ -90,10 +87,7 @@ public class AddSupportForSingleFrameworkDialog extends DialogWrapper { } protected void doOKAction() { - final Ref result = Ref.create(false); - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, () -> result.set(addSupport())); - - if (result.get()) { + if (addSupport()) { super.doOKAction(); } } diff --git a/java/idea-ui/src/com/intellij/ide/actions/ImportModuleAction.java b/java/idea-ui/src/com/intellij/ide/actions/ImportModuleAction.java index c4f0f23346fd..a5df73bb538d 100644 --- a/java/idea-ui/src/com/intellij/ide/actions/ImportModuleAction.java +++ b/java/idea-ui/src/com/intellij/ide/actions/ImportModuleAction.java @@ -153,11 +153,6 @@ public class ImportModuleAction extends AnAction { return createImportWizard(project, dialogParent, file, providers); } - /** @deprecated to be removed in IDEA 16 */ - public static String getFileChooserDescription(@Nullable final Project project) { - return getFileChooserDescription(getProviders(project)); - } - private static String getFileChooserDescription(List providers) { StringBuilder builder = new StringBuilder("Select "); boolean first = true; diff --git a/java/idea-ui/src/com/intellij/ide/projectView/actions/CreateLibraryFromFilesDialog.java b/java/idea-ui/src/com/intellij/ide/projectView/actions/CreateLibraryFromFilesDialog.java index a44353bb0760..bd169dee2b13 100644 --- a/java/idea-ui/src/com/intellij/ide/projectView/actions/CreateLibraryFromFilesDialog.java +++ b/java/idea-ui/src/com/intellij/ide/projectView/actions/CreateLibraryFromFilesDialog.java @@ -19,8 +19,6 @@ import com.intellij.application.options.ModulesComboBox; import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtil; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ModifiableRootModel; import com.intellij.openapi.roots.ModuleRootManager; @@ -132,7 +130,7 @@ public class CreateLibraryFromFilesDialog extends DialogWrapper { @Override protected void doOKAction() { - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, () -> addLibrary()); + addLibrary(); super.doOKAction(); } diff --git a/java/idea-ui/src/com/intellij/ide/util/frameworkSupport/AddFrameworkSupportDialog.java b/java/idea-ui/src/com/intellij/ide/util/frameworkSupport/AddFrameworkSupportDialog.java index 055f57985d82..3656e64b199e 100644 --- a/java/idea-ui/src/com/intellij/ide/util/frameworkSupport/AddFrameworkSupportDialog.java +++ b/java/idea-ui/src/com/intellij/ide/util/frameworkSupport/AddFrameworkSupportDialog.java @@ -22,8 +22,6 @@ import com.intellij.ide.util.newProjectWizard.impl.FrameworkSupportModelBase; import com.intellij.openapi.application.Result; import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.ProjectBundle; import com.intellij.openapi.roots.ModifiableRootModel; import com.intellij.openapi.roots.ModuleRootManager; @@ -89,13 +87,13 @@ public class AddFrameworkSupportDialog extends DialogWrapper { if (!myAddSupportPanel.validate()) return; if (!myAddSupportPanel.downloadLibraries(myAddSupportPanel.getMainPanel())) return; - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, () -> new WriteAction() { + new WriteAction() { protected void run(@NotNull final Result result) { ModifiableRootModel model = ModuleRootManager.getInstance(myModule).getModifiableModel(); myAddSupportPanel.addSupport(myModule, model); model.commit(); } - }.execute()); + }.execute(); } super.doOKAction(); } diff --git a/java/idea-ui/src/com/intellij/ide/util/newProjectWizard/AbstractProjectWizard.java b/java/idea-ui/src/com/intellij/ide/util/newProjectWizard/AbstractProjectWizard.java index 6c3d1f817017..806c61ac0573 100644 --- a/java/idea-ui/src/com/intellij/ide/util/newProjectWizard/AbstractProjectWizard.java +++ b/java/idea-ui/src/com/intellij/ide/util/newProjectWizard/AbstractProjectWizard.java @@ -27,14 +27,11 @@ import com.intellij.ide.wizard.StepWithSubSteps; import com.intellij.openapi.Disposable; import com.intellij.openapi.components.StorageScheme; import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.IdeBorderFactory; @@ -164,9 +161,7 @@ public abstract class AbstractProjectWizard extends AbstractWizard result = Ref.create(false); - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, () -> result.set(doFinishAction())); - if (!result.get()) return; + if (!doFinishAction()) return; super.doOKAction(); } diff --git a/java/idea-ui/src/com/intellij/ide/util/newProjectWizard/AddSupportForFrameworksPanel.java b/java/idea-ui/src/com/intellij/ide/util/newProjectWizard/AddSupportForFrameworksPanel.java index edcf6de77804..e1a16121e872 100644 --- a/java/idea-ui/src/com/intellij/ide/util/newProjectWizard/AddSupportForFrameworksPanel.java +++ b/java/idea-ui/src/com/intellij/ide/util/newProjectWizard/AddSupportForFrameworksPanel.java @@ -33,8 +33,6 @@ import com.intellij.ide.util.newProjectWizard.impl.FrameworkSupportModelBase; import com.intellij.openapi.Disposable; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.ProjectBundle; import com.intellij.openapi.roots.IdeaModifiableModelsProvider; import com.intellij.openapi.roots.ModifiableRootModel; @@ -43,7 +41,6 @@ import com.intellij.openapi.roots.ui.configuration.projectRoot.LibrariesContaine import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.Splitter; import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.CheckedTreeNode; import com.intellij.ui.IdeBorderFactory; @@ -402,26 +399,16 @@ public class AddSupportForFrameworksPanel implements Disposable { } public boolean downloadLibraries(@NotNull final JComponent parentComponent) { - final Ref result = Ref.create(true); - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, () -> { - applyLibraryOptionsForSelected(); - List list = getLibrariesCompositionSettingsList(); - for (LibraryCompositionSettings compositionSettings : list) { - if (!compositionSettings.downloadFiles(parentComponent)) { - result.set(false); - return; - } - } - }); - - if (!result.get()) { - int answer = Messages.showYesNoDialog(parentComponent, - ProjectBundle.message("warning.message.some.required.libraries.wasn.t.downloaded"), - CommonBundle.getWarningTitle(), Messages.getWarningIcon()); - if (answer != Messages.YES) { - return false; + applyLibraryOptionsForSelected(); + for (LibraryCompositionSettings compositionSettings : getLibrariesCompositionSettingsList()) { + if (!compositionSettings.downloadFiles(parentComponent)) { + int answer = Messages.showYesNoDialog(parentComponent, + ProjectBundle.message("warning.message.some.required.libraries.wasn.t.downloaded"), + CommonBundle.getWarningTitle(), Messages.getWarningIcon()); + return answer == Messages.YES; } } + return true; } diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ModulesConfigurator.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ModulesConfigurator.java index b251194ce9a0..d1db913acec9 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ModulesConfigurator.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ModulesConfigurator.java @@ -32,8 +32,6 @@ import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.options.ShowSettingsUtil; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectBundle; import com.intellij.openapi.projectRoots.Sdk; @@ -374,24 +372,22 @@ public class ModulesConfigurator implements ModulesProvider, ModuleEditor.Change final ProjectBuilder builder = runModuleWizard(parent, anImport); if (builder != null ) { final List modules = new ArrayList<>(); - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, () -> { - final List committedModules; - if (builder instanceof ProjectImportBuilder) { - final ModifiableArtifactModel artifactModel = - ProjectStructureConfigurable.getInstance(myProject).getArtifactsStructureConfigurable().getModifiableArtifactModel(); - committedModules = ((ProjectImportBuilder)builder).commit(myProject, myModuleModel, this, artifactModel); + final List committedModules; + if (builder instanceof ProjectImportBuilder) { + final ModifiableArtifactModel artifactModel = + ProjectStructureConfigurable.getInstance(myProject).getArtifactsStructureConfigurable().getModifiableArtifactModel(); + committedModules = ((ProjectImportBuilder)builder).commit(myProject, myModuleModel, this, artifactModel); + } + else { + committedModules = builder.commit(myProject, myModuleModel, this); + } + if (committedModules != null) { + modules.addAll(committedModules); + } + ApplicationManager.getApplication().runWriteAction(() -> { + for (Module module : modules) { + getOrCreateModuleEditor(module); } - else { - committedModules = builder.commit(myProject, myModuleModel, this); - } - if (committedModules != null) { - modules.addAll(committedModules); - } - ApplicationManager.getApplication().runWriteAction(() -> { - for (Module module : modules) { - getOrCreateModuleEditor(module); - } - }); }); return modules; } diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/libraries/AddCustomLibraryDialog.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/libraries/AddCustomLibraryDialog.java index 2bbc6fd318c6..be590cc2a7c9 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/libraries/AddCustomLibraryDialog.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/libraries/AddCustomLibraryDialog.java @@ -22,8 +22,6 @@ import com.intellij.ide.IdeBundle; import com.intellij.openapi.application.Result; import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.roots.ModifiableRootModel; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.libraries.Library; @@ -89,25 +87,23 @@ public class AddCustomLibraryDialog extends DialogWrapper { @Override protected void doOKAction() { final LibraryCompositionSettings settings = myPanel.apply(); - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, () -> { - if (settings != null && settings.downloadFiles(myPanel.getMainPanel())) { - if (myModifiableRootModel == null) { - final ModifiableRootModel model = ModuleRootManager.getInstance(myModule).getModifiableModel(); - new WriteAction() { - @Override - protected void run(@NotNull final Result result) { - addLibraries(model, settings); - model.commit(); - } - }.execute(); - } - else { - addLibraries(myModifiableRootModel, settings); - } - + if (settings != null && settings.downloadFiles(myPanel.getMainPanel())) { + if (myModifiableRootModel == null) { + final ModifiableRootModel model = ModuleRootManager.getInstance(myModule).getModifiableModel(); + new WriteAction() { + @Override + protected void run(@NotNull final Result result) { + addLibraries(model, settings); + model.commit(); + } + }.execute(); } - super.doOKAction(); - }); + else { + addLibraries(myModifiableRootModel, settings); + } + + } + super.doOKAction(); } private void addLibraries(ModifiableRootModel model, final LibraryCompositionSettings settings) { diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ChangeLibraryLevelAction.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ChangeLibraryLevelAction.java index af600301c20a..33c2541ecf1e 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ChangeLibraryLevelAction.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ChangeLibraryLevelAction.java @@ -17,15 +17,15 @@ package com.intellij.openapi.roots.ui.configuration.projectRoot; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.impl.libraries.LibraryEx; import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.roots.ui.configuration.LibraryTableModifiableModelProvider; import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable; import com.intellij.openapi.roots.ui.configuration.classpath.ChangeLibraryLevelActionBase; -import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.*; +import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.LibraryProjectStructureElement; +import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureElement; +import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureElementUsage; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -59,20 +59,18 @@ public class ChangeLibraryLevelAction extends ChangeLibraryLevelActionBase { final LibraryEx oldLibrary = (LibraryEx)context.getLibrary(libraryElement.getLibrary().getName(), mySourceConfigurable.getLevel()); LOG.assertTrue(oldLibrary != null); - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, () -> { - final Library newLibrary = doCopy(oldLibrary); - if (newLibrary == null) return; + final Library newLibrary = doCopy(oldLibrary); + if (newLibrary == null) return; - final Collection usages = context.getDaemonAnalyzer().getUsages(libraryElement); - for (ProjectStructureElementUsage usage : usages) { - usage.replaceElement(new LibraryProjectStructureElement(context, newLibrary)); - } + final Collection usages = context.getDaemonAnalyzer().getUsages(libraryElement); + for (ProjectStructureElementUsage usage : usages) { + usage.replaceElement(new LibraryProjectStructureElement(context, newLibrary)); + } - if (!myCopy) { - mySourceConfigurable.removeLibrary(libraryElement); - } - ProjectStructureConfigurable.getInstance(myProject).selectProjectOrGlobalLibrary(newLibrary, true); - }); + if (!myCopy) { + mySourceConfigurable.removeLibrary(libraryElement); + } + ProjectStructureConfigurable.getInstance(myProject).selectProjectOrGlobalLibrary(newLibrary, true); } @Override diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ModuleStructureConfigurable.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ModuleStructureConfigurable.java index 3c14262bf4a0..e57117ffc473 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ModuleStructureConfigurable.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ModuleStructureConfigurable.java @@ -33,7 +33,9 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.module.*; import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.project.*; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectBundle; import com.intellij.openapi.roots.*; import com.intellij.openapi.roots.impl.ClonableOrderEntry; import com.intellij.openapi.roots.impl.ProjectRootManagerImpl; @@ -58,8 +60,6 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager; import com.intellij.openapi.wm.WindowManager; import com.intellij.ui.navigation.Place; -import com.intellij.util.Consumer; -import com.intellij.util.Function; import com.intellij.util.PathUtil; import com.intellij.util.PlatformIcons; import com.intellij.util.containers.ContainerUtil; @@ -907,12 +907,10 @@ public class ModuleStructureConfigurable extends BaseStructureConfigurable imple }; builder.setName(component.getNameValue()); builder.setModuleFilePath(path + "/" + builder.getName() + ModuleFileType.DOT_DEFAULT_EXTENSION); - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, () -> { - final Module module = myContext.myModulesConfigurator.addModule(builder); - if (module != null) { - addModuleNode(module); - } - }); + final Module module = myContext.myModulesConfigurator.addModule(builder); + if (module != null) { + addModuleNode(module); + } } catch (Exception e1) { LOG.error(e1); diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/WrapLongWithMathToIntExactFix.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/WrapLongWithMathToIntExactFix.java index f0a34ce3d953..76c4d6a530bb 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/WrapLongWithMathToIntExactFix.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/WrapLongWithMathToIntExactFix.java @@ -114,7 +114,7 @@ public class WrapLongWithMathToIntExactFix extends LocalQuickFixAndIntentionActi @Nullable @Override protected PsiExpression getModifiedArgument(final PsiExpression expression, final PsiType toType) throws IncorrectOperationException { - return areSameTypes(toType, PsiType.INT) ? (PsiExpression)getModifiedExpression(expression) : null; + return areSameTypes(expression.getType(), PsiType.LONG) && areSameTypes(toType, PsiType.INT) ? (PsiExpression)getModifiedExpression(expression) : null; } @Override diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/unnecessaryModuleDependency/UnnecessaryModuleDependencyInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/unnecessaryModuleDependency/UnnecessaryModuleDependencyInspection.java index f2198709ad8e..e2e7f3f38db8 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/unnecessaryModuleDependency/UnnecessaryModuleDependencyInspection.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/unnecessaryModuleDependency/UnnecessaryModuleDependencyInspection.java @@ -162,6 +162,11 @@ public class UnnecessaryModuleDependencyInspection extends GlobalInspectionTool return "Remove dependency"; } + @Override + public boolean startInWriteAction() { + return false; + } + @Override public void applyFix(@NotNull Project project, @NotNull CommonProblemDescriptor descriptor) { final ModifiableRootModel model = ModuleRootManager.getInstance(myModule).getModifiableModel(); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/unusedLibraries/UnusedLibrariesInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/unusedLibraries/UnusedLibrariesInspection.java index 6147a0d7ea5b..2bd489c5ef30 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/unusedLibraries/UnusedLibrariesInspection.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/unusedLibraries/UnusedLibrariesInspection.java @@ -39,7 +39,6 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiUtilCore; -import com.intellij.util.Function; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -131,6 +130,11 @@ public class UnusedLibrariesInspection extends GlobalInspectionTool { myFiles = files; } + @Override + public boolean startInWriteAction() { + return false; + } + @Override @NotNull public String getFamilyName() { diff --git a/java/java-analysis-impl/src/com/intellij/openapi/roots/impl/JavaLanguageLevelPusher.java b/java/java-analysis-impl/src/com/intellij/openapi/roots/impl/JavaLanguageLevelPusher.java index 71fa0ce43b9b..541701672227 100644 --- a/java/java-analysis-impl/src/com/intellij/openapi/roots/impl/JavaLanguageLevelPusher.java +++ b/java/java-analysis-impl/src/com/intellij/openapi/roots/impl/JavaLanguageLevelPusher.java @@ -107,7 +107,16 @@ public class JavaLanguageLevelPusher implements FilePropertyPusher isJavaLike(f.getFileType())); + // Todo: GwtLanguageLevelPusher changes java language level for single files without firing filePropertiesChanged + // so code below doesn't work. + // Uncomment it and remove older code once the problem is fixed + //PushedFilePropertiesUpdater.getInstance(project).filePropertiesChanged(fileOrDir, f -> isJavaLike(f.getFileType())); + + for (VirtualFile child : fileOrDir.getChildren()) { + if (!child.isDirectory() && isJavaLike(child.getFileType())) { + PushedFilePropertiesUpdater.getInstance(project).filePropertiesChanged(child); + } + } } private static boolean isJavaLike(FileType type) { diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaModuleCompletion.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaModuleCompletion.java index c3a9a344c9a8..0602af3e842e 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaModuleCompletion.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaModuleCompletion.java @@ -88,18 +88,21 @@ class JavaModuleCompletion { } private static void addModuleReferences(PsiElement context, Consumer result) { - PsiJavaModule host = PsiTreeUtil.getParentOfType(context, PsiJavaModule.class); - if (host != null) { - String hostName = host.getModuleName(); - Project project = context.getProject(); - JavaModuleNameIndex index = JavaModuleNameIndex.getInstance(); - GlobalSearchScope scope = ProjectScope.getAllScope(project); - index.processAllKeys(project, name -> { - if (!name.equals(hostName) && index.get(name, project, scope).size() == 1) { - result.consume(new OverrideableSpace(LookupElementBuilder.create(name), TailType.SEMICOLON)); - } - return true; - }); + PsiElement statement = context.getParent(); + if (!(statement instanceof PsiJavaModule)) { + PsiElement host = statement.getParent(); + if (host instanceof PsiJavaModule) { + String hostName = ((PsiJavaModule)host).getModuleName(); + Project project = context.getProject(); + JavaModuleNameIndex index = JavaModuleNameIndex.getInstance(); + GlobalSearchScope scope = ProjectScope.getAllScope(project); + index.processAllKeys(project, name -> { + if (!name.equals(hostName) && index.get(name, project, scope).size() == 1) { + result.consume(new OverrideableSpace(LookupElementBuilder.create(name), TailType.SEMICOLON)); + } + return true; + }); + } } } diff --git a/java/java-impl/src/com/intellij/codeInsight/lookup/LookupItemUtil.java b/java/java-impl/src/com/intellij/codeInsight/lookup/LookupItemUtil.java index 97714e962350..571f76bbb614 100644 --- a/java/java-impl/src/com/intellij/codeInsight/lookup/LookupItemUtil.java +++ b/java/java-impl/src/com/intellij/codeInsight/lookup/LookupItemUtil.java @@ -24,9 +24,6 @@ import com.intellij.psi.*; import com.intellij.psi.meta.PsiMetaData; import com.intellij.psi.util.PsiUtilCore; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; /** * Created by IntelliJ IDEA. @@ -38,28 +35,6 @@ import java.util.Collection; public class LookupItemUtil{ private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.lookup.LookupItemUtil"); - /** - * @deprecated to remove in IDEA 16 - */ - @Nullable - public static LookupElement addLookupItem(Collection set, @NotNull Object object) { - if (object instanceof PsiType) { - PsiType psiType = (PsiType)object; - for (final LookupElement lookupItem : set) { - Object o = lookupItem.getObject(); - if (o.equals(psiType)) { - return lookupItem; - } - } - } - - for (LookupElement lookupItem : set) { - if(lookupItem.getObject().equals(lookupItem)) return null; - } - LookupElement item = objectToLookupItem(object); - return set.add(item) ? item : null; - } - /** * @deprecated * @see LookupElementBuilder diff --git a/java/java-impl/src/com/intellij/codeInsight/unwrap/JavaAnonymousUnwrapper.java b/java/java-impl/src/com/intellij/codeInsight/unwrap/JavaAnonymousUnwrapper.java index 4e0094267fa8..01d636a51306 100644 --- a/java/java-impl/src/com/intellij/codeInsight/unwrap/JavaAnonymousUnwrapper.java +++ b/java/java-impl/src/com/intellij/codeInsight/unwrap/JavaAnonymousUnwrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * 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. @@ -96,7 +96,7 @@ public class JavaAnonymousUnwrapper extends JavaUnwrapper { private static PsiElement findTopmostParentOfType(PsiElement el, Class clazz) { while (true) { @SuppressWarnings({"unchecked"}) - PsiElement temp = PsiTreeUtil.getParentOfType(el, clazz, true, PsiAnonymousClass.class); + PsiElement temp = PsiTreeUtil.getParentOfType(el, clazz, true, PsiAnonymousClass.class, PsiLambdaExpression.class); if (temp == null || temp instanceof PsiFile) return el; el = temp; } diff --git a/java/java-impl/src/com/intellij/codeInsight/unwrap/JavaLambdaUnwrapper.java b/java/java-impl/src/com/intellij/codeInsight/unwrap/JavaLambdaUnwrapper.java index 65007a462b43..d2737cc1d217 100644 --- a/java/java-impl/src/com/intellij/codeInsight/unwrap/JavaLambdaUnwrapper.java +++ b/java/java-impl/src/com/intellij/codeInsight/unwrap/JavaLambdaUnwrapper.java @@ -56,7 +56,7 @@ public class JavaLambdaUnwrapper extends JavaUnwrapper { } else { context.extractElement(body, from); - if (context.myIsEffective) { + if (context.myIsEffective && !(from.getParent() instanceof PsiLambdaExpression)) { PsiStatement emptyStatement = JavaPsiFacade.getElementFactory(from.getProject()).createStatementFromText(";", from); from.getParent().addBefore(emptyStatement, from); } diff --git a/java/java-impl/src/com/intellij/codeInspection/defaultFileTemplateUsage/DefaultFileTemplateUsageInspection.java b/java/java-impl/src/com/intellij/codeInspection/defaultFileTemplateUsage/DefaultFileTemplateUsageInspection.java index ea3c99ac0161..343732352779 100644 --- a/java/java-impl/src/com/intellij/codeInspection/defaultFileTemplateUsage/DefaultFileTemplateUsageInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/defaultFileTemplateUsage/DefaultFileTemplateUsageInspection.java @@ -90,6 +90,11 @@ public class DefaultFileTemplateUsageInspection extends BaseJavaLocalInspectionT return InspectionsBundle.message("default.file.template.edit.template"); } + @Override + public boolean startInWriteAction() { + return false; + } + @Override public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) { final FileTemplateConfigurable configurable = new FileTemplateConfigurable(project); diff --git a/java/java-impl/src/com/intellij/codeInspection/dependencyViolation/DependencyInspection.java b/java/java-impl/src/com/intellij/codeInspection/dependencyViolation/DependencyInspection.java index d317b5ee7d22..93485a5edc20 100644 --- a/java/java-impl/src/com/intellij/codeInspection/dependencyViolation/DependencyInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/dependencyViolation/DependencyInspection.java @@ -79,6 +79,11 @@ public class DependencyInspection extends DependencyInspectionBase { return InspectionsBundle.message("edit.dependency.rules.family"); } + @Override + public boolean startInWriteAction() { + return false; + } + @Override public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { ShowSettingsUtil.getInstance().editConfigurable(project, new DependencyConfigurable(project)); diff --git a/java/java-impl/src/com/intellij/ide/projectView/CompositePsiClasChildrenSource.java b/java/java-impl/src/com/intellij/ide/projectView/CompositePsiClassChildrenSource.java similarity index 74% rename from java/java-impl/src/com/intellij/ide/projectView/CompositePsiClasChildrenSource.java rename to java/java-impl/src/com/intellij/ide/projectView/CompositePsiClassChildrenSource.java index b76aece2c611..a70765a4a36f 100644 --- a/java/java-impl/src/com/intellij/ide/projectView/CompositePsiClasChildrenSource.java +++ b/java/java-impl/src/com/intellij/ide/projectView/CompositePsiClassChildrenSource.java @@ -15,22 +15,27 @@ */ package com.intellij.ide.projectView; +import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import java.util.List; -public class CompositePsiClasChildrenSource implements PsiClassChildrenSource { +class CompositePsiClassChildrenSource implements PsiClassChildrenSource { private final PsiClassChildrenSource[] mySources; - public CompositePsiClasChildrenSource(PsiClassChildrenSource[] sources) { + CompositePsiClassChildrenSource(PsiClassChildrenSource[] sources) { mySources = sources; } @Override public void addChildren(PsiClass psiClass, List children) { for (PsiClassChildrenSource source : mySources) { - source.addChildren(psiClass, children); + try { + source.addChildren(psiClass, children); + } + catch (IndexNotReadyException ignore) { + } } } } diff --git a/java/java-impl/src/com/intellij/ide/projectView/PsiClassChildrenSource.java b/java/java-impl/src/com/intellij/ide/projectView/PsiClassChildrenSource.java index 0466ab29bfec..5111bd60c4bb 100644 --- a/java/java-impl/src/com/intellij/ide/projectView/PsiClassChildrenSource.java +++ b/java/java-impl/src/com/intellij/ide/projectView/PsiClassChildrenSource.java @@ -50,5 +50,5 @@ public interface PsiClassChildrenSource { } }; - PsiClassChildrenSource DEFAULT_CHILDREN = new CompositePsiClasChildrenSource(new PsiClassChildrenSource[]{CLASSES, METHODS, FIELDS}); + PsiClassChildrenSource DEFAULT_CHILDREN = new CompositePsiClassChildrenSource(new PsiClassChildrenSource[]{CLASSES, METHODS, FIELDS}); } diff --git a/java/java-impl/src/com/intellij/openapi/projectRoots/impl/JavaSdkImpl.java b/java/java-impl/src/com/intellij/openapi/projectRoots/impl/JavaSdkImpl.java index 495e30da6429..4d680f17e55e 100644 --- a/java/java-impl/src/com/intellij/openapi/projectRoots/impl/JavaSdkImpl.java +++ b/java/java-impl/src/com/intellij/openapi/projectRoots/impl/JavaSdkImpl.java @@ -374,13 +374,10 @@ public class JavaSdkImpl extends JavaSdk { public void setupSdkPaths(@NotNull Sdk sdk) { String homePath = sdk.getHomePath(); assert homePath != null : sdk; - File jdkHome = new File(homePath); - List classes = findClasses(jdkHome, false); - VirtualFile sources = findSources(jdkHome); - VirtualFile docs = findDocs(jdkHome, "docs/api"); SdkModificator sdkModificator = sdk.getSdkModificator(); + List classes = findClasses(jdkHome, false); Set previousRoots = new LinkedHashSet<>(Arrays.asList(sdkModificator.getRoots(OrderRootType.CLASSES))); sdkModificator.removeRoots(OrderRootType.CLASSES); previousRoots.removeAll(new HashSet<>(classes)); @@ -391,49 +388,8 @@ public class JavaSdkImpl extends JavaSdk { sdkModificator.addRoot(root, OrderRootType.CLASSES); } - if (sources != null) { - sdkModificator.addRoot(sources, OrderRootType.SOURCES); - } - VirtualFile javaFxSources = findSources(jdkHome, "javafx-src"); - if (javaFxSources != null) { - sdkModificator.addRoot(javaFxSources, OrderRootType.SOURCES); - } - - if (docs != null) { - sdkModificator.addRoot(docs, JavadocOrderRootType.getInstance()); - } - else if (SystemInfo.isMac) { - VirtualFile commonDocs = findDocs(jdkHome, "docs"); - if (commonDocs == null) { - commonDocs = findInJar(new File(jdkHome, "docs.jar"), "doc/api"); - if (commonDocs == null) { - commonDocs = findInJar(new File(jdkHome, "docs.jar"), "docs/api"); - } - } - if (commonDocs != null) { - sdkModificator.addRoot(commonDocs, JavadocOrderRootType.getInstance()); - } - - VirtualFile appleDocs = findDocs(jdkHome, "appledocs"); - if (appleDocs == null) { - appleDocs = findInJar(new File(jdkHome, "appledocs.jar"), "appledoc/api"); - } - if (appleDocs != null) { - sdkModificator.addRoot(appleDocs, JavadocOrderRootType.getInstance()); - } - - if (commonDocs == null && appleDocs == null && sources == null) { - String url = getDefaultDocumentationUrl(sdk); - if (url != null) { - sdkModificator.addRoot(VirtualFileManager.getInstance().findFileByUrl(url), JavadocOrderRootType.getInstance()); - } - } - } - else if (getVersion(sdk) == JavaSdkVersion.JDK_1_7) { - VirtualFile url = VirtualFileManager.getInstance().findFileByUrl("http://docs.oracle.com/javafx/2/api/"); - sdkModificator.addRoot(url, JavadocOrderRootType.getInstance()); - } - + addSources(jdkHome, sdkModificator); + addDocs(jdkHome, sdkModificator, sdk); attachJdkAnnotations(sdkModificator); sdkModificator.commitChanges(); @@ -518,7 +474,7 @@ public class JavaSdkImpl extends JavaSdk { File jdkHomeFile = new File(home); addClasses(jdkHomeFile, sdkModificator, isJre); addSources(jdkHomeFile, sdkModificator); - addDocs(jdkHomeFile, sdkModificator); + addDocs(jdkHomeFile, sdkModificator, null); sdkModificator.commitChanges(); return jdk; @@ -529,20 +485,33 @@ public class JavaSdkImpl extends JavaSdk { public Sdk createMockJdk(@NotNull String jdkName, @NotNull String home, boolean isJre) { String homePath = home.replace(File.separatorChar, '/'); File jdkHomeFile = new File(homePath); - List classes = findClasses(jdkHomeFile, isJre); - VirtualFile sources = findSources(jdkHomeFile); - VirtualFile docs = findDocs(jdkHomeFile, "docs/api"); + ProjectRootContainerImpl rootContainer = new ProjectRootContainerImpl(true); + SdkModificator sdkModificator = new SdkModificator() { + @Override public String getName() { throw new UnsupportedOperationException(); } + @Override public void setName(String name) { throw new UnsupportedOperationException(); } + @Override public String getHomePath() { throw new UnsupportedOperationException(); } + @Override public void setHomePath(String path) { throw new UnsupportedOperationException(); } + @Override public String getVersionString() { throw new UnsupportedOperationException(); } + @Override public void setVersionString(String versionString) { throw new UnsupportedOperationException(); } + @Override public SdkAdditionalData getSdkAdditionalData() { throw new UnsupportedOperationException(); } + @Override public void setSdkAdditionalData(SdkAdditionalData data) { throw new UnsupportedOperationException(); } + @Override public VirtualFile[] getRoots(OrderRootType rootType) { throw new UnsupportedOperationException(); } + @Override public void removeRoot(VirtualFile root, OrderRootType rootType) { throw new UnsupportedOperationException(); } + @Override public void removeRoots(OrderRootType rootType) { throw new UnsupportedOperationException(); } + @Override public void removeAllRoots() { throw new UnsupportedOperationException(); } + @Override public void commitChanges() { throw new UnsupportedOperationException(); } + @Override public boolean isWritable() { throw new UnsupportedOperationException(); } + + @Override + public void addRoot(VirtualFile root, OrderRootType rootType) { + rootContainer.addRoot(root, rootType); + } + }; + rootContainer.startChange(); - for (VirtualFile aClass : classes) { - rootContainer.addRoot(aClass, OrderRootType.CLASSES); - } - if (sources != null) { - rootContainer.addRoot(sources, OrderRootType.SOURCES); - } - if (docs != null) { - rootContainer.addRoot(docs, OrderRootType.DOCUMENTATION); - } + addClasses(jdkHomeFile, sdkModificator, isJre); + addSources(jdkHomeFile, sdkModificator); rootContainer.finishChange(); ProjectJdkImpl jdk = new ProjectJdkImpl(jdkName, this, homePath, jdkName) { @@ -625,28 +594,25 @@ public class JavaSdkImpl extends JavaSdk { } @Override - public void addRootSetChangedListener(@NotNull RootSetChangedListener listener) { - } + public void addRootSetChangedListener(@NotNull RootSetChangedListener listener) { } @Override - public void addRootSetChangedListener(@NotNull RootSetChangedListener listener, @NotNull Disposable parentDisposable) { - } + public void addRootSetChangedListener(@NotNull RootSetChangedListener listener, @NotNull Disposable parentDisposable) { } @Override - public void removeRootSetChangedListener(@NotNull RootSetChangedListener listener) { - } + public void removeRootSetChangedListener(@NotNull RootSetChangedListener listener) { } }; } + + private void throwReadOnly() { + throw new IncorrectOperationException("Can't modify, MockJDK is read-only, consider calling .clone() first"); + } }; ProjectJdkImpl.copyRoots(rootContainer, jdk); return jdk; } - private static void throwReadOnly() { - throw new IncorrectOperationException("Can't modify, MockJDK is read-only, consider calling .clone() first"); - } - private static void addClasses(File file, SdkModificator sdkModificator, boolean isJre) { for (VirtualFile virtualFile : findClasses(file, isJre)) { sdkModificator.addRoot(virtualFile, OrderRootType.CLASSES); @@ -658,8 +624,7 @@ public class JavaSdkImpl extends JavaSdk { List result = ContainerUtil.newArrayList(); VirtualFileManager fileManager = VirtualFileManager.getInstance(); - VirtualFile jrt = fileManager.findFileByUrl( - VirtualFileManager.constructUrl(JrtFileSystem.PROTOCOL, FileUtil.toSystemIndependentName(file.getPath()) + JrtFileSystem.SEPARATOR)); + VirtualFile jrt = fileManager.findFileByUrl(JrtFileSystem.PROTOCOL_PREFIX + getPath(file) + JrtFileSystem.SEPARATOR); if (jrt != null) { ContainerUtil.addAll(result, jrt.getChildren()); } @@ -674,61 +639,91 @@ public class JavaSdkImpl extends JavaSdk { return result; } - private static void addSources(@NotNull File file, @NotNull SdkModificator sdkModificator) { - VirtualFile vFile = findSources(file); - if (vFile != null) { - sdkModificator.addRoot(vFile, OrderRootType.SOURCES); + private static void addSources(@NotNull File jdkHome, @NotNull SdkModificator sdkModificator) { + VirtualFile jdkSrc = findSources(jdkHome, "src"); + if (jdkSrc != null) { + sdkModificator.addRoot(jdkSrc, OrderRootType.SOURCES); + } + + VirtualFile fxSrc = findSources(jdkHome, "javafx-src"); + if (fxSrc != null) { + sdkModificator.addRoot(fxSrc, OrderRootType.SOURCES); } } @Nullable - private static VirtualFile findSources(File file) { - return findSources(file, "src"); - } - - @Nullable - private static VirtualFile findSources(File file, final String srcName) { - File jarFile = new File(file, srcName + ".jar"); - if (!jarFile.exists()) { - jarFile = new File(file, srcName + ".zip"); - } - - if (jarFile.exists()) { - VirtualFile vFile = findInJar(jarFile, "src"); - if (vFile != null) return vFile; - // try 1.4 format - vFile = findInJar(jarFile, ""); + private static VirtualFile findSources(File jdkHome, String srcName) { + File srcArc = new File(jdkHome, srcName + ".jar"); + if (!srcArc.exists()) srcArc = new File(jdkHome, srcName + ".zip"); + if (srcArc.exists()) { + VirtualFile vFile = findInJar(srcArc, "src"); + if (vFile == null) vFile = findInJar(srcArc, ""); return vFile; } - else { - File srcDir = new File(file, "src"); - if (!srcDir.exists() || !srcDir.isDirectory()) return null; - String path = srcDir.getAbsolutePath().replace(File.separatorChar, '/'); - return LocalFileSystem.getInstance().findFileByPath(path); + + File srcDir = new File(jdkHome, "src"); + if (srcDir.isDirectory()) { + return LocalFileSystem.getInstance().findFileByPath(getPath(srcDir)); } + + return null; } - private static void addDocs(File file, SdkModificator rootContainer) { - VirtualFile vFile = findDocs(file, "docs/api"); - if (vFile != null) { - rootContainer.addRoot(vFile, JavadocOrderRootType.getInstance()); + private void addDocs(File jdkHome, SdkModificator sdkModificator, @Nullable Sdk sdk) { + OrderRootType docRootType = JavadocOrderRootType.getInstance(); + + VirtualFile apiDocs = findDocs(jdkHome, "docs/api"); + if (apiDocs != null) { + sdkModificator.addRoot(apiDocs, docRootType); + } + else if (SystemInfo.isMac) { + VirtualFile commonDocs = findDocs(jdkHome, "docs"); + if (commonDocs == null) commonDocs = findInJar(new File(jdkHome, "docs.jar"), "doc/api"); + if (commonDocs == null) commonDocs = findInJar(new File(jdkHome, "docs.jar"), "docs/api"); + if (commonDocs != null) { + sdkModificator.addRoot(commonDocs, docRootType); + } + + VirtualFile appleDocs = findDocs(jdkHome, "appledocs"); + if (appleDocs == null) appleDocs = findInJar(new File(jdkHome, "appledocs.jar"), "appledoc/api"); + if (appleDocs != null) { + sdkModificator.addRoot(appleDocs, docRootType); + } + } + + if (sdk != null && sdkModificator.getRoots(docRootType).length == 0 && sdkModificator.getRoots(OrderRootType.SOURCES).length == 0) { + // registers external docs when both sources and local docs are missing + String docUrl = getDefaultDocumentationUrl(sdk); + if (docUrl != null) { + VirtualFile onlineDoc = VirtualFileManager.getInstance().findFileByUrl(docUrl); + if (onlineDoc != null) { + sdkModificator.addRoot(onlineDoc, docRootType); + } + } + + if (getVersion(sdk) == JavaSdkVersion.JDK_1_7) { + VirtualFile fxDocUrl = VirtualFileManager.getInstance().findFileByUrl("http://docs.oracle.com/javafx/2/api/"); + if (fxDocUrl != null) { + sdkModificator.addRoot(fxDocUrl, docRootType); + } + } } } @Nullable + private static VirtualFile findDocs(@NotNull File jdkHome, @NotNull String relativePath) { + File docDir = new File(jdkHome.getAbsolutePath(), relativePath); + return docDir.isDirectory() ? LocalFileSystem.getInstance().findFileByPath(getPath(docDir)) : null; + } + private static VirtualFile findInJar(File jarFile, String relativePath) { if (!jarFile.exists()) return null; - String url = JarFileSystem.PROTOCOL_PREFIX + - jarFile.getAbsolutePath().replace(File.separatorChar, '/') + JarFileSystem.JAR_SEPARATOR + relativePath; + String url = JarFileSystem.PROTOCOL_PREFIX + getPath(jarFile) + JarFileSystem.JAR_SEPARATOR + relativePath; return VirtualFileManager.getInstance().findFileByUrl(url); } - @Nullable - private static VirtualFile findDocs(@NotNull File file, @NotNull String relativePath) { - file = new File(file.getAbsolutePath() + File.separator + relativePath.replace('/', File.separatorChar)); - if (!file.exists() || !file.isDirectory()) return null; - String path = file.getAbsolutePath().replace(File.separatorChar, '/'); - return LocalFileSystem.getInstance().findFileByPath(path); + private static String getPath(File jarFile) { + return jarFile.getAbsolutePath().replace(File.separatorChar, '/'); } @Override diff --git a/java/java-impl/src/com/intellij/psi/formatter/java/MethodCallExpressionBlock.java b/java/java-impl/src/com/intellij/psi/formatter/java/MethodCallExpressionBlock.java deleted file mode 100644 index 502a42893106..000000000000 --- a/java/java-impl/src/com/intellij/psi/formatter/java/MethodCallExpressionBlock.java +++ /dev/null @@ -1,43 +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.psi.formatter.java; - -import com.intellij.lang.ASTNode; -import com.intellij.formatting.Wrap; -import com.intellij.formatting.Alignment; -import com.intellij.formatting.Indent; -import com.intellij.formatting.Block; -import com.intellij.psi.codeStyle.CodeStyleSettings; -import com.intellij.psi.codeStyle.JavaCodeStyleSettings; - -import java.util.List; - -public class MethodCallExpressionBlock extends AbstractJavaBlock{ - public MethodCallExpressionBlock(ASTNode node, - Wrap wrap, - Alignment alignment, - Indent indent, - CodeStyleSettings settings, - JavaCodeStyleSettings javaSettings) - { - super(node, wrap, alignment, indent, settings, javaSettings); - } - - @Override - protected List buildChildren() { - return null; - } -} diff --git a/java/java-impl/src/com/intellij/psi/impl/JavaCodeBlockModificationListener.java b/java/java-impl/src/com/intellij/psi/impl/JavaCodeBlockModificationListener.java index b99d4a319e36..401252b8bfb6 100644 --- a/java/java-impl/src/com/intellij/psi/impl/JavaCodeBlockModificationListener.java +++ b/java/java-impl/src/com/intellij/psi/impl/JavaCodeBlockModificationListener.java @@ -113,24 +113,18 @@ public class JavaCodeBlockModificationListener implements PsiTreeChangePreproces return false; } - private static boolean isInsideCodeBlock(PsiElement element) { - if (element instanceof PsiFileSystemItem) { - return false; - } - + private static boolean isInsideCodeBlock(final PsiElement element) { if (element == null || element.getParent() == null) return true; PsiElement parent = element; while (true) { - if (parent instanceof PsiFile || parent instanceof PsiDirectory || parent == null) { - return false; - } - if (parent instanceof PsiClass) return false; // anonymous or local class if (parent instanceof PsiModifiableCodeBlock) { if (!((PsiModifiableCodeBlock)parent).shouldChangeModificationCount(element)) { return true; } } + if (parent == null || parent instanceof PsiFileSystemItem) return false; + if (parent instanceof PsiClass) return false; // anonymous or local class parent = parent.getParent(); } } diff --git a/java/java-impl/src/com/intellij/refactoring/changeSignature/JavaChangeSignatureDetector.java b/java/java-impl/src/com/intellij/refactoring/changeSignature/JavaChangeSignatureDetector.java index 8eb0542cbabd..6b2a70b667c2 100644 --- a/java/java-impl/src/com/intellij/refactoring/changeSignature/JavaChangeSignatureDetector.java +++ b/java/java-impl/src/com/intellij/refactoring/changeSignature/JavaChangeSignatureDetector.java @@ -100,15 +100,23 @@ public class JavaChangeSignatureDetector implements LanguageChangeSignatureDetec buf.append(", "); } + String oldParamName = initialChangeInfo.getOldParameterNames()[i]; + String oldParamType = initialChangeInfo.getOldParameterTypes()[i]; if (toRemove[i]) { - String deletedParam = initialChangeInfo.getOldParameterTypes()[i] + " " + initialChangeInfo.getOldParameterNames()[i]; + String deletedParam = oldParamType + " " + oldParamName; deleteRanges.add(new TextRange(buf.length(), buf.length() + deletedParam.length())); buf.append(deletedParam); } else { for (JavaParameterInfo parameter : newParameters) { if (parameter.getOldIndex() == i) { - buf.append(parameter.getTypeText()).append(" ").append(parameter.getName()); + buf.append(parameter.getTypeText()); + buf.append(" "); + if (!oldParamName.equals(parameter.getName())) { + deleteRanges.add(new TextRange(buf.length(), buf.length() + oldParamName.length())); + buf.append(oldParamName); + } + buf.append(parameter.getName()); break; } } diff --git a/java/java-impl/src/com/intellij/refactoring/changeSignature/JavaChangeSignatureUsageProcessor.java b/java/java-impl/src/com/intellij/refactoring/changeSignature/JavaChangeSignatureUsageProcessor.java index 1d8804395404..8a7d6304ffbb 100644 --- a/java/java-impl/src/com/intellij/refactoring/changeSignature/JavaChangeSignatureUsageProcessor.java +++ b/java/java-impl/src/com/intellij/refactoring/changeSignature/JavaChangeSignatureUsageProcessor.java @@ -827,11 +827,12 @@ public class JavaChangeSignatureUsageProcessor implements ChangeSignatureUsagePr parameter.getNameIdentifier().replace(newIdentifier); } - final PsiTypeElement typeElement = parameter.getTypeElement(); + PsiTypeElement typeElement = parameter.getTypeElement(); if (typeElement != null) { + parameter.normalizeDeclaration(); + typeElement = parameter.getTypeElement(); String oldType = oldParameterTypes[index]; if (!oldType.equals(info.getTypeText())) { - parameter.normalizeDeclaration(); PsiType newType = substitutor.substitute(info.createType(changeInfo.getMethod().getParameterList(), changeInfo.getMethod().getManager())); typeElement.replace(factory.createTypeElement(newType)); diff --git a/java/java-impl/src/com/intellij/refactoring/copy/CopyClassesHandler.java b/java/java-impl/src/com/intellij/refactoring/copy/CopyClassesHandler.java index b2d8a64b1ef2..6e196eb5d971 100644 --- a/java/java-impl/src/com/intellij/refactoring/copy/CopyClassesHandler.java +++ b/java/java-impl/src/com/intellij/refactoring/copy/CopyClassesHandler.java @@ -365,7 +365,7 @@ public class CopyClassesHandler extends CopyHandlerDelegateBase { if (relativePath != null && !relativePath.isEmpty()) { finalTarget = WriteAction.compute(() -> buildRelativeDir(targetDirectory, relativePath).findOrCreateTargetDirectory()); } - final PsiFile fileCopy = CopyFilesOrDirectoriesHandler.copyToDirectory(file, getNewFileName(file, copyClassName), finalTarget, choice); + final PsiFile fileCopy = CopyFilesOrDirectoriesHandler.copyToDirectory(file, getNewFileName(file, copyClassName), finalTarget, choice, null); if (fileCopy != null) { createdFiles.add(fileCopy); } diff --git a/java/java-impl/src/com/intellij/refactoring/extractMethod/ExtractMethodProcessor.java b/java/java-impl/src/com/intellij/refactoring/extractMethod/ExtractMethodProcessor.java index 5629ba9a2ad9..a82b31f8615c 100644 --- a/java/java-impl/src/com/intellij/refactoring/extractMethod/ExtractMethodProcessor.java +++ b/java/java-impl/src/com/intellij/refactoring/extractMethod/ExtractMethodProcessor.java @@ -41,7 +41,10 @@ import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.*; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.Pass; +import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.WindowManager; @@ -67,7 +70,9 @@ import com.intellij.refactoring.introduceVariable.IntroduceVariableBase; import com.intellij.refactoring.util.*; import com.intellij.refactoring.util.classMembers.ElementNeedsThis; import com.intellij.refactoring.util.duplicates.*; -import com.intellij.util.*; +import com.intellij.util.ArrayUtil; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.VisibilityUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NonNls; @@ -1462,6 +1467,9 @@ public class ExtractMethodProcessor implements MatchProvider { myTargetClass = myCodeFragmentMember instanceof PsiMember ? ((PsiMember)myCodeFragmentMember).getContainingClass() : PsiTreeUtil.getParentOfType(myCodeFragmentMember, PsiClass.class); + if (myTargetClass == null) { + LOG.error(myElements[0].getContainingFile()); + } if (!shouldAcceptCurrentTarget(extractPass, myTargetClass)) { final LinkedHashMap> classes = new LinkedHashMap<>(); diff --git a/java/java-impl/src/com/intellij/refactoring/extractclass/ExtractClassHandler.java b/java/java-impl/src/com/intellij/refactoring/extractclass/ExtractClassHandler.java index 6a6146434d54..812bd6466404 100644 --- a/java/java-impl/src/com/intellij/refactoring/extractclass/ExtractClassHandler.java +++ b/java/java-impl/src/com/intellij/refactoring/extractclass/ExtractClassHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * 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. @@ -88,6 +88,9 @@ public class ExtractClassHandler implements ElementsHandler { if (classIsTrivial(containingClass)) { return RefactorJBundle.message("the.selected.class.has.no.members.to.extract"); } + if (!containingClass.getManager().isInProject(containingClass)) { + return "The selected class should belong to project sources"; + } return null; } diff --git a/java/java-impl/src/com/intellij/refactoring/extractclass/ExtractedClassBuilder.java b/java/java-impl/src/com/intellij/refactoring/extractclass/ExtractedClassBuilder.java index 721e0127a806..b77198bdcfcb 100644 --- a/java/java-impl/src/com/intellij/refactoring/extractclass/ExtractedClassBuilder.java +++ b/java/java-impl/src/com/intellij/refactoring/extractclass/ExtractedClassBuilder.java @@ -18,7 +18,6 @@ package com.intellij.refactoring.extractclass; import com.intellij.codeInsight.generation.GenerateMembersUtil; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.codeStyle.JavaCodeStyleManager; @@ -26,7 +25,6 @@ import com.intellij.psi.codeStyle.VariableKind; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiUtil; import com.intellij.refactoring.psi.MethodInheritanceUtils; -import com.intellij.util.Function; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NonNls; @@ -438,6 +436,12 @@ class ExtractedClassBuilder { } } } + else if (referent instanceof PsiClass) { + String qualifiedName = ((PsiClass)referent).getQualifiedName(); + if (qualifiedName != null) { + out.append(qualifiedName); + } + } else { visitElement(expression); } diff --git a/java/java-impl/src/com/intellij/refactoring/move/moveClassesOrPackages/MoveClassesOrPackagesUtil.java b/java/java-impl/src/com/intellij/refactoring/move/moveClassesOrPackages/MoveClassesOrPackagesUtil.java index a61a5c97bddf..d3f1a6f223ed 100644 --- a/java/java-impl/src/com/intellij/refactoring/move/moveClassesOrPackages/MoveClassesOrPackagesUtil.java +++ b/java/java-impl/src/com/intellij/refactoring/move/moveClassesOrPackages/MoveClassesOrPackagesUtil.java @@ -19,6 +19,7 @@ import com.intellij.ide.util.DirectoryChooserUtil; import com.intellij.lang.java.JavaFindUsagesProvider; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.JavaProjectRootsUtil; import com.intellij.openapi.roots.ProjectFileIndex; @@ -236,7 +237,11 @@ public class MoveClassesOrPackagesUtil { if (!Comparing.equal(moveDestination.getVirtualFile(), containingDirectory != null ? containingDirectory.getVirtualFile() : null)) { LOG.assertTrue(file.getVirtualFile() != null, aClass); + Project project = file.getProject(); MoveFilesOrDirectoriesUtil.doMoveFile(file, moveDestination); + + DumbService.getInstance(project).completeJustSubmittedTasks(); + file = moveDestination.findFile(file.getName()); if (newPackage != null && file instanceof PsiClassOwner && !FileTypeUtils.isInServerPageFile(file) && !PsiUtil.isModuleFile(file)) { diff --git a/java/java-impl/src/com/intellij/refactoring/move/moveMembers/MoveJavaMemberHandler.java b/java/java-impl/src/com/intellij/refactoring/move/moveMembers/MoveJavaMemberHandler.java index 4557dd9b7c15..2232d6b5f03f 100644 --- a/java/java-impl/src/com/intellij/refactoring/move/moveMembers/MoveJavaMemberHandler.java +++ b/java/java-impl/src/com/intellij/refactoring/move/moveMembers/MoveJavaMemberHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * 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. @@ -16,6 +16,8 @@ package com.intellij.refactoring.move.moveMembers; import com.intellij.codeInsight.ChangeContextUtil; +import com.intellij.codeInsight.ExpectedTypeInfo; +import com.intellij.codeInsight.ExpectedTypesProvider; import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector; import com.intellij.openapi.project.Project; import com.intellij.psi.*; @@ -32,7 +34,10 @@ import com.intellij.util.containers.MultiMap; 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.Set; /** * @author Maxim.Medvedev @@ -77,10 +82,10 @@ public class MoveJavaMemberHandler implements MoveMemberHandler { @Override public void checkConflictsOnUsage(@NotNull MoveMembersProcessor.MoveMembersUsageInfo usageInfo, - @Nullable String newVisibility, @Nullable PsiModifierList modifierListCopy, @NotNull PsiClass targetClass, @NotNull Set membersToMove, + @NotNull MoveMembersOptions moveMembersOptions, @NotNull MultiMap conflicts) { final PsiElement element = usageInfo.getElement(); if (element == null) return; @@ -94,6 +99,7 @@ public class MoveJavaMemberHandler implements MoveMemberHandler { } if (!JavaResolveUtil.isAccessible(member, targetClass, modifierListCopy, element, accessObjectClass, null)) { + String newVisibility = moveMembersOptions.getExplicitMemberVisibility(); String visibility = newVisibility != null ? newVisibility : VisibilityUtil.getVisibilityStringToDisplay(member); String message = RefactoringBundle.message("0.with.1.visibility.is.not.accessible.from.2", RefactoringUIUtil.getDescription(member, false), @@ -120,12 +126,27 @@ public class MoveJavaMemberHandler implements MoveMemberHandler { conflicts.putValue(usageInfo.member, "final variable initializer won't be available after move."); } + if (toBeConvertedToEnum(moveMembersOptions, member, targetClass) && !isEnumAcceptable(element, targetClass)) { + conflicts.putValue(element, "Enum type won't be applicable in the current context"); + } + final PsiReference reference = usageInfo.getReference(); if (reference != null) { RefactoringConflictsUtil.checkAccessibilityConflicts(reference, member, modifierListCopy, targetClass, membersToMove, conflicts); } } + private static boolean isEnumAcceptable(PsiElement element, PsiClass targetClass) { + if (element instanceof PsiExpression) { + ExpectedTypeInfo[] types = ExpectedTypesProvider.getExpectedTypes((PsiExpression)element, false); + if (types.length == 1) { + PsiType type = types[0].getType(); + return type.isAssignableFrom(JavaPsiFacade.getElementFactory(element.getProject()).createType(targetClass)); + } + } + return false; + } + @Override public void checkConflictsOnMember(@NotNull PsiMember member, @Nullable String newVisibility, @@ -216,9 +237,7 @@ public class MoveJavaMemberHandler implements MoveMemberHandler { ChangeContextUtil.encodeContextInfo(member, true); final PsiMember memberCopy; - if (options.makeEnumConstant() && - member instanceof PsiVariable && - EnumConstantsUtil.isSuitableForEnumConstant(((PsiVariable)member).getType(), targetClass)) { + if (toBeConvertedToEnum(options, member, targetClass)) { memberCopy = EnumConstantsUtil.createEnumConstant(targetClass, member.getName(), ((PsiVariable)member).getInitializer()); } else { @@ -237,6 +256,14 @@ public class MoveJavaMemberHandler implements MoveMemberHandler { return anchor != null ? (PsiMember)targetClass.addAfter(memberCopy, anchor) : (PsiMember)targetClass.add(memberCopy); } + private static boolean toBeConvertedToEnum(@NotNull MoveMembersOptions options, + @NotNull PsiMember member, + @NotNull PsiClass targetClass) { + return options.makeEnumConstant() && + member instanceof PsiVariable && + EnumConstantsUtil.isSuitableForEnumConstant(((PsiVariable)member).getType(), targetClass); + } + @Override public void decodeContextInfo(@NotNull PsiElement scope) { ChangeContextUtil.decodeContextInfo(scope, null, null); diff --git a/java/java-impl/src/com/intellij/refactoring/move/moveMembers/MoveMemberHandler.java b/java/java-impl/src/com/intellij/refactoring/move/moveMembers/MoveMemberHandler.java index 9846d3690f82..f7abbadab2f8 100644 --- a/java/java-impl/src/com/intellij/refactoring/move/moveMembers/MoveMemberHandler.java +++ b/java/java-impl/src/com/intellij/refactoring/move/moveMembers/MoveMemberHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * 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. @@ -35,12 +35,21 @@ public interface MoveMemberHandler { @NotNull Set membersToMove, @NotNull PsiClass targetClass); - void checkConflictsOnUsage(@NotNull MoveMembersProcessor.MoveMembersUsageInfo usageInfo, - @Nullable String newVisibility, - @Nullable PsiModifierList modifierListCopy, - @NotNull PsiClass targetClass, - @NotNull Set membersToMove, - @NotNull MultiMap conflicts); + default void checkConflictsOnUsage(@NotNull MoveMembersProcessor.MoveMembersUsageInfo usageInfo, + @Nullable PsiModifierList modifierListCopy, + @NotNull PsiClass targetClass, + @NotNull Set membersToMove, + MoveMembersOptions moveMembersOptions, + @NotNull MultiMap conflicts) { + checkConflictsOnUsage(usageInfo, moveMembersOptions.getExplicitMemberVisibility(), modifierListCopy, targetClass, membersToMove, conflicts); + } + + default void checkConflictsOnUsage(@NotNull MoveMembersProcessor.MoveMembersUsageInfo usageInfo, + @Nullable String newVisibility, + @Nullable PsiModifierList modifierListCopy, + @NotNull PsiClass targetClass, + @NotNull Set membersToMove, + @NotNull MultiMap conflicts) {} void checkConflictsOnMember(@NotNull PsiMember member, @Nullable String newVisibility, diff --git a/java/java-impl/src/com/intellij/refactoring/move/moveMembers/MoveMembersOptions.java b/java/java-impl/src/com/intellij/refactoring/move/moveMembers/MoveMembersOptions.java index 99595c07d1dd..a28bd00e09c5 100644 --- a/java/java-impl/src/com/intellij/refactoring/move/moveMembers/MoveMembersOptions.java +++ b/java/java-impl/src/com/intellij/refactoring/move/moveMembers/MoveMembersOptions.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * 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. @@ -16,6 +16,8 @@ package com.intellij.refactoring.move.moveMembers; import com.intellij.psi.PsiMember; +import com.intellij.psi.PsiModifier; +import com.intellij.util.VisibilityUtil; import org.jetbrains.annotations.Nullable; /** @@ -26,8 +28,19 @@ public interface MoveMembersOptions { String getTargetClassName(); + @PsiModifier.ModifierConstant @Nullable String getMemberVisibility(); + @PsiModifier.ModifierConstant + @Nullable + default String getExplicitMemberVisibility() { + String visibility = getMemberVisibility(); + if (VisibilityUtil.ESCALATE_VISIBILITY.equals(visibility)) { + return PsiModifier.PUBLIC; + } + return visibility; + } + boolean makeEnumConstant(); } diff --git a/java/java-impl/src/com/intellij/refactoring/move/moveMembers/MoveMembersProcessor.java b/java/java-impl/src/com/intellij/refactoring/move/moveMembers/MoveMembersProcessor.java index b7687e100049..893efa29e4a5 100644 --- a/java/java-impl/src/com/intellij/refactoring/move/moveMembers/MoveMembersProcessor.java +++ b/java/java-impl/src/com/intellij/refactoring/move/moveMembers/MoveMembersProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * 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. @@ -282,11 +282,7 @@ public class MoveMembersProcessor extends BaseRefactoringProcessor { final MultiMap conflicts = new MultiMap<>(); final UsageInfo[] usages = refUsages.get(); - String newVisibility = myNewVisibility; - if (VisibilityUtil.ESCALATE_VISIBILITY.equals(newVisibility)) { // still need to check for access object - newVisibility = PsiModifier.PUBLIC; - } - + String newVisibility = myOptions.getExplicitMemberVisibility(); // still need to check for access object final Map modifierListCopies = new HashMap<>(); for (PsiMember member : myMembersToMove) { PsiModifierList modifierListCopy = member.getModifierList(); @@ -304,7 +300,7 @@ public class MoveMembersProcessor extends BaseRefactoringProcessor { modifierListCopies.put(member, modifierListCopy); } - analyzeConflictsOnUsages(usages, myMembersToMove, newVisibility, myTargetClass, modifierListCopies, conflicts); + analyzeConflictsOnUsages(usages, myMembersToMove, myTargetClass, modifierListCopies, myOptions, conflicts); analyzeConflictsOnMembers(myMembersToMove, newVisibility, myTargetClass, modifierListCopies, conflicts); RefactoringConflictsUtil.analyzeModuleConflicts(myProject, myMembersToMove, usages, myTargetClass, conflicts); @@ -314,9 +310,9 @@ public class MoveMembersProcessor extends BaseRefactoringProcessor { private static void analyzeConflictsOnUsages(UsageInfo[] usages, Set membersToMove, - String newVisibility, @NotNull PsiClass targetClass, Map modifierListCopies, + MoveMembersOptions options, MultiMap conflicts) { for (UsageInfo usage : usages) { if (!(usage instanceof MoveMembersUsageInfo)) continue; @@ -324,7 +320,7 @@ public class MoveMembersProcessor extends BaseRefactoringProcessor { final PsiMember member = usageInfo.member; final MoveMemberHandler handler = MoveMemberHandler.EP_NAME.forLanguage(member.getLanguage()); if (handler != null) { - handler.checkConflictsOnUsage(usageInfo, newVisibility, modifierListCopies.get(member), targetClass, membersToMove, conflicts); + handler.checkConflictsOnUsage(usageInfo, modifierListCopies.get(member), targetClass, membersToMove, options, conflicts); } } } diff --git a/java/java-impl/src/com/intellij/refactoring/safeDelete/JavaSafeDeleteProcessor.java b/java/java-impl/src/com/intellij/refactoring/safeDelete/JavaSafeDeleteProcessor.java index 858d6116dd58..b5c72021d887 100644 --- a/java/java-impl/src/com/intellij/refactoring/safeDelete/JavaSafeDeleteProcessor.java +++ b/java/java-impl/src/com/intellij/refactoring/safeDelete/JavaSafeDeleteProcessor.java @@ -49,9 +49,7 @@ import com.intellij.usageView.UsageInfo; import com.intellij.usageView.UsageViewUtil; import com.intellij.usages.*; import com.intellij.util.ArrayUtil; -import com.intellij.util.Function; import com.intellij.util.IncorrectOperationException; -import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashMap; import com.intellij.util.containers.MultiMap; @@ -255,12 +253,22 @@ public class JavaSafeDeleteProcessor extends SafeDeleteProcessorDelegateBase { @Override public Collection findConflicts(PsiElement element, PsiElement[] elements, UsageInfo[] usages) { String methodRefFound = null; - if (!ApplicationManager.getApplication().isUnitTestMode() && (element instanceof PsiMethod || element instanceof PsiParameter)) { - for (UsageInfo usage : usages) { - final PsiElement refElement = usage.getElement(); - if (refElement instanceof PsiMethodReferenceExpression) { - methodRefFound = RefactoringBundle.message("expand.method.reference.warning"); - break; + if (element instanceof PsiMethod || element instanceof PsiParameter) { + PsiMethod method; + if (element instanceof PsiMethod) { + method = (PsiMethod)element; + } + else { + PsiElement declarationScope = ((PsiParameter)element).getDeclarationScope(); + method = declarationScope instanceof PsiMethod ? (PsiMethod)declarationScope : null; + } + if (method != null) { + for (UsageInfo usage : usages) { + final PsiElement refElement = usage.getElement(); + if (refElement instanceof PsiMethodReferenceExpression && method.equals(((PsiMethodReferenceExpression)refElement).resolve())) { + methodRefFound = RefactoringBundle.message("expand.method.reference.warning"); + break; + } } } } diff --git a/java/java-impl/src/com/intellij/testIntegration/createTest/CreateTestDialog.java b/java/java-impl/src/com/intellij/testIntegration/createTest/CreateTestDialog.java index c10ad5ffaca4..8801e6e0985c 100644 --- a/java/java-impl/src/com/intellij/testIntegration/createTest/CreateTestDialog.java +++ b/java/java-impl/src/com/intellij/testIntegration/createTest/CreateTestDialog.java @@ -34,7 +34,6 @@ import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.help.HelpManager; import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.DumbModePermission; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.JavaProjectRootsUtil; @@ -400,14 +399,13 @@ public class CreateTestDialog extends DialogWrapper { myFixLibraryButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, - () -> ApplicationManager.getApplication().runWriteAction(() -> { - if (mySelectedFramework instanceof JavaTestFramework) { - ((JavaTestFramework)mySelectedFramework).setupLibrary(myTargetModule); - } else { - OrderEntryFix.addJarToRoots(mySelectedFramework.getLibraryPath(), myTargetModule, null); - } - })); + ApplicationManager.getApplication().runWriteAction(() -> { + if (mySelectedFramework instanceof JavaTestFramework) { + ((JavaTestFramework)mySelectedFramework).setupLibrary(myTargetModule); + } else { + OrderEntryFix.addJarToRoots(mySelectedFramework.getLibraryPath(), myTargetModule, null); + } + }); myFixLibraryPanel.setVisible(false); } }); diff --git a/java/java-psi-api/src/com/intellij/codeInsight/AnnotationUtil.java b/java/java-psi-api/src/com/intellij/codeInsight/AnnotationUtil.java index 2d1bf2a43ac6..5a2e0f4623d7 100644 --- a/java/java-psi-api/src/com/intellij/codeInsight/AnnotationUtil.java +++ b/java/java-psi-api/src/com/intellij/codeInsight/AnnotationUtil.java @@ -217,9 +217,7 @@ public class AnnotationUtil { //noinspection unchecked list.addAll((Collection)result); - List dependencies = ContainerUtil.newArrayList(result); - dependencies.add(PsiModificationTracker.MODIFICATION_COUNT); - return Result.create(list, dependencies); + return Result.create(list, PsiModificationTracker.MODIFICATION_COUNT); } }); } diff --git a/java/java-psi-api/src/com/intellij/codeInsight/JavaCodeInsightSettingsFacade.java b/java/java-psi-api/src/com/intellij/codeInsight/JavaCodeInsightSettingsFacade.java deleted file mode 100644 index 7021f152bda1..000000000000 --- a/java/java-psi-api/src/com/intellij/codeInsight/JavaCodeInsightSettingsFacade.java +++ /dev/null @@ -1,29 +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.codeInsight; - -import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.project.Project; - -public abstract class JavaCodeInsightSettingsFacade { - /** todo remove in IDEA 16 */ - @Deprecated - public abstract boolean isShowStaticAfterInstance(); - - public static JavaCodeInsightSettingsFacade getInstance(Project project) { - return ServiceManager.getService(project, JavaCodeInsightSettingsFacade.class); - } -} diff --git a/java/java-psi-api/src/com/intellij/psi/util/TypesDistinctProver.java b/java/java-psi-api/src/com/intellij/psi/util/TypesDistinctProver.java index e1ad149a4b0f..ba454564a310 100644 --- a/java/java-psi-api/src/com/intellij/psi/util/TypesDistinctProver.java +++ b/java/java-psi-api/src/com/intellij/psi/util/TypesDistinctProver.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * 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. @@ -140,7 +140,12 @@ public class TypesDistinctProver { if (level < 2) return false; } + if (boundClass1 == null || boundClass2 == null) { + return type1 != null && type2 != null && !type1.equals(type2); + } + return type2 != null && type1 != null && !type1.equals(type2) && + !(level == 0 && boundClass1.isInterface() && boundClass2.isInterface()) && (!InheritanceUtil.isInheritorOrSelf(boundClass1, boundClass2, true) || !InheritanceUtil.isInheritorOrSelf(boundClass2, boundClass1, true)); } diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/PsiImplUtil.java b/java/java-psi-impl/src/com/intellij/psi/impl/PsiImplUtil.java index 69d353e49edc..7c97080ffe91 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/PsiImplUtil.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/PsiImplUtil.java @@ -51,7 +51,6 @@ import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.IncorrectOperationException; import com.intellij.util.PairFunction; -import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -169,7 +168,7 @@ public class PsiImplUtil { for (int i = 0; i < typeParameters.length; i++) { if (typeParameter.equals(typeParameters[i])) return i; } - LOG.assertTrue(false); + LOG.error(typeParameter + " in " + typeParameterList); return -1; } @@ -617,20 +616,6 @@ public class PsiImplUtil { } } - /** @deprecated use {@link #collectTypeUseAnnotations(PsiModifierList, List)} (to be removed in IDEA 16) */ - public static List getTypeUseAnnotations(@NotNull PsiModifierList modifierList) { - SmartList result = null; - - for (PsiAnnotation annotation : modifierList.getAnnotations()) { - if (isTypeAnnotation(annotation)) { - if (result == null) result = new SmartList(); - result.add(annotation); - } - } - - return result; - } - private static final Key TYPE_ANNO_MARK = Key.create("type.annotation.mark"); public static void markTypeAnnotations(@NotNull PsiTypeElement typeElement) { diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/InferenceSession.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/InferenceSession.java index 25406c5c3e6d..71499ba32b30 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/InferenceSession.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/graphInference/InferenceSession.java @@ -1614,7 +1614,7 @@ public class InferenceSession { } if (methodContainingClass != null) { - psiSubstitutor = TypeConversionUtil.getClassSubstitutor(methodContainingClass, containingClass, psiSubstitutor); + psiSubstitutor = JavaClassSupers.getInstance().getSuperClassSubstitutor(methodContainingClass, containingClass, reference.getResolveScope(), psiSubstitutor); LOG.assertTrue(psiSubstitutor != null, "derived: " + containingClass + "; super: " + methodContainingClass + "; reference: " + reference.getText() + @@ -1631,7 +1631,7 @@ public class InferenceSession { final PsiType pType = signature.getParameterTypes()[0]; // 15.13.1 If the ReferenceType is a raw type, and there exists a parameterization of this type, T, that is a supertype of P1, - // the type to search is the result of capture conversion (5.1.10) applied to T; + // the type to search is the result of capture conversion (5.1.10) applied to T; // otherwise, the type to search is the same as the type of the first search. Again, the type arguments, if any, are given by the method reference. if (PsiUtil.isRawSubstitutor(containingClass, psiSubstitutor)) { PsiType normalizedPType = PsiUtil.captureToplevelWildcards(pType, myContext); @@ -1646,7 +1646,7 @@ public class InferenceSession { mySiteSubstitutor = mySiteSubstitutor.putAll(receiverSubstitutor); if (methodContainingClass != null) { - final PsiSubstitutor superSubstitutor = TypeConversionUtil.getClassSubstitutor(methodContainingClass, containingClass, receiverSubstitutor); + final PsiSubstitutor superSubstitutor = JavaClassSupers.getInstance().getSuperClassSubstitutor(methodContainingClass, containingClass, reference.getResolveScope(), receiverSubstitutor); LOG.assertTrue(superSubstitutor != null, "mContainingClass: " + methodContainingClass.getName() + "; containingClass: " + containingClass.getName()); mySiteSubstitutor = mySiteSubstitutor.putAll(superSubstitutor); } @@ -1660,8 +1660,11 @@ public class InferenceSession { addConstraint(new TypeCompatibilityConstraint(substituteWithInferenceVariables(qType), pType)); if (methodContainingClass != null) { - psiSubstitutor = TypeConversionUtil.getClassSubstitutor(methodContainingClass, containingClass, psiSubstitutor); - LOG.assertTrue(psiSubstitutor != null, "derived: " + containingClass + "; super: " + methodContainingClass); + psiSubstitutor = JavaClassSupers.getInstance().getSuperClassSubstitutor(methodContainingClass, containingClass, reference.getResolveScope(), psiSubstitutor); + LOG.assertTrue(psiSubstitutor != null, "derived: " + containingClass + + "; super: " + methodContainingClass + + "; reference: " + reference.getText() + + "; containingFile: " + reference.getContainingFile().getName()); } for (int i = 0; i < signature.getParameterTypes().length - 1; i++) { diff --git a/java/java-psi-impl/src/com/intellij/psi/presentation/java/JavaPresentationUtil.java b/java/java-psi-impl/src/com/intellij/psi/presentation/java/JavaPresentationUtil.java index 9ac376a686c9..7e2a81b08b92 100644 --- a/java/java-psi-impl/src/com/intellij/psi/presentation/java/JavaPresentationUtil.java +++ b/java/java-psi-impl/src/com/intellij/psi/presentation/java/JavaPresentationUtil.java @@ -19,6 +19,7 @@ import com.intellij.navigation.ColoredItemPresentation; import com.intellij.navigation.ItemPresentation; import com.intellij.openapi.editor.colors.CodeInsightColors; import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.util.Iconable; import com.intellij.psi.*; import com.intellij.psi.util.PsiFormatUtil; @@ -47,8 +48,12 @@ public class JavaPresentationUtil { @Override public TextAttributesKey getTextAttributesKey() { - if (psiMethod.isDeprecated()) { - return CodeInsightColors.DEPRECATED_ATTRIBUTES; + try { + if (psiMethod.isDeprecated()) { + return CodeInsightColors.DEPRECATED_ATTRIBUTES; + } + } + catch (IndexNotReadyException ignore) { } return null; } @@ -75,8 +80,12 @@ public class JavaPresentationUtil { @Override public TextAttributesKey getTextAttributesKey() { - if (psiField.isDeprecated()) { - return CodeInsightColors.DEPRECATED_ATTRIBUTES; + try { + if (psiField.isDeprecated()) { + return CodeInsightColors.DEPRECATED_ATTRIBUTES; + } + } + catch (IndexNotReadyException ignore) { } return null; } diff --git a/java/java-structure-view/src/com/intellij/ide/structureView/impl/java/PropertyGroup.java b/java/java-structure-view/src/com/intellij/ide/structureView/impl/java/PropertyGroup.java index f30af8b1a661..cbe863b8933c 100644 --- a/java/java-structure-view/src/com/intellij/ide/structureView/impl/java/PropertyGroup.java +++ b/java/java-structure-view/src/com/intellij/ide/structureView/impl/java/PropertyGroup.java @@ -24,6 +24,7 @@ import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.colors.CodeInsightColors; import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.IconLoader; @@ -32,6 +33,7 @@ import com.intellij.psi.util.PropertyUtil; import com.intellij.psi.util.PsiUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.ArrayList; @@ -254,11 +256,13 @@ public class PropertyGroup implements Group, ColoredItemPresentation, AccessLeve return isDeprecated(getField()) && isDeprecated(getGetter()) && isDeprecated(getSetter()); } - private static boolean isDeprecated(final PsiElement element) { - if (element == null) return false; - if (!element.isValid()) return false; - if (!(element instanceof PsiDocCommentOwner)) return false; - return ((PsiDocCommentOwner)element).isDeprecated(); + private static boolean isDeprecated(@Nullable final PsiDocCommentOwner element) { + try { + return element != null && element.isValid() && element.isDeprecated(); + } + catch (IndexNotReadyException e) { + return false; + } } public boolean isComplete() { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/graphInference/TwoDifferentParameterizationCheckWithInterfaceTypeArguments.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/graphInference/TwoDifferentParameterizationCheckWithInterfaceTypeArguments.java new file mode 100644 index 000000000000..c6c214067503 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/graphInference/TwoDifferentParameterizationCheckWithInterfaceTypeArguments.java @@ -0,0 +1,28 @@ +class Test { + + { + Marker cm = forEach(child1(), child2()); + } + + public static Child1 child1() { + return null; + } + + public static Child2 child2() { + return null; + } + + public static > CM forEach(CM contents, CM cm) { + return null; + } + + interface Marker {} + static class Parent {} + + static class Child1 extends Parent implements Marker {} + interface ChildAttr1 {} + + static class Child2 extends Parent implements Marker {} + interface ChildAttr2 {} + +} diff --git a/java/java-tests/testData/codeInsight/multipleJdks/java3/p/MethodReferencePointingToDifferentJdk.java b/java/java-tests/testData/codeInsight/multipleJdks/java3/p/MethodReferencePointingToDifferentJdk.java new file mode 100644 index 000000000000..8de5b9a1f059 --- /dev/null +++ b/java/java-tests/testData/codeInsight/multipleJdks/java3/p/MethodReferencePointingToDifferentJdk.java @@ -0,0 +1,8 @@ +package p; + +import java.io.FileReader; +import java.io.IOException; + +public class PersistentMap extends FileReader { + public void close() throws IOException {} +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/multipleJdks/java8/p/MethodReferencePointingToDifferentJdk.java b/java/java-tests/testData/codeInsight/multipleJdks/java8/p/MethodReferencePointingToDifferentJdk.java new file mode 100644 index 000000000000..2adae27db380 --- /dev/null +++ b/java/java-tests/testData/codeInsight/multipleJdks/java8/p/MethodReferencePointingToDifferentJdk.java @@ -0,0 +1,23 @@ +package p; +import java.io.*; + +class Subst { + PersistentMap messages; + + { + register(() -> catchAndWarn(messages::close)); + } + + static void register(Runnable r) {} + + private static void catchAndWarn(ThrowableRunnable runnable) { + try { + runnable.run(); + } + catch (IOException e) {} + } + + interface ThrowableRunnable { + void run() throws IOException; + } +} diff --git a/java/java-tests/testData/refactoring/changeSignature/ReplaceOldStyleArrayWithVarargs.java b/java/java-tests/testData/refactoring/changeSignature/ReplaceOldStyleArrayWithVarargs.java new file mode 100644 index 000000000000..a647a9861e27 --- /dev/null +++ b/java/java-tests/testData/refactoring/changeSignature/ReplaceOldStyleArrayWithVarargs.java @@ -0,0 +1,3 @@ +class Test { + void foo(int a[]) {} +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/changeSignature/ReplaceOldStyleArrayWithVarargs_after.java b/java/java-tests/testData/refactoring/changeSignature/ReplaceOldStyleArrayWithVarargs_after.java new file mode 100644 index 000000000000..dfb7f5a10a3d --- /dev/null +++ b/java/java-tests/testData/refactoring/changeSignature/ReplaceOldStyleArrayWithVarargs_after.java @@ -0,0 +1,3 @@ +class Test { + void foo(int... a) {} +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/extractClass/ensurePreservedQualifier/after/Extracted.java b/java/java-tests/testData/refactoring/extractClass/ensurePreservedQualifier/after/Extracted.java new file mode 100644 index 000000000000..d1716b8177ca --- /dev/null +++ b/java/java-tests/testData/refactoring/extractClass/ensurePreservedQualifier/after/Extracted.java @@ -0,0 +1,11 @@ +import java.util.Collections; +import java.util.List; + +public class Extracted { + public Extracted() { + } + + List foo() { + return Collections.emptyList(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/extractClass/ensurePreservedQualifier/after/Test.java b/java/java-tests/testData/refactoring/extractClass/ensurePreservedQualifier/after/Test.java new file mode 100644 index 000000000000..a06540728b92 --- /dev/null +++ b/java/java-tests/testData/refactoring/extractClass/ensurePreservedQualifier/after/Test.java @@ -0,0 +1,2 @@ +public class Test { +} diff --git a/java/java-tests/testData/refactoring/extractClass/ensurePreservedQualifier/before/Test.java b/java/java-tests/testData/refactoring/extractClass/ensurePreservedQualifier/before/Test.java new file mode 100644 index 000000000000..15a9c94db706 --- /dev/null +++ b/java/java-tests/testData/refactoring/extractClass/ensurePreservedQualifier/before/Test.java @@ -0,0 +1,9 @@ + +import java.util.Collections; +import java.util.List; + +public class Test { + List foo() { + return Collections.emptyList(); + } +} diff --git a/java/java-tests/testData/refactoring/moveMembers/enumConstant/after/B.java b/java/java-tests/testData/refactoring/moveMembers/enumConstant/after/B.java index ebbe4dc08cb2..0efd250fa599 100644 --- a/java/java-tests/testData/refactoring/moveMembers/enumConstant/after/B.java +++ b/java/java-tests/testData/refactoring/moveMembers/enumConstant/after/B.java @@ -1,2 +1,5 @@ public class B { + { + Object o = A.ONE; + } } \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/moveMembers/enumConstant/before/B.java b/java/java-tests/testData/refactoring/moveMembers/enumConstant/before/B.java index acf3478236a6..2de88dcc3357 100644 --- a/java/java-tests/testData/refactoring/moveMembers/enumConstant/before/B.java +++ b/java/java-tests/testData/refactoring/moveMembers/enumConstant/before/B.java @@ -1,3 +1,6 @@ public class B { - public static final String ONE = ""; + public static final String ONE = ""; + { + Object o = ONE; + } } \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/safeDelete/NoConflictOnDeleteParameterWithMethodRefArg.java b/java/java-tests/testData/refactoring/safeDelete/NoConflictOnDeleteParameterWithMethodRefArg.java new file mode 100644 index 000000000000..926b0bb36aab --- /dev/null +++ b/java/java-tests/testData/refactoring/safeDelete/NoConflictOnDeleteParameterWithMethodRefArg.java @@ -0,0 +1,13 @@ +import java.util.function.Consumer; + +public class Subst { + void test1() { + test2(Subst::bar); + } + + private static void bar(Object o) { + + } + + void test2(final Consumer consumer) {} +} diff --git a/java/java-tests/testData/refactoring/safeDelete/NoConflictOnDeleteParameterWithMethodRefArg_after.java b/java/java-tests/testData/refactoring/safeDelete/NoConflictOnDeleteParameterWithMethodRefArg_after.java new file mode 100644 index 000000000000..87b2e5480514 --- /dev/null +++ b/java/java-tests/testData/refactoring/safeDelete/NoConflictOnDeleteParameterWithMethodRefArg_after.java @@ -0,0 +1,13 @@ +import java.util.function.Consumer; + +public class Subst { + void test1() { + test2(); + } + + private static void bar(Object o) { + + } + + void test2() {} +} diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/ExternalAnnotationsManagerTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/ExternalAnnotationsManagerTest.java index e3da0c5bd6a4..93e1fd902e73 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/ExternalAnnotationsManagerTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/ExternalAnnotationsManagerTest.java @@ -34,7 +34,10 @@ import com.intellij.testFramework.PsiTestUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MostlySingularMultiMap; import com.intellij.xml.util.XmlUtil; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.eclipse.util.PathUtil; import java.util.Arrays; import java.util.Collection; @@ -47,8 +50,8 @@ public class ExternalAnnotationsManagerTest extends IdeaTestCase { Sdk jdk = JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk(); Sdk sdk = PsiTestUtil.addJdkAnnotations(jdk); String home = jdk.getHomeDirectory().getParent().getPath(); - String toolsPath = home + "/lib/tools.jar!/"; VfsRootAccess.allowRootAccess(getTestRootDisposable(), home); + String toolsPath = home + "/lib/tools.jar!/"; VirtualFile toolsJar = JarFileSystem.getInstance().findFileByPath(toolsPath); Sdk plusTools = PsiTestUtil.addRootsToJdk(sdk, OrderRootType.CLASSES, toolsJar); @@ -60,22 +63,22 @@ public class ExternalAnnotationsManagerTest extends IdeaTestCase { LocalFileSystem.getInstance() .findFileByPath(FileUtil.toSystemIndependentName(path))) .toArray(VirtualFile[]::new); - Sdk result = PsiTestUtil.addRootsToJdk(plusTools, OrderRootType.CLASSES, files); - return result; + return PsiTestUtil.addRootsToJdk(plusTools, OrderRootType.CLASSES, files); } - public void testBundledAnnotationXmls() { + public void testBundledAnnotationXmlSyntax() { String root = PathManagerEx.getCommunityHomePath() + "/java/jdkAnnotations"; - findAnnotationsXmlAndCheck(root); + findAnnotationsXmlAndCheckSyntax(root); } - private void findAnnotationsXmlAndCheck(String root) { + private void findAnnotationsXmlAndCheckSyntax(String root) { VirtualFile jdkAnnoRoot = LocalFileSystem.getInstance().findFileByPath(root); VfsUtilCore.visitChildrenRecursively(jdkAnnoRoot, new VirtualFileVisitor() { @Override public boolean visitFile(@NotNull VirtualFile file) { if (file.getName().equals("annotations.xml")) { - check(file); + String assumedPackage = PathUtil.getRelative(root, file.getParent().getPath()).replaceAll("/","."); + checkSyntax(file, assumedPackage); } return true; } @@ -95,27 +98,27 @@ public class ExternalAnnotationsManagerTest extends IdeaTestCase { .setProjectSdk(PsiTestUtil.addRootsToJdk(getTestProjectJdk(), OrderRootType.CLASSES, androidJars))); String root = PathManagerEx.getCommunityHomePath() + "/android/android/annotations"; - findAnnotationsXmlAndCheck(root); + findAnnotationsXmlAndCheckSyntax(root); } - private void check(VirtualFile file) { + private void checkSyntax(@NotNull VirtualFile file, @NotNull String assumedPackage) { //System.out.println("file = " + file); ExternalAnnotationsManagerImpl manager = (ExternalAnnotationsManagerImpl)ExternalAnnotationsManager.getInstance(getProject()); PsiFile psiFile = getPsiManager().findFile(file); MostlySingularMultiMap map = manager.getDataFromFile(psiFile); for (String externalName : map.keySet()) { - checkExternalName(psiFile, externalName); + checkExternalName(psiFile, externalName, assumedPackage); // 'annotation name="org.jetbrains.annotations.NotNull"' should have FQN for (BaseExternalAnnotationsManager.AnnotationData annotationData : map.get(externalName)) { PsiAnnotation annotation = annotationData.getAnnotation(manager); String nameText = annotation.getNameReferenceElement().getText(); - assertClassFqn(nameText, psiFile, externalName); + assertClassFqn(nameText, psiFile, externalName, null); } } } - private PsiClass assertClassFqn(String text, PsiFile psiFile, String externalName) { + private PsiClass assertClassFqn(@NotNull String text, @NotNull PsiFile psiFile, @NotNull String externalName, @Nullable("null means can be any") String assumedPackage) { if (!PsiNameHelper.getInstance(getProject()).isQualifiedName(text) || !text.contains(".")) { fail("'" + text + "' doesn't seem like a FQN", psiFile, externalName); } @@ -124,21 +127,26 @@ public class ExternalAnnotationsManagerTest extends IdeaTestCase { if (aClass == null) { fail("'" + text + "' doesn't resolve to a class", psiFile, externalName); } + String packageName = ((PsiClassOwner)aClass.getContainingFile()).getPackageName(); + if (assumedPackage != null && !assumedPackage.equals(packageName)) { + fail("Wrong package for class '"+text+"'. Expected: '"+assumedPackage+"' but was: '"+packageName+"'", psiFile, externalName); + } return aClass; } + @Contract("_,_,_-> fail") private static void fail(String error, PsiFile psiFile, String externalName) { int offset = psiFile.getText().indexOf(XmlUtil.escape(externalName)); int line = PsiDocumentManager.getInstance(psiFile.getProject()).getDocument(psiFile).getLineNumber(offset); fail(error + "\nFile: " + psiFile.getVirtualFile().getPath() + ":" + (line+1) + " (offset: "+offset+")"); } - private void checkExternalName(PsiFile psiFile, String externalName) { + private void checkExternalName(@NotNull PsiFile psiFile, @NotNull String externalName, @NotNull String assumedPackage) { // 'item name="java.lang.ClassLoader java.net.URL getResource(java.lang.String) 0"' should have all FQNs String unescaped = StringUtil.unescapeXml(externalName); List words = StringUtil.split(unescaped, " "); String className = words.get(0); - PsiClass aClass = assertClassFqn(className, psiFile, externalName); + PsiClass aClass = assertClassFqn(className, psiFile, externalName, assumedPackage); if (words.size() == 1) return; String rest = unescaped.substring(className.length() + " ".length()); diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/MultipleJdksHighlightingTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/MultipleJdksHighlightingTest.java index 0fa886c71d14..131f6a37f4dc 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/MultipleJdksHighlightingTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/MultipleJdksHighlightingTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * 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. @@ -217,6 +217,16 @@ public class MultipleJdksHighlightingTest extends UsefulTestCase { myFixture.checkHighlighting(); } + public void testMethodReferencePointingToDifferentJdk() throws Exception { + ModuleRootModificationUtil.addDependency(myJava8Module, myJava3Module); + final String testName = getTestName(false); + myFixture.copyFileToProject("java3/p/" + testName + ".java"); + myFixture.copyFileToProject("java8/p/" + testName + ".java"); + + myFixture.configureByFiles("java8/p/" + testName + ".java", "java3/p/" + testName + ".java"); + myFixture.checkHighlighting(); + } + public void testInheritorsOfJdkClassOnlyInModulesWithThatJdk() { ModuleRootModificationUtil.addDependency(myJava8Module, myJava7Module); diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/completion/ModuleCompletionTest.kt b/java/java-tests/testSrc/com/intellij/codeInsight/completion/ModuleCompletionTest.kt index e022580dda2b..f8b08724043c 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/completion/ModuleCompletionTest.kt +++ b/java/java-tests/testSrc/com/intellij/codeInsight/completion/ModuleCompletionTest.kt @@ -26,6 +26,7 @@ class ModuleCompletionTest : LightJava9ModulesCodeInsightFixtureTestCase() { } fun testFileHeader() = complete("", "module ") + fun testModuleName() = variants("module M") fun testStatements1() = variants("module M { }", "requires", "exports", "uses", "provides") fun testStatements2() = complete("module M { requires X; ex }", "module M { requires X; exports }") fun testModuleRef() = complete("module M { requires M }", "module M { requires M2; }") diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonRespondToChangesTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonRespondToChangesTest.java index 492b99efaf67..d8880bec4749 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonRespondToChangesTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonRespondToChangesTest.java @@ -39,6 +39,7 @@ import com.intellij.codeInspection.ex.LocalInspectionToolWrapper; import com.intellij.codeInspection.htmlInspections.RequiredAttributesInspectionBase; import com.intellij.codeInspection.varScopeCanBeNarrowed.FieldCanBeLocalInspection; import com.intellij.diagnostic.PerformanceWatcher; +import com.intellij.diagnostic.ThreadDumper; import com.intellij.execution.filters.TextConsoleBuilderFactory; import com.intellij.execution.ui.ConsoleView; import com.intellij.execution.ui.ConsoleViewContentType; @@ -1335,16 +1336,16 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { List warns = doHighlighting(HighlightSeverity.WARNING); assertOneElement(warns); - List actions = ShowIntentionsPass.getAvailableActions(getEditor(), getFile(), -1); + List actions = ShowIntentionsPass.getAvailableFixes(getEditor(), getFile(), -1); final HighlightInfo.IntentionActionDescriptor descriptor = assertOneElement(actions); WriteCommandAction.runWriteCommandAction(getProject(), () -> descriptor.getAction().invoke(getProject(), getEditor(), getFile())); highlightErrors(); - actions = ShowIntentionsPass.getAvailableActions(getEditor(), getFile(), -1); + actions = ShowIntentionsPass.getAvailableFixes(getEditor(), getFile(), -1); assertEmpty(actions); } - + public void testApplyErrorInTheMiddle() throws Throwable { String text = "class X { "; for (int i = 0; i < 100; i++) { @@ -1372,7 +1373,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { assertEmpty(errors); } - + public void testErrorInTheEndOutsideVisibleArea() throws Throwable { String text = " \n" + StringUtil.repeatSymbol('\n', 1000) + "\nxxxxx"; configureByText(StdFileTypes.XML, text); @@ -1426,7 +1427,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { assertEquals(4, infos.size()); } - + public void testTypingNearEmptyErrorElement() throws Throwable { String text = "class LQF {\n" + " public void main() {\n" + @@ -1445,7 +1446,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { assertEmpty(infos); } - + public void testLIPGetAllParentsAfterCodeBlockModification() throws Throwable { @Language("JAVA") String text = "class LQF {\n" + @@ -1513,7 +1514,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { assertEquals(new HashSet<>(methodAndParents), new HashSet<>(visitedElements)); } - + public void testCancelsItSelfOnTypingInAlienProject() throws Throwable { String body = StringUtil.repeat("\"String field = null;\"\n", 1000); configureByText(StdFileTypes.JAVA, "class X{ void f() {" + body + "\n} }"); @@ -1523,7 +1524,6 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { boolean succ2 = ProjectManagerEx.getInstanceEx().openProject(alienProject); assertTrue(succ2); DaemonProgressIndicator.setDebug(true); - final DaemonProgressIndicator[] indicator = new DaemonProgressIndicator[1]; try { Module alienModule = doCreateRealModuleIn("x", alienProject, getModuleType()); @@ -1551,7 +1551,6 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { di.runPasses(getFile(), getEditor().getDocument(), textEditor, ArrayUtil.EMPTY_INT_ARRAY, true, () -> { if (checked[0]) return; checked[0] = true; - indicator[0] = myDaemonCodeAnalyzer.getUpdateProgress(); typeInAlienEditor(alienEditor, 'x'); }); } @@ -1659,7 +1658,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { long end = System.currentTimeMillis(); long interruptTime = end - now; interruptTimes[finalI] = interruptTime; - assertNull(codeAnalyzer.getUpdateProgress()); + assertTrue(codeAnalyzer.getUpdateProgress().isCanceled()); System.out.println(interruptTime); throw new ProcessCanceledException(); }; @@ -1730,7 +1729,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { long end = System.currentTimeMillis(); long interruptTime = end - now; interruptTimes[finalI] = interruptTime; - assertNull(codeAnalyzer.getUpdateProgress()); + assertTrue(codeAnalyzer.getUpdateProgress().isCanceled()); //System.out.println(interruptTime); throw new ProcessCanceledException(); }; @@ -2143,8 +2142,10 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { long elapsed = System.currentTimeMillis() - start; assertEquals(0, errors.size()); + if (!run.get()) { + fail(ThreadDumper.dumpThreadsToString()); + } assertTrue("Elapsed: "+elapsed, elapsed >= SLEEP); - assertTrue(run.get()); } finally { ExternalLanguageAnnotators.INSTANCE.removeExplicitExtension(JavaLanguage.INSTANCE, annotator); diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/inlays/InlayParameterHintsTest.kt b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/inlays/InlayParameterHintsTest.kt index d60c2a6dd8b1..8b208fe76027 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/inlays/InlayParameterHintsTest.kt +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/inlays/InlayParameterHintsTest.kt @@ -18,9 +18,15 @@ package com.intellij.codeInsight.daemon.inlays import com.intellij.codeInsight.daemon.impl.ParameterHintsPresentationManager import com.intellij.codeInsight.hints.settings.ParameterNameHintsSettings import com.intellij.openapi.editor.Inlay +import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.editor.ex.EditorSettingsExternalizable +import com.intellij.openapi.fileEditor.FileEditor +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.fileEditor.TextEditor import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiFile +import com.intellij.psi.impl.DebugUtil import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase import com.intellij.util.DocumentUtil import org.assertj.core.api.Assertions.assertThat @@ -80,6 +86,20 @@ class InlayAssert(private val file: PsiFile, val inlays: List) { assertThat(inlays).hasSize(0) } + private fun getAllInlays(): String { + val fileManager = FileEditorManager.getInstance(file.project) + val fileEditor: FileEditor = fileManager.getEditors(file.virtualFile).first()!! + val editor = ((fileEditor as TextEditor).editor as EditorEx) + + val inlays = editor.inlayModel.getInlineElementsInRange(0, file.textLength) + + val hintManager = ParameterHintsPresentationManager.getInstance() + return inlays + .filter { hintManager.isParameterHint(it) } + .map { it.offset to hintManager.getHintText(it) } + .joinToString(",") + } + fun assertInlays(vararg expectedInlays: String) { assertThat(expectedInlays.size).isNotEqualTo(0) @@ -89,7 +109,13 @@ class InlayAssert(private val file: PsiFile, val inlays: List) { val hintNames = hints.map { it.second } val elements = hintOffsets.mapNotNull { file.findElementAt(it) } - assertThat(hints.size).isEqualTo(expectedInlays.size).withFailMessage("Element at offsets: ${elements.joinToString(", ")}") + assertThat(hints.size) + .withFailMessage("Expected ${expectedInlays.size} elements with hints, Actual elements count ${hints.size}" + + ": ${elements.joinToString(", ")}, file text: \n\n ${file.text} \n\n isCommitted ${isCommitted(file)} \n\n" + + "Psi: \n ${DebugUtil.psiToString(file, true)}\n" + + "All inlays: ${getAllInlays()}") + + .isEqualTo(expectedInlays.size) val expect = expectedInlays.map { it.substringBefore("->") to it.substringAfter("->") } val expectedHintNames = expect.map { it.first } @@ -101,4 +127,12 @@ class InlayAssert(private val file: PsiFile, val inlays: List) { assertThat(wordsAfter).isEqualTo(expectedWordsAfter) } + private fun isCommitted(file: PsiFile): Boolean { + val manager = PsiDocumentManager.getInstance(file.project) + val document = manager.getDocument(file) + + assertThat(document).isNotNull() + return manager.isCommitted(document!!) + } + } \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/lambda/GraphInferenceHighlightingTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/lambda/GraphInferenceHighlightingTest.java index f04d7a0690c6..b402caf50635 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/lambda/GraphInferenceHighlightingTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/lambda/GraphInferenceHighlightingTest.java @@ -487,6 +487,10 @@ public class GraphInferenceHighlightingTest extends LightDaemonAnalyzerTestCase doTest(); } + public void testTwoDifferentParameterizationCheckWithInterfaceTypeArguments() throws Exception { + doTest(); + } + private void doTest() throws Exception { doTest(false); } diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/template/LiveTemplateTest.groovy b/java/java-tests/testSrc/com/intellij/codeInsight/template/LiveTemplateTest.groovy index 792af508a0a2..847c24646bd9 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/template/LiveTemplateTest.groovy +++ b/java/java-tests/testSrc/com/intellij/codeInsight/template/LiveTemplateTest.groovy @@ -665,14 +665,12 @@ class Outer { def copy = defContext.createCopy() - def write = new Element("context") - copy.writeTemplateContext(write) + def write = copy.writeTemplateContext(null) assert write.children.size() == 2 : JDOMUtil.writeElement(write) copy.setEnabled(TemplateContextType.EP_NAME.findExtension(JavaCommentContextType), false) - write = new Element("context") - copy.writeTemplateContext(write) + write = copy.writeTemplateContext(null) assert write.children.size() == 3 : JDOMUtil.writeElement(write) } @@ -700,8 +698,7 @@ class Outer { def javaContext = TemplateContextType.EP_NAME.findExtension(JavaCodeContextType.Generic) context.setEnabled(javaContext, true) - def saved = new Element('context') - context.writeTemplateContext(saved) + def saved = context.writeTemplateContext(null) context = new TemplateContext() context.readTemplateContext(saved) diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/unwrap/UnwrapLambdaTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/unwrap/UnwrapLambdaTest.java index c23b3ae9e17a..91564e531d55 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/unwrap/UnwrapLambdaTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/unwrap/UnwrapLambdaTest.java @@ -27,6 +27,16 @@ public class UnwrapLambdaTest extends UnwrapTestCase { " System.gc();\n" + "}\n"); } + + public void testUnwrapNestedLambda() throws Exception { + assertUnwrapped("{\n" + + " bar(() -> bar(() -> System.gc()));\n" + + "}\n", + + "{\n" + + " bar(() -> System.gc());\n" + + "}\n", 1); + } public void testUnwrapExpressionDeclaration() throws Exception { assertUnwrapped("{\n" + @@ -61,4 +71,14 @@ public class UnwrapLambdaTest extends UnwrapTestCase { " null;\n" + "}\n"); } + + @Override + protected String createCode(String codeBefore) { + return "public class A {\n" + + " void foo() {\n" + + indentTwice(codeBefore) + + " }\n" + + " void bar(Runnable r){}\n" + + "}"; + } } \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/execution/ConfigurationsTest.java b/java/java-tests/testSrc/com/intellij/execution/ConfigurationsTest.java index 61a1e0c14ed9..b490dde8f147 100644 --- a/java/java-tests/testSrc/com/intellij/execution/ConfigurationsTest.java +++ b/java/java-tests/testSrc/com/intellij/execution/ConfigurationsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * 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. @@ -20,6 +20,8 @@ import com.intellij.execution.application.ApplicationConfiguration; import com.intellij.execution.application.ApplicationConfigurationType; import com.intellij.execution.configurations.*; import com.intellij.execution.executors.DefaultRunExecutor; +import com.intellij.execution.impl.RunManagerImpl; +import com.intellij.execution.impl.RunnerAndConfigurationSettingsImpl; import com.intellij.execution.junit.*; import com.intellij.execution.junit2.configuration.JUnitConfigurable; import com.intellij.execution.junit2.configuration.JUnitConfigurationModel; @@ -27,6 +29,7 @@ import com.intellij.execution.runners.ExecutionEnvironmentBuilder; import com.intellij.execution.testframework.SearchForTestsTask; import com.intellij.execution.testframework.TestSearchScope; import com.intellij.execution.ui.CommonJavaParametersPanel; +import com.intellij.ide.util.AppPropertiesComponentImpl; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.options.Configurable; @@ -37,10 +40,7 @@ import com.intellij.openapi.roots.ContentEntry; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.ModuleRootModificationUtil; import com.intellij.openapi.ui.LabeledComponent; -import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.InvalidDataException; -import com.intellij.openapi.util.WriteExternalException; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.JarFileSystem; import com.intellij.openapi.vfs.LocalFileSystem; @@ -67,6 +67,8 @@ import java.util.Collections; import java.util.List; import java.util.StringTokenizer; +import static org.assertj.core.api.Assertions.assertThat; + public class ConfigurationsTest extends BaseConfigurationTestCase { private final Assertion CHECK = new Assertion(); @@ -253,17 +255,22 @@ public class ConfigurationsTest extends BaseConfigurationTestCase { CHECK.singleOccurence(classPath, getFSPath(findFile(MOCK_JUNIT))); } - public void testExternalizeJUnitConfiguration() throws WriteExternalException, InvalidDataException { - JUnitConfiguration configuration = createConfiguration(findTestA(getModule1())); - Element element = new Element("cfg"); - configuration.writeExternal(element); - JUnitConfiguration newCfg = - new JUnitConfiguration(null, myProject, JUnitConfigurationType.getInstance().getConfigurationFactories()[0]); + public void testExternalizeJUnitConfiguration() { + Module module = getModule1(); + JUnitConfiguration oldRc = createConfiguration(findTestA(module)); + oldRc.setWorkingDirectory(module.getModuleFilePath()); - newCfg.readExternal(element); - checkTestObject(configuration.getPersistentData().TEST_OBJECT, newCfg); - assertEquals(Collections.singleton(getModule1()), ContainerUtilRt.newHashSet(newCfg.getModules())); - checkClassName(configuration.getPersistentData().getMainClassName(), newCfg); + RunManagerImpl runManager = new RunManagerImpl(myProject, new AppPropertiesComponentImpl()); + Element element = new Element("configuration"); + new RunnerAndConfigurationSettingsImpl(runManager, oldRc, false).writeExternal(element); + + RunnerAndConfigurationSettingsImpl settings = new RunnerAndConfigurationSettingsImpl(runManager); + settings.readExternal(element); + JUnitConfiguration newRc = (JUnitConfiguration)settings.getConfiguration(); + + checkTestObject(oldRc.getPersistentData().TEST_OBJECT, newRc); + assertThat(newRc.getModules()).containsOnly(module); + checkClassName(oldRc.getPersistentData().getMainClassName(), newRc); } public void testTestClassPathWhenRunningConfigurations() throws IOException, ExecutionException { diff --git a/java/java-tests/testSrc/com/intellij/execution/impl/RunConfigurableTest.java b/java/java-tests/testSrc/com/intellij/execution/impl/RunConfigurableTest.java index 5592ae030f1a..d00b71835a4e 100644 --- a/java/java-tests/testSrc/com/intellij/execution/impl/RunConfigurableTest.java +++ b/java/java-tests/testSrc/com/intellij/execution/impl/RunConfigurableTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * 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. @@ -21,7 +21,6 @@ import com.intellij.execution.configurations.UnknownConfigurationType; import com.intellij.execution.junit.JUnitConfigurationType; import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.JDOMUtil; import com.intellij.openapi.util.Trinity; import com.intellij.testFramework.LightIdeaTestCase; @@ -196,7 +195,7 @@ public class RunConfigurableTest extends LightIdeaTestCase { assertEquals(expected, myConfigurable.getAvailableDropPosition(direction)); } - private static RunManagerImpl createRunManager(Element element) throws InvalidDataException { + private static RunManagerImpl createRunManager(Element element) { Project project = getProject(); RunManagerImpl runManager = new RunManagerImpl(project, PropertiesComponent.getInstance(project)); runManager.initializeConfigurationTypes(new ConfigurationType[]{ApplicationConfigurationType.getInstance(), diff --git a/java/java-tests/testSrc/com/intellij/psi/resolve/ResolveInCodeFragmentTest.java b/java/java-tests/testSrc/com/intellij/psi/resolve/ResolveInCodeFragmentTest.java index 5633acd5ba7d..4dd80ac62867 100644 --- a/java/java-tests/testSrc/com/intellij/psi/resolve/ResolveInCodeFragmentTest.java +++ b/java/java-tests/testSrc/com/intellij/psi/resolve/ResolveInCodeFragmentTest.java @@ -1,7 +1,9 @@ package com.intellij.psi.resolve; +import com.intellij.codeInsight.AnnotationUtil; import com.intellij.ide.highlighter.JavaFileType; import com.intellij.psi.*; +import com.intellij.psi.impl.PsiElementFactoryImpl; import com.intellij.psi.impl.search.JavaSourceFilterScope; import com.intellij.psi.impl.source.tree.java.PsiReferenceExpressionImpl; import com.intellij.psi.util.PsiTreeUtil; @@ -95,4 +97,14 @@ public class ResolveInCodeFragmentTest extends ResolveTestCase { PsiCodeBlock block = factory.createCodeBlockFromText("{ return o; }", method); assertInstanceOf(block.findReferenceAt(block.getText().indexOf("o")).resolve(), PsiParameter.class); } + + public void testDropCachesOnNonPhysicalContextChange() { + PsiElementFactoryImpl factory = (PsiElementFactoryImpl)JavaPsiFacade.getElementFactory(getProject()); + PsiClass superClass = ((PsiJavaFile) PsiFileFactory.getInstance(myProject).createFileFromText("a.java", JavaFileType.INSTANCE, "class Super { @Deprecated void foo(){} }")).getClasses()[0]; + PsiClass subClass = ((PsiNewExpression)factory.createExpressionFromText("new Super() { void foo(){} }", superClass)).getAnonymousClass(); + assertNotNull(AnnotationUtil.findAnnotationInHierarchy(subClass.getMethods()[0], Deprecated.class)); + + superClass.getMethods()[0].getModifierList().getAnnotations()[0].delete(); + assertNull(AnnotationUtil.findAnnotationInHierarchy(subClass.getMethods()[0], Deprecated.class)); + } } diff --git a/java/java-tests/testSrc/com/intellij/refactoring/ChangeSignatureTest.java b/java/java-tests/testSrc/com/intellij/refactoring/ChangeSignatureTest.java index c3e690487d91..15dd066297a2 100644 --- a/java/java-tests/testSrc/com/intellij/refactoring/ChangeSignatureTest.java +++ b/java/java-tests/testSrc/com/intellij/refactoring/ChangeSignatureTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * 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. @@ -355,6 +355,10 @@ public class ChangeSignatureTest extends ChangeSignatureBaseTest { }, false); } + public void testReplaceOldStyleArrayWithVarargs() throws Exception { + doTest(null, new ParameterInfoImpl[] {new ParameterInfoImpl(0, "a", new PsiEllipsisType(PsiType.INT))}, false); + } + public void testReorderParamsOfFunctionalInterface() { doTest(null, null, null, method -> new ParameterInfoImpl[]{ new ParameterInfoImpl(1, "b", PsiType.INT), diff --git a/java/java-tests/testSrc/com/intellij/refactoring/ExtractClassTest.java b/java/java-tests/testSrc/com/intellij/refactoring/ExtractClassTest.java index 30f547aa7265..2f1ba3669ed9 100644 --- a/java/java-tests/testSrc/com/intellij/refactoring/ExtractClassTest.java +++ b/java/java-tests/testSrc/com/intellij/refactoring/ExtractClassTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * 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. @@ -160,6 +160,10 @@ public class ExtractClassTest extends MultiFileTestCase{ doTestInnerClass(); } + public void testEnsurePreservedQualifier() throws Exception { + doTestMethod(); + } + private void doTestFieldAndMethod(final String methodName) throws Exception { doTest((rootDir, rootAfter) -> { PsiClass aClass = myJavaFacade.findClass("Test", GlobalSearchScope.projectScope(myProject)); diff --git a/java/java-tests/testSrc/com/intellij/refactoring/MoveMembersTest.java b/java/java-tests/testSrc/com/intellij/refactoring/MoveMembersTest.java index 7221ece56edc..490ca58a1741 100644 --- a/java/java-tests/testSrc/com/intellij/refactoring/MoveMembersTest.java +++ b/java/java-tests/testSrc/com/intellij/refactoring/MoveMembersTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * 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. @@ -119,7 +119,13 @@ public class MoveMembersTest extends MultiFileTestCase { } public void testEnumConstantFromCaseStatement() throws Exception { - doTest("B", "A", 0); + try { + doTest("B", "A", 0); + fail("Conflict expected"); + } + catch (BaseRefactoringProcessor.ConflictsInTestsException e) { + assertEquals("Enum type won't be applicable in the current context", e.getMessage()); + } } public void testStringConstantFromCaseStatement() throws Exception { diff --git a/java/java-tests/testSrc/com/intellij/refactoring/SafeDeleteTest.java b/java/java-tests/testSrc/com/intellij/refactoring/SafeDeleteTest.java index db481b14dc3d..9e5251c74b9b 100644 --- a/java/java-tests/testSrc/com/intellij/refactoring/SafeDeleteTest.java +++ b/java/java-tests/testSrc/com/intellij/refactoring/SafeDeleteTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * 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. @@ -312,6 +312,17 @@ public class SafeDeleteTest extends MultiFileTestCase { } public void testParameterInMethodUsedInMethodReference() throws Exception { + LanguageLevelProjectExtension.getInstance(getProject()).setLanguageLevel(LanguageLevel.JDK_1_8); + try { + BaseRefactoringProcessor.ConflictsInTestsException.setTestIgnore(true); + doSingleFileTest(); + } + finally { + BaseRefactoringProcessor.ConflictsInTestsException.setTestIgnore(false); + } + } + + public void testNoConflictOnDeleteParameterWithMethodRefArg() throws Exception { LanguageLevelProjectExtension.getInstance(getProject()).setLanguageLevel(LanguageLevel.JDK_1_8); doSingleFileTest(); } diff --git a/java/jdkAnnotations/java/lang/annotations.xml b/java/jdkAnnotations/java/lang/annotations.xml index 572588fb1356..59f2471b4df7 100644 --- a/java/jdkAnnotations/java/lang/annotations.xml +++ b/java/jdkAnnotations/java/lang/annotations.xml @@ -61,7 +61,12 @@ - + + + + + + diff --git a/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaPredefinedConfigurations.java b/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaPredefinedConfigurations.java index a065b81ffc4f..0add4f6684a6 100644 --- a/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaPredefinedConfigurations.java +++ b/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaPredefinedConfigurations.java @@ -172,7 +172,7 @@ class JavaPredefinedConfigurations { // Add comments and metadata createSearchTemplateInfo(SSRBundle.message("predefined.configuration.comments"),"/* 'CommentContent */", METADATA_TYPE), createSearchTemplateInfo(SSRBundle.message("predefined.configuration.javadoc.annotated.class"),"/** @'_Tag+ '_TagValue* */\nclass '_Class {\n}", METADATA_TYPE), - createSearchTemplateInfo(SSRBundle.message("predefined.configuration.javadoc.annotated.methods"),"/** @'_Tag+ '_TagValue* */\n'_Type+ '_Method+('_ParameterType '_Parameter*);", METADATA_TYPE), + createSearchTemplateInfo(SSRBundle.message("predefined.configuration.javadoc.annotated.methods"),"/** @'_Tag+ '_TagValue* */\n'_Type '_Method('_ParameterType '_Parameter*);", METADATA_TYPE), createSearchTemplateInfo(SSRBundle.message("predefined.configuration.javadoc.annotated.fields"),"class '_Class {\n /** @'_Tag+ '_TagValue* */\n '_Type+ 'Field+ = '_Init*;\n}", METADATA_TYPE), createSearchTemplateInfo(SSRBundle.message("predefined.configuration.javadoc.tags"),"/** @'Tag+ '_TagValue* */", METADATA_TYPE), createSearchTemplateInfo(SSRBundle.message("predefined.configuration.xdoclet.metadata"),"/** @'Tag \n '_Property+\n*/", METADATA_TYPE), @@ -187,10 +187,10 @@ class JavaPredefinedConfigurations { " '_FieldType 'Field+ = '_Init?;\n" + "}", METADATA_TYPE), createSearchTemplateInfo(SSRBundle.message("predefined.configuration.annotated.methods"), - "@'_Annotation+( )\n'_MethodType '_Method+('_ParameterType '_Parameter*);", METADATA_TYPE), + "@'_Annotation+( )\n'_MethodType '_Method('_ParameterType '_Parameter*);", METADATA_TYPE), createSearchTemplateInfo(SSRBundle.message("predefined.configuration.not.annotated.methods"), - "@'_Annotation{0,0}\n'_MethodType '_Method+('_ParameterType '_Parameter*);", METADATA_TYPE), + "@'_Annotation{0,0}\n'_MethodType '_Method('_ParameterType '_Parameter*);", METADATA_TYPE), createSearchTemplateInfo(SSRBundle.message("predefined.configuration.annotation.declarations"), "@interface 'Interface {}", METADATA_TYPE), @@ -264,7 +264,7 @@ class JavaPredefinedConfigurations { " }\n"+ "}",MISC_TYPE), createSearchTemplateInfo(SSRBundle.message("predefined.configuration.similar.methods.structure"), - "'_RetType '_Method+('_ParameterType '_Parameter) throws 'ExceptionType {\n" + + "'_RetType '_Method('_ParameterType '_Parameter*) throws 'ExceptionType {\n" + " try {\n" + " '_OtherStatements+;\n" + " } catch('_SomeException '_Exception) {\n" + diff --git a/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaStructuralSearchProfile.java b/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaStructuralSearchProfile.java index f2a013479a5f..08c456a9067e 100644 --- a/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaStructuralSearchProfile.java +++ b/java/structuralsearch-java/src/com/intellij/structuralsearch/JavaStructuralSearchProfile.java @@ -31,6 +31,7 @@ import com.intellij.openapi.fileTypes.StdFileTypes; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; +import com.intellij.psi.javadoc.PsiDocComment; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.PsiUtilCore; @@ -318,7 +319,11 @@ public class JavaStructuralSearchProfile extends StructuralSearchProfile { final PsiElement firstElement = elements[0]; final PsiElement secondElement = elements[1]; - if (firstElement instanceof PsiDeclarationStatement && PsiTreeUtil.lastChild(firstElement) instanceof PsiErrorElement) { + if (firstElement instanceof PsiDocComment) { + // might be method with javadoc + return true; + } + else if (firstElement instanceof PsiDeclarationStatement && PsiTreeUtil.lastChild(firstElement) instanceof PsiErrorElement) { // might be method return true; } 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 c2606724dc6e..fd7228b3c07c 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 @@ -1670,23 +1670,16 @@ public class JavaMatchingVisitor extends JavaElementVisitor { @Override public void visitClass(PsiClass clazz) { + PsiClass clazz2 = (PsiClass)myMatchingVisitor.getElement(); if (clazz.hasTypeParameters()) { - myMatchingVisitor - .setResult( - myMatchingVisitor.match(clazz.getTypeParameterList(), ((PsiClass)myMatchingVisitor.getElement()).getTypeParameterList())); - + myMatchingVisitor.setResult(myMatchingVisitor.match(clazz.getTypeParameterList(), clazz2.getTypeParameterList())); if (!myMatchingVisitor.getResult()) return; } - PsiClass clazz2; - - if (myMatchingVisitor.getElement() instanceof PsiDeclarationStatement && - myMatchingVisitor.getElement().getFirstChild() instanceof PsiClass - ) { - clazz2 = (PsiClass)myMatchingVisitor.getElement().getFirstChild(); - } - else { - clazz2 = (PsiClass)myMatchingVisitor.getElement(); + final PsiDocComment comment = clazz.getDocComment(); + if (comment != null) { + myMatchingVisitor.setResult(myMatchingVisitor.match(comment, clazz2)); + if (!myMatchingVisitor.getResult()) return; } final boolean isTypedVar = myMatchingVisitor.getMatchContext().getPattern().isTypedVar(clazz.getNameIdentifier()); diff --git a/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/handlers/DeclarationStatementHandler.java b/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/handlers/DeclarationStatementHandler.java index b11b6e34a494..cb334a341d61 100644 --- a/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/handlers/DeclarationStatementHandler.java +++ b/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/handlers/DeclarationStatementHandler.java @@ -1,3 +1,18 @@ +/* + * 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.structuralsearch.impl.matcher.handlers; import com.intellij.dupLocator.iterators.ArrayBackedNodeIterator; @@ -5,7 +20,6 @@ import com.intellij.dupLocator.iterators.CountingNodeIterator; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; -import com.intellij.structuralsearch.impl.matcher.GlobalMatchingVisitor; import com.intellij.structuralsearch.impl.matcher.MatchContext; import com.intellij.structuralsearch.impl.matcher.iterators.SsrFilteringNodeIterator; @@ -13,11 +27,9 @@ import java.util.ArrayList; import java.util.List; /** - * Created by IntelliJ IDEA. - * User: maxim + * @author maxim * Date: 31.12.2004 * Time: 12:01:29 - * To change this template use File | Settings | File Templates. */ public class DeclarationStatementHandler extends MatchingHandler { private MatchingHandler myCommentHandler; @@ -31,21 +43,17 @@ public class DeclarationStatementHandler extends MatchingHandler { if (!super.match(patternNode,matchedNode,context)) return false; final PsiDeclarationStatement dcl = (PsiDeclarationStatement)patternNode; if (matchedNode instanceof PsiDeclarationStatement) { - return GlobalMatchingVisitor.continueMatchingSequentially( - new SsrFilteringNodeIterator(patternNode.getFirstChild()), - new SsrFilteringNodeIterator(matchedNode.getFirstChild()), - context - ); + return context.getMatcher().matchSequentially(new SsrFilteringNodeIterator(patternNode.getFirstChild()), + new SsrFilteringNodeIterator(matchedNode.getFirstChild())); } final PsiElement[] declared = dcl.getDeclaredElements(); // declaration statement could wrap class or dcl if (declared.length > 0 && !(matchedNode.getParent() instanceof PsiDeclarationStatement) /* skip twice matching for child*/) { if (!(matchedNode instanceof PsiField)) { - return GlobalMatchingVisitor.continueMatchingSequentially( + return context.getMatcher().matchSequentially( new ArrayBackedNodeIterator(declared), - new CountingNodeIterator(declared.length, new SsrFilteringNodeIterator(matchedNode)), - context + new CountingNodeIterator(declared.length, new SsrFilteringNodeIterator(matchedNode)) ); } @@ -65,10 +73,9 @@ public class DeclarationStatementHandler extends MatchingHandler { } node = PsiTreeUtil.skipSiblingsForward(node, PsiWhiteSpace.class); } - boolean result = GlobalMatchingVisitor.continueMatchingSequentially( + boolean result = context.getMatcher().matchSequentially( new ArrayBackedNodeIterator(declared), - new ArrayBackedNodeIterator(matchNodes.toArray(new PsiElement[matchNodes.size()])), - context + new ArrayBackedNodeIterator(matchNodes.toArray(new PsiElement[matchNodes.size()])) ); if (result && declared[0] instanceof PsiVariable) { @@ -92,11 +99,7 @@ public class DeclarationStatementHandler extends MatchingHandler { @Override public boolean shouldAdvanceTheMatchFor(PsiElement patternElement, PsiElement matchedElement) { - if (patternElement instanceof PsiComment && - ( matchedElement instanceof PsiField || - matchedElement instanceof PsiClass - ) - ) { + if (patternElement instanceof PsiComment && (matchedElement instanceof PsiField || matchedElement instanceof PsiClass)) { return false; } diff --git a/java/testFramework/src/com/intellij/codeInsight/daemon/quickFix/LightQuickFixTestCase.java b/java/testFramework/src/com/intellij/codeInsight/daemon/quickFix/LightQuickFixTestCase.java index 19ec879a4f35..5cd855e49787 100644 --- a/java/testFramework/src/com/intellij/codeInsight/daemon/quickFix/LightQuickFixTestCase.java +++ b/java/testFramework/src/com/intellij/codeInsight/daemon/quickFix/LightQuickFixTestCase.java @@ -19,6 +19,7 @@ import com.intellij.codeInsight.daemon.LightDaemonAnalyzerTestCase; import com.intellij.codeInsight.daemon.impl.HighlightInfo; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.editor.Editor; @@ -55,11 +56,6 @@ public abstract class LightQuickFixTestCase extends LightDaemonAnalyzerTestCase return false; } - @NotNull - protected ActionHint parseActionHintImpl(@NotNull PsiFile file, @NotNull String contents) { - return ActionHint.parse(file, contents); - } - private static void doTestFor(@NotNull String testName, @NotNull QuickFixTestCase quickFixTestCase) { final String relativePath = ObjectUtils.notNull(quickFixTestCase.getBasePath(), "") + "/" + BEFORE_PREFIX + testName; final String testFullPath = quickFixTestCase.getTestDataPath().replace(File.separatorChar, '/') + relativePath; @@ -148,7 +144,8 @@ public abstract class LightQuickFixTestCase extends LightDaemonAnalyzerTestCase ReadonlyStatusHandlerImpl handler = (ReadonlyStatusHandlerImpl)ReadonlyStatusHandler.getInstance(file.getProject()); handler.setClearReadOnlyInTests(true); try { - ShowIntentionActionsHandler.chooseActionAndInvoke(file, getEditor(), action, action.getText()); + ApplicationManager.getApplication().invokeLater(() -> + ShowIntentionActionsHandler.chooseActionAndInvoke(file, getEditor(), action, action.getText())); UIUtil.dispatchAllInvocationEvents(); } finally { @@ -239,7 +236,7 @@ public abstract class LightQuickFixTestCase extends LightDaemonAnalyzerTestCase @NotNull @Override public ActionHint parseActionHintImpl(@NotNull PsiFile file, @NotNull String contents) { - return LightQuickFixTestCase.this.parseActionHintImpl(file, contents); + return ActionHint.parse(file, contents); } @Override diff --git a/java/testFramework/src/com/intellij/debugger/ExecutionWithDebuggerToolsTestCase.java b/java/testFramework/src/com/intellij/debugger/ExecutionWithDebuggerToolsTestCase.java index bbc8d7000597..df42d962bf85 100644 --- a/java/testFramework/src/com/intellij/debugger/ExecutionWithDebuggerToolsTestCase.java +++ b/java/testFramework/src/com/intellij/debugger/ExecutionWithDebuggerToolsTestCase.java @@ -21,7 +21,6 @@ import com.intellij.debugger.engine.evaluation.EvaluateException; import com.intellij.debugger.engine.evaluation.TextWithImportsImpl; import com.intellij.debugger.engine.events.DebuggerCommandImpl; import com.intellij.debugger.engine.events.SuspendContextCommandImpl; -import com.intellij.debugger.impl.DebuggerManagerImpl; import com.intellij.debugger.impl.PositionUtil; import com.intellij.debugger.impl.PrioritizedTask; import com.intellij.debugger.impl.SynchronizationBasedSemaphore; @@ -47,9 +46,9 @@ import com.intellij.util.SmartList; import com.intellij.util.TimeoutUtil; import com.intellij.util.lang.CompoundRuntimeException; import com.intellij.util.ui.UIUtil; -import com.intellij.xdebugger.breakpoints.XBreakpoint; import com.sun.jdi.Method; import com.sun.jdi.ThreadReference; +import org.jetbrains.java.debugger.breakpoints.properties.JavaMethodBreakpointProperties; import javax.swing.*; import java.util.ArrayList; @@ -379,33 +378,41 @@ public abstract class ExecutionWithDebuggerToolsTestCase extends ExecutionTestCa public void createBreakpoints(final PsiFile file) { Runnable runnable = () -> { - BreakpointManager breakpointManager = DebuggerManagerImpl.getInstanceEx(myProject).getBreakpointManager(); + BreakpointManager breakpointManager = DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager(); Document document = PsiDocumentManager.getInstance(myProject).getDocument(file); + String text = document.getText(); int offset = -1; - for (; ;) { - offset = document.getText().indexOf("Breakpoint!", offset + 1); + while (true) { + offset = text.indexOf("Breakpoint!", offset + 1); if (offset == -1) break; int commentLine = document.getLineNumber(offset); - String comment = document.getText().substring(document.getLineStartOffset(commentLine), document.getLineEndOffset(commentLine)); + String comment = text.substring(document.getLineStartOffset(commentLine), document.getLineEndOffset(commentLine)); Breakpoint breakpoint; - if (comment.indexOf("Method") != -1) { + if (comment.contains("Method")) { breakpoint = breakpointManager.addMethodBreakpoint(document, commentLine + 1); if (breakpoint != null) { println("MethodBreakpoint created at " + file.getVirtualFile().getName() + ":" + (commentLine + 2), ProcessOutputTypes.SYSTEM); + + String emulated = readValue(comment, "Emulated"); + if (emulated != null) { + ((JavaMethodBreakpointProperties)breakpoint.getXBreakpoint().getProperties()).EMULATED = Boolean.valueOf(emulated); + println("Emulated = " + emulated, ProcessOutputTypes.SYSTEM); + } + } } - else if (comment.indexOf("Field") != -1) { + else if (comment.contains("Field")) { breakpoint = breakpointManager.addFieldBreakpoint(document, commentLine + 1, readValue(comment, "Field")); if (breakpoint != null) { println("FieldBreakpoint created at " + file.getVirtualFile().getName() + ":" + (commentLine + 2), ProcessOutputTypes.SYSTEM); } } - else if (comment.indexOf("Exception") != -1) { + else if (comment.contains("Exception")) { breakpoint = breakpointManager.addExceptionBreakpoint(readValue(comment, "Exception"), ""); if (breakpoint != null) { println("ExceptionBreakpoint created at " + file.getVirtualFile().getName() + ":" + (commentLine + 2), @@ -419,6 +426,11 @@ public abstract class ExecutionWithDebuggerToolsTestCase extends ExecutionTestCa } } + if (breakpoint == null) { + LOG.error("Unable to set a breakpoint at line " + (commentLine + 1)); + continue; + } + String suspendPolicy = readValue(comment, "suspendPolicy"); if (suspendPolicy != null) { //breakpoint.setSuspend(!DebuggerSettings.SUSPEND_NONE.equals(suspendPolicy)); diff --git a/jps/model-serialization/src/com/intellij/openapi/components/PathMacroMap.java b/jps/model-serialization/src/com/intellij/openapi/components/PathMacroMap.java index ba6b779a3766..bd5c592f384e 100644 --- a/jps/model-serialization/src/com/intellij/openapi/components/PathMacroMap.java +++ b/jps/model-serialization/src/com/intellij/openapi/components/PathMacroMap.java @@ -31,6 +31,12 @@ public abstract class PathMacroMap { public abstract String substitute(String text, boolean caseSensitive); + public final String substitute(String text, boolean caseSensitive, boolean recursively) { + return recursively + ? substituteRecursively(text, caseSensitive) + : substitute(text, caseSensitive); + } + public final void substitute(@NotNull Element e, boolean caseSensitive) { substitute(e, caseSensitive, false); } diff --git a/platform/analysis-api/src/com/intellij/codeInspection/InspectionProfile.java b/platform/analysis-api/src/com/intellij/codeInspection/InspectionProfile.java index 0cd11db2e062..d9968db36ede 100644 --- a/platform/analysis-api/src/com/intellij/codeInspection/InspectionProfile.java +++ b/platform/analysis-api/src/com/intellij/codeInspection/InspectionProfile.java @@ -34,8 +34,6 @@ import java.util.List; * Date: Dec 7, 2004 */ public interface InspectionProfile extends Comparable { - void setName(@NotNull String name); - @NotNull String getName(); @@ -56,8 +54,7 @@ public interface InspectionProfile extends Comparable { InspectionProfileEntry getUnwrappedTool(@NotNull String shortName, @NotNull PsiElement element); /** Returns (unwrapped) inspection */ - - T getUnwrappedTool(@NotNull Key shortNameKey, @NotNull PsiElement element); + T getUnwrappedTool(@NotNull Key shortNameKey, @NotNull PsiElement element); /** * Allows a plugin to modify the settings of the inspection tool with the specified ID programmatically, without going through diff --git a/platform/analysis-api/src/com/intellij/codeInspection/ModifiableModel.java b/platform/analysis-api/src/com/intellij/codeInspection/ModifiableModel.java index fa2619335b41..54181b862648 100644 --- a/platform/analysis-api/src/com/intellij/codeInspection/ModifiableModel.java +++ b/platform/analysis-api/src/com/intellij/codeInspection/ModifiableModel.java @@ -13,42 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.intellij.codeInspection; import com.intellij.codeHighlighting.HighlightDisplayLevel; import com.intellij.codeInsight.daemon.HighlightDisplayKey; -import com.intellij.codeInspection.ex.InspectionToolWrapper; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElement; import com.intellij.psi.search.scope.packageSet.NamedScope; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -/** - * User: anna - * Date: 15-Feb-2006 - */ public interface ModifiableModel extends InspectionProfile { void enableTool(@NotNull String inspectionTool, NamedScope namedScope, Project project); void setErrorLevel(HighlightDisplayKey key, @NotNull HighlightDisplayLevel level, Project project); - @Override - HighlightDisplayLevel getErrorLevel(HighlightDisplayKey inspectionToolKey, PsiElement element); - - @Override - boolean isToolEnabled(HighlightDisplayKey key); - - @Override - boolean isToolEnabled(@Nullable HighlightDisplayKey key, @Nullable PsiElement element); - - @Override - InspectionProfileEntry getUnwrappedTool(@NotNull String shortName, @NotNull PsiElement element); - - @Override - InspectionToolWrapper[] getInspectionTools(PsiElement element); - /** * @see InspectionProfile#getSingleTool() */ diff --git a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/RainbowVisitor.java b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/RainbowVisitor.java index 44ff60534678..96f3b5458dfb 100644 --- a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/RainbowVisitor.java +++ b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/RainbowVisitor.java @@ -40,10 +40,10 @@ public abstract class RainbowVisitor implements HighlightVisitor { } @Override - public final boolean analyze(@NotNull PsiFile file, - boolean updateWholeFile, - @NotNull HighlightInfoHolder holder, - @NotNull Runnable action) { + public boolean analyze(@NotNull PsiFile file, + boolean updateWholeFile, + @NotNull HighlightInfoHolder holder, + @NotNull Runnable action) { myHolder = holder; myRainbowHighlighter = new RainbowHighlighter(myHolder.getColorsScheme()); try { diff --git a/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/DistributionJARsBuilder.groovy b/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/DistributionJARsBuilder.groovy index bcd62fa11017..745194349c69 100644 --- a/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/DistributionJARsBuilder.groovy +++ b/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/DistributionJARsBuilder.groovy @@ -30,6 +30,10 @@ import org.jetbrains.jps.model.module.JpsModuleReference import org.jetbrains.jps.util.JpsPathUtil /** + * Assembles output of modules to platform JARs (in {@link org.jetbrains.intellij.build.BuildPaths#distAll distAll}/lib directory), + * bunlded plugins' JARs (in {@link org.jetbrains.intellij.build.BuildPaths#distAll distAll}/plugins directory) and zip archives with + * non-bundled plugins (in {@link org.jetbrains.intellij.build.BuildPaths#artifacts artifacts}/plugins directory). + * * @author nik */ class DistributionJARsBuilder { @@ -399,10 +403,11 @@ class DistributionJARsBuilder { replace: "${buildNumber}") def sinceBuild def untilBuild - /* Use relaxed build numbers range for EAP/release branches, i.e. plugins for 163.1111.22 build will be marked as compatible with 163.1111.* builds. - Usually there are no API changes in EAP/release branches so it's convenient to be able to publish a single plugin for different IDEs built - from the same EAP/release branch. */ - if (!setExactNumberInUntilBuild && buildNumber.matches(/(\d+\.)+\d+\.\d+/)) { + /* Plugins from builds with numbers like 163.1111 (nightly builds) will be marked as compatible with all 163.* builds; it's required to + allow us to use such plugins in other nightly builds and in IDEA built from sources (with build number 163.SNAPSHOT). + Plugins from builds with numbers like 163.1111.22 (EAP/release builds) will be marked as compatible with all 163.1111.* builds. Usually there are no API changes in EAP/release branches so it's convenient to be able to publish a single plugin for different IDEs built + from the same EAP/release branch. */ + if (!setExactNumberInUntilBuild && buildNumber.matches(/(\d+\.)+\d+/)) { sinceBuild = buildNumber.substring(0, buildNumber.lastIndexOf('.')) untilBuild = sinceBuild + ".*" } diff --git a/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/PlatformLayout.groovy b/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/PlatformLayout.groovy index 1b10ba395f82..b5bdeaebc1c9 100644 --- a/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/PlatformLayout.groovy +++ b/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/PlatformLayout.groovy @@ -23,6 +23,11 @@ import org.jetbrains.jps.model.module.JpsModuleReference import java.util.function.Consumer /** + * Describes layout of the platform (*.jar files in IDE_HOME/lib directory). By default it includes all modules specified in {@link org.jetbrains.intellij.build.ProductModulesLayout}, + * all libraries these modules depend on with scope 'Compile' or 'Runtime', and all project libraries from dependencies (with scope 'Compile' + * or 'Runtime') of plugin modules for plugins which are {@link org.jetbrains.intellij.build.ProductModulesLayout#bundledPluginModules bundled} + * (or prepared to be {@link org.jetbrains.intellij.build.ProductModulesLayout#pluginModulesToPublish published}) with the product. + * * @author nik */ class PlatformLayout extends BaseLayout { diff --git a/platform/configuration-store-impl/src/ExportSettingsAction.kt b/platform/configuration-store-impl/src/ExportSettingsAction.kt index d535df5678a6..e6087fcd26d6 100644 --- a/platform/configuration-store-impl/src/ExportSettingsAction.kt +++ b/platform/configuration-store-impl/src/ExportSettingsAction.kt @@ -72,10 +72,7 @@ private class ExportSettingsAction : AnAction(), DumbAware { return } - val exportFiles = THashSet() - for ((files) in markedComponents) { - exportFiles.addAll(files) - } + val exportFiles = markedComponents.mapTo(THashSet()) { it.file } val saveFile = dialog.exportFile try { diff --git a/platform/configuration-store-impl/src/StateStorageManagerImpl.kt b/platform/configuration-store-impl/src/StateStorageManagerImpl.kt index 5e054292a2f5..325f8c27769a 100644 --- a/platform/configuration-store-impl/src/StateStorageManagerImpl.kt +++ b/platform/configuration-store-impl/src/StateStorageManagerImpl.kt @@ -60,7 +60,7 @@ open class StateStorageManagerImpl(private val rootTagName: String, var streamProvider: StreamProvider? get() = streamWrapper set (value) { - streamWrapper.setStreamProvider(value) + streamWrapper.streamProvider = value } // access under storageLock diff --git a/platform/configuration-store-impl/src/StreamProviderWrapper.java b/platform/configuration-store-impl/src/StreamProviderWrapper.java index 24f0aca09e5f..31bc97893348 100644 --- a/platform/configuration-store-impl/src/StreamProviderWrapper.java +++ b/platform/configuration-store-impl/src/StreamProviderWrapper.java @@ -22,6 +22,10 @@ public class StreamProviderWrapper implements StreamProvider { return null; } + public StreamProvider getStreamProvider() { + return myStreamProvider; + } + public void setStreamProvider(@Nullable StreamProvider streamProvider) { myStreamProvider = streamProvider; } diff --git a/platform/configuration-store-impl/testSrc/TemplateSchemeTest.kt b/platform/configuration-store-impl/testSrc/TemplateSchemeTest.kt new file mode 100644 index 000000000000..dc55a44cd96f --- /dev/null +++ b/platform/configuration-store-impl/testSrc/TemplateSchemeTest.kt @@ -0,0 +1,44 @@ +package com.intellij.configurationStore + +import com.intellij.codeInsight.template.impl.TemplateSettings +import com.intellij.testFramework.InMemoryFsRule +import com.intellij.testFramework.ProjectRule +import com.intellij.util.io.readText +import com.intellij.util.io.write +import org.assertj.core.api.Assertions.assertThat +import org.junit.ClassRule +import org.junit.Rule +import org.junit.Test + +class TemplateSchemeTest { + companion object { + @JvmField + @ClassRule + val projectRule = ProjectRule() + } + + @JvmField + @Rule + val fsRule = InMemoryFsRule() + + // https://youtrack.jetbrains.com/issue/IDEA-155623#comment=27-1721029 + @Test fun `do not remove unknown context`() { + val schemeFile = fsRule.fs.getPath("templates/Groovy.xml") + val schemeManagerFactory = SchemeManagerFactoryBase.TestSchemeManagerFactory(fsRule.fs.getPath("")) + val schemeData = """ + + + """.trimIndent() + + schemeFile.write(schemeData) + + TemplateSettings(schemeManagerFactory) + schemeManagerFactory.save() + assertThat(schemeFile.readText()).isEqualTo(schemeData) + } +} \ No newline at end of file diff --git a/platform/core-api/src/com/intellij/openapi/command/WriteCommandAction.java b/platform/core-api/src/com/intellij/openapi/command/WriteCommandAction.java index bc3470940bf4..3bd15a607bc6 100644 --- a/platform/core-api/src/com/intellij/openapi/command/WriteCommandAction.java +++ b/platform/core-api/src/com/intellij/openapi/command/WriteCommandAction.java @@ -24,6 +24,8 @@ import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.ThrowableComputable; import com.intellij.psi.PsiFile; +import com.intellij.util.ArrayUtil; +import com.intellij.util.ThrowableRunnable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -33,17 +35,84 @@ import java.util.Collection; public abstract class WriteCommandAction extends BaseActionRunnable { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.command.WriteCommandAction"); + private static final String DEFAULT_COMMAND_NAME = "Undefined"; + private static final String DEFAULT_GROUP_ID = null; + + public interface Builder { + @NotNull Builder withName(@Nullable String name); + @NotNull Builder withGroupId(@Nullable String groupId); + + void run(@NotNull ThrowableRunnable action) throws E; + R compute(@NotNull ThrowableComputable action) throws E; + } + + private static class BuilderImpl implements Builder { + private final Project myProject; + private final PsiFile[] myFiles; + private String myCommandName = DEFAULT_COMMAND_NAME; + private String myGroupId = DEFAULT_GROUP_ID; + + private BuilderImpl(Project project, PsiFile... files) { + myProject = project; + myFiles = files; + } + + @NotNull + @Override + public Builder withName(String name) { + myCommandName = name; + return this; + } + + @NotNull + @Override + public Builder withGroupId(String groupId) { + myGroupId = groupId; + return this; + } + + @Override + public void run(@NotNull final ThrowableRunnable action) throws E { + new WriteCommandAction(myProject, myCommandName, myGroupId, myFiles) { + @Override + protected void run(@NotNull Result result) throws Throwable { + action.run(); + } + }.execute(); + } + + @Override + public R compute(@NotNull final ThrowableComputable action) throws E { + return new WriteCommandAction(myProject, myCommandName, myGroupId, myFiles) { + @Override + protected void run(@NotNull Result result) throws Throwable { + result.setResult(action.compute()); + } + }.execute().getResultObject(); + } + } + + @NotNull + public static Builder writeCommandAction(Project project) { + return new BuilderImpl(project); + } + + @NotNull + public static Builder writeCommandAction(@NotNull PsiFile first, @NotNull PsiFile... others) { + return new BuilderImpl(first.getProject(), ArrayUtil.prepend(first, others)); + } + private final String myCommandName; private final String myGroupID; private final Project myProject; private final PsiFile[] myPsiFiles; protected WriteCommandAction(@Nullable Project project, /*@NotNull*/ PsiFile... files) { - this(project, "Undefined", files); + this(project, DEFAULT_COMMAND_NAME, files); } protected WriteCommandAction(@Nullable Project project, @Nullable String commandName, /*@NotNull*/ PsiFile... files) { - this(project, commandName, null, files); + this(project, commandName, DEFAULT_GROUP_ID, files); } protected WriteCommandAction(@Nullable Project project, @Nullable String commandName, @Nullable String groupID, /*@NotNull*/ PsiFile... files) { @@ -186,7 +255,7 @@ public abstract class WriteCommandAction extends BaseActionRunnable { } public static void runWriteCommandAction(Project project, @NotNull Runnable runnable) { - runWriteCommandAction(project, "Undefined", null, runnable); + runWriteCommandAction(project, DEFAULT_COMMAND_NAME, DEFAULT_GROUP_ID, runnable); } public static void runWriteCommandAction(Project project, diff --git a/platform/core-api/src/com/intellij/openapi/project/DumbModePermission.java b/platform/core-api/src/com/intellij/openapi/project/DumbModePermission.java index 54aa5a0acdfb..c0faa2684159 100644 --- a/platform/core-api/src/com/intellij/openapi/project/DumbModePermission.java +++ b/platform/core-api/src/com/intellij/openapi/project/DumbModePermission.java @@ -15,10 +15,7 @@ */ package com.intellij.openapi.project; -/** - * Permits the dumb mode to start at a specific moment, either modally or in background. - * @see DumbService#allowStartingDumbModeInside(DumbModePermission, Runnable) - */ +@Deprecated public enum DumbModePermission { MAY_START_MODAL, MAY_START_BACKGROUND 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 bfaef2ab5ce6..364358922427 100644 --- a/platform/core-api/src/com/intellij/openapi/project/DumbService.java +++ b/platform/core-api/src/com/intellij/openapi/project/DumbService.java @@ -20,6 +20,7 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.ModificationTracker; import com.intellij.openapi.util.NotNullLazyKey; @@ -208,10 +209,29 @@ public abstract class DumbService { return new ArrayList(collection); } + /** + * Queues a task to be executed in "dumb mode", where access to indices is forbidden. Tasks are executed sequentially + * in background unless {@link #completeJustSubmittedTasks()} is called in the same dispatch thread activity.

+ * + * Tasks can specify custom "equality" policy via their constructor. Calling this method has no effect if an "equal" task is already enqueued (but not yet running). + */ public abstract void queueTask(@NotNull DumbModeTask task); - + + /** + * Cancels the given task. If it's in the queue, it won't be executed. If it's already running, its {@link com.intellij.openapi.progress.ProgressIndicator} is canceled, so the next {@link ProgressManager#checkCanceled()} call + * will throw {@link com.intellij.openapi.progress.ProcessCanceledException}. + */ public abstract void cancelTask(@NotNull DumbModeTask task); + /** + * Runs the "just submitted" tasks under a modal dialog. "Just submitted" means that tasks were queued for execution + * earlier within the same Swing event dispatch thread event processing, and there were no other tasks already running at that moment. Otherwise this method does nothing.

+ * + * This functionality can be useful in refactorings (invoked in "smart mode"), when after VFS or root changes + * (which could start "dumb mode") some reference resolve is required (which again requires "smart mode"). + */ + public abstract void completeJustSubmittedTasks(); + public abstract JComponent wrapGently(@NotNull JComponent dumbUnawareContent, @NotNull Disposable parentDisposable); public void makeDumbAware(@NotNull final JComponent component, @NotNull Disposable disposable) { @@ -279,19 +299,13 @@ public abstract class DumbService { public abstract boolean isAlternativeResolveEnabled(); /** - * By default, dumb mode tasks (including indexing) are allowed in non-modal state only. The reason is that - * when some code shows a dialog, it probably does't expect that after the dialog is closed the dumb mode will be on. - * Therefore any dumb mode started within a dialog is considered a mistake, performed under modal progress and reported as an exception.

- * - * If the dialog (e.g. Project Structure) starting background dumb mode is an expected situation, the dumb mode should be started inside the runnable - * passed to this method. This will suppress the exception and allow either modal or background indexing. Note that this will only affect the invocation time - * modality state, so showing other dialogs from within the runnable and starting dumb mode from them would still result in an assertion failure.

- * - * If this exception occurs inside invokeLater call which happens to run when a modal dialog is shown, the correct fix is supplying an explicit modality state - * in {@link com.intellij.openapi.application.Application#invokeLater(Runnable, ModalityState)}. + * Obsolete, does nothing, just executes the passed runnable. + * @see #completeJustSubmittedTasks() */ + @SuppressWarnings({"deprecation", "unused"}) + @Deprecated public static void allowStartingDumbModeInside(@NotNull DumbModePermission permission, @NotNull Runnable runnable) { - ServiceManager.getService(DumbPermissionService.class).allowStartingDumbModeInside(permission, runnable); + runnable.run(); } /** diff --git a/platform/core-api/src/com/intellij/openapi/vfs/impl/AddonlyKeylessHash.java b/platform/core-api/src/com/intellij/openapi/vfs/impl/AddonlyKeylessHash.java new file mode 100644 index 000000000000..176fd1da1779 --- /dev/null +++ b/platform/core-api/src/com/intellij/openapi/vfs/impl/AddonlyKeylessHash.java @@ -0,0 +1,101 @@ +/* + * 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.openapi.vfs.impl; + +import gnu.trove.PrimeFinder; + +/** + * IdentityHashMap that have the (immutable) keys stored in values (2 times more compact). + */ +final class AddonlyKeylessHash { + private int size; + private Object[] entries; + private final KeyValueMapper keyValueMapper; + + public AddonlyKeylessHash(KeyValueMapper _keyValueMapper) { + this(4, _keyValueMapper); + } + + public AddonlyKeylessHash(int expectedSize, KeyValueMapper _keyValueMapper) { + int i = PrimeFinder.nextPrime(5 * expectedSize / 4 + 1); + entries = new Object[i]; + keyValueMapper = _keyValueMapper; + } + + public int size() { + return size; + } + + public void add(V item) { + if (size >= (4 * entries.length) / 5) rehash(); + V v = doPut(entries, item); + if (v == null) size++; + } + + private V doPut(Object[] a, V o) { + K key = keyValueMapper.key(o); + int index = hashIndex(a, key); + V obj = (V)a[index]; + a[index] = o; + return obj; + } + + private int hashIndex(Object[] a, K key) { + int hash = keyValueMapper.hash(key) & 0x7fffffff; + int index = hash % a.length; + V candidate = (V)a[index]; + if (candidate == null || keyValueMapper.valueHasKey(candidate, key)) { + return index; + } + + final int probe = 1 + (hash % (a.length - 2)); + + do { + index -= probe; + if (index < 0) index += a.length; + + candidate = (V)a[index]; + } + while (candidate != null && !keyValueMapper.valueHasKey(candidate, key)); + + return index; + } + + private void rehash() { + Object[] b = new Object[PrimeFinder.nextPrime(entries.length * 2)]; + for (int i = entries.length; --i >= 0; ) { + V ns = (V)entries[i]; + if (ns != null) doPut(b, ns); + } + entries = b; + } + + public V get(K key) { + return (V)entries[hashIndex(entries, key)]; + } + + public abstract static class KeyValueMapper { + public abstract int hash(K k); + + public abstract K key(V v); + + public boolean valueHasKey(V value, K key) { + return key == key(value); // identity + } + + protected boolean isIdentity() { return true; } + } +} diff --git a/platform/core-api/src/com/intellij/openapi/vfs/impl/ArchiveHandler.java b/platform/core-api/src/com/intellij/openapi/vfs/impl/ArchiveHandler.java index 958d92f73dee..26fcff811c8f 100644 --- a/platform/core-api/src/com/intellij/openapi/vfs/impl/ArchiveHandler.java +++ b/platform/core-api/src/com/intellij/openapi/vfs/impl/ArchiveHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * 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. @@ -21,8 +21,10 @@ import com.intellij.openapi.util.io.FileAttributes; import com.intellij.openapi.util.io.FileSystemUtil; import com.intellij.reference.SoftReference; import com.intellij.util.ArrayUtil; +import com.intellij.util.SmartList; import com.intellij.util.text.ByteArrayCharSequence; -import gnu.trove.THashSet; +import gnu.trove.THashMap; +import gnu.trove.TObjectObjectProcedure; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -30,8 +32,8 @@ import java.io.File; import java.io.IOException; import java.lang.ref.Reference; import java.util.Collections; +import java.util.List; import java.util.Map; -import java.util.Set; public abstract class ArchiveHandler { public static final long DEFAULT_LENGTH = 0L; @@ -44,11 +46,6 @@ public abstract class ArchiveHandler { public final long length; public final long timestamp; - /** @deprecated use {@link EntryInfo#EntryInfo(CharSequence, boolean, long, long, EntryInfo)} instead (to be removed in IDEA 16) */ - public EntryInfo(EntryInfo parent, @NotNull String shortName, boolean isDirectory, long length, long timestamp) { - this(shortName, isDirectory, length, timestamp, parent); - } - public EntryInfo(@NotNull CharSequence shortName, boolean isDirectory, long length, long timestamp, @Nullable EntryInfo parent) { this.parent = parent; this.shortName = shortName; @@ -61,6 +58,7 @@ public abstract class ArchiveHandler { private final File myPath; private final Object myLock = new Object(); private volatile Reference> myEntries = new SoftReference>(null); + private volatile Reference> myChildrenEntries = new SoftReference>(null); private boolean myCorrupted; protected ArchiveHandler(@NotNull String path) { @@ -89,17 +87,84 @@ public abstract class ArchiveHandler { EntryInfo entry = getEntryInfo(relativePath); if (entry == null || !entry.isDirectory) return ArrayUtil.EMPTY_STRING_ARRAY; - Set names = new THashSet(); - for (EntryInfo info : getEntriesMap().values()) { - if (info.parent == entry) { - names.add(info.shortName.toString()); + AddonlyKeylessHash result = getParentChildrenMap(); + + Object o = result.get(entry); + if (o == null) { + return ArrayUtil.EMPTY_STRING_ARRAY; // directories without children + } + if (o instanceof EntryInfo) { + return new String[] {((EntryInfo)o).shortName.toString()}; + } + EntryInfo[] infos = (EntryInfo[])o; + + String[] names = new String[infos.length]; + for (int i = 0; i < infos.length; ++i) { + names[i] = infos[i].shortName.toString(); + } + return names; + } + + @NotNull + private AddonlyKeylessHash getParentChildrenMap() { + AddonlyKeylessHash map = SoftReference.dereference(myChildrenEntries); + if (map == null) { + synchronized (myLock) { + map = SoftReference.dereference(myChildrenEntries); + + if (map == null) { + if (myCorrupted) { + map = new AddonlyKeylessHash(ourKeyValueMapper); + } + else { + try { + map = createParentChildrenMap(); + } + catch (Exception e) { + myCorrupted = true; + Logger.getInstance(getClass()).warn(e.getMessage() + ": " + myPath, e); + map = new AddonlyKeylessHash(ourKeyValueMapper); + } + } + + myChildrenEntries = new SoftReference>(map); + } } } - return ArrayUtil.toStringArray(names); + return map; + } + + private AddonlyKeylessHash createParentChildrenMap() { + THashMap> map = new THashMap>(); + for (EntryInfo info : getEntriesMap().values()) { + if (info.isDirectory && !map.containsKey(info)) map.put(info, new SmartList()); + if (info.parent != null) { + List parentChildren = map.get(info.parent); + if (parentChildren == null) map.put(info.parent, parentChildren = new SmartList()); + parentChildren.add(info); + } + } + + final AddonlyKeylessHash result = new AddonlyKeylessHash(map.size(), ourKeyValueMapper); + map.forEachEntry(new TObjectObjectProcedure>() { + @Override + public boolean execute(EntryInfo a, List b) { + int numberOfChildren = b.size(); + if (numberOfChildren == 1) { + result.add(b.get(0)); + } + else if (numberOfChildren > 1) { + result.add(b.toArray(new EntryInfo[numberOfChildren])); + } + return true; + } + }); + return result; } public void dispose() { myEntries.clear(); + myChildrenEntries.clear(); } @Nullable @@ -167,4 +232,17 @@ public abstract class ArchiveHandler { @NotNull public abstract byte[] contentsToByteArray(@NotNull String relativePath) throws IOException; -} + + private static final AddonlyKeylessHash.KeyValueMapper ourKeyValueMapper = new AddonlyKeylessHash.KeyValueMapper() { + @Override + public int hash(EntryInfo info) { + return System.identityHashCode(info); + } + + @Override + public EntryInfo key(Object o) { + if (o instanceof EntryInfo) return ((EntryInfo)o).parent; + return ((EntryInfo[])o)[0].parent; + } + }; +} \ No newline at end of file diff --git a/platform/core-api/src/com/intellij/psi/util/PsiModificationTracker.java b/platform/core-api/src/com/intellij/psi/util/PsiModificationTracker.java index 343bc721a12e..615f35140193 100644 --- a/platform/core-api/src/com/intellij/psi/util/PsiModificationTracker.java +++ b/platform/core-api/src/com/intellij/psi/util/PsiModificationTracker.java @@ -29,21 +29,21 @@ import org.jetbrains.annotations.NotNull; * to check if PSI has changed since they accessed it last time. This can be used to flush and rebuild various internal caches. * See {@link #getModificationCount()}, {@link #getJavaStructureModificationCount()}, {@link #getOutOfCodeBlockModificationCount()} * - *

  • Make a {@link com.intellij.psi.util.CachedValue} instance dependent on a specific PSI modification tracker. - * To achieve that, one should can one of the constants in this interface as {@link com.intellij.psi.util.CachedValueProvider.Result} + *
  • Make a {@link CachedValue} instance dependent on a specific PSI modification tracker. + * To achieve that, one should can one of the constants in this interface as {@link CachedValueProvider.Result} * dependencies. * See {@link #MODIFICATION_COUNT}, {@link #JAVA_STRUCTURE_MODIFICATION_COUNT}, {@link #OUT_OF_CODE_BLOCK_MODIFICATION_COUNT} * *
  • Subscribe to any PSI change (for example, to drop caches in the listener manually). - * See {@link com.intellij.psi.util.PsiModificationTracker.Listener} + * See {@link PsiModificationTracker.Listener} * * */ public interface PsiModificationTracker extends ModificationTracker { /** - * Provides a way to get the instance of {@link com.intellij.psi.util.PsiModificationTracker} corresponding to a given project. - * @see #getInstance(com.intellij.openapi.project.Project) + * Provides a way to get the instance of {@link PsiModificationTracker} corresponding to a given project. + * @see #getInstance(Project) */ class SERVICE { private SERVICE() { @@ -51,7 +51,7 @@ public interface PsiModificationTracker extends ModificationTracker { /** * @param project - * @return The instance of {@link com.intellij.psi.util.PsiModificationTracker} corresponding to the given project. + * @return The instance of {@link PsiModificationTracker} corresponding to the given project. */ public static PsiModificationTracker getInstance(Project project) { return ServiceManager.getService(project, PsiModificationTracker.class); @@ -59,23 +59,23 @@ public interface PsiModificationTracker extends ModificationTracker { } /** - * This key can be passed as a dependency in a {@link com.intellij.psi.util.CachedValueProvider}. - * The corresponding {@link com.intellij.psi.util.CachedValue} will then be flushed on every physical PSI change. + * This key can be passed as a dependency in a {@link CachedValueProvider}. + * The corresponding {@link CachedValue} will then be flushed on every physical PSI change. * @see #getModificationCount() */ Key MODIFICATION_COUNT = Key.create("MODIFICATION_COUNT"); /** - * This key can be passed as a dependency in a {@link com.intellij.psi.util.CachedValueProvider}. - * The corresponding {@link com.intellij.psi.util.CachedValue} will then be flushed on every physical PSI change that doesn't happen inside a Java code block. + * This key can be passed as a dependency in a {@link CachedValueProvider}. + * The corresponding {@link CachedValue} will then be flushed on every physical PSI change that doesn't happen inside a Java code block. * This can include changes on Java class or file level, or changes in non-Java files, e.g. XML. Rarely needed. * @see #getOutOfCodeBlockModificationCount() */ Key OUT_OF_CODE_BLOCK_MODIFICATION_COUNT = Key.create("OUT_OF_CODE_BLOCK_MODIFICATION_COUNT"); /** - * This key can be passed as a dependency in a {@link com.intellij.psi.util.CachedValueProvider}. - * The corresponding {@link com.intellij.psi.util.CachedValue} will then be flushed on every physical PSI change that can affect Java structure and resolve. + * This key can be passed as a dependency in a {@link CachedValueProvider}. + * The corresponding {@link CachedValue} will then be flushed on every physical PSI change that can affect Java structure and resolve. * @see #getJavaStructureModificationCount() */ Key JAVA_STRUCTURE_MODIFICATION_COUNT = Key.create("JAVA_STRUCTURE_MODIFICATION_COUNT"); diff --git a/platform/core-impl/src/com/intellij/mock/MockDumbService.java b/platform/core-impl/src/com/intellij/mock/MockDumbService.java index e97a9f64c7f3..cf06dfb0ca17 100644 --- a/platform/core-impl/src/com/intellij/mock/MockDumbService.java +++ b/platform/core-impl/src/com/intellij/mock/MockDumbService.java @@ -66,6 +66,10 @@ public class MockDumbService extends DumbService { @Override public void cancelTask(@NotNull DumbModeTask task) { } + @Override + public void completeJustSubmittedTasks() { + } + @Override public JComponent wrapGently(@NotNull JComponent dumbUnawareContent, @NotNull Disposable parentDisposable) { throw new UnsupportedOperationException(); 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 3b601125e8fc..30ff377de7f7 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 @@ -387,8 +387,8 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { @Override public RangeMarker getRangeGuard(int start, int end) { for (RangeMarker block : myGuardedBlocks) { - if (rangesIntersect(start, true, block.getStartOffset(), block.isGreedyToLeft(), end, true, block.getEndOffset(), - block.isGreedyToRight())) { + if (rangesIntersect(start, end, true, true, + block.getStartOffset(), block.getEndOffset(), block.isGreedyToLeft(), block.isGreedyToRight())) { return block; } } @@ -411,15 +411,13 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { return start <= offset && offset < end; } - private static boolean rangesIntersect(int start0, boolean leftInclusive0, - int start1, boolean leftInclusive1, - int end0, boolean rightInclusive0, - int end1, boolean rightInclusive1) { - if (start0 > start1 || start0 == start1 && !leftInclusive0) { - if (end1 == start0) return leftInclusive0 && rightInclusive1; + private static boolean rangesIntersect(int start0, int end0, boolean start0Inclusive, boolean end0Inclusive, + int start1, int end1, boolean start1Inclusive, boolean end1Inclusive) { + if (start0 > start1 || start0 == start1 && !start0Inclusive) { + if (end1 == start0) return start0Inclusive && end1Inclusive; return end1 > start0; } - if (end0 == start1) return leftInclusive1 && rightInclusive0; + if (end0 == start1) return start1Inclusive && end0Inclusive; return end0 > start1; } @@ -558,7 +556,6 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { final int newStringLength = s.length(); final CharSequence chars = myText; int newStartInString = 0; - int newEndInString = newStringLength; while (newStartInString < newStringLength && startOffset < endOffset && s.charAt(newStartInString) == chars.charAt(startOffset)) { @@ -566,6 +563,7 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { newStartInString++; } + int newEndInString = newStringLength; while (endOffset > startOffset && newEndInString > newStartInString && s.charAt(newEndInString - 1) == chars.charAt(endOffset - 1)) { @@ -643,14 +641,14 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { * * *

    - * There is a possible case that 'before change' notification produces new change. We have a problem then - imagine - * that initial change was 'replace particular range at document end' and 'nested change' was to - * 'remove text at document end'. That means that when initial change will be actually performed, the document may be + * There is a possible case that {@code 'before change'} notification produces new change. We have a problem then - imagine + * that initial change was {@code 'replace particular range at document end'} and {@code 'nested change'} was to + * {@code 'remove text at document end'}. That means that when initial change will be actually performed, the document may be * not long enough to contain target range. *

    - * Current method allows to check if document change is a 'nested call'. + * Current method allows to check if document change is a {@code 'nested call'}. * - * @throws IllegalStateException if this method is called during a 'nested document modification' + * @throws IllegalStateException if this method is called during a {@code 'nested document modification'} */ private void assertNotNestedModification() throws IllegalStateException { if (myChangeInProgress) { @@ -777,7 +775,13 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx { try { if (LOG.isDebugEnabled()) LOG.debug(event.toString()); + assert event.getOldFragment().length() == event.getOldLength() : "event.getOldFragment().length() = " + event.getOldFragment().length()+"; event.getOldLength() = " + event.getOldLength(); + assert event.getNewFragment().length() == event.getNewLength() : "event.getNewFragment().length() = " + event.getNewFragment().length()+"; event.getNewLength() = " + event.getNewLength(); + assert prevText.length() + event.getNewLength() - event.getOldLength() == getTextLength() : "prevText.length() = " + prevText.length()+ "; event.getNewLength() = " + event.getNewLength()+ "; event.getOldLength() = " + event.getOldLength()+ "; getTextLength() = " + getTextLength(); + myLineSet = getLineSet().update(prevText, event.getOffset(), event.getOffset() + event.getOldLength(), event.getNewFragment(), event.isWholeTextReplaced()); + assert getTextLength() == myLineSet.getLength() : "getTextLength() = " + getTextLength()+ "; myLineSet.getLength() = " + myLineSet.getLength(); + myFrozen = null; if (myTabTrackingRequestors > 0) { updateMightContainTabs(event.getNewFragment()); diff --git a/platform/core-impl/src/com/intellij/openapi/editor/impl/LineSet.java b/platform/core-impl/src/com/intellij/openapi/editor/impl/LineSet.java index b4033a107c84..282978a7c765 100644 --- a/platform/core-impl/src/com/intellij/openapi/editor/impl/LineSet.java +++ b/platform/core-impl/src/com/intellij/openapi/editor/impl/LineSet.java @@ -71,7 +71,7 @@ public class LineSet{ return createLineSet(replacement, !wholeTextReplaced); } - LineSet result = isSingleLineChange(prevText, start, end, replacement) + LineSet result = isSingleLineChange(start, end, replacement) ? updateInsideOneLine(findLineIndex(start), replacement.length() - (end - start)) : genericUpdate(prevText, start, end, replacement); @@ -84,7 +84,7 @@ public class LineSet{ return wholeTextReplaced ? result.clearModificationFlags() : result; } - private boolean isSingleLineChange(@NotNull CharSequence prevText, int start, int end, @NotNull CharSequence replacement) { + private boolean isSingleLineChange(int start, int end, @NotNull CharSequence replacement) { if (start == 0 && end == myLength && replacement.length() == 0) return false; int startLine = findLineIndex(start); @@ -256,6 +256,9 @@ public class LineSet{ doTest = testMode; } - private static boolean doTest = false; + private static boolean doTest; + int getLength() { + return myLength; + } } 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 4ee1dc2e7324..b556a63f62e6 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 @@ -31,8 +31,7 @@ public class DocumentEventImpl extends DocumentEvent { private final long myOldTimeStamp; private final boolean myIsWholeDocReplaced; private Diff.Change myChange; - private static final Diff.Change TOO_BIG_FILE = new Diff.Change(0, 0, 0, 0, null) { - }; + private static final Diff.Change TOO_BIG_FILE = new Diff.Change(0, 0, 0, 0, null); private final int myInitialStartOffset; private final int myInitialOldLength; @@ -68,6 +67,9 @@ public class DocumentEventImpl extends DocumentEvent { myOldTimeStamp = oldTimeStamp; myIsWholeDocReplaced = getDocument().getTextLength() != 0 && wholeTextReplaced; + assert initialStartOffset >= 0 : initialStartOffset; + assert initialOldLength >= 0 : initialOldLength; + assert initialStartOffset+initialOldLength <= document.getTextLength() : "initialStartOffset = " + initialStartOffset + "; initialOldLength = " + initialOldLength+";document.getTextLength() = " + document.getTextLength(); } @Override diff --git a/platform/core-impl/src/com/intellij/openapi/fileEditor/impl/LoadTextUtil.java b/platform/core-impl/src/com/intellij/openapi/fileEditor/impl/LoadTextUtil.java index d82d030c8347..2843c8bf0b6f 100644 --- a/platform/core-impl/src/com/intellij/openapi/fileEditor/impl/LoadTextUtil.java +++ b/platform/core-impl/src/com/intellij/openapi/fileEditor/impl/LoadTextUtil.java @@ -98,17 +98,7 @@ public final class LoadTextUtil { detectedLineSeparator = "\n"; } - CharSequence result; - if (buffer.length() == dst) { - result = buffer; - } - else { - // in Mac JDK CharBuffer.subSequence() signature differs from Oracle's - // more than that, the signature has changed between jd6 and jdk7, - // so use more generic CharSequence.subSequence() just in case - @SuppressWarnings("UnnecessaryLocalVariable") CharSequence seq = buffer; - result = seq.subSequence(0, dst); - } + CharSequence result = buffer.length() == dst ? buffer : buffer.subSequence(0, dst); return Pair.create(result, detectedLineSeparator); } @@ -142,13 +132,9 @@ public final class LoadTextUtil { @NotNull public static Charset detectCharsetAndSetBOM(@NotNull VirtualFile virtualFile, @NotNull byte[] content) { - return doDetectCharsetAndSetBOM(virtualFile, content, true).getFirst(); + return doDetectCharsetAndSetBOM(virtualFile, content, true, virtualFile.getFileType()).getFirst(); } - @NotNull - private static Pair.NonNull doDetectCharsetAndSetBOM(@NotNull VirtualFile virtualFile, @NotNull byte[] content, boolean saveBOM) { - return doDetectCharsetAndSetBOM(virtualFile, content, saveBOM, virtualFile.getFileType()); - } @NotNull private static Pair.NonNull doDetectCharsetAndSetBOM(@NotNull VirtualFile virtualFile, @NotNull byte[] content, boolean saveBOM, @NotNull FileType fileType) { @NotNull Charset charset = virtualFile.isCharsetSet() ? virtualFile.getCharset() : detectCharset(virtualFile, content,fileType); diff --git a/platform/core-impl/src/com/intellij/psi/PsiAnchor.java b/platform/core-impl/src/com/intellij/psi/PsiAnchor.java index e6f497ffa6b4..d4e2b98f78c2 100644 --- a/platform/core-impl/src/com/intellij/psi/PsiAnchor.java +++ b/platform/core-impl/src/com/intellij/psi/PsiAnchor.java @@ -453,14 +453,9 @@ public abstract class PsiAnchor { if (myProject.isDisposed() || !myVirtualFile.isValid()) { return null; } - final PsiFile file = PsiManager.getInstance(myProject).findFile(myVirtualFile); - if (file == null) { - return null; - } - if (file.getLanguage() == myLanguage) { - return file; - } - return file.getViewProvider().getPsi(myLanguage); + FileViewProvider viewProvider = PsiManager.getInstance(myProject).findViewProvider(myVirtualFile); + PsiFile file = viewProvider == null ? null : viewProvider.getPsi(myLanguage); + return file instanceof PsiFileWithStubSupport ? file : null; } @Override diff --git a/platform/core-impl/src/com/intellij/psi/impl/file/PsiDirectoryImpl.java b/platform/core-impl/src/com/intellij/psi/impl/file/PsiDirectoryImpl.java index 485cabe5768c..8563d63b6553 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/file/PsiDirectoryImpl.java +++ b/platform/core-impl/src/com/intellij/psi/impl/file/PsiDirectoryImpl.java @@ -25,6 +25,7 @@ import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.impl.LoadTextUtil; import com.intellij.openapi.progress.ProgressIndicatorProvider; +import com.intellij.openapi.project.DumbService; import com.intellij.openapi.ui.Queryable; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.TextRange; @@ -366,6 +367,8 @@ public class PsiDirectoryImpl extends PsiElementBase implements PsiDirectory, Qu } if (copyVFile == null) throw new IncorrectOperationException("File was not copied: " + vFile); + DumbService.getInstance(getProject()).completeJustSubmittedTasks(); + final PsiFile copyPsi = myManager.findFile(copyVFile); if (copyPsi == null) throw new IncorrectOperationException("Could not find file " + copyVFile + " after copying " + vFile); updateAddedFile(copyPsi); diff --git a/platform/core-impl/src/com/intellij/psi/impl/file/PsiFileImplUtil.java b/platform/core-impl/src/com/intellij/psi/impl/file/PsiFileImplUtil.java index aa5e498e2082..db5a8e7cf50a 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/file/PsiFileImplUtil.java +++ b/platform/core-impl/src/com/intellij/psi/impl/file/PsiFileImplUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * 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. @@ -16,7 +16,6 @@ package com.intellij.psi.impl.file; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileTypes.FileType; @@ -59,7 +58,6 @@ public class PsiFileImplUtil { } public static void checkSetName(@NotNull PsiFile file, @NotNull String name) throws IncorrectOperationException { - ApplicationManager.getApplication().assertWriteAccessAllowed(); VirtualFile vFile = file.getVirtualFile(); VirtualFile parentFile = vFile.getParent(); if (parentFile == null) return; 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 8b1142c6d8f3..804a803f5be7 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 @@ -38,16 +38,19 @@ class FileElementInfo extends SmartPointerElementInfo { private final VirtualFile myVirtualFile; private final Project myProject; private final Language myLanguage; + private final Class myFileClass; public FileElementInfo(@NotNull final PsiFile file) { myVirtualFile = file.getVirtualFile(); myProject = file.getProject(); myLanguage = LanguageUtil.getRootLanguage(file); + myFileClass = file.getClass(); } @Override public PsiElement restoreElement() { - return SelfElementInfo.restoreFileFromVirtual(myVirtualFile, myProject, myLanguage); + PsiFile file = SelfElementInfo.restoreFileFromVirtual(myVirtualFile, myProject, myLanguage); + return myFileClass.isInstance(file) ? file : null; } @Override diff --git a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/HardElementInfo.java b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/HardElementInfo.java index a88db5febb74..6fe4bbd8fe95 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/HardElementInfo.java +++ b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/HardElementInfo.java @@ -47,7 +47,7 @@ class HardElementInfo extends SmartPointerElementInfo { @Override public PsiFile restoreFile() { - return myElement.getContainingFile(); + return myElement.isValid() ? myElement.getContainingFile() : null; } @Override diff --git a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/Identikit.java b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/Identikit.java index 5b7c186566b7..17d934a81cdd 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/Identikit.java +++ b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/Identikit.java @@ -195,7 +195,8 @@ public abstract class Identikit { @Override public PsiElement findPsiElement(@NotNull PsiFile file, int startOffset, int endOffset) { PsiElement anchor = myAnchorInfo.findPsiElement(file, startOffset, endOffset); - return anchor == null ? null : myAnchorProvider.restoreElement(anchor); + PsiElement element = anchor == null ? null : myAnchorProvider.restoreElement(anchor); + return element != null && myElementInfo.isAcceptable(element) ? element : null; } @NotNull diff --git a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/MarkerCache.java b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/MarkerCache.java index 018bd8c12e86..05bc1cf7261e 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/MarkerCache.java +++ b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/MarkerCache.java @@ -107,15 +107,15 @@ class MarkerCache { for (DocumentEvent event : events) { final FrozenDocument before = frozen; final DocumentEvent corrected; - if ((event instanceof RetargetRangeMarkers)) { + if (event instanceof RetargetRangeMarkers) { RetargetRangeMarkers retarget = (RetargetRangeMarkers)event; corrected = new RetargetRangeMarkers(frozen, retarget.getStartOffset(), retarget.getEndOffset(), retarget.getMoveDestinationOffset()); } else { - frozen = frozen.applyEvent(event, 0); corrected = new DocumentEventImpl(frozen, event.getOffset(), event.getOldFragment(), event.getNewFragment(), event.getOldTimeStamp(), event.isWholeTextReplaced(), ((DocumentEventImpl) event).getInitialStartOffset(), ((DocumentEventImpl) event).getInitialOldLength()); + frozen = frozen.applyEvent(event, 0); } int i = 0; diff --git a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPsiElementPointerImpl.java b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPsiElementPointerImpl.java index 91bad35328c5..c88c6962cdef 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPsiElementPointerImpl.java +++ b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPsiElementPointerImpl.java @@ -46,18 +46,15 @@ class SmartPsiElementPointerImpl implements SmartPointerEx private Reference myElement; private final SmartPointerElementInfo myElementInfo; - private final Class myElementClass; private byte myReferenceCount = 1; @Nullable SmartPointerTracker.PointerReference pointerReference; SmartPsiElementPointerImpl(@NotNull Project project, @NotNull E element, @Nullable PsiFile containingFile, boolean forInjected) { - this(element, createElementInfo(project, element, containingFile, forInjected), element.getClass()); + this(element, createElementInfo(project, element, containingFile, forInjected)); } SmartPsiElementPointerImpl(@NotNull E element, - @NotNull SmartPointerElementInfo elementInfo, - @NotNull Class elementClass) { + @NotNull SmartPointerElementInfo elementInfo) { ApplicationManager.getApplication().assertReadAccessAllowed(); - myElementClass = elementClass; myElementInfo = elementInfo; cacheElement(element); } @@ -93,7 +90,7 @@ class SmartPsiElementPointerImpl implements SmartPointerEx E doRestoreElement() { //noinspection unchecked E element = (E)myElementInfo.restoreElement(); - if (element != null && (!element.getClass().equals(myElementClass) || !element.isValid())) { + if (element != null && !element.isValid()) { return null; } return element; diff --git a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPsiFileRangePointerImpl.java b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPsiFileRangePointerImpl.java index 4f08cc1f34df..1c24a2069aa2 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPsiFileRangePointerImpl.java +++ b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPsiFileRangePointerImpl.java @@ -28,7 +28,7 @@ import org.jetbrains.annotations.NotNull; */ class SmartPsiFileRangePointerImpl extends SmartPsiElementPointerImpl implements SmartPsiFileRange { SmartPsiFileRangePointerImpl(@NotNull PsiFile containingFile, @NotNull ProperTextRange range, boolean forInjected) { - super(containingFile, createElementInfo(containingFile, range, forInjected), PsiFile.class); + super(containingFile, createElementInfo(containingFile, range, forInjected)); } @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 217a39d7c8c1..5298f2468a71 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 @@ -433,7 +433,10 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF @Override public long getModificationStamp() { - return myModificationStamp; + PsiElement context = getContext(); + PsiFile contextFile = context == null ? null : context.getContainingFile(); + long contextStamp = contextFile == null ? 0 : contextFile.getModificationStamp(); + return myModificationStamp + contextStamp; } @Override diff --git a/platform/core-impl/src/com/intellij/psi/impl/source/tree/LeafElement.java b/platform/core-impl/src/com/intellij/psi/impl/source/tree/LeafElement.java index 73d53ea22198..42ac36916205 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/source/tree/LeafElement.java +++ b/platform/core-impl/src/com/intellij/psi/impl/source/tree/LeafElement.java @@ -64,16 +64,17 @@ public abstract class LeafElement extends TreeElement { @NotNull @Override public String getText() { - if (myText.length() > 1000 && !(myText instanceof String)) { // e.g. a large text file - String text = SoftReference.dereference(getUserData(CACHED_TEXT)); - if (text == null) { - text = myText.toString(); - putUserData(CACHED_TEXT, new SoftReference(text)); + CharSequence text = myText; + if (text.length() > 1000 && !(text instanceof String)) { // e.g. a large text file + String cachedText = SoftReference.dereference(getUserData(CACHED_TEXT)); + if (cachedText == null) { + cachedText = text.toString(); + putUserData(CACHED_TEXT, new SoftReference(cachedText)); } - return text; + return cachedText; } - return myText.toString(); + return text.toString(); } public char charAt(int position) { @@ -99,10 +100,10 @@ public abstract class LeafElement extends TreeElement { @Override public boolean textContains(char c) { final CharSequence text = myText; - final int len = myText.length(); + final int len = text.length(); if (len > TEXT_MATCHES_THRESHOLD) { - char[] chars = CharArrayUtil.fromSequenceWithoutCopying(myText); + char[] chars = CharArrayUtil.fromSequenceWithoutCopying(text); if (chars != null) { for (char aChar : chars) { if (aChar == c) return true; @@ -121,8 +122,7 @@ public abstract class LeafElement extends TreeElement { @Override protected int textMatches(@NotNull CharSequence buffer, int start) { assert start >= 0 : start; - final CharSequence text = myText; - return leafTextMatches(text, buffer, start); + return leafTextMatches(myText, buffer, start); } static int leafTextMatches(@NotNull CharSequence text, @NotNull CharSequence buffer, int start) { diff --git a/platform/diff-impl/src/com/intellij/diff/requests/UnknownFileTypeDiffRequest.java b/platform/diff-impl/src/com/intellij/diff/requests/UnknownFileTypeDiffRequest.java index fe1eb3cbec23..667ede984349 100644 --- a/platform/diff-impl/src/com/intellij/diff/requests/UnknownFileTypeDiffRequest.java +++ b/platform/diff-impl/src/com/intellij/diff/requests/UnknownFileTypeDiffRequest.java @@ -22,8 +22,6 @@ import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.FileTypeManager; import com.intellij.openapi.fileTypes.UnknownFileType; import com.intellij.openapi.fileTypes.ex.FileTypeChooser; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.vcs.changes.issueLinks.LinkMouseListenerBase; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.SimpleColoredComponent; @@ -60,10 +58,8 @@ public class UnknownFileTypeDiffRequest extends ComponentDiffRequest { label.append("Associate", SimpleTextAttributes.LINK_ATTRIBUTES, new Runnable() { @Override public void run() { - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, () -> { - FileType type = FileTypeChooser.associateFileType(myFileName); - if (type != null) onSuccess(context); - }); + FileType type = FileTypeChooser.associateFileType(myFileName); + if (type != null) onSuccess(context); } }); LinkMouseListenerBase.installSingleTagOn(label); diff --git a/platform/editor-ui-ex/src/com/intellij/openapi/editor/colors/impl/AbstractColorsScheme.java b/platform/editor-ui-ex/src/com/intellij/openapi/editor/colors/impl/AbstractColorsScheme.java index 60672f8bf92c..0f12c4e9ce37 100644 --- a/platform/editor-ui-ex/src/com/intellij/openapi/editor/colors/impl/AbstractColorsScheme.java +++ b/platform/editor-ui-ex/src/com/intellij/openapi/editor/colors/impl/AbstractColorsScheme.java @@ -31,6 +31,7 @@ import com.intellij.openapi.options.SchemeManager; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Couple; import com.intellij.openapi.util.WriteExternalException; +import com.intellij.util.JdomKt; import com.intellij.util.PlatformUtils; import com.intellij.util.containers.ContainerUtilRt; import com.intellij.util.containers.HashMap; @@ -531,13 +532,6 @@ public abstract class AbstractColorsScheme implements EditorColorsScheme, Serial } } - private static void addOptionTag(@NotNull Element parentNode, @NotNull String name, @NotNull String value) { - Element element = new Element(OPTION_ELEMENT); - element.setAttribute(NAME_ATTR, name); - element.setAttribute(VALUE_ELEMENT, value); - parentNode.addContent(element); - } - public void writeExternal(Element parentNode) { parentNode.setAttribute(NAME_ATTR, getName()); parentNode.setAttribute(VERSION_ATTR, Integer.toString(myVersion)); @@ -551,7 +545,7 @@ public abstract class AbstractColorsScheme implements EditorColorsScheme, Serial } if (getLineSpacing() != 1) { - addOptionTag(parentNode, LINE_SPACING, String.valueOf(getLineSpacing())); + JdomKt.addOptionTag(parentNode, LINE_SPACING, String.valueOf(getLineSpacing())); } // IJ has used a 'single customizable font' mode for ages. That's why we want to support that format now, when it's possible @@ -559,7 +553,7 @@ public abstract class AbstractColorsScheme implements EditorColorsScheme, Serial // That's why we want to use old format when zero or one font is selected and 'extended' format otherwise. boolean useOldFontFormat = myFontPreferences.getEffectiveFontFamilies().size() <= 1; if (useOldFontFormat) { - addOptionTag(parentNode, EDITOR_FONT_SIZE, String.valueOf(getEditorFontSize())); + JdomKt.addOptionTag(parentNode, EDITOR_FONT_SIZE, String.valueOf(getEditorFontSize())); } else { writeFontPreferences(EDITOR_FONT, parentNode, myFontPreferences); @@ -568,10 +562,10 @@ public abstract class AbstractColorsScheme implements EditorColorsScheme, Serial if (!myFontPreferences.equals(myConsoleFontPreferences)) { if (myConsoleFontPreferences.getEffectiveFontFamilies().size() <= 1) { - addOptionTag(parentNode, CONSOLE_FONT_NAME, getConsoleFontName()); + JdomKt.addOptionTag(parentNode, CONSOLE_FONT_NAME, getConsoleFontName()); if (getConsoleFontSize() != getEditorFontSize()) { - addOptionTag(parentNode, CONSOLE_FONT_SIZE, Integer.toString(getConsoleFontSize())); + JdomKt.addOptionTag(parentNode, CONSOLE_FONT_SIZE, Integer.toString(getConsoleFontSize())); } } else { @@ -581,15 +575,15 @@ public abstract class AbstractColorsScheme implements EditorColorsScheme, Serial } if (getConsoleLineSpacing() != getLineSpacing()) { - addOptionTag(parentNode, CONSOLE_LINE_SPACING, Float.toString(getConsoleLineSpacing())); + JdomKt.addOptionTag(parentNode, CONSOLE_LINE_SPACING, Float.toString(getConsoleLineSpacing())); } if (DEFAULT_FONT_SIZE != getQuickDocFontSize()) { - addOptionTag(parentNode, EDITOR_QUICK_JAVADOC_FONT_SIZE, getQuickDocFontSize().toString()); + JdomKt.addOptionTag(parentNode, EDITOR_QUICK_JAVADOC_FONT_SIZE, getQuickDocFontSize().toString()); } if (useOldFontFormat) { - addOptionTag(parentNode, EDITOR_FONT_NAME, getEditorFontName()); + JdomKt.addOptionTag(parentNode, EDITOR_FONT_NAME, getEditorFontName()); } Element colorElements = new Element(COLORS_ELEMENT); @@ -610,15 +604,15 @@ public abstract class AbstractColorsScheme implements EditorColorsScheme, Serial private static void writeLigaturesPreferences(Element parentNode, FontPreferences preferences, String optionName) { if (preferences.useLigatures()) { - addOptionTag(parentNode, optionName, String.valueOf(true)); + JdomKt.addOptionTag(parentNode, optionName, String.valueOf(true)); } } private static void writeFontPreferences(@NotNull String key, @NotNull Element parent, @NotNull FontPreferences preferences) { for (String fontFamily : preferences.getRealFontFamilies()) { Element element = new Element(key); - addOptionTag(element, EDITOR_FONT_NAME, fontFamily); - addOptionTag(element, EDITOR_FONT_SIZE, String.valueOf(preferences.getSize(fontFamily))); + JdomKt.addOptionTag(element, EDITOR_FONT_NAME, fontFamily); + JdomKt.addOptionTag(element, EDITOR_FONT_SIZE, String.valueOf(preferences.getSize(fontFamily))); parent.addContent(element); } } @@ -711,7 +705,8 @@ public abstract class AbstractColorsScheme implements EditorColorsScheme, Serial for (ColorKey key : list) { if (haveToWrite(key)) { Color value = myColorsMap.get(key); - addOptionTag(colorElements, key.getExternalName(), value == null ? "" : Integer.toString(value.getRGB() & 0xFFFFFF, 16)); + String value1 = value == null ? "" : Integer.toString(value.getRGB() & 0xFFFFFF, 16); + JdomKt.addOptionTag(colorElements, key.getExternalName(), value1); } } } diff --git a/platform/lang-api/src/com/intellij/execution/configurations/ModuleBasedConfiguration.java b/platform/lang-api/src/com/intellij/execution/configurations/ModuleBasedConfiguration.java index 5ab405254a93..d7e5aa27bf00 100644 --- a/platform/lang-api/src/com/intellij/execution/configurations/ModuleBasedConfiguration.java +++ b/platform/lang-api/src/com/intellij/execution/configurations/ModuleBasedConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * 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. @@ -25,13 +25,13 @@ import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.WriteExternalException; import com.intellij.util.xmlb.annotations.Property; +import gnu.trove.THashSet; import org.jdom.Element; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import java.util.Arrays; import java.util.Collection; -import java.util.HashSet; import java.util.Set; /** @@ -68,11 +68,11 @@ public abstract class ModuleBasedConfiguration() { - @Override - @SuppressWarnings({"ConstantConditions"}) - public Module[] compute() { - final Module module = getConfigurationModule().getModule(); - return module == null ? Module.EMPTY_ARRAY : new Module[] {module}; - } - }); + Module module = ApplicationManager.getApplication().runReadAction((Computable)() -> getConfigurationModule().getModule()); + return module == null ? Module.EMPTY_ARRAY : new Module[] {module}; } public void restoreOriginalModule(final Module originalModule) { - if (originalModule == null) return; - final Module[] classModules = getModules(); - final Set modules = new HashSet<>(); - for (Module classModule : classModules) { + if (originalModule == null) { + return; + } + + Set modules = new THashSet<>(); + for (Module classModule : getModules()) { ModuleUtilCore.collectModulesDependsOn(classModule, modules); } - if (modules.contains(originalModule)) setModule(originalModule); + if (modules.contains(originalModule)) { + setModule(originalModule); + } } public void onNewConfigurationCreated() { diff --git a/platform/lang-api/src/com/intellij/execution/configurations/RunConfigurationBase.java b/platform/lang-api/src/com/intellij/execution/configurations/RunConfigurationBase.java index adad3dd2de19..fa9e71b0e82e 100644 --- a/platform/lang-api/src/com/intellij/execution/configurations/RunConfigurationBase.java +++ b/platform/lang-api/src/com/intellij/execution/configurations/RunConfigurationBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * 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. @@ -33,7 +33,6 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.ArrayList; -import java.util.List; /** * Standard base class for run configuration implementations. @@ -196,16 +195,15 @@ public abstract class RunConfigurationBase extends UserDataHolderBase implements @Override public void readExternal(Element element) throws InvalidDataException { myLogFiles.clear(); - for (final Object o : element.getChildren(LOG_FILE)) { + for (Element o : element.getChildren(LOG_FILE)) { LogFileOptions logFileOptions = new LogFileOptions(); - logFileOptions.readExternal((Element)o); + logFileOptions.readExternal(o); myLogFiles.add(logFileOptions); } myPredefinedLogFiles.clear(); - final List list = element.getChildren(PREDEFINED_LOG_FILE_ELEMENT); - for (Object fileElement : list) { + for (Element fileElement : element.getChildren(PREDEFINED_LOG_FILE_ELEMENT)) { final PredefinedLogFile logFile = new PredefinedLogFile(); - logFile.readExternal((Element)fileElement); + logFile.readExternal(fileElement); myPredefinedLogFiles.add(logFile); } final Element fileOutputElement = element.getChild(FILE_OUTPUT); diff --git a/platform/lang-api/src/com/intellij/execution/configurations/RunConfigurationModule.java b/platform/lang-api/src/com/intellij/execution/configurations/RunConfigurationModule.java index de230e2e8e0d..100aa02ce077 100644 --- a/platform/lang-api/src/com/intellij/execution/configurations/RunConfigurationModule.java +++ b/platform/lang-api/src/com/intellij/execution/configurations/RunConfigurationModule.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * 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. @@ -55,7 +55,6 @@ public class RunConfigurationModule implements JDOMExternalizable { } @Override - @SuppressWarnings({"unchecked"}) public void readExternal(@NotNull Element element) { List modules = element.getChildren(ELEMENT); if (!modules.isEmpty()) { @@ -97,7 +96,8 @@ public class RunConfigurationModule implements JDOMExternalizable { @Nullable @Transient public Module getModule() { - if (myModuleName != null) { //caching + //caching + if (myModuleName != null) { myModule = findModule(myModuleName); } if (myModule != null && myModule.isDisposed()) { @@ -107,18 +107,11 @@ public class RunConfigurationModule implements JDOMExternalizable { } @Nullable - public Module findModule(final String moduleName) { + public Module findModule(@NotNull String moduleName) { if (myProject.isDisposed()) { return null; } - - return ApplicationManager.getApplication().runReadAction(new Computable() { - @Nullable - @Override - public Module compute() { - return getModuleManager().findModuleByName(moduleName); - } - }); + return ApplicationManager.getApplication().runReadAction((Computable)() -> getModuleManager().findModuleByName(moduleName)); } public void setModule(final Module module) { diff --git a/platform/lang-api/src/com/intellij/execution/filters/CompositeFilter.java b/platform/lang-api/src/com/intellij/execution/filters/CompositeFilter.java index dd2cdac55f2b..b2a51ea4802f 100644 --- a/platform/lang-api/src/com/intellij/execution/filters/CompositeFilter.java +++ b/platform/lang-api/src/com/intellij/execution/filters/CompositeFilter.java @@ -27,6 +27,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.Collections; import java.util.List; @SuppressWarnings("ForLoopReplaceableByForEach") @@ -201,6 +202,11 @@ public class CompositeFilter implements Filter, FilterMixin { myIsAnyHeavy |= filter instanceof FilterMixin; } + @NotNull + public List getFilters() { + return Collections.unmodifiableList(myFilters); + } + public void setForceUseAllFilters(boolean forceUseAllFilters) { this.forceUseAllFilters = forceUseAllFilters; } diff --git a/platform/lang-api/src/com/intellij/execution/filters/Filter.java b/platform/lang-api/src/com/intellij/execution/filters/Filter.java index 8c18ff960e42..849833a7813d 100644 --- a/platform/lang-api/src/com/intellij/execution/filters/Filter.java +++ b/platform/lang-api/src/com/intellij/execution/filters/Filter.java @@ -15,6 +15,7 @@ */ package com.intellij.execution.filters; +import com.intellij.execution.ui.ConsoleHighlighterLayer; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.colors.*; import com.intellij.openapi.editor.markup.TextAttributes; @@ -73,6 +74,7 @@ public interface Filter { myResultItems = resultItems; } + @NotNull public List getResultItems() { List resultItems = myResultItems; if (resultItems == null) { @@ -179,6 +181,8 @@ public interface Filter { */ @Deprecated @Nullable public final HyperlinkInfo hyperlinkInfo; + + private final int myHighlighterLayer; private final TextAttributes myFollowedHyperlinkAttributes; @@ -210,11 +214,21 @@ public interface Filter { @Nullable final HyperlinkInfo hyperlinkInfo, @Nullable final TextAttributes highlightAttributes, @Nullable final TextAttributes followedHyperlinkAttributes) { + this(highlightStartOffset, highlightEndOffset, hyperlinkInfo, highlightAttributes, followedHyperlinkAttributes, + hyperlinkInfo != null ? ConsoleHighlighterLayer.HYPERLINK_LAYER : ConsoleHighlighterLayer.HIGHLIGHT_LAYER); + } + public ResultItem(final int highlightStartOffset, + final int highlightEndOffset, + @Nullable final HyperlinkInfo hyperlinkInfo, + @Nullable final TextAttributes highlightAttributes, + @Nullable final TextAttributes followedHyperlinkAttributes, + int highlighterLayer) { this.highlightStartOffset = highlightStartOffset; this.highlightEndOffset = highlightEndOffset; this.hyperlinkInfo = hyperlinkInfo; this.highlightAttributes = highlightAttributes; myFollowedHyperlinkAttributes = followedHyperlinkAttributes; + myHighlighterLayer = highlighterLayer; } public int getHighlightStartOffset() { @@ -244,6 +258,10 @@ public interface Filter { return hyperlinkInfo; } + public int getHighlighterLayer() { + return myHighlighterLayer; + } + @Nullable private static TextAttributes getGrayedHyperlinkAttributes(@NotNull TextAttributesKey normalHyperlinkAttrsKey) { EditorColorsScheme globalScheme = EditorColorsManager.getInstance().getGlobalScheme(); diff --git a/platform/lang-api/src/com/intellij/execution/runners/ExecutionUtil.java b/platform/lang-api/src/com/intellij/execution/runners/ExecutionUtil.java index 13bf79168418..9da721f92bb3 100644 --- a/platform/lang-api/src/com/intellij/execution/runners/ExecutionUtil.java +++ b/platform/lang-api/src/com/intellij/execution/runners/ExecutionUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * 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. @@ -23,7 +23,6 @@ import com.intellij.execution.process.ProcessNotCreatedException; import com.intellij.execution.ui.RunContentDescriptor; import com.intellij.ide.DataManager; import com.intellij.ide.util.PropertiesComponent; -import com.intellij.notification.Notification; import com.intellij.notification.NotificationGroup; import com.intellij.notification.NotificationListener; import com.intellij.notification.NotificationType; @@ -38,6 +37,7 @@ import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.ui.ColorUtil; import com.intellij.ui.LayeredIcon; import com.intellij.ui.content.Content; +import com.intellij.util.ExceptionUtil; import com.intellij.util.ui.GraphicsUtil; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; @@ -110,8 +110,8 @@ public class ExecutionUtil { LOG.error(fullMessage, e); } - if (listener == null && e instanceof HyperlinkListener) { - listener = (HyperlinkListener)e; + if (listener == null) { + listener = ExceptionUtil.findCause(e, HyperlinkListener.class); } final HyperlinkListener finalListener = listener; @@ -129,9 +129,8 @@ public class ExecutionUtil { else { Messages.showErrorDialog(project, UIUtil.toHtml(fullMessage), ""); } - NotificationListener notificationListener = finalListener == null ? null : new NotificationListener() { - @Override - public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { + NotificationListener notificationListener = finalListener == null ? null : (notification, event) -> { + if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { finalListener.hyperlinkUpdate(event); } }; @@ -203,7 +202,7 @@ public class ExecutionUtil { GraphicsUtil.setupAAPainting(g2d); g2d.setColor(Color.GREEN); Ellipse2D.Double shape = - new Ellipse2D.Double(x + getIconWidth() - JBUI.scale(iSize), y + getIconHeight() - iSize, iSize, iSize); + new Ellipse2D.Double(x + getIconWidth() - iSize, y + getIconHeight() - iSize, iSize, iSize); g2d.fill(shape); g2d.setColor(ColorUtil.withAlpha(Color.BLACK, .40)); g2d.draw(shape); diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/changes/MarkerVcsContentRevision.java b/platform/lang-api/src/com/intellij/execution/ui/ConsoleHighlighterLayer.java similarity index 65% rename from platform/vcs-api/src/com/intellij/openapi/vcs/changes/MarkerVcsContentRevision.java rename to platform/lang-api/src/com/intellij/execution/ui/ConsoleHighlighterLayer.java index d7c3f1464c49..691013af753a 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/changes/MarkerVcsContentRevision.java +++ b/platform/lang-api/src/com/intellij/execution/ui/ConsoleHighlighterLayer.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * 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. @@ -13,16 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.openapi.vcs.changes; +package com.intellij.execution.ui; -import com.intellij.openapi.vcs.VcsKey; +import com.intellij.openapi.editor.markup.HighlighterLayer; -/** - * Created with IntelliJ IDEA. - * User: Irina.Chernushina - * Date: 6/14/12 - * Time: 5:13 PM - */ -public interface MarkerVcsContentRevision { - VcsKey getVcsKey(); +public interface ConsoleHighlighterLayer { + int HYPERLINK_LAYER = HighlighterLayer.SELECTION - 123; + int HIGHLIGHT_LAYER = HighlighterLayer.SELECTION - 111; } diff --git a/platform/lang-api/src/com/intellij/formatting/Alignment.java b/platform/lang-api/src/com/intellij/formatting/Alignment.java index 68eb0d6ee900..48ad2b4f32ed 100644 --- a/platform/lang-api/src/com/intellij/formatting/Alignment.java +++ b/platform/lang-api/src/com/intellij/formatting/Alignment.java @@ -22,8 +22,8 @@ import org.jetbrains.annotations.NotNull; * alignment object instance from the getAlignment method * are aligned with each other. * - * @see com.intellij.formatting.Block#getAlignment() - * @see com.intellij.formatting.ChildAttributes#getAlignment() + * @see Block#getAlignment() + * @see ChildAttributes#getAlignment() */ public abstract class Alignment { @@ -36,23 +36,10 @@ public abstract class Alignment { myFactory = factory; } - /** - * Shorthand for calling {@link #createAlignment(boolean)} with 'false'. - * - * @return alignment object with default settings - */ public static Alignment createAlignment() { return createAlignment(false, Anchor.LEFT); } - /** - * Delegates the processing to {@link #createAlignment(boolean, Anchor)} with given 'allow backward shift' value - * and {@link Anchor#LEFT}. - * - * @param allowBackwardShift flag that specifies if former aligned block may be shifted to right in order to align to subsequent - * aligned block - * @return alignment object with the given 'allow backward shift' setting - */ public static Alignment createAlignment(boolean allowBackwardShift) { return createAlignment(allowBackwardShift, Anchor.LEFT); } diff --git a/platform/lang-impl/src/com/intellij/application/options/InitialConfigurationDialog.java b/platform/lang-impl/src/com/intellij/application/options/InitialConfigurationDialog.java index 7d61d23ac2ac..a87f4608a3db 100644 --- a/platform/lang-impl/src/com/intellij/application/options/InitialConfigurationDialog.java +++ b/platform/lang-impl/src/com/intellij/application/options/InitialConfigurationDialog.java @@ -329,19 +329,6 @@ public class InitialConfigurationDialog extends DialogWrapper { updateColorSchemePreview(false); } - @Override - public boolean refreshDataSynch() { - return false; - } - - @Override - public void dataChanged() {} - - @Override - public boolean isStillValid(Object o) { - return false; - } - @Override public void refresh() { updateColorSchemePreview(false); @@ -352,9 +339,6 @@ public class InitialConfigurationDialog extends DialogWrapper { return (JPanel)myPreviewEditor.getPanel(); } - @Override - public void away() {} - @Override public void dispose() { disposeUIResources(); diff --git a/platform/lang-impl/src/com/intellij/application/options/ModuleAwareProjectConfigurable.java b/platform/lang-impl/src/com/intellij/application/options/ModuleAwareProjectConfigurable.java index be7e3b84a59e..ae5402401104 100644 --- a/platform/lang-impl/src/com/intellij/application/options/ModuleAwareProjectConfigurable.java +++ b/platform/lang-impl/src/com/intellij/application/options/ModuleAwareProjectConfigurable.java @@ -15,6 +15,7 @@ */ package com.intellij.application.options; +import com.intellij.icons.AllIcons; import com.intellij.openapi.module.Module; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.ConfigurationException; @@ -22,7 +23,6 @@ import com.intellij.openapi.options.SearchableConfigurable; import com.intellij.openapi.options.UnnamedConfigurable; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Splitter; -import com.intellij.openapi.util.Condition; import com.intellij.platform.ModuleAttachProcessor; import com.intellij.ui.CollectionListModel; import com.intellij.ui.ListSpeedSearch; @@ -52,6 +52,7 @@ public abstract class ModuleAwareProjectConfigurable myModuleConfigurables = new HashMap<>(); + private final static String PROJECT_ITEM_KEY = "thisisnotthemoduleyouarelookingfor"; public ModuleAwareProjectConfigurable(@NotNull Project project, String displayName, String helpTopic) { myProject = project; @@ -85,29 +86,56 @@ public abstract class ModuleAwareProjectConfigurable modules = ContainerUtil.filter(ModuleAttachProcessor.getSortedModules(myProject), module -> isSuitableForModule(module)); - if (modules.size() == 1) { + + final T projectConfigurable = createProjectConfigurable(); + + if (modules.size() == 1 && projectConfigurable == null) { Module module = modules.get(0); final T configurable = createModuleConfigurable(module); myModuleConfigurables.put(module, configurable); return configurable.createComponent(); } final Splitter splitter = new Splitter(false, 0.25f); - final JBList moduleList = new JBList(new CollectionListModel<>(modules)); + CollectionListModel listDataModel = new CollectionListModel<>(modules); + final JBList moduleList = new JBList(listDataModel); new ListSpeedSearch(moduleList, new Function() { @Override public String fun(Object o) { - if (o instanceof Module) { + if (o == null) { + return getProjectConfigurableItemName(); + } + else if (o instanceof Module) { return ((Module)o).getName(); } return null; } }); moduleList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - moduleList.setCellRenderer(new ModuleListCellRenderer()); + moduleList.setCellRenderer(new ModuleListCellRenderer() { + @Override + public void customize(JList list, Module module, int index, boolean selected, boolean hasFocus) { + if (module == null) { + setText(getProjectConfigurableItemName()); + setIcon(getProjectConfigurableItemIcon()); + } + else { + super.customize(list, module, index, selected, hasFocus); + } + } + }); splitter.setFirstComponent(new JBScrollPane(moduleList)); final CardLayout layout = new CardLayout(); final JPanel cardPanel = new JPanel(layout); splitter.setSecondComponent(cardPanel); + + + if (projectConfigurable != null) { + myModuleConfigurables.put(null, projectConfigurable); + final JComponent component = projectConfigurable.createComponent(); + cardPanel.add(component, PROJECT_ITEM_KEY); + listDataModel.add(0, null); + } + for (Module module : modules) { final T configurable = createModuleConfigurable(module); myModuleConfigurables.put(module, configurable); @@ -118,12 +146,14 @@ public abstract class ModuleAwareProjectConfigurable 0) { + + if (moduleList.getItemsCount() > 0) { moduleList.setSelectedIndex(0); - layout.show(cardPanel, modules.get(0).getName()); + Module module = listDataModel.getElementAt(0); + layout.show(cardPanel, module == null ? PROJECT_ITEM_KEY : module.getName()); } return splitter; } @@ -133,6 +163,32 @@ public abstract class ModuleAwareProjectConfigurable, C public boolean AUTOCOMPLETE_ON_CODE_COMPLETION = true; public boolean AUTOCOMPLETE_ON_SMART_TYPE_COMPLETION = true; - /** todo remove in IDEA 16 */ - @Deprecated public boolean AUTOCOMPLETE_ON_CLASS_NAME_COMPLETION; - public boolean AUTOCOMPLETE_COMMON_PREFIX = true; - /** todo remove in IDEA 16 */ - @Deprecated - public boolean SHOW_STATIC_AFTER_INSTANCE; - public boolean SHOW_FULL_SIGNATURES_IN_PARAMETER_INFO; public boolean SHOW_SOURCE_INFERRED_ANNOTATIONS = true; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/AbstractLayoutCodeProcessor.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/AbstractLayoutCodeProcessor.java index 4749a4e737c9..a6878c231881 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/AbstractLayoutCodeProcessor.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/AbstractLayoutCodeProcessor.java @@ -51,6 +51,7 @@ import com.intellij.psi.PsiBundle; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; +import com.intellij.util.ExceptionUtil; import com.intellij.util.IncorrectOperationException; import com.intellij.util.SequentialTask; import com.intellij.util.SmartList; @@ -228,14 +229,20 @@ public abstract class AbstractLayoutCodeProcessor { final FutureTask currentTask = prepareTask(file, processChangedTextOnly); return new FutureTask<>(() -> { - if (previousTask != null) { - previousTask.run(); - if (!previousTask.get() || previousTask.isCancelled()) return false; + try { + if (previousTask != null) { + previousTask.run(); + if (!previousTask.get() || previousTask.isCancelled()) return false; + } + + ApplicationManager.getApplication().runWriteAction(() -> currentTask.run()); + + return currentTask.get() && !currentTask.isCancelled(); + } + catch (ExecutionException e) { + ExceptionUtil.rethrowUnchecked(e); + throw e; } - - ApplicationManager.getApplication().runWriteAction(() -> currentTask.run()); - - return currentTask.get() && !currentTask.isCancelled(); }); } @@ -337,6 +344,12 @@ public abstract class AbstractLayoutCodeProcessor { } catch (CancellationException ignored) { } + catch (ExecutionException e) { + if (e.getCause() instanceof IndexNotReadyException) { + throw (IndexNotReadyException)e.getCause(); + } + LOG.error(e); + } catch (Exception e) { LOG.error(e); } 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 da0d6b351686..ecaf9f96a27b 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 @@ -60,6 +60,7 @@ import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.EmptyRunnable; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.vfs.VirtualFile; @@ -73,7 +74,6 @@ import com.intellij.psi.util.PsiModificationTracker; import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.*; import com.intellij.util.concurrency.EdtExecutorService; -import com.intellij.util.concurrency.FixedFuture; import com.intellij.util.io.storage.HeavyProcessLatch; import com.intellij.util.ui.UIUtil; import gnu.trove.THashMap; @@ -85,10 +85,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; import java.util.*; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.*; /** * This class also controls the auto-reparse and auto-hints. @@ -105,14 +102,14 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements Pers private final DaemonCodeAnalyzerSettings mySettings; @NotNull private final EditorTracker myEditorTracker; @NotNull private final PsiDocumentManager myPsiDocumentManager; - private DaemonProgressIndicator myUpdateProgress; //guarded by this + private DaemonProgressIndicator myUpdateProgress = new DaemonProgressIndicator(); //guarded by this private final Runnable myUpdateRunnable = createUpdateRunnable(); // use scheduler instead of Alarm because the latter requires ModalityState.current() which is obtainable from EDT only which requires too many invokeLaters private final ScheduledExecutorService myAlarm = EdtExecutorService.getScheduledExecutorInstance(); @NotNull - private volatile Future myUpdateRunnableFuture = new FixedFuture<>(null); + private volatile ScheduledFuture myUpdateRunnableFuture = myAlarm.schedule(EmptyRunnable.getInstance(), 0, TimeUnit.NANOSECONDS); private boolean myUpdateByTimerEnabled = true; private final Collection myDisabledHintsFiles = new THashSet<>(); private final Collection myDisabledHighlightingFiles = new THashSet<>(); @@ -165,8 +162,9 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements Pers } @Override - public void dispose() { - + public synchronized void dispose() { + myUpdateProgress = null; // leak of highlight session via user data + myUpdateRunnableFuture.cancel(true); } @NotNull @@ -569,8 +567,13 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements Pers @Override public void restart() { + doRestart(); + } + + // return true if the progress was really canceled + boolean doRestart() { myFileStatusMap.markAllFilesDirty("Global restart"); - stopProcess(true, "Global restart"); + return stopProcess(true, "Global restart"); } @Override @@ -618,7 +621,7 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements Pers } synchronized boolean isRunning() { - return myUpdateProgress != null && !myUpdateProgress.isCanceled(); + return !myUpdateProgress.isCanceled(); } @TestOnly @@ -627,22 +630,29 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements Pers return isRunning() || !myUpdateRunnableFuture.isDone(); } - synchronized void stopProcess(boolean toRestartAlarm, @NotNull @NonNls String reason) { - cancelUpdateProgress(toRestartAlarm, reason); - myUpdateRunnableFuture.cancel(false); + // return true if the progress really was canceled + synchronized boolean stopProcess(boolean toRestartAlarm, @NotNull @NonNls String reason) { + boolean canceled = cancelUpdateProgress(toRestartAlarm, reason); + // optimisation: this check is to avoid too many re-schedules in case of thousands of events spikes boolean restart = toRestartAlarm && !myDisposed && myInitialized; - if (restart) { + if (restart && myUpdateRunnableFuture.isDone()) { + myUpdateRunnableFuture.cancel(false); myUpdateRunnableFuture = myAlarm.schedule(myUpdateRunnable, mySettings.AUTOREPARSE_DELAY, TimeUnit.MILLISECONDS); } + + return canceled; } - private synchronized void cancelUpdateProgress(final boolean start, @NonNls String reason) { - if (myUpdateProgress != null) { - PassExecutorService.log(myUpdateProgress, null, "Cancel", reason, start); + // return true if the progress really was canceled + private synchronized boolean cancelUpdateProgress(boolean toRestartAlarm, @NonNls String reason) { + boolean wasCanceled = myUpdateProgress.isCanceled(); + myPassExecutorService.cancelAll(false); + if (!wasCanceled) { + PassExecutorService.log(myUpdateProgress, null, "Cancel", reason, toRestartAlarm); myUpdateProgress.cancel(); - myPassExecutorService.cancelAll(false); - myUpdateProgress = null; + return true; } + return false; } @@ -885,7 +895,7 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements Pers @NotNull private synchronized DaemonProgressIndicator createUpdateProgress() { DaemonProgressIndicator old = myUpdateProgress; - if (old != null && !old.isCanceled()) { + if (!old.isCanceled()) { old.cancel(); } DaemonProgressIndicator progress = new DaemonProgressIndicator() { @@ -909,6 +919,7 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implements Pers } @TestOnly + @NotNull synchronized DaemonProgressIndicator getUpdateProgress() { return myUpdateProgress; } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonListeners.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonListeners.java index c7678f3d1921..cae0df26f42c 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonListeners.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonListeners.java @@ -39,7 +39,6 @@ import com.intellij.openapi.command.undo.UndoManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.actionSystem.DocCommandGroupId; -import com.intellij.openapi.editor.colors.EditorColorsListener; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.editor.event.*; @@ -276,12 +275,7 @@ public class DaemonListeners implements Disposable { }); connection.subscribe(PowerSaveMode.TOPIC, () -> stopDaemon(true, "Power save mode change")); - connection.subscribe(EditorColorsManager.TOPIC, new EditorColorsListener() { - @Override - public void globalSchemeChange(EditorColorsScheme scheme) { - stopDaemonAndRestartAllFiles("Editor color scheme changed"); - } - }); + connection.subscribe(EditorColorsManager.TOPIC, scheme -> stopDaemonAndRestartAllFiles("Editor color scheme changed")); commandProcessor.addCommandListener(new MyCommandListener(), this); application.addApplicationListener(new MyApplicationListener(), this); @@ -587,13 +581,15 @@ public class DaemonListeners implements Disposable { } private void stopDaemon(boolean toRestartAlarm, @NonNls @NotNull String reason) { - myDaemonEventPublisher.daemonCancelEventOccurred(reason); - myDaemonCodeAnalyzer.stopProcess(toRestartAlarm, reason); + if (myDaemonCodeAnalyzer.stopProcess(toRestartAlarm, reason)) { + myDaemonEventPublisher.daemonCancelEventOccurred(reason); + } } private void stopDaemonAndRestartAllFiles(@NotNull String reason) { - myDaemonEventPublisher.daemonCancelEventOccurred(reason); - myDaemonCodeAnalyzer.restart(); + if (myDaemonCodeAnalyzer.doRestart()) { + myDaemonEventPublisher.daemonCancelEventOccurred(reason); + } } static void repaintErrorStripeRenderer(@NotNull Editor editor, @NotNull Project project) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ShowIntentionsPass.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ShowIntentionsPass.java index 3a6c1b94ece7..863a16d99342 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ShowIntentionsPass.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/ShowIntentionsPass.java @@ -86,9 +86,9 @@ public class ShowIntentionsPass extends TextEditorHighlightingPass { private volatile boolean myHasToRecreate; @NotNull - public static List getAvailableActions(@NotNull final Editor editor, - @NotNull final PsiFile file, - final int passId) { + public static List getAvailableFixes(@NotNull final Editor editor, + @NotNull final PsiFile file, + final int passId) { final int offset = ((EditorEx)editor).getExpectedCaretOffset(); final Project project = file.getProject(); @@ -96,16 +96,16 @@ public class ShowIntentionsPass extends TextEditorHighlightingPass { DaemonCodeAnalyzerImpl.processHighlightsNearOffset(editor.getDocument(), project, HighlightSeverity.INFORMATION, offset, true, new CommonProcessors.CollectProcessor<>(infos)); List result = new ArrayList<>(); - infos.forEach(info->addAvailableActionsForGroups(info, editor, file, result, passId, offset)); + infos.forEach(info-> addAvailableFixesForGroups(info, editor, file, result, passId, offset)); return result; } - private static void addAvailableActionsForGroups(@NotNull HighlightInfo info, - @NotNull Editor editor, - @NotNull PsiFile file, - @NotNull List outList, - int group, - int offset) { + private static void addAvailableFixesForGroups(@NotNull HighlightInfo info, + @NotNull Editor editor, + @NotNull PsiFile file, + @NotNull List outList, + int group, + int offset) { if (info.quickFixActionMarkers == null) return; if (group != -1 && group != info.getGroup()) return; boolean fixRangeIsNotEmpty = !info.getFixTextRange().isEmpty(); @@ -157,7 +157,7 @@ public class ShowIntentionsPass extends TextEditorHighlightingPass { public final List guttersToShow = ContainerUtil.createLockFreeCopyOnWriteList(); public final List notificationActionsToShow = ContainerUtil.createLockFreeCopyOnWriteList(); - public void filterActions(@Nullable PsiFile psiFile) { + void filterActions(@Nullable PsiFile psiFile) { IntentionActionFilter[] filters = IntentionActionFilter.EXTENSION_POINT_NAME.getExtensions(); filter(intentionsToShow, psiFile, filters); filter(errorFixesToShow, psiFile, filters); @@ -287,7 +287,7 @@ public class ShowIntentionsPass extends TextEditorHighlightingPass { int offset = hostEditor.getCaretModel().getOffset(); final Project project = hostFile.getProject(); - List fixes = getAvailableActions(hostEditor, hostFile, passIdToShowIntentionsFor); + List fixes = getAvailableFixes(hostEditor, hostFile, passIdToShowIntentionsFor); final DaemonCodeAnalyzer codeAnalyzer = DaemonCodeAnalyzer.getInstance(project); final Document hostDocument = hostEditor.getDocument(); HighlightInfo infoAtCursor = ((DaemonCodeAnalyzerImpl)codeAnalyzer).findHighlightByOffset(hostDocument, offset, true); @@ -396,6 +396,7 @@ public class ShowIntentionsPass extends TextEditorHighlightingPass { PsiElement el = psiElement; while (el != null) { elements.add(el); + if (el instanceof PsiFile) break; el = el.getParent(); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/SelectWordUtil.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/SelectWordUtil.java index 7c3f5738a74b..0bef80ad6a72 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/SelectWordUtil.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/SelectWordUtil.java @@ -23,6 +23,7 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.actions.EditorActionUtil; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.util.TextRange; import com.intellij.psi.FileViewProvider; import com.intellij.psi.PsiElement; @@ -257,12 +258,8 @@ public class SelectWordUtil { availableSelectioners.add(selectioner); } } - long stamp = editor.getDocument().getModificationStamp(); for (ExtendWordSelectionHandler selectioner : availableSelectioners) { - List ranges = selectioner.select(element, text, cursorOffset, editor); - if (stamp != editor.getDocument().getModificationStamp()) { - throw new AssertionError("Selectioner " + selectioner + " has changed the document"); - } + List ranges = askSelectioner(element, text, cursorOffset, editor, selectioner); if (ranges == null) continue; for (TextRange range : ranges) { @@ -275,6 +272,25 @@ public class SelectWordUtil { return stop; } + @Nullable + private static List askSelectioner(@NotNull PsiElement element, + CharSequence text, + int cursorOffset, + Editor editor, + ExtendWordSelectionHandler selectioner) { + try { + long stamp = editor.getDocument().getModificationStamp(); + List ranges = selectioner.select(element, text, cursorOffset, editor); + if (stamp != editor.getDocument().getModificationStamp()) { + throw new AssertionError("Selectioner " + selectioner + " has changed the document"); + } + return ranges; + } + catch (IndexNotReadyException e) { + return null; + } + } + public static void addWordHonoringEscapeSequences(CharSequence editorText, TextRange literalTextRange, int cursorOffset, diff --git a/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/GenerateAction.java b/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/GenerateAction.java index 0ddc5981e6e2..5c2cdd6683e9 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/GenerateAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/GenerateAction.java @@ -18,6 +18,7 @@ package com.intellij.codeInsight.generation.actions; import com.intellij.codeInsight.CodeInsightBundle; import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.application.impl.LaterInvocator; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.DumbService; @@ -61,7 +62,7 @@ public class GenerateAction extends DumbAwareAction { return; } - boolean groupEmpty = ActionGroupUtil.isGroupEmpty(getGroup(), event); + boolean groupEmpty = ActionGroupUtil.isGroupEmpty(getGroup(), event, LaterInvocator.isInModalContext()); presentation.setEnabled(!groupEmpty); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/ShowIntentionActionsHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/ShowIntentionActionsHandler.java index 8a6d9d27854a..6c63d2332fe7 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/ShowIntentionActionsHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/ShowIntentionActionsHandler.java @@ -108,7 +108,6 @@ public class ShowIntentionActionsHandler implements CodeInsightActionHandler { return false; } - // returns editor,file where the action is available or null if there are none public static boolean availableFor(@NotNull PsiFile psiFile, @NotNull Editor editor, @NotNull IntentionAction action) { if (!psiFile.isValid()) return false; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/CustomTemplateCallback.java b/platform/lang-impl/src/com/intellij/codeInsight/template/CustomTemplateCallback.java index 5b30af72122b..281a3ee6c558 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/CustomTemplateCallback.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/CustomTemplateCallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * 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. @@ -24,6 +24,7 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.ScrollType; +import com.intellij.openapi.editor.SelectionModel; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiDocumentManager; @@ -84,7 +85,8 @@ public class CustomTemplateCallback { } public static int getOffset(@NotNull Editor editor) { - return Math.max(editor.getSelectionModel().getSelectionStart() - 1, 0); + SelectionModel selectionModel = editor.getSelectionModel(); + return selectionModel.hasSelection() ? selectionModel.getSelectionStart() : Math.max(editor.getCaretModel().getOffset() - 1, 0); } @Nullable diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/LiveTemplateTree.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/LiveTemplateTree.java index d3cd1e3e820c..401423f19bbd 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/LiveTemplateTree.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/LiveTemplateTree.java @@ -88,10 +88,11 @@ class LiveTemplateTree extends CheckboxTree implements DataProvider, CopyProvide public void performCopy(@NotNull DataContext dataContext) { final Set templates = myConfigurable.getSelectedTemplates().keySet(); - + TemplateSettings templateSettings = TemplateSettings.getInstance(); CopyPasteManager.getInstance().setContents( new StringSelection(StringUtil.join(templates, - template -> JDOMUtil.writeElement(TemplateSettings.serializeTemplate(template)), + template -> JDOMUtil.writeElement( + TemplateSettings.serializeTemplate(template, templateSettings.getDefaultTemplate(template))), SystemProperties.getLineSeparator()))); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateContext.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateContext.java index ccb3a30ae3a7..ab0ef669677e 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateContext.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateContext.java @@ -13,15 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.intellij.codeInsight.template.impl; - import com.google.common.annotations.VisibleForTesting; import com.intellij.codeInsight.template.TemplateContextType; -import com.intellij.openapi.util.WriteExternalException; +import com.intellij.util.JdomKt; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.JBIterable; +import gnu.trove.THashMap; import org.jdom.Element; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -88,7 +87,7 @@ public class TemplateContext { // used during initialization => no sync @VisibleForTesting - public void readTemplateContext(Element element) { + public void readTemplateContext(@NotNull Element element) { for (Element option : element.getChildren("option")) { String name = option.getAttributeValue("name"); String value = option.getAttributeValue("value"); @@ -124,20 +123,53 @@ public class TemplateContext { } @VisibleForTesting - public void writeTemplateContext(Element element) throws WriteExternalException { + @Nullable + public Element writeTemplateContext(@Nullable TemplateContext defaultContext) { + if (myContextStates.isEmpty()) { + return null; + } + + Map idToType = new THashMap<>(); for (TemplateContextType type : TemplateManagerImpl.getAllContextTypes()) { - Boolean ownValue = getOwnValue(type); - if (ownValue != null) { - TemplateContextType base = type.getBaseContextType(); - boolean baseEnabled = base != null && isEnabled(base); - if (ownValue != baseEnabled) { - Element optionElement = new Element("option"); - optionElement.setAttribute("name", type.getContextId()); - optionElement.setAttribute("value", ownValue.toString()); - element.addContent(optionElement); - } + idToType.put(type.getContextId(), type); + } + + Element element = new Element(TemplateSettings.CONTEXT); + for (Map.Entry entry : myContextStates.entrySet()) { + Boolean ownValue = entry.getValue(); + if (ownValue == null) { + continue; + } + + TemplateContextType type = idToType.get(entry.getKey()); + if (type == null) { + // https://youtrack.jetbrains.com/issue/IDEA-155623#comment=27-1721029 + JdomKt.addOptionTag(element, entry.getKey(), ownValue.toString()); + } + else if (isNotDefault(ownValue, type, defaultContext)) { + JdomKt.addOptionTag(element, type.getContextId(), ownValue.toString()); } } + return element; + } + + /** + * Default value for GROOVY_STATEMENT is `true` (defined in the `plugins/groovy/groovy-psi/resources/liveTemplates/Groovy.xml`). + * Base value is `false`. + * + * If default value is defined (as in our example) — we must not take base value in account. + * Because on init `setDefaultContext` will be called and we will have own value. + * Otherwise it will be not possible to set value for `GROOVY_STATEMENT` neither to `true` (equals to default), nor to `false` (equals to base). + * See TemplateSchemeTest. + */ + boolean isNotDefault(@NotNull Boolean ownValue, @NotNull TemplateContextType type, @Nullable TemplateContext defaultContext) { + Boolean defaultValue = defaultContext == null ? null : defaultContext.getOwnValue(type); + if (defaultValue == null) { + TemplateContextType base = type.getBaseContextType(); + boolean baseEnabled = base != null && isEnabled(base); + return ownValue != baseEnabled; + } + return !ownValue.equals(defaultValue); } @Override diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateGroup.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateGroup.java index cdfc67dc2c87..c9db01b08e51 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateGroup.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateGroup.java @@ -16,7 +16,6 @@ package com.intellij.codeInsight.template.impl; import com.intellij.openapi.options.CompoundScheme; -import com.intellij.openapi.util.Condition; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateImpl.java index 4fc381b5506f..fa99fb3c08a6 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateImpl.java @@ -23,7 +23,6 @@ import com.intellij.openapi.options.SchemeElement; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.tree.IElementType; import com.intellij.util.SmartList; -import com.intellij.util.containers.IntArrayList; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -104,8 +103,6 @@ public class TemplateImpl extends Template implements SchemeElement { private boolean myIsInline = false; - - public TemplateImpl(@NotNull String key, @NotNull String group) { this(key, null, group); toParseSegments = false; @@ -119,7 +116,6 @@ public class TemplateImpl extends Template implements SchemeElement { myGroupName = group; } - @Override public void addTextSegment(@NotNull String text) { text = StringUtil.convertLineSeparators(text); @@ -201,7 +197,7 @@ public class TemplateImpl extends Template implements SchemeElement { for (Property property : Property.values()) { boolean value = another.getValue(property); if (value != Template.getDefaultValue(property)) { - setValue(property, value); + setValue(property, true); } } for (Variable variable : another.myVariables) { @@ -246,7 +242,8 @@ public class TemplateImpl extends Template implements SchemeElement { return isDeactivated; } - @NotNull public TemplateContext getTemplateContext() { + @NotNull + public TemplateContext getTemplateContext() { return myTemplateContext; } @@ -273,18 +270,6 @@ public class TemplateImpl extends Template implements SchemeElement { return -1; } - public IntArrayList getVariableSegmentNumbers(String variableName) { - IntArrayList result = new IntArrayList(); - parseSegments(); - for (int i = 0; i < mySegments.size(); i++) { - Segment segment = mySegments.get(i); - if (segment.name.equals(variableName)) { - result.add(i); - } - } - return result; - } - @NotNull @Override public String getTemplateText() { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateSettings.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateSettings.java index 95f1883f5f55..e7b4c6ec593a 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateSettings.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateSettings.java @@ -30,7 +30,7 @@ import com.intellij.openapi.options.SchemeState; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.JDOMUtil; -import com.intellij.openapi.util.WriteExternalException; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.JdomKt; import com.intellij.util.SmartList; import com.intellij.util.containers.MultiMap; @@ -83,7 +83,7 @@ public class TemplateSettings implements PersistentStateComponent { IntArrayList indices = initEmptyVariables(); mySegments.setSegmentsGreedy(false); @@ -483,7 +477,7 @@ public class TemplateState implements Disposable { "template key: " + myTemplate.getKey() + "; " + "template text" + myTemplate.getTemplateText() + "; " + "variable number: " + getCurrentVariableNumber()); - reformat(finalRangeMarker); + reformat(); mySegments.setSegmentsGreedy(true); restoreEmptyVariables(indices); }; @@ -570,7 +564,7 @@ public class TemplateState implements Disposable { } private void focusCurrentExpression() { - if (isFinished()) { + if (isFinished() || isDisposed()) { return; } @@ -627,8 +621,9 @@ public class TemplateState implements Disposable { focusCurrentHighlighter(true); } + @Nullable PsiFile getPsiFile() { - return PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument); + return !isDisposed() ? PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument) : null; } private void insertSingleItem(List lookupItems) { @@ -658,7 +653,7 @@ public class TemplateState implements Disposable { } private void runLookup(final List lookupItems, @Nullable String advertisingText) { - if (myEditor == null) return; + if (isDisposed()) return; final LookupManager lookupManager = LookupManager.getInstance(myProject); @@ -831,13 +826,14 @@ public class TemplateState implements Disposable { } private void recalcSegment(int segmentNumber, boolean isQuick, Expression expressionNode, Expression defaultValue) { + if (isDisposed()) return; String oldValue = getExpressionString(segmentNumber); int start = mySegments.getSegmentStart(segmentNumber); int end = mySegments.getSegmentEnd(segmentNumber); PsiDocumentManager.getInstance(myProject).commitDocument(myDocument); PsiFile psiFile = getPsiFile(); - PsiElement element = psiFile.findElementAt(start); + PsiElement element = psiFile != null ? psiFile.findElementAt(start) : null; if (element != null) { PsiUtilCore.ensureValid(element); } @@ -912,7 +908,7 @@ public class TemplateState implements Disposable { if (previousVariableNumber >= 0) { focusCurrentHighlighter(false); calcResults(false); - doReformat(null); + doReformat(); setCurrentVariableNumber(previousVariableNumber); focusCurrentExpression(); currentVariableChanged(oldVar); @@ -933,13 +929,13 @@ public class TemplateState implements Disposable { int nextVariableNumber = getNextVariableNumber(oldVar); if (nextVariableNumber == -1) { calcResults(false); - ApplicationManager.getApplication().runWriteAction(() -> reformat(null)); + ApplicationManager.getApplication().runWriteAction(() -> reformat()); finishTemplateEditing(); return; } focusCurrentHighlighter(false); calcResults(false); - doReformat(null); + doReformat(); setCurrentVariableNumber(nextVariableNumber); focusCurrentExpression(); currentVariableChanged(oldVar); @@ -1033,7 +1029,7 @@ public class TemplateState implements Disposable { LookupManager.getInstance(myProject).hideActiveLookup(); calcResults(false); if (!brokenOff) { - doReformat(null); + doReformat(); } setFinalEditorState(brokenOff); cleanupTemplateState(brokenOff); @@ -1060,6 +1056,7 @@ public class TemplateState implements Disposable { } private void setFinalEditorState(boolean brokenOff) { + if (isDisposed()) return; myEditor.getSelectionModel().removeSelection(); if (brokenOff && !((TemplateManagerImpl)TemplateManager.getInstance(myProject)).shouldSkipInTests()) return; @@ -1252,7 +1249,7 @@ public class TemplateState implements Disposable { } } - private void reformat(RangeMarker rangeMarkerToReformat) { + private void reformat() { final PsiFile file = getPsiFile(); if (file != null) { CodeStyleManager style = CodeStyleManager.getInstance(myProject); @@ -1284,10 +1281,6 @@ public class TemplateState implements Disposable { } int reformatStartOffset = myTemplateRange.getStartOffset(); int reformatEndOffset = myTemplateRange.getEndOffset(); - if (rangeMarkerToReformat != null) { - reformatStartOffset = rangeMarkerToReformat.getStartOffset(); - reformatEndOffset = rangeMarkerToReformat.getEndOffset(); - } if (dummyAdjustLineMarkerRange == null && endVarOffset >= 0) { // There is a possible case that indent marker element was not inserted (e.g. because there is no blank line // at the target offset). However, we want to reformat white space adjacent to the current template (if any). @@ -1303,8 +1296,7 @@ public class TemplateState implements Disposable { } } style.reformatText(file, reformatStartOffset, reformatEndOffset); - PsiDocumentManager.getInstance(myProject).commitDocument(myDocument); - PsiDocumentManager.getInstance(myProject).doPostponedOperationsAndUnblockDocument(myDocument); + unblockDocument(); if (dummyAdjustLineMarkerRange != null && dummyAdjustLineMarkerRange.isValid()) { //[ven] TODO: [max] correct javadoc reformatting to eliminate isValid() check!!! diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ProblematicWhitespaceInspection.java b/platform/lang-impl/src/com/intellij/codeInspection/ProblematicWhitespaceInspection.java index 487b1cbbfb68..4d77cb07b1b2 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ProblematicWhitespaceInspection.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ProblematicWhitespaceInspection.java @@ -45,6 +45,11 @@ public class ProblematicWhitespaceInspection extends LocalInspectionTool { return InspectionsBundle.message("problematic.whitespace.show.whitespaces.quickfix"); } + @Override + public boolean startInWriteAction() { + return false; + } + @Override public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { final FileEditorManager editorManager = FileEditorManager.getInstance(project); diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionRVContentProviderImpl.java b/platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionRVContentProviderImpl.java index 01a27855fce9..62132d59866a 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionRVContentProviderImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionRVContentProviderImpl.java @@ -131,9 +131,7 @@ public class InspectionRVContentProviderImpl extends InspectionRVContentProvider assert problem != null; elemNode .insertByOrder(ReadAction.compute(() -> new ProblemDescriptionNode(refElement, problem, toolWrapper, presentation)), true); - if (problems.length == 1) { - elemNode.setProblem(problems[0]); - } + elemNode.setProblem(elemNode.getChildCount() == 1 ? problems[0] : null); } } else { diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ex/PerformFixesModalTask.java b/platform/lang-impl/src/com/intellij/codeInspection/ex/PerformFixesModalTask.java index 023aa8625306..4e050b4b5d2a 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ex/PerformFixesModalTask.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ex/PerformFixesModalTask.java @@ -15,14 +15,11 @@ */ package com.intellij.codeInspection.ex; -import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInspection.CommonProblemDescriptor; import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.codeInspection.QuickFix; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; @@ -86,17 +83,15 @@ public abstract class PerformFixesModalTask implements SequentialTask { } } - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_MODAL, () -> { - ApplicationManager.getApplication().runWriteAction(() -> { - myDocumentManager.commitAllDocuments(); - if (!runInReadAction[0]) { - applyFix(myProject, descriptor); - } - }); - if (runInReadAction[0]) { + ApplicationManager.getApplication().runWriteAction(() -> { + myDocumentManager.commitAllDocuments(); + if (!runInReadAction[0]) { applyFix(myProject, descriptor); } }); + if (runInReadAction[0]) { + applyFix(myProject, descriptor); + } return isDone(); } 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 69249542cf31..6a6ef36a6c59 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java @@ -967,15 +967,12 @@ public class InspectionResultsView extends JPanel implements Disposable, Occuren if (elementFromDescriptor == null) { final InspectionTreeNode node = (InspectionTreeNode)refElementNode.getChildAt(0); if (node.isValid()) { - return null; + return InspectionResultsViewUtil.getNavigatableForInvalidNode((ProblemDescriptionNode)node); } } else { psiElement = elementFromDescriptor; } } - else { - return null; - } } if (CommonDataKeys.NAVIGATABLE.is(dataId)) { diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/RefElementNode.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/RefElementNode.java index a92de6a639e9..bb92904bef59 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/RefElementNode.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/RefElementNode.java @@ -102,7 +102,7 @@ public class RefElementNode extends SuppressableInspectionTreeNode { return super.insertByOrder(child, allowDuplication); } - public void setProblem(@NotNull CommonProblemDescriptor descriptor) { + public void setProblem(CommonProblemDescriptor descriptor) { mySingleDescriptor = descriptor; } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/actions/suppress/SuppressActionSequentialTask.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/actions/suppress/SuppressActionSequentialTask.java index d1c941785aad..87345182ad62 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/actions/suppress/SuppressActionSequentialTask.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/actions/suppress/SuppressActionSequentialTask.java @@ -27,8 +27,6 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiDocumentManager; @@ -74,15 +72,13 @@ public class SuppressActionSequentialTask implements SequentialTask { indicator.setFraction((double)myCount / myNodesToSuppress.length); } - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_MODAL, () -> { - final Pair content = node.getSuppressContent(); - if (content.first != null) { - final PsiElement element = content.first; - RefEntity refEntity = node.getElement(); - LOG.assertTrue(refEntity != null); - suppress(element, content.second, mySuppressAction, refEntity, myWrapper, node); - } - }); + final Pair content = node.getSuppressContent(); + if (content.first != null) { + final PsiElement element = content.first; + RefEntity refEntity = node.getElement(); + LOG.assertTrue(refEntity != null); + suppress(element, content.second, mySuppressAction, refEntity, myWrapper, node); + } return false; } diff --git a/platform/lang-impl/src/com/intellij/execution/configuration/RunConfigurationPathMacroFilter.java b/platform/lang-impl/src/com/intellij/execution/configuration/RunConfigurationPathMacroFilter.java index e6293c649f2a..1656d7bcf301 100644 --- a/platform/lang-impl/src/com/intellij/execution/configuration/RunConfigurationPathMacroFilter.java +++ b/platform/lang-impl/src/com/intellij/execution/configuration/RunConfigurationPathMacroFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * 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. @@ -22,7 +22,7 @@ import org.jdom.Element; /** * @author yole */ -public class RunConfigurationPathMacroFilter extends PathMacroFilter { +class RunConfigurationPathMacroFilter extends PathMacroFilter { @Override public boolean skipPathMacros(Attribute attribute) { final Element parent = attribute.getParent(); diff --git a/platform/lang-impl/src/com/intellij/execution/console/LanguageConsoleBuilder.java b/platform/lang-impl/src/com/intellij/execution/console/LanguageConsoleBuilder.java index c7da1cea0b20..f1f77a5e8a0e 100644 --- a/platform/lang-impl/src/com/intellij/execution/console/LanguageConsoleBuilder.java +++ b/platform/lang-impl/src/com/intellij/execution/console/LanguageConsoleBuilder.java @@ -326,6 +326,12 @@ public final class LanguageConsoleBuilder { super.dispose(); } + @Override + public void scrollToEnd() { + getComponent().validate(); + super.scrollToEnd(); + } + private final class GutterUpdateScheduler extends DocumentAdapter implements DocumentBulkUpdateListener { private final ConsoleGutterComponent lineStartGutter; private final ConsoleGutterComponent lineEndGutter; diff --git a/platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.java b/platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.java index 1daa5aaafa3a..418c73b0d6e7 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.java +++ b/platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.java @@ -26,6 +26,7 @@ import com.intellij.execution.actions.EOFAction; import com.intellij.execution.filters.*; import com.intellij.execution.filters.Filter.ResultItem; import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.ui.ConsoleHighlighterLayer; import com.intellij.execution.ui.ConsoleView; import com.intellij.execution.ui.ConsoleViewContentType; import com.intellij.execution.ui.ObservableConsoleView; @@ -44,7 +45,9 @@ import com.intellij.openapi.editor.actionSystem.*; import com.intellij.openapi.editor.actions.ScrollToTheEndToolbarAction; import com.intellij.openapi.editor.actions.ToggleUseSoftWrapsToolbarAction; import com.intellij.openapi.editor.colors.EditorColorsScheme; -import com.intellij.openapi.editor.event.*; +import com.intellij.openapi.editor.event.DocumentAdapter; +import com.intellij.openapi.editor.event.DocumentEvent; +import com.intellij.openapi.editor.event.EditorMouseEvent; import com.intellij.openapi.editor.ex.DocumentEx; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.ex.util.EditorUtil; @@ -1027,7 +1030,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo if (additionalAttributes != null) { ResultItem item = additionalHighlight.getResultItems().get(0); myHyperlinks.addHighlighter(item.getHighlightStartOffset(), item.getHighlightEndOffset(), - additionalAttributes); + additionalAttributes, ConsoleHighlighterLayer.HIGHLIGHT_LAYER); } else { myHyperlinks.highlightHyperlinks(additionalHighlight, 0); diff --git a/platform/lang-impl/src/com/intellij/execution/impl/EditConfigurationsDialog.java b/platform/lang-impl/src/com/intellij/execution/impl/EditConfigurationsDialog.java index 6b4a0e99b096..42ea1de3e7b7 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/EditConfigurationsDialog.java +++ b/platform/lang-impl/src/com/intellij/execution/impl/EditConfigurationsDialog.java @@ -21,8 +21,6 @@ import com.intellij.execution.Executor; import com.intellij.execution.configurations.ConfigurationFactory; import com.intellij.execution.configurations.RunConfiguration; import com.intellij.openapi.options.ex.SingleConfigurableEditor; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -47,13 +45,6 @@ public class EditConfigurationsDialog extends SingleConfigurableEditor implement } } - @Override - public void show() { - // run configurations don't support dumb mode yet, but some code inside them may trigger root change and start it - // so let it be modal to prevent IndexNotReadyException from the configuration editors - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_MODAL, () -> super.show()); - } - public void addRunConfiguration(@NotNull final ConfigurationFactory factory) { final RunConfigurable configurable = (RunConfigurable)getConfigurable(); final SingleConfigurationConfigurable configuration = configurable.createNewConfiguration(factory); diff --git a/platform/lang-impl/src/com/intellij/find/SearchReplaceComponent.java b/platform/lang-impl/src/com/intellij/find/SearchReplaceComponent.java index a8e4d57b34c7..48449e05321f 100644 --- a/platform/lang-impl/src/com/intellij/find/SearchReplaceComponent.java +++ b/platform/lang-impl/src/com/intellij/find/SearchReplaceComponent.java @@ -216,9 +216,16 @@ public class SearchReplaceComponent extends EditorHeaderComponent implements Dat } public void requestFocusInTheSearchFieldAndSelectContent(Project project) { - mySearchTextComponent.setSelectionStart(0); - mySearchTextComponent.setSelectionEnd(mySearchTextComponent.getText().length()); + doSelectAll(mySearchTextComponent); IdeFocusManager.getInstance(project).requestFocus(mySearchTextComponent, true); + if (myReplaceTextComponent != null) { + doSelectAll(myReplaceTextComponent); + } + } + + private static void doSelectAll(JTextComponent searchTextComponent) { + searchTextComponent.setSelectionStart(0); + searchTextComponent.setSelectionEnd(searchTextComponent.getText().length()); } public void setStatusText(@NotNull String status) { @@ -346,6 +353,7 @@ public class SearchReplaceComponent extends EditorHeaderComponent implements Dat private void updateReplaceComponent(@NotNull String textToSet) { final int oldCaretPosition = myReplaceTextComponent != null ? myReplaceTextComponent.getCaretPosition() : 0; + boolean wasNull = myReplaceTextComponent == null; if (!updateTextComponent(false)) { if (!myReplaceTextComponent.getText().equals(textToSet)) { myReplaceTextComponent.setText(textToSet); @@ -366,7 +374,10 @@ public class SearchReplaceComponent extends EditorHeaderComponent implements Dat } //myReplaceTextComponent.setText(myFindModel.getStringToReplace()); - ApplicationManager.getApplication().invokeLater(() -> myReplaceTextComponent.setCaretPosition(oldCaretPosition)); + if (!wasNull) { + ApplicationManager.getApplication().invokeLater( + () -> myReplaceTextComponent.setCaretPosition(Math.min(oldCaretPosition, myReplaceTextComponent.getText().length()))); + } new VariantsCompletionAction(myReplaceTextComponent); myReplaceFieldWrapper.revalidate(); diff --git a/platform/lang-impl/src/com/intellij/formatting/AbstractBlockAlignmentProcessor.java b/platform/lang-impl/src/com/intellij/formatting/AbstractBlockAlignmentProcessor.java index f3571b6d7c03..c202510eabbe 100644 --- a/platform/lang-impl/src/com/intellij/formatting/AbstractBlockAlignmentProcessor.java +++ b/platform/lang-impl/src/com/intellij/formatting/AbstractBlockAlignmentProcessor.java @@ -20,12 +20,6 @@ import org.jetbrains.annotations.Nullable; import java.util.Set; -/** - * Gof Template Method for {@link BlockAlignmentProcessor}. - * - * @author Denis Zhdanov - * @since 4/29/11 11:52 AM - */ public abstract class AbstractBlockAlignmentProcessor implements BlockAlignmentProcessor { @Override diff --git a/platform/lang-impl/src/com/intellij/formatting/AlignmentImpl.java b/platform/lang-impl/src/com/intellij/formatting/AlignmentImpl.java index 3af772a999e2..35b60ee4db4a 100644 --- a/platform/lang-impl/src/com/intellij/formatting/AlignmentImpl.java +++ b/platform/lang-impl/src/com/intellij/formatting/AlignmentImpl.java @@ -33,20 +33,10 @@ public class AlignmentImpl extends Alignment { private AlignmentImpl myParentAlignment; private ProbablyIncreasingLowerboundAlgorithm myOffsetRespBlocksCalculator; - /** - * Creates new AlignmentImpl object with 'false' as 'allows backward shift' argument flag. - */ AlignmentImpl() { this(false, Anchor.LEFT); } - /** - * Creates new AlignmentImpl object with the given 'allows backward shift' argument flag. - * - * @param allowBackwardShift flag that indicates if it should be possible to shift former aligned block to right - * in order to align to subsequent aligned block (see {@link Alignment#createAlignment(boolean, Anchor)}) - * @param anchor alignment anchor (see {@link Alignment#createAlignment(boolean, Anchor)}) - */ AlignmentImpl(boolean allowBackwardShift, @NotNull Anchor anchor) { myAllowBackwardShift = allowBackwardShift; myAnchor = anchor; diff --git a/platform/lang-impl/src/com/intellij/ide/actions/CreateDirectoryOrPackageAction.java b/platform/lang-impl/src/com/intellij/ide/actions/CreateDirectoryOrPackageAction.java index 527f954d8e0b..e84ff92db71f 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/CreateDirectoryOrPackageAction.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/CreateDirectoryOrPackageAction.java @@ -21,8 +21,6 @@ import com.intellij.ide.IdeView; import com.intellij.ide.util.DirectoryChooserUtil; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.psi.PsiDirectory; @@ -50,11 +48,12 @@ public class CreateDirectoryOrPackageAction extends AnAction implements DumbAwar final CreateDirectoryOrPackageHandler validator = new CreateDirectoryOrPackageHandler(project, directory, isDirectory, isDirectory ? "\\/" : "."); - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, () -> Messages.showInputDialog(project, isDirectory - ? IdeBundle.message("prompt.enter.new.directory.name") - : IdeBundle.message("prompt.enter.new.package.name"), - isDirectory ? IdeBundle.message("title.new.directory") : IdeBundle.message("title.new.package"), - Messages.getQuestionIcon(), "", validator)); + Messages.showInputDialog(project, + isDirectory ? IdeBundle.message("prompt.enter.new.directory.name") + : IdeBundle.message("prompt.enter.new.package.name"), + isDirectory ? IdeBundle.message("title.new.directory") + : IdeBundle.message("title.new.package"), + Messages.getQuestionIcon(), "", validator); final PsiElement result = validator.getCreatedElement(); if (result != null) { diff --git a/platform/lang-impl/src/com/intellij/ide/actions/CreateFileAction.java b/platform/lang-impl/src/com/intellij/ide/actions/CreateFileAction.java index adaad1e7aa38..b55241004bfc 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/CreateFileAction.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/CreateFileAction.java @@ -20,12 +20,9 @@ import com.intellij.icons.AllIcons; import com.intellij.ide.IdeBundle; import com.intellij.internal.statistic.UsageTrigger; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.FileTypeManager; import com.intellij.openapi.fileTypes.ex.FileTypeChooser; import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.InputValidatorEx; import com.intellij.openapi.ui.Messages; @@ -231,10 +228,8 @@ public class CreateFileAction extends CreateElementActionBase implements DumbAwa final Project project = psiDirectory.getProject(); final boolean[] result = {false}; - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, () -> { - FileTypeChooser.getKnownFileTypeOrAssociate(psiDirectory.getVirtualFile(), getFileName(inputString), project); - result[0] = super.canClose(getFileName(inputString)); - }); + FileTypeChooser.getKnownFileTypeOrAssociate(psiDirectory.getVirtualFile(), getFileName(inputString), project); + result[0] = super.canClose(getFileName(inputString)); return result[0]; } } diff --git a/platform/lang-impl/src/com/intellij/ide/actions/CreateFileFromTemplateAction.java b/platform/lang-impl/src/com/intellij/ide/actions/CreateFileFromTemplateAction.java index 6872aa59936a..972ebc79bc3a 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/CreateFileFromTemplateAction.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/CreateFileFromTemplateAction.java @@ -21,8 +21,6 @@ import com.intellij.ide.fileTemplates.FileTemplateUtil; import com.intellij.ide.fileTemplates.actions.CreateFromTemplateActionBase; import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.vfs.VirtualFile; @@ -46,10 +44,7 @@ public abstract class CreateFileFromTemplateAction extends CreateFromTemplateAct } protected PsiFile createFileFromTemplate(final String name, final FileTemplate template, final PsiDirectory dir) { - final PsiFile[] file = new PsiFile[1]; - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, - () -> file[0] = createFileFromTemplate(name, template, dir, getDefaultTemplateProperty(), true)); - return file[0]; + return createFileFromTemplate(name, template, dir, getDefaultTemplateProperty(), true); } @Nullable diff --git a/platform/lang-impl/src/com/intellij/ide/actions/NewElementAction.java b/platform/lang-impl/src/com/intellij/ide/actions/NewElementAction.java index 8e5722cacaf6..058c4d737b0d 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/NewElementAction.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/NewElementAction.java @@ -95,7 +95,7 @@ public class NewElementAction extends AnAction implements DumbAware, PopupActio return; } - presentation.setEnabled(!ActionGroupUtil.isGroupEmpty(getGroup(e.getDataContext()), e)); + presentation.setEnabled(!ActionGroupUtil.isGroupEmpty(getGroup(e.getDataContext()), e, isEnabledInModalContext())); } protected boolean isEnabled(AnActionEvent e) { diff --git a/platform/lang-impl/src/com/intellij/ide/fileTemplates/actions/CreateFromTemplateActionBase.java b/platform/lang-impl/src/com/intellij/ide/fileTemplates/actions/CreateFromTemplateActionBase.java index 789f1a425270..a6b1a60fb198 100644 --- a/platform/lang-impl/src/com/intellij/ide/fileTemplates/actions/CreateFromTemplateActionBase.java +++ b/platform/lang-impl/src/com/intellij/ide/fileTemplates/actions/CreateFromTemplateActionBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * 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. @@ -100,6 +100,8 @@ public abstract class CreateFromTemplateActionBase extends AnAction { template.addVariable(variable, null, "\"" + variable + "\"", true); } WriteCommandAction.runWriteCommandAction(project, () -> editor.getDocument().setText(template.getTemplateText())); + //ensure caret at the start of the template + editor.getCaretModel().moveToOffset(0); TemplateManager.getInstance(project).startTemplate(editor, template); } diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkRootActionBase.java b/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkRootActionBase.java index 422bc7ba2af1..4e85a2c91c77 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkRootActionBase.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/actions/MarkRootActionBase.java @@ -22,8 +22,6 @@ import com.intellij.openapi.actionSystem.LangDataKeys; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.*; import com.intellij.openapi.roots.impl.DirectoryIndex; @@ -81,11 +79,10 @@ public abstract class MarkRootActionBase extends DumbAwareAction { } static void commitModel(@NotNull Module module, ModifiableRootModel model) { - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, - () -> ApplicationManager.getApplication().runWriteAction(() -> { - model.commit(); - module.getProject().save(); - })); + ApplicationManager.getApplication().runWriteAction(() -> { + model.commit(); + module.getProject().save(); + }); } protected abstract void modifyRoots(VirtualFile file, ContentEntry entry); diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewImpl.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewImpl.java index 55e45c235803..901c45c589ca 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewImpl.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewImpl.java @@ -522,6 +522,7 @@ public class ProjectViewImpl extends ProjectView implements PersistentStateCompo } private void createToolbarActions() { + if (myActionGroup == null) return; List titleActions = ContainerUtil.newSmartList(); myActionGroup.removeAll(); if (ProjectViewDirectoryHelper.getInstance(myProject).supportsFlattenPackages()) { diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/RenameModuleHandler.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/RenameModuleHandler.java index 63a2ed17ea68..dd2b6bc45e4c 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/RenameModuleHandler.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/RenameModuleHandler.java @@ -28,8 +28,6 @@ import com.intellij.openapi.module.ModifiableModuleModel; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.module.ModuleWithNameAlreadyExists; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.InputValidator; import com.intellij.openapi.ui.Messages; @@ -98,8 +96,7 @@ public class RenameModuleHandler implements RenameHandler, TitledHandler { final String oldName = myModule.getName(); final ModifiableModuleModel modifiableModel = renameModule(inputString); if (modifiableModel == null) return false; - CommandProcessor.getInstance().executeCommand(myProject, () -> DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, - () -> ApplicationManager.getApplication().runWriteAction(() -> modifiableModel.commit())), IdeBundle.message("command.renaming.module", oldName), null); + CommandProcessor.getInstance().executeCommand(myProject, () -> ApplicationManager.getApplication().runWriteAction(() -> modifiableModel.commit()), IdeBundle.message("command.renaming.module", oldName), null); return true; } diff --git a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/ContributorsBasedGotoByModel.java b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/ContributorsBasedGotoByModel.java index f184e3c40f57..a0a112f1e7fa 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/ContributorsBasedGotoByModel.java +++ b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/ContributorsBasedGotoByModel.java @@ -269,6 +269,9 @@ public abstract class ContributorsBasedGotoByModel implements ChooseByNameModelE @Override public String getElementName(Object element) { + if (!(element instanceof NavigationItem)) { + throw new AssertionError(element + " of " + element.getClass()); + } return ((NavigationItem)element).getName(); } diff --git a/platform/lang-impl/src/com/intellij/ide/util/projectWizard/AbstractNewProjectDialog.java b/platform/lang-impl/src/com/intellij/ide/util/projectWizard/AbstractNewProjectDialog.java index c4e7b86f8bf9..126b5734b0c3 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/projectWizard/AbstractNewProjectDialog.java +++ b/platform/lang-impl/src/com/intellij/ide/util/projectWizard/AbstractNewProjectDialog.java @@ -19,8 +19,6 @@ import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.util.Pair; @@ -103,8 +101,4 @@ public abstract class AbstractNewProjectDialog extends DialogWrapper { return new Action[0]; } - @Override - public void show() { - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, super::show); - } } 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 dda254f7454c..c436c1eecbe0 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 @@ -23,8 +23,6 @@ import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.fileChooser.FileChooserDescriptor; import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.ui.*; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.wm.impl.welcomeScreen.AbstractActionWithPanel; @@ -127,8 +125,7 @@ public class ProjectSettingsStepBase extends AbstractActionWithPanel implements if (dialog != null) { dialog.close(DialogWrapper.OK_EXIT_CODE); } - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, - () -> myCallback.consume(ProjectSettingsStepBase.this)); + myCallback.consume(ProjectSettingsStepBase.this); } } }; diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ProjectSdksModel.java b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ProjectSdksModel.java index 8e7bfa5999df..87ab99f56f33 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ProjectSdksModel.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ProjectSdksModel.java @@ -130,7 +130,7 @@ public class ProjectSdksModel implements SdkModel { throw new ConfigurationException(errorString[0]); } - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, () -> doApply()); + doApply(); myModified = false; } diff --git a/platform/lang-impl/src/com/intellij/platform/renameProject/RenameProjectHandler.java b/platform/lang-impl/src/com/intellij/platform/renameProject/RenameProjectHandler.java index a94d7248d616..018fa54b854b 100644 --- a/platform/lang-impl/src/com/intellij/platform/renameProject/RenameProjectHandler.java +++ b/platform/lang-impl/src/com/intellij/platform/renameProject/RenameProjectHandler.java @@ -27,8 +27,6 @@ import com.intellij.openapi.module.ModifiableModuleModel; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.module.ModuleWithNameAlreadyExists; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ex.ProjectEx; import com.intellij.openapi.ui.InputValidator; @@ -121,7 +119,9 @@ public class RenameProjectHandler implements RenameHandler, TitledHandler { return false; } final Ref success = Ref.create(Boolean.TRUE); - CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, () -> modifiableModel.commit())), IdeBundle.message("command.renaming.module", module.getName()), null); + CommandProcessor.getInstance().executeCommand(project, + () -> ApplicationManager.getApplication().runWriteAction(() -> modifiableModel.commit()), + IdeBundle.message("command.renaming.module", module.getName()), null); return success.get().booleanValue(); } return true; diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/lineIndent/IndentCalculator.java b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/lineIndent/IndentCalculator.java index 92d6c3c35a42..ddd3564b8943 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/lineIndent/IndentCalculator.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/lineIndent/IndentCalculator.java @@ -34,7 +34,7 @@ import org.jetbrains.annotations.Nullable; import static com.intellij.formatting.Indent.Type.CONTINUATION; import static com.intellij.formatting.Indent.Type.NORMAL; -class IndentCalculator { +public class IndentCalculator { private @NotNull final Project myProject; private @NotNull final Editor myEditor; diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/lineIndent/JavaLikeLangLineIndentProvider.java b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/lineIndent/JavaLikeLangLineIndentProvider.java index 0daff82bbf0f..bc98f039b689 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/lineIndent/JavaLikeLangLineIndentProvider.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/lineIndent/JavaLikeLangLineIndentProvider.java @@ -93,7 +93,8 @@ public abstract class JavaLikeLangLineIndentProvider implements LineIndentProvid return myFactory.createIndentCalculator(NONE, IndentCalculator.LINE_AFTER); } } - else if (getPosition(editor, offset + 1).isAt(BlockClosingBrace)) { + else if (getPosition(editor, offset + 1).matchesRule( + position -> position.isAt(BlockClosingBrace) && !position.after().afterOptional(Whitespace).isAt(Comma))) { return myFactory.createIndentCalculator( NONE, position -> { @@ -133,7 +134,7 @@ public abstract class JavaLikeLangLineIndentProvider implements LineIndentProvid return myFactory.createIndentCalculator(CONTINUATION, IndentCalculator.LINE_BEFORE); } else if (getPosition(editor, offset).matchesRule( - position -> position.before().isAt(BlockOpeningBrace) + position -> position.before().isAt(BlockOpeningBrace) && !position.before().beforeOptional(Whitespace).isAt(LeftParenthesis) )) { SemanticEditorPosition position = getPosition(editor, offset).before(); return myFactory.createIndentCalculator(getIndentTypeInBlock(project, language, position), this::getBlockStatementStartOffset); diff --git a/platform/lang-impl/src/com/intellij/refactoring/BaseRefactoringProcessor.java b/platform/lang-impl/src/com/intellij/refactoring/BaseRefactoringProcessor.java index 3a67137e7ece..24a016633df9 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/BaseRefactoringProcessor.java +++ b/platform/lang-impl/src/com/intellij/refactoring/BaseRefactoringProcessor.java @@ -64,7 +64,6 @@ import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashSet; import com.intellij.util.containers.MultiMap; -import com.intellij.util.ui.UIUtil; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -479,6 +478,8 @@ public abstract class BaseRefactoringProcessor implements Runnable { } }); + DumbService.getInstance(myProject).completeJustSubmittedTasks(); + for(Map.Entry e: preparedData.entrySet()) { //noinspection unchecked e.getKey().performOperation(myProject, e.getValue()); diff --git a/platform/lang-impl/src/com/intellij/refactoring/changeSignature/inplace/ChangeSignaturePassFactory.java b/platform/lang-impl/src/com/intellij/refactoring/changeSignature/inplace/ChangeSignaturePassFactory.java index 9e20a2f1902f..80527ffcfa60 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/changeSignature/inplace/ChangeSignaturePassFactory.java +++ b/platform/lang-impl/src/com/intellij/refactoring/changeSignature/inplace/ChangeSignaturePassFactory.java @@ -22,7 +22,6 @@ import com.intellij.codeInsight.daemon.impl.HighlightInfo; import com.intellij.codeInsight.daemon.impl.HighlightInfoType; import com.intellij.codeInsight.daemon.impl.UpdateHighlightersUtil; import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction; -import com.intellij.lang.findUsages.DescriptiveNameUtil; import com.intellij.openapi.components.AbstractProjectComponent; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.colors.CodeInsightColors; @@ -92,7 +91,7 @@ public class ChangeSignaturePassFactory extends AbstractProjectComponent impleme builder.textAttributes(attributes); builder.descriptionAndTooltip(SIGNATURE_SHOULD_BE_POSSIBLY_CHANGED); info = builder.createUnconditionally(); - QuickFixAction.registerQuickFixAction(info, new ApplyChangeSignatureAction(DescriptiveNameUtil.getDescriptiveName(element))); + QuickFixAction.registerQuickFixAction(info, new ApplyChangeSignatureAction(currentRefactoring.getInitialName())); } Collection infos = info != null ? Collections.singletonList(info) : Collections.emptyList(); UpdateHighlightersUtil.setHighlightersToEditor(myProject, myDocument, 0, myFile.getTextLength(), infos, getColorsScheme(), getId()); diff --git a/platform/lang-impl/src/com/intellij/refactoring/changeSignature/inplace/InplaceChangeSignature.java b/platform/lang-impl/src/com/intellij/refactoring/changeSignature/inplace/InplaceChangeSignature.java index 44c0d8ce84eb..e6fc38c755aa 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/changeSignature/inplace/InplaceChangeSignature.java +++ b/platform/lang-impl/src/com/intellij/refactoring/changeSignature/inplace/InplaceChangeSignature.java @@ -16,6 +16,7 @@ package com.intellij.refactoring.changeSignature.inplace; import com.intellij.codeInsight.highlighting.HighlightManager; +import com.intellij.lang.findUsages.DescriptiveNameUtil; import com.intellij.openapi.application.Result; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.command.impl.FinishMarkAction; @@ -53,6 +54,7 @@ import com.intellij.refactoring.changeSignature.ChangeSignatureHandler; import com.intellij.refactoring.rename.inplace.InplaceRefactoring; import com.intellij.ui.NonFocusableCheckBox; import com.intellij.ui.awt.RelativePoint; +import com.intellij.ui.components.JBLabel; import com.intellij.util.ui.PositionTracker; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -66,6 +68,7 @@ public class InplaceChangeSignature implements DocumentListener { private ChangeInfo myCurrentInfo; private ChangeInfo myStableChange; private String myInitialSignature; + private String myInitialName; private Editor myEditor; private LanguageChangeSignatureDetector myDetector; @@ -97,6 +100,7 @@ public class InplaceChangeSignature implements DocumentListener { myDetector = LanguageChangeSignatureDetectors.INSTANCE.forLanguage(element.getLanguage()); myStableChange = myDetector.createInitialChangeInfo(element); myInitialSignature = myDetector.extractSignature(myStableChange); + myInitialName = DescriptiveNameUtil.getDescriptiveName(myStableChange.getMethod()); TextRange highlightingRange = myDetector.getHighlightingRange(myStableChange); HighlightManager highlightManager = HighlightManager.getInstance(myProject); @@ -121,6 +125,10 @@ public class InplaceChangeSignature implements DocumentListener { return myCurrentInfo; } + public String getInitialName() { + return myInitialName; + } + public String getInitialSignature() { return myInitialSignature; } @@ -168,21 +176,25 @@ public class InplaceChangeSignature implements DocumentListener { return; } - if (myCurrentInfo == null) { - myCurrentInfo = myStableChange; - } - String signature = myDetector.extractSignature(myCurrentInfo); - ChangeInfo changeInfo = myDetector.createNextChangeInfo(signature, myCurrentInfo, myDelegate); - if (changeInfo == null && myCurrentInfo != null) { - myStableChange = myCurrentInfo; - } - if (changeInfo != null) { - updateMethodSignature(changeInfo); - } - myCurrentInfo = changeInfo; + updateCurrentInfo(); }); } + private void updateCurrentInfo() { + if (myCurrentInfo == null) { + myCurrentInfo = myStableChange; + } + String signature = myDetector.extractSignature(myCurrentInfo); + ChangeInfo changeInfo = myDetector.createNextChangeInfo(signature, myCurrentInfo, myDelegate); + if (changeInfo == null && myCurrentInfo != null) { + myStableChange = myCurrentInfo; + } + if (changeInfo != null) { + updateMethodSignature(changeInfo); + } + myCurrentInfo = changeInfo; + } + private void updateMethodSignature(ChangeInfo changeInfo) { ArrayList deleteRanges = new ArrayList<>(); ArrayList newRanges = new ArrayList<>(); @@ -209,9 +221,13 @@ public class InplaceChangeSignature implements DocumentListener { protected void showBalloon() { NonFocusableCheckBox checkBox = new NonFocusableCheckBox(RefactoringBundle.message("delegation.panel.delegate.via.overloading.method")); - checkBox.addActionListener(e -> myDelegate = checkBox.isSelected()); + checkBox.addActionListener(e -> { + myDelegate = checkBox.isSelected(); + updateCurrentInfo(); + }); JPanel content = new JPanel(new BorderLayout()); - content.add(myPreview.getComponent(), BorderLayout.NORTH); + content.add(new JBLabel("Performed signature modifications:"), BorderLayout.NORTH); + content.add(myPreview.getComponent(), BorderLayout.CENTER); updateMethodSignature(myStableChange); content.add(checkBox, BorderLayout.SOUTH); final BalloonBuilder balloonBuilder = JBPopupFactory.getInstance().createDialogBalloonBuilder(content, null).setSmallVariant(true); diff --git a/platform/lang-impl/src/com/intellij/refactoring/copy/CopyFilesOrDirectoriesHandler.java b/platform/lang-impl/src/com/intellij/refactoring/copy/CopyFilesOrDirectoriesHandler.java index 7389f7edc630..b791850c0434 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/copy/CopyFilesOrDirectoriesHandler.java +++ b/platform/lang-impl/src/com/intellij/refactoring/copy/CopyFilesOrDirectoriesHandler.java @@ -21,10 +21,9 @@ import com.intellij.ide.util.EditorHelper; import com.intellij.ide.util.PlatformPackageUtil; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.Result; +import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.ui.Messages; @@ -103,8 +102,7 @@ public class CopyFilesOrDirectoriesHandler extends CopyHandlerDelegateBase { } public static void copyAsFiles(PsiElement[] elements, @Nullable PsiDirectory defaultTargetDirectory, Project project) { - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_MODAL, () -> - doCopyAsFiles(elements, defaultTargetDirectory, project)); + doCopyAsFiles(elements, defaultTargetDirectory, project); } private static void doCopyAsFiles(PsiElement[] elements, @Nullable PsiDirectory defaultTargetDirectory, Project project) { @@ -224,37 +222,30 @@ public class CopyFilesOrDirectoriesHandler extends CopyHandlerDelegateBase { } String title = RefactoringBundle.message(doClone ? "copy,handler.clone.files.directories" : "copy.handler.copy.files.directories"); - new WriteCommandAction(project, title) { - @Override - protected void run(@NotNull Result result) { - try { - PsiFile firstFile = null; - final int[] choice = elements.length > 1 || elements[0] instanceof PsiDirectory ? new int[]{-1} : null; - for (PsiElement element : elements) { - PsiFile f = copyToDirectory((PsiFileSystemItem)element, newName, targetDirectory, choice); - if (firstFile == null) { - firstFile = f; - } - } - - if (firstFile != null && openInEditor) { - CopyHandler.updateSelectionInActiveProjectView(firstFile, project, doClone); - if (!(firstFile instanceof PsiBinaryFile)) { - EditorHelper.openInEditor(firstFile); - ApplicationManager.getApplication().invokeLater(() -> ToolWindowManager.getInstance(project).activateEditorComponent(), project.getDisposed()); - } - } - } - catch (final IncorrectOperationException ex) { - ApplicationManager.getApplication().invokeLater( - () -> Messages.showErrorDialog(project, ex.getMessage(), RefactoringBundle.message("error.title"))); - } - catch (final IOException ex) { - ApplicationManager.getApplication().invokeLater( - () -> Messages.showErrorDialog(project, ex.getMessage(), RefactoringBundle.message("error.title"))); + try { + PsiFile firstFile = null; + final int[] choice = elements.length > 1 || elements[0] instanceof PsiDirectory ? new int[]{-1} : null; + for (PsiElement element : elements) { + PsiFile f = copyToDirectory((PsiFileSystemItem)element, newName, targetDirectory, choice, title); + if (firstFile == null) { + firstFile = f; } } - }.execute(); + + if (firstFile != null && openInEditor) { + CopyHandler.updateSelectionInActiveProjectView(firstFile, project, doClone); + if (!(firstFile instanceof PsiBinaryFile)) { + EditorHelper.openInEditor(firstFile); + ToolWindowManager.getInstance(project).activateEditorComponent(); + } + } + } + catch (final IncorrectOperationException ex) { + Messages.showErrorDialog(project, ex.getMessage(), RefactoringBundle.message("error.title")); + } + catch (final IOException ex) { + Messages.showErrorDialog(project, ex.getMessage(), RefactoringBundle.message("error.title")); + } } /** @@ -266,7 +257,7 @@ public class CopyFilesOrDirectoriesHandler extends CopyHandlerDelegateBase { public static PsiFile copyToDirectory(@NotNull PsiFileSystemItem elementToCopy, @Nullable String newName, @NotNull PsiDirectory targetDirectory) throws IncorrectOperationException, IOException { - return copyToDirectory(elementToCopy, newName, targetDirectory, null); + return copyToDirectory(elementToCopy, newName, targetDirectory, null, null); } /** @@ -279,12 +270,18 @@ public class CopyFilesOrDirectoriesHandler extends CopyHandlerDelegateBase { public static PsiFile copyToDirectory(@NotNull PsiFileSystemItem elementToCopy, @Nullable String newName, @NotNull PsiDirectory targetDirectory, - @Nullable int[] choice) throws IncorrectOperationException, IOException { + @Nullable int[] choice, + @Nullable String title) throws IncorrectOperationException, IOException { if (elementToCopy instanceof PsiFile) { PsiFile file = (PsiFile)elementToCopy; String name = newName == null ? file.getName() : newName; if (checkFileExist(targetDirectory, choice, file, name, "Copy")) return null; - return targetDirectory.copyFileFrom(name, file); + return new WriteCommandAction(targetDirectory.getProject(), title) { + @Override + protected void run(@NotNull Result result) throws Throwable { + result.setResult(targetDirectory.copyFileFrom(name, file)); + } + }.execute().getResultObject(); } else if (elementToCopy instanceof PsiDirectory) { PsiDirectory directory = (PsiDirectory)elementToCopy; @@ -301,7 +298,7 @@ public class CopyFilesOrDirectoriesHandler extends CopyHandlerDelegateBase { PsiElement[] children = directory.getChildren(); for (PsiElement child : children) { PsiFileSystemItem item = (PsiFileSystemItem)child; - PsiFile f = copyToDirectory(item, item.getName(), subdirectory, choice); + PsiFile f = copyToDirectory(item, item.getName(), subdirectory, choice, title); if (firstFile == null) { firstFile = f; } @@ -334,7 +331,7 @@ public class CopyFilesOrDirectoriesHandler extends CopyHandlerDelegateBase { } if (selection == 0 && file != existing) { - existing.delete(); + WriteAction.run(() -> existing.delete()); } else { return true; diff --git a/platform/lang-impl/src/com/intellij/refactoring/move/moveFilesOrDirectories/MoveFilesOrDirectoriesDialog.java b/platform/lang-impl/src/com/intellij/refactoring/move/moveFilesOrDirectories/MoveFilesOrDirectoriesDialog.java index 9755cd426ba1..a7ed55525d58 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/move/moveFilesOrDirectories/MoveFilesOrDirectoriesDialog.java +++ b/platform/lang-impl/src/com/intellij/refactoring/move/moveFilesOrDirectories/MoveFilesOrDirectoriesDialog.java @@ -27,7 +27,6 @@ import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; import com.intellij.openapi.fileChooser.FileChooserFactory; import com.intellij.openapi.help.HelpManager; import com.intellij.openapi.keymap.KeymapUtil; -import com.intellij.openapi.project.DumbModePermission; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; @@ -208,7 +207,7 @@ public class MoveFilesOrDirectoriesDialog extends DialogWrapper { Messages.showMessageDialog(myProject, "Move refactoring is not available while indexing is in progress", "Indexing", null); return; } - + CommandProcessor.getInstance().executeCommand(myProject, () -> { final Runnable action = () -> { String directoryName = myTargetDirectoryField.getChildComponent().getText().replace(File.separatorChar, '/'); @@ -220,15 +219,13 @@ public class MoveFilesOrDirectoriesDialog extends DialogWrapper { } }; - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_MODAL, () -> { - ApplicationManager.getApplication().runWriteAction(action); - if (myTargetDirectory == null) { - CommonRefactoringUtil.showErrorMessage(getTitle(), - RefactoringBundle.message("cannot.create.directory"), myHelpID, myProject); - return; - } - myCallback.run(this); - }); + ApplicationManager.getApplication().runWriteAction(action); + if (myTargetDirectory == null) { + CommonRefactoringUtil.showErrorMessage(getTitle(), + RefactoringBundle.message("cannot.create.directory"), myHelpID, myProject); + return; + } + myCallback.run(this); }, RefactoringBundle.message("move.title"), null); } @@ -236,9 +233,4 @@ public class MoveFilesOrDirectoriesDialog extends DialogWrapper { return myTargetDirectory; } - @Override - public void show() { - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_MODAL, () -> super.show()); - } - } diff --git a/platform/lang-impl/src/com/intellij/refactoring/move/moveFilesOrDirectories/MoveFilesOrDirectoriesProcessor.java b/platform/lang-impl/src/com/intellij/refactoring/move/moveFilesOrDirectories/MoveFilesOrDirectoriesProcessor.java index f7158cb7dcfa..cf7ed622bf00 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/move/moveFilesOrDirectories/MoveFilesOrDirectoriesProcessor.java +++ b/platform/lang-impl/src/com/intellij/refactoring/move/moveFilesOrDirectories/MoveFilesOrDirectoriesProcessor.java @@ -19,6 +19,7 @@ import com.intellij.ide.util.EditorHelper; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.paths.PsiDynaReference; +import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.psi.PsiDirectory; @@ -181,6 +182,8 @@ public class MoveFilesOrDirectoriesProcessor extends BaseRefactoringProcessor { // sort by offset descending to process correctly several usages in one PsiElement [IDEADEV-33013] CommonRefactoringUtil.sortDepthFirstRightLeftOrder(usages); + DumbService.getInstance(myProject).completeJustSubmittedTasks(); + // fix references in moved files to outer files for (PsiFile movedFile : movedFiles) { MoveFileHandler.forElement(movedFile).updateMovedFile(movedFile); diff --git a/platform/lang-impl/src/com/intellij/refactoring/move/moveFilesOrDirectories/MoveFilesOrDirectoriesUtil.java b/platform/lang-impl/src/com/intellij/refactoring/move/moveFilesOrDirectories/MoveFilesOrDirectoriesUtil.java index fb1e9471fe6d..9ffdc237f2b2 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/move/moveFilesOrDirectories/MoveFilesOrDirectoriesUtil.java +++ b/platform/lang-impl/src/com/intellij/refactoring/move/moveFilesOrDirectories/MoveFilesOrDirectoriesUtil.java @@ -22,7 +22,6 @@ import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.util.Computable; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.refactoring.RefactoringBundle; @@ -153,13 +152,7 @@ public class MoveFilesOrDirectoriesUtil { for (final PsiElement psiElement : newElements) { if (psiElement instanceof PsiFile) { final PsiFile file = (PsiFile)psiElement; - final boolean fileExist = ApplicationManager.getApplication().runWriteAction(new Computable() { - @Override - public Boolean compute() { - return CopyFilesOrDirectoriesHandler.checkFileExist(targetDirectory1, choice, file, file.getName(), "Move"); - } - }); - if (fileExist) continue; + if (CopyFilesOrDirectoriesHandler.checkFileExist(targetDirectory1, choice, file, file.getName(), "Move")) continue; } checkMove(psiElement, targetDirectory1); els.add(psiElement); diff --git a/platform/lang-impl/src/com/intellij/refactoring/rename/RenameProcessor.java b/platform/lang-impl/src/com/intellij/refactoring/rename/RenameProcessor.java index 8c438ab4442e..5869fead47d0 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/rename/RenameProcessor.java +++ b/platform/lang-impl/src/com/intellij/refactoring/rename/RenameProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * 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. @@ -200,6 +200,26 @@ public class RenameProcessor extends BaseRefactoringProcessor { } } + final int[] choice = myAllRenames.size() > 1 ? new int[]{-1} : null; + try { + for (Iterator> iterator = myAllRenames.entrySet().iterator(); iterator.hasNext(); ) { + Map.Entry entry = iterator.next(); + if (entry.getKey() instanceof PsiFile) { + final PsiFile file = (PsiFile)entry.getKey(); + final PsiDirectory containingDirectory = file.getContainingDirectory(); + if (CopyFilesOrDirectoriesHandler.checkFileExist(containingDirectory, choice, file, entry.getValue(), "Rename")) { + iterator.remove(); + continue; + } + } + RenameUtil.checkRename(entry.getKey(), entry.getValue()); + } + } + catch (IncorrectOperationException e) { + CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("rename.title"), e.getMessage(), getHelpID(), myProject); + return false; + } + final Set usagesSet = ContainerUtil.newLinkedHashSet(usagesIn); usagesSet.addAll(variableUsages); final List conflictUsages = RenameUtil.removeConflictUsages(usagesSet); @@ -350,31 +370,6 @@ public class RenameProcessor extends BaseRefactoringProcessor { @Override public void performRefactoring(@NotNull UsageInfo[] usages) { - final int[] choice = myAllRenames.size() > 1 ? new int[]{-1} : null; - String message = null; - try { - for (Iterator> iterator = myAllRenames.entrySet().iterator(); iterator.hasNext(); ) { - Map.Entry entry = iterator.next(); - if (entry.getKey() instanceof PsiFile) { - final PsiFile file = (PsiFile)entry.getKey(); - final PsiDirectory containingDirectory = file.getContainingDirectory(); - if (CopyFilesOrDirectoriesHandler.checkFileExist(containingDirectory, choice, file, entry.getValue(), "Rename")) { - iterator.remove(); - continue; - } - } - RenameUtil.checkRename(entry.getKey(), entry.getValue()); - } - } - catch (IncorrectOperationException e) { - message = e.getMessage(); - } - - if (message != null) { - CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("rename.title"), message, getHelpID(), myProject); - return; - } - List postRenameCallbacks = new ArrayList<>(); final MultiMap classified = classifyUsages(myAllRenames.keySet(), usages); diff --git a/platform/lang-impl/src/com/intellij/refactoring/safeDelete/SafeDeleteDialog.java b/platform/lang-impl/src/com/intellij/refactoring/safeDelete/SafeDeleteDialog.java index cd0bd871da21..8faa197ce9b6 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/safeDelete/SafeDeleteDialog.java +++ b/platform/lang-impl/src/com/intellij/refactoring/safeDelete/SafeDeleteDialog.java @@ -21,7 +21,6 @@ import com.intellij.ide.util.DeleteUtil; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.fileEditor.impl.NonProjectFileWritingAccessProvider; import com.intellij.openapi.help.HelpManager; -import com.intellij.openapi.project.DumbModePermission; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; @@ -202,14 +201,14 @@ public class SafeDeleteDialog extends DialogWrapper { return; } - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_MODAL, () -> NonProjectFileWritingAccessProvider.disableChecksDuring(() -> { + NonProjectFileWritingAccessProvider.disableChecksDuring(() -> { if (myCallback != null && isSafeDelete()) { myCallback.run(this); } else { super.doOKAction(); } - })); + }); final RefactoringSettings refactoringSettings = RefactoringSettings.getInstance(); if (myCbSafeDelete != null) { diff --git a/platform/lang-impl/src/com/intellij/refactoring/safeDelete/SafeDeleteProcessor.java b/platform/lang-impl/src/com/intellij/refactoring/safeDelete/SafeDeleteProcessor.java index 6cb99de89333..713e43cabc4d 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/safeDelete/SafeDeleteProcessor.java +++ b/platform/lang-impl/src/com/intellij/refactoring/safeDelete/SafeDeleteProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * 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. @@ -23,8 +23,6 @@ import com.intellij.lang.refactoring.RefactoringSupportProvider; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Ref; @@ -51,7 +49,6 @@ import com.intellij.usageView.UsageViewUtil; import com.intellij.usages.*; import com.intellij.util.ArrayUtil; import com.intellij.util.IncorrectOperationException; -import com.intellij.util.Processor; import com.intellij.util.containers.HashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -244,7 +241,7 @@ public class SafeDeleteProcessor extends BaseRefactoringProcessor { presentation.setTargetsNodeText(RefactoringBundle.message("attempting.to.delete.targets.node.text")); presentation.setShowReadOnlyStatusAsRed(true); presentation.setShowCancelButton(true); - presentation.setCodeUsagesString(RefactoringBundle.message("references.found.in.code")); + presentation.setCodeUsagesString(RefactoringBundle.message("safe.delete.conflict.title")); presentation.setUsagesInGeneratedCodeString(RefactoringBundle.message("references.found.in.generated.code")); presentation.setNonCodeUsagesString(RefactoringBundle.message("occurrences.found.in.comments.strings.and.non.java.files")); presentation.setUsagesString(RefactoringBundle.message("usageView.usagesText")); @@ -385,17 +382,15 @@ public class SafeDeleteProcessor extends BaseRefactoringProcessor { } } - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_MODAL, () -> { - for (PsiElement element : myElements) { - for (SafeDeleteProcessorDelegate delegate : Extensions.getExtensions(SafeDeleteProcessorDelegate.EP_NAME)) { - if (delegate.handlesElement(element)) { - delegate.prepareForDeletion(element); - } + for (PsiElement element : myElements) { + for (SafeDeleteProcessorDelegate delegate : Extensions.getExtensions(SafeDeleteProcessorDelegate.EP_NAME)) { + if (delegate.handlesElement(element)) { + delegate.prepareForDeletion(element); } - - element.delete(); } - }); + + element.delete(); + } } catch (IncorrectOperationException e) { RefactoringUIUtil.processIncorrectOperation(myProject, e); } diff --git a/platform/lang-impl/src/com/intellij/refactoring/ui/RefactoringDialog.java b/platform/lang-impl/src/com/intellij/refactoring/ui/RefactoringDialog.java index 46cfaf472cf1..653db59613b2 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/ui/RefactoringDialog.java +++ b/platform/lang-impl/src/com/intellij/refactoring/ui/RefactoringDialog.java @@ -16,7 +16,6 @@ package com.intellij.refactoring.ui; import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.project.DumbModePermission; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; @@ -184,9 +183,4 @@ public abstract class RefactoringDialog extends DialogWrapper { processor.run(); } - @Override - public void show() { - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_MODAL, () -> super.show()); - } - } diff --git a/platform/platform-api/src/com/intellij/notification/Notification.java b/platform/platform-api/src/com/intellij/notification/Notification.java index a8c6bf444724..14ddd5c4eead 100644 --- a/platform/platform-api/src/com/intellij/notification/Notification.java +++ b/platform/platform-api/src/com/intellij/notification/Notification.java @@ -263,6 +263,10 @@ public class Notification { } public void expire() { + if (myExpired) { + return; + } + NotificationsManager.getNotificationsManager().expire(this); hideBalloon(); myExpired = true; diff --git a/platform/platform-api/src/com/intellij/openapi/MnemonicHelper.java b/platform/platform-api/src/com/intellij/openapi/MnemonicHelper.java index aecd28684f3b..cf4ac1a50df2 100644 --- a/platform/platform-api/src/com/intellij/openapi/MnemonicHelper.java +++ b/platform/platform-api/src/com/intellij/openapi/MnemonicHelper.java @@ -151,10 +151,7 @@ public class MnemonicHelper extends ComponentTreeWatcher { */ public static CustomShortcutSet createShortcut(char ch) { Character mnemonic = Character.valueOf(ch); - String shortcut = SystemInfo.isMac && SystemInfo.isJavaVersionAtLeast("1.7") - ? "control alt pressed " - : "alt pressed "; - return CustomShortcutSet.fromString(shortcut + mnemonic); + return CustomShortcutSet.fromString("alt " + (SystemInfo.isMac ? "released" : "pressed") + " " + mnemonic); } /** diff --git a/platform/platform-api/src/com/intellij/openapi/actionSystem/ActionGroupUtil.java b/platform/platform-api/src/com/intellij/openapi/actionSystem/ActionGroupUtil.java index 6c903183843f..ef7d2d960834 100644 --- a/platform/platform-api/src/com/intellij/openapi/actionSystem/ActionGroupUtil.java +++ b/platform/platform-api/src/com/intellij/openapi/actionSystem/ActionGroupUtil.java @@ -30,19 +30,26 @@ public class ActionGroupUtil { return action2presentation.computeIfAbsent(action, k -> action.getTemplatePresentation().clone()); } + @Deprecated + // Use #isGroupEmpty with isModalContext instead public static boolean isGroupEmpty(@NotNull ActionGroup actionGroup, @NotNull AnActionEvent e) { - return isGroupEmpty(actionGroup, e, new HashMap<>()); + return isGroupEmpty(actionGroup, e, new HashMap<>(), false); + } + + public static boolean isGroupEmpty(@NotNull ActionGroup actionGroup, @NotNull AnActionEvent e, boolean isInModalContext) { + return isGroupEmpty(actionGroup, e, new HashMap<>(), isInModalContext); } private static boolean isGroupEmpty(@NotNull ActionGroup actionGroup, @NotNull AnActionEvent e, - @NotNull Map action2presentation) { + @NotNull Map action2presentation, + boolean inModalContext) { AnAction[] actions = actionGroup.getChildren(e); for (AnAction action : actions) { if (action instanceof Separator) continue; - if (isActionEnabledAndVisible(e, action2presentation, action)) { + if (isActionEnabledAndVisible(e, action2presentation, action, inModalContext)) { if (action instanceof ActionGroup) { - if (!isGroupEmpty((ActionGroup)action, e, action2presentation)) { + if (!isGroupEmpty((ActionGroup)action, e, action2presentation, inModalContext)) { return false; } // else continue for-loop @@ -56,8 +63,8 @@ public class ActionGroupUtil { } @Nullable - public static AnAction getSingleActiveAction(@NotNull ActionGroup actionGroup, @NotNull AnActionEvent e) { - List children = getEnabledChildren(actionGroup, e, new HashMap<>()); + public static AnAction getSingleActiveAction(@NotNull ActionGroup actionGroup, @NotNull AnActionEvent e, boolean isInModalContext) { + List children = getEnabledChildren(actionGroup, e, new HashMap<>(), isInModalContext); if (children.size() == 1) { return children.get(0); } @@ -66,17 +73,18 @@ public class ActionGroupUtil { private static List getEnabledChildren(@NotNull ActionGroup actionGroup, @NotNull AnActionEvent e, - @NotNull Map action2presentation) { + @NotNull Map action2presentation, + boolean isInModalContext) { List result = new ArrayList<>(); AnAction[] actions = actionGroup.getChildren(e); for (AnAction action : actions) { if (action instanceof ActionGroup) { - if (isActionEnabledAndVisible(e, action2presentation, action)) { - result.addAll(getEnabledChildren((ActionGroup)action, e, action2presentation)); + if (isActionEnabledAndVisible(e, action2presentation, action, isInModalContext)) { + result.addAll(getEnabledChildren((ActionGroup)action, e, action2presentation, isInModalContext)); } } else if (!(action instanceof Separator)) { - if (isActionEnabledAndVisible(e, action2presentation, action)) { + if (isActionEnabledAndVisible(e, action2presentation, action, isInModalContext)) { result.add(action); } } @@ -86,7 +94,8 @@ public class ActionGroupUtil { private static boolean isActionEnabledAndVisible(@NotNull final AnActionEvent e, @NotNull final Map action2presentation, - @NotNull final AnAction action) { + @NotNull final AnAction action, + boolean isInModalContext) { Presentation presentation = getPresentation(action, action2presentation); AnActionEvent event = new AnActionEvent(e.getInputEvent(), e.getDataContext(), @@ -95,7 +104,7 @@ public class ActionGroupUtil { ActionManager.getInstance(), e.getModifiers()); event.setInjectedContext(action.isInInjectedContext()); - ActionUtil.performDumbAwareUpdate(action, event, false); + ActionUtil.performDumbAwareUpdate(isInModalContext, action, event, false); return presentation.isEnabled() && presentation.isVisible(); } diff --git a/platform/platform-api/src/com/intellij/openapi/actionSystem/ex/ActionUtil.java b/platform/platform-api/src/com/intellij/openapi/actionSystem/ex/ActionUtil.java index 71efd710294d..bec45fae330a 100644 --- a/platform/platform-api/src/com/intellij/openapi/actionSystem/ex/ActionUtil.java +++ b/platform/platform-api/src/com/intellij/openapi/actionSystem/ex/ActionUtil.java @@ -23,6 +23,7 @@ import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.ObjectUtils; import com.intellij.util.PausesStat; @@ -99,7 +100,7 @@ public class ActionUtil { * {@link AnAction#update(AnActionEvent)} * @return true if update tried to access indices in dumb mode */ - public static boolean performDumbAwareUpdate(@NotNull AnAction action, @NotNull AnActionEvent e, boolean beforeActionPerformed) { + public static boolean performDumbAwareUpdate(boolean isInModalContext, @NotNull AnAction action, @NotNull AnActionEvent e, boolean beforeActionPerformed) { final Presentation presentation = e.getPresentation(); final Boolean wasEnabledBefore = (Boolean)presentation.getClientProperty(WAS_ENABLED_BEFORE_DUMB); final boolean dumbMode = isDumbMode(e.getProject()); @@ -110,7 +111,7 @@ public class ActionUtil { } final boolean enabledBeforeUpdate = presentation.isEnabled(); - final boolean notAllowed = dumbMode && !action.isDumbAware(); + final boolean notAllowed = dumbMode && !action.isDumbAware() || (Registry.is("actionSystem.honor.modal.context") && isInModalContext && !action.isEnabledInModalContext()); if (insidePerformDumbAwareUpdate++ == 0) { ActionPauses.STAT.started(); @@ -145,6 +146,13 @@ public class ActionUtil { return false; } + + @Deprecated + // Use #performDumbAwareUpdate with isModalContext instead + public static boolean performDumbAwareUpdate(@NotNull AnAction action, @NotNull AnActionEvent e, boolean beforeActionPerformed) { + return performDumbAwareUpdate(false, action, e, beforeActionPerformed); + } + public static class ActionPauses { public static final PausesStat STAT = new PausesStat("AnAction.update()"); } @@ -167,7 +175,7 @@ public class ActionUtil { } public static boolean lastUpdateAndCheckDumb(AnAction action, AnActionEvent e, boolean visibilityMatters) { - performDumbAwareUpdate(action, e, true); + performDumbAwareUpdate(false, action, e, true); final Project project = e.getProject(); if (project != null && DumbService.getInstance(project).isDumb() && !action.isDumbAware()) { diff --git a/platform/platform-api/src/com/intellij/openapi/ui/DialogBuilder.java b/platform/platform-api/src/com/intellij/openapi/ui/DialogBuilder.java index 3c971e64c405..940ce6dfdb91 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/DialogBuilder.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/DialogBuilder.java @@ -83,8 +83,6 @@ public class DialogBuilder implements Disposable { } private MyDialogWrapper showImpl(boolean isModal) { - LOG.assertTrue(!StringUtil.isEmptyOrSpaces(myTitle), - String.format("Dialog title shouldn't be empty or null: [%s]", myTitle)); myDialogWrapper.setTitle(myTitle); myDialogWrapper.init(); myDialogWrapper.setModal(isModal); diff --git a/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java b/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java index b3d34f097164..301b0f2174dd 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java @@ -750,7 +750,7 @@ public abstract class DialogWrapper { final char mnemonic = (char)eachInfo.getMnemonic(); JRootPane rootPane = getPeer().getRootPane(); if (rootPane != null) { - new DumbAwareAction() { + new DumbAwareAction("Show JBOptionButton popup") { @Override public void actionPerformed(AnActionEvent e) { final JBOptionButton buttonToActivate = eachInfo.getButton(); diff --git a/platform/platform-api/src/com/intellij/openapi/ui/ThreeComponentsSplitter.java b/platform/platform-api/src/com/intellij/openapi/ui/ThreeComponentsSplitter.java index 9d3ec5e0efef..1a0223f43bc9 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/ThreeComponentsSplitter.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/ThreeComponentsSplitter.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * 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. @@ -172,7 +172,7 @@ public class ThreeComponentsSplitter extends JPanel implements Disposable { } public void setDividerMouseZoneSize(int size) { - myDividerZone = size; + myDividerZone = JBUI.scale(size); } public boolean isHonorMinimumSize() { @@ -612,7 +612,7 @@ public class ThreeComponentsSplitter extends JPanel implements Disposable { private boolean isInside(Point p) { if (!isVisible()) return false; - int dndOff = myIsOnePixel ? Registry.intValue("ide.splitter.mouseZone") / 2 : 0; + int dndOff = myIsOnePixel ? JBUI.scale(Registry.intValue("ide.splitter.mouseZone")) / 2 : 0; if (myVerticalSplit) { if (p.x >= 0 && p.x < getWidth()) { if (getHeight() > 0) { diff --git a/platform/platform-api/src/com/intellij/ui/SeparatorWithText.java b/platform/platform-api/src/com/intellij/ui/SeparatorWithText.java index 2a4d5eb75a3b..0dbfffe8fb1b 100644 --- a/platform/platform-api/src/com/intellij/ui/SeparatorWithText.java +++ b/platform/platform-api/src/com/intellij/ui/SeparatorWithText.java @@ -16,19 +16,22 @@ package com.intellij.ui; import com.intellij.ide.ui.UISettings; +import com.intellij.util.ui.JBInsets; import com.intellij.util.ui.UIUtil; import javax.swing.*; import java.awt.*; +import static com.intellij.ui.paint.RectanglePainter.FILL; import static javax.swing.SwingConstants.CENTER; import static javax.swing.SwingConstants.LEFT; +import static javax.swing.SwingUtilities.layoutCompoundLabel; public class SeparatorWithText extends JComponent { - private String myCaption = ""; + private String myCaption; private int myPrefWidth; - private boolean myCaptionCentered = true; + private int myAlignment; public SeparatorWithText() { setBorder(BorderFactory.createEmptyBorder(getVgap(), 0, getVgap(), 0)); @@ -45,37 +48,29 @@ public class SeparatorWithText extends JComponent { } public void setCaptionCentered(boolean captionCentered) { - myCaptionCentered = captionCentered; + myAlignment = captionCentered ? CENTER : LEFT; } public Dimension getPreferredSize() { - final Dimension size = getPreferredFontSize(); - size.width = myPrefWidth == -1 ? size.width : myPrefWidth; - return size; + return isPreferredSizeSet() ? super.getPreferredSize() : getPreferredFontSize(); } private Dimension getPreferredFontSize() { - if (hasCaption()) { + Dimension size = new Dimension(myPrefWidth < 0 ? 0 : myPrefWidth, 1); + String caption = getCaption(); + if (caption != null) { FontMetrics fm = getFontMetrics(getFont()); - int preferredHeight = fm.getHeight(); - int preferredWidth = getPreferredWidth(fm); - - return new Dimension(preferredWidth, preferredHeight + getVgap() * 2); + size.height = fm.getHeight(); + if (myPrefWidth < 0) { + size.width = 2 * getHgap() + fm.stringWidth(caption); + } } - - return new Dimension(0, getVgap() * 2 + 1); - } - - private int getPreferredWidth(FontMetrics fm) { - return fm.stringWidth(myCaption) + 2 * getHgap(); - } - - private boolean hasCaption() { - return myCaption != null && !myCaption.trim().isEmpty(); + JBInsets.addTo(size, getInsets()); + return size; } public Dimension getMinimumSize() { - return getPreferredSize(); + return isMinimumSizeSet() ? super.getMinimumSize() : getPreferredFontSize(); } public void setMinimumWidth(int width) { @@ -85,34 +80,44 @@ public class SeparatorWithText extends JComponent { protected void paintComponent(Graphics g) { g.setColor(GroupedElementsRenderer.POPUP_SEPARATOR_FOREGROUND); - if (hasCaption()) { - Rectangle viewR = new Rectangle(0, getVgap(), getWidth() - 1, getHeight() - getVgap() - 1); + Rectangle bounds = new Rectangle(getWidth(), getHeight()); + JBInsets.removeFrom(bounds, getInsets()); + + String caption = getCaption(); + if (caption != null) { + int hGap = getHgap(); + bounds.x += hGap; + bounds.width -= hGap + hGap; + Rectangle iconR = new Rectangle(); Rectangle textR = new Rectangle(); - String s = SwingUtilities - .layoutCompoundLabel(g.getFontMetrics(), myCaption, null, CENTER, - myCaptionCentered ? CENTER : LEFT, - CENTER, - myCaptionCentered ? CENTER : LEFT, - viewR, iconR, textR, 0); - final int lineY = textR.y + textR.height / 2; - if (s.equals(myCaption) && viewR.width - textR.width > 2 * getHgap()) { - if (myCaptionCentered) { - g.drawLine(0, lineY, textR.x - getHgap(), lineY); - } - g.drawLine(textR.x + textR.width + getHgap(), lineY, getWidth() - 1, lineY); + FontMetrics fm = g.getFontMetrics(); + String label = layoutCompoundLabel(fm, caption, null, CENTER, myAlignment, CENTER, myAlignment, bounds, iconR, textR, 0); + textR.y += fm.getAscent(); + if (caption.equals(label)) { + int y = textR.y + (int)fm.getLineMetrics(label, g).getStrikethroughOffset(); + paintLinePart(g, bounds.x, textR.x, -hGap, y); + paintLinePart(g, textR.x + textR.width, bounds.x + bounds.width, hGap, y); } UISettings.setupAntialiasing(g); g.setColor(GroupedElementsRenderer.POPUP_SEPARATOR_TEXT_FOREGROUND); - g.drawString(s, textR.x, textR.y + g.getFontMetrics().getAscent()); + g.drawString(label, textR.x, textR.y); } else { - g.drawLine(0, getVgap(), getWidth() - 1, getVgap()); + paintLine(g, bounds.x, bounds.y, bounds.width); } } + protected void paintLinePart(Graphics g, int xMin, int xMax, int hGap, int y) { + if (xMax > xMin) paintLine(g, xMin + hGap, y, xMax - xMin); + } + + protected void paintLine(Graphics g, int x, int y, int width) { + FILL.paint((Graphics2D)g, x, y, width, 1, null); + } + protected String getCaption() { - return myCaption; + return myCaption == null || myCaption.trim().isEmpty() ? null : myCaption; } public void setCaption(String captionAboveOf) { diff --git a/platform/platform-api/src/com/intellij/ui/components/labels/ActionLink.java b/platform/platform-api/src/com/intellij/ui/components/labels/ActionLink.java index 46c6e21bddb3..2e1ea431794a 100644 --- a/platform/platform-api/src/com/intellij/ui/components/labels/ActionLink.java +++ b/platform/platform-api/src/com/intellij/ui/components/labels/ActionLink.java @@ -62,7 +62,7 @@ public class ActionLink extends LinkLabel implements DataProvider { presentation, ActionManager.getInstance(), 0); - ActionUtil.performDumbAwareUpdate(myAction, event, true); + ActionUtil.performDumbAwareUpdate(false, myAction, event, true); if (event.getPresentation().isEnabled() && event.getPresentation().isVisible()) { myAction.actionPerformed(event); if (onDone != null) { diff --git a/platform/platform-api/src/com/intellij/ui/paint/EffectPainter.java b/platform/platform-api/src/com/intellij/ui/paint/EffectPainter.java index 80bd11388b7c..11dfdd5a777a 100644 --- a/platform/platform-api/src/com/intellij/ui/paint/EffectPainter.java +++ b/platform/platform-api/src/com/intellij/ui/paint/EffectPainter.java @@ -233,7 +233,7 @@ public enum EffectPainter implements RegionPainter { @Override int getPeriod(int height) { - return (Registry.is("ide.text.effect.new.metrics") ? height : getMaxHeight(height)) - 1; + return Math.max((Registry.is("ide.text.effect.new.metrics") ? height : getMaxHeight(height)) - 1, 1); } @Override diff --git a/platform/platform-api/src/com/intellij/util/Url.java b/platform/platform-api/src/com/intellij/util/Url.java index bc377945d581..81b824fbb568 100644 --- a/platform/platform-api/src/com/intellij/util/Url.java +++ b/platform/platform-api/src/com/intellij/util/Url.java @@ -52,4 +52,6 @@ public interface Url { Url trimParameters(); int hashCodeCaseInsensitive(); + + Url resolve(@NotNull String subPath); } diff --git a/platform/platform-api/src/com/intellij/util/net/HttpConfigurable.java b/platform/platform-api/src/com/intellij/util/net/HttpConfigurable.java index 5e6774401e6a..727d8fd5c169 100644 --- a/platform/platform-api/src/com/intellij/util/net/HttpConfigurable.java +++ b/platform/platform-api/src/com/intellij/util/net/HttpConfigurable.java @@ -633,7 +633,12 @@ public class HttpConfigurable implements PersistentStateComponent OLD_HYPERLINK_TEXT_ATTRIBUTES = Key.create("OLD_HYPERLINK_TEXT_ATTRIBUTES"); private static final Key HYPERLINK = Key.create("HYPERLINK"); - private static final int HYPERLINK_LAYER = HighlighterLayer.SELECTION - 123; - private static final int HIGHLIGHT_LAYER = HighlighterLayer.SELECTION - 111; private final Editor myEditor; @NotNull private final Project myProject; @@ -217,7 +216,8 @@ public class EditorHyperlinkSupport { int highlightEndOffset, @Nullable TextAttributes highlightAttributes, @NotNull HyperlinkInfo hyperlinkInfo) { - return createHyperlink(highlightStartOffset, highlightEndOffset, highlightAttributes, hyperlinkInfo, null); + return createHyperlink(highlightStartOffset, highlightEndOffset, highlightAttributes, hyperlinkInfo, null, + HYPERLINK_LAYER); } @NotNull @@ -225,11 +225,12 @@ public class EditorHyperlinkSupport { final int highlightEndOffset, @Nullable final TextAttributes highlightAttributes, @NotNull final HyperlinkInfo hyperlinkInfo, - @Nullable TextAttributes followedHyperlinkAttributes) { + @Nullable TextAttributes followedHyperlinkAttributes, + int layer) { TextAttributes textAttributes = highlightAttributes != null ? highlightAttributes : getHyperlinkAttributes(); final RangeHighlighter highlighter = myEditor.getMarkupModel().addRangeHighlighter(highlightStartOffset, highlightEndOffset, - HYPERLINK_LAYER, + layer, textAttributes, HighlighterTargetArea.EXACT_RANGE); associateHyperlink(highlighter, hyperlinkInfo, followedHyperlinkAttributes); @@ -283,16 +284,17 @@ public class EditorHyperlinkSupport { TextAttributes attributes = resultItem.getHighlightAttributes(); if (resultItem.getHyperlinkInfo() != null) { - createHyperlink(start, end, attributes, resultItem.getHyperlinkInfo(), resultItem.getFollowedHyperlinkAttributes()); + createHyperlink(start, end, attributes, resultItem.getHyperlinkInfo(), resultItem.getFollowedHyperlinkAttributes(), + resultItem.getHighlighterLayer()); } else if (attributes != null) { - addHighlighter(start, end, attributes); + addHighlighter(start, end, attributes, resultItem.getHighlighterLayer()); } } } - public void addHighlighter(int highlightStartOffset, int highlightEndOffset, TextAttributes highlightAttributes) { - myEditor.getMarkupModel().addRangeHighlighter(highlightStartOffset, highlightEndOffset, HIGHLIGHT_LAYER, highlightAttributes, + public void addHighlighter(int highlightStartOffset, int highlightEndOffset, TextAttributes highlightAttributes, int highlighterLayer) { + myEditor.getMarkupModel().addRangeHighlighter(highlightStartOffset, highlightEndOffset, highlighterLayer, highlightAttributes, HighlighterTargetArea.EXACT_RANGE); } @@ -365,7 +367,7 @@ public class EditorHyperlinkSupport { } } //refresh highlighter text attributes - markupModel.addRangeHighlighter(0, 0, HYPERLINK_LAYER, getHyperlinkAttributes(), HighlighterTargetArea.EXACT_RANGE).dispose(); + markupModel.addRangeHighlighter(0, 0, link.getLayer(), getHyperlinkAttributes(), HighlighterTargetArea.EXACT_RANGE).dispose(); } diff --git a/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java b/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java index ac5aaf7098b5..84ac9d166627 100644 --- a/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java +++ b/platform/platform-impl/src/com/intellij/ide/IdeEventQueue.java @@ -46,7 +46,6 @@ import com.intellij.util.ReflectionUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashMap; import com.intellij.util.io.storage.HeavyProcessLatch; -import com.intellij.util.ui.MouseEventAdapter; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -317,27 +316,6 @@ public class IdeEventQueue extends EventQueue { return myCurrentEvent; } - private static class InertialMouseRouter { - private static final int MOUSE_WHEEL_RESTART_THRESHOLD = 50; - private static Component wheelDestinationComponent; - private static long lastMouseWheel; - - @NotNull - private static AWTEvent changeSourceIfNeeded(@NotNull AWTEvent awtEvent) { - if (SystemInfo.isMac && Registry.is("ide.inertial.mouse.fix") && awtEvent instanceof MouseWheelEvent) { - MouseWheelEvent mwe = (MouseWheelEvent) awtEvent; - if (mwe.getWhen() - lastMouseWheel > MOUSE_WHEEL_RESTART_THRESHOLD) { - wheelDestinationComponent = SwingUtilities.getDeepestComponentAt(mwe.getComponent(), mwe.getX(), mwe.getY()); - } - lastMouseWheel = System.currentTimeMillis(); - - int modifiers = mwe.getModifiers() | mwe.getModifiersEx(); - return MouseEventAdapter.convert(mwe, wheelDestinationComponent, mwe.getID(), lastMouseWheel, modifiers, mwe.getX(), mwe.getY()); - } - return awtEvent; - } - } - private static boolean ourAppIsLoaded; private static boolean appIsLoaded() { @@ -366,8 +344,6 @@ public class IdeEventQueue extends EventQueue { return; } - e = InertialMouseRouter.changeSourceIfNeeded(e); - e = fixNonEnglishKeyboardLayouts(e); e = mapEvent(e); diff --git a/platform/platform-impl/src/com/intellij/ide/actions/NonTrivialActionGroup.java b/platform/platform-impl/src/com/intellij/ide/actions/NonTrivialActionGroup.java index 1739c25c63b6..e73fd99f041e 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/NonTrivialActionGroup.java +++ b/platform/platform-impl/src/com/intellij/ide/actions/NonTrivialActionGroup.java @@ -18,6 +18,7 @@ package com.intellij.ide.actions; import com.intellij.openapi.actionSystem.ActionGroupUtil; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.intellij.openapi.application.impl.LaterInvocator; import com.intellij.openapi.project.DumbAware; /** @@ -34,6 +35,6 @@ public class NonTrivialActionGroup extends DefaultActionGroup implements DumbAwa } public void update(AnActionEvent e) { - e.getPresentation().setVisible(!ActionGroupUtil.isGroupEmpty(this, e)); + e.getPresentation().setVisible(!ActionGroupUtil.isGroupEmpty(this, e, LaterInvocator.isInModalContext())); } } diff --git a/platform/platform-impl/src/com/intellij/ide/actions/ToggleToolbarAction.java b/platform/platform-impl/src/com/intellij/ide/actions/ToggleToolbarAction.java index 14e848273088..61160891c1b8 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/ToggleToolbarAction.java +++ b/platform/platform-impl/src/com/intellij/ide/actions/ToggleToolbarAction.java @@ -17,6 +17,7 @@ package com.intellij.ide.actions; import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.application.impl.LaterInvocator; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.wm.ToolWindow; @@ -127,7 +128,7 @@ public class ToggleToolbarAction extends ToggleAction implements DumbAware { @Override public void update(AnActionEvent e) { - e.getPresentation().setVisible(!ActionGroupUtil.isGroupEmpty(this, e)); + e.getPresentation().setVisible(!ActionGroupUtil.isGroupEmpty(this, e, LaterInvocator.isInModalContext())); } @NotNull diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaRadioButtonUI.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaRadioButtonUI.java index 15a2b6a5b583..2523b746581c 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaRadioButtonUI.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaRadioButtonUI.java @@ -106,7 +106,7 @@ public class DarculaRadioButtonUI extends MetalRadioButtonUI { if (!UIUtil.isUnderDarcula() && selected) { final GraphicsConfig fillOvalConf = new GraphicsConfig(g); g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); - g.fillOval(0, 1, w, h); + g.fillOval(0, JBUI.scale(1), w, h); fillOvalConf.restore(); } else { if (focus) { diff --git a/platform/platform-impl/src/com/intellij/ide/util/ExportToFileUtil.java b/platform/platform-impl/src/com/intellij/ide/util/ExportToFileUtil.java index 41d29cc5f900..efef222f709e 100644 --- a/platform/platform-impl/src/com/intellij/ide/util/ExportToFileUtil.java +++ b/platform/platform-impl/src/com/intellij/ide/util/ExportToFileUtil.java @@ -183,7 +183,7 @@ public class ExportToFileUtil { } protected JComponent createNorthPanel() { - JPanel filePanel = createFilePanel(myTfFile.getTextField(), myTfFile.getButton()); + JPanel filePanel = createFilePanel(); JComponent settingsPanel = myExporter.getSettingsEditor(); if (settingsPanel == null) { return filePanel; @@ -194,7 +194,7 @@ public class ExportToFileUtil { return northPanel; } - protected JPanel createFilePanel(JTextField textField, JButton button) { + protected JPanel createFilePanel() { JPanel panel = new JPanel(); panel.setLayout(new GridBagLayout()); GridBagConstraints gbConstraints = new GridBagConstraints(); @@ -206,9 +206,10 @@ public class ExportToFileUtil { panel.add(myTfFile, gbConstraints); String defaultFilePath = myExporter.getDefaultFilePath(); - if (! new File(defaultFilePath).isAbsolute()) { + if (!new File(defaultFilePath).isAbsolute()) { defaultFilePath = PathMacroManager.getInstance(myProject).collapsePath(defaultFilePath).replace('/', File.separatorChar); - } else { + } + else { defaultFilePath = defaultFilePath.replace('/', File.separatorChar); } myTfFile.setText(defaultFilePath); diff --git a/platform/platform-impl/src/com/intellij/notification/impl/NotificationsManagerImpl.java b/platform/platform-impl/src/com/intellij/notification/impl/NotificationsManagerImpl.java index bdce240d9644..420fb013e6af 100644 --- a/platform/platform-impl/src/com/intellij/notification/impl/NotificationsManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/notification/impl/NotificationsManagerImpl.java @@ -265,12 +265,13 @@ public class NotificationsManagerImpl extends NotificationsManager { } final Balloon balloon = createBalloon((IdeFrame)window, notification, false, false, layoutDataRef, project != null ? project : ApplicationManager.getApplication()); - ((BalloonImpl)balloon).traceDispose(true); if (notification.isExpired()) { return null; } + ((BalloonImpl)balloon).traceDispose(true); + layout.add(balloon, layoutDataRef == null ? null : layoutDataRef.get()); if (layoutDataRef != null && layoutDataRef.get() instanceof BalloonLayoutData) { ((BalloonLayoutData)layoutDataRef.get()).project = project; 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 4c21edc54b95..cc7c1c0fbd46 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 @@ -22,8 +22,8 @@ import com.intellij.openapi.actionSystem.ex.ActionButtonLook; import com.intellij.openapi.actionSystem.ex.ActionManagerEx; import com.intellij.openapi.actionSystem.ex.ActionUtil; import com.intellij.openapi.actionSystem.ex.CustomComponentAction; +import com.intellij.openapi.application.impl.LaterInvocator; import com.intellij.openapi.keymap.KeymapUtil; -import com.intellij.openapi.util.Getter; import com.intellij.openapi.util.IconLoader; import com.intellij.util.ui.EmptyIcon; import com.intellij.util.ui.JBDimension; @@ -203,7 +203,7 @@ public class ActionButton extends JComponent implements ActionButtonComponent, A myPresentation.addPropertyChangeListener(myPresentationListener = this::presentationPropertyChanded); } AnActionEvent e = new AnActionEvent(null, getDataContext(), myPlace, myPresentation, ActionManager.getInstance(), 0); - ActionUtil.performDumbAwareUpdate(myAction, e, false); + ActionUtil.performDumbAwareUpdate(LaterInvocator.isInModalContext(), myAction, e, false); updateToolTipText(); updateIcon(); } @@ -280,7 +280,7 @@ public class ActionButton extends JComponent implements ActionButtonComponent, A y++; } - AllIcons.General.Dropdown.paintIcon(this, g, x, y); + AllIcons.General.Dropdown.paintIcon(this, g, JBUI.scale(x), JBUI.scale(y)); } } 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 78986c74ffe8..765722fe30a8 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 @@ -31,6 +31,7 @@ import com.intellij.openapi.actionSystem.ex.ActionUtil; import com.intellij.openapi.actionSystem.ex.AnActionListener; import com.intellij.openapi.application.*; import com.intellij.openapi.application.ex.ApplicationManagerEx; +import com.intellij.openapi.application.impl.LaterInvocator; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.PluginId; import com.intellij.openapi.keymap.Keymap; @@ -1293,7 +1294,7 @@ public final class ActionManagerImpl extends ActionManagerEx implements Disposab inputEvent.getModifiersEx() ); - ActionUtil.performDumbAwareUpdate(action, event, false); + ActionUtil.performDumbAwareUpdate(LaterInvocator.isInModalContext(), action, event, false); if (!event.getPresentation().isEnabled()) { result.setRejected(); return; diff --git a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionMenu.java b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionMenu.java index fc506a13dbe4..023ea18faca1 100644 --- a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionMenu.java +++ b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionMenu.java @@ -21,6 +21,7 @@ import com.intellij.ide.ui.UISettings; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.actionSystem.impl.actionholder.ActionRef; +import com.intellij.openapi.application.impl.LaterInvocator; import com.intellij.openapi.ui.JBPopupMenu; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.IconLoader; @@ -322,7 +323,7 @@ public final class ActionMenu extends JBMenu { mayContextBeInvalid = true; } - Utils.fillMenu(myGroup.getAction(), this, myMnemonicEnabled, myPresentationFactory, context, myPlace, true, mayContextBeInvalid); + Utils.fillMenu(myGroup.getAction(), this, myMnemonicEnabled, myPresentationFactory, context, myPlace, true, mayContextBeInvalid, LaterInvocator.isInModalContext()); } private class MenuItemSynchronizer implements PropertyChangeListener { diff --git a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionPopupMenuImpl.java b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionPopupMenuImpl.java index 1b98c784e836..2cc85907c203 100644 --- a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionPopupMenuImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionPopupMenuImpl.java @@ -23,6 +23,7 @@ import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationActivationListener; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.impl.LaterInvocator; import com.intellij.openapi.ui.JBPopupMenu; import com.intellij.openapi.util.Getter; import com.intellij.openapi.wm.IdeFrame; @@ -92,7 +93,7 @@ public final class ActionPopupMenuImpl extends ApplicationActivationListener.Ada int y2 = Math.max(0, Math.min(y, component.getHeight() - 1)); // fit y into [0, height-1] myContext = myDataContextProvider != null ? myDataContextProvider.get() : DataManager.getInstance().getDataContext(component, x2, y2); - Utils.fillMenu(myGroup, this, true, myPresentationFactory, myContext, myPlace, false, false); + Utils.fillMenu(myGroup, this, true, myPresentationFactory, myContext, myPlace, false, false, LaterInvocator.isInModalContext()); if (getComponentCount() == 0) { return; } @@ -136,7 +137,7 @@ public final class ActionPopupMenuImpl extends ApplicationActivationListener.Ada public void popupMenuWillBecomeVisible(PopupMenuEvent e) { MyMenu.this.removeAll(); Utils.fillMenu(myGroup, MyMenu.this, !UISettings.getInstance().DISABLE_MNEMONICS, myPresentationFactory, myContext, myPlace, false, - false); + false, LaterInvocator.isInModalContext()); myManager.addActionPopup(ActionPopupMenuImpl.this); } } 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 3b1161a34c78..9cd7bf80e1d2 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 @@ -25,6 +25,7 @@ import com.intellij.openapi.actionSystem.ex.ActionManagerEx; import com.intellij.openapi.actionSystem.ex.AnActionListener; import com.intellij.openapi.actionSystem.ex.CustomComponentAction; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.impl.LaterInvocator; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.keymap.ex.KeymapManagerEx; import com.intellij.openapi.ui.popup.*; @@ -945,7 +946,7 @@ public class ActionToolbarImpl extends JPanel implements ActionToolbar { List newVisibleActions = ContainerUtil.newArrayListWithCapacity(myVisibleActions.size()); DataContext dataContext = getDataContext(); - Utils.expandActionGroup(myActionGroup, newVisibleActions, myPresentationFactory, dataContext, + Utils.expandActionGroup(LaterInvocator.isInModalContext(), myActionGroup, newVisibleActions, myPresentationFactory, dataContext, myPlace, myActionManager, transparentOnly); if (forced || !newVisibleActions.equals(myVisibleActions)) { diff --git a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/Utils.java b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/Utils.java index c3e604dcb580..9a17463bb8b5 100644 --- a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/Utils.java +++ b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/Utils.java @@ -19,6 +19,7 @@ import com.intellij.ide.DataManager; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.actionSystem.ex.ActionUtil; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.impl.LaterInvocator; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.DumbService; @@ -73,19 +74,33 @@ public class Utils{ } } - /** - * @param list this list contains expanded actions. - * @param actionManager manager - */ + @Deprecated + // Use #expandActionGroup with isModalContext instead public static void expandActionGroup(@NotNull ActionGroup group, List list, PresentationFactory presentationFactory, @NotNull DataContext context, String place, ActionManager actionManager){ - expandActionGroup(group, list, presentationFactory, context, place, actionManager, false, group instanceof CompactActionGroup); + expandActionGroup(false, group, list, presentationFactory, context, place, actionManager, false, group instanceof CompactActionGroup); } + /** + * @param list this list contains expanded actions. + * @param actionManager manager + */ + public static void expandActionGroup(boolean isInModalContext, + @NotNull ActionGroup group, + List list, + PresentationFactory presentationFactory, + @NotNull DataContext context, + String place, + ActionManager actionManager){ + expandActionGroup(isInModalContext, group, list, presentationFactory, context, place, actionManager, false, group instanceof CompactActionGroup); + } + + @Deprecated + // Use #expandActionGroup with isModalContext instead public static void expandActionGroup(@NotNull ActionGroup group, List list, PresentationFactory presentationFactory, @@ -93,14 +108,39 @@ public class Utils{ @NotNull String place, ActionManager actionManager, boolean transparentOnly) { - expandActionGroup(group, list, presentationFactory, context, place, actionManager, transparentOnly, false); + expandActionGroup(false, group, list, presentationFactory, context, place, actionManager, transparentOnly, false); + } + + public static void expandActionGroup(boolean isInModalContext, + @NotNull ActionGroup group, + List list, + PresentationFactory presentationFactory, + DataContext context, + @NotNull String place, + ActionManager actionManager, + boolean transparentOnly) { + expandActionGroup(isInModalContext, group, list, presentationFactory, context, place, actionManager, transparentOnly, false); + } + + @Deprecated + // Use #expandActionGroup with isModalContext instead + public static void expandActionGroup(@NotNull ActionGroup group, + List list, + PresentationFactory presentationFactory, + DataContext context, + @NotNull String place, + ActionManager actionManager, + boolean transparentOnly, + boolean hideDisabled) { + expandActionGroup(false, group, list, presentationFactory, context, place, actionManager, transparentOnly, hideDisabled); } /** * @param list this list contains expanded actions. * @param actionManager manager */ - public static void expandActionGroup(@NotNull ActionGroup group, + public static void expandActionGroup(boolean isInModalContext, + @NotNull ActionGroup group, List list, PresentationFactory presentationFactory, DataContext context, @@ -117,7 +157,7 @@ public class Utils{ actionManager, 0 ); - if (!doUpdate(group, e, presentation)) return; + if (!doUpdate(isInModalContext, group, e, presentation)) return; if (!presentation.isVisible()) { // don't process invisible groups return; @@ -136,7 +176,7 @@ public class Utils{ e1.setInjectedContext(child.isInInjectedContext()); if (transparentOnly && child.isTransparentUpdate() || !transparentOnly) { - if (!doUpdate(child, e1, presentation)) continue; + if (!doUpdate(isInModalContext, child, e1, presentation)) continue; } if (!presentation.isVisible() || (!presentation.isEnabled() && hideDisabled)) { // don't create invisible items in the menu @@ -161,7 +201,7 @@ public class Utils{ list.add(child); } else { - expandActionGroup((ActionGroup)child, list, presentationFactory, context, place, actionManager, false, hideDisabled); + expandActionGroup(isInModalContext, (ActionGroup)child, list, presentationFactory, context, place, actionManager, false, hideDisabled); } } else if (child instanceof Separator) { @@ -178,14 +218,20 @@ public class Utils{ } } + @Deprecated + // Use #doUpdate with isModalContext instead + private static boolean doUpdate(final AnAction action, final AnActionEvent e, final Presentation presentation) { + return doUpdate(false, action, e, presentation); + } + // returns false if exception was thrown and handled - private static boolean doUpdate(final AnAction action, final AnActionEvent e, final Presentation presentation) throws ProcessCanceledException { + private static boolean doUpdate(boolean isInModalContext, final AnAction action, final AnActionEvent e, final Presentation presentation) throws ProcessCanceledException { if (ApplicationManager.getApplication().isDisposed()) return false; long startTime = System.currentTimeMillis(); final boolean result; try { - result = !ActionUtil.performDumbAwareUpdate(action, e, false); + result = !ActionUtil.performDumbAwareUpdate(isInModalContext, action, e, false); } catch (ProcessCanceledException ex) { throw ex; @@ -262,7 +308,20 @@ public class Utils{ public static void updateGroupChild(DataContext context, String place, AnAction anAction, final Presentation presentation) { AnActionEvent event1 = new AnActionEvent(null, context, place, presentation, ActionManager.getInstance(), 0); event1.setInjectedContext(anAction.isInInjectedContext()); - doUpdate(anAction, event1, presentation); + doUpdate(false, anAction, event1, presentation); + } + + @Deprecated + // Use #expandActionGroup with isModalContext instead + public static void fillMenu(@NotNull final ActionGroup group, + final JComponent component, + final boolean enableMnemonics, + final PresentationFactory presentationFactory, + @NotNull DataContext context, + final String place, + final boolean isWindowMenu, + final boolean mayDataContextBeInvalid) { + fillMenu(group, component, enableMnemonics, presentationFactory, context, place, isWindowMenu, mayDataContextBeInvalid, false); } public static void fillMenu(@NotNull final ActionGroup group, @@ -272,12 +331,13 @@ public class Utils{ @NotNull DataContext context, final String place, final boolean isWindowMenu, - final boolean mayDataContextBeInvalid){ + final boolean mayDataContextBeInvalid, + boolean isInModalContext) { final ActionCallback menuBuilt = new ActionCallback(); final boolean checked = group instanceof CheckedActionGroup; final ArrayList list = new ArrayList<>(); - expandActionGroup(group, list, presentationFactory, context, place, ActionManager.getInstance()); + expandActionGroup(isInModalContext, group, list, presentationFactory, context, place, ActionManager.getInstance()); final boolean fixMacScreenMenu = SystemInfo.isMacSystemMenu && isWindowMenu && Registry.is("actionSystem.mac.screenMenuNotUpdatedFix"); final ArrayList children = new ArrayList<>(); @@ -373,7 +433,7 @@ public class Utils{ if (!component.isShowing()) return; DataContext context1 = DataManager.getInstance().getDataContext(); - expandActionGroup(group, new ArrayList<>(), presentationFactory, context1, place, ActionManager.getInstance()); + expandActionGroup(LaterInvocator.isInModalContext(), group, new ArrayList<>(), presentationFactory, context1, place, ActionManager.getInstance()); for (Component each : children) { if (each instanceof ActionMenuItem) { diff --git a/platform/platform-impl/src/com/intellij/openapi/diff/impl/external/ExtMergeFiles.java b/platform/platform-impl/src/com/intellij/openapi/diff/impl/external/ExtMergeFiles.java index f9bd11bad5a8..f05f6ad7052a 100644 --- a/platform/platform-impl/src/com/intellij/openapi/diff/impl/external/ExtMergeFiles.java +++ b/platform/platform-impl/src/com/intellij/openapi/diff/impl/external/ExtMergeFiles.java @@ -25,8 +25,6 @@ import com.intellij.openapi.diff.impl.mergeTool.MergeRequestImpl; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.text.StringUtil; @@ -101,8 +99,7 @@ public class ExtMergeFiles extends BaseExternalTool { "Merge In External Tool", "Mark as Resolved", "Revert", null)) { result = DialogWrapper.OK_EXIT_CODE; } - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, - () -> ((MergeRequestImpl)request).getResultContent().getFile().refresh(false, false)); + ((MergeRequestImpl)request).getResultContent().getFile().refresh(false, false); // We can actually check exit code of external tool, but some of them could work with tabs -> do not close at all } catch (Exception e) { diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java index a34fbdaf1532..486545a21937 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java @@ -30,7 +30,6 @@ import com.intellij.ide.ui.customization.CustomActionsSchema; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.actionSystem.ex.ActionManagerEx; -import com.intellij.openapi.actionSystem.impl.MouseGestureManager; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.application.TransactionGuard; @@ -7235,7 +7234,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi @Override protected void processMouseWheelEvent(@NotNull MouseWheelEvent e) { - if (mySettings.isWheelFontChangeEnabled() && !MouseGestureManager.getInstance().hasTrackpad()) { + if (mySettings.isWheelFontChangeEnabled()) { if (EditorUtil.isChangeFontSize(e)) { int size = myScheme.getEditorFontSize() - e.getWheelRotation(); if (size >= MIN_FONT_SIZE) { diff --git a/platform/platform-impl/src/com/intellij/openapi/fileChooser/ex/FileChooserDialogImpl.java b/platform/platform-impl/src/com/intellij/openapi/fileChooser/ex/FileChooserDialogImpl.java index 24965ef91f0c..ef8882112db0 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileChooser/ex/FileChooserDialogImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileChooser/ex/FileChooserDialogImpl.java @@ -31,8 +31,6 @@ import com.intellij.openapi.fileChooser.*; import com.intellij.openapi.fileChooser.impl.FileChooserFactoryImpl; import com.intellij.openapi.fileChooser.impl.FileChooserUtil; import com.intellij.openapi.ide.CopyPasteManager; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; @@ -351,8 +349,7 @@ public class FileChooserDialogImpl extends DialogWrapper implements FileChooserD .subscribe(ApplicationActivationListener.TOPIC, new ApplicationActivationListener.Adapter() { @Override public void applicationActivated(IdeFrame ideFrame) { - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_MODAL, - () -> ((SaveAndSyncHandlerImpl)SaveAndSyncHandler.getInstance()).maybeRefresh(ModalityState.current())); + ((SaveAndSyncHandlerImpl)SaveAndSyncHandler.getInstance()).maybeRefresh(ModalityState.current()); } }); diff --git a/platform/platform-impl/src/com/intellij/openapi/fileChooser/ex/FileSystemTreeImpl.java b/platform/platform-impl/src/com/intellij/openapi/fileChooser/ex/FileSystemTreeImpl.java index cc89503d7dae..fe1b961007a4 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileChooser/ex/FileSystemTreeImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileChooser/ex/FileSystemTreeImpl.java @@ -33,8 +33,6 @@ import com.intellij.openapi.fileChooser.impl.FileComparator; import com.intellij.openapi.fileChooser.impl.FileTreeBuilder; import com.intellij.openapi.fileChooser.impl.FileTreeStructure; import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.text.StringUtil; @@ -281,25 +279,20 @@ public class FileSystemTreeImpl implements FileSystemTree { CommandProcessor.getInstance().executeCommand( myProject, new Runnable() { public void run() { - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_MODAL, new Runnable() { - @Override + ApplicationManager.getApplication().runWriteAction(new Runnable() { public void run() { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - try { - VirtualFile parent = parentDirectory; - for (String name : StringUtil.tokenize(newFolderName, "\\/")) { - VirtualFile folder = parent.createChildDirectory(this, name); - updateTree(); - select(folder, null); - parent = folder; - } - } - catch (IOException e) { - failReason[0] = e; - } + try { + VirtualFile parent = parentDirectory; + for (String name : StringUtil.tokenize(newFolderName, "\\/")) { + VirtualFile folder = parent.createChildDirectory(this, name); + updateTree(); + select(folder, null); + parent = folder; } - }); + } + catch (IOException e) { + failReason[0] = e; + } } }); } @@ -467,14 +460,12 @@ public class FileSystemTreeImpl implements FileSystemTree { final boolean async = myTreeBuilder.isToBuildChildrenInBackground(virtualFile); - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_MODAL, () -> { - if (virtualFile instanceof NewVirtualFile) { - RefreshQueue.getInstance().refresh(async, false, null, ModalityState.stateForComponent(myTree), virtualFile); - } - else { - virtualFile.refresh(async, false); - } - }); + if (virtualFile instanceof NewVirtualFile) { + RefreshQueue.getInstance().refresh(async, false, null, ModalityState.stateForComponent(myTree), virtualFile); + } + else { + virtualFile.refresh(async, false); + } } } } diff --git a/platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeManagerImpl.java index 15cf174e5225..fdd1f8147939 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/FileTypeManagerImpl.java @@ -80,11 +80,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; -@State( - name = "FileTypeManager", - storages = @Storage("filetypes.xml"), - additionalExportFile = FileTypeManagerImpl.FILE_SPEC -) +@State(name = "FileTypeManager", storages = @Storage("filetypes.xml"), additionalExportFile = FileTypeManagerImpl.FILE_SPEC ) public class FileTypeManagerImpl extends FileTypeManagerEx implements PersistentStateComponent, ApplicationComponent, Disposable { private static final Logger LOG = Logger.getInstance(FileTypeManagerImpl.class); @@ -229,25 +225,22 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent bus.connect().subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener.Adapter() { @Override public void after(@NotNull List events) { - Collection files = ContainerUtil.map2Set(events, new Function() { - @Override - public VirtualFile fun(VFileEvent event) { - VirtualFile file = event instanceof VFileCreateEvent ? /* avoid expensive find child here */ null : event.getFile(); - VirtualFile filtered = file != null && wasAutoDetectedBefore(file) && isDetectable(file) ? file : null; - if (toLog()) { - log("F: after() VFS event " + event + - "; filtered file: " + filtered + - " (file: " + file + - "; wasAutoDetectedBefore(file): " + (file == null ? null : wasAutoDetectedBefore(file)) + - "; isDetectable(file): " + (file == null ? null : isDetectable(file)) + - "; file.getLength(): " + (file == null ? null : file.getLength()) + - "; file.isValid(): " + (file == null ? null : file.isValid()) + - "; file.is(VFileProperty.SPECIAL): " + (file == null ? null : file.is(VFileProperty.SPECIAL)) + - "; packedFlags.get(id): " + (file instanceof VirtualFileWithId ? readableFlags(packedFlags.get(((VirtualFileWithId)file).getId())) : null) + - "; file.getFileSystem():" + (file == null ? null : file.getFileSystem()) + ")"); - } - return filtered; + Collection files = ContainerUtil.map2Set(events, (Function)event -> { + VirtualFile file = event instanceof VFileCreateEvent ? /* avoid expensive find child here */ null : event.getFile(); + VirtualFile filtered = file != null && wasAutoDetectedBefore(file) && isDetectable(file) ? file : null; + if (toLog()) { + log("F: after() VFS event " + event + + "; filtered file: " + filtered + + " (file: " + file + + "; wasAutoDetectedBefore(file): " + (file == null ? null : wasAutoDetectedBefore(file)) + + "; isDetectable(file): " + (file == null ? null : isDetectable(file)) + + "; file.getLength(): " + (file == null ? null : file.getLength()) + + "; file.isValid(): " + (file == null ? null : file.isValid()) + + "; file.is(VFileProperty.SPECIAL): " + (file == null ? null : file.is(VFileProperty.SPECIAL)) + + "; packedFlags.get(id): " + (file instanceof VirtualFileWithId ? readableFlags(packedFlags.get(((VirtualFileWithId)file).getId())) : null) + + "; file.getFileSystem():" + (file == null ? null : file.getFileSystem()) + ")"); } + return filtered; }); files.remove(null); if (toLog()) { @@ -352,7 +345,7 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent } private static void log(String message) { - System.out.println(message + " - "+Thread.currentThread()); + LOG.debug(message + " - "+Thread.currentThread()); } private final BoundedTaskExecutor reDetectExecutor = new BoundedTaskExecutor("FileTypeManager redetect pool", PooledThreadExecutor.INSTANCE, 1, this); @@ -655,14 +648,8 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent // writes AUTO_DETECTED_AS_TEXT_MASK, AUTO_DETECTED_AS_BINARY_MASK bits only // protected for Upsource protected void writeFlagsToCache(@NotNull VirtualFile file, int flags) { - DataOutputStream stream = autoDetectedAttribute.writeAttribute(file); - try { - try { - stream.writeByte(flags & (AUTO_DETECTED_AS_TEXT_MASK | AUTO_DETECTED_AS_BINARY_MASK)); - } - finally { - stream.close(); - } + try (DataOutputStream stream = autoDetectedAttribute.writeAttribute(file)) { + stream.writeByte(flags & (AUTO_DETECTED_AS_TEXT_MASK | AUTO_DETECTED_AS_BINARY_MASK)); } catch (IOException e) { LOG.error(e); @@ -766,12 +753,7 @@ public class FileTypeManagerImpl extends FileTypeManagerEx implements Persistent if (toLog()) { log("F: processFirstBytes(): inputStream.read() returned "+n+"; retrying with read action. stream="+ streamInfo(stream)); } - n = ApplicationManager.getApplication().runReadAction(new ThrowableComputable() { - @Override - public Integer compute() throws IOException { - return stream.read(bytes, 0, length); - } - }); + n = ApplicationManager.getApplication().runReadAction((ThrowableComputable)() -> stream.read(bytes, 0, length)); if (toLog()) { log("F: processFirstBytes(): under read action inputStream.read() returned "+n+"; stream="+ streamInfo(stream)); } 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 8eb44c654ba1..5751a73967cf 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 @@ -25,6 +25,7 @@ import com.intellij.openapi.actionSystem.ex.ActionManagerEx; import com.intellij.openapi.actionSystem.ex.ActionUtil; import com.intellij.openapi.actionSystem.impl.PresentationFactory; import com.intellij.openapi.application.*; +import com.intellij.openapi.application.impl.LaterInvocator; import com.intellij.openapi.keymap.KeyMapBundle; import com.intellij.openapi.keymap.Keymap; import com.intellij.openapi.keymap.KeymapManager; @@ -420,7 +421,7 @@ public final class IdeKeyEventDispatcher implements Disposable { if (SystemInfo.isMac) { boolean keyTyped = e.getID() == KeyEvent.KEY_TYPED; boolean hasMnemonicsInWindow = e.getID() == KeyEvent.KEY_PRESSED && hasMnemonicInWindow(focusOwner, e.getKeyCode()) || - keyTyped && hasMnemonicInWindow(focusOwner, e.getKeyChar()); + keyTyped && hasMnemonicInWindow(focusOwner, e.getKeyChar()); boolean imEnabled = IdeEventQueue.getInstance().isInputMethodEnabled(); if (e.getModifiersEx() == InputEvent.ALT_DOWN_MASK && (hasMnemonicsInWindow || !imEnabled && keyTyped)) { @@ -496,31 +497,10 @@ public final class IdeKeyEventDispatcher implements Disposable { private static boolean hasMnemonicInWindow(Component focusOwner, int keyCode) { if (keyCode == KeyEvent.VK_ALT || keyCode == 0) return false; // Optimization - final Container container = getContainer(focusOwner); + final Container container = UIUtil.getWindow(focusOwner); return hasMnemonic(container, keyCode) || hasMnemonicInBalloons(container, keyCode); } - @Nullable - private static Container getContainer(@Nullable final Component focusOwner) { - if (focusOwner == null) return null; - if (focusOwner.isLightweight()) { - Container container = focusOwner.getParent(); - while (container != null) { - final Container parent = container.getParent(); - if (parent instanceof JLayeredPane) break; - if (parent != null && parent.isLightweight()) { - container = parent; - } - else { - break; - } - } - return container; - } - - return SwingUtilities.windowForComponent(focusOwner); - } - private static boolean hasMnemonic(final Container container, final int keyCode) { if (container == null) return false; @@ -529,7 +509,11 @@ public final class IdeKeyEventDispatcher implements Disposable { if (component instanceof AbstractButton) { final AbstractButton button = (AbstractButton)component; if (button instanceof JBOptionButton) { - if (((JBOptionButton)button).isOkToProcessDefaultMnemonics()) return true; + if (((JBOptionButton)button).isOkToProcessDefaultMnemonics() || + button.getMnemonic() == keyCode) + { + return true; + } } else { if (button.getMnemonic() == keyCode) return true; } @@ -607,7 +591,7 @@ public final class IdeKeyEventDispatcher implements Disposable { processor.createEvent(e, myContext.getDataContext(), ActionPlaces.MAIN_MENU, presentation, ActionManager.getInstance()); try (AccessToken ignored = ProhibitAWTEvents.start("update")) { - ActionUtil.performDumbAwareUpdate(action, actionEvent, true); + ActionUtil.performDumbAwareUpdate(LaterInvocator.isInModalContext(), action, actionEvent, true); } if (dumb && !action.isDumbAware()) { @@ -879,7 +863,7 @@ public final class IdeKeyEventDispatcher implements Disposable { ActionManager.getInstance(), 0); - ActionUtil.performDumbAwareUpdate(action, event, true); + ActionUtil.performDumbAwareUpdate(LaterInvocator.isInModalContext(), action, event, true); return presentation.isEnabled() && presentation.isVisible(); })) { @Override diff --git a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/KeymapPanel.java b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/KeymapPanel.java index 3fe87b7d3fb4..7b909a385282 100644 --- a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/KeymapPanel.java +++ b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ui/KeymapPanel.java @@ -780,7 +780,7 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi .getComponent() .show(e.getComponent(), ((MouseEvent)e).getX(), ((MouseEvent)e).getY()); } - else if (!isDoubleClick || group.getChildrenCount() == 0) { + else if (!isDoubleClick || !ActionManager.getInstance().isGroup(actionId)) { DataContext dataContext = DataManager.getInstance().getDataContext(this); ListPopup popup = JBPopupFactory.getInstance().createActionGroupPopup("Edit Shortcuts", group, diff --git a/platform/platform-impl/src/com/intellij/openapi/options/TabbedConfigurable.java b/platform/platform-impl/src/com/intellij/openapi/options/TabbedConfigurable.java index 59cde1bfc0e9..39be2062378f 100644 --- a/platform/platform-impl/src/com/intellij/openapi/options/TabbedConfigurable.java +++ b/platform/platform-impl/src/com/intellij/openapi/options/TabbedConfigurable.java @@ -16,9 +16,9 @@ package com.intellij.openapi.options; import com.intellij.openapi.Disposable; +import com.intellij.openapi.util.Disposer; import com.intellij.ui.TabbedPaneWrapper; import com.intellij.util.ui.JBUI; -import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -30,15 +30,11 @@ import static com.intellij.openapi.options.ex.ConfigurableCardPanel.createConfig public abstract class TabbedConfigurable extends CompositeConfigurable implements Configurable.NoScroll, Configurable.NoMargin { protected TabbedPaneWrapper myTabbedPane; - private final Disposable myParentDisposable; - - protected TabbedConfigurable(@NotNull Disposable parentDisposable) { - myParentDisposable = parentDisposable; - } + private final Disposable myDisposable = Disposer.newDisposable(); @Override public JComponent createComponent() { - myTabbedPane = new TabbedPaneWrapper(myParentDisposable); + myTabbedPane = new TabbedPaneWrapper(myDisposable); createConfigurableTabs(); final JComponent component = myTabbedPane.getComponent(); component.setBorder(JBUI.Borders.emptyTop(5)); @@ -54,11 +50,8 @@ public abstract class TabbedConfigurable extends CompositeConfigurable super.show()); - } - public Configurable getConfigurable() { return myConfigurable; } diff --git a/platform/platform-impl/src/com/intellij/openapi/options/newEditor/ConfigurableEditor.java b/platform/platform-impl/src/com/intellij/openapi/options/newEditor/ConfigurableEditor.java index cd3991d65b4a..40155a620d3b 100644 --- a/platform/platform-impl/src/com/intellij/openapi/options/newEditor/ConfigurableEditor.java +++ b/platform/platform-impl/src/com/intellij/openapi/options/newEditor/ConfigurableEditor.java @@ -31,8 +31,6 @@ import com.intellij.openapi.options.ShowSettingsUtil; import com.intellij.openapi.options.ex.ConfigurableCardPanel; import com.intellij.openapi.options.ex.ConfigurableExtensionPointUtil; import com.intellij.openapi.options.ex.ConfigurableVisitor; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.ActionCallback; import com.intellij.openapi.util.Disposer; @@ -75,7 +73,7 @@ class ConfigurableEditor extends AbstractEditor implements AnActionListener, AWT private final AbstractAction myApplyAction = new AbstractAction(CommonBundle.getApplyButtonText()) { @Override public void actionPerformed(ActionEvent event) { - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, () -> apply()); + apply(); } }; private final AbstractAction myResetAction = new AbstractAction(RESET_NAME) { diff --git a/platform/platform-impl/src/com/intellij/openapi/options/newEditor/SettingsDialog.java b/platform/platform-impl/src/com/intellij/openapi/options/newEditor/SettingsDialog.java index f1692ae57ac6..e0aba405f984 100644 --- a/platform/platform-impl/src/com/intellij/openapi/options/newEditor/SettingsDialog.java +++ b/platform/platform-impl/src/com/intellij/openapi/options/newEditor/SettingsDialog.java @@ -22,8 +22,6 @@ import com.intellij.openapi.application.TransactionGuard; import com.intellij.openapi.help.HelpManager; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.ConfigurableGroup; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import org.jetbrains.annotations.NonNls; @@ -75,9 +73,7 @@ public class SettingsDialog extends DialogWrapper implements DataProvider { @Override public void show() { - TransactionGuard.getInstance().submitTransactionAndWait( - () -> DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, () -> - super.show())); + TransactionGuard.getInstance().submitTransactionAndWait(() -> super.show()); } diff --git a/platform/platform-impl/src/com/intellij/openapi/project/DumbPermissionServiceImpl.java b/platform/platform-impl/src/com/intellij/openapi/project/DumbPermissionServiceImpl.java deleted file mode 100644 index 1ecb23aee1ed..000000000000 --- a/platform/platform-impl/src/com/intellij/openapi/project/DumbPermissionServiceImpl.java +++ /dev/null @@ -1,52 +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.intellij.openapi.project; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * @author peter - */ -public class DumbPermissionServiceImpl implements DumbPermissionService { - private final ThreadLocal myPermission = new ThreadLocal<>(); - - @Override - public void allowStartingDumbModeInside(@NotNull DumbModePermission permission, @NotNull Runnable runnable) { - DumbModePermission prev = myPermission.get(); - if (prev == DumbModePermission.MAY_START_MODAL && permission == DumbModePermission.MAY_START_BACKGROUND) { - runnable.run(); - return; - } - - myPermission.set(permission); - try { - runnable.run(); - } - finally { - if (prev == null) { - myPermission.remove(); - } else { - myPermission.set(prev); - } - } - } - - @Nullable - public DumbModePermission getPermission() { - return myPermission.get(); - } -} 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 0e8c5c4348c7..980c2dc462c3 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java @@ -16,11 +16,9 @@ package com.intellij.openapi.project; import com.intellij.ide.IdeBundle; -import com.intellij.ide.startup.StartupManagerEx; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.*; import com.intellij.openapi.application.impl.ApplicationImpl; -import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Attachment; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; @@ -55,8 +53,6 @@ import java.util.concurrent.atomic.AtomicReference; public class DumbServiceImpl extends DumbService implements Disposable, ModificationTracker { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.project.DumbServiceImpl"); - private static final NotNullLazyValue ourPermissionService = NotNullLazyValue.createValue( - () -> (DumbPermissionServiceImpl)ServiceManager.getService(DumbPermissionService.class)); private static Throwable ourForcedTrace; private final AtomicReference myState = new AtomicReference<>(State.SMART); private volatile Throwable myDumbStart; @@ -143,7 +139,7 @@ public class DumbServiceImpl extends DumbService implements Disposable, Modifica } else { myState.set(State.WAITING_FOR_FINISH); - updateFinished(true); + updateFinished(); } } @@ -166,7 +162,6 @@ public class DumbServiceImpl extends DumbService implements Disposable, Modifica TransactionId contextTransaction = TransactionGuard.getInstance().getContextTransaction(); final Throwable trace = ourForcedTrace != null ? ourForcedTrace : new Throwable(); // please report exceptions here to peter - final DumbModePermission schedulerPermission = getExplicitPermission(); if (LOG.isDebugEnabled()) LOG.debug("Scheduling task " + task); final Application application = ApplicationManager.getApplication(); @@ -189,7 +184,7 @@ public class DumbServiceImpl extends DumbService implements Disposable, Modifica return; } - Runnable runnable = () -> queueTaskOnEdt(task, contextTransaction, trace, schedulerPermission); + Runnable runnable = () -> queueTaskOnEdt(task, contextTransaction, trace); if (application.isDispatchThread()) { runnable.run(); // will log errors if not already in a write-safe context } else { @@ -199,19 +194,12 @@ public class DumbServiceImpl extends DumbService implements Disposable, Modifica private void queueTaskOnEdt(@NotNull DumbModeTask task, @Nullable TransactionId contextTransaction, - @NotNull Throwable trace, - @Nullable DumbModePermission schedulerPermission) { + @NotNull Throwable trace) { if (!addTaskToQueue(task)) return; - if (myState.get() != State.RUNNING_DUMB_TASKS) { + if (myState.get() == State.SMART || myState.get() == State.WAITING_FOR_FINISH) { WriteAction.run(() -> enterDumbMode(contextTransaction, trace)); - - if (shouldStartModalProgress(schedulerPermission, trace)) { - showModalProgress(); - } - else { - startBackgroundProcess(); - } + ApplicationManager.getApplication().invokeLater(this::startBackgroundProcess); } } @@ -230,21 +218,10 @@ public class DumbServiceImpl extends DumbService implements Disposable, Modifica return true; } - private boolean shouldStartModalProgress(@Nullable DumbModePermission schedulerPermission, @NotNull Throwable trace) { - DumbModePermission permission = schedulerPermission != null ? schedulerPermission : getEdtPermission(); - if (permission == null) { - LOG.info("Dumb mode not permitted in modal environment; see DumbService.allowStartingDumbModeInside documentation", trace); - } - else if (permission == DumbModePermission.MAY_START_MODAL) { - LOG.debug("Starting modal dumb mode, caused by the following trace", trace); - } - return permission != DumbModePermission.MAY_START_BACKGROUND; - } - private void enterDumbMode(@Nullable TransactionId contextTransaction, @NotNull Throwable trace) { boolean wasSmart = !isDumb(); synchronized (myRunWhenSmartQueue) { - myState.set(State.RUNNING_DUMB_TASKS); + myState.set(State.SCHEDULED_TASKS); } myDumbStart = trace; myDumbStartTransaction = contextTransaction; @@ -259,25 +236,6 @@ public class DumbServiceImpl extends DumbService implements Disposable, Modifica } } - @Nullable - private DumbModePermission getEdtPermission() { - DumbModePermission permission = getExplicitPermission(); - if (permission != null) { - return permission; - } - - if (ModalityState.current() == ModalityState.NON_MODAL || !StartupManagerEx.getInstanceEx(myProject).postStartupActivityPassed()) { - return DumbModePermission.MAY_START_BACKGROUND; - } - - return null; - } - - @Nullable - public static DumbModePermission getExplicitPermission() { - return ourPermissionService.getValue().getPermission(); - } - @NotNull public static AccessToken forceDumbModeStartTrace(@NotNull Throwable trace) { ApplicationManager.getApplication().assertIsDispatchThread(); @@ -292,16 +250,16 @@ public class DumbServiceImpl extends DumbService implements Disposable, Modifica }; } - private void queueUpdateFinished(boolean modal) { + private void queueUpdateFinished() { if (myState.compareAndSet(State.RUNNING_DUMB_TASKS, State.WAITING_FOR_FINISH)) { StartupManager.getInstance(myProject).runWhenProjectIsInitialized( () -> TransactionGuard.getInstance().submitTransaction(myProject, myDumbStartTransaction, () -> WriteAction.run( - () -> updateFinished(modal)))); + () -> updateFinished()))); } } - private void updateFinished(boolean modal) { + private void updateFinished() { synchronized (myRunWhenSmartQueue) { if (!myState.compareAndSet(State.WAITING_FOR_FINISH, State.SMART)) { return; @@ -313,10 +271,7 @@ public class DumbServiceImpl extends DumbService implements Disposable, Modifica if (ApplicationManager.getApplication().isInternal()) LOG.info("updateFinished"); - // some listeners might start yet another dumb mode - // allow that whatever the current modality is, because it won't harm anyone - allowStartingDumbModeInside(modal ? DumbModePermission.MAY_START_MODAL : DumbModePermission.MAY_START_BACKGROUND, - this::notifyUpdateFinished); + notifyUpdateFinished(); } private void notifyUpdateFinished() { @@ -417,13 +372,28 @@ public class DumbServiceImpl extends DumbService implements Disposable, Modifica }, modalityState, myProject.getDisposed()); } + @Override + public void completeJustSubmittedTasks() { + assert myProject.isInitialized(); + if (myState.get() != State.SCHEDULED_TASKS) { + return; + } + + while (isDumb()) { + showModalProgress(); + } + } + private void showModalProgress() { try { ((ApplicationImpl)ApplicationManager.getApplication()).executeSuspendingWriteAction(myProject, IdeBundle.message("progress.indexing"), () -> - runBackgroundProcess(ProgressManager.getInstance().getProgressIndicator(), true)); + runBackgroundProcess(ProgressManager.getInstance().getProgressIndicator())); } finally { - queueUpdateFinished(true); + if (myState.get() != State.SMART) { + if (myState.get() != State.WAITING_FOR_FINISH) throw new AssertionError(myState.get()); + WriteAction.run(() -> updateFinished()); + } } } @@ -432,21 +402,22 @@ public class DumbServiceImpl extends DumbService implements Disposable, Modifica ProgressManager.getInstance().run(new Task.Backgroundable(myProject, IdeBundle.message("progress.indexing"), false) { @Override public void run(@NotNull final ProgressIndicator visibleIndicator) { - runBackgroundProcess(visibleIndicator, false); + runBackgroundProcess(visibleIndicator); } }); } catch (Throwable e) { - queueUpdateFinished(false); + queueUpdateFinished(); LOG.error("Failed to start background index update task", e); } } - private void runBackgroundProcess(@NotNull final ProgressIndicator visibleIndicator, boolean modal) { + private void runBackgroundProcess(@NotNull final ProgressIndicator visibleIndicator) { + if (!myState.compareAndSet(State.SCHEDULED_TASKS, State.RUNNING_DUMB_TASKS)) return; + final ShutDownTracker shutdownTracker = ShutDownTracker.getInstance(); final Thread self = Thread.currentThread(); - AccessToken token = HeavyProcessLatch.INSTANCE.processStarted("Performing indexing tasks"); - try { + try (AccessToken ignored = HeavyProcessLatch.INSTANCE.processStarted("Performing indexing tasks")) { shutdownTracker.registerStopperThread(self); if (visibleIndicator instanceof ProgressIndicatorEx) { @@ -455,7 +426,7 @@ public class DumbServiceImpl extends DumbService implements Disposable, Modifica DumbModeTask task = null; while (true) { - Pair pair = getNextTask(task, modal); + Pair pair = getNextTask(task); if (pair == null) break; task = pair.first; @@ -477,7 +448,6 @@ public class DumbServiceImpl extends DumbService implements Disposable, Modifica } finally { shutdownTracker.unregisterStopperThread(self); - token.finish(); } } @@ -502,7 +472,7 @@ public class DumbServiceImpl extends DumbService implements Disposable, Modifica }, taskIndicator); } - @Nullable private Pair getNextTask(@Nullable final DumbModeTask prevTask, final boolean modal) { + @Nullable private Pair getNextTask(@Nullable final DumbModeTask prevTask) { final Ref> result = Ref.create(); invokeAndWaitIfNeeded(() -> { if (myProject.isDisposed()) return; @@ -512,7 +482,7 @@ public class DumbServiceImpl extends DumbService implements Disposable, Modifica while (true) { if (myUpdatesQueue.isEmpty()) { - queueUpdateFinished(modal); + queueUpdateFinished(); return; } @@ -589,5 +559,26 @@ public class DumbServiceImpl extends DumbService implements Disposable, Modifica } } - private enum State { SMART, RUNNING_DUMB_TASKS, WAITING_FOR_FINISH } + private enum State { + /** Non-dumb mode. For all other states, {@link #isDumb()} returns true. */ + SMART, + + /** + * A state between entering dumb mode ({@link #queueTaskOnEdt}) and actually starting the background progress later ({@link #runBackgroundProcess}). + * In this state, it's possible to call {@link #completeJustSubmittedTasks()} and perform all submitted the tasks modally. + * This state can happen after {@link #SMART} or {@link #WAITING_FOR_FINISH}. Followed by {@link #RUNNING_DUMB_TASKS}. + */ + SCHEDULED_TASKS, + + /** + * Indicates that a background thread is currently executing dumb tasks. + */ + RUNNING_DUMB_TASKS, + + /** + * Set after background execution ({@link #RUNNING_DUMB_TASKS}) finishes, until the dumb mode can be exited + * (in a write-safe context on EDT when project is initialized). If new tasks are queued at this state, it's switched to {@link #SCHEDULED_TASKS}. + */ + WAITING_FOR_FINISH + } } diff --git a/platform/platform-impl/src/com/intellij/openapi/ui/impl/AbstractDialog.java b/platform/platform-impl/src/com/intellij/openapi/ui/impl/AbstractDialog.java index 757077c892ce..8de7c907ed1c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/ui/impl/AbstractDialog.java +++ b/platform/platform-impl/src/com/intellij/openapi/ui/impl/AbstractDialog.java @@ -99,6 +99,7 @@ interface AbstractDialog extends Disposable { void show(); + @NotNull IdeFocusManager getFocusManager(); FocusTrackback getFocusTrackback(); diff --git a/platform/platform-impl/src/com/intellij/openapi/ui/impl/DialogWrapperPeerImpl.java b/platform/platform-impl/src/com/intellij/openapi/ui/impl/DialogWrapperPeerImpl.java index 5c0c912c4542..5634f11e0cdc 100644 --- a/platform/platform-impl/src/com/intellij/openapi/ui/impl/DialogWrapperPeerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/ui/impl/DialogWrapperPeerImpl.java @@ -784,6 +784,7 @@ public class DialogWrapperPeerImpl extends DialogWrapperPeer implements FocusTra return SoftReference.dereference(myProject); } + @NotNull @Override public IdeFocusManager getFocusManager() { Project project = getProject(); diff --git a/platform/platform-impl/src/com/intellij/openapi/ui/impl/HeadlessDialog.java b/platform/platform-impl/src/com/intellij/openapi/ui/impl/HeadlessDialog.java index 3f5ba96aba71..6065e057c54d 100644 --- a/platform/platform-impl/src/com/intellij/openapi/ui/impl/HeadlessDialog.java +++ b/platform/platform-impl/src/com/intellij/openapi/ui/impl/HeadlessDialog.java @@ -17,6 +17,7 @@ package com.intellij.openapi.ui.impl; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.openapi.wm.impl.IdeFocusManagerHeadless; import com.intellij.ui.FocusTrackback; import org.jetbrains.annotations.NotNull; @@ -184,9 +185,10 @@ class HeadlessDialog implements AbstractDialog { myWrapper.close(DialogWrapper.OK_EXIT_CODE); } + @NotNull @Override public IdeFocusManager getFocusManager() { - return null; + return new IdeFocusManagerHeadless(); } @Override diff --git a/platform/platform-impl/src/com/intellij/openapi/ui/impl/ShadowPainter.java b/platform/platform-impl/src/com/intellij/openapi/ui/impl/ShadowPainter.java index 89ce58d1ee9e..9749f4abc613 100644 --- a/platform/platform-impl/src/com/intellij/openapi/ui/impl/ShadowPainter.java +++ b/platform/platform-impl/src/com/intellij/openapi/ui/impl/ShadowPainter.java @@ -15,6 +15,7 @@ */ package com.intellij.openapi.ui.impl; +import com.intellij.util.IconUtil; import org.jetbrains.annotations.Nullable; import javax.swing.*; @@ -37,13 +38,13 @@ public class ShadowPainter { private Color myBorderColor; public ShadowPainter(Icon top, Icon topRight, Icon right, Icon bottomRight, Icon bottom, Icon bottomLeft, Icon left, Icon topLeft) { - myTop = top; + myTop = IconUtil.cropIcon(top, 1, Integer.MAX_VALUE); myTopRight = topRight; - myRight = right; + myRight = IconUtil.cropIcon(right, Integer.MAX_VALUE, 1); myBottomRight = bottomRight; - myBottom = bottom; + myBottom = IconUtil.cropIcon(bottom, 1, Integer.MAX_VALUE); myBottomLeft = bottomLeft; - myLeft = left; + myLeft = IconUtil.cropIcon(left, Integer.MAX_VALUE, 1); myTopLeft = topLeft; } diff --git a/platform/platform-impl/src/com/intellij/openapi/ui/playback/commands/ToggleActionCommand.java b/platform/platform-impl/src/com/intellij/openapi/ui/playback/commands/ToggleActionCommand.java index b34ee8d2a750..1555bc2d51b2 100644 --- a/platform/platform-impl/src/com/intellij/openapi/ui/playback/commands/ToggleActionCommand.java +++ b/platform/platform-impl/src/com/intellij/openapi/ui/playback/commands/ToggleActionCommand.java @@ -18,6 +18,7 @@ package com.intellij.openapi.ui.playback.commands; import com.intellij.ide.DataManager; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.actionSystem.ex.ActionUtil; +import com.intellij.openapi.application.impl.LaterInvocator; import com.intellij.openapi.ui.playback.PlaybackContext; import com.intellij.openapi.util.ActionCallback; import com.intellij.openapi.util.registry.Registry; @@ -88,7 +89,7 @@ public class ToggleActionCommand extends AbstractCommand { .getDataContext(KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner()), ActionPlaces.UNKNOWN, presentation, ActionManager.getInstance(), 0); - ActionUtil.performDumbAwareUpdate(action, event, false); + ActionUtil.performDumbAwareUpdate(LaterInvocator.isInModalContext(), action, event, false); Boolean state = (Boolean)event.getPresentation().getClientProperty(ToggleAction.SELECTED_PROPERTY); if (state.booleanValue() != on) { diff --git a/platform/platform-impl/src/com/intellij/openapi/vcs/changes/RefreshablePanel.java b/platform/platform-impl/src/com/intellij/openapi/vcs/changes/RefreshablePanel.java index a7d7b0cb6f02..8513a87f59eb 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vcs/changes/RefreshablePanel.java +++ b/platform/platform-impl/src/com/intellij/openapi/vcs/changes/RefreshablePanel.java @@ -4,16 +4,7 @@ import com.intellij.openapi.Disposable; import javax.swing.*; -/** - * @author irengrig - * Date: 8/12/11 - * Time: 6:47 PM - */ -public interface RefreshablePanel extends Disposable { - boolean refreshDataSynch(); - void dataChanged(); +public interface RefreshablePanel extends Disposable { void refresh(); JPanel getPanel(); - void away(); - boolean isStillValid(Data data); } diff --git a/platform/platform-impl/src/com/intellij/openapi/vcs/changes/TabbedRefreshablePanel.java b/platform/platform-impl/src/com/intellij/openapi/vcs/changes/TabbedRefreshablePanel.java deleted file mode 100644 index b7c80c10581c..000000000000 --- a/platform/platform-impl/src/com/intellij/openapi/vcs/changes/TabbedRefreshablePanel.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2000-2012 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.changes; - -import com.intellij.openapi.util.Disposer; -import com.intellij.ui.components.JBTabbedPane; - -import javax.swing.*; -import java.awt.*; -import java.util.ArrayList; -import java.util.List; - -/** - * Created with IntelliJ IDEA. - * User: Irina.Chernushina - * Date: 4/25/12 - * Time: 4:53 PM - */ -public class TabbedRefreshablePanel implements RefreshablePanel { - private final JBTabbedPane myPane; - private final JPanel myPanel; - private final List myPanels; - - public TabbedRefreshablePanel() { - myPanels = new ArrayList<>(); - myPane = new JBTabbedPane(); - myPanel = new JPanel(new BorderLayout()); - myPanel.add(myPane, BorderLayout.CENTER); - } - - public void addTab(final String title, final RefreshablePanel panel) { - myPanels.add(panel); - myPane.add(title, panel.getPanel()); - } - - @Override - public boolean refreshDataSynch() { - for (RefreshablePanel panel : myPanels) { - if (! panel.refreshDataSynch()) return false; - } - return true; - } - - @Override - public boolean isStillValid(Object o) { - for (RefreshablePanel panel : myPanels) { - if (! panel.isStillValid(o)) return false; - } - return true; - } - - @Override - public void dataChanged() { - for (RefreshablePanel panel : myPanels) { - panel.dataChanged(); - } - } - - @Override - public void refresh() { - for (RefreshablePanel panel : myPanels) { - panel.refresh(); - } - } - - @Override - public JPanel getPanel() { - return myPanel; - } - - @Override - public void away() { - for (RefreshablePanel panel : myPanels) { - panel.away(); - } - } - - @Override - public void dispose() { - for (RefreshablePanel panel : myPanels) { - Disposer.dispose(panel); - } - } -} diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/ex/dummy/DummyCachingFileSystem.java b/platform/platform-impl/src/com/intellij/openapi/vfs/ex/dummy/DummyCachingFileSystem.java index 40bde0ea332f..b6552220e2f7 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/ex/dummy/DummyCachingFileSystem.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/ex/dummy/DummyCachingFileSystem.java @@ -27,6 +27,7 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.containers.BidirectionalMap; import com.intellij.util.containers.ConcurrentFactoryMap; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.FactoryMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -121,6 +122,18 @@ public abstract class DummyCachingFileSystem extends Dumm return list == null || list.size() > 1 ? null : list.get(0); } + @NotNull + public Project getProjectOrFail(String projectId) { + List list = myProject2Id.getKeysByValue(projectId); + if (list == null || list.isEmpty()) { + throw new AssertionError(projectId + " project not found among: " + ContainerUtil.newArrayList(myProject2Id.values())); + } + if (list.size() != 1) { + throw new AssertionError(projectId + " is mapped to several projects: " + list); + } + return list.get(0); + } + @NotNull public Collection getCachedFiles() { return myCachedFiles.notNullValues(); 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 90253f1f4d0a..b5eacbcdc6e8 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 @@ -17,8 +17,6 @@ package com.intellij.openapi.vfs.newvfs; import com.intellij.openapi.application.*; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.DumbServiceImpl; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; @@ -48,7 +46,6 @@ public class RefreshSessionImpl extends RefreshSession { private final boolean myIsRecursive; private final Runnable myFinishRunnable; private final ModalityState myModalityState; - private final DumbModePermission myDumbModePermission; private final Throwable myStartTrace; private final Semaphore mySemaphore = new Semaphore(); @@ -68,11 +65,9 @@ public class RefreshSessionImpl extends RefreshSession { LOG.assertTrue(modalityState == ModalityState.NON_MODAL || modalityState != ModalityState.any(), "Refresh session should have a specific modality"); if (modalityState == ModalityState.NON_MODAL) { - myDumbModePermission = null; myStartTrace = null; } else { - myDumbModePermission = DumbServiceImpl.getExplicitPermission(); myStartTrace = new Throwable(); // please report exceptions here to peter } } @@ -181,13 +176,10 @@ public class RefreshSessionImpl extends RefreshSession { } try (AccessToken ignore = myStartTrace == null ? null : DumbServiceImpl.forceDumbModeStartTrace(myStartTrace)) { - WriteAction.run(() -> { - if (myDumbModePermission != null) { - DumbService.allowStartingDumbModeInside(myDumbModePermission, this::fireEventsInWriteAction); - } else { - fireEventsInWriteAction(); - } - }); + if (LOG.isDebugEnabled()) { + LOG.debug("RefreshSessionImpl: Events are about to fire: "+myEvents); + } + WriteAction.run(this::fireEventsInWriteAction); } finally { mySemaphore.up(); 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 1267acbb8763..6e340aa58109 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 @@ -15,7 +15,6 @@ */ package com.intellij.openapi.vfs.newvfs.persistent; -import com.intellij.openapi.Forceable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationNamesInfo; import com.intellij.openapi.application.PathManager; @@ -41,6 +40,7 @@ import com.intellij.util.io.storage.*; import gnu.trove.TIntArrayList; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; import javax.swing.*; import java.awt.*; @@ -57,7 +57,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; * @author max */ @SuppressWarnings({"PointlessArithmeticExpression", "HardCodedStringLiteral"}) -public class FSRecords implements Forceable { +public class FSRecords { private static final Logger LOG = Logger.getInstance("#com.intellij.vfs.persistent.FSRecords"); public static final boolean weHaveContentHashes = SystemProperties.getBooleanProperty("idea.share.contents", true); @@ -541,7 +541,7 @@ public class FSRecords implements Forceable { } } - public FSRecords() { + private FSRecords() { } public static void connect() { @@ -755,13 +755,13 @@ public class FSRecords implements Forceable { } } - @Override - public void force() { + @TestOnly + public static void force() { DbConnection.force(); } - @Override - public boolean isDirty() { + @TestOnly + public static boolean isDirty() { return DbConnection.isDirty(); } @@ -1001,21 +1001,16 @@ public class FSRecords implements Forceable { } private static void incModCount(int id) { - DbConnection.markDirty(); - ourLocalModificationCount++; + incLocalModCount(); final int count = getModCount() + 1; getRecords().putInt(HEADER_GLOBAL_MOD_COUNT_OFFSET, count); - int parent = id; - int depth = 10000; - while (parent != 0) { - setModCount(parent, count); - parent = getParent(parent); - if (depth -- == 0) { - LOG.error("Cyclic parent child relation? file: " + getName(id)); - return; - } - } + setModCount(id, count); + } + + private static void incLocalModCount() { + DbConnection.markDirty(); + ourLocalModificationCount++; } static int getLocalModCount() { @@ -1226,8 +1221,12 @@ public class FSRecords implements Forceable { public static void setLength(int id, long len) { w.lock(); try { - incModCount(id); - getRecords().putLong(getOffset(id, LENGTH_OFFSET), len); + ResizeableMappedFile records = getRecords(); + int lengthOffset = getOffset(id, LENGTH_OFFSET); + if (records.getLong(lengthOffset) != len) { + incModCount(id); + records.putLong(lengthOffset, len); + } } catch (Throwable e) { DbConnection.handleError(e); @@ -1250,8 +1249,12 @@ public class FSRecords implements Forceable { public static void setTimestamp(int id, long value) { w.lock(); try { - incModCount(id); - getRecords().putLong(getOffset(id, TIMESTAMP_OFFSET), value); + int timeStampOffset = getOffset(id, TIMESTAMP_OFFSET); + ResizeableMappedFile records = getRecords(); + if (records.getLong(timeStampOffset) != value) { + incModCount(id); + records.putLong(timeStampOffset, value); + } } catch (Throwable e) { DbConnection.handleError(e); @@ -1645,8 +1648,6 @@ public class FSRecords implements Forceable { RefCountingStorage contentStorage = getContentStorage(); w.lock(); try { - incModCount(myFileId); - checkFileIsValid(myFileId); int page; @@ -1654,8 +1655,10 @@ public class FSRecords implements Forceable { if (weHaveContentHashes) { page = findOrCreateContentRecord(bytes.getBytes(), bytes.getOffset(), bytes.getLength()); - incModCount(myFileId); - checkFileIsValid(myFileId); + if (page < 0 || getContentId(myFileId) != page) { + incModCount(myFileId); + setContentRecordId(myFileId, page > 0 ? page : -page); + } setContentRecordId(myFileId, page > 0 ? page : -page); @@ -1663,6 +1666,7 @@ public class FSRecords implements Forceable { page = -page; fixedSize = true; } else { + incModCount(myFileId); page = getContentRecordId(myFileId); if (page == 0 || contentStorage.getRefCount(page) > 1) { page = contentStorage.acquireNewRecord(); @@ -1782,7 +1786,7 @@ public class FSRecords implements Forceable { w.lock(); try { rewriteDirectoryRecordWithAttrContent(_out); - incModCount(myFileId); + incLocalModCount(); } finally { w.unlock(); @@ -1791,7 +1795,7 @@ public class FSRecords implements Forceable { else { w.lock(); try { - incModCount(myFileId); + incLocalModCount(); int page = findAttributePage(myFileId, myAttribute, true); if (inlineAttributes && page < 0) { rewriteDirectoryRecordWithAttrContent(new BufferExposingByteArrayOutputStream()); diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/StripeButtonUI.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/StripeButtonUI.java index 69766e3b0192..7742986715c7 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/StripeButtonUI.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/StripeButtonUI.java @@ -112,7 +112,7 @@ public final class StripeButtonUI extends MetalToggleButtonUI{ final Color background = button.getBackground(); ourIconRect.x -= JBUI.scale(2); ourTextRect.x -= JBUI.scale(2); - final int off = JBUI.scale(1); + final int off = 1; if (model.isArmed() && model.isPressed() || model.isSelected() || model.isRollover()) { if (anchor == ToolWindowAnchor.LEFT) g2.translate(-off, 0); if (anchor.isHorizontal()) g2.translate(0, -off); diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowHeader.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowHeader.java index e1b2f8b04190..ea9b3ceca6f0 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowHeader.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowHeader.java @@ -23,6 +23,7 @@ import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.actionSystem.ex.ActionManagerEx; import com.intellij.openapi.actionSystem.impl.*; +import com.intellij.openapi.application.impl.LaterInvocator; import com.intellij.openapi.keymap.KeymapUtil; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.util.SystemInfo; @@ -264,7 +265,7 @@ public abstract class ToolWindowHeader extends JPanel implements Disposable, UIS List newVisibleActions = ContainerUtil.newArrayListWithCapacity(myVisibleActions.size()); DataContext dataContext = DataManager.getInstance().getDataContext(this); - Utils.expandActionGroup(myActionGroup, newVisibleActions, myPresentationFactory, dataContext, + Utils.expandActionGroup(LaterInvocator.isInModalContext(), myActionGroup, newVisibleActions, myPresentationFactory, dataContext, ActionPlaces.TOOLWINDOW_TITLE, myUpdater.getActionManager(), transparentOnly); if (forced || !newVisibleActions.equals(myVisibleActions)) { diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/AddModalTestProcessAction.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/AddModalTestProcessAction.java new file mode 100644 index 000000000000..dc322767f3c1 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/AddModalTestProcessAction.java @@ -0,0 +1,93 @@ +/* + * 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.openapi.wm.impl.status; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +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.DumbAware; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; + +@SuppressWarnings({"HardCodedStringLiteral"}) +public class AddModalTestProcessAction extends AnAction implements DumbAware { + public AddModalTestProcessAction() { + super("Add Test Process"); + } + + public void actionPerformed(AnActionEvent e) { + final Project project = e.getData(CommonDataKeys.PROJECT); + new Task.Modal(project, "Test Process", true) { + public void run(@NotNull final ProgressIndicator indicator) { + try { + indicator.setText("welcome!"); + + Thread.sleep(6000); + + countTo(1000, new Count() { + public void onCount(int each) { + + indicator.setText("Found: " + each / 20 + 1); + if (each / 10.0 == Math.round(each / 10.0)) { + indicator.setText(null); + } + indicator.setFraction(each / 1000.0); + + try { + Thread.sleep(100); + } + catch (InterruptedException e1) { + e1.printStackTrace(); + } + + indicator.checkCanceled(); + indicator.setText2("bla bla bla"); + } + }); + indicator.stop(); + } + catch (ProcessCanceledException e1) { + try { + Thread.sleep(2000); + indicator.stop(); + } + catch (InterruptedException e2) { + e2.printStackTrace(); + } + } + catch (InterruptedException e1) { + e1.printStackTrace(); + } + } + }.queue(); + } + + private static void countTo(int top, Count count) { + for (int i = 0; i < top; i++) { + count.onCount(i); + } + } + + private interface Count { + void onCount(int each); + } +} diff --git a/platform/platform-impl/src/com/intellij/ui/popup/AbstractPopup.java b/platform/platform-impl/src/com/intellij/ui/popup/AbstractPopup.java index 955d8d55fbb9..dcd098f04726 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/AbstractPopup.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/AbstractPopup.java @@ -866,7 +866,7 @@ public class AbstractPopup implements JBPopup { int i = Registry.intValue("ide.popup.resizable.border.sensitivity", 4); WindowResizeListener resizeListener = new WindowResizeListener( myContent, - myMovable ? new Insets(i, i, i, i) : new Insets(0, 0, i, i), + myMovable ? JBUI.insets(i, i, i, i) : JBUI.insets(0, 0, i, i), isToDrawMacCorner() ? AllIcons.General.MacCorner : null) { private Cursor myCursor; diff --git a/platform/platform-impl/src/com/intellij/ui/popup/PopupFactoryImpl.java b/platform/platform-impl/src/com/intellij/ui/popup/PopupFactoryImpl.java index 76b3099b9a26..7d3f54a00a37 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/PopupFactoryImpl.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/PopupFactoryImpl.java @@ -27,6 +27,7 @@ import com.intellij.openapi.actionSystem.impl.ActionMenu; import com.intellij.openapi.actionSystem.impl.Utils; import com.intellij.openapi.application.ex.ApplicationEx; import com.intellij.openapi.application.ex.ApplicationManagerEx; +import com.intellij.openapi.application.impl.LaterInvocator; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.CaretModel; import com.intellij.openapi.editor.Editor; @@ -294,7 +295,7 @@ public class PopupFactoryImpl extends JBPopupFactory { new AnActionEvent(null, DataManager.getInstance().getDataContext(myComponent), myActionPlace, presentation, ActionManager.getInstance(), 0); actionEvent.setInjectedContext(action.isInInjectedContext()); - ActionUtil.performDumbAwareUpdate(action, actionEvent, false); + ActionUtil.performDumbAwareUpdate(LaterInvocator.isInModalContext(), action, actionEvent, false); return presentation; } @@ -1060,7 +1061,7 @@ public class PopupFactoryImpl extends JBPopupFactory { Presentation presentation = getPresentation(action); AnActionEvent event = createActionEvent(action); - ActionUtil.performDumbAwareUpdate(action, event, true); + ActionUtil.performDumbAwareUpdate(LaterInvocator.isInModalContext(), action, event, true); if ((myShowDisabled || presentation.isEnabled()) && presentation.isVisible()) { String text = presentation.getText(); if (myShowNumbers) { diff --git a/platform/platform-impl/src/com/intellij/util/LocalFileUrl.java b/platform/platform-impl/src/com/intellij/util/LocalFileUrl.java index 26cef2d80492..a96c338dc30d 100644 --- a/platform/platform-impl/src/com/intellij/util/LocalFileUrl.java +++ b/platform/platform-impl/src/com/intellij/util/LocalFileUrl.java @@ -14,6 +14,11 @@ public final class LocalFileUrl implements Url { this.path = path; } + @Override + public Url resolve(@NotNull String subPath) { + return new LocalFileUrl(path.isEmpty() ? subPath : (path + "/" + subPath)); + } + @NotNull @Override public String getPath() { diff --git a/platform/platform-impl/src/com/intellij/util/UrlImpl.java b/platform/platform-impl/src/com/intellij/util/UrlImpl.java index 99b6e629415f..b4cb5def6eac 100644 --- a/platform/platform-impl/src/com/intellij/util/UrlImpl.java +++ b/platform/platform-impl/src/com/intellij/util/UrlImpl.java @@ -47,6 +47,11 @@ public final class UrlImpl implements Url { this.parameters = StringUtil.nullize(parameters); } + @Override + public Url resolve(@NotNull String subPath) { + return new UrlImpl(scheme, authority, path.isEmpty() ? subPath : (path + "/" + subPath), parameters); + } + @NotNull @Override public String getPath() { diff --git a/platform/platform-resources-en/src/messages/RefactoringBundle.properties b/platform/platform-resources-en/src/messages/RefactoringBundle.properties index 72c52ea2224a..0abad2fa561c 100644 --- a/platform/platform-resources-en/src/messages/RefactoringBundle.properties +++ b/platform/platform-resources-en/src/messages/RefactoringBundle.properties @@ -702,6 +702,7 @@ getter.method.found.for.the.field.0=Getter method found for the field {0}. \n{1} setter.method.found.for.the.field.0=Setter method found for the field {0}. \n{1} the setter as well? 0.has.1.usages.that.are.not.safe.to.delete={0} has {1,choice,1#1 usage that is|2#{1,number} usages that are} not safe to delete. safe.delete.of.those.0.in.comments.strings.non.code=Of those {0,choice,1#1 usage is|2#{0,number} usages are} in strings, comments, non-code files or generated code. +safe.delete.conflict.title=Potentially unsafe to delete references found in code type.cook.drop.obsolete.casts=&Drop obsolete casts type.cook.preserve.raw.arrays=Preserve raw &arrays type.cook.leave.object.parameterized.types.raw=&Leave Object-parameterized types raw diff --git a/platform/platform-resources-en/src/messages/VcsBundle.properties b/platform/platform-resources-en/src/messages/VcsBundle.properties index 486ac584b984..005f6aedfff3 100644 --- a/platform/platform-resources-en/src/messages/VcsBundle.properties +++ b/platform/platform-resources-en/src/messages/VcsBundle.properties @@ -560,7 +560,6 @@ vcs.quicklist.pupup.section.local.history=Local History options.background.processes.title=Background processes options.background.processes.enable=Enable background processes options.check.changed.on.server.title="Changed on server" conflicts -revision.changes.not.supported=Current VCS does not support viewing changes by revision line.annotation.aspect.author=Author line.annotation.aspect.date=Date line.annotation.aspect.revision=Revision diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index ebdea69a5879..acfae106a918 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -242,7 +242,6 @@ - diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/fileTypes/impl/FileTypesTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/fileTypes/impl/FileTypesTest.java index 578e2084f7fb..cd2d30010547 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/fileTypes/impl/FileTypesTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/fileTypes/impl/FileTypesTest.java @@ -586,7 +586,7 @@ public class FileTypesTest extends PlatformTestCase { file.putUserData(FileTypeManagerImpl.DETECTED_FROM_CONTENT_FILE_TYPE_KEY, null); ensureRedetected(file, detectorCalled); - assertTrue(file.getFileType().toString(), file.getFileType() == stuffType); + assertSame(file.getFileType().toString(), file.getFileType(), stuffType); log("T: ------"); } finally { 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 485701fb2277..1445241d4314 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 @@ -720,25 +720,6 @@ public class LocalFileSystemTest extends PlatformTestCase { assertWritable(file, vFile, true); } - public void testModCountIncreases() throws IOException { - File file = IoTestUtil.createTestFile("file.txt"); - VirtualFile vFile = myFS.refreshAndFindFileByIoFile(file); - assertNotNull(vFile); - assertWritable(file, vFile, true); - ManagingFS managingFS = ManagingFS.getInstance(); - final int globalModCount = managingFS.getFilesystemModificationCount(); - final int parentModCount = managingFS.getModificationCount(vFile.getParent()); - - ApplicationManager.getApplication().runWriteAction((ThrowableComputable)() -> { - vFile.setWritable(false); - return null; - }); - - assertEquals(globalModCount + 1, managingFS.getModificationCount(vFile)); - assertEquals(globalModCount + 1, managingFS.getFilesystemModificationCount()); - assertEquals(globalModCount + 1, managingFS.getModificationCount(vFile.getParent())); - } - private static void assertWritable(File file, VirtualFile vFile, boolean expected) { assertEquals(expected, file.canWrite()); assertEquals(expected, ObjectUtils.assertNotNull(FileSystemUtil.getAttributes(file)).isWritable()); 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 f349f8f8a313..e834f0983514 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,20 +15,29 @@ */ package com.intellij.openapi.vfs.local; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.ThrowableComputable; 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.FileAttribute; +import com.intellij.openapi.vfs.newvfs.ManagingFS; import com.intellij.openapi.vfs.newvfs.NewVirtualFile; +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.io.DataInputOutputUtil; import org.apache.log4j.Logger; import org.jetbrains.annotations.NotNull; +import java.io.DataOutputStream; import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; import java.util.jar.JarFile; public class PersistentFsTest extends PlatformTestCase { @@ -169,4 +178,78 @@ public class PersistentFsTest extends PlatformTestCase { assertEquals(1, logCount[0]); } + + public void testModCountIncreases() throws IOException { + VirtualFile vFile = setupFile(); + ManagingFS managingFS = ManagingFS.getInstance(); + int globalModCount = managingFS.getFilesystemModificationCount(); + final int parentModCount = managingFS.getModificationCount(vFile.getParent()); + + ApplicationManager.getApplication().runWriteAction((ThrowableComputable)() -> { + vFile.setWritable(false); + return null; + }); + + assertEquals(globalModCount + 1, managingFS.getModificationCount(vFile)); + assertEquals(globalModCount + 1, managingFS.getFilesystemModificationCount()); + assertEquals(parentModCount, managingFS.getModificationCount(vFile.getParent())); + + FSRecords.force(); + assertTrue(!FSRecords.isDirty()); + ++globalModCount; + + ApplicationManager.getApplication().runWriteAction((ThrowableComputable)() -> { + vFile.setWritable(true); // 1 + vFile.setBinaryContent("foo".getBytes(Charset.defaultCharset())); // 1 + timestamp + length + return null; + }); + + assertEquals(globalModCount + 4, managingFS.getModificationCount(vFile)); + assertEquals(globalModCount + 4, managingFS.getFilesystemModificationCount()); + assertEquals(parentModCount, managingFS.getModificationCount(vFile.getParent())); + } + + @NotNull + private static VirtualFile setupFile() throws IOException { + File file = IoTestUtil.createTestFile("file.txt"); + VirtualFile vFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file); + assertNotNull(vFile); + return vFile; + } + + public void testModCountNotIncreases() throws IOException { + VirtualFile vFile = setupFile(); + ManagingFS managingFS = ManagingFS.getInstance(); + final int globalModCount = managingFS.getFilesystemModificationCount(); + final int parentModCount = managingFS.getModificationCount(vFile.getParent()); + + FSRecords.force(); + assertTrue(!FSRecords.isDirty()); + + FileAttribute attribute = new FileAttribute("test.attribute", 1, true); + ApplicationManager.getApplication().runWriteAction((ThrowableComputable)() -> { + try(DataOutputStream output = attribute.writeAttribute(vFile)) { + DataInputOutputUtil.writeINT(output, 1); + } + return null; + }); + + assertEquals(globalModCount, managingFS.getModificationCount(vFile)); + assertEquals(globalModCount, managingFS.getFilesystemModificationCount()); + assertEquals(parentModCount, managingFS.getModificationCount(vFile.getParent())); + + assertTrue(FSRecords.isDirty()); + FSRecords.force(); + assertTrue(!FSRecords.isDirty()); + + // + int fileId = ((VirtualFileWithId)vFile).getId(); + FSRecords.setTimestamp(fileId, FSRecords.getTimestamp(fileId)); + FSRecords.setLength(fileId, FSRecords.getLength(fileId)); + + assertEquals(globalModCount, managingFS.getModificationCount(vFile)); + assertEquals(globalModCount, managingFS.getFilesystemModificationCount()); + assertEquals(parentModCount, managingFS.getModificationCount(vFile.getParent())); + assertTrue(!FSRecords.isDirty()); + } } diff --git a/platform/platform-tests/testSrc/com/intellij/util/containers/TreeTraverserTest.java b/platform/platform-tests/testSrc/com/intellij/util/containers/TreeTraverserTest.java index fe004304c77f..5d84ccc49f8d 100644 --- a/platform/platform-tests/testSrc/com/intellij/util/containers/TreeTraverserTest.java +++ b/platform/platform-tests/testSrc/com/intellij/util/containers/TreeTraverserTest.java @@ -172,14 +172,39 @@ public class TreeTraverserTest extends TestCase { assertEquals(new Integer(1), it.current()); } - public void testIteratorContractsCursor() { + public void testCursorIterableContract() { List list = ContainerUtil.newArrayList(); - for (JBIterator it : JBIterator.cursor(JBIterator.from(JBIterable.of(1, 2).iterator()))) { + JBIterable orig = JBIterable.generate(1, INCREMENT).take(5); + for (JBIterator it : JBIterator.cursor(JBIterator.from(orig.iterator()))) { it.current(); it.hasNext(); list.add(it.current()); } - assertEquals(Arrays.asList(1, 2), list); + assertEquals(orig.toList(), list); + } + + public void testCursorIteratorContract() { + JBIterable orig = JBIterable.generate(1, INCREMENT).take(5); + JBIterator> it = JBIterator.from(JBIterator.cursor( + JBIterator.from(orig.iterator())).iterator()); + List list = ContainerUtil.newArrayList(); + while (it.advance()) { + it.hasNext(); + list.add(it.current().current()); + } + assertEquals(orig.toList(), list); + } + + public void testCursorTransform() { + JBIterable orig = JBIterable.generate(1, INCREMENT).take(5); + + List expected = ContainerUtil.newArrayList(1, 2, 3, 4, 5); + List expectedOdd = ContainerUtil.newArrayList(1, 3, 5); + assertEquals(expected, JBIterator.cursor(JBIterator.from(orig.iterator())).transform(o -> o.current()).toList()); + assertEquals(expected.size(), JBIterator.cursor(JBIterator.from(orig.iterator())).last().current().intValue()); + assertEquals(expectedOdd, JBIterator.cursor(JBIterator.from(orig.iterator())).transform(o -> o.current()).filter(IS_ODD).toList()); + assertEquals(expectedOdd, JBIterator.cursor(JBIterator.from(orig.iterator())).filter(o -> IS_ODD.value(o.current())).transform(o -> o.current()).toList()); + assertEquals(expected.subList(0, 4), JBIterator.cursor(JBIterator.from(orig.iterator())).filter(o -> o.hasNext()).transform(o -> o.current()).toList()); } public void testIteratorContractsSkipAndStop() { diff --git a/platform/platform-tests/testSrc/com/intellij/util/messages/MessageBusTest.java b/platform/platform-tests/testSrc/com/intellij/util/messages/MessageBusTest.java index 5df08117ca5c..7750a16fdec2 100644 --- a/platform/platform-tests/testSrc/com/intellij/util/messages/MessageBusTest.java +++ b/platform/platform-tests/testSrc/com/intellij/util/messages/MessageBusTest.java @@ -46,6 +46,7 @@ public class MessageBusTest extends TestCase { private static final Topic TOPIC1 = new Topic<>("T1", T1Listener.class); private static final Topic TOPIC2 = new Topic<>("T2", T2Listener.class); + private static final Topic RUNNABLE_TOPIC = new Topic<>("runnableTopic", Runnable.class); private class T1Handler implements T1Listener { private final String id; @@ -312,4 +313,26 @@ public class MessageBusTest extends TestCase { assertEquals("events mismatch", joinExpected, joinActual); } + + public void testHasUndeliveredEvents() { + assertFalse(myBus.hasUndeliveredEvents(RUNNABLE_TOPIC)); + assertFalse(myBus.hasUndeliveredEvents(TOPIC2)); + + myBus.connect().subscribe(RUNNABLE_TOPIC, () -> { + assertTrue(myBus.hasUndeliveredEvents(RUNNABLE_TOPIC)); + assertFalse(myBus.hasUndeliveredEvents(TOPIC2)); + }); + myBus.connect().subscribe(RUNNABLE_TOPIC, () -> { + assertFalse(myBus.hasUndeliveredEvents(RUNNABLE_TOPIC)); + assertFalse(myBus.hasUndeliveredEvents(TOPIC2)); + }); + myBus.syncPublisher(RUNNABLE_TOPIC).run(); + } + + public void testHasUndeliveredEventsInChildBys() { + MessageBusImpl childBus = new MessageBusImpl(this, myBus); + myBus.connect().subscribe(RUNNABLE_TOPIC, () -> assertTrue(myBus.hasUndeliveredEvents(RUNNABLE_TOPIC))); + childBus.connect().subscribe(RUNNABLE_TOPIC, () -> assertFalse(myBus.hasUndeliveredEvents(RUNNABLE_TOPIC))); + myBus.syncPublisher(RUNNABLE_TOPIC).run(); + } } diff --git a/platform/projectModel-api/src/com/intellij/openapi/application/PathMacros.java b/platform/projectModel-api/src/com/intellij/openapi/application/PathMacros.java index fac4e5e9edb1..17681c6b270f 100644 --- a/platform/projectModel-api/src/com/intellij/openapi/application/PathMacros.java +++ b/platform/projectModel-api/src/com/intellij/openapi/application/PathMacros.java @@ -21,6 +21,14 @@ import org.jetbrains.annotations.NotNull; import java.util.Collection; import java.util.Set; +/** + * Stores predefined and custom (user-defined) path variables. Path variables are used to convert paths from absolute to portable form and + * vice versa. It allows us to reuse project configuration files on different machines. + *

    + * In order to make a path (or URL) portable the serialization subsystem replaces its prefix by name of a corresponding path variable. + * There are {@link #getSystemMacroNames() predefined path variables} and also it's possible to specify {@link #getUserMacroNames() custom path variables}. + *

    + */ public abstract class PathMacros { public static PathMacros getInstance() { return ServiceManager.getService(PathMacros.class); diff --git a/platform/projectModel-api/src/com/intellij/openapi/module/ModuleUtilCore.java b/platform/projectModel-api/src/com/intellij/openapi/module/ModuleUtilCore.java index f5c18dcab0aa..d3a82734d399 100644 --- a/platform/projectModel-api/src/com/intellij/openapi/module/ModuleUtilCore.java +++ b/platform/projectModel-api/src/com/intellij/openapi/module/ModuleUtilCore.java @@ -157,8 +157,10 @@ public class ModuleUtilCore { * @param result resulted set */ public static void collectModulesDependsOn(@NotNull final Module module, @NotNull Set result) { - if (result.contains(module)) return; - result.add(module); + if (!result.add(module)) { + return; + } + final ModuleManager moduleManager = ModuleManager.getInstance(module.getProject()); final List dependentModules = moduleManager.getModuleDependentModules(module); for (final Module dependentModule : dependentModules) { @@ -169,7 +171,8 @@ public class ModuleUtilCore { if (orderEntry.getModule() == module) { if (orderEntry.isExported()) { collectModulesDependsOn(dependentModule, result); - } else { + } + else { result.add(dependentModule); } break; diff --git a/platform/projectModel-api/src/com/intellij/openapi/roots/ProjectFileIndex.java b/platform/projectModel-api/src/com/intellij/openapi/roots/ProjectFileIndex.java index 369866a3fa57..0ffe24353db6 100644 --- a/platform/projectModel-api/src/com/intellij/openapi/roots/ProjectFileIndex.java +++ b/platform/projectModel-api/src/com/intellij/openapi/roots/ProjectFileIndex.java @@ -34,11 +34,19 @@ public interface ProjectFileIndex extends FileIndex { class SERVICE { private SERVICE() { } + /** + * @deprecated use {@link ProjectFileIndex#getInstance(Project)} instead + */ public static ProjectFileIndex getInstance(Project project) { - return ServiceManager.getService(project, ProjectFileIndex.class); + return ProjectFileIndex.getInstance(project); } } + @NotNull + static ProjectFileIndex getInstance(@NotNull Project project) { + return ServiceManager.getService(project, ProjectFileIndex.class); + } + /** * Returns module to which content the specified file belongs or null if the file does not belong to content of any module. */ diff --git a/platform/projectModel-impl/src/com/intellij/application/options/ReplacePathToMacroMap.java b/platform/projectModel-impl/src/com/intellij/application/options/ReplacePathToMacroMap.java index 7ff4ffcd249a..2e8533179e1d 100644 --- a/platform/projectModel-impl/src/com/intellij/application/options/ReplacePathToMacroMap.java +++ b/platform/projectModel-impl/src/com/intellij/application/options/ReplacePathToMacroMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * 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. @@ -19,7 +19,6 @@ import com.intellij.openapi.components.PathMacroMap; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.ArrayUtil; -import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.ContainerUtilRt; import gnu.trove.TObjectIntHashMap; @@ -28,7 +27,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; import java.util.Map; diff --git a/platform/projectModel-impl/src/com/intellij/openapi/components/PathMacroManager.java b/platform/projectModel-impl/src/com/intellij/openapi/components/PathMacroManager.java index 659f345fbd18..8be21446b96c 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/components/PathMacroManager.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/components/PathMacroManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * 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. @@ -15,20 +15,13 @@ */ package com.intellij.openapi.components; -import org.jdom.Element; import org.jetbrains.annotations.NotNull; public abstract class PathMacroManager implements PathMacroSubstitutor { - public static PathMacroManager getInstance(@NotNull ComponentManager componentManager) { - final PathMacroManager component = (PathMacroManager)componentManager.getPicoContainer().getComponentInstance(PathMacroManager.class); - assert component != null; - return component; - } - - public abstract void collapsePathsRecursively(@NotNull Element element); - @NotNull - public abstract String collapsePathsRecursively(@NotNull String text); + public static PathMacroManager getInstance(@NotNull ComponentManager componentManager) { + return (PathMacroManager)componentManager.getPicoContainer().getComponentInstance(PathMacroManager.class); + } @NotNull public abstract TrackingPathMacroSubstitutor createTrackingSubstitutor(); diff --git a/platform/projectModel-impl/src/com/intellij/openapi/components/PathMacroSubstitutor.java b/platform/projectModel-impl/src/com/intellij/openapi/components/PathMacroSubstitutor.java index bcffa0bc34ee..cbbedbb7947a 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/components/PathMacroSubstitutor.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/components/PathMacroSubstitutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * 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. @@ -17,14 +17,51 @@ package com.intellij.openapi.components; import org.jdom.Element; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +/** + * Provides methods to convert paths from absolute to portable form and vice versa. + * + * @see com.intellij.openapi.application.PathMacros + */ public interface PathMacroSubstitutor { + /** + * Convert path to absolute by replacing all names of path variables by its values + */ String expandPath(String path); - String collapsePath(@Nullable String path); + @NotNull + default String collapsePath(@NotNull String text) { + return collapsePath(text, false); + } + /** + * Convert paths inside {@code text} to portable form by replacing all values of path variables by their names. + * @param recursively if {@code true} all occurrences of paths inside {@code text} will be processed, otherwise {@code text} will be converted + * only if its entire content is a path (or URL) + */ + String collapsePath(@NotNull String text, boolean recursively); + + /** + * Process sub tags of {@code element} recursively and convert paths to absolute forms in all tag texts and attribute values. + */ void expandPaths(@NotNull Element element); - void collapsePaths(@NotNull Element element); + default void collapsePaths(@NotNull Element element) { + collapsePaths(element, false); + } + + default void collapsePathsRecursively(@NotNull Element element) { + collapsePaths(element, true); + } + + /** + * Process sub tags of {@code element} recursively and convert paths to portable forms in all tag texts and attribute values. + * @param recursively if {@code true} all occurrences of paths inside tag texts and attribute values will be processed, otherwise + * they will be converted only if their entire content is a path (or URL) + */ + void collapsePaths(@NotNull Element element, boolean recursively); + + default String collapsePathsRecursively(@NotNull String string) { + return collapsePath(string, true); + } } diff --git a/platform/projectModel-impl/src/com/intellij/openapi/components/impl/BasePathMacroManager.java b/platform/projectModel-impl/src/com/intellij/openapi/components/impl/BasePathMacroManager.java index 36d1a2ccfeff..58f18c39afc2 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/components/impl/BasePathMacroManager.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/components/impl/BasePathMacroManager.java @@ -131,20 +131,10 @@ public class BasePathMacroManager extends PathMacroManager { return getExpandMacroMap().substitute(path, SystemInfo.isFileSystemCaseSensitive); } - @Override - public String collapsePath(@Nullable String path) { - return getReplacePathMap().substitute(path, SystemInfo.isFileSystemCaseSensitive); - } - - @Override - public void collapsePathsRecursively(@NotNull final Element element) { - getReplacePathMap().substitute(element, SystemInfo.isFileSystemCaseSensitive, true); - } - @NotNull @Override - public String collapsePathsRecursively(@NotNull final String text) { - return getReplacePathMap().substituteRecursively(text, SystemInfo.isFileSystemCaseSensitive); + public String collapsePath(@NotNull final String text, boolean recursively) { + return getReplacePathMap().substitute(text, SystemInfo.isFileSystemCaseSensitive, recursively); } @Override @@ -153,8 +143,8 @@ public class BasePathMacroManager extends PathMacroManager { } @Override - public void collapsePaths(@NotNull final Element element) { - getReplacePathMap().substitute(element, SystemInfo.isFileSystemCaseSensitive); + public void collapsePaths(@NotNull final Element element, boolean recursively) { + getReplacePathMap().substitute(element, SystemInfo.isFileSystemCaseSensitive, recursively); } @NotNull @@ -185,8 +175,8 @@ public class BasePathMacroManager extends PathMacroManager { } @Override - public String collapsePath(@Nullable String path) { - return getReplacePathMap().substitute(path, SystemInfo.isFileSystemCaseSensitive); + public String collapsePath(@Nullable String path, boolean recursively) { + return getReplacePathMap().substitute(path, SystemInfo.isFileSystemCaseSensitive, recursively); } @Override @@ -195,8 +185,8 @@ public class BasePathMacroManager extends PathMacroManager { } @Override - public void collapsePaths(@NotNull final Element element) { - getReplacePathMap().substitute(element, SystemInfo.isFileSystemCaseSensitive, false, Holder.FILTER); + public void collapsePaths(@NotNull final Element element, boolean recursively) { + getReplacePathMap().substitute(element, SystemInfo.isFileSystemCaseSensitive, recursively, Holder.FILTER); } public int hashCode() { diff --git a/platform/projectModel-impl/src/com/intellij/util/jdom.kt b/platform/projectModel-impl/src/com/intellij/util/jdom.kt index 19f92aa75e38..ceebf94833ae 100644 --- a/platform/projectModel-impl/src/com/intellij/util/jdom.kt +++ b/platform/projectModel-impl/src/com/intellij/util/jdom.kt @@ -110,4 +110,11 @@ fun Element.toByteArray(): ByteArray { val out = BufferExposingByteArrayOutputStream(512) JDOMUtil.writeParent(this, out, "\n") return out.toByteArray() +} + +fun Element.addOptionTag(name: String, value: String) { + val element = Element("option") + element.setAttribute("name", name) + element.setAttribute("value", value) + addContent(element) } \ No newline at end of file diff --git a/platform/script-debugger/debugger-ui/src/LineBreakpointManager.kt b/platform/script-debugger/debugger-ui/src/LineBreakpointManager.kt index 86326877c779..d565e67c71ec 100644 --- a/platform/script-debugger/debugger-ui/src/LineBreakpointManager.kt +++ b/platform/script-debugger/debugger-ui/src/LineBreakpointManager.kt @@ -44,7 +44,7 @@ abstract class LineBreakpointManager(internal val debugProcess: DebugProcessImpl private val breakpointResolvedListenerAdded = AtomicBoolean() fun setBreakpoint(vm: Vm, breakpoint: XLineBreakpoint<*>) { - val target = synchronized (lock) { IDE_TO_VM_BREAKPOINTS_KEY.get(vm)?.let { it.get(breakpoint) } } + val target = synchronized (lock) { IDE_TO_VM_BREAKPOINTS_KEY.get(vm)?.get(breakpoint) } if (target == null) { setBreakpoint(vm, breakpoint, debugProcess.getLocationsForBreakpoint(vm, breakpoint)) } @@ -73,7 +73,7 @@ abstract class LineBreakpointManager(internal val debugProcess: DebugProcessImpl var vmBreakpoints: Collection = emptySet() synchronized (lock) { if (disable) { - val list = IDE_TO_VM_BREAKPOINTS_KEY.get(vm)?.let { it.get(breakpoint) } ?: return nullPromise() + val list = IDE_TO_VM_BREAKPOINTS_KEY.get(vm)?.get(breakpoint) ?: return nullPromise() val iterator = list.iterator() vmBreakpoints = list while (iterator.hasNext()) { @@ -85,7 +85,7 @@ abstract class LineBreakpointManager(internal val debugProcess: DebugProcessImpl } } else { - vmBreakpoints = IDE_TO_VM_BREAKPOINTS_KEY.get(vm)?.let { it.remove(breakpoint) } ?: return nullPromise() + vmBreakpoints = IDE_TO_VM_BREAKPOINTS_KEY.get(vm)?.remove(breakpoint) ?: return nullPromise() if (!vmBreakpoints.isEmpty()) { for (vmBreakpoint in vmBreakpoints) { vmToIdeBreakpoints.remove(vmBreakpoint, breakpoint) @@ -172,7 +172,7 @@ abstract class LineBreakpointManager(internal val debugProcess: DebugProcessImpl synchronized (lock) { vmToIdeBreakpoints.remove(breakpoint)?.let { for (ideBreakpoint in it) { - IDE_TO_VM_BREAKPOINTS_KEY.get(vm)?.let { it.remove(ideBreakpoint, breakpoint) } + IDE_TO_VM_BREAKPOINTS_KEY.get(vm)?.remove(ideBreakpoint, breakpoint) } it } @@ -213,12 +213,10 @@ abstract class LineBreakpointManager(internal val debugProcess: DebugProcessImpl protected abstract fun doRunToLocation(position: XSourcePosition): List - fun isRunToCursorBreakpoints(breakpoints: List) = synchronized (runToLocationBreakpoints) { runToLocationBreakpoints.containsAll(breakpoints) } - fun isRunToCursorBreakpoint(breakpoint: Breakpoint) = synchronized (runToLocationBreakpoints) { runToLocationBreakpoints.contains(breakpoint) } fun updateAllBreakpoints(vm: Vm) { - val array = synchronized (lock) { IDE_TO_VM_BREAKPOINTS_KEY.get(vm)?.let { it.keys.toTypedArray() } } ?: return + val array = synchronized (lock) { IDE_TO_VM_BREAKPOINTS_KEY.get(vm)?.keys?.toTypedArray() } ?: return for (breakpoint in array) { removeBreakpoint(vm, breakpoint, false) setBreakpoint(vm, breakpoint) @@ -227,7 +225,7 @@ abstract class LineBreakpointManager(internal val debugProcess: DebugProcessImpl fun removeAllBreakpoints(vm: Vm): Promise<*> { synchronized (lock) { - IDE_TO_VM_BREAKPOINTS_KEY.get(vm)?.let { it.clear() } + IDE_TO_VM_BREAKPOINTS_KEY.get(vm)?.clear() vmToIdeBreakpoints.clear() runToLocationBreakpoints.clear() } diff --git a/platform/script-debugger/debugger-ui/src/com/intellij/javascript/debugger/ExpressionInfoFactory.java b/platform/script-debugger/debugger-ui/src/com/intellij/javascript/debugger/ExpressionInfoFactory.java new file mode 100644 index 000000000000..66214935f12f --- /dev/null +++ b/platform/script-debugger/debugger-ui/src/com/intellij/javascript/debugger/ExpressionInfoFactory.java @@ -0,0 +1,31 @@ +/* + * 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.javascript.debugger; + +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.xdebugger.evaluation.ExpressionInfo; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface ExpressionInfoFactory { + @NotNull + ExpressionInfo create(@NotNull PsiElement element, @NotNull Document document); + + @Nullable + NameMapper createNameMapper(@NotNull VirtualFile file, @NotNull Document document); +} diff --git a/platform/script-debugger/debugger-ui/src/com/jetbrains/javascript/debugger/NameMapper.kt b/platform/script-debugger/debugger-ui/src/com/intellij/javascript/debugger/NameMapper.kt similarity index 99% rename from platform/script-debugger/debugger-ui/src/com/jetbrains/javascript/debugger/NameMapper.kt rename to platform/script-debugger/debugger-ui/src/com/intellij/javascript/debugger/NameMapper.kt index 27db8d89a31b..d98e3593e934 100644 --- a/platform/script-debugger/debugger-ui/src/com/jetbrains/javascript/debugger/NameMapper.kt +++ b/platform/script-debugger/debugger-ui/src/com/intellij/javascript/debugger/NameMapper.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.jetbrains.javascript.debugger +package com.intellij.javascript.debugger import com.google.common.base.CharMatcher import com.intellij.openapi.editor.Document diff --git a/platform/script-debugger/debugger-ui/src/com/jetbrains/javascript/debugger/ExpressionInfoFactory.java b/platform/script-debugger/debugger-ui/src/com/jetbrains/javascript/debugger/ExpressionInfoFactory.java deleted file mode 100644 index a5f9082bfaf6..000000000000 --- a/platform/script-debugger/debugger-ui/src/com/jetbrains/javascript/debugger/ExpressionInfoFactory.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.jetbrains.javascript.debugger; - -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiElement; -import com.intellij.xdebugger.evaluation.ExpressionInfo; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public interface ExpressionInfoFactory { - @NotNull - ExpressionInfo create(@NotNull PsiElement element, @NotNull Document document); - - @Nullable - NameMapper createNameMapper(@NotNull VirtualFile file, @NotNull Document document); -} diff --git a/platform/script-debugger/debugger-ui/src/com/jetbrains/javascript/debugger/JavaScriptDebugAware.java b/platform/script-debugger/debugger-ui/src/com/jetbrains/javascript/debugger/JavaScriptDebugAware.java index 798d3c2e59f4..21b092b213a4 100644 --- a/platform/script-debugger/debugger-ui/src/com/jetbrains/javascript/debugger/JavaScriptDebugAware.java +++ b/platform/script-debugger/debugger-ui/src/com/jetbrains/javascript/debugger/JavaScriptDebugAware.java @@ -15,6 +15,8 @@ */ package com.jetbrains.javascript.debugger; +import com.intellij.javascript.debugger.ExpressionInfoFactory; +import com.intellij.javascript.debugger.NameMapper; import com.intellij.openapi.editor.Document; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.fileTypes.FileType; 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 3622115f4420..ecfc5958a65f 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/GlobalMatchingVisitor.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/GlobalMatchingVisitor.java @@ -113,6 +113,7 @@ public class GlobalMatchingVisitor extends AbstractMatchingVisitor { * @param el2 the tree element for matching * @return true if equal and false otherwise */ + @Override public boolean match(final PsiElement el1, final PsiElement el2) { if (el1 == el2) return true; if (el1 == null) { @@ -174,28 +175,13 @@ public class GlobalMatchingVisitor extends AbstractMatchingVisitor { * @param nodes2 the tree element for matching * @return if they are equal and false otherwise */ + @Override public boolean matchSequentially(NodeIterator nodes, NodeIterator nodes2) { if (!nodes.hasNext()) { return !nodes2.hasNext(); } - return matchContext.getPattern().getHandler(nodes.current()).matchSequentially( - nodes, - nodes2, - matchContext - ); - } - - public static boolean continueMatchingSequentially(final NodeIterator nodes, final NodeIterator nodes2, MatchContext matchContext) { - if (!nodes.hasNext()) { - return !nodes2.hasNext(); - } - - return matchContext.getPattern().getHandler(nodes.current()).matchSequentially( - nodes, - nodes2, - matchContext - ); + return matchContext.getPattern().getHandler(nodes.current()).matchSequentially(nodes, nodes2, matchContext); } /** diff --git a/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralReplaceTest.java b/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralReplaceTest.java index eb2c53a60396..47db6d0d1ae0 100644 --- a/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralReplaceTest.java +++ b/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralReplaceTest.java @@ -911,7 +911,9 @@ public class StructuralReplaceTest extends StructuralReplaceTestCase { String s12 = "public class $a$ {\n" + " $Other$\n" + "}"; - String expectedResult4 = "/** @example */\n" + + String expectedResult4 = " /**\n" + + " * @example\n" + + " */\n" + " public class A {\n" + " class C {\n" + " }\n\n" + diff --git a/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralSearchTest.java b/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralSearchTest.java index 3cf9672b81ba..073c3c14909d 100644 --- a/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralSearchTest.java +++ b/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralSearchTest.java @@ -154,17 +154,6 @@ public class StructuralSearchTest extends StructuralSearchTestCase { " /** @serializable */ private int value2; " + "}" + "class G { /** @param a*/ void a() {} }"; - private static final String s57_2 = "/** @author Maxim */ class C { " + - "} " + - "class D {" + - "/** @serializable */ private int value; " + - "/** @since 1.4 */ void a() {} "+ - "}" + - "class F { " + - "/** @since 1.4 */ void a() {} "+ - "/** @serializable */ private int value2; " + - "}" + - "class G { /** @param a*/ void a() {} }"; private static final String s59 = "interface A { void B(); }"; @@ -1094,11 +1083,11 @@ public class StructuralSearchTest extends StructuralSearchTestCase { public void testSearchJavaDoc() { final String s58 = "/** @'T '_T2 */ class '_ { }"; - assertEquals("java doc comment in class", 1, findMatchesCount(s57,s58)); - assertEquals("java doc comment in class in file", 1, findMatchesCount(s57_2,s58,true)); + assertEquals("java doc comment in class in file", 1, findMatchesCount(s57,s58,true)); assertEquals("javadoc comment for field", 2, findMatchesCount(s57, "class '_ { /** @serializable '_* */ '_ '_; }")); assertEquals("javadoc comment for method", 2, findMatchesCount(s57, "class '_ { /** @'T 1.4 */ '_ '_() {} }")); + assertEquals("javadoc comment for method 2", 2, findMatchesCount(s57, "/** @'T 1.4 */ '_t '_m();")); assertEquals("just javadoc comment search", 4, findMatchesCount(s57, "/** @'T '_T2 */")); final String s84 = " /**\n" + @@ -1116,8 +1105,16 @@ public class StructuralSearchTest extends StructuralSearchTestCase { assertEquals("optional tag value match", 6, findMatchesCount(s57, "/** @'T '_T2? */")); assertEquals("multiple tags match +", 2, findMatchesCount(s75, " /** @'_tag+ '_value+ */")); assertEquals("multiple tags match *", 3, findMatchesCount(s75, " /** @'_tag* '_value* */")); - assertEquals("multiple tags match ?", 3, findMatchesCount(s75, " /** @'_tag? '_value? */ class 't {}")); - assertEquals("no infinite loop on javadoc matching", 1, findMatchesCount(s57, "/** 'Text */ class '_ { }")); + assertEquals("multiple tags match ?", 3, findMatchesCount(s75, " /** @'_tag? '_value? */ class 't {}", true)); + assertEquals("no infinite loop on javadoc matching", 1, findMatchesCount(s57, "/** 'Text */ class '_ { }", true)); + + final String source = "class outer {\n" + + " /** bla */\n" + + " class One {}\n" + + " class Two {}\n" + + "}"; + final String pattern = "class '_A { /** '_Text */class 'B {}}"; + assertEquals("match inner class with javadoc", 1, findMatchesCount(source, pattern)); } public void testNamedPatterns() { diff --git a/platform/lang-impl/src/com/intellij/ide/structureView/customRegions/CustomRegionStructureUtil.java b/platform/structure-view-impl/src/com/intellij/ide/structureView/customRegions/CustomRegionStructureUtil.java similarity index 86% rename from platform/lang-impl/src/com/intellij/ide/structureView/customRegions/CustomRegionStructureUtil.java rename to platform/structure-view-impl/src/com/intellij/ide/structureView/customRegions/CustomRegionStructureUtil.java index 9386040f21f1..20fd5f0b00ca 100644 --- a/platform/lang-impl/src/com/intellij/ide/structureView/customRegions/CustomRegionStructureUtil.java +++ b/platform/structure-view-impl/src/com/intellij/ide/structureView/customRegions/CustomRegionStructureUtil.java @@ -18,10 +18,7 @@ package com.intellij.ide.structureView.customRegions; import com.intellij.ide.structureView.StructureViewTreeElement; import com.intellij.lang.folding.CustomFoldingProvider; import com.intellij.openapi.util.TextRange; -import com.intellij.psi.PsiComment; -import com.intellij.psi.PsiElement; -import com.intellij.psi.StubBasedPsiElement; -import com.intellij.psi.SyntaxTraverser; +import com.intellij.psi.*; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -41,7 +38,7 @@ public class CustomRegionStructureUtil { } Set childrenRanges = ContainerUtil.map2SetNotNull(originalElements, element -> { Object value = element.getValue(); - return value instanceof PsiElement ? ((PsiElement)value).getTextRange() : null; + return value instanceof PsiElement ? getTextRange((PsiElement)value) : null; }); Collection customRegions = collectCustomRegions(rootElement, childrenRanges); if (customRegions.size() > 0) { @@ -63,6 +60,21 @@ public class CustomRegionStructureUtil { return originalElements; } + /* + * Fix cases when a line comment before an element (for example, method) gets inside it as a first child. + */ + private static TextRange getTextRange(@NotNull PsiElement element) { + PsiElement first = element.getFirstChild(); + if (first instanceof PsiComment && !first.textContains('\n')) { + PsiElement next = first.getNextSibling(); + if (next instanceof PsiWhiteSpace) next = next.getNextSibling(); + if (next != null) { + return new TextRange(next.getTextRange().getStartOffset(), element.getTextRange().getEndOffset()); + } + } + return element.getTextRange(); + } + private static Collection collectCustomRegions(@NotNull PsiElement rootElement, @NotNull Set ranges) { Iterator iterator = SyntaxTraverser.psiTraverser(rootElement) .regard(element -> isCustomRegionCommentCandidate(element) && !isInsideRanges(element, ranges)) @@ -112,7 +124,7 @@ public class CustomRegionStructureUtil { } return false; } - + private static boolean isCustomRegionCommentCandidate(@NotNull PsiElement element) { return element instanceof PsiComment && !element.textContains('\n'); } diff --git a/platform/lang-impl/src/com/intellij/ide/structureView/customRegions/CustomRegionTreeElement.java b/platform/structure-view-impl/src/com/intellij/ide/structureView/customRegions/CustomRegionTreeElement.java similarity index 97% rename from platform/lang-impl/src/com/intellij/ide/structureView/customRegions/CustomRegionTreeElement.java rename to platform/structure-view-impl/src/com/intellij/ide/structureView/customRegions/CustomRegionTreeElement.java index 17b9409005f0..f0024608e3f8 100644 --- a/platform/lang-impl/src/com/intellij/ide/structureView/customRegions/CustomRegionTreeElement.java +++ b/platform/structure-view-impl/src/com/intellij/ide/structureView/customRegions/CustomRegionTreeElement.java @@ -133,7 +133,7 @@ public class CustomRegionTreeElement implements StructureViewTreeElement { Object o = element.getValue(); if (o instanceof PsiElement) { TextRange elementRange = ((PsiElement)o).getTextRange(); - if(elementRange.getStartOffset() > myStartElement.getTextRange().getStartOffset() && elementRange.getEndOffset() < myEndOffset) { + if(elementRange.getStartOffset() >= myStartElement.getTextRange().getStartOffset() && elementRange.getEndOffset() <= myEndOffset) { return true; } } diff --git a/platform/structure-view-impl/src/com/intellij/ide/structureView/impl/common/PsiTreeElementBase.java b/platform/structure-view-impl/src/com/intellij/ide/structureView/impl/common/PsiTreeElementBase.java index 2bf56474a7c5..d0bd82ae2c66 100644 --- a/platform/structure-view-impl/src/com/intellij/ide/structureView/impl/common/PsiTreeElementBase.java +++ b/platform/structure-view-impl/src/com/intellij/ide/structureView/impl/common/PsiTreeElementBase.java @@ -18,6 +18,7 @@ package com.intellij.ide.structureView.impl.common; import com.intellij.ide.structureView.StructureViewExtension; import com.intellij.ide.structureView.StructureViewFactoryEx; import com.intellij.ide.structureView.StructureViewTreeElement; +import com.intellij.ide.structureView.customRegions.CustomRegionStructureUtil; import com.intellij.ide.util.treeView.NodeDescriptorProvidingKey; import com.intellij.navigation.ItemPresentation; import com.intellij.openapi.util.Iconable; @@ -102,7 +103,7 @@ public abstract class PsiTreeElementBase implements Struc if (element == null) return EMPTY_ARRAY; List result = new ArrayList<>(); Collection baseChildren = getChildrenBase(); - result.addAll(baseChildren); + result.addAll(CustomRegionStructureUtil.groupByCustomRegions(element, baseChildren)); StructureViewFactoryEx structureViewFactory = StructureViewFactoryEx.getInstanceEx(element.getProject()); Class aClass = element.getClass(); for (StructureViewExtension extension : structureViewFactory.getAllExtensions(aClass)) { diff --git a/platform/testFramework/src/com/intellij/testFramework/ExpectedHighlightingData.java b/platform/testFramework/src/com/intellij/testFramework/ExpectedHighlightingData.java index 59e8e695d5ea..18270e7b4c95 100644 --- a/platform/testFramework/src/com/intellij/testFramework/ExpectedHighlightingData.java +++ b/platform/testFramework/src/com/intellij/testFramework/ExpectedHighlightingData.java @@ -142,8 +142,6 @@ public class ExpectedHighlightingData { } registerHighlightingType(END_LINE_HIGHLIGHT_MARKER, new ExpectedHighlightingSet(HighlightSeverity.ERROR, true, true)); registerHighlightingType(END_LINE_WARNING_MARKER, new ExpectedHighlightingSet(HighlightSeverity.WARNING, true, false)); - - initAdditionalHighlightingTypes(); } public void init() { @@ -610,9 +608,4 @@ public class ExpectedHighlightingData { return String.format("(%d:%d..%d:%d)", startLine + 1, endLine + 1, startCol + 1, endCol + 1); } - /** @deprecated use {@link #registerHighlightingType(String, ExpectedHighlightingSet)} (to be removed in IDEA 17) */ - protected final Map highlightingTypes = myHighlightingTypes; - - /** @deprecated use {@link #registerHighlightingType(String, ExpectedHighlightingSet)} (to be removed in IDEA 17) */ - protected void initAdditionalHighlightingTypes() { } } \ No newline at end of file diff --git a/platform/testFramework/src/com/intellij/testFramework/LightPlatformLangTestCase.java b/platform/testFramework/src/com/intellij/testFramework/LightPlatformLangTestCase.java deleted file mode 100644 index 7044a9cf8f4b..000000000000 --- a/platform/testFramework/src/com/intellij/testFramework/LightPlatformLangTestCase.java +++ /dev/null @@ -1,21 +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.intellij.testFramework; - -/** @deprecated platform autodetection should work well enough, just use base class instead (to be removed in IDEA 16) */ -@SuppressWarnings("ALL") -public abstract class LightPlatformLangTestCase extends LightPlatformTestCase { -} diff --git a/platform/testFramework/src/com/intellij/testFramework/PlatformLangTestCase.java b/platform/testFramework/src/com/intellij/testFramework/PlatformLangTestCase.java deleted file mode 100644 index 7c8b387b5a9b..000000000000 --- a/platform/testFramework/src/com/intellij/testFramework/PlatformLangTestCase.java +++ /dev/null @@ -1,20 +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.intellij.testFramework; - -/** @deprecated platform autodetection should work well enough, just use base class instead (to be removed in IDEA 16) */ -public abstract class PlatformLangTestCase extends PlatformTestCase { -} diff --git a/platform/testFramework/src/com/intellij/testFramework/UsefulTestCase.java b/platform/testFramework/src/com/intellij/testFramework/UsefulTestCase.java index 4b733a9646b7..a83b4a56d885 100644 --- a/platform/testFramework/src/com/intellij/testFramework/UsefulTestCase.java +++ b/platform/testFramework/src/com/intellij/testFramework/UsefulTestCase.java @@ -741,16 +741,6 @@ public abstract class UsefulTestCase extends TestCase { return name == null ? "" : PlatformTestUtil.getTestName(name, lowercaseFirstLetter); } - /** @deprecated use {@link PlatformTestUtil#lowercaseFirstLetter(String, boolean)} (to be removed in IDEA 17) */ - public static String lowercaseFirstLetter(String name, boolean lowercaseFirstLetter) { - return PlatformTestUtil.lowercaseFirstLetter(name, lowercaseFirstLetter); - } - - /** @deprecated use {@link PlatformTestUtil#isAllUppercaseName(String)} (to be removed in IDEA 17) */ - public static boolean isAllUppercaseName(String name) { - return PlatformTestUtil.isAllUppercaseName(name); - } - protected String getTestDirectoryName() { final String testName = getTestName(true); return testName.replaceAll("_.*", ""); diff --git a/platform/testFramework/src/com/intellij/testFramework/fixtures/LightPlatformCodeInsightFixtureTestCase.java b/platform/testFramework/src/com/intellij/testFramework/fixtures/LightPlatformCodeInsightFixtureTestCase.java index 50119953fad1..d55e1259b248 100644 --- a/platform/testFramework/src/com/intellij/testFramework/fixtures/LightPlatformCodeInsightFixtureTestCase.java +++ b/platform/testFramework/src/com/intellij/testFramework/fixtures/LightPlatformCodeInsightFixtureTestCase.java @@ -42,9 +42,6 @@ public abstract class LightPlatformCodeInsightFixtureTestCase extends UsefulTest public LightPlatformCodeInsightFixtureTestCase() { } - /** @deprecated call {@link #LightPlatformCodeInsightFixtureTestCase()} instead (to be removed in IDEA 16) */ - protected LightPlatformCodeInsightFixtureTestCase(boolean autodetect) { } - protected CodeInsightTestFixture myFixture; protected Module myModule; diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index e18b56e2c4a9..d20cc0b91422 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -31,6 +31,7 @@ actionSystem.secondKeystrokeTimeout=2000 actionSystem.secondKeystrokeAutoPopupEnabled=false actionSystem.secondKeystrokePopupTimeout=500 actionSystem.win.suppressAlt=true +actionSystem.honor.modal.context=false actionSystem.win.suppressAlt.new=true actionSystem.win.suppressAlt.new.description=Enables another approach to consume Alt which may move focus to main menu (Windows L&F only). @@ -242,7 +243,7 @@ debugger.renderers.file.description=Enable file object renderer debugger.single.smart.step.force=true debugger.single.smart.step.force.description=Do force step into on single variant smart step into debugger.emulate.method.breakpoints=false -debugger.emulate.method.breakpoints.description=Emulate method breakpoints with line breakpoints +debugger.emulate.method.breakpoints.description=Emulate method breakpoints with line breakpoints by default debugger.intern.string.literals=false debugger.intern.string.literals.description=Make string literal refer to the same instance of class String @@ -613,9 +614,6 @@ focus.fix.lost.cursor.description=See IDEA-79312 suppress.focus.stealing=false suppress.focus.stealing.description=(Experimental) Trying to fight focus stealing -ide.inertial.mouse.fix=false -ide.inertial.mouse.fix.description=See IDEA-71508 - ide.mouse.popup.trigger.modifiers.disabled=true ide.mouse.popup.trigger.modifiers.disabled.description=Do not show a context popup if right button is pressed with modifiers diff --git a/platform/util/src/com/intellij/openapi/util/DefaultJDOMExternalizer.java b/platform/util/src/com/intellij/openapi/util/DefaultJDOMExternalizer.java index b6aa856e76c9..4ed7ece3a175 100644 --- a/platform/util/src/com/intellij/openapi/util/DefaultJDOMExternalizer.java +++ b/platform/util/src/com/intellij/openapi/util/DefaultJDOMExternalizer.java @@ -33,7 +33,7 @@ import java.util.List; * @deprecated {@link com.intellij.util.xmlb.XmlSerializer} should be used instead * @author mike */ -@SuppressWarnings({"HardCodedStringLiteral"}) +@SuppressWarnings("HardCodedStringLiteral") public class DefaultJDOMExternalizer { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.DefaultJDOMExternalizer"); @@ -138,7 +138,7 @@ public class DefaultJDOMExternalizer { } @Nullable - public static String filterXMLCharacters(String value) { + static String filterXMLCharacters(String value) { if (value != null) { StringBuilder builder = null; for (int i=0; i paths) { } - - public static void stopBackgroundThread() { } -} diff --git a/platform/util/src/com/intellij/openapi/util/text/CharSequenceWithStringHash.java b/platform/util/src/com/intellij/openapi/util/text/CharSequenceWithStringHash.java index b57fa1f76e05..b6def5219196 100644 --- a/platform/util/src/com/intellij/openapi/util/text/CharSequenceWithStringHash.java +++ b/platform/util/src/com/intellij/openapi/util/text/CharSequenceWithStringHash.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * 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. @@ -16,10 +16,11 @@ package com.intellij.openapi.util.text; /** - * This interface implementations must have hashCode values equal to those for String - * @see com.intellij.openapi.util.text.StringUtil#stringHashCode(CharSequence) + * This interface implementations must have {@code hashCode} values equal to those for String. + * + * @see StringUtil#stringHashCode(CharSequence) */ -public interface CharSequenceWithStringHash extends CharSequence{ +public interface CharSequenceWithStringHash extends CharSequence { @Override int hashCode(); -} +} \ No newline at end of file diff --git a/platform/util/src/com/intellij/util/Range.java b/platform/util/src/com/intellij/util/Range.java index 6c7b4c30e109..887149c3b9ab 100644 --- a/platform/util/src/com/intellij/util/Range.java +++ b/platform/util/src/com/intellij/util/Range.java @@ -20,8 +20,8 @@ import org.jetbrains.annotations.NotNull; public class Range> { - private final T myFrom; - private final T myTo; + @NotNull private final T myFrom; + @NotNull private final T myTo; public Range(@NotNull final T from, @NotNull final T to) { @@ -29,15 +29,16 @@ public class Range> { myTo = to; } - public boolean isWithin(T object) { + public boolean isWithin(@NotNull T object) { return object.compareTo(myFrom) >= 0 && object.compareTo(myTo) <= 0; } - + @NotNull public T getFrom() { return myFrom; } + @NotNull public T getTo() { return myTo; } @@ -46,4 +47,25 @@ public class Range> { public String toString() { return "(" + myFrom + "," + myTo + ")"; } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Range range = (Range)o; + + if (!myFrom.equals(range.myFrom)) return false; + if (!myTo.equals(range.myTo)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = myFrom.hashCode(); + result = 31 * result + myTo.hashCode(); + return result; + } } diff --git a/platform/util/src/com/intellij/util/concurrency/EdtExecutorService.java b/platform/util/src/com/intellij/util/concurrency/EdtExecutorService.java index f77bae0a5025..a24ab92f28ca 100644 --- a/platform/util/src/com/intellij/util/concurrency/EdtExecutorService.java +++ b/platform/util/src/com/intellij/util/concurrency/EdtExecutorService.java @@ -25,7 +25,8 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** - * {@link java.util.concurrent.ExecutorService} implementation which delegates tasks for execution to the {@link javax.swing.SwingUtilities#invokeLater(Runnable)} + * An {@link ExecutorService} implementation which + * delegates tasks to the {@link javax.swing.SwingUtilities#invokeLater(Runnable)} for execution. */ public class EdtExecutorService extends AbstractExecutorService { @NotNull diff --git a/platform/util/src/com/intellij/util/containers/JBIterable.java b/platform/util/src/com/intellij/util/containers/JBIterable.java index d772bb48f305..88c532a90440 100644 --- a/platform/util/src/com/intellij/util/containers/JBIterable.java +++ b/platform/util/src/com/intellij/util/containers/JBIterable.java @@ -210,7 +210,7 @@ public abstract class JBIterable implements Iterable { @NotNull @Override public String toString() { - return myIterable == this ? super.toString() : String.valueOf(myIterable); + return myIterable == this ? JBIterable.class.getSimpleName() : String.valueOf(myIterable); } /** diff --git a/platform/util/src/com/intellij/util/containers/JBIterator.java b/platform/util/src/com/intellij/util/containers/JBIterator.java index 73356e3d136e..eff41eead1ba 100644 --- a/platform/util/src/com/intellij/util/containers/JBIterator.java +++ b/platform/util/src/com/intellij/util/containers/JBIterator.java @@ -46,16 +46,13 @@ import java.util.NoSuchElementException; * * @author gregsh * - * @noinspection unchecked, AssignmentToForLoopParameter + * @noinspection unchecked, TypeParameterHidesVisibleType, AssignmentToForLoopParameter */ public abstract class JBIterator implements Iterator { - private static final Object NONE = new String("#none"); - private static final Object STOP = new String("#stop"); - private static final Object SKIP = new String("#skip"); @NotNull public static > JBIterable cursor(@NotNull E iterator) { - return JBIterable.generate(iterator, Functions.identity()).takeWhile(ADVANCE); + return JBIterable.generate(iterator, Functions.id()).intercept(CURSOR_NEXT); } @NotNull @@ -73,10 +70,11 @@ public abstract class JBIterator implements Iterator { }; } - private Object myCurrent = NONE; - private Object myNext = NONE; + private enum Do {INIT, STOP, SKIP} + private Object myCurrent = Do.INIT; + private Object myNext = Do.INIT; - private Op myFirstOp = new Op(null); + private Op myFirstOp = new NextOp(); private Op myLastOp = myFirstOp; /** @@ -94,7 +92,7 @@ public abstract class JBIterator implements Iterator { */ @Nullable protected final E stop() { - myNext = STOP; + myNext = Do.STOP; return null; } @@ -103,14 +101,14 @@ public abstract class JBIterator implements Iterator { */ @Nullable protected final E skip() { - myNext = SKIP; + myNext = Do.SKIP; return null; } @Override public final boolean hasNext() { peekNext(); - return myNext != STOP; + return myNext != Do.STOP; } @Override @@ -123,11 +121,14 @@ public abstract class JBIterator implements Iterator { * Proceeds to the next element if any and returns true; otherwise false. */ public final boolean advance() { - myCurrent = NONE; + myCurrent = Do.INIT; peekNext(); - if (myNext == STOP) return false; + if (myNext == Do.STOP) return false; myCurrent = myNext; - myNext = NONE; + myNext = Do.INIT; + if (myFirstOp instanceof JBIterator.CursorOp) { + ((CursorOp)myFirstOp).advance(myCurrent); + } currentChanged(); return true; } @@ -136,20 +137,22 @@ public abstract class JBIterator implements Iterator { * Returns the current element if any; otherwise throws exception. */ public final E current() { - if (myCurrent == NONE) throw new NoSuchElementException(); + if (myCurrent == Do.INIT) { + throw new NoSuchElementException(); + } return (E)myCurrent; } private void peekNext() { - if (myNext != NONE) return; - Object o = NONE; + if (myNext != Do.INIT) return; + Object o = Do.INIT; for (Op op = myFirstOp; op != null; op = op == null ? myFirstOp : op.nextOp) { - o = op.impl == null ? nextImpl() : op.apply(o); - if (myNext == SKIP) { - o = myNext = NONE; + o = op.apply(op.impl == null ? nextImpl() : o); + if (myNext == Do.STOP) return; + if (myNext == Do.SKIP) { + o = myNext = Do.INIT; op = null; } - if (myNext == STOP) return; } myNext = o; } @@ -167,7 +170,7 @@ public abstract class JBIterator implements Iterator { @NotNull public final JBIterator take(int count) { // add first so that the underlying iterator stay on 'count' position - return addOp(myLastOp.impl != null, new WhileOp(new CountDown(count))); + return addOp(!(myLastOp instanceof NextOp), new WhileOp(new CountDown(count))); } @NotNull @@ -187,7 +190,10 @@ public abstract class JBIterator implements Iterator { @NotNull private T addOp(boolean last, @NotNull Op op) { - if (last) { + if (op.impl == null) { + myFirstOp = myLastOp = op; + } + else if (last) { myLastOp.nextOp = op; myLastOp = myLastOp.nextOp; } @@ -210,8 +216,8 @@ public abstract class JBIterator implements Iterator { @Override public String toString() { - JBIterable ops = operationsImpl(); - return "{cur=" + myCurrent + "; next=" + myNext + (ops.isEmpty() ? "" : "; ops[" + ops.size() + "]=" + ops) + "}"; + List ops = operationsImpl().toList(); + return "{cur=" + myCurrent + "; next=" + myNext + (ops.size() < 2 ? "" : "; ops=" + ops) + "}"; } @NotNull @@ -226,7 +232,7 @@ public abstract class JBIterator implements Iterator { @NotNull private JBIterable operationsImpl() { - return JBIterable.generate(myFirstOp.nextOp, new Function() { + return JBIterable.generate(myFirstOp, new Function() { @Override public Op fun(Op op) { return op.nextOp; @@ -234,15 +240,20 @@ public abstract class JBIterator implements Iterator { }); } + @NotNull static String toShortString(@NotNull Object o) { - String fqn = o.getClass().getName(); - return StringUtil.replace(o.toString(), fqn, StringUtil.getShortName(fqn, '.')); + String name = o.getClass().getName(); + int idx = name.lastIndexOf('$'); + if (idx > 0 && idx < name.length() && StringUtil.isJavaIdentifierStart(name.charAt(idx + 1))) { + return name.substring(idx + 1); + } + return name.substring(name.lastIndexOf('.') + 1); } - private static final Condition> ADVANCE = new Condition>() { + private static final Function.Mono CURSOR_NEXT = new Function.Mono>() { @Override - public boolean value(JBIterator it) { - return it.advance(); + public JBIterator fun(JBIterator iterator) { + return iterator.addOp(false, iterator.new CursorOp()); } }; @@ -260,7 +271,7 @@ public abstract class JBIterator implements Iterator { @Override public String toString() { - return impl == null ? "" : toShortString(impl); + return toShortString(impl == null ? this : impl); } } @@ -283,7 +294,7 @@ public abstract class JBIterator implements Iterator { } @Override - public Object apply(Object o) { + Object apply(Object o) { return impl.fun((E)o); } } @@ -294,7 +305,7 @@ public abstract class JBIterator implements Iterator { } @Override - public Object apply(Object o) { + Object apply(Object o) { return impl.value((E)o) ? o : skip(); } } @@ -305,24 +316,54 @@ public abstract class JBIterator implements Iterator { super(condition); } @Override - public Object apply(Object o) { + Object apply(Object o) { return impl.value((E)o) ? o : stop(); } } private class SkipOp extends Op> { - boolean active; + boolean active = true; SkipOp(Condition condition) { super(condition); - active = true; } @Override - public Object apply(Object o) { + Object apply(Object o) { if (active && impl.value((E)o)) return skip(); active = false; return o; } } + + private static class NextOp extends Op { + NextOp() { + super(null); + } + + @Override + Object apply(Object o) { + return o; + } + } + + private class CursorOp extends Op { + boolean advanced; + + CursorOp() { + super(null); + } + + @Override + Object apply(Object o) { + JBIterator it = (JBIterator)o; + return ((advanced = nextOp != null) ? it.advance() : it.hasNext()) ? it : stop(); + } + + void advance(Object o) { + if (advanced || !(o instanceof JBIterator)) return; + ((JBIterator)o).advance(); + advanced = true; + } + } } diff --git a/platform/util/src/com/intellij/util/messages/MessageBus.java b/platform/util/src/com/intellij/util/messages/MessageBus.java index 2885d9623bbe..0f640f6128c3 100644 --- a/platform/util/src/com/intellij/util/messages/MessageBus.java +++ b/platform/util/src/com/intellij/util/messages/MessageBus.java @@ -140,4 +140,10 @@ public interface MessageBus { * {@link #connect(Disposable) connections}. */ void dispose(); + + /** + * @return true when events in the given topic are being dispatched in the current thread, + * and not all listeners have received the events yet. + */ + boolean hasUndeliveredEvents(@NotNull Topic topic); } \ No newline at end of file diff --git a/platform/util/src/com/intellij/util/messages/impl/MessageBusConnectionImpl.java b/platform/util/src/com/intellij/util/messages/impl/MessageBusConnectionImpl.java index ec6049868863..4f2fe46b7dc9 100644 --- a/platform/util/src/com/intellij/util/messages/impl/MessageBusConnectionImpl.java +++ b/platform/util/src/com/intellij/util/messages/impl/MessageBusConnectionImpl.java @@ -138,6 +138,15 @@ public class MessageBusConnectionImpl implements MessageBusConnection { myPendingMessages.get().offer(message); } + boolean containsMessage(@NotNull Topic topic) { + for (Message message : myPendingMessages.get()) { + if (message.getTopic() == topic) { + return true; + } + } + return false; + } + public String toString() { return mySubscriptions.toString(); } diff --git a/platform/util/src/com/intellij/util/messages/impl/MessageBusImpl.java b/platform/util/src/com/intellij/util/messages/impl/MessageBusImpl.java index e14d9b4d3c85..bb74d5a0413b 100644 --- a/platform/util/src/com/intellij/util/messages/impl/MessageBusImpl.java +++ b/platform/util/src/com/intellij/util/messages/impl/MessageBusImpl.java @@ -279,6 +279,22 @@ public class MessageBusImpl implements MessageBus { myDisposed = true; } + @Override + public boolean hasUndeliveredEvents(@NotNull Topic topic) { + if (!isDispatchingAnything()) return false; + + for (MessageBusConnectionImpl connection : getTopicSubscribers(topic)) { + if (connection.containsMessage(topic)) { + return true; + } + } + return false; + } + + private boolean isDispatchingAnything() { + return getRootBus().myWaitingBuses.get() != null; + } + private void checkNotDisposed() { if (myDisposed) { LOG.error("Already disposed: " + this); @@ -306,13 +322,7 @@ public class MessageBusImpl implements MessageBus { private void postMessage(Message message) { checkNotDisposed(); - final Topic topic = message.getTopic(); - List topicSubscribers = mySubscriberCache.get(topic); - if (topicSubscribers == null) { - topicSubscribers = new SmartList(); - calcSubscribers(topic, topicSubscribers); - mySubscriberCache.put(topic, topicSubscribers); - } + List topicSubscribers = getTopicSubscribers(message.getTopic()); if (!topicSubscribers.isEmpty()) { for (MessageBusConnectionImpl subscriber : topicSubscribers) { subscriber.getBus().myMessageQueue.get().offer(new DeliveryJob(subscriber, message)); @@ -322,6 +332,17 @@ public class MessageBusImpl implements MessageBus { } } + @NotNull + private List getTopicSubscribers(Topic topic) { + List topicSubscribers = mySubscriberCache.get(topic); + if (topicSubscribers == null) { + topicSubscribers = new SmartList(); + calcSubscribers(topic, topicSubscribers); + mySubscriberCache.put(topic, topicSubscribers); + } + return topicSubscribers; + } + private void notifyPendingJobChange(int delta) { ThreadLocal> ref = getRootBus().myWaitingBuses; SortedMap map = ref.get(); diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/AreaMap.java b/platform/vcs-api/src/com/intellij/openapi/vcs/AreaMap.java deleted file mode 100644 index 603ef6efed25..000000000000 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/AreaMap.java +++ /dev/null @@ -1,188 +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; - -import com.intellij.util.PairProcessor; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.*; - -/** - * @author irengrig - */ -public class AreaMap { - protected final List myKeys; - protected final Map myMap; - // [admittedly] parent, [-"-] child - protected final PairProcessor myKeysResemblance; - private final Comparator myComparator; - - public static, Val> AreaMap create(final PairProcessor keysResemblance) { - return new AreaMap<>(keysResemblance, new ComparableComparator<>()); - } - - public static AreaMap create(final PairProcessor keysResemblance, final Comparator comparator) { - return new AreaMap<>(keysResemblance, comparator); - } - - protected AreaMap(final PairProcessor keysResemblance, final Comparator comparator) { - myKeysResemblance = keysResemblance; - myComparator = comparator; - myKeys = new LinkedList<>(); - myMap = new HashMap<>(); - } - - public void putAll(final AreaMap other) { - myKeys.addAll(other.myKeys); - myMap.putAll(other.myMap); - } - - public void put(final Key key, final Val val) { - putImpl(key, val); - } - - protected int putIfNoParent(final Key key, final Val val) { - if(myMap.put(key, val) != null) { - return -1; - } - - if (myKeys.isEmpty()) { - myKeys.add(key); - return 0; - } - - final int idx = Collections.binarySearch(myKeys, key, myComparator); - if (idx < 0) { - // insertion, no copy exist - final int insertionIdx = - idx - 1; - // check parent - for (final ListIterator listIterator = myKeys.listIterator(insertionIdx); listIterator.hasPrevious();) { - final Key previous = listIterator.previous(); - if (myKeysResemblance.process(previous, key)) { - myMap.remove(key); - return -1; - } - } - // insertionIdx not necessarily exist - myKeys.add(insertionIdx, key); - return insertionIdx; - } - assert true; - myMap.remove(key); - return -1; - } - - protected int putImpl(final Key key, final Val val) { - myMap.put(key, val); - - if (myKeys.isEmpty()) { - myKeys.add(key); - return 0; - } - - final int idx = Collections.binarySearch(myKeys, key, myComparator); - if (idx < 0) { - // insertion, no copy exist - final int insertionIdx = - idx - 1; - // insertionIdx not necessarily exist - myKeys.add(insertionIdx, key); - return insertionIdx; - } - return idx; - } - - public Collection values() { - return Collections.unmodifiableCollection(myMap.values()); - } - - public Collection keySet() { - return Collections.unmodifiableCollection(myKeys); - } - - @Nullable - public Val getExact(final Key key) { - return myMap.get(key); - } - - public void removeByValue(@NotNull final Val val) { - for (Iterator iterator = myKeys.iterator(); iterator.hasNext();) { - final Key key = iterator.next(); - final Val current = myMap.get(key); - if (val.equals(current)) { - iterator.remove(); - myMap.remove(key); - return; - } - } - } - - public void remove(final Key key) { - myKeys.remove(key); - myMap.remove(key); - } - - public boolean contains(final Key key) { - return myKeys.contains(key); - } - - public void getSimiliar(final Key key, final PairProcessor consumer) { - final int idx = Collections.binarySearch(myKeys, key, myComparator); - if (idx < 0) { - final int insertionIdx = - idx - 1; - // take item before - final int itemBeforeIdx = insertionIdx - 1; - if (itemBeforeIdx >= 0) { - for (ListIterator iterator = myKeys.listIterator(itemBeforeIdx + 1); iterator.hasPrevious(); ) { - final Key candidate = iterator.previous(); - if (! myKeysResemblance.process(candidate, key)) continue; - if (consumer.process(candidate, myMap.get(candidate))) break; // if need only a part of keys - } - } - } else { - consumer.process(key, myMap.get(key)); - } - } - - public boolean isEmpty() { - return myKeys.isEmpty(); - } - - public void clear() { - myKeys.clear(); - myMap.clear(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - AreaMap areaMap = (AreaMap)o; - - if (!myKeys.equals(areaMap.myKeys)) return false; - if (!myMap.equals(areaMap.myMap)) return false; - - return true; - } - - @Override - public int hashCode() { - int result = myKeys.hashCode(); - result = 31 * result + myMap.hashCode(); - return result; - } -} diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/CalledInAny.java b/platform/vcs-api/src/com/intellij/openapi/vcs/CalledInAny.java deleted file mode 100644 index 18866912dd8c..000000000000 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/CalledInAny.java +++ /dev/null @@ -1,30 +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; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * @deprecated use {@link org.jetbrains.annotations.CalledInAny } - */ -@Deprecated -@Retention(RetentionPolicy.SOURCE) -@Target({ElementType.METHOD}) -public @interface CalledInAny { -} diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/CalledInAwt.java b/platform/vcs-api/src/com/intellij/openapi/vcs/CalledInAwt.java deleted file mode 100644 index 87f943507ded..000000000000 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/CalledInAwt.java +++ /dev/null @@ -1,34 +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; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.lang.annotation.ElementType; - -/** - * Used to indicate that a method - * would be called in AWT thread - * - * @deprecated use {@link org.jetbrains.annotations.CalledInAwt } - */ -@Deprecated -@Retention(RetentionPolicy.SOURCE) -@Target({ElementType.METHOD}) -public @interface CalledInAwt { -} diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/CalledInBackground.java b/platform/vcs-api/src/com/intellij/openapi/vcs/CalledInBackground.java deleted file mode 100644 index 6dcef5e1fbcf..000000000000 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/CalledInBackground.java +++ /dev/null @@ -1,33 +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; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.lang.annotation.ElementType; - -/** - * Used to indicate that a method (for instance, of version control plugin provider interface) - * would be called in background thread - * - * @deprecated use {@link org.jetbrains.annotations.CalledInBackground } - */ -@Deprecated -@Retention(RetentionPolicy.SOURCE) -@Target({ElementType.METHOD}) -public @interface CalledInBackground { -} diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/CollectionsMultiplier.java b/platform/vcs-api/src/com/intellij/openapi/vcs/CollectionsMultiplier.java deleted file mode 100644 index 5311161ff6d4..000000000000 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/CollectionsMultiplier.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2000-2011 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.intellij.openapi.vcs; - -import com.intellij.util.Consumer; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * @author irengrig - * Date: 2/2/11 - * Time: 10:11 AM - * Cartesian product - */ -public class CollectionsMultiplier { - private List> myInner; - - public void add(@Nullable final List list) { - if (list == null || list.isEmpty()) return; - if (myInner == null) { - myInner = Collections.singletonList(list); - return; - } - final List> copy = myInner; - myInner = new ArrayList<>(); - for (T t : list) { - for (List existing : copy) { - final ArrayList newList = new ArrayList<>(existing); - newList.add(t); - myInner.add(newList); - } - } - } - - public boolean isEmpty() { - return myInner == null; - } - - public void iterateResult(final Consumer> consumer) { - if (myInner == null) return; - for (List list : myInner) { - consumer.consume(list); - } - } -} diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/CompoundNumber.java b/platform/vcs-api/src/com/intellij/openapi/vcs/CompoundNumber.java deleted file mode 100644 index 7d3dd1568173..000000000000 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/CompoundNumber.java +++ /dev/null @@ -1,57 +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; - -/** -* @author irengrig -*/ -public class CompoundNumber { - private final int myMemberNumber; - private final int myIdx; - - public CompoundNumber(int memberNumber, int idx) { - myIdx = idx; - myMemberNumber = memberNumber; - } - - public int getIdx() { - return myIdx; - } - - public int getMemberNumber() { - return myMemberNumber; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - CompoundNumber that = (CompoundNumber)o; - - if (myIdx != that.myIdx) return false; - if (myMemberNumber != that.myMemberNumber) return false; - - return true; - } - - @Override - public int hashCode() { - int result = myMemberNumber; - result = 31 * result + myIdx; - return result; - } -} diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/Details.java b/platform/vcs-api/src/com/intellij/openapi/vcs/Details.java deleted file mode 100644 index 4d59c22b0d5d..000000000000 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/Details.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2000-2011 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.intellij.openapi.vcs; - -/** - * @author irengrig - * Date: 7/5/11 - * Time: 7:32 PM - */ -public interface Details { - void take(Id id, Data data) throws AlreadyDisposedException; - Id getCurrentlySelected(); - - class AlreadyDisposedException extends Exception {} -} diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/GenericDetailsLoader.java b/platform/vcs-api/src/com/intellij/openapi/vcs/GenericDetailsLoader.java deleted file mode 100644 index 611b8fa67218..000000000000 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/GenericDetailsLoader.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2000-2011 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.intellij.openapi.vcs; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.util.Comparing; -import com.intellij.util.Consumer; -import com.intellij.util.PairConsumer; -import org.jetbrains.annotations.CalledInAny; -import org.jetbrains.annotations.CalledInAwt; -import org.jetbrains.annotations.Nullable; - -import java.util.concurrent.atomic.AtomicReference; - -/** - * @author irengrig - * Date: 6/29/11 - * Time: 11:38 PM - */ -public class GenericDetailsLoader implements Details, Disposable { - private final Consumer myLoader; - private final ValueConsumer myValueConsumer; - private final AtomicReference myCurrentlySelected; - private boolean myIsDisposed; - - /** - * @param loader - is called in AWT. Should call {@link #take} with data when ready. Also in AWT - * @param valueConsumer - is called in AWT. passive, just benefits from details loading - */ - public GenericDetailsLoader(final Consumer loader, final PairConsumer valueConsumer) { - myLoader = loader; - myValueConsumer = new ValueConsumer<>(valueConsumer); - myCurrentlySelected = new AtomicReference<>(null); - } - - @CalledInAwt - public void updateSelection(@Nullable final Id id, boolean force) { - ApplicationManager.getApplication().assertIsDispatchThread(); - if (myIsDisposed) return; - myValueConsumer.setId(id); - - final Id wasId = myCurrentlySelected.getAndSet(id); - if (force || ! Comparing.equal(id, wasId)) { - myLoader.consume(id); - } - } - - public void setCacheConsumer(final PairConsumer cacheConsumer) { - myValueConsumer.setCacheConsumer(cacheConsumer); - } - - @CalledInAwt - @Override - public void take(Id id, Data data) throws AlreadyDisposedException { - ApplicationManager.getApplication().assertIsDispatchThread(); - if (myIsDisposed) throw new AlreadyDisposedException(); - myValueConsumer.consume(id, data); - } - - public void resetValueConsumer() { - myValueConsumer.reset(); - } - - @CalledInAny - @Override - public Id getCurrentlySelected() { - return myCurrentlySelected.get(); - } - - @Override - public void dispose() { - myIsDisposed = true; - } -} diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/Heavy.java b/platform/vcs-api/src/com/intellij/openapi/vcs/Heavy.java deleted file mode 100644 index 2ad229b236a6..000000000000 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/Heavy.java +++ /dev/null @@ -1,27 +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; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Deprecated -@Retention(RetentionPolicy.SOURCE) -@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) -public @interface Heavy { -} diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/IssueNavigationConfiguration.java b/platform/vcs-api/src/com/intellij/openapi/vcs/IssueNavigationConfiguration.java index b5458fc19353..99e492b5e378 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/IssueNavigationConfiguration.java +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/IssueNavigationConfiguration.java @@ -21,6 +21,7 @@ import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.SimpleModificationTracker; import com.intellij.openapi.util.TextRange; @@ -92,22 +93,27 @@ public class IssueNavigationConfiguration extends SimpleModificationTracker public List findIssueLinks(CharSequence text) { final List result = new ArrayList<>(); - for (IssueNavigationLink link : myLinks) { - Pattern issuePattern = link.getIssuePattern(); - Matcher m = issuePattern.matcher(text); - while (m.find()) { - try { - String replacement = issuePattern.matcher(m.group(0)).replaceFirst(link.getLinkRegexp()); - addMatch(result, new TextRange(m.start(), m.end()), replacement); - } - catch (Exception e) { - LOG.debug("Malformed regex replacement. IssueLink: " + link + "; text: " + text, e); + try { + for (IssueNavigationLink link : myLinks) { + Pattern issuePattern = link.getIssuePattern(); + Matcher m = issuePattern.matcher(text); + while (m.find()) { + try { + String replacement = issuePattern.matcher(m.group(0)).replaceFirst(link.getLinkRegexp()); + addMatch(result, new TextRange(m.start(), m.end()), replacement); + } + catch (Exception e) { + LOG.debug("Malformed regex replacement. IssueLink: " + link + "; text: " + text, e); + } } } + Matcher m = URLUtil.URL_PATTERN.matcher(text); + while (m.find()) { + addMatch(result, new TextRange(m.start(), m.end()), m.group()); + } } - Matcher m = URLUtil.URL_PATTERN.matcher(text); - while (m.find()) { - addMatch(result, new TextRange(m.start(), m.end()), m.group()); + catch (ProcessCanceledException e) { + //skip too long processing completely } Collections.sort(result); return result; diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/LineProcessEventListenerAdapter.java b/platform/vcs-api/src/com/intellij/openapi/vcs/LineProcessEventListenerAdapter.java deleted file mode 100644 index 16cbbf925d4d..000000000000 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/LineProcessEventListenerAdapter.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2000-2013 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; - -import com.intellij.openapi.util.Key; - -/** - * Created with IntelliJ IDEA. - * User: Irina.Chernushina - * Date: 2/25/13 - * Time: 5:47 PM - */ -public class LineProcessEventListenerAdapter implements LineProcessEventListener { - @Override - public void onLineAvailable(String line, Key outputType) { - } - - @Override - public void processTerminated(int exitCode) { - } - - @Override - public void startFailed(Throwable exception) { - } -} diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/MembershipMap.java b/platform/vcs-api/src/com/intellij/openapi/vcs/MembershipMap.java deleted file mode 100644 index 942707211285..000000000000 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/MembershipMap.java +++ /dev/null @@ -1,93 +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.openapi.vcs; - -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.Ref; -import com.intellij.util.PairProcessor; - -import java.util.Comparator; -import java.util.Iterator; -import java.util.ListIterator; - -/** - * @author irengrig - */ -public class MembershipMap extends AreaMap { - public static, Val> MembershipMap createMembershipMap(final PairProcessor keysResemblance) { - return new MembershipMap<>(keysResemblance, new ComparableComparator<>()); - } - - public static MembershipMap createMembershipMap(final PairProcessor keysResemblance, final Comparator comparator) { - return new MembershipMap<>(keysResemblance, comparator); - } - - private MembershipMap(final PairProcessor keysResemblance, final Comparator comparator) { - super(keysResemblance, comparator); - } - - public void putOptimal(final Key key, final Val val) { - final int idx = putIfNoParent(key, val); - if (idx < 0) return; - - if (idx + 1 < myKeys.size()) { - for (final ListIterator listIterator = myKeys.listIterator(idx + 1); listIterator.hasNext();) { - final Key next = listIterator.next(); - if (myKeysResemblance.process(key, next)) { - listIterator.remove(); - myMap.remove(next); - } else { - break; - } - } - } - } - - public void optimizeMap(final PairProcessor valuesAreas) { - int i = 0; - for (Iterator iterator = myKeys.iterator(); iterator.hasNext();) { - final Key key = iterator.next(); - final Val value = myMap.get(key); - - // go for parents - for (int j = i - 1; j >= 0; -- j) { - final Key innerKey = myKeys.get(j); - if (myKeysResemblance.process(innerKey, key)) { - if (valuesAreas.process(myMap.get(innerKey), value)) { - -- i; - iterator.remove(); - myMap.remove(key); - } - // otherwise we found a "parent", and do not remove the child - break; - } - } - ++ i; - } - } - - public Pair getMapping(final Key key) { - final Ref> result = new Ref<>(); - getSimiliar(key, new PairProcessor() { - @Override - public boolean process(final Key key, final Val val) { - result.set(Pair.create(key, val)); - return true; - } - }); - return result.get(); - } -} diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/ProjectLevelVcsManager.java b/platform/vcs-api/src/com/intellij/openapi/vcs/ProjectLevelVcsManager.java index df6bec75a2ef..fa413e64ef92 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/ProjectLevelVcsManager.java +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/ProjectLevelVcsManager.java @@ -272,11 +272,6 @@ public abstract class ProjectLevelVcsManager { public abstract CheckoutProvider.Listener getCompositeCheckoutListener(); - // TODO: To be removed in IDEA 16. - @Deprecated - @Nullable - public abstract VcsEventsListenerManager getVcsEventsListenerManager(); - public abstract VcsHistoryCache getVcsHistoryCache(); public abstract ContentRevisionCache getContentRevisionCache(); public abstract boolean isFileInContent(final VirtualFile vf); diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/RevisionChangesNotSupportedException.java b/platform/vcs-api/src/com/intellij/openapi/vcs/RevisionChangesNotSupportedException.java deleted file mode 100644 index 55a33570d2d3..000000000000 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/RevisionChangesNotSupportedException.java +++ /dev/null @@ -1,25 +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; - -/** - * @author Konstantin Bulenkov - */ -public class RevisionChangesNotSupportedException extends VcsException { - public RevisionChangesNotSupportedException() { - super(VcsBundle.message("revision.changes.not.supported")); - } -} diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/ThreadLocalDefendedInvoker.java b/platform/vcs-api/src/com/intellij/openapi/vcs/ThreadLocalDefendedInvoker.java deleted file mode 100644 index 9fef4917cd6c..000000000000 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/ThreadLocalDefendedInvoker.java +++ /dev/null @@ -1,46 +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; - -import com.intellij.openapi.project.Project; - -public abstract class ThreadLocalDefendedInvoker { - protected final static ThreadLocal myThreadLocal = new ThreadLocal() { - @Override - protected Boolean initialValue() { - return Boolean.TRUE; - } - }; - - protected abstract T execute(Project project); - - public static boolean isInside() { - return ! Boolean.TRUE.equals(myThreadLocal.get()); - } - - public static boolean isOutside() { - return ! isInside(); - } - - public T executeDefended(Project project) { - try { - myThreadLocal.set(Boolean.FALSE); - return execute(project); - } finally { - myThreadLocal.set(Boolean.TRUE); - } - } -} diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/ValueConsumer.java b/platform/vcs-api/src/com/intellij/openapi/vcs/ValueConsumer.java deleted file mode 100644 index 83c9c9b67ad7..000000000000 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/ValueConsumer.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2000-2011 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.intellij.openapi.vcs; - -import com.intellij.util.PairConsumer; - -/** - * @author irengrig - * Date: 6/29/11 - * Time: 11:51 PM - */ -public class ValueConsumer { - private Id myId; - private Id mySetId; - private final PairConsumer myConsumer; - private PairConsumer myCacheConsumer; - - protected ValueConsumer(PairConsumer consumer) { - myConsumer = consumer; - } - - public void setCacheConsumer(PairConsumer cacheConsumer) { - myCacheConsumer = cacheConsumer; - } - - public void consume(final Id id, final Data data) { - if (id.equals(mySetId) || ! id.equals(myId)) { - if (myCacheConsumer != null) { - myCacheConsumer.consume(id, data); - } - return; - } - mySetId = id; - myConsumer.consume(id, data); - } - - public void setId(Id id) { - myId = id; - mySetId = null; - } - - public void reset() { - mySetId = null; - } -} diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/VcsDataConstants.java b/platform/vcs-api/src/com/intellij/openapi/vcs/VcsDataConstants.java deleted file mode 100644 index 7de8abf6fe14..000000000000 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/VcsDataConstants.java +++ /dev/null @@ -1,31 +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; - -/** - * @deprecated use {@link com.intellij.openapi.vcs.VcsDataKeys} instead - */ -@SuppressWarnings({"UnusedDeclaration"}) -public interface VcsDataConstants { - String IO_FILE_ARRAY = VcsDataKeys.IO_FILE_ARRAY.getName(); - String IO_FILE = VcsDataKeys.IO_FILE.getName(); - String VCS_FILE_REVISION = VcsDataKeys.VCS_FILE_REVISION.getName(); - String VCS_FILE_REVISIONS = VcsDataKeys.VCS_FILE_REVISIONS.getName(); - String VCS_VIRTUAL_FILE = VcsDataKeys.VCS_VIRTUAL_FILE.getName(); - String FILE_PATH = VcsDataKeys.FILE_PATH.getName(); - String FILE_PATH_ARRAY = VcsDataKeys.FILE_PATH_ARRAY.getName(); - String FILE_HISTORY_PANEL = VcsDataKeys.FILE_HISTORY_PANEL.getName(); -} diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/VcsEventsListenerManager.java b/platform/vcs-api/src/com/intellij/openapi/vcs/VcsEventsListenerManager.java deleted file mode 100644 index 9f26db671cef..000000000000 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/VcsEventsListenerManager.java +++ /dev/null @@ -1,39 +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.intellij.openapi.vcs; - -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.vcs.checkin.CheckinEnvironment; -import com.intellij.openapi.vcs.rollback.RollbackEnvironment; -import com.intellij.openapi.vcs.update.UpdateEnvironment; -import com.intellij.util.Consumer; - -/** - * TODO: To be removed in IDEA 16. - * @author irengrig - * Date: 12/15/10 - * Time: 5:42 PM - */ -@Deprecated -public interface VcsEventsListenerManager { - void removeCheckin(final Object key); - void removeUpdate(final Object key); - void removeRollback(final Object key); - - Object addCheckin(Consumer>> consumer); - Object addUpdate(Consumer>> consumer); - Object addRollback(Consumer>> consumer); -} diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/changes/SortByVcsRoots.java b/platform/vcs-api/src/com/intellij/openapi/vcs/changes/SortByVcsRoots.java deleted file mode 100644 index 83c30ce71bce..000000000000 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/changes/SortByVcsRoots.java +++ /dev/null @@ -1,51 +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.changes; - -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.ProjectLevelVcsManager; -import com.intellij.openapi.vcs.VcsRoot; -import com.intellij.util.containers.Convertor; -import com.intellij.util.containers.MultiMap; - -import java.util.Collection; - -public class SortByVcsRoots { - private final Project myProject; - private final Convertor myConvertor; - private ProjectLevelVcsManager myVcsManager; - public static final VcsRoot ourFictiveValue = new VcsRoot(null, null); - - public SortByVcsRoots(Project project, final Convertor convertor) { - myProject = project; - myVcsManager = ProjectLevelVcsManager.getInstance(project); - myConvertor = convertor; - } - - public MultiMap sort(final Collection in) { - final MultiMap result = new MultiMap<>(); - for (T t : in) { - final VcsRoot root = myVcsManager.getVcsRootObjectFor(myConvertor.convert(t)); - if (root != null) { - result.putValue(root, t); - } else { - result.putValue(ourFictiveValue, t); - } - } - return result; - } -} diff --git a/platform/vcs-api/src/com/intellij/util/ProducerConsumer.java b/platform/vcs-api/src/com/intellij/util/ProducerConsumer.java deleted file mode 100644 index c433a4fc7e48..000000000000 --- a/platform/vcs-api/src/com/intellij/util/ProducerConsumer.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2000-2011 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.intellij.util; - -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.util.concurrency.Semaphore; - -import java.util.ArrayDeque; - -/** - * @author irengrig - * Date: 7/5/11 - * Time: 1:48 AM - */ -public class ProducerConsumer { - public static final int ourDefaultMaxSize = 20; - - private final ArrayDeque myQueue; - private final Consumer myConsumer; - private final int myMaxSize; - private final Object myLock; - private final ConsumerRunnable myConsumerThread; - private boolean myIsAlive; - - public ProducerConsumer(final Consumer consumer) { - this(consumer, ourDefaultMaxSize); - } - - public void start() { - myIsAlive = true; - myConsumerThread.start(); - } - - public void stop() { - synchronized (myLock) { - myIsAlive = false; - myLock.notifyAll(); - } - } - - public ProducerConsumer(final Consumer consumer, final int maxSize) { - this(consumer, maxSize, false); - } - - public ProducerConsumer(final Consumer consumer, final int maxSize, final boolean onPooledThread) { - myConsumer = consumer; - myQueue = new ArrayDeque<>(); - myMaxSize = maxSize; - myLock = new Object(); - - if (onPooledThread) { - myConsumerThread = new PooledConsumerRunnable(); - ApplicationManager.getApplication().executeOnPooledThread(myConsumerThread); - } else { - myConsumerThread = new ConsumerRunnable(); - } - } - - private class PooledConsumerRunnable extends ConsumerRunnable { - private final Semaphore mySemaphore; - - private PooledConsumerRunnable() { - mySemaphore = new Semaphore(); - mySemaphore.down(); - } - - public void start() { - mySemaphore.up(); - } - - @Override - protected void waitForStart() { - mySemaphore.waitFor(); - } - } - - private class ConsumerRunnable implements Runnable { - private Thread myThread; - - public void start() { - myThread.start(); - } - - public void setThread(Thread thread) { - myThread = thread; - } - - @Override - public void run() { - waitForStart(); - synchronized (myLock) { - while (myIsAlive) { - if (! myQueue.isEmpty()) { - myConsumer.consume(myQueue.removeFirst()); - } else { - try { - myLock.wait(10); - } - catch (InterruptedException e) { - // - } - } - } - } - } - - protected void waitForStart() { - } - } - - public void produce(final T t) { - synchronized (myLock) { - while (myQueue.size() >= myMaxSize) { - try { - myLock.notifyAll(); - myLock.wait(10); - } - catch (InterruptedException e) { - // - } - } - myQueue.addLast(t); - myLock.notifyAll(); - } - } -} diff --git a/platform/vcs-api/src/com/intellij/vcsUtil/AbstractActionStateConsumer.java b/platform/vcs-api/src/com/intellij/vcsUtil/AbstractActionStateConsumer.java deleted file mode 100644 index b2e635a44cb0..000000000000 --- a/platform/vcs-api/src/com/intellij/vcsUtil/AbstractActionStateConsumer.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2000-2012 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.vcsUtil; - -/** - * Created with IntelliJ IDEA. - * User: Irina.Chernushina - * Date: 7/6/12 - * Time: 7:41 PM - */ -public abstract class AbstractActionStateConsumer implements ActionStateConsumer { - protected boolean myVisible; - protected boolean myEnabled; - - protected AbstractActionStateConsumer() { - myVisible = true; - myEnabled = true; - } - - @Override - public void hide() { - myVisible = false; - myEnabled = false; - } - - @Override - public void disable() { - myVisible = true; - myEnabled = false; - } - - @Override - public void enable() { - myVisible = true; - myEnabled = true; - } -} diff --git a/platform/vcs-api/src/com/intellij/vcsUtil/ActionUpdateHelper.java b/platform/vcs-api/src/com/intellij/vcsUtil/ActionUpdateHelper.java deleted file mode 100644 index f878524ba6fb..000000000000 --- a/platform/vcs-api/src/com/intellij/vcsUtil/ActionUpdateHelper.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2000-2012 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.vcsUtil; - -import com.intellij.openapi.actionSystem.AnActionEvent; - -/** - * Created with IntelliJ IDEA. - * User: Irina.Chernushina - * Date: 7/6/12 - * Time: 7:43 PM - */ -public class ActionUpdateHelper extends AbstractActionStateConsumer { - public void apply(final AnActionEvent e) { - e.getPresentation().setVisible(myVisible); - e.getPresentation().setEnabled(myEnabled); - } -} diff --git a/platform/vcs-api/src/com/intellij/vcsUtil/UIVcsUtil.java b/platform/vcs-api/src/com/intellij/vcsUtil/UIVcsUtil.java index c4856811e3f0..00f1887f1d1b 100644 --- a/platform/vcs-api/src/com/intellij/vcsUtil/UIVcsUtil.java +++ b/platform/vcs-api/src/com/intellij/vcsUtil/UIVcsUtil.java @@ -27,6 +27,7 @@ public class UIVcsUtil { private UIVcsUtil() { } + @SuppressWarnings("unused") // Required for compatibility with external plugins. public static JPanel errorPanel(final String text, boolean isError) { final JLabel label = new JLabel(XmlStringUtil.wrapInHtml(escapeXmlAndAddBr(text))); label.setForeground(isError ? SimpleTextAttributes.ERROR_ATTRIBUTES.getFgColor() : UIUtil.getInactiveTextColor()); diff --git a/platform/vcs-api/src/com/intellij/vcsUtil/VcsUtil.java b/platform/vcs-api/src/com/intellij/vcsUtil/VcsUtil.java index f606bf50eeca..4d678b5c3724 100644 --- a/platform/vcs-api/src/com/intellij/vcsUtil/VcsUtil.java +++ b/platform/vcs-api/src/com/intellij/vcsUtil/VcsUtil.java @@ -18,8 +18,7 @@ package com.intellij.vcsUtil; import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileEditor.FileDocumentManager; @@ -28,7 +27,6 @@ import com.intellij.openapi.fileTypes.FileTypes; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; @@ -41,6 +39,7 @@ import com.intellij.openapi.vcs.roots.VcsRootDetector; import com.intellij.openapi.vfs.*; import com.intellij.openapi.vfs.newvfs.RefreshQueue; import com.intellij.openapi.wm.StatusBar; +import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtilRt; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -50,6 +49,10 @@ import java.io.File; import java.io.IOException; import java.util.*; +import static com.intellij.openapi.application.ApplicationManager.getApplication; +import static com.intellij.util.ObjectUtils.notNull; +import static java.util.stream.Collectors.groupingBy; + @SuppressWarnings({"UtilityClassWithoutPrivateConstructor"}) public class VcsUtil { protected static final char[] ourCharsToBeChopped = new char[]{'/', '\\'}; @@ -58,6 +61,8 @@ public class VcsUtil { public final static String MAX_VCS_LOADED_SIZE_KB = "idea.max.vcs.loaded.size.kb"; private static final int ourMaxLoadedFileSize = computeLoadedFileSize(); + @NotNull private static final VcsRoot FICTIVE_ROOT = new VcsRoot(null, null); + public static int getMaxVcsLoadedFileSize() { return ourMaxLoadedFileSize; } @@ -133,86 +138,46 @@ public class VcsUtil { } @Nullable - public static AbstractVcs getVcsFor(@NotNull final Project project, final FilePath file) { - final AbstractVcs[] vcss = new AbstractVcs[1]; - ApplicationManager.getApplication().runReadAction(new Runnable() { - @Override - public void run() { - // IDEADEV-17916, when e.g. ContentRevision.getContent is called in - // a future task after the component has been disposed. - if (!project.isDisposed()) { - ProjectLevelVcsManager mgr = ProjectLevelVcsManager.getInstance(project); - vcss[0] = (mgr != null) ? mgr.getVcsFor(file) : null; - } - } - }); - return vcss[0]; + public static AbstractVcs getVcsFor(@NotNull Project project, FilePath filePath) { + return computeValue(project, manager -> manager.getVcsFor(filePath)); } @Nullable - public static AbstractVcs getVcsFor(final Project project, @NotNull final VirtualFile file) { - final AbstractVcs[] vcss = new AbstractVcs[1]; - - ApplicationManager.getApplication().runReadAction(new Runnable() { - @Override - public void run() { - // IDEADEV-17916, when e.g. ContentRevision.getContent is called in - // a future task after the component has been disposed. - if( !project.isDisposed() ) - { - ProjectLevelVcsManager mgr = ProjectLevelVcsManager.getInstance( project ); - vcss[ 0 ] = (mgr != null) ? mgr.getVcsFor(file) : null; - } - } - }); - return vcss[0]; + public static AbstractVcs getVcsFor(@NotNull Project project, @NotNull VirtualFile file) { + return computeValue(project, manager -> manager.getVcsFor(file)); } @Nullable - public static VirtualFile getVcsRootFor(final Project project, final FilePath file) { - final VirtualFile[] roots = new VirtualFile[1]; - - ApplicationManager.getApplication().runReadAction(new Runnable() { - @Override - public void run() { - // IDEADEV-17916, when e.g. ContentRevision.getContent is called in - // a future task after the component has been disposed. - if( !project.isDisposed() ) - { - ProjectLevelVcsManager mgr = ProjectLevelVcsManager.getInstance( project ); - roots[ 0 ] = (mgr != null) ? mgr.getVcsRootFor( file ) : null; - } - } - }); - return roots[0]; + public static VirtualFile getVcsRootFor(@NotNull Project project, FilePath filePath) { + return computeValue(project, manager -> manager.getVcsRootFor(filePath)); } @Nullable - public static VirtualFile getVcsRootFor(final Project project, final VirtualFile file) { - final VirtualFile[] roots = new VirtualFile[1]; + public static VirtualFile getVcsRootFor(@NotNull Project project, @Nullable VirtualFile file) { + return computeValue(project, manager -> manager.getVcsRootFor(file)); + } - ApplicationManager.getApplication().runReadAction(new Runnable() { - @Override - public void run() { - // IDEADEV-17916, when e.g. ContentRevision.getContent is called in - // a future task after the component has been disposed. - if( !project.isDisposed() ) - { - ProjectLevelVcsManager mgr = ProjectLevelVcsManager.getInstance( project ); - roots[ 0 ] = (mgr != null) ? mgr.getVcsRootFor( file ) : null; - } + @Nullable + private static T computeValue(@NotNull Project project, @NotNull Function provider) { + return ReadAction.compute(() -> { + // IDEADEV-17916, when e.g. ContentRevision.getContent is called in + // a future task after the component has been disposed. + T result = null; + if (!project.isDisposed()) { + ProjectLevelVcsManager manager = ProjectLevelVcsManager.getInstance(project); + result = manager != null ? provider.fun(manager) : null; } + return result; }); - return roots[0]; } public static void refreshFiles(final FilePath[] roots, final Runnable runnable) { - ApplicationManager.getApplication().assertIsDispatchThread(); + getApplication().assertIsDispatchThread(); refreshFiles(collectFilesToRefresh(roots), runnable); } public static void refreshFiles(final File[] roots, final Runnable runnable) { - ApplicationManager.getApplication().assertIsDispatchThread(); + getApplication().assertIsDispatchThread(); refreshFiles(collectFilesToRefresh(roots), runnable); } @@ -254,25 +219,13 @@ public class VcsUtil { } @Nullable - public static VirtualFile getVirtualFile(final String path) { - return ApplicationManager.getApplication().runReadAction(new Computable() { - @Override - @Nullable - public VirtualFile compute() { - return LocalFileSystem.getInstance().findFileByPath(path.replace(File.separatorChar, '/')); - } - }); + public static VirtualFile getVirtualFile(@NotNull String path) { + return ReadAction.compute(() -> LocalFileSystem.getInstance().findFileByPath(path.replace(File.separatorChar, '/'))); } @Nullable - public static VirtualFile getVirtualFile(final File file) { - return ApplicationManager.getApplication().runReadAction(new Computable() { - @Override - @Nullable - public VirtualFile compute() { - return LocalFileSystem.getInstance().findFileByIoFile(file); - } - }); + public static VirtualFile getVirtualFile(@NotNull File file) { + return ReadAction.compute(() -> LocalFileSystem.getInstance().findFileByIoFile(file)); } @Nullable @@ -286,14 +239,11 @@ public class VcsUtil { return result; } - public static String getFileContent(final String path) { - return ApplicationManager.getApplication().runReadAction(new Computable() { - @Override - public String compute() { - VirtualFile vFile = getVirtualFile(path); - assert vFile != null; - return FileDocumentManager.getInstance().getDocument(vFile).getText(); - } + public static String getFileContent(@NotNull String path) { + return ReadAction.compute(() -> { + VirtualFile vFile = getVirtualFile(path); + assert vFile != null; + return FileDocumentManager.getInstance().getDocument(vFile).getText(); }); } @@ -352,13 +302,10 @@ public class VcsUtil { * @param project Current project component * @param message information message */ - public static void showStatusMessage(final Project project, final String message) { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - if (project.isOpen()) { - StatusBar.Info.set(message, project); - } + public static void showStatusMessage(@NotNull Project project, @Nullable String message) { + SwingUtilities.invokeLater(() -> { + if (project.isOpen()) { + StatusBar.Info.set(message, project); } }); } @@ -428,12 +375,7 @@ public class VcsUtil { } private static FilePath[] sortPaths(FilePath[] files, final int sign) { - Arrays.sort(files, new Comparator() { - @Override - public int compare(@NotNull FilePath o1, @NotNull FilePath o2) { - return sign * o1.getPath().compareTo(o2.getPath()); - } - }); + Arrays.sort(files, (file1, file2) -> sign * file1.getPath().compareTo(file2.getPath())); return files; } @@ -494,15 +436,12 @@ public class VcsUtil { public static boolean runVcsProcessWithProgress(final VcsRunnable runnable, String progressTitle, boolean canBeCanceled, Project project) throws VcsException { final Ref ex = new Ref<>(); - boolean result = ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { - @Override - public void run() { - try { - runnable.run(); - } - catch (VcsException e) { - ex.set(e); - } + boolean result = ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> { + try { + runnable.run(); + } + catch (VcsException e) { + ex.set(e); } }, progressTitle, canBeCanceled, project); if (!ex.isNull()) { @@ -511,24 +450,15 @@ public class VcsUtil { return result; } - public static VirtualFile waitForTheFile(final String path) { - final VirtualFile[] file = new VirtualFile[1]; - final Application app = ApplicationManager.getApplication(); - Runnable action = new Runnable() { - @Override - public void run() { - app.runWriteAction(new Runnable() { - @Override - public void run() { - file[0] = LocalFileSystem.getInstance().refreshAndFindFileByPath(path); - } - }); - } - }; + @Nullable + public static VirtualFile waitForTheFile(@NotNull String path) { + Ref result = Ref.create(); - app.invokeAndWait(action); + getApplication().invokeAndWait( + () -> getApplication().runWriteAction( + () -> result.set(LocalFileSystem.getInstance().refreshAndFindFileByPath(path)))); - return file[0]; + return result.get(); } public static String getCanonicalLocalPath(String localPath) { @@ -624,6 +554,15 @@ public class VcsUtil { return file.getName() + " (" + file.getParent() + ")"; } + @NotNull + public static Map> groupByRoots(@NotNull Project project, + @NotNull Collection items, + @NotNull Function filePathMapper) { + ProjectLevelVcsManager manager = ProjectLevelVcsManager.getInstance(project); + + return items.stream().collect(groupingBy(item -> notNull(manager.getVcsRootObjectFor(filePathMapper.fun(item)), FICTIVE_ROOT))); + } + @NotNull public static Collection findRoots(@NotNull VirtualFile rootDir, @NotNull Project project) throws IllegalArgumentException { diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/IdeaTextPatchBuilder.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/IdeaTextPatchBuilder.java index 066b3c3e1fb9..51ba7caf94a5 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/IdeaTextPatchBuilder.java +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/IdeaTextPatchBuilder.java @@ -22,19 +22,22 @@ import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.VcsOutgoingChangesProvider; import com.intellij.openapi.vcs.VcsRoot; -import com.intellij.openapi.vcs.changes.*; +import com.intellij.openapi.vcs.changes.BinaryContentRevision; +import com.intellij.openapi.vcs.changes.ByteBackedContentRevision; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ContentRevision; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.BeforeAfter; -import com.intellij.util.containers.Convertor; -import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.List; +import java.util.*; + +import static com.intellij.openapi.vcs.changes.ChangesUtil.getAfterPath; +import static com.intellij.openapi.vcs.changes.ChangesUtil.getBeforePath; +import static com.intellij.util.ObjectUtils.chooseNotNull; +import static com.intellij.vcsUtil.VcsUtil.groupByRoots; public class IdeaTextPatchBuilder { private IdeaTextPatchBuilder() { @@ -42,14 +45,8 @@ public class IdeaTextPatchBuilder { public static List> revisionsConvertor(final Project project, final List changes) throws VcsException { final List> result = new ArrayList<>(changes.size()); - - final Convertor beforePrefferingConvertor = new Convertor() { - public FilePath convert(Change o) { - final FilePath before = ChangesUtil.getBeforePath(o); - return before == null ? ChangesUtil.getAfterPath(o) : before; - } - }; - final MultiMap byRoots = new SortByVcsRoots<>(project, beforePrefferingConvertor).sort(changes); + Map> byRoots = + groupByRoots(project, changes, change -> chooseNotNull(getBeforePath(change), getAfterPath(change))); for (VcsRoot root : byRoots.keySet()) { final Collection rootChanges = byRoots.get(root); diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/TriggerAdditionOrDeletion.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/TriggerAdditionOrDeletion.java index 4a56ee36af1e..ce275bf47ddf 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/TriggerAdditionOrDeletion.java +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/formove/TriggerAdditionOrDeletion.java @@ -20,7 +20,6 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.*; -import com.intellij.openapi.vcs.changes.SortByVcsRoots; import com.intellij.openapi.vcs.checkin.CheckinEnvironment; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtilCore; @@ -29,12 +28,14 @@ import com.intellij.util.FilePathByPathComparator; import com.intellij.util.ObjectUtils; import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.Convertor; import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NotNull; import java.util.*; +import static com.intellij.util.Functions.identity; +import static com.intellij.vcsUtil.VcsUtil.groupByRoots; + public class TriggerAdditionOrDeletion { private final Collection myExisting; private final Collection myDeleted; @@ -71,13 +72,11 @@ public class TriggerAdditionOrDeletion { public void prepare() { if (myExisting.isEmpty() && myDeleted.isEmpty()) return; - final SortByVcsRoots sortByVcsRoots = new SortByVcsRoots<>(myProject, new Convertor.IntoSelf<>()); - if (! myExisting.isEmpty()) { - processAddition(sortByVcsRoots); + processAddition(); } if (! myDeleted.isEmpty()) { - processDeletion(sortByVcsRoots); + processDeletion(); } } @@ -147,8 +146,9 @@ public class TriggerAdditionOrDeletion { return myAffected; } - private void processDeletion(SortByVcsRoots sortByVcsRoots) { - final MultiMap map = sortByVcsRoots.sort(myDeleted); + private void processDeletion() { + Map> map = groupByRoots(myProject, myDeleted, identity()); + myPreparedDeletion = new MultiMap<>(); for (VcsRoot vcsRoot : map.keySet()) { if (vcsRoot != null && vcsRoot.getVcs() != null) { @@ -175,8 +175,9 @@ public class TriggerAdditionOrDeletion { } } - private void processAddition(SortByVcsRoots sortByVcsRoots) { - final MultiMap map = sortByVcsRoots.sort(myExisting); + private void processAddition() { + Map> map = groupByRoots(myProject, myExisting, identity()); + myPreparedAddition = new MultiMap<>(); for (VcsRoot vcsRoot : map.keySet()) { if (vcsRoot != null && vcsRoot.getVcs() != null) { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/AbstractRefreshablePanel.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/AbstractRefreshablePanel.java deleted file mode 100644 index 615b07b1c906..000000000000 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/AbstractRefreshablePanel.java +++ /dev/null @@ -1,211 +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.openapi.vcs.changes; - -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.BackgroundTaskQueue; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.MessageType; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.vcs.Details; -import com.intellij.openapi.vcs.GenericDetailsLoader; -import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier; -import com.intellij.util.continuation.ModalityIgnorantBackgroundableTask; -import org.jetbrains.annotations.CalledInAwt; -import org.jetbrains.annotations.CalledInBackground; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -/** - * For presentation, which is itself in GenericDetails (not necessarily) - shown from time to time, but cached, and - * which is a listener to some intensive changes (a group of invalidating changes should provoke a reload, but "outdated" - * (loaded but already not actual) results should be thrown away) - * - * @author Irina.Chernushina - * @since 7.09.2011 - */ -public abstract class AbstractRefreshablePanel implements RefreshablePanel { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.AbstractRefreshablePanel"); - - private final Ticket myTicket; - private final DetailsPanel myDetailsPanel; - private final GenericDetailsLoader myDetailsLoader; - private final BackgroundTaskQueue myQueue; - private volatile boolean myDisposed; - - protected AbstractRefreshablePanel(final Project project, final String loadingTitle, final BackgroundTaskQueue queue) { - myQueue = queue; - myTicket = new Ticket(); - myDetailsPanel = new DetailsPanel(); - myDetailsPanel.loading(); - myDetailsPanel.layout(); - - myDetailsLoader = new GenericDetailsLoader<>(ticket -> { - final Loader loader = new Loader(project, loadingTitle, myTicket.copy()); - loader.runSteadily(backgroundable -> myQueue.run(backgroundable)); - }, (ticket, t) -> acceptData(t)); - } - - @Override - public boolean refreshDataSynch() { - return false; - } - - @CalledInAwt - @Override - public void dataChanged() { - ApplicationManager.getApplication().assertIsDispatchThread(); - myTicket.increment(); - } - - @CalledInAwt - @Override - public void refresh() { - ApplicationManager.getApplication().assertIsDispatchThread(); - - if (! Comparing.equal(myDetailsLoader.getCurrentlySelected(), myTicket)) { - final Ticket copy = myTicket.copy(); - myDetailsLoader.updateSelection(copy, false); - myDetailsPanel.loading(); - myDetailsPanel.layout(); - } else { - refreshPresentation(); - } - } - - protected abstract void refreshPresentation(); - - @CalledInBackground - protected abstract T loadImpl() throws VcsException; - @CalledInAwt - protected abstract JPanel dataToPresentation(final T t); - protected abstract void disposeImpl(); - - @CalledInAwt - private void acceptData(final T t) { - final JPanel panel = dataToPresentation(t); - myDetailsPanel.data(panel); - myDetailsPanel.layout(); - } - - @Override - public JPanel getPanel() { - return myDetailsPanel.getPanel(); - } - - @Override - public boolean isStillValid(Change data) { - return true; - } - - private class Loader extends ModalityIgnorantBackgroundableTask { - private final Ticket myTicketCopy; - private T myT; - - private Loader(@Nullable Project project, @NotNull String title, final Ticket ticketCopy) { - super(project, title, false); - myTicketCopy = ticketCopy; - } - - @Override - protected void doInAwtIfFail(Exception e) { - final Exception cause; - if (e instanceof RuntimeException && e.getCause() != null) { - cause = (Exception) e.getCause(); - } else { - cause = e; - } - LOG.info(e); - String message = cause.getMessage() == null ? e.getMessage() : cause.getMessage(); - message = message == null ? "Unknown error" : message; - VcsBalloonProblemNotifier.showOverChangesView(myProject, message, MessageType.ERROR); - } - - @Override - protected void doInAwtIfCancel() { - } - - @Override - protected void doInAwtIfSuccess() { - if (myDisposed) return; - try { - myDetailsLoader.take(myTicketCopy, myT); - } - catch (Details.AlreadyDisposedException e) { - // t is not disposable - } - } - - @Override - protected void runImpl(@NotNull ProgressIndicator indicator) { - if (myDisposed) return; - try { - myT = loadImpl(); - } - catch (VcsException e) { - throw new RuntimeException(e); - } - } - } - - @Override - public void dispose() { - myDisposed = true; - disposeImpl(); - } - - private static class Ticket { - private int myId; - - public Ticket() { - myId = 0; - } - - public Ticket(int id) { - myId = id; - } - - public Ticket copy() { - return new Ticket(myId); - } - - public void increment() { - ++ myId; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Ticket ticket = (Ticket)o; - - if (myId != ticket.myId) return false; - - return true; - } - - @Override - public int hashCode() { - return myId; - } - } -} \ No newline at end of file diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/DetailsPanel.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/DetailsPanel.java deleted file mode 100644 index 0c41d231298a..000000000000 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/DetailsPanel.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2000-2011 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.intellij.openapi.vcs.changes; - -import com.intellij.vcsUtil.UIVcsUtil; - -import javax.swing.*; -import java.awt.*; - -/** -* Created by IntelliJ IDEA. -* User: Irina.Chernushina -* Date: 9/7/11 -* Time: 2:43 PM -*/ -public class DetailsPanel { - private CardLayout myLayout; - private JPanel myPanel; - private Layer myCurrentLayer; - - public DetailsPanel() { - myPanel = new JPanel(); - myLayout = new CardLayout(); - myPanel.setLayout(myLayout); - JPanel dataPanel = new JPanel(new BorderLayout()); - - myPanel.add(UIVcsUtil.errorPanel("No details available", false), Layer.notAvailable.name()); - myPanel.add(UIVcsUtil.errorPanel("Nothing selected", false), Layer.nothingSelected.name()); - myPanel.add(UIVcsUtil.errorPanel("Changes content is not loaded yet", false), Layer.notLoadedInitial.name()); - myPanel.add(UIVcsUtil.errorPanel("Loading...", false), Layer.loading.name()); - myPanel.add(dataPanel, Layer.data.name()); - } - - public void nothingSelected() { - myCurrentLayer = Layer.nothingSelected; - } - - public void notAvailable() { - myCurrentLayer = Layer.notAvailable; - } - - public void loading() { - myCurrentLayer = Layer.loading; - } - - public void loadingInitial() { - myCurrentLayer = Layer.notLoadedInitial; - } - - public void data(final JPanel panel) { - myCurrentLayer = Layer.data; - myPanel.add(panel, Layer.data.name()); - } - - public void layout() { - myLayout.show(myPanel, myCurrentLayer.name()); - } - - public void clear() { - myPanel.removeAll(); - } - - private static enum Layer { - notAvailable, - nothingSelected, - notLoadedInitial, - loading, - data, - } - - public JPanel getPanel() { - return myPanel; - } -} diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/UnversionedViewDialog.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/UnversionedViewDialog.java index 987cc6f72152..1f8080835df0 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/UnversionedViewDialog.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/UnversionedViewDialog.java @@ -19,6 +19,7 @@ import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.actionSystem.ex.AnActionListener; import com.intellij.openapi.actionSystem.impl.PresentationFactory; import com.intellij.openapi.actionSystem.impl.Utils; +import com.intellij.openapi.application.impl.LaterInvocator; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.changes.ui.ChangesListView; import com.intellij.openapi.vfs.VirtualFile; @@ -78,7 +79,7 @@ public class UnversionedViewDialog extends SpecificFilesViewDialog { ActionManager manager = ActionManager.getInstance(); List actions = ContainerUtil.newArrayList(); - Utils.expandActionGroup(getUnversionedActionGroup(), actions, new PresentationFactory(), dataContext, "", manager); + Utils.expandActionGroup(LaterInvocator.isInModalContext(), getUnversionedActionGroup(), actions, new PresentationFactory(), dataContext, "", manager); for (AnAction action : actions) { action.registerCustomShortcutSet(action.getShortcutSet(), component); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/AbstractMissingFilesAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/AbstractMissingFilesAction.java index 69cbd76807e3..b2fe04eeed88 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/AbstractMissingFilesAction.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/AbstractMissingFilesAction.java @@ -27,8 +27,6 @@ import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.AbstractVcs; import com.intellij.openapi.vcs.AbstractVcsHelper; @@ -79,12 +77,7 @@ public abstract class AbstractMissingFilesAction extends AnAction implements Dum if (synchronously()) { action.run(); } else { - progressManager.runProcessWithProgressSynchronously(new Runnable() { - @Override - public void run() { - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, action); - } - }, getName(), true, project); + progressManager.runProcessWithProgressSynchronously(action, getName(), true, project); } } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/DeleteUnversionedFilesAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/DeleteUnversionedFilesAction.java index e44bea6bee4f..f8e20782a708 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/DeleteUnversionedFilesAction.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/DeleteUnversionedFilesAction.java @@ -26,8 +26,6 @@ import com.intellij.ide.DeleteProvider; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.PlatformDataKeys; import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.util.IconUtil; public class DeleteUnversionedFilesAction extends DumbAwareAction { @@ -36,14 +34,9 @@ public class DeleteUnversionedFilesAction extends DumbAwareAction { } public void actionPerformed(final AnActionEvent e) { - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, new Runnable() { - @Override - public void run() { - DeleteProvider deleteProvider = e.getData(PlatformDataKeys.DELETE_ELEMENT_PROVIDER); - if (deleteProvider == null) return; - deleteProvider.deleteElement(e.getDataContext()); - } - }); + DeleteProvider deleteProvider = e.getData(PlatformDataKeys.DELETE_ELEMENT_PROVIDER); + if (deleteProvider == null) return; + deleteProvider.deleteElement(e.getDataContext()); } @Override diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/diff/ChangeDiffRequestProducer.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/diff/ChangeDiffRequestProducer.java index 74ff182f3e92..3e12b05ca98d 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/diff/ChangeDiffRequestProducer.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/diff/ChangeDiffRequestProducer.java @@ -90,6 +90,12 @@ public class ChangeDiffRequestProducer implements DiffRequestProducer { } public static boolean isEquals(@NotNull Change change1, @NotNull Change change2) { + if (!Comparing.equal(ChangesUtil.getBeforePath(change1), ChangesUtil.getBeforePath(change2)) || + !Comparing.equal(ChangesUtil.getAfterPath(change1), ChangesUtil.getAfterPath(change2))) { + // we use Change.hashCode(), so removing this check might violate comparison contract + return false; + } + for (ChangeDiffViewerWrapperProvider provider : ChangeDiffViewerWrapperProvider.EP_NAME.getExtensions()) { ThreeState equals = provider.isEquals(change1, change2); if (equals == ThreeState.NO) return false; diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/ShelveChangesManager.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/ShelveChangesManager.java index dc9449616b4c..23e57b8e68e7 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/ShelveChangesManager.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/ShelveChangesManager.java @@ -35,8 +35,6 @@ import com.intellij.openapi.options.SchemeManagerFactory; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.*; import com.intellij.openapi.util.io.FileUtil; @@ -651,7 +649,7 @@ public class ShelveChangesManager extends AbstractProjectComponent implements JD @CalledInAwt public void shelveSilentlyUnderProgress(@NotNull List changes) { final boolean completed = ProgressManager.getInstance().runProcessWithProgressSynchronously( - () -> DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, () -> shelveChangesInSeparatedLists(changes)), + () -> shelveChangesInSeparatedLists(changes), VcsBundle.getString("shelve.changes.progress.title"), true, myProject); if (completed) { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitChangeListDialog.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitChangeListDialog.java index 7613749fa73f..e4b1fe1c5ef1 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitChangeListDialog.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitChangeListDialog.java @@ -27,8 +27,6 @@ import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.*; import com.intellij.openapi.util.Comparing; @@ -688,12 +686,7 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj new Runnable() { @Override public void run() { - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, new Runnable() { - @Override - public void run() { - session.execute(getIncludedChanges(), getCommitMessage()); - } - }); + session.execute(getIncludedChanges(), getCommitMessage()); } }, commitExecutor.getActionText(), true, getProject()); @@ -1052,15 +1045,6 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj protected RefreshablePanel createDetails() { final JPanel panel = JBUI.Panels.simplePanel(myDiffDetails.getComponent()); return new RefreshablePanel() { - @Override - public boolean refreshDataSynch() { - return false; - } - - @Override - public void dataChanged() { - } - @Override public void refresh() { } @@ -1070,15 +1054,6 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj return panel; } - @Override - public void away() { - } - - @Override - public boolean isStillValid(Object o) { - return false; - } - @Override public void dispose() { } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/RollbackWorker.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/RollbackWorker.java index f92e119ce2f2..bc1fda02a575 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/RollbackWorker.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/RollbackWorker.java @@ -21,8 +21,6 @@ import com.intellij.lifecycle.PeriodicalTasksCloser; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.progress.*; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.io.FileUtil; @@ -102,7 +100,7 @@ public class RollbackWorker { ProgressManager.getInstance().run(new Task.Modal(myProject, myOperationName, true) { @Override public void run(@NotNull ProgressIndicator indicator) { - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, rollbackAction); + rollbackAction.run(); } }); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/SelectFilesDialog.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/SelectFilesDialog.java index 4e4411b47aab..c7045d726132 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/SelectFilesDialog.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/SelectFilesDialog.java @@ -21,8 +21,6 @@ import com.google.common.collect.Collections2; import com.intellij.ide.DeleteProvider; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.fileChooser.actions.VirtualFileDeleteProvider; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.VcsShowConfirmationOption; import com.intellij.openapi.vcs.changes.actions.DeleteUnversionedFilesAction; @@ -95,16 +93,6 @@ public class SelectFilesDialog extends AbstractSelectFilesDialog { return defaultGroup; } - @Override - public void show() { - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, new Runnable() { - @Override - public void run() { - SelectFilesDialog.super.show(); - } - }); - } - public static class VirtualFileList extends ChangesTreeList { @Nullable private final DeleteProvider myDeleteProvider; diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/checkout/CheckoutAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/checkout/CheckoutAction.java index 224cacbc79e0..2408c4293d5e 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/checkout/CheckoutAction.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/checkout/CheckoutAction.java @@ -31,14 +31,9 @@ public class CheckoutAction extends AnAction implements DumbAware { } public void actionPerformed(final AnActionEvent e) { - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, new Runnable() { - @Override - public void run() { - Project project = e.getData(CommonDataKeys.PROJECT); - project = (project == null) ? ProjectManager.getInstance().getDefaultProject() : project; - myProvider.doCheckout(project, getListener(project)); - } - }); + Project project = e.getData(CommonDataKeys.PROJECT); + project = (project == null) ? ProjectManager.getInstance().getDefaultProject() : project; + myProvider.doCheckout(project, getListener(project)); } protected CheckoutProvider.Listener getListener(Project project) { 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 efd4a03d71aa..048250ee9d8a 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 @@ -202,7 +202,7 @@ public class VcsMappingConfigurationDialog extends DialogWrapper { VcsDescriptor probableVcs = null; @Override - protected void doInAwtIfFail(Exception e) { + protected void doInAwtIfFail(@NotNull Exception e) { } @Override diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/ProjectLevelVcsManagerImpl.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/ProjectLevelVcsManagerImpl.java index f8b4a9be2996..9f61d9b3d7bd 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/ProjectLevelVcsManagerImpl.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/ProjectLevelVcsManagerImpl.java @@ -788,12 +788,6 @@ public class ProjectLevelVcsManagerImpl extends ProjectLevelVcsManagerEx impleme return new CompositeCheckoutListener(myProject); } - @Override - @Nullable - public VcsEventsListenerManager getVcsEventsListenerManager() { - return null; - } - @Override public void fireDirectoryMappingsChanged() { if (myProject.isOpen() && !myProject.isDisposed()) { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/merge/MultipleFileMergeDialog.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/merge/MultipleFileMergeDialog.java index b853840639cc..518a236940ef 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/merge/MultipleFileMergeDialog.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/merge/MultipleFileMergeDialog.java @@ -31,8 +31,6 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diff.impl.mergeTool.MergeVersion; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ex.ProjectManagerEx; import com.intellij.openapi.ui.DialogWrapper; @@ -430,11 +428,6 @@ public class MultipleFileMergeDialog extends DialogWrapper { return myProcessedFiles; } - @Override - public void show() { - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, () -> super.show()); - } - private class VirtualFileRenderer extends ColoredTableCellRenderer { @Override protected void customizeCellRenderer(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/update/BlockReloadingUtil.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/update/BlockReloadingUtil.java deleted file mode 100644 index 40ddcd5e6bc3..000000000000 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/update/BlockReloadingUtil.java +++ /dev/null @@ -1,32 +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.update; - -import com.intellij.openapi.project.ex.ProjectManagerEx; - -@Deprecated -public class BlockReloadingUtil { - private BlockReloadingUtil() { - } - - public static void block() { - ProjectManagerEx.getInstanceEx().blockReloadingProjectOnExternalChanges(); - } - - public static void unblock() { - ProjectManagerEx.getInstanceEx().unblockReloadingProjectOnExternalChanges(); - } -} diff --git a/platform/vcs-impl/src/com/intellij/util/continuation/ModalityIgnorantBackgroundableTask.java b/platform/vcs-impl/src/com/intellij/util/continuation/ModalityIgnorantBackgroundableTask.java index 4173ed822471..0b7e1ccdd834 100644 --- a/platform/vcs-impl/src/com/intellij/util/continuation/ModalityIgnorantBackgroundableTask.java +++ b/platform/vcs-impl/src/com/intellij/util/continuation/ModalityIgnorantBackgroundableTask.java @@ -16,96 +16,44 @@ package com.intellij.util.continuation; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.PerformInBackgroundOption; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; -import com.intellij.util.Consumer; -import com.intellij.util.TimeoutUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -/** - * Created by IntelliJ IDEA. - * User: Irina.Chernushina - * Date: 8/19/11 - * Time: 12:14 PM - */ public abstract class ModalityIgnorantBackgroundableTask extends Task.Backgroundable { - private final static Logger LOG = Logger.getInstance("#com.intellij.util.continuation.ModalityIgnorantBackgroundableTask"); - private Consumer myRunner; - private int myCnt; + private final static Logger LOG = Logger.getInstance(ModalityIgnorantBackgroundableTask.class); - public ModalityIgnorantBackgroundableTask(@Nullable Project project, - @NotNull String title, - boolean canBeCancelled, - @Nullable PerformInBackgroundOption backgroundOption) { - super(project, title, canBeCancelled, backgroundOption); - } - - public ModalityIgnorantBackgroundableTask(@Nullable Project project, - @NotNull String title, - boolean canBeCancelled) { + public ModalityIgnorantBackgroundableTask(@Nullable Project project, @NotNull String title, boolean canBeCancelled) { super(project, title, canBeCancelled); } - public ModalityIgnorantBackgroundableTask(@Nullable Project project, @NotNull String title) { - super(project, title); - } - - protected abstract void doInAwtIfFail(final Exception e); + protected abstract void doInAwtIfFail(@NotNull Exception e); protected abstract void doInAwtIfCancel(); protected abstract void doInAwtIfSuccess(); protected abstract void runImpl(@NotNull ProgressIndicator indicator); - public void runSteadily(final Consumer consumer) { - myRunner = consumer; - myCnt = 100; - consumer.consume(this); - } - @Override - public void run(@NotNull final ProgressIndicator indicator) { + public void run(@NotNull ProgressIndicator indicator) { try { runImpl(indicator); - } catch (final ToBeRepeatedException tbre) { - if (myRunner != null && myCnt > 0) { - -- myCnt; - // we are on some background thread and do not want to reschedule too often - TimeoutUtil.sleep(100); - myRunner.consume(this); - return; - } - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - doInAwtIfFail(tbre); - } - }); - } catch (final Exception e) { + } + catch (Exception e) { LOG.info(e); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - doInAwtIfFail(e); - } - }); + SwingUtilities.invokeLater(() -> doInAwtIfFail(e)); return; } - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - if (indicator.isCanceled()) { - doInAwtIfCancel(); - } else { - doInAwtIfSuccess(); - } + SwingUtilities.invokeLater(() -> { + if (indicator.isCanceled()) { + doInAwtIfCancel(); + } + else { + doInAwtIfSuccess(); } }); } - - public static class ToBeRepeatedException extends RuntimeException {} } diff --git a/platform/vcs-log/impl/gen/icons/VcsLogIcons.java b/platform/vcs-log/impl/gen/icons/VcsLogIcons.java index 93672db6bbb1..5df9d98ace43 100644 --- a/platform/vcs-log/impl/gen/icons/VcsLogIcons.java +++ b/platform/vcs-log/impl/gen/icons/VcsLogIcons.java @@ -18,6 +18,7 @@ public class VcsLogIcons { public static final Icon CollapseMerges = load("/icons/CollapseMerges.png"); // 16x16 public static final Icon ExpandBranches = load("/icons/ExpandBranches.png"); // 16x16 public static final Icon ExpandMerges = load("/icons/ExpandMerges.png"); // 16x16 + public static final Icon GearWithDropDown = load("/icons/GearWithDropDown.png"); // 21x21 public static final Icon IntelliSort = load("/icons/IntelliSort.png"); // 16x16 public static class Process { diff --git a/platform/vcs-log/impl/resources/icons/GearWithDropDown.png b/platform/vcs-log/impl/resources/icons/GearWithDropDown.png new file mode 100644 index 000000000000..f330f7af5f07 Binary files /dev/null and b/platform/vcs-log/impl/resources/icons/GearWithDropDown.png differ diff --git a/platform/vcs-log/impl/resources/icons/GearWithDropDown@2x.png b/platform/vcs-log/impl/resources/icons/GearWithDropDown@2x.png new file mode 100644 index 000000000000..bee0234c8a73 Binary files /dev/null and b/platform/vcs-log/impl/resources/icons/GearWithDropDown@2x.png differ diff --git a/platform/vcs-log/impl/resources/icons/GearWithDropDown@2x_dark.png b/platform/vcs-log/impl/resources/icons/GearWithDropDown@2x_dark.png new file mode 100644 index 000000000000..9b7b4f8f1e64 Binary files /dev/null and b/platform/vcs-log/impl/resources/icons/GearWithDropDown@2x_dark.png differ diff --git a/platform/vcs-log/impl/resources/icons/GearWithDropDown_dark.png b/platform/vcs-log/impl/resources/icons/GearWithDropDown_dark.png new file mode 100644 index 000000000000..df24fbe8634e Binary files /dev/null and b/platform/vcs-log/impl/resources/icons/GearWithDropDown_dark.png differ diff --git a/platform/vcs-log/impl/src/META-INF/vcs-log.xml b/platform/vcs-log/impl/src/META-INF/vcs-log.xml index ddb7c123fe4c..9305816dc5c7 100644 --- a/platform/vcs-log/impl/src/META-INF/vcs-log.xml +++ b/platform/vcs-log/impl/src/META-INF/vcs-log.xml @@ -36,7 +36,7 @@ use-shortcut-of="Find" text="Go To Hash/Branch/Tag" description="Specify hash or name of a branch or a tag to navigate to the commit it points"/> + icon="VcsLogIcons.GearWithDropDown" text="Quick Settings" description="Quick Settings"/> 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 23d98a492b9b..05f0dfb59792 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebugSessionImpl.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/XDebugSessionImpl.java @@ -73,6 +73,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; @@ -935,7 +936,11 @@ public class XDebugSessionImpl implements XDebugSession { @Override public void reportMessage(@NotNull final String message, @NotNull final MessageType type, @Nullable final HyperlinkListener listener) { - NotificationListener notificationListener = listener == null ? null : (notification, event) -> listener.hyperlinkUpdate(event); + NotificationListener notificationListener = listener == null ? null : (notification, event) -> { + if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + listener.hyperlinkUpdate(event); + } + }; NOTIFICATION_GROUP.createNotification("", message, type.toNotificationType(), notificationListener).notify(myProject); } diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/fixes/ConvertToVarargsMethodFix.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/fixes/ConvertToVarargsMethodFix.java index b92f3ed3411f..e62cb016649a 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/fixes/ConvertToVarargsMethodFix.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/fixes/ConvertToVarargsMethodFix.java @@ -75,6 +75,7 @@ public class ConvertToVarargsMethodFix extends InspectionGadgetsFix { } final PsiParameter[] parameters = parameterList.getParameters(); final PsiParameter lastParameter = parameters[parameters.length - 1]; + lastParameter.normalizeDeclaration(); final PsiType type = lastParameter.getType(); if (!(type instanceof PsiArrayType)) { return; diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/packaging/EmptyDirectoryInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/packaging/EmptyDirectoryInspection.java index 4cdf27d7894c..53e77adec6b2 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/packaging/EmptyDirectoryInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/packaging/EmptyDirectoryInspection.java @@ -126,6 +126,11 @@ public class EmptyDirectoryInspection extends BaseGlobalInspection { this.name = name; } + @Override + public boolean startInWriteAction() { + return false; + } + @NotNull @Override public String getName() { diff --git a/plugins/InspectionGadgets/src/com/siyeh/ig/equality/EqualityOperatorComparesObjectsInspection.java b/plugins/InspectionGadgets/src/com/siyeh/ig/equality/EqualityOperatorComparesObjectsInspection.java index 57d00fc9dcfb..39d789872eaf 100644 --- a/plugins/InspectionGadgets/src/com/siyeh/ig/equality/EqualityOperatorComparesObjectsInspection.java +++ b/plugins/InspectionGadgets/src/com/siyeh/ig/equality/EqualityOperatorComparesObjectsInspection.java @@ -19,6 +19,7 @@ import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.psi.tree.IElementType; +import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.TypeConversionUtil; import com.siyeh.InspectionGadgetsBundle; @@ -208,6 +209,12 @@ public class EqualityOperatorComparesObjectsInspection extends BaseInspection { if (rhsType == null || rhsType instanceof PsiPrimitiveType || TypeConversionUtil.isEnumType(rhsType)) { return; } + if (lhs instanceof PsiThisExpression || rhs instanceof PsiThisExpression) { + final PsiMethod method = PsiTreeUtil.getParentOfType(expression, PsiMethod.class); + if (method != null && "equals".equals(method.getName())) { + return; + } + } final String operationText = expression.getOperationSign().getText(); final String prefix = tokenType.equals(JavaTokenType.NE) ? "!" : ""; registerError(expression, operationText, prefix); diff --git a/plugins/InspectionGadgets/test/com/siyeh/igfixes/equality/replace_equality_with_equals/CompareFieldInEqualsMethod.after.java b/plugins/InspectionGadgets/test/com/siyeh/igfixes/equality/replace_equality_with_equals/CompareFieldInEqualsMethod.after.java new file mode 100644 index 000000000000..c3f00c5d834f --- /dev/null +++ b/plugins/InspectionGadgets/test/com/siyeh/igfixes/equality/replace_equality_with_equals/CompareFieldInEqualsMethod.after.java @@ -0,0 +1,14 @@ +class Demo { + private Object field; + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + else { + Demo other = (Demo)obj; + return field.equals(other.field); + } + } +} \ No newline at end of file diff --git a/plugins/InspectionGadgets/test/com/siyeh/igfixes/equality/replace_equality_with_equals/CompareFieldInEqualsMethod.java b/plugins/InspectionGadgets/test/com/siyeh/igfixes/equality/replace_equality_with_equals/CompareFieldInEqualsMethod.java new file mode 100644 index 000000000000..28f947454dcd --- /dev/null +++ b/plugins/InspectionGadgets/test/com/siyeh/igfixes/equality/replace_equality_with_equals/CompareFieldInEqualsMethod.java @@ -0,0 +1,14 @@ +class Demo { + private Object field; + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + else { + Demo other = (Demo)obj; + return field == other.field; + } + } +} \ No newline at end of file diff --git a/plugins/InspectionGadgets/test/com/siyeh/igfixes/equality/replace_equality_with_equals/CompareThisInEqualsMethod.java b/plugins/InspectionGadgets/test/com/siyeh/igfixes/equality/replace_equality_with_equals/CompareThisInEqualsMethod.java new file mode 100644 index 000000000000..f2a9bfbe301b --- /dev/null +++ b/plugins/InspectionGadgets/test/com/siyeh/igfixes/equality/replace_equality_with_equals/CompareThisInEqualsMethod.java @@ -0,0 +1,14 @@ +class Demo { + private Object field; + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + else { + Demo other = (Demo)obj; + return field == other.field; + } + } +} \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/codeInsight/JavaCodeInsightSettingsFacadeImpl.java b/plugins/InspectionGadgets/test/com/siyeh/igfixes/inheritance/problematic_varargs_method_override/OldStyleArray.after.java similarity index 64% rename from java/java-impl/src/com/intellij/codeInsight/JavaCodeInsightSettingsFacadeImpl.java rename to plugins/InspectionGadgets/test/com/siyeh/igfixes/inheritance/problematic_varargs_method_override/OldStyleArray.after.java index 8eaa6616f40a..d3a1ca398a7f 100644 --- a/java/java-impl/src/com/intellij/codeInsight/JavaCodeInsightSettingsFacadeImpl.java +++ b/plugins/InspectionGadgets/test/com/siyeh/igfixes/inheritance/problematic_varargs_method_override/OldStyleArray.after.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * 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. @@ -13,11 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.codeInsight; +package com.siyeh.igfixes.inheritance.problematic_varargs_method_override; -public class JavaCodeInsightSettingsFacadeImpl extends JavaCodeInsightSettingsFacade { - @Override - public boolean isShowStaticAfterInstance() { - return CodeInsightSettings.getInstance().SHOW_STATIC_AFTER_INSTANCE; - } +class One { + + public void m(String... ss) {} } +class Two extends One { + public void m(String... ss) {} +} +class Three { + public static void main(String... args) { + new Two().m("1", "2", "3"); + } +} \ No newline at end of file diff --git a/platform/vcs-api/src/com/intellij/vcsUtil/ActionExecuteHelper.java b/plugins/InspectionGadgets/test/com/siyeh/igfixes/inheritance/problematic_varargs_method_override/OldStyleArray.java similarity index 63% rename from platform/vcs-api/src/com/intellij/vcsUtil/ActionExecuteHelper.java rename to plugins/InspectionGadgets/test/com/siyeh/igfixes/inheritance/problematic_varargs_method_override/OldStyleArray.java index 2d297ab45856..7498bdbbca36 100644 --- a/platform/vcs-api/src/com/intellij/vcsUtil/ActionExecuteHelper.java +++ b/plugins/InspectionGadgets/test/com/siyeh/igfixes/inheritance/problematic_varargs_method_override/OldStyleArray.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * 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. @@ -13,16 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.vcsUtil; +package com.siyeh.igfixes.inheritance.problematic_varargs_method_override; -/** - * Created with IntelliJ IDEA. - * User: Irina.Chernushina - * Date: 7/6/12 - * Time: 7:45 PM - */ -public class ActionExecuteHelper extends AbstractActionStateConsumer { - public boolean isOk() { - return myVisible && myEnabled; - } +class One { + + public void m(String... ss) {} } +class Two extends One { + public void m(String ss[]) {} +} +class Three { + public static void main(String... args) { + new Two().m(new String[]{"1", "2", "3"}); + } +} \ No newline at end of file diff --git a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/fixes/equality/EqualityOperatorComparesObjectsInspectionTestBase.java b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/fixes/equality/EqualityOperatorComparesObjectsInspectionTestBase.java index be5f15a96be7..b49cca0b7232 100644 --- a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/fixes/equality/EqualityOperatorComparesObjectsInspectionTestBase.java +++ b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/fixes/equality/EqualityOperatorComparesObjectsInspectionTestBase.java @@ -30,6 +30,8 @@ public abstract class EqualityOperatorComparesObjectsInspectionTestBase extends public void testPrimitiveComparison() { assertQuickfixNotAvailable(); } public void testSimpleObjectComparison() { doTest(true, false); } public void testNegatedObjectComparison() { doTest(false, false); } + public void testCompareThisInEqualsMethod() { assertQuickfixNotAvailable(); } + public void testCompareFieldInEqualsMethod() { doTest(true, false); } @Override protected void setUp() throws Exception { diff --git a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/fixes/inheritance/ProblematicVarargsMethodOverrideFixTest.java b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/fixes/inheritance/ProblematicVarargsMethodOverrideFixTest.java index 0e94dc6c2883..4037261acfef 100644 --- a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/fixes/inheritance/ProblematicVarargsMethodOverrideFixTest.java +++ b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/fixes/inheritance/ProblematicVarargsMethodOverrideFixTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * 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. @@ -25,6 +25,7 @@ import com.siyeh.ig.inheritance.ProblematicVarargsMethodOverrideInspection; public class ProblematicVarargsMethodOverrideFixTest extends IGQuickFixesTestCase { public void testOne() { doTest(); } + public void testOldStyleArray() { doTest(); } @Override protected void setUp() throws Exception { diff --git a/plugins/ant/src/com/intellij/lang/ant/config/execution/AntBuildMessageView.java b/plugins/ant/src/com/intellij/lang/ant/config/execution/AntBuildMessageView.java index e38e3b4e6ee1..7dfb89c04b10 100644 --- a/plugins/ant/src/com/intellij/lang/ant/config/execution/AntBuildMessageView.java +++ b/plugins/ant/src/com/intellij/lang/ant/config/execution/AntBuildMessageView.java @@ -35,6 +35,7 @@ import com.intellij.lang.ant.segments.OutputPacketProcessor; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; @@ -112,15 +113,9 @@ public final class AntBuildMessageView extends JPanel implements DataProvider, O private final AntMessageCustomizer[] myMessageCustomizers = AntMessageCustomizer.EP_NAME.getExtensions(); private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD); - private final Runnable myFlushLogRunnable = new Runnable() { - @Override - public void run() { - if (myCommandsProcessedCount < myLog.size()) { - if (!myIsOutputPaused) { - new OutputFlusher().doFlush(); - myTreeView.scrollToLastMessage(); - } - } + private final Runnable myFlushLogRunnable = () -> { + if (myCommandsProcessedCount < myLog.size()) { + flushWhenSmart(true); } }; @@ -834,14 +829,16 @@ public final class AntBuildMessageView extends JPanel implements DataProvider, O } } }); - //noinspection SSBasedInspection - SwingUtilities.invokeLater(() -> { - if (!myProject.isDisposed()) { - DumbService.getInstance(myProject).runWhenSmart(() -> { - if (!myIsOutputPaused) { - new OutputFlusher().doFlush(); - } - }); + ApplicationManager.getApplication().invokeLater(() -> flushWhenSmart(false), ModalityState.any(), myProject.getDisposed()); + } + + private void flushWhenSmart(boolean scroll) { + DumbService.getInstance(myProject).runWhenSmart(() -> { + if (!myIsOutputPaused) { + new OutputFlusher().doFlush(); + if (scroll) { + myTreeView.scrollToLastMessage(); + } } }); } diff --git a/plugins/devkit/src/inspections/quickfix/CreateHtmlDescriptionFix.java b/plugins/devkit/src/inspections/quickfix/CreateHtmlDescriptionFix.java index 3a0d8bcf18d6..4c1b791a9a95 100644 --- a/plugins/devkit/src/inspections/quickfix/CreateHtmlDescriptionFix.java +++ b/plugins/devkit/src/inspections/quickfix/CreateHtmlDescriptionFix.java @@ -130,6 +130,11 @@ public class CreateHtmlDescriptionFix implements LocalQuickFix, Iconable { } } + @Override + public boolean startInWriteAction() { + return false; + } + private String getPath(VirtualFile file) { String path = file.getPresentableUrl() + File.separator + getDescriptionFolderName() + File.separator + myFilename; if (isFixedDescriptionFilename()) { diff --git a/plugins/generate-tostring/src/org/jetbrains/java/generate/GenerateToStringActionHandlerImpl.java b/plugins/generate-tostring/src/org/jetbrains/java/generate/GenerateToStringActionHandlerImpl.java index 09885f752218..34ca18479d60 100644 --- a/plugins/generate-tostring/src/org/jetbrains/java/generate/GenerateToStringActionHandlerImpl.java +++ b/plugins/generate-tostring/src/org/jetbrains/java/generate/GenerateToStringActionHandlerImpl.java @@ -20,7 +20,6 @@ import com.intellij.codeInsight.FileModificationService; import com.intellij.codeInsight.generation.PsiElementClassMember; import com.intellij.codeInsight.hint.HintManager; import com.intellij.ide.util.MemberChooser; -import com.intellij.openapi.Disposable; import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; @@ -31,7 +30,6 @@ import com.intellij.openapi.options.TabbedConfigurable; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.ComboBox; import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.util.Disposer; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiTreeUtil; @@ -211,8 +209,7 @@ public class GenerateToStringActionHandlerImpl implements GenerateToStringAction settingsButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { final TemplatesPanel ui = new TemplatesPanel(clazz.getProject()); - Disposable disposable = Disposer.newDisposable(); - Configurable composite = new TabbedConfigurable(disposable) { + Configurable composite = new TabbedConfigurable() { protected List createConfigurables() { List res = new ArrayList<>(); res.add(new GenerateToStringConfigurable(clazz.getProject())); @@ -242,7 +239,7 @@ public class GenerateToStringActionHandlerImpl implements GenerateToStringAction }; ShowSettingsUtil.getInstance().editConfigurable(MemberChooserHeaderPanel.this, composite, () -> ui.selectItem(ToStringTemplatesManager.getInstance().getDefaultTemplate())); - Disposer.dispose(disposable); + composite.disposeUIResources(); } }); diff --git a/plugins/git4idea/src/git4idea/branch/DeepComparator.java b/plugins/git4idea/src/git4idea/branch/DeepComparator.java index 7aed03546ce8..4cd1d3bc7689 100644 --- a/plugins/git4idea/src/git4idea/branch/DeepComparator.java +++ b/plugins/git4idea/src/git4idea/branch/DeepComparator.java @@ -139,9 +139,14 @@ public class DeepComparator implements VcsLogHighlighter, Disposable { return; } + String comparedBranch = myTask.myComparedBranch; + VcsLogBranchFilter branchFilter = dataPack.getFilters().getBranchFilter(); + if (branchFilter == null || !myTask.myComparedBranch.equals(VcsLogUtil.getSingleFilteredBranch(branchFilter, dataPack.getRefs()))) { + stopAndUnhighlight(); + return; + } + if (refreshHappened) { - // collect data - String comparedBranch = myTask.myComparedBranch; Map repositoriesWithCurrentBranches = myTask.myRepositoriesWithCurrentBranches; VcsLogDataProvider provider = myTask.myProvider; @@ -149,14 +154,12 @@ public class DeepComparator implements VcsLogHighlighter, Disposable { // highlight again Map repositories = getRepositories(dataPack.getLogProviders(), comparedBranch); - if (repositories.equals(repositoriesWithCurrentBranches)) { // but not if current branch changed + if (repositories.equals(repositoriesWithCurrentBranches)) { + // but not if current branch changed highlightInBackground(comparedBranch, provider); } - } - else { - VcsLogBranchFilter branchFilter = dataPack.getFilters().getBranchFilter(); - if (branchFilter == null || !myTask.myComparedBranch.equals(VcsLogUtil.getSingleFilteredBranch(branchFilter, dataPack.getRefs()))) { - stopAndUnhighlight(); + else { + removeHighlighting(); } } } @@ -278,6 +281,5 @@ public class DeepComparator implements VcsLogHighlighter, Disposable { handler.runInCurrentThread(null); return pickedCommits; } - } } \ No newline at end of file diff --git a/plugins/git4idea/src/git4idea/commands/GitImpl.java b/plugins/git4idea/src/git4idea/commands/GitImpl.java index 13c2d84c13c0..69592795f146 100644 --- a/plugins/git4idea/src/git4idea/commands/GitImpl.java +++ b/plugins/git4idea/src/git4idea/commands/GitImpl.java @@ -352,6 +352,7 @@ public class GitImpl implements Git { @NotNull String newName, @NotNull GitLineHandlerListener... listeners) { GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.BRANCH); + h.setSilent(false); h.setStdoutSuppressed(false); h.addParameters("-m", currentName, newName); return run(h); diff --git a/plugins/git4idea/tests/git4idea/log/GitLogProviderTest.java b/plugins/git4idea/tests/git4idea/log/GitLogProviderTest.java index 91463557010d..9366c8a13444 100644 --- a/plugins/git4idea/tests/git4idea/log/GitLogProviderTest.java +++ b/plugins/git4idea/tests/git4idea/log/GitLogProviderTest.java @@ -125,12 +125,7 @@ public class GitLogProviderTest extends GitSingleRepoTest { VcsLogProvider.DetailedLogData block = myLogProvider.readFirstBlock(myProjectRoot, new RequirementsImpl(1000, false, Collections.emptySet())); - assertFalse("origin/HEAD should be ignored", ContainerUtil.exists(block.getRefs(), new Condition() { - @Override - public boolean value(VcsRef ref) { - return ref.getName().equals("origin/HEAD"); - } - })); + assertFalse("origin/HEAD should be ignored", ContainerUtil.exists(block.getRefs(), ref -> ref.getName().equals("origin/HEAD"))); } public void test_support_equally_named_branch_and_tag() throws Exception { @@ -142,18 +137,8 @@ public class GitLogProviderTest extends GitSingleRepoTest { new RequirementsImpl(1000, true, Collections.emptySet())); List expectedLog = log(); assertOrderedEquals(data.getCommits(), expectedLog); - assertTrue(ContainerUtil.exists(data.getRefs(), new Condition() { - @Override - public boolean value(VcsRef ref) { - return ref.getName().equals("build") && ref.getType() == GitRefManager.LOCAL_BRANCH; - } - })); - assertTrue(ContainerUtil.exists(data.getRefs(), new Condition() { - @Override - public boolean value(VcsRef ref) { - return ref.getName().equals("build") && ref.getType() == GitRefManager.TAG; - } - })); + assertTrue(ContainerUtil.exists(data.getRefs(), ref -> ref.getName().equals("build") && ref.getType() == GitRefManager.LOCAL_BRANCH)); + assertTrue(ContainerUtil.exists(data.getRefs(), ref -> ref.getName().equals("build") && ref.getType() == GitRefManager.TAG)); } public void test_filter_by_branch() throws Exception { @@ -166,8 +151,10 @@ public class GitLogProviderTest extends GitSingleRepoTest { public void test_filter_by_branch_and_user() throws Exception { List hashes = generateHistoryForFilters(false); VcsLogBranchFilter branchFilter = VcsLogBranchFilterImpl.fromBranch("feature"); - VcsLogUserFilter userFilter = new VcsLogUserFilterImpl(singleton(GitTestUtil.USER_NAME), Collections.emptyMap(), - Collections.emptySet()); + VcsUserImpl user = new VcsUserImpl(GitTestUtil.USER_NAME, GitTestUtil.USER_EMAIL); + VcsLogUserFilter userFilter = new VcsLogUserFilterImpl(singleton(GitTestUtil.USER_NAME), + Collections.emptyMap(), + singleton(user)); List actualHashes = getFilteredHashes(branchFilter, userFilter); assertEquals(hashes, actualHashes); } @@ -177,12 +164,7 @@ public class GitLogProviderTest extends GitSingleRepoTest { List log = log(); final List hashes = ContainerUtil.newArrayList(); - myLogProvider.readAllHashes(myProjectRoot, new Consumer() { - @Override - public void consume(TimedVcsCommit timedVcsCommit) { - hashes.add(timedVcsCommit.getId().asString()); - } - }); + myLogProvider.readAllHashes(myProjectRoot, timedVcsCommit -> hashes.add(timedVcsCommit.getId().asString())); List shortDetails = myLogProvider.readShortDetails(myProjectRoot, hashes); @@ -195,42 +177,29 @@ public class GitLogProviderTest extends GitSingleRepoTest { List log = log(); final List hashes = ContainerUtil.newArrayList(); - myLogProvider.readAllHashes(myProjectRoot, new Consumer() { - @Override - public void consume(TimedVcsCommit timedVcsCommit) { - hashes.add(timedVcsCommit.getId().asString()); - } - }); + myLogProvider.readAllHashes(myProjectRoot, timedVcsCommit -> hashes.add(timedVcsCommit.getId().asString())); List fullDetails = myLogProvider.readFullDetails(myProjectRoot, hashes); // we do not check for changes here final Function shortDetailsToString = getShortDetailsToString(); - Function metadataToString = new Function() { - @Override - public String fun(VcsCommitMetadata details) { - return shortDetailsToString.fun(details) + "\n" + details.getFullMessage(); - } - }; + Function metadataToString = details -> shortDetailsToString.fun(details) + "\n" + details.getFullMessage(); assertOrderedEquals(ContainerUtil.map(fullDetails, metadataToString), ContainerUtil.map(log, metadataToString)); } @NotNull private Function getShortDetailsToString() { - return new Function() { - @Override - public String fun(VcsShortCommitDetails details) { - String result = ""; + return details -> { + String result = ""; - result += details.getId().toShortString() + "\n"; - result += details.getAuthorTime() + "\n"; - result += details.getAuthor() + "\n"; - result += details.getCommitTime() + "\n"; - result += details.getCommitter() + "\n"; - result += details.getSubject(); + result += details.getId().toShortString() + "\n"; + result += details.getAuthorTime() + "\n"; + result += details.getAuthor() + "\n"; + result += details.getCommitTime() + "\n"; + result += details.getCommitter() + "\n"; + result += details.getSubject(); - return result; - } + return result; }; } @@ -267,12 +236,7 @@ public class GitLogProviderTest extends GitSingleRepoTest { @Nullable VcsLogUserFilter userFilter) throws VcsException { VcsLogFilterCollectionImpl filters = new VcsLogFilterCollectionImpl(branchFilter, userFilter, null, null, null, null, null); List commits = myLogProvider.getCommitsMatchingFilter(myProjectRoot, filters, -1); - return ContainerUtil.map(commits, new Function() { - @Override - public String fun(TimedVcsCommit commit) { - return commit.getId().asString(); - } - }); + return ContainerUtil.map(commits, commit -> commit.getId().asString()); } private static void prepareSomeHistory() { @@ -311,20 +275,12 @@ public class GitLogProviderTest extends GitSingleRepoTest { private List log() { String output = git("log --all --date-order --full-history --sparse --pretty='%H|%P|%ct|%s|%B'"); final VcsUser defaultUser = getDefaultUser(); - final Function TO_HASH = new Function() { - @Override - public Hash fun(String s) { - return HashImpl.build(s); - } - }; - return ContainerUtil.map(StringUtil.splitByLines(output), new Function() { - @Override - public VcsCommitMetadata fun(String record) { - String[] items = ArrayUtil.toStringArray(StringUtil.split(record, "|", true, false)); - long time = Long.valueOf(items[2]) * 1000; - return new VcsCommitMetadataImpl(TO_HASH.fun(items[0]), ContainerUtil.map(items[1].split(" "), TO_HASH), time, - myProjectRoot, items[3], defaultUser, items[4], defaultUser, time); - } + final Function TO_HASH = s -> HashImpl.build(s); + return ContainerUtil.map(StringUtil.splitByLines(output), record -> { + String[] items = ArrayUtil.toStringArray(StringUtil.split(record, "|", true, false)); + long time = Long.valueOf(items[2]) * 1000; + return new VcsCommitMetadataImpl(TO_HASH.fun(items[0]), ContainerUtil.map(items[1].split(" "), TO_HASH), time, + myProjectRoot, items[3], defaultUser, items[4], defaultUser, time); }); } } diff --git a/plugins/git4idea/tests/git4idea/test/RefParser.java b/plugins/git4idea/tests/git4idea/test/RefParser.java index 1f6ccf9c1fb1..61062b95ddc6 100644 --- a/plugins/git4idea/tests/git4idea/test/RefParser.java +++ b/plugins/git4idea/tests/git4idea/test/RefParser.java @@ -48,7 +48,7 @@ class RefParser { String strHash = input.substring(0, firstSpaceIndex); Hash hash = HashImpl.build(strHash); String refPaths = input.substring(firstSpaceIndex + 2, input.length() - 1); - String[] longRefPaths = refPaths.split(", "); + String[] longRefPaths = refPaths.split("(, )|( -> )"); List refs = new ArrayList<>(); for (String longRefPatch : longRefPaths) { VcsRef ref = createRef(hash, longRefPatch, root); diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/GroovyInspectionBundle.properties b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/GroovyInspectionBundle.properties index 90f9afefe558..9ad98f402eb7 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/GroovyInspectionBundle.properties +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/GroovyInspectionBundle.properties @@ -42,10 +42,10 @@ parameter.can.be.final.tooltip=Parameter ''{0}'' can be final groovy.probable.bugs=Probable bugs equals.between.inconvertible.types='equals()' between objects of inconvertible types equals.between.inconvertible.types.tooltip={0} between objects of inconvertible types ''{1}'' and ''{2}'' -change.to.operator.message=''{0}'' can be changed to operator -change.to.operator.fix=Change ''{0}'' to operator -change.to.operator.double.negation.option=Use double negation (i.e. !!) -change.to.operator.compareTo.equality.option=Change compareTo equality to equals (i.e. ==) +replace.with.operator.message=''{0}'' can be replaced with operator +replace.with.operator.fix=Replace ''{0}'' with operator +replace.with.operator.double.negation.option=Use double negation (i.e. !!) +replace.with.operator.compareTo.equality.option=Replace 'compareTo' equality to equals (i.e. ==) unassigned.access=Variable Not Assigned unassigned.access.short.name=VariableNotAssigned unassigned.access.tooltip=Variable ''{0}'' might not be assigned diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/ChangeToOperatorInspection.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/ChangeToOperatorInspection.java index 9f9d98fd147b..64b082171ecf 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/ChangeToOperatorInspection.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/ChangeToOperatorInspection.java @@ -19,9 +19,9 @@ import com.intellij.codeInspection.LocalQuickFix; import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel; import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiModifier; -import com.intellij.psi.PsiModifierListOwner; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.groovy.codeInspection.BaseInspection; import org.jetbrains.plugins.groovy.codeInspection.BaseInspectionVisitor; @@ -29,19 +29,17 @@ import org.jetbrains.plugins.groovy.codeInspection.GroovyFix; import org.jetbrains.plugins.groovy.codeInspection.changeToOperator.data.OptionsData; import org.jetbrains.plugins.groovy.codeInspection.changeToOperator.data.ReplacementData; import org.jetbrains.plugins.groovy.codeInspection.changeToOperator.transformations.Transformation; -import org.jetbrains.plugins.groovy.codeInspection.changeToOperator.transformations.Transformations; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression; import javax.swing.*; -import java.util.Map; import static com.intellij.codeInspection.ProblemHighlightType.GENERIC_ERROR_OR_WARNING; import static org.jetbrains.plugins.groovy.codeInspection.GroovyInspectionBundle.message; +import static org.jetbrains.plugins.groovy.codeInspection.changeToOperator.transformations.Transformations.TRANSFORMATIONS; public class ChangeToOperatorInspection extends BaseInspection { - private static final Map TRANSFORMATIONS = Transformations.get(); - public boolean useDoubleNegation = true; public boolean shouldChangeCompareToEqualityToEquals = true; @@ -51,44 +49,45 @@ public class ChangeToOperatorInspection extends BaseInspection { return new BaseInspectionVisitor() { @Override public void visitMethodCallExpression(@NotNull GrMethodCallExpression methodCallExpression) { - super.visitMethodCallExpression(methodCallExpression); - processMethodCall(methodCallExpression); - } + GrExpression invokedExpression = methodCallExpression.getInvokedExpression(); + if (!(invokedExpression instanceof GrReferenceExpression)) return; + + PsiElement element = ((GrReferenceExpression)invokedExpression).getReferenceNameElement(); + if (element == null) return; - private void processMethodCall(GrMethodCallExpression methodCallExpression) { PsiMethod method = methodCallExpression.resolveMethod(); - if (!isValid(method)) return; + if (method == null || method.hasModifierProperty(PsiModifier.STATIC)) return; String methodName = method.getName(); Transformation transformation = TRANSFORMATIONS.get(methodName); if (transformation == null) return; - // TODO apply transformation recursively OptionsData optionsData = new OptionsData(useDoubleNegation, shouldChangeCompareToEqualityToEquals); ReplacementData replacement = transformation.transform(methodCallExpression, optionsData); if (replacement == null) return; - GrExpression expression = replacement.getExpression(); - String text = expression.getText(); - GroovyFix fix = getFix(message("change.to.operator.fix", text), replacement.getReplacement()); - registerError(expression, message("change.to.operator.message", text), new LocalQuickFix[]{fix}, GENERIC_ERROR_OR_WARNING); + GroovyFix fix = getFix(message("replace.with.operator.fix", methodName), replacement); + registerError(element, message("replace.with.operator.message", methodName), new LocalQuickFix[]{fix}, GENERIC_ERROR_OR_WARNING); } - private boolean isValid(PsiModifierListOwner method) { - return ((method != null) && !method.hasModifierProperty(PsiModifier.STATIC)); - } - - private GroovyFix getFix(@NotNull final String message, final String replacement) { + private GroovyFix getFix(@NotNull final String title, final ReplacementData replacement) { return new GroovyFix() { + @NotNull @Override - public String getName() { - return message; + public String getFamilyName() { + return title; } @Override protected void doFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { - replaceExpression((GrExpression)descriptor.getPsiElement(), replacement); + PsiElement element = descriptor.getPsiElement().getParent(); + if (!(element instanceof GrReferenceExpression)) return; + + PsiElement call = element.getParent(); + if (!(call instanceof GrMethodCallExpression)) return; + + replaceExpression(replacement.getElementToReplace((GrMethodCallExpression)call), replacement.getReplacement()); } }; } @@ -98,8 +97,8 @@ public class ChangeToOperatorInspection extends BaseInspection { @Override public JComponent createOptionsPanel() { MultipleCheckboxOptionsPanel optionsPanel = new MultipleCheckboxOptionsPanel(this); - optionsPanel.addCheckbox(message("change.to.operator.double.negation.option"), "useDoubleNegation"); - optionsPanel.addCheckbox(message("change.to.operator.compareTo.equality.option"), "shouldChangeCompareToEqualityToEquals"); + optionsPanel.addCheckbox(message("replace.with.operator.double.negation.option"), "useDoubleNegation"); + optionsPanel.addCheckbox(message("replace.with.operator.compareTo.equality.option"), "shouldChangeCompareToEqualityToEquals"); return optionsPanel; } } \ No newline at end of file diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/data/ReplacementData.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/data/ReplacementData.java index 28ccbd207a66..ae2bf323dfe7 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/data/ReplacementData.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/data/ReplacementData.java @@ -15,23 +15,31 @@ */ package org.jetbrains.plugins.groovy.codeInspection.changeToOperator.data; +import com.intellij.util.NotNullFunction; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression; public final class ReplacementData { - private final GrExpression element; - private final String replacement; - public ReplacementData(@NotNull GrExpression element, @NotNull String replacement) { - this.element = element; - this.replacement = replacement; - } + private final String myReplacement; + private final NotNullFunction myElementToReplace; - public GrExpression getExpression() { - return element; + public ReplacementData(@NotNull String replacement, @Nullable NotNullFunction elementToReplace) { + this.myReplacement = replacement; + this.myElementToReplace = elementToReplace; } + @NotNull + @Contract(pure = true) public String getReplacement() { - return replacement; + return myReplacement; + } + + @NotNull + public GrExpression getElementToReplace(@NotNull GrMethodCallExpression call) { + return myElementToReplace == null ? call : myElementToReplace.fun(call); } } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/AsBooleanTransformation.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/AsBooleanTransformation.java index fcae291e5955..1d26a707c54a 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/AsBooleanTransformation.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/AsBooleanTransformation.java @@ -16,6 +16,7 @@ package org.jetbrains.plugins.groovy.codeInspection.changeToOperator.transformations; import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.codeInspection.changeToOperator.data.MethodCallData; import org.jetbrains.plugins.groovy.codeInspection.changeToOperator.data.OptionsData; @@ -30,12 +31,9 @@ class AsBooleanTransformation extends UnaryTransformation { public static final String NEGATION = mLNOT.toString(); public static final String DOUBLE_NEGATION = NEGATION + NEGATION; - public AsBooleanTransformation() { - super(null); - } - + @NotNull @Override - protected GrExpression getExpandedElement(GrMethodCallExpression callExpression) { + protected GrExpression getExpandedElement(@NotNull GrMethodCallExpression callExpression) { PsiElement parent = callExpression.getParent(); if (isNegation(parent)) { return (GrExpression)parent; @@ -46,7 +44,7 @@ class AsBooleanTransformation extends UnaryTransformation { } @Override - @Nullable + @Nullable protected String getPrefix(MethodCallData methodInfo, OptionsData optionsData) { if (methodInfo.isNegated()) { return NEGATION; @@ -64,5 +62,5 @@ class AsBooleanTransformation extends UnaryTransformation { private static boolean isImplicitlyBoolean(MethodCallData methodInfo) { return methodInfo.getBackingElement().getParent() instanceof GrIfStatement; - } + } } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/BinaryTransformation.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/BinaryTransformation.java index 555932e6a1a2..462dc017c967 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/BinaryTransformation.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/BinaryTransformation.java @@ -15,7 +15,6 @@ */ package org.jetbrains.plugins.groovy.codeInspection.changeToOperator.transformations; -import com.intellij.psi.tree.IElementType; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.codeInspection.changeToOperator.data.MethodCallData; import org.jetbrains.plugins.groovy.codeInspection.changeToOperator.data.OptionsData; @@ -27,16 +26,13 @@ import static java.lang.String.format; * a.equals(b) → (a == b) * !a.equals(b) → (a != b) */ -public class BinaryTransformation extends Transformation { - public BinaryTransformation(@Nullable IElementType operator) { - super(operator); - } +abstract class BinaryTransformation extends Transformation { @Override @Nullable public String getReplacement(MethodCallData methodInfo, OptionsData optionsData) { String lhs = getLhs(methodInfo); - IElementType operator = getOperator(methodInfo, optionsData); + String operator = getOperator(methodInfo, optionsData); String rhs = getRhs(methodInfo); if (lhs == null || operator == null || rhs == null) return null; @@ -49,9 +45,7 @@ public class BinaryTransformation extends Transformation { } @Nullable - protected IElementType getOperator(MethodCallData methodInfo, OptionsData optionsData) { - return operator; - } + protected abstract String getOperator(MethodCallData methodInfo, OptionsData optionsData); @Nullable protected String getRhs(MethodCallData methodInfo) { diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/CompareToTransformation.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/CompareToTransformation.java index acd951758e00..ed02135ef1c9 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/CompareToTransformation.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/CompareToTransformation.java @@ -17,6 +17,7 @@ package org.jetbrains.plugins.groovy.codeInspection.changeToOperator.transformat import com.intellij.psi.PsiElement; import com.intellij.psi.tree.IElementType; +import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.groovy.codeInspection.changeToOperator.data.MethodCallData; import org.jetbrains.plugins.groovy.codeInspection.changeToOperator.data.OptionsData; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; @@ -27,22 +28,19 @@ import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.*; import static org.jetbrains.plugins.groovy.lang.psi.impl.utils.ComparisonUtils.isComparison; class CompareToTransformation extends BinaryTransformation { - public CompareToTransformation() { - super(null); - } + @NotNull @Override - protected GrExpression getExpandedElement(GrMethodCallExpression callExpression) { + protected GrExpression getExpandedElement(@NotNull GrMethodCallExpression callExpression) { PsiElement parent = callExpression.getParent(); - return isComparison(parent) ? (GrExpression)parent - : super.getExpandedElement(callExpression); + return isComparison(parent) ? (GrExpression)parent : super.getExpandedElement(callExpression); } @Override - protected IElementType getOperator(MethodCallData methodInfo, OptionsData optionsData) { + protected String getOperator(MethodCallData methodInfo, OptionsData optionsData) { IElementType comparison = methodInfo.getComparison(); if (shouldChangeCompareToEqualityToEquals(optionsData, comparison)) { - return firstNonNull(comparison, mCOMPARE_TO); + return firstNonNull(comparison, mCOMPARE_TO).toString(); } else { return null; diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/EqualsTransformation.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/EqualsTransformation.java index 0ddb90e629ed..40b001d4bf8e 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/EqualsTransformation.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/EqualsTransformation.java @@ -16,33 +16,27 @@ package org.jetbrains.plugins.groovy.codeInspection.changeToOperator.transformations; import com.intellij.psi.PsiElement; -import com.intellij.psi.tree.IElementType; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.codeInspection.changeToOperator.data.MethodCallData; import org.jetbrains.plugins.groovy.codeInspection.changeToOperator.data.OptionsData; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression; -import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mEQUAL; -import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mNOT_EQUAL; import static org.jetbrains.plugins.groovy.lang.psi.impl.utils.BoolUtils.isNegation; class EqualsTransformation extends BinaryTransformation { - public EqualsTransformation() { - super(null); - } + @NotNull @Override - protected GrExpression getExpandedElement(GrMethodCallExpression callExpression) { + protected GrExpression getExpandedElement(@NotNull GrMethodCallExpression callExpression) { PsiElement parent = callExpression.getParent(); - return isNegation(parent) ? (GrExpression)parent - : super.getExpandedElement(callExpression); + return isNegation(parent) ? (GrExpression)parent : super.getExpandedElement(callExpression); } @Override @Nullable - protected IElementType getOperator(MethodCallData methodInfo, OptionsData optionsData) { - return methodInfo.isNegated() ? mNOT_EQUAL - : mEQUAL; + protected String getOperator(MethodCallData methodInfo, OptionsData optionsData) { + return methodInfo.isNegated() ? "!=" : "=="; } } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/GetAtTransformation.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/GetAtTransformation.java index b42d8aa83da3..75a886367224 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/GetAtTransformation.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/GetAtTransformation.java @@ -22,9 +22,6 @@ import org.jetbrains.plugins.groovy.codeInspection.changeToOperator.data.Options import static java.lang.String.format; class GetAtTransformation extends Transformation { - public GetAtTransformation() { - super(null); - } @Override @Nullable @@ -32,7 +29,6 @@ class GetAtTransformation extends Transformation { String argument = methodInfo.getArgument(0); if (argument == null) return null; - return format("%s[%s]", - methodInfo.getBase(), argument); + return format("%s[%s]", methodInfo.getBase(), argument); } } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/IsCaseTransformation.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/IsCaseTransformation.java index 86208aa5633b..e4d1ea3479a7 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/IsCaseTransformation.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/IsCaseTransformation.java @@ -17,11 +17,11 @@ package org.jetbrains.plugins.groovy.codeInspection.changeToOperator.transformat import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.codeInspection.changeToOperator.data.MethodCallData; -import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes; -class IsCaseTransformation extends BinaryTransformation { +class IsCaseTransformation extends SimpleBinaryTransformation { + public IsCaseTransformation() { - super(GroovyTokenTypes.kIN); + super("in"); } @Override diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/PutAtTransformation.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/PutAtTransformation.java index 0a18a41dbf75..191508c91a43 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/PutAtTransformation.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/PutAtTransformation.java @@ -25,7 +25,6 @@ class PutAtTransformation extends Transformation { private final Transformation getAtTransformation; public PutAtTransformation(Transformation getAtTransformation) { - super(null); this.getAtTransformation = getAtTransformation; } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/CallTransformation.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/SimpleBinaryTransformation.java similarity index 63% rename from plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/CallTransformation.java rename to plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/SimpleBinaryTransformation.java index 9835ad2cd9b1..15f054b1f148 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/CallTransformation.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/SimpleBinaryTransformation.java @@ -15,21 +15,26 @@ */ package org.jetbrains.plugins.groovy.codeInspection.changeToOperator.transformations; +import com.intellij.psi.tree.IElementType; +import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.groovy.codeInspection.changeToOperator.data.MethodCallData; import org.jetbrains.plugins.groovy.codeInspection.changeToOperator.data.OptionsData; -import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mLPAREN; -import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mRPAREN; +public class SimpleBinaryTransformation extends BinaryTransformation { -class CallTransformation extends UnaryTransformation { - public static final String EMPTY_PARENS = mLPAREN.toString() + mRPAREN; + private final String myOperator; - public CallTransformation() { - super(null); + public SimpleBinaryTransformation(@NotNull IElementType operatorType) { + this(operatorType.toString()); } + public SimpleBinaryTransformation(@NotNull String operator) { + myOperator = operator; + } + + @NotNull @Override - public String getReplacement(MethodCallData call, OptionsData options) { - return super.getReplacement(call, options) + EMPTY_PARENS; + protected String getOperator(MethodCallData methodInfo, OptionsData optionsData) { + return myOperator; } } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/Transformation.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/Transformation.java index 50afdcebf1d9..2ffb3468eb2a 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/Transformation.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/Transformation.java @@ -15,7 +15,7 @@ */ package org.jetbrains.plugins.groovy.codeInspection.changeToOperator.transformations; -import com.intellij.psi.tree.IElementType; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.codeInspection.changeToOperator.data.MethodCallData; import org.jetbrains.plugins.groovy.codeInspection.changeToOperator.data.OptionsData; @@ -24,12 +24,6 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpres import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression; public abstract class Transformation { - @Nullable - protected final IElementType operator; - - public Transformation(@Nullable IElementType operator) { - this.operator = operator; - } @Nullable public ReplacementData transform(GrMethodCallExpression callExpression, OptionsData optionsData) { @@ -40,10 +34,11 @@ public abstract class Transformation { String replacement = getReplacement(methodInfo, optionsData); if (replacement == null) return null; - return new ReplacementData(element, replacement); + return new ReplacementData(replacement, this::getExpandedElement); } - protected GrExpression getExpandedElement(GrMethodCallExpression callExpression) { + @NotNull + protected GrExpression getExpandedElement(@NotNull GrMethodCallExpression callExpression) { return callExpression; } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/Transformations.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/Transformations.java index c02886b9f577..daf73a68cc04 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/Transformations.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/Transformations.java @@ -15,68 +15,46 @@ */ package org.jetbrains.plugins.groovy.codeInspection.changeToOperator.transformations; -import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.ContainerUtil.ImmutableMapBuilder; -import org.jetbrains.plugins.groovy.lang.lexer.GroovyElementType; import java.util.Map; import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.*; import static org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.HardcodedGroovyMethodConstants.*; -public class Transformations { - private final ImmutableMapBuilder result = ContainerUtil.immutableMapBuilder(); +public interface Transformations { - public static Map get() { - Transformations builder = new Transformations(); - return builder.build(); - } + Map TRANSFORMATIONS = new ImmutableMapBuilder() - private Transformations() { - addUnary(); - addBinary(); - addCustom(); - } + // unary + .put(BITWISE_NEGATE, new UnaryTransformation(mBNOT)) + .put(NEGATIVE, new UnaryTransformation(mMINUS)) + .put(POSITIVE, new UnaryTransformation(mPLUS)) + .put(NEXT, new UnaryTransformation(mINC)) + .put(PREVIOUS, new UnaryTransformation(mDEC)) + .put(AS_BOOLEAN, new AsBooleanTransformation()) - private void addUnary() { - result.put(BITWISE_NEGATE, new UnaryTransformation(mBNOT)); - result.put(NEGATIVE, new UnaryTransformation(mMINUS)); - result.put(POSITIVE, new UnaryTransformation(mPLUS)); - result.put(NEXT, new UnaryTransformation(mINC)); - result.put(PREVIOUS, new UnaryTransformation(mDEC)); + // binary + .put(PLUS, new SimpleBinaryTransformation(mPLUS)) + .put(MINUS, new SimpleBinaryTransformation(mMINUS)) + .put(MULTIPLY, new SimpleBinaryTransformation(mSTAR)) + .put(POWER, new SimpleBinaryTransformation(mSTAR_STAR)) + .put(DIV, new SimpleBinaryTransformation(mDIV)) + .put(MOD, new SimpleBinaryTransformation(mMOD)) + .put(OR, new SimpleBinaryTransformation(mBOR)) + .put(AND, new SimpleBinaryTransformation(mBAND)) + .put(XOR, new SimpleBinaryTransformation(mBXOR)) + .put(LEFT_SHIFT, new SimpleBinaryTransformation("<<")) + .put(RIGHT_SHIFT, new SimpleBinaryTransformation(">>")) + .put(RIGHT_SHIFT_UNSIGNED, new SimpleBinaryTransformation(">>>")) + .put(AS_TYPE, new SimpleBinaryTransformation(kAS)) + .put(IS_CASE, new IsCaseTransformation()) + .put(EQUALS, new EqualsTransformation()) + .put(COMPARE_TO, new CompareToTransformation()) - result.put(AS_BOOLEAN, new AsBooleanTransformation()); - //result.put(CALL, new CallTransformation()); - } + // custom + .put(GET_AT, new GetAtTransformation()) + .put(PUT_AT, new PutAtTransformation(new GetAtTransformation())) - private void addBinary() { - result.put(PLUS, new BinaryTransformation(mPLUS)); - result.put(MINUS, new BinaryTransformation(mMINUS)); - result.put(MULTIPLY, new BinaryTransformation(mSTAR)); - result.put(POWER, new BinaryTransformation(mSTAR_STAR)); - result.put(DIV, new BinaryTransformation(mDIV)); - result.put(MOD, new BinaryTransformation(mMOD)); - result.put(OR, new BinaryTransformation(mBOR)); - result.put(AND, new BinaryTransformation(mBAND)); - result.put(XOR, new BinaryTransformation(mBXOR)); - result.put(LEFT_SHIFT, new BinaryTransformation(new GroovyElementType("<<"))); - result.put(RIGHT_SHIFT, new BinaryTransformation(new GroovyElementType(">>"))); - result.put(RIGHT_SHIFT_UNSIGNED, new BinaryTransformation(new GroovyElementType(">>>"))); - - result.put(AS_TYPE, new BinaryTransformation(kAS)); - - result.put(IS_CASE, new IsCaseTransformation()); - result.put(EQUALS, new EqualsTransformation()); - result.put(COMPARE_TO, new CompareToTransformation()); - } - - private void addCustom() { - Transformation getAtTransformation = new GetAtTransformation(); - result.put(GET_AT, getAtTransformation); - result.put(PUT_AT, new PutAtTransformation(getAtTransformation)); - } - - private Map build() { - return result.build(); - } + .build(); } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/UnaryTransformation.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/UnaryTransformation.java index db44889e19cd..6ae72cd354d8 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/UnaryTransformation.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/transformations/UnaryTransformation.java @@ -16,12 +16,11 @@ package org.jetbrains.plugins.groovy.codeInspection.changeToOperator.transformations; import com.intellij.psi.tree.IElementType; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.codeInspection.changeToOperator.data.MethodCallData; import org.jetbrains.plugins.groovy.codeInspection.changeToOperator.data.OptionsData; -import static com.google.common.base.MoreObjects.firstNonNull; - /** * e.g. * !a.asBoolean() → !a @@ -29,8 +28,19 @@ import static com.google.common.base.MoreObjects.firstNonNull; * if(a.asBoolean()) → if(a) */ public class UnaryTransformation extends Transformation { - public UnaryTransformation(@Nullable IElementType operator) { - super(operator); + + private final @NotNull String myOperator; + + public UnaryTransformation(@NotNull IElementType operatorType) { + this(operatorType.toString()); + } + + public UnaryTransformation(@NotNull String operator) { + myOperator = operator; + } + + public UnaryTransformation() { + this(""); } @Override @@ -38,13 +48,13 @@ public class UnaryTransformation extends Transformation { public String getReplacement(MethodCallData call, OptionsData options) { String prefix = getPrefix(call, options); String base = call.getBase(); - if ((prefix == null) || (base == null)) return null; + if (prefix == null) return null; return prefix + base; } @Nullable protected String getPrefix(MethodCallData call, OptionsData optionsData) { - return firstNonNull(operator, "").toString(); + return myOperator; } } \ No newline at end of file diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyFileImpl.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyFileImpl.java index 70ac76f3210d..a0a58b19bad5 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyFileImpl.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyFileImpl.java @@ -70,7 +70,8 @@ import java.util.concurrent.ConcurrentMap; * * @author ilyas */ -public class GroovyFileImpl extends GroovyFileBaseImpl implements GroovyFile { +public class GroovyFileImpl extends GroovyFileBaseImpl implements GroovyFile, PsiModifiableCodeBlock { + private static final Logger LOG = Logger.getInstance("org.jetbrains.plugins.groovy.lang.psi.impl.GroovyFileImpl"); private static final String SYNTHETIC_PARAMETER_NAME = "args"; @@ -439,7 +440,8 @@ public class GroovyFileImpl extends GroovyFileBaseImpl implements GroovyFile { if (currentPackage != null) { final ASTNode currNode = currentPackage.getNode(); fileNode.replaceChild(currNode, newNode); - } else { + } + else { ASTNode anchor = fileNode.getFirstChildNode(); if (anchor != null && anchor.getElementType() == GroovyTokenTypes.mSH_COMMENT) { anchor = anchor.getTreeNext(); @@ -541,9 +543,18 @@ public class GroovyFileImpl extends GroovyFileBaseImpl implements GroovyFile { return PsiImplUtilKt.getScriptDeclarations(this, topLevelOnly); } + @Override + public boolean shouldChangeModificationCount(PsiElement place) { + // 1. We actually should never get GrTypeDefinition as a parent, because it is a PsiClass, + // and PsiClasses prevent to go up in a tree any further + // 2. If place is under a variable then @BaseScript or @Field may be changed, + // which actually is a change in Java Structure + return !isScript() || PsiTreeUtil.getParentOfType(place, GrTypeDefinition.class, GrVariableDeclaration.class) != null; + } + @Override public String toString() { - if (ApplicationManager.getApplication().isUnitTestMode()){ + if (ApplicationManager.getApplication().isUnitTestMode()) { return super.toString(); } return "GroovyFileImpl:" + getName(); diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/blocks/GrOpenBlockImpl.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/blocks/GrOpenBlockImpl.java index dfe65a996e17..63a13c98b092 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/blocks/GrOpenBlockImpl.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/blocks/GrOpenBlockImpl.java @@ -17,6 +17,7 @@ package org.jetbrains.plugins.groovy.lang.psi.impl.statements.blocks; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiModifiableCodeBlock; import com.intellij.psi.tree.IElementType; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor; @@ -27,7 +28,8 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMe /** * @author ilyas */ -public class GrOpenBlockImpl extends GrBlockImpl implements GrOpenBlock { +public class GrOpenBlockImpl extends GrBlockImpl implements GrOpenBlock, PsiModifiableCodeBlock { + public GrOpenBlockImpl(@NotNull IElementType type, CharSequence buffer) { super(type, buffer); } @@ -46,4 +48,9 @@ public class GrOpenBlockImpl extends GrBlockImpl implements GrOpenBlock { final PsiElement parent = getParent(); return parent instanceof GrMethod || parent instanceof GrClassInitializer; } + + @Override + public boolean shouldChangeModificationCount(PsiElement place) { + return !isTopControlFlowOwner(); + } } diff --git a/plugins/groovy/jps-plugin/src/org/jetbrains/jps/incremental/groovy/GroovycOutputParser.java b/plugins/groovy/jps-plugin/src/org/jetbrains/jps/incremental/groovy/GroovycOutputParser.java index 18cd2e9118e4..0c6672feb575 100644 --- a/plugins/groovy/jps-plugin/src/org/jetbrains/jps/incremental/groovy/GroovycOutputParser.java +++ b/plugins/groovy/jps-plugin/src/org/jetbrains/jps/incremental/groovy/GroovycOutputParser.java @@ -205,7 +205,7 @@ public class GroovycOutputParser { public boolean shouldRetry() { for (CompilerMessage message : compilerMessages) { String text = message.getMessageText(); - if (text.contains("java.lang.NoClassDefFoundError") || text.contains("unable to resolve class")) { + if (text.contains("java.lang.NoClassDefFoundError") || text.contains("java.lang.TypeNotPresentException") || text.contains("unable to resolve class")) { LOG.debug("Resolve issue: " + message); return true; } diff --git a/plugins/groovy/jps-plugin/src/org/jetbrains/jps/incremental/groovy/JpsGroovycRunner.java b/plugins/groovy/jps-plugin/src/org/jetbrains/jps/incremental/groovy/JpsGroovycRunner.java index 5eafabac40e8..8cc03b0ef4da 100644 --- a/plugins/groovy/jps-plugin/src/org/jetbrains/jps/incremental/groovy/JpsGroovycRunner.java +++ b/plugins/groovy/jps-plugin/src/org/jetbrains/jps/incremental/groovy/JpsGroovycRunner.java @@ -228,12 +228,12 @@ public abstract class JpsGroovycRunner createFiles = { String prefix -> + def genParam = myFixture.addFileToProject(prefix + "GenParam.java", "class GenParam {}") + myFixture.addFileToProject(prefix + "Intf.java", "class Intf {}") + myFixture.addFileToProject(prefix + "SuperFoo.java", "class SuperFoo extends Intf {}") + def fooGroovy = myFixture.addFileToProject(prefix + "Foo.groovy", "class Foo extends SuperFoo {}") + return { + touch(genParam.virtualFile) + touch(fooGroovy.virtualFile) + myFixture.addFileToProject(prefix + "Bar.java", "class Bar extends Foo { }") + } as Runnable + } + + addGroovyLibrary(addModule('mod2', true)) + + def touch1 = createFiles('') + def touch2 = createFiles('mod2/') + + assertEmpty(make()) + + touch1.run() + touch2.run() + + assert !make().find { it.category == CompilerMessageCategory.ERROR } + } + static class GroovycTest extends GroovyCompilerTest { void "test navigate from stub to source"() { GroovyFile groovyFile = (GroovyFile) myFixture.addFileToProject("a.groovy", "class Groovy3 { InvalidType type }") diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/inspections/GrChangeToOperatorTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/inspections/GrChangeToOperatorTest.groovy index a5f5cf6a6dc1..9e2dfbbc8daf 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/inspections/GrChangeToOperatorTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/inspections/GrChangeToOperatorTest.groovy @@ -16,179 +16,193 @@ package org.jetbrains.plugins.groovy.inspections import com.intellij.testFramework.LightProjectDescriptor -import org.intellij.lang.annotations.Language +import groovy.transform.CompileStatic import org.jetbrains.plugins.groovy.GroovyLightProjectDescriptor import org.jetbrains.plugins.groovy.LightGroovyTestCase import org.jetbrains.plugins.groovy.codeInspection.changeToOperator.ChangeToOperatorInspection +import org.jetbrains.plugins.groovy.lang.psi.GroovyFile +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrBinaryExpression +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrUnaryExpression -import java.util.regex.Pattern - -import static org.jetbrains.plugins.groovy.GroovyFileType.GROOVY_FILE_TYPE -import static org.jetbrains.plugins.groovy.util.TestUtils.CARET_MARKER - +@CompileStatic class GrChangeToOperatorTest extends LightGroovyTestCase { final LightProjectDescriptor projectDescriptor = GroovyLightProjectDescriptor.GROOVY_LATEST - def inspection = new ChangeToOperatorInspection() + final ChangeToOperatorInspection inspection = new ChangeToOperatorInspection() + + @Override + void setUp() throws Exception { + super.setUp() + fixture.with { + addFileToProject 'Operators.groovy', '''\ +class Operators { + def bitwiseNegate() { null } + def negative() { null } + def positive() { null } + def call() { null } + def next() { null } + def previous() { null } + def plus(b) { null } + def minus(b) { null } + def multiply(b) { null } + def power(b) { null } + def div(b) { null } + def mod(b) { null } + def or(b) { null } + def and(b) { null } + def xor(b) { null } + def leftShift(b) { null } + def rightShift(b) { null } + def rightShiftUnsigned(b) { null } + def asType(b) { null } + def getAt(b) { null } + def putAt(b, c) { null } + boolean asBoolean() { true } + boolean isCase(b) { true } + boolean equals(b) { true } + int compareTo(b) { 0 } +} +''' + enableInspections inspection + } + } void testSimpleUnaryExpression() { - assertValid(/a.bitwiseNegate()/, /~a/) - assertValid(/a.negative()/, /-a/) - assertValid(/a.positive()/, /+a/) -// assertValid(/a.call()/, /a()/) - assertValid(/a.next()/, /++a/) - assertValid(/a.previous()/, /--a/) + doTest "a.bitwiseNegate()", "~a" + doTest "a.negative()", "-a" + doTest "a.positive()", "+a" + doTest "a.next()", "++a" + doTest "a.previous()", "--a" } void testNegatableUnaryExpression() { - assertValid(/a.asBoolean()/, /!!a/) - assertValid(/!a.asBoolean()/, /!a/) - - assertValid(/if (${_}a.asBoolean()${_});/, /a/) - assertValid(/if (${_}!a.asBoolean()${_});/, /!a/) - } - - void testComplexNegatableUnaryExpression() { - assertValid(/if (${_}'a'.intern().asBoolean()${_});/, /'a'.intern()/) + doTest "a.asBoolean()", "!!a" + doTest "!a.asBoolean()", "!a" + doTest "if (a.asBoolean());", "if (a);" + doTest "if (!a.asBoolean());", "if (!a);" + doTest "if ('a'.intern().asBoolean());", "if ('a'.intern());" } void testNegatedOption() { inspection.useDoubleNegation = false - - assertValid(/a.asBoolean()/) - assertValid(/!a.asBoolean()/, /!a/) - - assertValid(/if (${_}a.asBoolean()${_});/, /a/) - assertValid(/if (${_}!a.asBoolean()${_});/, /!a/) - } - - void testComplexNegatedOption() { - inspection.useDoubleNegation = false - - assertValid(/if (${_}'a'.intern().asBoolean()${_});/, /'a'.intern()/) + doTest "a.asBoolean()" + doTest "!a.asBoolean()", "!a" + doTest "if (a.asBoolean());", "if (a);" + doTest "if (!a.asBoolean());", "if (!a);" + doTest "if ('a'.intern().asBoolean());", "if ('a'.intern());" } void testSimpleBinaryExpression() { - assertValid(/a.minus(b)/, /a - b/) - assertValid(/a.plus(b)/, /a + b/) - assertValid(/a.power(b)/, /a**b/) - assertValid(/a.div(b)/, $/a / b/$) - assertValid(/a.mod(b)/, /a % b/) - assertValid(/a.or(b)/, /a | b/) - assertValid(/a.and(b)/, /a & b/) - assertValid(/a.xor(b)/, /a ^ b/) - assertValid(/a.leftShift(b)/, /a << b/) - assertValid(/a.rightShift(b)/, /a >> b/) - assertValid(/a.rightShiftUnsigned(b)/, /a >>> b/) - - assertValid(/a.asType(String)/, /a as String/) - assertValid(/a.multiply(b)/, /a * b/) - - assertValid(/(a.toString() as Operators).minus(b.hashCode())/, /(a.toString() as Operators) - b.hashCode()/) - - assertValid(/!${_}a.asType(String)${_}/, /(a as String)/) - - assertValid(/${_}a.xor((a.b+1) == b)${_} == a/, /(a ^ ((a.b + 1) == b))/) + [ + "a.plus(b)" : "a + b", + "a.minus(b)" : "a - b", + "a.multiply(b)" : "a * b", + "a.div(b)" : "a / b", + "a.power(b)" : "a**b", + "a.mod(b)" : "a % b", + "a.or(b)" : "a | b", + "a.and(b)" : "a & b", + "a.xor(b)" : "a ^ b", + "a.leftShift(b)" : "a << b", + "a.rightShift(b)" : "a >> b", + "a.rightShiftUnsigned(b)": "a >>> b", + "a.asType(String)" : "a as String", + "!a.asType(String)" : "!(a as String)", + ].each { + doTest it.key, it.value + } } void testComplexBinaryExpression() { - assertValid(/b.isCase(a)/, /a in b/) - assertValid(/if (${_}[1, 2, 3].isCase(2-1)${_});/, /(2 - 1) in [1, 2, 3]/) - assertValid(/def x = ${_}"1".plus(1)${_}/, /"1" + 1/) - assertValid(/("1" + 1).plus(1)/, /("1" + 1) + 1/) - assertValid(/!a.toString().asBoolean()/, /!a.toString()/) + [ + "(a.toString() as Operators).minus(b.hashCode())": "(a.toString() as Operators) - b.hashCode()", + "b.isCase(a)" : "a in b", + "if ([1, 2, 3].isCase(2-1));" : "if ((2 - 1) in [1, 2, 3]);", + 'def x = "1".plus(1)' : 'def x = "1" + 1', + '("1" + 1).plus(1)' : '("1" + 1) + 1', + '!a.toString().asBoolean()' : '!a.toString()', + "a.xor((a.b + 1) == b) == a" : "(a ^ ((a.b + 1) == b)) == a", + ].each { + doTest it.key, it.value + } } void testNegatableBinaryExpression() { - assertValid(/a.equals(b)/, /a == b/) - assertValid(/!a.equals(b)/, /a != b/) + doTest "a.equals(b)", "a == b" + doTest "!a.equals(b)", "a != b" } void testComplexNegatableBinaryExpression() { - assertValid(/!(1.toString().replace('1', '2')+"").equals(2.toString())/, /(1.toString().replace('1', '2') + "") != 2.toString()/) + doTest(/!(1.toString().replace('1', '2')+"").equals(2.toString())/, /(1.toString().replace('1', '2') + "") != 2.toString()/) } void testCompareTo() { - assertValid(/a.compareTo(b)/, /a <=> b/) - assertValid(/a.compareTo(b) < 0/, /a < b/) - assertValid(/a.compareTo(b) <= 0/, /a <= b/) - assertValid(/a.compareTo(b) == 0/, /a == b/) - assertValid(/a.compareTo(b) != 0/, /a != b/) - assertValid(/a.compareTo(b) >= 0/, /a >= b/) - assertValid(/a.compareTo(b) > 0/, /a > b/) - - assertValid(/if (${_}(2-1).compareTo(3) > 0${_});/, /(2 - 1) > 3/) + doTest "a.compareTo(b)", "a <=> b" + doTest "a.compareTo(b) < 0", "a < b" + doTest "a.compareTo(b) <= 0", "a <= b" + doTest "a.compareTo(b) == 0", "a == b" + doTest "a.compareTo(b) != 0", "a != b" + doTest "a.compareTo(b) >= 0", "a >= b" + doTest "a.compareTo(b) > 0", "a > b" + doTest "if ((2-1).compareTo(3) > 0);", /if ((2 - 1) > 3);/ } void testCompareToOption() { inspection.shouldChangeCompareToEqualityToEquals = false - assertValid(/a.compareTo(b) == 0/) - assertValid(/a.compareTo(b) != 0/) + doTest "a.compareTo(b) == 0" + doTest "a.compareTo(b) != 0" } void testGetAndPut() { - assertValid(/a.getAt(b)/, /a[b]/) - assertValid(/a.putAt(b, 'c')/, /a[b] = 'c'/) - - assertValid(/a.putAt(b, 'c'*2)/, /a[b] = ('c' * 2)/) + doTest "a.getAt(b)", "a[b]" + doTest "a.putAt(b, 'c')", "a[b] = 'c'" + doTest "a.putAt(b, 'c'*2)", "a[b] = ('c' * 2)" } - final _ = '/*placeholder*/' - @Language('Groovy') final DECLARATIONS = ''' - @SuppressWarnings("GrMethodMayBeStatic") - class Operators { - def bitwiseNegate() { null } - def negative() { null } - def positive() { null } - def call() { null } - def next() { null } - def previous() { null } - def plus(b) { null } - def minus(b) { null } - def multiply(b) { null } - def power(b) { null } - def div(b) { null } - def mod(b) { null } - def or(b) { null } - def and(b) { null } - def xor(b) { null } - def leftShift(b) { null } - def rightShift(b) { null } - def rightShiftUnsigned(b) { null } - def asType(b) { null } - def getAt(b) { null } - def putAt(b, c) { null } + final String DECLARATIONS = 'def (Operators a, Operators b) = [null, null]\n' - boolean asBoolean() { true } - boolean isCase(b) { true } - boolean equals(b) { true } - int compareTo(b) { 0 } + private void doTest(String before, String after = null) { + fixture.with { + configureByText '_.groovy', "$DECLARATIONS$before" + moveCaret() + def intentions = filterAvailableIntentions('Replace ') + if (after) { + assert intentions + launchAction intentions.first() + checkResult "$DECLARATIONS$after" + } + else { + assert !intentions + } + } + } + + private void moveCaret() { + def statement = (fixture.file as GroovyFile).statements.last() + GrMethodCall call = null + + if (statement instanceof GrMethodCall) { + call = statement as GrMethodCall + } + else if (statement instanceof GrUnaryExpression) { + def operand = (statement as GrUnaryExpression).operand + if (operand instanceof GrMethodCall) { + call = operand as GrMethodCall + } + } + else if (statement instanceof GrBinaryExpression) { + def left = (statement as GrBinaryExpression).leftOperand + if (left instanceof GrMethodCall) { + call = left as GrMethodCall + } } - def (Operators a, Operators b) = [null, null] - ''' - - private void assertValid(@Language('Groovy') String text, @Language('Groovy') String methodReplacement) { - def (prefix, method, suffix) = getMessage(text) - configure("${prefix}${CARET_MARKER}${method}${suffix}") - myFixture.launchAction(myFixture.findSingleIntention("Change '")) - myFixture.checkResult("${DECLARATIONS} ${prefix}${methodReplacement}${suffix}") - } - - private void assertValid(@Language('Groovy') String text) { - configure(text) - assertEmpty myFixture.getAvailableIntentions() - } - - private configure(@Language('Groovy') String text) { - myFixture.enableInspections(inspection) - myFixture.configureByText(GROOVY_FILE_TYPE, "${DECLARATIONS} ${text}") - } - - private getMessage(String text) { - def _ = Pattern.quote(_) - def (__, prefix, method, suffix) = (text =~ /^(?:(.*?)${_})?(.+?)(?:${_}(.*?))?$/)[0] - [prefix ?: '', method, suffix ?: ''] + if (call) { + def invoked = call.invokedExpression as GrReferenceExpression + fixture.editor.caretModel.moveToOffset(invoked.referenceNameElement.textRange.startOffset) + } } } \ No newline at end of file diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/psi/GroovyModificationTrackersTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/psi/GroovyModificationTrackersTest.groovy new file mode 100644 index 000000000000..551d696394ff --- /dev/null +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/psi/GroovyModificationTrackersTest.groovy @@ -0,0 +1,87 @@ +/* + * 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 org.jetbrains.plugins.groovy.lang.psi + +import com.intellij.psi.PsiDocumentManager +import com.intellij.testFramework.LightProjectDescriptor +import groovy.transform.CompileStatic +import groovy.transform.TypeCheckingMode +import org.jetbrains.plugins.groovy.GroovyLightProjectDescriptor +import org.jetbrains.plugins.groovy.LightGroovyTestCase + +@CompileStatic +class GroovyModificationTrackersTest extends LightGroovyTestCase { + + LightProjectDescriptor projectDescriptor = GroovyLightProjectDescriptor.GROOVY_LATEST + + void 'test class method'() { + doTest '''\ +class A { + def foo() { } +} +''', false + } + + void 'test class initializer'() { + doTest '''\ +class A { + { + + } +} +''', false + } + + void 'test class body'() { + doTest '''\ +class A { + +} +''', true + } + + void 'test script body'() { + doTest '', false + } + + void 'test script variable'() { + doTest 'def a= 1', true + } + + @CompileStatic(TypeCheckingMode.SKIP) + void doTest(String text, boolean shouldChange) { + fixture.configureByText '_.groovy', text + def (beforeStructure, beforeOutOfCodeBlock) = getModificationCounts() + fixture.type " " + PsiDocumentManager.getInstance(project).commitDocument(editor.document) + def (afterStructure, afterOutOfCodeBlock) = getModificationCounts() + if (shouldChange) { + assert beforeStructure < afterStructure + assert beforeOutOfCodeBlock < afterOutOfCodeBlock + } + else { + assert beforeStructure == afterStructure + assert beforeOutOfCodeBlock == afterOutOfCodeBlock + } + } + + @CompileStatic + List getModificationCounts() { + psiManager.modificationTracker.with { + [javaStructureModificationCount, outOfCodeBlockModificationCount] + } + } +} diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/transformations/BaseScriptTransformationSupportTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/transformations/BaseScriptTransformationSupportTest.groovy index 4e2f57a35599..da0c653327d5 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/transformations/BaseScriptTransformationSupportTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/transformations/BaseScriptTransformationSupportTest.groovy @@ -41,8 +41,12 @@ import groovy.transform.BaseScript $text """) as GroovyFileImpl + file.treeElementPointer = null + def clazz = fixture.findClass('Zzz') assert clazz instanceof GroovyScriptClass + assert !file.contentsLoaded + assert InheritanceUtil.isInheritor(clazz as PsiClass, 'MyBaseScript') assert !file.contentsLoaded } diff --git a/plugins/java-i18n/src/com/intellij/codeInspection/SuspiciousLocalesLanguagesInspection.java b/plugins/java-i18n/src/com/intellij/codeInspection/SuspiciousLocalesLanguagesInspection.java index 85d8faf5a5be..d570783a1bb8 100644 --- a/plugins/java-i18n/src/com/intellij/codeInspection/SuspiciousLocalesLanguagesInspection.java +++ b/plugins/java-i18n/src/com/intellij/codeInspection/SuspiciousLocalesLanguagesInspection.java @@ -23,7 +23,6 @@ import com.intellij.lang.properties.psi.PropertiesFile; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.InputValidator; import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.WriteExternalException; import com.intellij.openapi.util.text.StringUtil; @@ -33,7 +32,6 @@ import com.intellij.ui.AnActionButton; import com.intellij.ui.AnActionButtonRunnable; import com.intellij.ui.ToolbarDecorator; import com.intellij.ui.components.JBList; -import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashSet; import org.jdom.Element; @@ -146,6 +144,11 @@ public class SuspiciousLocalesLanguagesInspection extends BaseLocalInspectionToo return PropertiesBundle.message("dissociate.resource.bundle.quick.fix.name"); } + @Override + public boolean startInWriteAction() { + return false; + } + @Override public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { DissociateResourceBundleAction.dissociate(Collections.singleton(myResourceBundle), project); diff --git a/plugins/java-i18n/src/com/intellij/codeInspection/i18n/I18nInspection.java b/plugins/java-i18n/src/com/intellij/codeInspection/i18n/I18nInspection.java index 01e66540a132..a70e455f0da1 100644 --- a/plugins/java-i18n/src/com/intellij/codeInspection/i18n/I18nInspection.java +++ b/plugins/java-i18n/src/com/intellij/codeInspection/i18n/I18nInspection.java @@ -28,7 +28,6 @@ import com.intellij.codeInspection.*; import com.intellij.codeInspection.ex.BaseLocalInspectionTool; import com.intellij.ide.util.TreeClassChooser; import com.intellij.ide.util.TreeClassChooserFactory; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Document; import com.intellij.openapi.extensions.ExtensionPoint; import com.intellij.openapi.extensions.Extensions; @@ -438,15 +437,17 @@ public class I18nInspection extends BaseLocalInspectionTool { } @Override - public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) { - //do it later because it is invoked from write action - ApplicationManager.getApplication().invokeLater(() -> { - PsiElement element = descriptor.getPsiElement(); - if (!(element instanceof PsiExpression)) return; + public boolean startInWriteAction() { + return false; + } - PsiExpression[] expressions = {(PsiExpression)element}; - new IntroduceConstantHandler().invoke(project, expressions); - }, project.getDisposed()); + @Override + public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) { + PsiElement element = descriptor.getPsiElement(); + if (!(element instanceof PsiExpression)) return; + + PsiExpression[] expressions = {(PsiExpression)element}; + new IntroduceConstantHandler().invoke(project, expressions); } }; } diff --git a/plugins/junit/src/com/intellij/execution/junit/JUnitConfiguration.java b/plugins/junit/src/com/intellij/execution/junit/JUnitConfiguration.java index 7d5fe0b9098e..8dd209be8bab 100644 --- a/plugins/junit/src/com/intellij/execution/junit/JUnitConfiguration.java +++ b/plugins/junit/src/com/intellij/execution/junit/JUnitConfiguration.java @@ -351,8 +351,7 @@ public class JUnitConfiguration extends JavaTestConfigurationBase { final Element patternsElement = element.getChild(PATTERNS_EL_NAME); if (patternsElement != null) { final LinkedHashSet tests = new LinkedHashSet<>(); - for (Object o : patternsElement.getChildren(PATTERN_EL_NAME)) { - Element patternElement = (Element)o; + for (Element patternElement : patternsElement.getChildren(PATTERN_EL_NAME)) { tests.add(patternElement.getAttributeValue(TEST_CLASS_ATT_NAME)); } myData.setPatterns(tests); diff --git a/plugins/junit5_rt/src/com/intellij/junit5/JUnit5TestExecutionListener.java b/plugins/junit5_rt/src/com/intellij/junit5/JUnit5TestExecutionListener.java index ee9f6de00ed1..2117fae7dec0 100644 --- a/plugins/junit5_rt/src/com/intellij/junit5/JUnit5TestExecutionListener.java +++ b/plugins/junit5_rt/src/com/intellij/junit5/JUnit5TestExecutionListener.java @@ -16,6 +16,7 @@ package com.intellij.junit5; import com.intellij.junit4.ExpectedPatterns; +import com.intellij.junit4.JUnit4TestListener; import com.intellij.rt.execution.junit.ComparisonFailureData; import com.intellij.rt.execution.junit.MapSerializerUtil; import org.junit.platform.engine.TestExecutionResult; @@ -34,7 +35,6 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.HashMap; import java.util.Map; -import java.util.Optional; import java.util.Set; public class JUnit5TestExecutionListener implements TestExecutionListener { @@ -158,21 +158,21 @@ public class JUnit5TestExecutionListener implements TestExecutionListener { else if (status == TestExecutionResult.Status.ABORTED) { messageName = MapSerializerUtil.TEST_IGNORED; } - if (messageName != null && myFinishCount == 0) { + if (messageName != null) { + if (status == TestExecutionResult.Status.FAILED) { + myPrintStream.println("\n##teamcity[testStarted name=\'" + JUnit4TestListener.CLASS_CONFIGURATION + "\']"); + testFailure(JUnit4TestListener.CLASS_CONFIGURATION, JUnit4TestListener.CLASS_CONFIGURATION, messageName, throwableOptional, 0, reason, true); + myPrintStream.println("\n##teamcity[testFinished name=\'" + JUnit4TestListener.CLASS_CONFIGURATION + "\']"); + } + final Set descendants = myTestPlan.getDescendants(testIdentifier); - if (!descendants.isEmpty()) { + if (!descendants.isEmpty() && myFinishCount == 0) { for (TestIdentifier childIdentifier : descendants) { testStarted(childIdentifier); - testFailure(childIdentifier, messageName, throwableOptional, 0, reason, true); + testFailure(childIdentifier, MapSerializerUtil.TEST_IGNORED, status == TestExecutionResult.Status.ABORTED ? throwableOptional : null, 0, reason, status == TestExecutionResult.Status.ABORTED); testFinished(childIdentifier, 0); } } - else { - testStarted(testIdentifier); - testFailure(testIdentifier, messageName, throwableOptional, 0, reason, true); - testFinished(testIdentifier, 0); - myFinishCount++; - } } myPrintStream.println("##teamcity[testSuiteFinished " + idAndName(testIdentifier, displayName) + "\']"); } @@ -189,16 +189,26 @@ public class JUnit5TestExecutionListener implements TestExecutionListener { private void testFinished(TestIdentifier testIdentifier, long duration) { myPrintStream.println("\n##teamcity[testFinished" + idAndName(testIdentifier) + (duration > 0 ? "\' duration=\'" + Long.toString(duration) : "") + "\']"); } - + private void testFailure(TestIdentifier testIdentifier, - String messageName, + String messageName, Throwable ex, - long duration, + long duration, + String reason, + boolean includeThrowable) { + testFailure(testIdentifier.getDisplayName(), testIdentifier.getUniqueId().toString(), messageName, ex, duration, reason, includeThrowable); + } + + private void testFailure(String methodName, + String id, + String messageName, + Throwable ex, + long duration, String reason, boolean includeThrowable) { final Map attrs = new HashMap<>(); - attrs.put("name", testIdentifier.getDisplayName()); - attrs.put("id", testIdentifier.getUniqueId().toString()); + attrs.put("name", methodName); + attrs.put("id", id); if (duration > 0) { attrs.put("duration", Long.toString(duration)); } @@ -210,7 +220,7 @@ public class JUnit5TestExecutionListener implements TestExecutionListener { ComparisonFailureData failureData = null; if (ex instanceof MultipleFailuresError && ((MultipleFailuresError)ex).hasFailures()) { for (AssertionError assertionError : ((MultipleFailuresError)ex).getFailures()) { - testFailure(testIdentifier, messageName, assertionError, duration, reason, false); + testFailure(methodName, id, messageName, assertionError, duration, reason, false); } } else if (ex instanceof AssertionFailedError && ((AssertionFailedError)ex).isActualDefined() && ((AssertionFailedError)ex).isExpectedDefined()) { diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/annotator/MavenDomAnnotator.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/annotator/MavenDomAnnotator.java index d671b9efde2c..dbdaca6a6399 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/annotator/MavenDomAnnotator.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/annotator/MavenDomAnnotator.java @@ -85,6 +85,11 @@ public class MavenDomAnnotator implements DomElementsAnnotator { return MavenDomBundle.message("inspection.group"); } + @Override + public boolean startInWriteAction() { + return false; + } + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { new OpenFileDescriptor(project, myFile).navigate(true); } diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/converters/MavenArtifactCoordinatesConverter.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/converters/MavenArtifactCoordinatesConverter.java index 52cf0a8a140c..d5d2666a1aac 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/converters/MavenArtifactCoordinatesConverter.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/dom/converters/MavenArtifactCoordinatesConverter.java @@ -18,7 +18,6 @@ package org.jetbrains.idea.maven.dom.converters; import com.intellij.codeInspection.LocalQuickFix; import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.RecursionManager; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; @@ -166,6 +165,11 @@ public abstract class MavenArtifactCoordinatesConverter extends ResolvingConvert return MavenDomBundle.message("fix.update.indices"); } + @Override + public boolean startInWriteAction() { + return false; + } + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { MavenProjectIndicesManager.getInstance(project).scheduleUpdateAll(); } diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/library/RepositoryAddLibraryAction.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/library/RepositoryAddLibraryAction.java index 58cbc221ffe5..b3af071286e7 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/library/RepositoryAddLibraryAction.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/library/RepositoryAddLibraryAction.java @@ -35,6 +35,11 @@ public class RepositoryAddLibraryAction extends IntentionAndQuickFixAction { return ProjectBundle.message("maven.library.family.name"); } + @Override + public boolean startInWriteAction() { + return false; + } + @Override public void applyFix(@NotNull Project project, PsiFile file, @Nullable Editor editor) { addLibraryToModule(libraryDescription, module); diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/library/RepositoryAttachHandler.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/library/RepositoryAttachHandler.java index 6f90373382bb..0027df2096b0 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/library/RepositoryAttachHandler.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/library/RepositoryAttachHandler.java @@ -24,8 +24,6 @@ import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.JavadocOrderRootType; import com.intellij.openapi.roots.OrderRootType; @@ -344,9 +342,7 @@ public class RepositoryAttachHandler { finally { manager.release(embedder); if (!cancelled && resultProcessor != null) { - ApplicationManager.getApplication().invokeAndWait(() -> DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, - () -> resultProcessor.process( - new ArrayList<>(result))), indicator.getModalityState()); + ApplicationManager.getApplication().invokeAndWait(() -> resultProcessor.process(new ArrayList<>(result))); } } } diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/persistent/SmallMapSerializer.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SmallMapSerializer.java similarity index 95% rename from platform/vcs-api/src/com/intellij/openapi/vcs/persistent/SmallMapSerializer.java rename to plugins/svn4idea/src/org/jetbrains/idea/svn/SmallMapSerializer.java index 436747a5df73..2f2aca5181f4 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/persistent/SmallMapSerializer.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SmallMapSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * 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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.openapi.vcs.persistent; +package org.jetbrains.idea.svn; import com.intellij.openapi.Forceable; import com.intellij.openapi.diagnostic.Logger; @@ -40,7 +40,7 @@ public class SmallMapSerializer implements Forceable { private final KeyDescriptor myKeyDescriptor; private final DataExternalizer myValueExternalizer; private boolean myDirty; - private Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.persistent.SmallMapSerializer"); + private Logger LOG = Logger.getInstance(SmallMapSerializer.class); public SmallMapSerializer(final File file, final KeyDescriptor keyDescriptor, final DataExternalizer valueExternalizer) { myFile = file; diff --git a/platform/core-api/src/com/intellij/openapi/project/DumbPermissionService.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBaseContentRevision.java similarity index 53% rename from platform/core-api/src/com/intellij/openapi/project/DumbPermissionService.java rename to plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBaseContentRevision.java index 8903b90636fa..d795c15178a1 100644 --- a/platform/core-api/src/com/intellij/openapi/project/DumbPermissionService.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBaseContentRevision.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * 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. @@ -13,15 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.openapi.project; +package org.jetbrains.idea.svn; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.changes.ContentRevision; import org.jetbrains.annotations.NotNull; -/** - * A helper class for {@link DumbService} - */ -interface DumbPermissionService { +public abstract class SvnBaseContentRevision implements ContentRevision { - void allowStartingDumbModeInside(@NotNull final DumbModePermission permission, @NotNull Runnable runnable); + @NotNull protected final SvnVcs myVcs; + @NotNull protected final FilePath myFile; + protected SvnBaseContentRevision(@NotNull SvnVcs vcs, @NotNull FilePath file) { + myVcs = vcs; + myFile = file; + } + + @NotNull + @Override + public FilePath getFile() { + return myFile; + } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBundle.properties b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBundle.properties index 902b1a529701..d00dfa556813 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBundle.properties +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBundle.properties @@ -597,7 +597,6 @@ action.working.copies.map.correct.roots.button.text=Correct &Roots action.working.copies.map.correct.warning.text=Some folders marked in 'Project Settings | Version Control' as under Subversion control,\n\ are unversioned (Versioned folders may be under them).\n\ You can use 'Correct Roots' button to perform auto correction. -action.Subversion.properties.diff.name=Properties Diff action.Subversion.properties.difference.diff.title=Subversion properties difference: {0} action.Subversion.properties.difference.diff.for.move.title=Subversion properties difference: {0} -> {1} message.Subversion.files.can.not.be.added.to.subversion.are.not.under.working.copy.text=File(s) {0} can not be added to Subversion control since they are not under working copy. diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProviderContext.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProviderContext.java index b4363e3482bd..450c4c3794c8 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProviderContext.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProviderContext.java @@ -30,12 +30,10 @@ import com.intellij.util.containers.ContainerUtil; import com.intellij.vcsUtil.VcsUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.idea.svn.actions.AbstractShowPropertiesDiffAction; import org.jetbrains.idea.svn.api.NodeKind; import org.jetbrains.idea.svn.branchConfig.SvnBranchConfigurationManager; import org.jetbrains.idea.svn.history.SimplePropertyRevision; import org.jetbrains.idea.svn.info.Info; -import org.jetbrains.idea.svn.properties.PropertyData; import org.jetbrains.idea.svn.status.Status; import org.jetbrains.idea.svn.status.StatusType; import org.tmatesoft.svn.core.SVNException; @@ -46,6 +44,8 @@ import java.io.File; import java.util.List; import java.util.Map; +import static org.jetbrains.idea.svn.actions.ShowPropertiesDiffAction.getPropertyList; + class SvnChangeProviderContext implements StatusReceiver { private static final Logger LOG = Logger.getInstance("org.jetbrains.idea.svn.SvnChangeProviderContext"); @@ -380,7 +380,7 @@ class SvnChangeProviderContext implements StatusReceiver { ContentRevision contentRevision = isBeforeRevision ? change.getBeforeRevision() : change.getAfterRevision(); SVNRevision revision = isBeforeRevision ? SVNRevision.BASE : SVNRevision.WORKING; - return new SimplePropertyRevision(getProperties(file, revision), path, getRevisionNumber(contentRevision)); + return new SimplePropertyRevision(getPropertyList(myVcs, file, revision), path, getRevisionNumber(contentRevision)); } @Nullable @@ -388,11 +388,6 @@ class SvnChangeProviderContext implements StatusReceiver { return revision != null ? revision.getRevisionNumber().asString() : null; } - @NotNull - private List getProperties(@NotNull File file, @NotNull SVNRevision revision) throws SVNException { - return AbstractShowPropertiesDiffAction.getPropertyList(myVcs, file, revision); - } - @NotNull private ConflictState getState(@Nullable Status svnStatus) { ConflictState result = svnStatus != null ? ConflictState.from(svnStatus) : ConflictState.none; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnContentRevision.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnContentRevision.java index 00b57d3ba14d..83201771bb85 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnContentRevision.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnContentRevision.java @@ -17,12 +17,9 @@ package org.jetbrains.idea.svn; import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.Throwable2Computable; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.VcsKey; import com.intellij.openapi.vcs.changes.ByteBackedContentRevision; -import com.intellij.openapi.vcs.changes.MarkerVcsContentRevision; import com.intellij.openapi.vcs.history.VcsRevisionNumber; import com.intellij.openapi.vcs.impl.ContentRevisionCache; import com.intellij.openapi.vcs.impl.CurrentRevisionProvider; @@ -36,13 +33,8 @@ import org.tmatesoft.svn.core.wc2.SvnTarget; import java.io.File; import java.io.IOException; -/** - * @author yole -*/ -public class SvnContentRevision implements ByteBackedContentRevision, MarkerVcsContentRevision { +public class SvnContentRevision extends SvnBaseContentRevision implements ByteBackedContentRevision { - @NotNull private final SvnVcs myVcs; - @NotNull protected final FilePath myFile; @NotNull private final SVNRevision myRevision; /** * this flag is necessary since SVN would not do remote request only if constant SVNRevision.BASE @@ -51,10 +43,9 @@ public class SvnContentRevision implements ByteBackedContentRevision, MarkerVcsC private final boolean myUseBaseRevision; protected SvnContentRevision(@NotNull SvnVcs vcs, @NotNull FilePath file, @NotNull SVNRevision revision, boolean useBaseRevision) { - myVcs = vcs; + super(vcs, file); myRevision = revision; myUseBaseRevision = useBaseRevision; - myFile = file; } @NotNull @@ -104,13 +95,7 @@ public class SvnContentRevision implements ByteBackedContentRevision, MarkerVcsC }).getSecond(); } else { return ContentRevisionCache.getOrLoadAsBytes(myVcs.getProject(), myFile, getRevisionNumber(), myVcs.getKeyInstanceMethod(), - ContentRevisionCache.UniqueType.REPOSITORY_CONTENT, - new Throwable2Computable() { - @Override - public byte[] compute() throws VcsException, IOException { - return getUpToDateBinaryContent(); - } - }); + ContentRevisionCache.UniqueType.REPOSITORY_CONTENT, () -> getUpToDateBinaryContent()); } } catch (IOException e) { @@ -128,11 +113,6 @@ public class SvnContentRevision implements ByteBackedContentRevision, MarkerVcsC SVNRevision.UNDEFINED); } - @NotNull - public FilePath getFile() { - return myFile; - } - @NotNull public VcsRevisionNumber getRevisionNumber() { return new SvnRevisionNumber(myRevision); @@ -142,9 +122,4 @@ public class SvnContentRevision implements ByteBackedContentRevision, MarkerVcsC public String toString() { return myFile.getPath(); } - - @Override - public VcsKey getVcsKey() { - return SvnVcs.getKey(); - } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileUrlMappingImpl.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileUrlMappingImpl.java index 4a0cb9081787..6f4a46aa2648 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileUrlMappingImpl.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileUrlMappingImpl.java @@ -24,7 +24,6 @@ import com.intellij.openapi.project.DumbAwareRunnable; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vcs.ProjectLevelVcsManager; -import com.intellij.openapi.vcs.ThreadLocalDefendedInvoker; import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl; import com.intellij.openapi.vcs.impl.VcsInitObject; import com.intellij.openapi.vfs.LocalFileSystem; @@ -53,21 +52,35 @@ public class SvnFileUrlMappingImpl implements SvnFileUrlMapping, PersistentState // grouped; if there are several mappings one under another, will return the upmost private final SvnMapping myMoreRealMapping; private final List myErrorRoots; - private final MyRootsHelper myHelper; + @NotNull private final MyRootsHelper myRootsHelper; private final Project myProject; private final NestedCopiesHolder myNestedCopiesHolder; private boolean myInitialized; private boolean myInitedReloaded; - private static class MyRootsHelper extends ThreadLocalDefendedInvoker { - private final ProjectLevelVcsManager myPlVcsManager; + private static class MyRootsHelper { + @NotNull private final static ThreadLocal ourInProgress = ThreadLocal.withInitial(() -> Boolean.FALSE); + @NotNull private final Project myProject; + @NotNull private final ProjectLevelVcsManager myVcsManager; - private MyRootsHelper(final ProjectLevelVcsManager vcsManager) { - myPlVcsManager = vcsManager; + private MyRootsHelper(@NotNull Project project, @NotNull ProjectLevelVcsManager vcsManager) { + myProject = project; + myVcsManager = vcsManager; } - protected VirtualFile[] execute(Project project) { - return myPlVcsManager.getRootsUnderVcs(SvnVcs.getInstance(project)); + @NotNull + public VirtualFile[] execute() { + try { + ourInProgress.set(Boolean.TRUE); + return myVcsManager.getRootsUnderVcs(SvnVcs.getInstance(myProject)); + } + finally { + ourInProgress.set(Boolean.FALSE); + } + } + + public static boolean isInProgress() { + return ourInProgress.get(); } } @@ -81,7 +94,7 @@ public class SvnFileUrlMappingImpl implements SvnFileUrlMapping, PersistentState myMapping = new SvnMapping(); myMoreRealMapping = new SvnMapping(); myErrorRoots = ContainerUtil.newArrayList(); - myHelper = new MyRootsHelper(vcsManager); + myRootsHelper = new MyRootsHelper(project, vcsManager); myChecker = new SvnCompatibilityChecker(project); myNestedCopiesHolder = new NestedCopiesHolder(); } @@ -183,7 +196,7 @@ public class SvnFileUrlMappingImpl implements SvnFileUrlMapping, PersistentState @NotNull public List convertRoots(@NotNull List result) { - if (ThreadLocalDefendedInvoker.isInside()) return ContainerUtil.newArrayList(result); + if (MyRootsHelper.isInProgress()) return ContainerUtil.newArrayList(result); synchronized (myMonitor) { final List cachedRoots = myMoreRealMapping.getUnderVcsRoots(); @@ -214,7 +227,7 @@ public class SvnFileUrlMappingImpl implements SvnFileUrlMapping, PersistentState } else { final SvnVcs vcs = SvnVcs.getInstance(myProject); - final VirtualFile[] roots = myHelper.executeDefended(myProject); + final VirtualFile[] roots = myRootsHelper.execute(); final SvnRootsDetector rootsDetector = new SvnRootsDetector(vcs, this, myNestedCopiesHolder); // do not send additional request for nested copies when in init state rootsDetector.detectCopyRoots(roots, init(), afterRefreshCallback); @@ -306,8 +319,9 @@ public class SvnFileUrlMappingImpl implements SvnFileUrlMapping, PersistentState } } + @NotNull public VirtualFile[] getNotFilteredRoots() { - return myHelper.executeDefended(myProject); + return myRootsHelper.execute(); } public boolean isEmpty() { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/AbstractShowPropertiesDiffAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/AbstractShowPropertiesDiffAction.java deleted file mode 100644 index c9b089a1b906..000000000000 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/AbstractShowPropertiesDiffAction.java +++ /dev/null @@ -1,318 +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 org.jetbrains.idea.svn.actions; - -import com.intellij.diff.DiffManager; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.progress.PerformInBackgroundOption; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.Task; -import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.Condition; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.VcsDataKeys; -import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.changes.Change; -import com.intellij.openapi.vcs.changes.ChangesUtil; -import com.intellij.openapi.vcs.changes.ContentRevision; -import com.intellij.openapi.vcs.changes.MarkerVcsContentRevision; -import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.idea.svn.SvnBundle; -import org.jetbrains.idea.svn.SvnVcs; -import org.jetbrains.idea.svn.api.Depth; -import org.jetbrains.idea.svn.difftool.properties.SvnPropertiesDiffRequest; -import org.jetbrains.idea.svn.difftool.properties.SvnPropertiesDiffRequest.PropertyContent; -import org.jetbrains.idea.svn.history.SvnRepositoryContentRevision; -import org.jetbrains.idea.svn.properties.PropertyConsumer; -import org.jetbrains.idea.svn.properties.PropertyData; -import org.jetbrains.idea.svn.properties.PropertyValue; -import org.tmatesoft.svn.core.SVNErrorCode; -import org.tmatesoft.svn.core.SVNErrorMessage; -import org.tmatesoft.svn.core.SVNException; -import org.tmatesoft.svn.core.SVNURL; -import org.tmatesoft.svn.core.wc.SVNRevision; -import org.tmatesoft.svn.core.wc2.SvnTarget; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -public abstract class AbstractShowPropertiesDiffAction extends AnAction implements DumbAware { - protected AbstractShowPropertiesDiffAction(String name) { - super(name); - } - - protected abstract DataKey getChangesKey(); - @Nullable - protected abstract SVNRevision getBeforeRevisionValue(final Change change, final SvnVcs vcs) throws SVNException; - @Nullable - protected abstract SVNRevision getAfterRevisionValue(final Change change, final SvnVcs vcs) throws SVNException; - - @Override - public void update(final AnActionEvent e) { - final DataContext dataContext = e.getDataContext(); - final Presentation presentation = e.getPresentation(); - final Change[] data = VcsDataKeys.CHANGES.getData(dataContext); - boolean showAction = checkThatChangesAreUnderSvn(data); - presentation.setVisible(data != null && showAction); - presentation.setEnabled(showAction); - } - - private static boolean checkThatChangesAreUnderSvn(@Nullable Change[] changes) { - boolean result = false; - - if (changes != null) { - result = ContainerUtil.or(changes, new Condition() { - @Override - public boolean value(Change change) { - return isUnderSvn(change.getBeforeRevision()) || isUnderSvn(change.getAfterRevision()); - } - }); - } - - return result; - } - - private static boolean isUnderSvn(@Nullable ContentRevision revision) { - return revision instanceof MarkerVcsContentRevision && SvnVcs.getKey().equals(((MarkerVcsContentRevision)revision).getVcsKey()); - } - - public void actionPerformed(final AnActionEvent e) { - final DataContext dataContext = e.getDataContext(); - final Project project = CommonDataKeys.PROJECT.getData(dataContext); - final Change[] changes = e.getData(getChangesKey()); - - if (! checkThatChangesAreUnderSvn(changes)) { - return; - } - - final Change change = changes[0]; - - final CalculateAndShow worker = new CalculateAndShow(project, change, e.getPresentation().getText()); - ProgressManager.getInstance().run(worker); - } - - private class CalculateAndShow extends Task.Backgroundable { - private final Change myChange; - private List myBeforeContent; - private List myAfterContent; - private SVNRevision myBeforeRevisionValue; - private SVNRevision myAfterRevision; - private Exception myException; - private final String myErrorTitle; - - private CalculateAndShow(@Nullable final Project project, final Change change, final String errorTitle) { - super(project, SvnBundle.message("fetching.properties.contents.progress.title"), true, PerformInBackgroundOption.DEAF); - myChange = change; - myErrorTitle = errorTitle; - } - - public void run(@NotNull final ProgressIndicator indicator) { - final SvnVcs vcs = SvnVcs.getInstance(myProject); - - try { - myBeforeRevisionValue = getBeforeRevisionValue(myChange, vcs); - myAfterRevision = getAfterRevisionValue(myChange, vcs); - - myBeforeContent = getPropertyList(vcs, myChange.getBeforeRevision(), myBeforeRevisionValue); - indicator.checkCanceled(); - // gets exactly WORKING revision property - myAfterContent = getPropertyList(vcs, myChange.getAfterRevision(), myAfterRevision); - } - catch (SVNException exc) { - myException = exc; - } - catch (VcsException exc) { - myException = exc; - } - } - - @Override - public void onSuccess() { - if (myException != null) { - Messages.showErrorDialog(myException.getMessage(), myErrorTitle); - return; - } - if (myBeforeContent != null && myAfterContent != null && myBeforeRevisionValue != null && myAfterRevision != null) { - SvnPropertiesDiffRequest diffRequest; - if (compareRevisions(myBeforeRevisionValue, myAfterRevision) > 0) { - diffRequest = new SvnPropertiesDiffRequest(getDiffWindowTitle(myChange), - new PropertyContent(myAfterContent), new PropertyContent(myBeforeContent), - revisionToString(myAfterRevision), revisionToString(myBeforeRevisionValue)); - } - else { - diffRequest = new SvnPropertiesDiffRequest(getDiffWindowTitle(myChange), - new PropertyContent(myBeforeContent), new PropertyContent(myAfterContent), - revisionToString(myBeforeRevisionValue), revisionToString(myAfterRevision)); - } - DiffManager.getInstance().showDiff(myProject, diffRequest); - } - } - } - - @NotNull - private static String getDiffWindowTitle(@NotNull Change change) { - if (change.isMoved() || change.isRenamed()) { - final FilePath beforeFilePath = ChangesUtil.getBeforePath(change); - final FilePath afterFilePath = ChangesUtil.getAfterPath(change); - - final String beforePath = beforeFilePath == null ? "" : beforeFilePath.getPath(); - final String afterPath = afterFilePath == null ? "" : afterFilePath.getPath(); - return SvnBundle.message("action.Subversion.properties.difference.diff.for.move.title", beforePath, afterPath); - } else { - return SvnBundle.message("action.Subversion.properties.difference.diff.title", ChangesUtil.getFilePath(change).getPath()); - } - } - - private static int compareRevisions(@NotNull SVNRevision revision1, @NotNull SVNRevision revision2) { - if (revision1.equals(revision2)) { - return 0; - } - // working(local) ahead of head - if (SVNRevision.WORKING.equals(revision1)) { - return 1; - } - if (SVNRevision.WORKING.equals(revision2)) { - return -1; - } - if (SVNRevision.HEAD.equals(revision1)) { - return 1; - } - if (SVNRevision.HEAD.equals(revision2)) { - return -1; - } - return revision1.getNumber() > revision2.getNumber() ? 1 : -1; - } - - @NotNull - private static String revisionToString(@Nullable SVNRevision revision) { - return revision == null ? "not exists" : revision.toString(); - } - - private final static String ourPropertiesDelimiter = "\n"; - - @NotNull - private static List getPropertyList(@NotNull SvnVcs vcs, - @Nullable final ContentRevision contentRevision, - @Nullable final SVNRevision revision) - throws SVNException, VcsException { - if (contentRevision == null) { - return Collections.emptyList(); - } - - SvnTarget target; - if (contentRevision instanceof SvnRepositoryContentRevision) { - final SvnRepositoryContentRevision svnRevision = (SvnRepositoryContentRevision)contentRevision; - target = SvnTarget.fromURL(SVNURL.parseURIEncoded(svnRevision.getFullPath()), revision); - } else { - final File ioFile = contentRevision.getFile().getIOFile(); - target = SvnTarget.fromFile(ioFile, revision); - } - - return getPropertyList(vcs, target, revision); - } - - @NotNull - public static List getPropertyList(@NotNull SvnVcs vcs, @NotNull final SVNURL url, @Nullable final SVNRevision revision) - throws VcsException { - return getPropertyList(vcs, SvnTarget.fromURL(url, revision), revision); - } - - @NotNull - public static List getPropertyList(@NotNull SvnVcs vcs, @NotNull final File ioFile, @Nullable final SVNRevision revision) - throws SVNException { - try { - return getPropertyList(vcs, SvnTarget.fromFile(ioFile, revision), revision); - } - catch (VcsException e) { - throw new SVNException(SVNErrorMessage.create(SVNErrorCode.FS_GENERAL, e), e); - } - } - - @NotNull - private static List getPropertyList(@NotNull SvnVcs vcs, @NotNull SvnTarget target, @Nullable SVNRevision revision) - throws VcsException { - final List lines = new ArrayList<>(); - final PropertyConsumer propertyHandler = createHandler(revision, lines); - - vcs.getFactory(target).createPropertyClient().list(target, revision, Depth.EMPTY, propertyHandler); - - return lines; - } - - @NotNull - private static PropertyConsumer createHandler(SVNRevision revision, @NotNull final List lines) { - final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); - if (indicator != null) { - indicator.checkCanceled(); - indicator.setText(SvnBundle.message("show.properties.diff.progress.text.revision.information", revision.toString())); - } - - return new PropertyConsumer() { - public void handleProperty(final File path, final PropertyData property) throws SVNException { - registerProperty(property); - } - - public void handleProperty(final SVNURL url, final PropertyData property) throws SVNException { - registerProperty(property); - } - - public void handleProperty(final long revision, final PropertyData property) throws SVNException { - // revision properties here - } - - private void registerProperty(@NotNull PropertyData property) { - if (indicator != null) { - indicator.checkCanceled(); - indicator.setText2(SvnBundle.message("show.properties.diff.progress.text2.property.information", property.getName())); - } - lines.add(property); - } - }; - } - - @NotNull - public static String toSortedStringPresentation(@NotNull List lines) { - StringBuilder sb = new StringBuilder(); - - Collections.sort(lines, new Comparator() { - public int compare(final PropertyData o1, final PropertyData o2) { - return o1.getName().compareTo(o2.getName()); - } - }); - - for (PropertyData line : lines) { - addPropertyPresentation(line, sb); - } - - return sb.toString(); - } - - private static void addPropertyPresentation(@NotNull PropertyData property, @NotNull StringBuilder sb) { - if (sb.length() != 0) { - sb.append(ourPropertiesDelimiter); - } - sb.append(property.getName()).append("=").append(StringUtil.notNullize(PropertyValue.toString(property.getValue()))); - } -} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/CreateExternalAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/CreateExternalAction.java index 5bd743995302..7623d2fd7e88 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/CreateExternalAction.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/CreateExternalAction.java @@ -17,186 +17,132 @@ package org.jetbrains.idea.svn.actions; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.*; import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.vcsUtil.ActionExecuteHelper; -import com.intellij.vcsUtil.ActionStateConsumer; -import com.intellij.vcsUtil.ActionUpdateHelper; -import com.intellij.vcsUtil.VcsUtil; import org.jetbrains.annotations.NotNull; -import org.jetbrains.idea.svn.SvnBundle; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.SvnProgressCanceller; import org.jetbrains.idea.svn.SvnPropertyKeys; import org.jetbrains.idea.svn.SvnVcs; import org.jetbrains.idea.svn.api.ClientFactory; import org.jetbrains.idea.svn.api.Depth; -import org.jetbrains.idea.svn.api.ProgressEvent; -import org.jetbrains.idea.svn.api.ProgressTracker; -import org.jetbrains.idea.svn.commandLine.CommandUtil; import org.jetbrains.idea.svn.dialogs.SelectCreateExternalTargetDialog; -import org.jetbrains.idea.svn.properties.ExternalsDefinitionParser; import org.jetbrains.idea.svn.properties.PropertyValue; import org.jetbrains.idea.svn.update.UpdateClient; -import org.tmatesoft.svn.core.SVNCancelException; -import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.SvnTarget; import java.io.File; -import java.util.Map; -/** - * Created with IntelliJ IDEA. - * User: Irina.Chernushina - * Date: 7/6/12 - * Time: 7:21 PM - */ +import static com.intellij.openapi.util.text.StringUtil.isEmptyOrSpaces; +import static com.intellij.openapi.vcs.changes.ChangesUtil.getVcsForFile; +import static com.intellij.openapi.vfs.VfsUtilCore.virtualToIoFile; +import static com.intellij.util.ObjectUtils.notNull; +import static com.intellij.util.containers.UtilKt.getIfSingle; +import static org.jetbrains.idea.svn.SvnBundle.message; +import static org.jetbrains.idea.svn.commandLine.CommandUtil.escape; +import static org.jetbrains.idea.svn.properties.ExternalsDefinitionParser.parseExternalsProperty; + public class CreateExternalAction extends DumbAwareAction { public CreateExternalAction() { - super(SvnBundle.message("svn.create.external.below.action"), SvnBundle.message("svn.create.external.below.description"), null); + super(message("svn.create.external.below.action"), message("svn.create.external.below.description"), null); } @Override - public void actionPerformed(AnActionEvent e) { - final ActionExecuteHelper helper = new ActionExecuteHelper(); - checkState(e, helper); - if (! helper.isOk()) return; + public void actionPerformed(@NotNull AnActionEvent e) { + Project project = e.getRequiredData(CommonDataKeys.PROJECT); + VirtualFile file = notNull(getIfSingle(e.getData(VcsDataKeys.VIRTUAL_FILE_STREAM))); + SelectCreateExternalTargetDialog dialog = new SelectCreateExternalTargetDialog(project, file); - final DataContext dc = e.getDataContext(); - final Project project = CommonDataKeys.PROJECT.getData(dc); - final VirtualFile vf = CommonDataKeys.VIRTUAL_FILE.getData(dc); + if (dialog.showAndGet()) { + String url = dialog.getSelectedURL(); + boolean checkout = dialog.isCheckout(); + String target = dialog.getLocalTarget().trim(); - //1 select target - final SelectCreateExternalTargetDialog dialog = new SelectCreateExternalTargetDialog(project, vf); - dialog.show(); - if (DialogWrapper.OK_EXIT_CODE != dialog.getExitCode()) return; - - final String url = dialog.getSelectedURL(); - final boolean checkout = dialog.isCheckout(); - final String target = dialog.getLocalTarget().trim(); - - ProgressManager.getInstance().run(new Task.Backgroundable(project, "Creating External", true, null) { - @Override - public void run(@NotNull ProgressIndicator indicator) { - doInBackground(project, vf, url, checkout, target); - } - }); + new Task.Backgroundable(project, "Creating External") { + @Override + public void run(@NotNull ProgressIndicator indicator) { + doInBackground(project, file, url, checkout, target); + } + }.queue(); + } } - private void doInBackground(Project project, VirtualFile vf, String url, boolean checkout, String target) { - final SvnVcs vcs = SvnVcs.getInstance(project); + private static void doInBackground(@NotNull Project project, @NotNull VirtualFile file, String url, boolean checkout, String target) { + SvnVcs vcs = SvnVcs.getInstance(project); + VcsDirtyScopeManager dirtyScopeManager = VcsDirtyScopeManager.getInstance(project); + File ioFile = virtualToIoFile(file); + try { - final File ioFile = new File(vf.getPath()); - if (addToExternalProperty(vcs, ioFile, target, url)) return; - final VcsDirtyScopeManager dirtyScopeManager = VcsDirtyScopeManager.getInstance(project); - final FilePath filePath = VcsUtil.getFilePath(ioFile, true); - dirtyScopeManager.fileDirty(filePath); - if (checkout) { - // +- - final UpdateClient client = vcs.getFactory(ioFile).createUpdateClient(); - client.setEventHandler(new ProgressTracker() { - @Override - public void consume(ProgressEvent event) throws SVNException { - } + addToExternalProperty(vcs, ioFile, target, url); + dirtyScopeManager.fileDirty(file); - @Override - public void checkCancelled() throws SVNCancelException { - final ProgressIndicator pi = ProgressManager.getInstance().getProgressIndicator(); - if (pi != null && pi.isCanceled()) throw new SVNCancelException(); - } - }); + if (checkout) { + UpdateClient client = vcs.getFactory(ioFile).createUpdateClient(); + client.setEventHandler(new SvnProgressCanceller()); client.doUpdate(ioFile, SVNRevision.HEAD, Depth.UNKNOWN, false, false); - vf.refresh(true, true, new Runnable() { - @Override - public void run() { - dirtyScopeManager.dirDirtyRecursively(filePath); - } - }); + file.refresh(true, true, () -> dirtyScopeManager.dirDirtyRecursively(file)); } } - catch (SVNException e1) { - AbstractVcsHelper.getInstance(project).showError(new VcsException(e1), "Create External"); - } - catch (VcsException e1) { - AbstractVcsHelper.getInstance(project).showError(e1, "Create External"); + catch (VcsException e) { + AbstractVcsHelper.getInstance(project).showError(e, "Create External"); } } - public static boolean addToExternalProperty(@NotNull SvnVcs vcs, @NotNull File ioFile, String target, String url) - throws SVNException, VcsException { + public static void addToExternalProperty(@NotNull SvnVcs vcs, @NotNull File ioFile, String target, String url) throws VcsException { ClientFactory factory = vcs.getFactory(ioFile); - PropertyValue propertyValue = factory.createPropertyClient().getProperty(SvnTarget.fromFile(ioFile), SvnPropertyKeys.SVN_EXTERNALS, - false, SVNRevision.UNDEFINED); - String newValue; - if (propertyValue != null && !StringUtil.isEmptyOrSpaces(propertyValue.toString())) { - Map externalsMap = ExternalsDefinitionParser.parseExternalsProperty(propertyValue.toString()); - String externalsForTarget = externalsMap.get(target); + PropertyValue propertyValue = + factory.createPropertyClient().getProperty(SvnTarget.fromFile(ioFile), SvnPropertyKeys.SVN_EXTERNALS, false, SVNRevision.UNDEFINED); + boolean hasExternals = propertyValue != null && !isEmptyOrSpaces(propertyValue.toString()); + String newExternals = ""; + + if (hasExternals) { + String externalsForTarget = parseExternalsProperty(propertyValue.toString()).get(target); if (externalsForTarget != null) { - AbstractVcsHelper.getInstance(vcs.getProject()).showError( - new VcsException("Selected destination conflicts with existing: " + externalsForTarget), "Create External"); - return true; + throw new VcsException("Selected destination conflicts with existing: " + externalsForTarget); } - final String string = createExternalDefinitionString(url, target); - newValue = propertyValue.toString().trim() + "\n" + string; - } else { - newValue = createExternalDefinitionString(url, target); - } - factory.createPropertyClient().setProperty(ioFile, SvnPropertyKeys.SVN_EXTERNALS, PropertyValue.create(newValue), Depth.EMPTY, false); - return false; - } - public static String createExternalDefinitionString(String url, String target) { - return CommandUtil.escape(url) + " " + target; + newExternals = propertyValue.toString().trim() + "\n"; + } + + newExternals += escape(url) + " " + target; + factory.createPropertyClient() + .setProperty(ioFile, SvnPropertyKeys.SVN_EXTERNALS, PropertyValue.create(newExternals), Depth.EMPTY, false); } @Override - public void update(AnActionEvent e) { - final ActionUpdateHelper helper = new ActionUpdateHelper(); - checkState(e, helper); - helper.apply(e); + public void update(@NotNull AnActionEvent e) { + Project project = e.getProject(); + boolean visible = project != null && isSvnActive(project); + boolean enabled = visible && isEnabled(project, getIfSingle(e.getData(VcsDataKeys.VIRTUAL_FILE_STREAM))); + + e.getPresentation().setVisible(visible); + e.getPresentation().setEnabled(enabled); } - private void checkState(AnActionEvent e, final ActionStateConsumer sc) { - final DataContext dc = e.getDataContext(); - final Project project = CommonDataKeys.PROJECT.getData(dc); - if (project == null) { - sc.hide(); - return; - } - final ProjectLevelVcsManager manager = ProjectLevelVcsManager.getInstance(project); - if (!manager.checkVcsIsActive(SvnVcs.getKey().getName())) { - sc.hide(); - return; - } + private static boolean isSvnActive(@NotNull Project project) { + return ProjectLevelVcsManager.getInstance(project).checkVcsIsActive(SvnVcs.VCS_NAME); + } - final VirtualFile vf = CommonDataKeys.VIRTUAL_FILE.getData(dc); - final VirtualFile[] files = CommonDataKeys.VIRTUAL_FILE_ARRAY.getData(dc); - if (vf == null || files == null || files.length != 1 || ! vf.isDirectory()) { - sc.disable(); - return; - } + private static boolean isEnabled(@NotNull Project project, @Nullable VirtualFile file) { + return file != null && + file.isDirectory() && + getVcsForFile(file, project) instanceof SvnVcs && + isEnabled(FileStatusManager.getInstance(project).getStatus(file)); + } - final AbstractVcs vcsFor = manager.getVcsFor(vf); - if (vcsFor == null || ! SvnVcs.getKey().equals(vcsFor.getKeyInstanceMethod())) { - sc.disable(); - return; - } - - final FileStatus status = FileStatusManager.getInstance(project).getStatus(vf); - if (status == null || FileStatus.DELETED.equals(status) || FileStatus.IGNORED.equals(status) || - FileStatus.MERGED_WITH_PROPERTY_CONFLICTS.equals(status) || FileStatus.OBSOLETE.equals(status) || FileStatus.UNKNOWN.equals(status)) { - sc.disable(); - return; - } - sc.enable(); + private static boolean isEnabled(@Nullable FileStatus status) { + return status != null && + !FileStatus.DELETED.equals(status) && + !FileStatus.IGNORED.equals(status) && + !FileStatus.MERGED_WITH_PROPERTY_CONFLICTS.equals(status) && + !FileStatus.OBSOLETE.equals(status) && + !FileStatus.UNKNOWN.equals(status); } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ShowPropertiesDiffAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ShowPropertiesDiffAction.java index d8126a1300f1..0a2bb2a94d68 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ShowPropertiesDiffAction.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ShowPropertiesDiffAction.java @@ -15,51 +15,302 @@ */ package org.jetbrains.idea.svn.actions; +import com.intellij.diff.DiffManager; +import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.DataKey; +import com.intellij.openapi.progress.PerformInBackgroundOption; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.VcsDataKeys; +import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ChangesUtil; import com.intellij.openapi.vcs.changes.ContentRevision; import com.intellij.openapi.vcs.changes.CurrentContentRevision; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.SvnBaseContentRevision; import org.jetbrains.idea.svn.SvnBundle; import org.jetbrains.idea.svn.SvnRevisionNumber; import org.jetbrains.idea.svn.SvnVcs; +import org.jetbrains.idea.svn.api.Depth; +import org.jetbrains.idea.svn.difftool.properties.SvnPropertiesDiffRequest; +import org.jetbrains.idea.svn.difftool.properties.SvnPropertiesDiffRequest.PropertyContent; +import org.jetbrains.idea.svn.history.SvnRepositoryContentRevision; +import org.jetbrains.idea.svn.properties.PropertyConsumer; +import org.jetbrains.idea.svn.properties.PropertyData; +import org.jetbrains.idea.svn.properties.PropertyValue; +import org.tmatesoft.svn.core.SVNErrorCode; +import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc2.SvnTarget; -public class ShowPropertiesDiffAction extends AbstractShowPropertiesDiffAction { - public ShowPropertiesDiffAction() { - super(SvnBundle.message("action.Subversion.properties.diff.name")); +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import static com.intellij.util.ObjectUtils.notNull; +import static com.intellij.util.containers.ContainerUtil.exists; + +public class ShowPropertiesDiffAction extends AnAction implements DumbAware { + + @Override + public void update(@NotNull AnActionEvent e) { + Change[] changes = e.getData(VcsDataKeys.CHANGES); + boolean showAction = checkThatChangesAreUnderSvn(changes); + + e.getPresentation().setVisible(changes != null && showAction); + e.getPresentation().setEnabled(showAction); + } + + private static boolean checkThatChangesAreUnderSvn(@Nullable Change[] changes) { + return changes != null && exists(changes, change -> isUnderSvn(change.getBeforeRevision()) || isUnderSvn(change.getAfterRevision())); + } + + private static boolean isUnderSvn(@Nullable ContentRevision revision) { + return revision instanceof SvnBaseContentRevision; } @Override - public void update(final AnActionEvent e) { - super.update(e); - //e.getPresentation().setText(SvnBundle.message("action.Subversion.properties.diff.name")); - } + public void actionPerformed(@NotNull AnActionEvent e) { + Change[] changes = e.getData(VcsDataKeys.CHANGE_LEAD_SELECTION); - protected DataKey getChangesKey() { - return VcsDataKeys.CHANGE_LEAD_SELECTION; - } - - protected SVNRevision getBeforeRevisionValue(final Change change, final SvnVcs vcs) throws SVNException { - final ContentRevision beforeRevision = change.getBeforeRevision(); - if (beforeRevision != null) { - return ((SvnRevisionNumber) beforeRevision.getRevisionNumber()).getRevision(); - } else { - return SVNRevision.create(((SvnRevisionNumber) change.getAfterRevision().getRevisionNumber()).getRevision().getNumber() - 1); + if (checkThatChangesAreUnderSvn(changes)) { + new CalculateAndShow(e.getProject(), changes[0], e.getPresentation().getText()).queue(); } } - protected SVNRevision getAfterRevisionValue(final Change change, final SvnVcs vcs) throws SVNException { - final ContentRevision afterRevision = change.getAfterRevision(); + private static class CalculateAndShow extends Task.Backgroundable { + private final Change myChange; + private List myBeforeContent; + private List myAfterContent; + private SVNRevision myBeforeRevisionValue; + private SVNRevision myAfterRevision; + private Exception myException; + private final String myErrorTitle; + + private CalculateAndShow(@Nullable final Project project, final Change change, final String errorTitle) { + super(project, SvnBundle.message("fetching.properties.contents.progress.title"), true, PerformInBackgroundOption.DEAF); + myChange = change; + myErrorTitle = errorTitle; + } + + public void run(@NotNull final ProgressIndicator indicator) { + final SvnVcs vcs = SvnVcs.getInstance(myProject); + + try { + myBeforeRevisionValue = getBeforeRevisionValue(myChange); + myAfterRevision = getAfterRevisionValue(myChange); + + myBeforeContent = getPropertyList(vcs, myChange.getBeforeRevision(), myBeforeRevisionValue); + indicator.checkCanceled(); + // gets exactly WORKING revision property + myAfterContent = getPropertyList(vcs, myChange.getAfterRevision(), myAfterRevision); + } + catch (SVNException exc) { + myException = exc; + } + catch (VcsException exc) { + myException = exc; + } + } + + @Override + public void onSuccess() { + if (myException != null) { + Messages.showErrorDialog(myException.getMessage(), myErrorTitle); + return; + } + if (myBeforeContent != null && myAfterContent != null && myBeforeRevisionValue != null && myAfterRevision != null) { + SvnPropertiesDiffRequest diffRequest; + if (compareRevisions(myBeforeRevisionValue, myAfterRevision) > 0) { + diffRequest = new SvnPropertiesDiffRequest(getDiffWindowTitle(myChange), + new PropertyContent(myAfterContent), new PropertyContent(myBeforeContent), + revisionToString(myAfterRevision), revisionToString(myBeforeRevisionValue)); + } + else { + diffRequest = new SvnPropertiesDiffRequest(getDiffWindowTitle(myChange), + new PropertyContent(myBeforeContent), new PropertyContent(myAfterContent), + revisionToString(myBeforeRevisionValue), revisionToString(myAfterRevision)); + } + DiffManager.getInstance().showDiff(myProject, diffRequest); + } + } + } + + @NotNull + private static SVNRevision getBeforeRevisionValue(@NotNull Change change) throws SVNException { + ContentRevision beforeRevision = change.getBeforeRevision(); + if (beforeRevision != null) { + return ((SvnRevisionNumber)beforeRevision.getRevisionNumber()).getRevision(); + } + else { + return SVNRevision.create(((SvnRevisionNumber)notNull(change.getAfterRevision()).getRevisionNumber()).getRevision().getNumber() - 1); + } + } + + @NotNull + private static SVNRevision getAfterRevisionValue(@NotNull Change change) throws SVNException { + ContentRevision afterRevision = change.getAfterRevision(); if (afterRevision != null) { // CurrentContentRevision will be here, for instance, if invoked from changes dialog for "Compare with Branch" action return afterRevision instanceof CurrentContentRevision ? SVNRevision.WORKING : ((SvnRevisionNumber)afterRevision.getRevisionNumber()).getRevision(); - } else { - return SVNRevision.create(((SvnRevisionNumber) change.getBeforeRevision().getRevisionNumber()).getRevision().getNumber() + 1); + } + else { + return SVNRevision.create(((SvnRevisionNumber)notNull(change.getBeforeRevision()).getRevisionNumber()).getRevision().getNumber() + 1); } } + + @NotNull + private static String getDiffWindowTitle(@NotNull Change change) { + if (change.isMoved() || change.isRenamed()) { + final FilePath beforeFilePath = ChangesUtil.getBeforePath(change); + final FilePath afterFilePath = ChangesUtil.getAfterPath(change); + + final String beforePath = beforeFilePath == null ? "" : beforeFilePath.getPath(); + final String afterPath = afterFilePath == null ? "" : afterFilePath.getPath(); + return SvnBundle.message("action.Subversion.properties.difference.diff.for.move.title", beforePath, afterPath); + } else { + return SvnBundle.message("action.Subversion.properties.difference.diff.title", ChangesUtil.getFilePath(change).getPath()); + } + } + + private static int compareRevisions(@NotNull SVNRevision revision1, @NotNull SVNRevision revision2) { + if (revision1.equals(revision2)) { + return 0; + } + // working(local) ahead of head + if (SVNRevision.WORKING.equals(revision1)) { + return 1; + } + if (SVNRevision.WORKING.equals(revision2)) { + return -1; + } + if (SVNRevision.HEAD.equals(revision1)) { + return 1; + } + if (SVNRevision.HEAD.equals(revision2)) { + return -1; + } + return revision1.getNumber() > revision2.getNumber() ? 1 : -1; + } + + @NotNull + private static String revisionToString(@Nullable SVNRevision revision) { + return revision == null ? "not exists" : revision.toString(); + } + + private final static String ourPropertiesDelimiter = "\n"; + + @NotNull + private static List getPropertyList(@NotNull SvnVcs vcs, + @Nullable final ContentRevision contentRevision, + @Nullable final SVNRevision revision) + throws SVNException, VcsException { + if (contentRevision == null) { + return Collections.emptyList(); + } + + SvnTarget target; + if (contentRevision instanceof SvnRepositoryContentRevision) { + final SvnRepositoryContentRevision svnRevision = (SvnRepositoryContentRevision)contentRevision; + target = SvnTarget.fromURL(SVNURL.parseURIEncoded(svnRevision.getFullPath()), revision); + } else { + final File ioFile = contentRevision.getFile().getIOFile(); + target = SvnTarget.fromFile(ioFile, revision); + } + + return getPropertyList(vcs, target, revision); + } + + @NotNull + public static List getPropertyList(@NotNull SvnVcs vcs, @NotNull final SVNURL url, @Nullable final SVNRevision revision) + throws VcsException { + return getPropertyList(vcs, SvnTarget.fromURL(url, revision), revision); + } + + @NotNull + public static List getPropertyList(@NotNull SvnVcs vcs, @NotNull final File ioFile, @Nullable final SVNRevision revision) + throws SVNException { + try { + return getPropertyList(vcs, SvnTarget.fromFile(ioFile, revision), revision); + } + catch (VcsException e) { + throw new SVNException(SVNErrorMessage.create(SVNErrorCode.FS_GENERAL, e), e); + } + } + + @NotNull + private static List getPropertyList(@NotNull SvnVcs vcs, @NotNull SvnTarget target, @Nullable SVNRevision revision) + throws VcsException { + final List lines = new ArrayList<>(); + final PropertyConsumer propertyHandler = createHandler(revision, lines); + + vcs.getFactory(target).createPropertyClient().list(target, revision, Depth.EMPTY, propertyHandler); + + return lines; + } + + @NotNull + private static PropertyConsumer createHandler(SVNRevision revision, @NotNull final List lines) { + final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + if (indicator != null) { + indicator.checkCanceled(); + indicator.setText(SvnBundle.message("show.properties.diff.progress.text.revision.information", revision.toString())); + } + + return new PropertyConsumer() { + public void handleProperty(final File path, final PropertyData property) throws SVNException { + registerProperty(property); + } + + public void handleProperty(final SVNURL url, final PropertyData property) throws SVNException { + registerProperty(property); + } + + public void handleProperty(final long revision, final PropertyData property) throws SVNException { + // revision properties here + } + + private void registerProperty(@NotNull PropertyData property) { + if (indicator != null) { + indicator.checkCanceled(); + indicator.setText2(SvnBundle.message("show.properties.diff.progress.text2.property.information", property.getName())); + } + lines.add(property); + } + }; + } + + @NotNull + public static String toSortedStringPresentation(@NotNull List lines) { + StringBuilder sb = new StringBuilder(); + + Collections.sort(lines, Comparator.comparing(PropertyData::getName)); + + for (PropertyData line : lines) { + addPropertyPresentation(line, sb); + } + + return sb.toString(); + } + + private static void addPropertyPresentation(@NotNull PropertyData property, @NotNull StringBuilder sb) { + if (sb.length() != 0) { + sb.append(ourPropertiesDelimiter); + } + sb.append(property.getName()).append("=").append(StringUtil.notNullize(PropertyValue.toString(property.getValue()))); + } } 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 e2c5300181cb..ed0b987f56ba 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SelectBranchPopup.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SelectBranchPopup.java @@ -197,9 +197,9 @@ public class SelectBranchPopup { @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")) { + pm.run(new ModalityIgnorantBackgroundableTask(myProject, SvnBundle.message("compare.with.branch.progress.loading.branches"), true) { @Override - protected void doInAwtIfFail(Exception e) { + protected void doInAwtIfFail(@NotNull Exception e) { runnable.run(); } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnLoadedBranchesStorage.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnLoadedBranchesStorage.java index 918c76a6060d..6070fd62f650 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnLoadedBranchesStorage.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnLoadedBranchesStorage.java @@ -17,7 +17,7 @@ package org.jetbrains.idea.svn.branchConfig; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.project.Project; -import com.intellij.openapi.vcs.persistent.SmallMapSerializer; +import org.jetbrains.idea.svn.SmallMapSerializer; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.io.DataExternalizer; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/difftool/SvnTreeConflictDiffRequestProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/difftool/SvnTreeConflictDiffRequestProvider.java index 918985fcdb3c..bd126b324c60 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/difftool/SvnTreeConflictDiffRequestProvider.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/difftool/SvnTreeConflictDiffRequestProvider.java @@ -1,14 +1,15 @@ package org.jetbrains.idea.svn.difftool; -import com.intellij.openapi.progress.BackgroundTaskQueue; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.UserDataHolder; import com.intellij.diff.DiffContext; import com.intellij.diff.FrameDiffTool; import com.intellij.diff.chains.DiffRequestProducerException; import com.intellij.diff.requests.DiffRequest; +import com.intellij.openapi.progress.BackgroundTaskQueue; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.UserDataHolder; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vcs.changes.actions.diff.ChangeDiffRequestProducer; import com.intellij.openapi.vcs.changes.actions.diff.ChangeDiffRequestProvider; @@ -102,7 +103,7 @@ public class SvnTreeConflictDiffRequestProvider implements ChangeDiffRequestProv myQueue = new BackgroundTaskQueue(myContext.getProject(), "Loading change details"); - // We don't need to listen on File/Document, because panel always will be the same for a single change (@see myDelegate.isStillValid()) + // We don't need to listen on File/Document, because panel always will be the same for a single change. // And if Change will change - we'll create new DiffRequest and DiffViewer myDelegate = new TreeConflictRefreshablePanel(myContext.getProject(), "Loading tree conflict details", myQueue, myRequest.getChange()); @@ -131,6 +132,7 @@ public class SvnTreeConflictDiffRequestProvider implements ChangeDiffRequestProv @Override public void dispose() { myQueue.clear(); + Disposer.dispose(myDelegate); } } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SimplePropertyRevision.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SimplePropertyRevision.java index 881984cb1fb6..4be53aabd294 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SimplePropertyRevision.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SimplePropertyRevision.java @@ -6,11 +6,12 @@ import com.intellij.openapi.vcs.changes.ContentRevision; import com.intellij.openapi.vcs.history.VcsRevisionNumber; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.idea.svn.actions.AbstractShowPropertiesDiffAction; import org.jetbrains.idea.svn.properties.PropertyData; import java.util.List; +import static org.jetbrains.idea.svn.actions.ShowPropertiesDiffAction.toSortedStringPresentation; + public class SimplePropertyRevision implements ContentRevision, PropertyRevision { private final List myProperty; private final FilePath myNewFilePath; @@ -30,7 +31,7 @@ public class SimplePropertyRevision implements ContentRevision, PropertyRevision @Nullable public String getContent() { - return AbstractShowPropertiesDiffAction.toSortedStringPresentation(myProperty); + return toSortedStringPresentation(myProperty); } @NotNull diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnChangeList.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnChangeList.java index 2cb287172d7c..569025819fc4 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnChangeList.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnChangeList.java @@ -561,7 +561,7 @@ public class SvnChangeList implements CommittedChangeList, VcsRevisionNumberAwar private SvnLazyPropertyContentRevision createPropertyRevision(@NotNull FilePath filePath, @Nullable ContentRevision revision, @NotNull SVNURL url) { - return revision == null ? null : new SvnLazyPropertyContentRevision(filePath, revision.getRevisionNumber(), myVcs.getProject(), url); + return revision == null ? null : new SvnLazyPropertyContentRevision(myVcs, filePath, revision.getRevisionNumber(), url); } @NotNull diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnLazyPropertyContentRevision.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnLazyPropertyContentRevision.java index c313794e6451..9edb9c100a74 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnLazyPropertyContentRevision.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnLazyPropertyContentRevision.java @@ -17,42 +17,32 @@ package org.jetbrains.idea.svn.history; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.VcsKey; -import com.intellij.openapi.vcs.changes.ContentRevision; -import com.intellij.openapi.vcs.changes.MarkerVcsContentRevision; import com.intellij.openapi.vcs.history.VcsRevisionNumber; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.SvnBaseContentRevision; import org.jetbrains.idea.svn.SvnBundle; import org.jetbrains.idea.svn.SvnRevisionNumber; import org.jetbrains.idea.svn.SvnVcs; -import org.jetbrains.idea.svn.actions.AbstractShowPropertiesDiffAction; import org.jetbrains.idea.svn.properties.PropertyData; import org.tmatesoft.svn.core.SVNURL; import java.util.List; -/** - * Created with IntelliJ IDEA. - * User: Irina.Chernushina - * Date: 2/22/12 - * Time: 10:28 AM - */ -public class SvnLazyPropertyContentRevision implements ContentRevision, MarkerVcsContentRevision, PropertyRevision { - private final FilePath myPath; +import static org.jetbrains.idea.svn.actions.ShowPropertiesDiffAction.getPropertyList; +import static org.jetbrains.idea.svn.actions.ShowPropertiesDiffAction.toSortedStringPresentation; + +public class SvnLazyPropertyContentRevision extends SvnBaseContentRevision implements PropertyRevision { private final VcsRevisionNumber myNumber; - private final Project myProject; private final SVNURL myUrl; private List myContent; - public SvnLazyPropertyContentRevision(FilePath path, VcsRevisionNumber number, Project project, SVNURL url) { - myPath = path; + public SvnLazyPropertyContentRevision(@NotNull SvnVcs vcs, @NotNull FilePath file, VcsRevisionNumber number, SVNURL url) { + super(vcs, file); myNumber = number; - myProject = project; myUrl = url; } @@ -67,27 +57,24 @@ public class SvnLazyPropertyContentRevision implements ContentRevision, MarkerVc @Override public String getContent() throws VcsException { - return AbstractShowPropertiesDiffAction.toSortedStringPresentation(getProperties()); + return toSortedStringPresentation(getProperties()); } private List loadContent() throws VcsException { - final SvnVcs vcs = SvnVcs.getInstance(myProject); final Ref> ref = new Ref<>(); final Ref exceptionRef = new Ref<>(); - final Runnable runnable = new Runnable() { - @Override - public void run() { - try { - ref.set(AbstractShowPropertiesDiffAction.getPropertyList(vcs, myUrl, ((SvnRevisionNumber)myNumber).getRevision())); - } - catch (VcsException e) { - exceptionRef.set(e); - } + final Runnable runnable = () -> { + try { + ref.set(getPropertyList(myVcs, myUrl, ((SvnRevisionNumber)myNumber).getRevision())); + } + catch (VcsException e) { + exceptionRef.set(e); } }; if (ApplicationManager.getApplication().isDispatchThread()) { - final boolean completed = ProgressManager.getInstance() - .runProcessWithProgressSynchronously(runnable, SvnBundle.message("progress.title.loading.file.properties"), true, myProject); + boolean completed = ProgressManager.getInstance() + .runProcessWithProgressSynchronously(runnable, SvnBundle.message("progress.title.loading.file.properties"), true, + myVcs.getProject()); if (!completed) { throw new VcsException("Properties load for revision " + getRevisionNumber().asString() + " was canceled."); } @@ -99,20 +86,9 @@ public class SvnLazyPropertyContentRevision implements ContentRevision, MarkerVc return ref.get(); } - @NotNull - @Override - public FilePath getFile() { - return myPath; - } - @NotNull @Override public VcsRevisionNumber getRevisionNumber() { return myNumber; } - - @Override - public VcsKey getVcsKey() { - return SvnVcs.getKey(); - } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryContentRevision.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryContentRevision.java index 82f064b87aa9..4b9aec924eaf 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryContentRevision.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryContentRevision.java @@ -25,22 +25,16 @@ package org.jetbrains.idea.svn.history; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.util.Throwable2Computable; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.VcsKey; import com.intellij.openapi.vcs.changes.ByteBackedContentRevision; -import com.intellij.openapi.vcs.changes.MarkerVcsContentRevision; import com.intellij.openapi.vcs.impl.ContentRevisionCache; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.vcsUtil.VcsUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.idea.svn.SvnBundle; -import org.jetbrains.idea.svn.SvnRevisionNumber; -import org.jetbrains.idea.svn.SvnUtil; -import org.jetbrains.idea.svn.SvnVcs; +import org.jetbrains.idea.svn.*; import org.jetbrains.idea.svn.commandLine.SvnBindException; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.SvnTarget; @@ -49,40 +43,33 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; -public class SvnRepositoryContentRevision implements ByteBackedContentRevision, MarkerVcsContentRevision { +import static com.intellij.util.ObjectUtils.notNull; + +public class SvnRepositoryContentRevision extends SvnBaseContentRevision implements ByteBackedContentRevision { - @NotNull private final SvnVcs myVcs; @NotNull private final String myPath; - @NotNull private final FilePath myFilePath; private final long myRevision; public SvnRepositoryContentRevision(@NotNull SvnVcs vcs, @NotNull FilePath remotePath, @Nullable FilePath localPath, long revision) { - myVcs = vcs; + super(vcs, notNull(localPath, remotePath)); myPath = FileUtil.toSystemIndependentName(remotePath.getPath()); - myFilePath = localPath != null ? localPath : remotePath; myRevision = revision; } @NotNull public String getContent() throws VcsException { - return ContentRevisionCache.getAsString(getContentAsBytes(), myFilePath, null); + return ContentRevisionCache.getAsString(getContentAsBytes(), myFile, null); } @NotNull @Override public byte[] getContentAsBytes() throws VcsException { try { - if (myFilePath.getVirtualFile() == null) { - LocalFileSystem.getInstance().refreshAndFindFileByPath(myFilePath.getPath()); + if (myFile.getVirtualFile() == null) { + LocalFileSystem.getInstance().refreshAndFindFileByPath(myFile.getPath()); } - return ContentRevisionCache.getOrLoadAsBytes(myVcs.getProject(), myFilePath, getRevisionNumber(), myVcs.getKeyInstanceMethod(), - ContentRevisionCache.UniqueType.REPOSITORY_CONTENT, - new Throwable2Computable() { - @Override - public byte[] compute() throws VcsException, IOException { - return loadContent().toByteArray(); - } - }); + return ContentRevisionCache.getOrLoadAsBytes(myVcs.getProject(), myFile, getRevisionNumber(), myVcs.getKeyInstanceMethod(), + ContentRevisionCache.UniqueType.REPOSITORY_CONTENT, () -> loadContent().toByteArray()); } catch (IOException e) { throw new VcsException(e); @@ -108,11 +95,6 @@ public class SvnRepositoryContentRevision implements ByteBackedContentRevision, return buffer; } - @NotNull - public FilePath getFile() { - return myFilePath; - } - @NotNull public SvnRevisionNumber getRevisionNumber() { return new SvnRevisionNumber(SVNRevision.create(myRevision)); @@ -156,7 +138,7 @@ public class SvnRepositoryContentRevision implements ByteBackedContentRevision, @Override public String toString() { - return myFilePath.getIOFile() + "#" + myRevision; + return myFile.getIOFile() + "#" + myRevision; } private class ContentLoader implements Runnable { @@ -206,11 +188,6 @@ public class SvnRepositoryContentRevision implements ByteBackedContentRevision, return SvnUtil.getRelativePath(repositoryUrl, myPath); } - @Override - public VcsKey getVcsKey() { - return SvnVcs.getKey(); - } - @NotNull public SvnTarget toTarget() throws SvnBindException { return SvnTarget.fromURL(SvnUtil.createUrl(getFullPath()), getRevisionNumber().getRevision()); diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/CheckSamePattern.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/CheckSamePattern.java similarity index 92% rename from platform/vcs-api/src/com/intellij/openapi/vcs/CheckSamePattern.java rename to plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/CheckSamePattern.java index ce531362eac4..cfaf42622c1f 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/CheckSamePattern.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/CheckSamePattern.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * 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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.openapi.vcs; +package org.jetbrains.idea.svn.integrate; public class CheckSamePattern { private boolean mySame; diff --git a/platform/vcs-impl/src/com/intellij/vcsUtil/MoreAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/MoreAction.java similarity index 98% rename from platform/vcs-impl/src/com/intellij/vcsUtil/MoreAction.java rename to plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/MoreAction.java index 4c5f424820cb..40b2b2924180 100644 --- a/platform/vcs-impl/src/com/intellij/vcsUtil/MoreAction.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/MoreAction.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.vcsUtil; +package org.jetbrains.idea.svn.integrate; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/QuantitySelection.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/QuantitySelection.java similarity index 96% rename from platform/vcs-api/src/com/intellij/openapi/vcs/QuantitySelection.java rename to plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/QuantitySelection.java index ce07651866a8..3b9a3ddef3b1 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/QuantitySelection.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/QuantitySelection.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * 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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.openapi.vcs; +package org.jetbrains.idea.svn.integrate; import org.jetbrains.annotations.NotNull; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/SelectedChangeListsChecker.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/SelectedChangeListsChecker.java index fb5a5835fbdb..ec2a8a18843f 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/SelectedChangeListsChecker.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/SelectedChangeListsChecker.java @@ -16,7 +16,6 @@ package org.jetbrains.idea.svn.integrate; import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.vcs.CheckSamePattern; import com.intellij.openapi.vcs.VcsDataKeys; import com.intellij.openapi.vcs.changes.ChangeList; import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/SvnBranchPointsCalculator.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/SvnBranchPointsCalculator.java index e2f13b868dd8..bac79556c69a 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/SvnBranchPointsCalculator.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/SvnBranchPointsCalculator.java @@ -18,7 +18,7 @@ package org.jetbrains.idea.svn.integrate; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.persistent.SmallMapSerializer; +import org.jetbrains.idea.svn.SmallMapSerializer; import com.intellij.util.io.DataExternalizer; import com.intellij.util.io.EnumeratorStringDescriptor; import org.jetbrains.annotations.NotNull; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/ToBeMergedDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/ToBeMergedDialog.java index 22cb13e84c37..a042e575ce94 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/ToBeMergedDialog.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/ToBeMergedDialog.java @@ -27,7 +27,6 @@ import com.intellij.openapi.ui.Splitter; import com.intellij.openapi.ui.popup.util.PopupUtil; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Pair; -import com.intellij.openapi.vcs.QuantitySelection; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vcs.changes.committed.CommittedChangeListRenderer; @@ -43,7 +42,6 @@ import com.intellij.util.ui.JBUI; import com.intellij.util.ui.ListTableModel; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.components.BorderLayoutPanel; -import com.intellij.vcsUtil.MoreAction; import org.jetbrains.annotations.NotNull; import org.jetbrains.idea.svn.history.SvnChangeList; import org.jetbrains.idea.svn.mergeinfo.ListMergeStatus; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/OneShotMergeInfoHelper.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/OneShotMergeInfoHelper.java index b1ecbbb10232..6e0e64968235 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/OneShotMergeInfoHelper.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/OneShotMergeInfoHelper.java @@ -17,7 +17,6 @@ package org.jetbrains.idea.svn.mergeinfo; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.util.SystemInfo; -import com.intellij.openapi.vcs.AreaMap; import com.intellij.openapi.vcs.VcsException; import com.intellij.util.PairProcessor; import org.jetbrains.annotations.NotNull; @@ -38,16 +37,14 @@ import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.SvnTarget; import java.io.File; -import java.util.Collection; -import java.util.LinkedList; -import java.util.Map; -import java.util.Set; +import java.util.*; import static com.intellij.openapi.util.io.FileUtil.getRelativePath; import static com.intellij.openapi.util.io.FileUtil.toSystemIndependentName; import static com.intellij.openapi.util.text.StringUtil.toUpperCase; import static com.intellij.util.ObjectUtils.notNull; import static com.intellij.util.containers.ContainerUtil.*; +import static java.util.Collections.reverseOrder; import static org.jetbrains.idea.svn.SvnUtil.ensureStartSlash; import static org.jetbrains.idea.svn.mergeinfo.SvnMergeInfoCache.MergeCheckResult; import static org.tmatesoft.svn.core.internal.util.SVNPathUtil.isAncestor; @@ -57,16 +54,14 @@ public class OneShotMergeInfoHelper implements MergeChecker { @NotNull private final MergeContext myMergeContext; @NotNull private final Map> myPartiallyMerged; // subpath [file] (local) to (subpathURL - merged FROM - to ranges list) - @NotNull private final AreaMap> myMergeInfoMap; + @NotNull private final NavigableMap> myMergeInfoMap; @NotNull private final Object myMergeInfoLock; public OneShotMergeInfoHelper(@NotNull MergeContext mergeContext) { myMergeContext = mergeContext; myPartiallyMerged = newHashMap(); myMergeInfoLock = new Object(); - // TODO: Rewrite without AreaMap usage - myMergeInfoMap = - AreaMap.create((parentUrl, childUrl) -> ".".equals(parentUrl) || isAncestor(ensureStartSlash(parentUrl), ensureStartSlash(childUrl))); + myMergeInfoMap = new TreeMap<>(reverseOrder()); } @Override @@ -118,9 +113,20 @@ public class OneShotMergeInfoHelper implements MergeChecker { // TODO: SVNPathUtil.getRelativePath() is @NotNull - probably we need to check also isEmpty() here? if (sourceRelativePath != null) { InfoProcessor processor = new InfoProcessor(sourceRelativePath, myMergeContext.getRepositoryRelativeSourcePath(), revisionNumber); + String key = toKey(sourceRelativePath); synchronized (myMergeInfoLock) { - myMergeInfoMap.getSimiliar(toKey(sourceRelativePath), processor); + Map mergeInfo = myMergeInfoMap.get(key); + if (mergeInfo != null) { + processor.process(key, mergeInfo); + } + else { + for (Map.Entry> entry : myMergeInfoMap.tailMap(key).entrySet()) { + if (isUnder(entry.getKey(), key) && processor.process(entry.getKey(), entry.getValue())) { + break; + } + } + } } result = MergeCheckResult.getInstance(processor.isMerged()); @@ -129,6 +135,10 @@ public class OneShotMergeInfoHelper implements MergeChecker { return result; } + private static boolean isUnder(@NotNull String parentUrl, @NotNull String childUrl) { + return ".".equals(parentUrl) || isAncestor(ensureStartSlash(parentUrl), ensureStartSlash(childUrl)); + } + private static class InfoProcessor implements PairProcessor> { @NotNull private final String myRepositoryRelativeSourcePath; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/treeConflict/TreeConflictRefreshablePanel.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/treeConflict/TreeConflictRefreshablePanel.java index 8b1f5e45c9bd..c8422792cb50 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/treeConflict/TreeConflictRefreshablePanel.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/treeConflict/TreeConflictRefreshablePanel.java @@ -17,10 +17,10 @@ package org.jetbrains.idea.svn.treeConflict; import com.intellij.openapi.CompositeDisposable; import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.progress.BackgroundTaskQueue; -import com.intellij.openapi.progress.PerformInBackgroundOption; -import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.*; +import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.Messages; @@ -29,12 +29,12 @@ import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.changes.AbstractRefreshablePanel; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vcs.changes.ChangesUtil; import com.intellij.openapi.vcs.history.*; import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier; import com.intellij.ui.JBColor; +import com.intellij.ui.components.JBLoadingPanel; import com.intellij.util.BeforeAfter; import com.intellij.util.containers.Convertor; import com.intellij.util.ui.JBUI; @@ -42,7 +42,10 @@ import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.VcsBackgroundTask; import com.intellij.vcsUtil.VcsUtil; import gnu.trove.TLongArrayList; +import org.jetbrains.annotations.CalledInAwt; +import org.jetbrains.annotations.CalledInBackground; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.ConflictedSvnChange; import org.jetbrains.idea.svn.SvnRevisionNumber; import org.jetbrains.idea.svn.SvnVcs; @@ -51,7 +54,6 @@ import org.jetbrains.idea.svn.conflict.ConflictReason; import org.jetbrains.idea.svn.conflict.ConflictVersion; import org.jetbrains.idea.svn.conflict.TreeConflictDescription; import org.jetbrains.idea.svn.history.SvnHistoryProvider; -import org.jetbrains.idea.svn.history.SvnHistorySession; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.wc.SVNRevision; @@ -62,15 +64,12 @@ import java.awt.event.ActionListener; import java.util.Collections; import java.util.List; +import static com.intellij.openapi.application.ModalityState.defaultModalityState; import static com.intellij.util.ObjectUtils.notNull; +import static org.jetbrains.idea.svn.history.SvnHistorySession.getCurrentCommittedRevision; + +public class TreeConflictRefreshablePanel implements Disposable { -/** - * Created with IntelliJ IDEA. - * User: Irina.Chernushina - * Date: 4/25/12 - * Time: 5:33 PM - */ -public class TreeConflictRefreshablePanel extends AbstractRefreshablePanel { public static final String TITLE = "Resolve tree conflict"; private final ConflictedSvnChange myChange; private final SvnVcs myVcs; @@ -78,25 +77,24 @@ public class TreeConflictRefreshablePanel extends AbstractRefreshablePanel { private FilePath myPath; private final CompositeDisposable myChildDisposables = new CompositeDisposable(); private final TLongArrayList myRightRevisionsList; + @NotNull private final String myLoadingTitle; + @NotNull private final JBLoadingPanel myDetailsPanel; + @NotNull private final BackgroundTaskQueue myQueue; + private volatile ProgressIndicator myIndicator = new EmptyProgressIndicator(); - public TreeConflictRefreshablePanel(Project project, String loadingTitle, BackgroundTaskQueue queue, Change change) { - super(project, loadingTitle, queue); + public TreeConflictRefreshablePanel(@NotNull Project project, + @NotNull String loadingTitle, + @NotNull BackgroundTaskQueue queue, + Change change) { myVcs = SvnVcs.getInstance(project); assert change instanceof ConflictedSvnChange; myChange = (ConflictedSvnChange) change; myPath = ChangesUtil.getFilePath(myChange); myRightRevisionsList = new TLongArrayList(); - } - @Override - public boolean isStillValid(final Change change) { - return change.isTreeConflict() && change instanceof ConflictedSvnChange && - descriptionsEqual(((ConflictedSvnChange)change).getBeforeDescription(), myChange.getBeforeDescription()); - } - - @Override - public boolean refreshDataSynch() { - return true; + myLoadingTitle = loadingTitle; + myQueue = queue; + myDetailsPanel = new JBLoadingPanel(new BorderLayout(), this); } public static boolean descriptionsEqual(TreeConflictDescription d1, TreeConflictDescription d2) { @@ -124,88 +122,93 @@ public class TreeConflictRefreshablePanel extends AbstractRefreshablePanel { return true; } - @Override - protected void refreshPresentation() { + @NotNull + public JPanel getPanel() { + return myDetailsPanel; } - @Override - protected Object loadImpl() throws VcsException { - return new BeforeAfter<>(processDescription(myChange.getBeforeDescription()), - processDescription(myChange.getAfterDescription())); - } - - private BeforeAfter processDescription(TreeConflictDescription description) throws VcsException { + @CalledInBackground + private BeforeAfter processDescription(@NotNull ProgressIndicator indicator, + TreeConflictDescription description) throws VcsException { if (description == null) return null; if (myChange.getBeforeRevision() != null) { - myCommittedRevision = (SvnRevisionNumber)SvnHistorySession.getCurrentCommittedRevision(myVcs, - myChange.getBeforeRevision() != null ? myChange.getBeforeRevision().getFile().getIOFile() : myPath.getIOFile()); + myCommittedRevision = (SvnRevisionNumber)getCurrentCommittedRevision(myVcs, myChange.getBeforeRevision() != null ? myChange + .getBeforeRevision().getFile().getIOFile() : myPath.getIOFile()); } - boolean differentURLs = isDifferentURLs(description); - ConflictSidePresentation leftSide = null; - ConflictSidePresentation rightSide = null; - try { - if (differentURLs) { - leftSide = createSide(description.getSourceLeftVersion(), null, true); - rightSide = createSide(description.getSourceRightVersion(), null, false); - leftSide.load(); - rightSide.load(); - } else { - //only one side - leftSide = EmptyConflictSide.getInstance(); - final SVNRevision pegFromLeft; - if (description.getSourceLeftVersion() == null) { - pegFromLeft = null; - } - else { - long committed = description.getSourceLeftVersion().getPegRevision(); - if (myCommittedRevision != null && myCommittedRevision.getRevision().getNumber() < committed && - myCommittedRevision.getRevision().isValid()) { - committed = myCommittedRevision.getRevision().getNumber(); - } - pegFromLeft = SVNRevision.create(committed); - } - rightSide = createSide(description.getSourceRightVersion(), pegFromLeft, false); - rightSide.load(); - return new BeforeAfter<>(leftSide, rightSide); - } - } catch (SVNException e) { - throw new VcsException(e); - } finally { - if (leftSide != null) { - myChildDisposables.add(leftSide); - } - if (rightSide != null) { - myChildDisposables.add(rightSide); - } + indicator.checkCanceled(); + + ConflictSidePresentation leftSide; + ConflictSidePresentation rightSide; + if (isDifferentURLs(description)) { + leftSide = createSide(description.getSourceLeftVersion(), null, true); + rightSide = createSide(description.getSourceRightVersion(), null, false); } + else { //only one side + leftSide = createSide(null, null, true); + rightSide = createSide(description.getSourceRightVersion(), getPegRevisionFromLeftSide(description), false); + } + indicator.checkCanceled(); + leftSide.load(); + indicator.checkCanceled(); + rightSide.load(); + indicator.checkCanceled(); return new BeforeAfter<>(leftSide, rightSide); } + @Nullable + private SVNRevision getPegRevisionFromLeftSide(@NotNull TreeConflictDescription description) { + SVNRevision result = null; + if (description.getSourceLeftVersion() != null) { + long committed = description.getSourceLeftVersion().getPegRevision(); + if (myCommittedRevision != null && + myCommittedRevision.getRevision().getNumber() < committed && + myCommittedRevision.getRevision().isValid()) { + committed = myCommittedRevision.getRevision().getNumber(); + } + result = SVNRevision.create(committed); + } + return result; + } + private static boolean isDifferentURLs(TreeConflictDescription description) { return description.getSourceLeftVersion() != null && description.getSourceRightVersion() != null && ! Comparing.equal(description.getSourceLeftVersion().getPath(), description.getSourceRightVersion().getPath()); } - private ConflictSidePresentation createSide(ConflictVersion version, final SVNRevision untilThisOther, final boolean isLeft) throws VcsException { - if (version == null) return EmptyConflictSide.getInstance(); - if (myChange.getBeforeRevision() != null && myCommittedRevision != null) { - SvnRevisionNumber number = myCommittedRevision; - if (isLeft && number.getRevision().isValid() && number.getRevision().getNumber() == version.getPegRevision()) { - return EmptyConflictSide.getInstance(); + @NotNull + private ConflictSidePresentation createSide(@Nullable ConflictVersion version, @Nullable SVNRevision untilThisOther, boolean isLeft) + throws VcsException { + ConflictSidePresentation result = EmptyConflictSide.getInstance(); + if (version != null && + (myChange.getBeforeRevision() == null || + myCommittedRevision == null || + !isLeft || + !myCommittedRevision.getRevision().isValid() || + myCommittedRevision.getRevision().getNumber() != version.getPegRevision())) { + HistoryConflictSide side = new HistoryConflictSide(myVcs, version, untilThisOther); + if (untilThisOther != null && !isLeft) { + side.setListToReportLoaded(myRightRevisionsList); } + result = side; } - HistoryConflictSide side = new HistoryConflictSide(myVcs, version, untilThisOther); - if (untilThisOther != null && ! isLeft) { - side.setListToReportLoaded(myRightRevisionsList); - } - return side; + myChildDisposables.add(result); + return result; } - @Override - protected JPanel dataToPresentation(Object o) { - final BeforeAfter> ba = (BeforeAfter>) o; + @CalledInAwt + public void refresh() { + ApplicationManager.getApplication().assertIsDispatchThread(); + + myDetailsPanel.startLoading(); + Loader task = new Loader(myVcs.getProject(), myLoadingTitle); + myIndicator = new BackgroundableProcessIndicator(task); + myQueue.run(task, defaultModalityState(), myIndicator); + } + + @CalledInAwt + protected JPanel dataToPresentation(BeforeAfter> data) { final JPanel wrapper = new JPanel(new BorderLayout()); final JPanel main = new JPanel(new GridBagLayout()); @@ -223,8 +226,8 @@ public class TreeConflictRefreshablePanel extends AbstractRefreshablePanel { main.add(name, gb); ++ gb.gridy; gb.insets.top = 10; - appendDescription(myChange.getBeforeDescription(), main, gb, ba.getBefore(), myPath.isDirectory()); - appendDescription(myChange.getAfterDescription(), main, gb, ba.getAfter(), myPath.isDirectory()); + appendDescription(myChange.getBeforeDescription(), main, gb, data.getBefore(), myPath.isDirectory()); + appendDescription(myChange.getAfterDescription(), main, gb, data.getAfter(), myPath.isDirectory()); wrapper.add(main, BorderLayout.NORTH); return wrapper; } @@ -424,17 +427,15 @@ public class TreeConflictRefreshablePanel extends AbstractRefreshablePanel { } @Override - protected void disposeImpl() { + public void dispose() { + myIndicator.cancel(); Disposer.dispose(myChildDisposables); } - @Override - public void away() { - } - private interface ConflictSidePresentation extends Disposable { JPanel createPanel(); - void load() throws SVNException, VcsException; + + void load() throws VcsException; } private static class EmptyConflictSide implements ConflictSidePresentation { @@ -454,7 +455,7 @@ public class TreeConflictRefreshablePanel extends AbstractRefreshablePanel { } @Override - public void load() throws SVNException { + public void load() { } } @@ -507,7 +508,7 @@ public class TreeConflictRefreshablePanel extends AbstractRefreshablePanel { } @Override - public void load() throws SVNException, VcsException { + public void load() throws VcsException { SVNRevision from = SVNRevision.create(myVersion.getPegRevision()); myProvider.reportAppendableHistory(myPath, mySessionAdapter, from, myPeg, myPeg == null ? LIMIT : 0, myPeg, true); VcsAbstractHistorySession session = mySessionAdapter.getSession(); @@ -556,4 +557,35 @@ public class TreeConflictRefreshablePanel extends AbstractRefreshablePanel { return myFileHistoryPanel; } } + + private class Loader extends Task.Backgroundable { + private BeforeAfter> myData; + private VcsException myException; + + private Loader(@Nullable Project project, @NotNull String title) { + super(project, title, false); + } + + @Override + public void run(@NotNull ProgressIndicator indicator) { + try { + myData = new BeforeAfter<>(processDescription(indicator, myChange.getBeforeDescription()), + processDescription(indicator, myChange.getAfterDescription())); + } + catch (VcsException e) { + myException = e; + } + } + + @Override + public void onSuccess() { + if (myException != null) { + VcsBalloonProblemNotifier.showOverChangesView(myProject, myException.getMessage(), MessageType.ERROR); + } + else { + myDetailsPanel.add(dataToPresentation(myData)); + myDetailsPanel.stopLoading(); + } + } + } } diff --git a/plugins/ui-designer/src/com/intellij/uiDesigner/actions/DataBindingWizardAction.java b/plugins/ui-designer/src/com/intellij/uiDesigner/actions/DataBindingWizardAction.java index b5bbd645b26e..767be0a4192c 100644 --- a/plugins/ui-designer/src/com/intellij/uiDesigner/actions/DataBindingWizardAction.java +++ b/plugins/ui-designer/src/com/intellij/uiDesigner/actions/DataBindingWizardAction.java @@ -46,7 +46,9 @@ public final class DataBindingWizardAction extends AnAction{ final Project project; final VirtualFile formFile; GuiEditor editor = FormEditingUtil.getActiveEditor(e.getDataContext()); - assert editor != null; + if (editor == null) { + return; + } project = editor.getProject(); formFile = editor.getFile(); diff --git a/plugins/ui-designer/src/com/intellij/uiDesigner/designSurface/InplaceEditingLayer.java b/plugins/ui-designer/src/com/intellij/uiDesigner/designSurface/InplaceEditingLayer.java index df37d70df8e5..a17e4d602795 100644 --- a/plugins/ui-designer/src/com/intellij/uiDesigner/designSurface/InplaceEditingLayer.java +++ b/plugins/ui-designer/src/com/intellij/uiDesigner/designSurface/InplaceEditingLayer.java @@ -17,6 +17,7 @@ package com.intellij.uiDesigner.designSurface; 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.wm.FocusWatcher; @@ -331,7 +332,7 @@ public final class InplaceEditingLayer extends JComponent{ */ private final class MyComponentSelectionListener implements ComponentSelectionListener{ public void selectedComponentChanged(final GuiEditor source) { - finishInplaceEditing(); + TransactionGuard.submitTransaction(myEditor.getEditor(), () -> finishInplaceEditing()); } } diff --git a/plugins/xpath/xpath-lang/src/org/intellij/lang/xpath/xslt/quickfix/DownloadResourceFix.java b/plugins/xpath/xpath-lang/src/org/intellij/lang/xpath/xslt/quickfix/DownloadResourceFix.java index fef83e127a57..91b3893d6488 100644 --- a/plugins/xpath/xpath-lang/src/org/intellij/lang/xpath/xslt/quickfix/DownloadResourceFix.java +++ b/plugins/xpath/xpath-lang/src/org/intellij/lang/xpath/xslt/quickfix/DownloadResourceFix.java @@ -44,6 +44,11 @@ public class DownloadResourceFix implements LocalQuickFix { return "Download External Resource"; } + @Override + public boolean startInWriteAction() { + return false; + } + @Override public void applyFix(@NotNull final Project project, @NotNull ProblemDescriptor descriptor) { boolean tryAgain = true; diff --git a/plugins/yaml/src/org/jetbrains/yaml/folding/YAMLFoldingBuilder.java b/plugins/yaml/src/org/jetbrains/yaml/folding/YAMLFoldingBuilder.java index a891b40e0fb1..679ac2004c73 100644 --- a/plugins/yaml/src/org/jetbrains/yaml/folding/YAMLFoldingBuilder.java +++ b/plugins/yaml/src/org/jetbrains/yaml/folding/YAMLFoldingBuilder.java @@ -26,7 +26,7 @@ import java.util.List; */ public class YAMLFoldingBuilder extends FoldingBuilderEx implements DumbAware { - private static final int PLACEHOLDER_LEN = 10; + private static final int PLACEHOLDER_LEN = 20; @NotNull @Override @@ -110,6 +110,6 @@ public class YAMLFoldingBuilder extends FoldingBuilderEx implements DumbAware { if (text.length() <= PLACEHOLDER_LEN) { return text; } - return text.substring(0, Math.min(text.length(), PLACEHOLDER_LEN)) + "..."; + return StringUtil.trimMiddle(text, PLACEHOLDER_LEN); } } diff --git a/plugins/yaml/testSrc/org/jetbrains/yaml/folding/YAMLFoldingTest.java b/plugins/yaml/testSrc/org/jetbrains/yaml/folding/YAMLFoldingTest.java index 5ba2cafb11a4..4826665d1522 100644 --- a/plugins/yaml/testSrc/org/jetbrains/yaml/folding/YAMLFoldingTest.java +++ b/plugins/yaml/testSrc/org/jetbrains/yaml/folding/YAMLFoldingTest.java @@ -27,6 +27,10 @@ public class YAMLFoldingTest extends LightPlatformCodeInsightFixtureTestCase { defaultTest(); } + public void testRuby18677() { + defaultTest(); + } + public void defaultTest() { myFixture.testFolding(getTestDataPath() + getTestName(true) + ".yaml"); } diff --git a/plugins/yaml/testSrc/org/jetbrains/yaml/folding/data/folding.yaml b/plugins/yaml/testSrc/org/jetbrains/yaml/folding/data/folding.yaml index 69068a60b74a..5ffa5718b8c9 100644 --- a/plugins/yaml/testSrc/org/jetbrains/yaml/folding/data/folding.yaml +++ b/plugins/yaml/testSrc/org/jetbrains/yaml/folding/data/folding.yaml @@ -14,14 +14,14 @@ bar foo bar - Key8: "dfjsdfdfsdfsdfsdf" - LongKeyVeryLongYeah9: sdfjhsdjkfhsdf - jshdfjkhsdjkfh - + Key8: "dfjsdfdfsdfssdkjfklsdjfklsdkljfsddfsdf" + LongKeyVeryLongYeah9: fjhsdjkfhsdf + jshdfjkhsdjksdfjsdjhfjskfh + --- key1: {Key2: foo, Key3: bar, Key4: [foo, bar, baz], - Key5: {Key6: {Key7: 'foo bar', Key8: "dfjsdfdfsdfsdfsdf", LongKeyVeryLongYeah9: sdfjhsdjkfhsdf}}} + Key5: {Key6: {Key7: 'foo bar', Key8: "dfjsdfdfsdfsdfsdfskdjhfkjsdhfjkdshjkfsdkjfh", LongKeyVeryLongYeah9: sdfjhsdjkfhsdfsdfhjksdhfjhsdjfhjksdhfjkshdjkfhksjdhfk}}} diff --git a/plugins/yaml/testSrc/org/jetbrains/yaml/folding/data/ruby18677.yaml b/plugins/yaml/testSrc/org/jetbrains/yaml/folding/data/ruby18677.yaml new file mode 100644 index 000000000000..66694d186c7c --- /dev/null +++ b/plugins/yaml/testSrc/org/jetbrains/yaml/folding/data/ruby18677.yaml @@ -0,0 +1,6 @@ +fairly_long_prefix.first: + first: {} +fairly_long_prefix.second: + second: {} +fairly_long_prefix.third: + third: {} \ No newline at end of file diff --git a/python/educational-core/course-creator/resources/META-INF/plugin.xml b/python/educational-core/course-creator/resources/META-INF/plugin.xml index 253ecc51e376..05df2d0152e9 100644 --- a/python/educational-core/course-creator/resources/META-INF/plugin.xml +++ b/python/educational-core/course-creator/resources/META-INF/plugin.xml @@ -17,7 +17,7 @@ - + @@ -26,11 +26,14 @@ + + + @@ -77,16 +80,28 @@ - - - - - + + + + + + + + + + + + + + + + + diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCAnswerPlaceholderActionGroup.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCAnswerPlaceholderActionGroup.java new file mode 100644 index 000000000000..27e915eec5c9 --- /dev/null +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCAnswerPlaceholderActionGroup.java @@ -0,0 +1,12 @@ +package com.jetbrains.edu.coursecreator; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.intellij.openapi.project.DumbAware; + +public class CCAnswerPlaceholderActionGroup extends DefaultActionGroup implements DumbAware { + @Override + public void update(AnActionEvent e) { + CCUtils.updateActionGroup(e); + } +} diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCLanguageManager.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCLanguageManager.java index e797ad9a6160..32b41c7e8037 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCLanguageManager.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCLanguageManager.java @@ -4,6 +4,7 @@ import com.intellij.ide.fileTemplates.FileTemplate; import com.intellij.lang.LanguageExtension; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; +import com.jetbrains.edu.learning.courseFormat.Task; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -26,4 +27,6 @@ public interface CCLanguageManager { default boolean isTestFile(VirtualFile file) { return false; } + + default void createTestsForNewSubtask(@NotNull Project project, @NotNull Task task) {} } diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCSubtaskChangeListener.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCSubtaskChangeListener.java new file mode 100644 index 000000000000..5b9370453bab --- /dev/null +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCSubtaskChangeListener.java @@ -0,0 +1,56 @@ +package com.jetbrains.edu.coursecreator; + +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.io.FileUtilRt; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.containers.ContainerUtil; +import com.jetbrains.edu.learning.StudyLanguageManager; +import com.jetbrains.edu.learning.StudySubtaskChangeListener; +import com.jetbrains.edu.learning.StudyTaskManager; +import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.core.EduNames; +import com.jetbrains.edu.learning.courseFormat.Course; +import com.jetbrains.edu.learning.courseFormat.Task; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class CCSubtaskChangeListener implements StudySubtaskChangeListener { + @Override + public void subtaskChanged(@NotNull Project project, @NotNull Task task, int oldSubtaskNumber, int newSubtaskNumber) { + VirtualFile taskDir = task.getTaskDir(project); + if (taskDir == null) { + return; + } + Course course = StudyTaskManager.getInstance(project).getCourse(); + if (course == null) { + return; + } + StudyLanguageManager manager = StudyUtils.getLanguageManager(course); + if (manager == null) { + return; + } + String testFileName = manager.getTestFileName(); + if (newSubtaskNumber != 0) { + String nameWithoutExtension = FileUtil.getNameWithoutExtension(testFileName); + String extension = FileUtilRt.getExtension(testFileName); + testFileName = nameWithoutExtension + EduNames.SUBTASK_MARKER + newSubtaskNumber + "." + extension; + } + VirtualFile newTestFile = taskDir.findChild(testFileName); + if (newTestFile == null) { + return; + } + FileEditorManager editorManager = FileEditorManager.getInstance(project); + List testFiles = + ContainerUtil.filter(taskDir.getChildren(), file -> CCUtils.isTestsFile(project, file) && editorManager.isFileOpen(file)); + if (testFiles.isEmpty()) { + return; + } + for (VirtualFile testFile : testFiles) { + editorManager.closeFile(testFile); + } + editorManager.openFile(newTestFile, false); + } +} diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCSubtaskEditorNotificationProvider.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCSubtaskEditorNotificationProvider.java new file mode 100644 index 000000000000..a2a49225e155 --- /dev/null +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCSubtaskEditorNotificationProvider.java @@ -0,0 +1,282 @@ +package com.jetbrains.edu.coursecreator; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.colors.EditorColors; +import com.intellij.openapi.editor.colors.EditorColorsManager; +import com.intellij.openapi.fileEditor.FileEditor; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +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.Key; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.io.FileUtilRt; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.EditorNotificationPanel; +import com.intellij.ui.EditorNotifications; +import com.intellij.ui.awt.RelativePoint; +import com.intellij.util.containers.ContainerUtil; +import com.jetbrains.edu.coursecreator.actions.CCNewSubtaskAction; +import com.jetbrains.edu.learning.StudySubtaskUtils; +import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.core.EduNames; +import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; +import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholderSubtaskInfo; +import com.jetbrains.edu.learning.courseFormat.Task; +import com.jetbrains.edu.learning.courseFormat.TaskFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; +import java.io.IOException; +import java.util.*; +import java.util.List; + +public class CCSubtaskEditorNotificationProvider extends EditorNotifications.Provider implements DumbAware { + private static final Key KEY = Key.create("edu.coursecreator.subtask"); + public static final String SWITCH_SUBTASK = "Switch subtask"; + public static final Integer ADD_SUBTASK_ID = -1; + private static final Logger LOG = Logger.getInstance(CCSubtaskEditorNotificationProvider.class); + private final Project myProject; + + public CCSubtaskEditorNotificationProvider(Project project) { + myProject = project; + } + + @NotNull + @Override + public Key getKey() { + return KEY; + } + + @Nullable + @Override + public EditorNotificationPanel createNotificationPanel(@NotNull VirtualFile file, @NotNull FileEditor fileEditor) { + if (!CCUtils.isCourseCreator(myProject)) { + return null; + } + boolean isTestFile = CCUtils.isTestsFile(myProject, file); + if (!isTestFile && StudyUtils.getTaskFile(myProject, file) == null) { + return null; + } + Task task = StudyUtils.getTaskForFile(myProject, file); + if (task == null || !task.hasSubtasks()) { + return null; + } + EditorNotificationPanel panel = new EditorNotificationPanel() { + @Override + public Color getBackground() { + Color color = EditorColorsManager.getInstance().getGlobalScheme().getColor(EditorColors.GUTTER_BACKGROUND); + return color == null ? super.getBackground() : color; + } + }; + String header = (isTestFile ? "test" : "task") + " file"; + int activeSubtaskIndex = task.getActiveSubtaskIndex() + 1; + int subtaskSize = task.getLastSubtaskIndex() + 1; + panel.setText("This is a " + header + " for " + EduNames.SUBTASK + " " + activeSubtaskIndex + "/" + subtaskSize); + panel.createActionLabel(SWITCH_SUBTASK, () -> { + ArrayList values = new ArrayList<>(); + for (int i = 0; i <= task.getLastSubtaskIndex(); i++) { + values.add(i); + } + values.add(ADD_SUBTASK_ID); + JBPopupFactory.getInstance().createListPopup(new SwitchSubtaskPopupStep(SWITCH_SUBTASK, values, task, file)) + .show(RelativePoint.getSouthEastOf(panel)); + }); + return panel; + } + + private class SwitchSubtaskPopupStep extends BaseListPopupStep { + private final Task myTask; + private final VirtualFile myFile; + + public SwitchSubtaskPopupStep(@Nullable String title, + List values, + Task task, VirtualFile file) { + super(title, values); + myTask = task; + myFile = file; + } + + @NotNull + @Override + public String getTextFor(Integer value) { + if (value.equals(ADD_SUBTASK_ID)) { + return CCNewSubtaskAction.NEW_SUBTASK; + } + int subtaskNum = value + 1; + String text = EduNames.SUBTASK + " " + subtaskNum; + if (value == myTask.getActiveSubtaskIndex()) { + text += " (selected)"; + } + return text; + } + + @Override + public PopupStep onChosen(Integer selectedValue, boolean finalChoice) { + if (finalChoice) { + if (selectedValue.equals(ADD_SUBTASK_ID)) { + return doFinalStep(() -> CCNewSubtaskAction.addSubtask(myFile, myProject)); + } + StudySubtaskUtils.switchStep(myProject, myTask, selectedValue); + } + else { + if (hasSubstep(selectedValue)) { + return new ActionsPopupStep(myTask, selectedValue); + } + } + return super.onChosen(selectedValue, false); + } + + @Override + public boolean hasSubstep(Integer selectedValue) { + return !selectedValue.equals(ADD_SUBTASK_ID); + } + + @Override + public int getDefaultOptionIndex() { + return myTask.getActiveSubtaskIndex(); + } + + @Nullable + @Override + public ListSeparator getSeparatorAbove(Integer value) { + return value.equals(ADD_SUBTASK_ID) ? new ListSeparator() : null; + } + } + + private class ActionsPopupStep extends BaseListPopupStep { + + public static final String SELECT = "Select"; + public static final String DELETE = "Delete"; + private final Task myTask; + private final int mySubtaskIndex; + + public ActionsPopupStep(Task task, int subtaskIndex) { + super(null, Arrays.asList(SELECT, DELETE)); + myTask = task; + mySubtaskIndex = subtaskIndex; + } + + @Override + public PopupStep onChosen(String selectedValue, boolean finalChoice) { + if (finalChoice) { + if (selectedValue.equals(SELECT)) { + StudySubtaskUtils.switchStep(myProject, myTask, mySubtaskIndex); + } + else { + for (TaskFile taskFile : myTask.getTaskFiles().values()) { + List emptyPlaceholders = new ArrayList<>(); + for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) { + Map infos = placeholder.getSubtaskInfos(); + if (infos.containsKey(mySubtaskIndex)) { + infos.remove(mySubtaskIndex); + if (infos.isEmpty()) { + emptyPlaceholders.add(placeholder); + } + } + } + taskFile.getAnswerPlaceholders().removeAll(emptyPlaceholders); + } + VirtualFile taskDir = myTask.getTaskDir(myProject); + if (taskDir == null) { + return FINAL_CHOICE; + } + deleteSubtaskFiles(taskDir); + if (mySubtaskIndex != myTask.getLastSubtaskIndex()) { + renameFiles(taskDir); + updateInfoIndexes(); + } + myTask.setLastSubtaskIndex(myTask.getLastSubtaskIndex() - 1); + int activeSubtaskIndex = myTask.getActiveSubtaskIndex(); + if (mySubtaskIndex != 0 && activeSubtaskIndex == mySubtaskIndex) { + StudySubtaskUtils.switchStep(myProject, myTask, mySubtaskIndex - 1); + } + if (activeSubtaskIndex > mySubtaskIndex) { + myTask.setActiveSubtaskIndex(activeSubtaskIndex - 1); + } + StudySubtaskUtils.updateUI(myProject, myTask, taskDir); + for (VirtualFile file : FileEditorManager.getInstance(myProject).getOpenFiles()) { + EditorNotifications.getInstance(myProject).updateNotifications(file); + } + + return FINAL_CHOICE; + } + } + return super.onChosen(selectedValue, finalChoice); + } + + private void updateInfoIndexes() { + for (TaskFile taskFile : myTask.getTaskFiles().values()) { + for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) { + List filtered = ContainerUtil.filter(placeholder.getSubtaskInfos().keySet(), index -> index > mySubtaskIndex); + Map savedInfos = new HashMap<>(); + for (Integer index : filtered) { + savedInfos.put(index, placeholder.getSubtaskInfos().get(index)); + placeholder.getSubtaskInfos().remove(index); + } + for (Integer index : filtered) { + placeholder.getSubtaskInfos().put(index - 1, savedInfos.get(index)); + } + } + } + } + + private void renameFiles(VirtualFile taskDir) { + ApplicationManager.getApplication().runWriteAction(() -> { + Map newNames = new HashMap<>(); + for (VirtualFile virtualFile : taskDir.getChildren()) { + int subtaskIndex = CCUtils.getSubtaskIndex(myProject, virtualFile); + if (subtaskIndex == -1) { + continue; + } + if (subtaskIndex > mySubtaskIndex) { + String index = subtaskIndex == 1 ? "" : Integer.toString(subtaskIndex - 1); + String fileName = virtualFile.getName(); + String nameWithoutExtension = FileUtil.getNameWithoutExtension(fileName); + String extension = FileUtilRt.getExtension(fileName); + int subtaskMarkerIndex = nameWithoutExtension.indexOf(EduNames.SUBTASK_MARKER); + String newName = subtaskMarkerIndex == -1 + ? nameWithoutExtension + : nameWithoutExtension.substring(0, subtaskMarkerIndex); + newName += index.isEmpty() ? "" : EduNames.SUBTASK_MARKER; + newName += index + "." + extension; + newNames.put(virtualFile, newName); + } + } + for (Map.Entry entry : newNames.entrySet()) { + try { + entry.getKey().rename(myProject, entry.getValue()); + } + catch (IOException e) { + LOG.info(e); + } + } + }); + } + + private void deleteSubtaskFiles(VirtualFile taskDir) { + ApplicationManager.getApplication().runWriteAction(() -> { + List filesToDelete = new ArrayList<>(); + for (VirtualFile file : taskDir.getChildren()) { + int index = CCUtils.getSubtaskIndex(myProject, file); + if (index != -1 && mySubtaskIndex == index) { + filesToDelete.add(file); + } + } + for (VirtualFile file : filesToDelete) { + try { + file.delete(myProject); + } + catch (IOException e) { + LOG.info(e); + } + } + }); + } + } +} \ No newline at end of file diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCTestsTabTitleProvider.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCTestsTabTitleProvider.java new file mode 100644 index 000000000000..fc74f839a8a2 --- /dev/null +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCTestsTabTitleProvider.java @@ -0,0 +1,30 @@ +package com.jetbrains.edu.coursecreator; + +import com.intellij.openapi.fileEditor.impl.EditorTabTitleProvider; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.jetbrains.edu.learning.StudyLanguageManager; +import com.jetbrains.edu.learning.StudyTaskManager; +import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.courseFormat.Course; +import org.jetbrains.annotations.Nullable; + +public class CCTestsTabTitleProvider implements EditorTabTitleProvider { + @Nullable + @Override + public String getEditorTabTitle(Project project, VirtualFile file) { + if (!CCUtils.isCourseCreator(project)) { + return null; + } + if (!CCUtils.isTestsFile(project, file)) { + return null; + } + Course course = StudyTaskManager.getInstance(project).getCourse(); + assert course != null; + StudyLanguageManager manager = StudyUtils.getLanguageManager(course); + if (manager == null) { + return null; + } + return manager.getTestFileName(); + } +} \ No newline at end of file diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCUtils.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCUtils.java index 27e5c6534f4b..2dd6270f5d67 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCUtils.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCUtils.java @@ -2,6 +2,11 @@ package com.jetbrains.edu.coursecreator; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; +import com.intellij.ide.IdeView; +import com.intellij.ide.fileTemplates.FileTemplate; +import com.intellij.ide.fileTemplates.FileTemplateManager; +import com.intellij.ide.fileTemplates.FileTemplateUtil; +import com.intellij.ide.util.EditorHelper; import com.intellij.lang.Language; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.Presentation; @@ -18,8 +23,12 @@ import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiElement; import com.intellij.util.Function; +import com.jetbrains.edu.coursecreator.settings.CCSettings; import com.jetbrains.edu.learning.StudyTaskManager; +import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.core.EduUtils; import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.StudyItem; @@ -41,6 +50,29 @@ public class CCUtils { public static final String GENERATED_FILES_FOLDER = ".coursecreator"; public static final String COURSE_MODE = "Course Creator"; + public static int getSubtaskIndex(@NotNull Project project, @NotNull VirtualFile file) { + String fileName = file.getName(); + String name = FileUtil.getNameWithoutExtension(fileName); + boolean canBeSubtaskFile = isTestsFile(project, file) || StudyUtils.isTaskDescriptionFile(fileName); + if (!canBeSubtaskFile) { + return -1; + } + if (!name.contains(EduNames.SUBTASK_MARKER)) { + return 0; + } + int markerIndex = name.indexOf(EduNames.SUBTASK_MARKER); + String index = name.substring(markerIndex + EduNames.SUBTASK_MARKER.length()); + if (index.isEmpty()) { + return -1; + } + try { + return Integer.valueOf(index); + } + catch (NumberFormatException e) { + return -1; + } + } + @Nullable public static CCLanguageManager getStudyLanguageManager(@NotNull final Course course) { Language language = Language.findLanguageByID(course.getLanguageID()); @@ -131,7 +163,8 @@ public class CCUtils { if (contentRootForFile == null) { return; } - ModuleRootModificationUtil.updateExcludedFolders(module, contentRootForFile, Collections.emptyList(), Collections.singletonList(generatedRoot.get().getUrl())); + ModuleRootModificationUtil.updateExcludedFolders(module, contentRootForFile, Collections.emptyList(), + Collections.singletonList(generatedRoot.get().getUrl())); } catch (IOException e) { LOG.info("Failed to create folder for generated files", e); @@ -238,7 +271,7 @@ public class CCUtils { continue; } ApplicationManager.getApplication().runWriteAction(() -> { - EduUtils.createStudentFile(CCUtils.class, project, answerFile, studentDir, null); + EduUtils.createStudentFile(CCUtils.class, project, answerFile, studentDir, null, task.getActiveSubtaskIndex()); }); } } @@ -248,4 +281,40 @@ public class CCUtils { Project project = e.getProject(); presentation.setEnabledAndVisible(project != null && isCourseCreator(project)); } + + private static void createFromTemplate(@NotNull final PsiDirectory taskDirectory, + @Nullable final FileTemplate template, + @Nullable IdeView view, boolean open) { + if (template == null) { + return; + } + try { + final PsiElement file = FileTemplateUtil.createFromTemplate(template, template.getName(), null, taskDirectory); + if (view != null && open) { + EditorHelper.openInEditor(file, false); + view.selectElement(file); + } + } + catch (Exception e) { + LOG.error(e); + } + } + + public static void createTaskContent(@NotNull Project project, + @Nullable IdeView view, + @NotNull Course course, + PsiDirectory taskDirectory) { + CCLanguageManager manager = getStudyLanguageManager(course); + if (manager == null) { + return; + } + createFromTemplate(taskDirectory, manager.getTestsTemplate(project), view, false); + createFromTemplate(taskDirectory, FileTemplateManager.getInstance(project) + .getInternalTemplate(StudyUtils.getTaskDescriptionFileName(CCSettings.getInstance().useHtmlAsDefaultTaskFormat())), view, false); + String defaultExtension = manager.getDefaultTaskFileExtension(); + if (defaultExtension != null) { + FileTemplate taskFileTemplate = manager.getTaskFileTemplateForExtension(project, defaultExtension); + createFromTemplate(taskDirectory, taskFileTemplate, view, true); + } + } } diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCVirtualFileListener.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCVirtualFileListener.java index 572db02ec8d1..c34c6260669f 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCVirtualFileListener.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCVirtualFileListener.java @@ -58,8 +58,7 @@ public class CCVirtualFileListener extends VirtualFileAdapter { || name.contains(EduNames.ANSWERS_POSTFIX)) { return; } - - VirtualFile taskVF = createdFile.getParent(); + VirtualFile taskVF = StudyUtils.getTaskDir(createdFile); if (taskVF == null) { return; } diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCCreateCourseArchive.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCCreateCourseArchive.java index 85c257fa03af..244a0b984384 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCCreateCourseArchive.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCCreateCourseArchive.java @@ -14,10 +14,12 @@ import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.io.ZipUtil; import com.jetbrains.edu.coursecreator.CCLanguageManager; import com.jetbrains.edu.coursecreator.CCUtils; @@ -32,6 +34,8 @@ import com.jetbrains.edu.learning.statistics.EduUsagesCollector; import org.jetbrains.annotations.NotNull; import java.io.*; +import java.util.Arrays; +import java.util.List; import java.util.zip.ZipOutputStream; public class CCCreateCourseArchive extends DumbAwareAction { @@ -103,6 +107,7 @@ public class CCCreateCourseArchive extends DumbAwareAction { ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { + archiveFolder.refresh(false, true); Course courseCopy = course.copy(); replaceAnswerFilesWithTaskFiles(courseCopy); generateJson(archiveFolder, courseCopy); @@ -113,25 +118,48 @@ public class CCCreateCourseArchive extends DumbAwareAction { private void replaceAnswerFilesWithTaskFiles(Course courseCopy) { for (Lesson lesson : courseCopy.getLessons()) { - final VirtualFile lessonDir = baseDir.findChild(EduNames.LESSON + String.valueOf(lesson.getIndex())); + String lessonDirName = EduNames.LESSON + String.valueOf(lesson.getIndex()); + final VirtualFile lessonDir = baseDir.findChild(lessonDirName); if (lessonDir == null) continue; for (Task task : lesson.getTaskList()) { - final VirtualFile taskDir = lessonDir.findChild(EduNames.TASK + String.valueOf(task.getIndex())); + final VirtualFile taskDir = task.getTaskDir(project); if (taskDir == null) continue; - VirtualFile studentFileDir = VfsUtil.findRelativeFile(archiveFolder, lessonDir.getName(), taskDir.getName()); + String taskDirName = EduNames.TASK + String.valueOf(task.getIndex()); + VirtualFile studentFileDir = VfsUtil.findRelativeFile(archiveFolder, lessonDirName, taskDirName); if (studentFileDir == null) { continue; } + VirtualFile srcDir = studentFileDir.findChild(EduNames.SRC); + if (srcDir != null) { + studentFileDir = srcDir; + } + if (task.hasSubtasks()) { + transformSubtaskTestsToTextFiles(studentFileDir); + } for (String taskFile : task.getTaskFiles().keySet()) { VirtualFile answerFile = taskDir.findChild(taskFile); if (answerFile == null) { continue; } - EduUtils.createStudentFile(this, project, answerFile, studentFileDir, task); + EduUtils.createStudentFile(this, project, answerFile, studentFileDir, task, 0); } + } + } + } + + private void transformSubtaskTestsToTextFiles(VirtualFile studentFileDir) { + Condition isSubtaskTestFile = + file -> CCUtils.isTestsFile(project, file) && file.getName().contains(EduNames.SUBTASK_MARKER); + List subtaskTests = ContainerUtil.filter(Arrays.asList(studentFileDir.getChildren()), isSubtaskTestFile); + for (VirtualFile subtaskTest : subtaskTests) { + try { + subtaskTest.rename(this, subtaskTest.getNameWithoutExtension() + ".txt"); + } + catch (IOException e) { + LOG.error(e); + } } } - } }); } diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCCreateTask.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCCreateTask.java index fbbe0d95fc67..6b4201b22452 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCCreateTask.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCCreateTask.java @@ -1,23 +1,14 @@ package com.jetbrains.edu.coursecreator.actions; import com.intellij.ide.IdeView; -import com.intellij.ide.fileTemplates.FileTemplate; -import com.intellij.ide.fileTemplates.FileTemplateManager; -import com.intellij.ide.fileTemplates.FileTemplateUtil; import com.intellij.ide.util.DirectoryUtil; -import com.intellij.ide.util.EditorHelper; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiElement; import com.intellij.util.Function; -import com.jetbrains.edu.coursecreator.CCLanguageManager; import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.coursecreator.settings.CCSettings; -import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.core.EduUtils; import com.jetbrains.edu.learning.courseFormat.Course; @@ -32,7 +23,6 @@ import java.util.Collections; import java.util.List; public class CCCreateTask extends CCCreateStudyItemActionBase { - private static final Logger LOG = Logger.getInstance(CCCreateTask.class.getName()); public static final String TITLE = "Create New " + EduNames.TASK_TITLED; public CCCreateTask() { @@ -40,23 +30,7 @@ public class CCCreateTask extends CCCreateStudyItemActionBase { } - private static void createFromTemplate(@NotNull final PsiDirectory taskDirectory, - @Nullable final FileTemplate template, - @Nullable IdeView view, boolean open) { - if (template == null) { - return; - } - try { - final PsiElement file = FileTemplateUtil.createFromTemplate(template, template.getName(), null, taskDirectory); - if (view != null && open) { - EditorHelper.openInEditor(file, false); - view.selectElement(file); - } - } - catch (Exception e) { - LOG.error(e); - } - } + @Nullable @Override @@ -96,18 +70,7 @@ public class CCCreateTask extends CCCreateStudyItemActionBase { String taskDirName = EduNames.TASK + item.getIndex(); taskDirectory.set(DirectoryUtil.createSubdirectories(taskDirName, parentDirectory, "\\/")); if (taskDirectory.get() != null) { - CCLanguageManager manager = CCUtils.getStudyLanguageManager(course); - if (manager == null) { - return; - } - createFromTemplate(taskDirectory.get(), manager.getTestsTemplate(project), view, false); - createFromTemplate(taskDirectory.get(), FileTemplateManager.getInstance(project) - .getInternalTemplate(StudyUtils.getTaskDescriptionFileName(CCSettings.getInstance().useHtmlAsDefaultTaskFormat())), view, false); - String defaultExtension = manager.getDefaultTaskFileExtension(); - if (defaultExtension != null) { - FileTemplate taskFileTemplate = manager.getTaskFileTemplateForExtension(project, defaultExtension); - createFromTemplate(taskDirectory.get(), taskFileTemplate, view, true); - } + CCUtils.createTaskContent(project, view, course, taskDirectory.get()); } }); return taskDirectory.get(); diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCEditTaskTextAction.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCEditTaskTextAction.java index 5786ebe51199..fe4ae15c8de3 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCEditTaskTextAction.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCEditTaskTextAction.java @@ -50,7 +50,7 @@ public class CCEditTaskTextAction extends ToggleAction implements DumbAware { return; } final StudyState studyState = new StudyState(selectedEditor); - VirtualFile taskTextFile = StudyUtils.findTaskDescriptionVirtualFile(studyState.getTaskDir()); + VirtualFile taskTextFile = StudyUtils.findTaskDescriptionVirtualFile(project, studyState.getTaskDir()); if (taskTextFile == null) { LOG.info("Failed to find task.html"); return; diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCFromCourseArchive.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCFromCourseArchive.java index 69c628f360ce..91e13d9314d0 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCFromCourseArchive.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCFromCourseArchive.java @@ -143,7 +143,7 @@ public class CCFromCourseArchive extends DumbAwareAction { EduDocumentListener listener = new EduDocumentListener(taskFile, false); document.addDocumentListener(listener); taskFile.sortAnswerPlaceholders(); - for (int i = taskFile.getAnswerPlaceholders().size() - 1; i >= 0; i--) { + for (int i = taskFile.getActivePlaceholders().size() - 1; i >= 0; i--) { final AnswerPlaceholder answerPlaceholder = taskFile.getAnswerPlaceholders().get(i); replaceAnswerPlaceholder(project, document, answerPlaceholder); } diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCHideFromStudent.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCHideFromStudent.java index 8d3100240063..b183eeda7b38 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCHideFromStudent.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCHideFromStudent.java @@ -64,7 +64,7 @@ public class CCHideFromStudent extends CCTaskFileActionBase { for (FileEditor fileEditor : FileEditorManager.getInstance(myProject).getEditors(myFile)) { if (fileEditor instanceof TextEditor) { Editor editor = ((TextEditor)fileEditor).getEditor(); - StudyUtils.drawAllWindows(editor, myTaskFile); + StudyUtils.drawAllAnswerPlaceholders(editor, myTaskFile); } } } diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCNewSubtaskAction.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCNewSubtaskAction.java new file mode 100644 index 000000000000..ae3558f524ed --- /dev/null +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCNewSubtaskAction.java @@ -0,0 +1,112 @@ +package com.jetbrains.edu.coursecreator.actions; + +import com.intellij.ide.fileTemplates.FileTemplate; +import com.intellij.ide.fileTemplates.FileTemplateManager; +import com.intellij.ide.fileTemplates.FileTemplateUtil; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.io.FileUtilRt; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiManager; +import com.jetbrains.edu.coursecreator.CCLanguageManager; +import com.jetbrains.edu.coursecreator.CCUtils; +import com.jetbrains.edu.coursecreator.settings.CCSettings; +import com.jetbrains.edu.learning.StudySubtaskUtils; +import com.jetbrains.edu.learning.StudyTaskManager; +import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.core.EduNames; +import com.jetbrains.edu.learning.courseFormat.Course; +import com.jetbrains.edu.learning.courseFormat.Task; +import org.jetbrains.annotations.NotNull; + + +public class CCNewSubtaskAction extends DumbAwareAction { + private static final Logger LOG = Logger.getInstance(CCNewSubtaskAction.class); + public static final String NEW_SUBTASK = "Add Subtask"; + + public CCNewSubtaskAction() { + super(NEW_SUBTASK); + } + + @Override + public void actionPerformed(AnActionEvent e) { + DataContext dataContext = e.getDataContext(); + VirtualFile virtualFile = CommonDataKeys.VIRTUAL_FILE.getData(dataContext); + Editor editor = CommonDataKeys.EDITOR.getData(dataContext); + Project project = CommonDataKeys.PROJECT.getData(dataContext); + if (virtualFile == null || project == null || editor == null) { + return; + } + addSubtask(virtualFile, project); + } + + public static void addSubtask(@NotNull VirtualFile virtualFile, @NotNull Project project) { + Task task = StudyUtils.getTaskForFile(project, virtualFile); + if (task == null) { + return; + } + VirtualFile taskDir = task.getTaskDir(project); + if (taskDir == null) { + return; + } + createTestsForNewSubtask(project, task); + int num = task.getLastSubtaskIndex() + 1; + createTaskDescriptionFile(project, taskDir, num); + StudySubtaskUtils.switchStep(project, task, num); + task.setLastSubtaskIndex(num); + } + + private static void createTestsForNewSubtask(Project project, Task task) { + Course course = StudyTaskManager.getInstance(project).getCourse(); + if (course == null) { + return; + } + CCLanguageManager manager = CCUtils.getStudyLanguageManager(course); + if (manager == null) { + return; + } + manager.createTestsForNewSubtask(project, task); + } + + private static void createTaskDescriptionFile(Project project, VirtualFile taskDir, int index) { + String taskDescriptionFileName = StudyUtils.getTaskDescriptionFileName(CCSettings.getInstance().useHtmlAsDefaultTaskFormat()); + FileTemplate taskTextTemplate = FileTemplateManager.getInstance(project).getInternalTemplate(taskDescriptionFileName); + PsiDirectory taskPsiDir = PsiManager.getInstance(project).findDirectory(taskDir); + if (taskTextTemplate != null && taskPsiDir != null) { + String nextTaskTextName = FileUtil.getNameWithoutExtension(taskDescriptionFileName) + + EduNames.SUBTASK_MARKER + + index + "." + + FileUtilRt.getExtension(taskDescriptionFileName); + try { + FileTemplateUtil.createFromTemplate(taskTextTemplate, nextTaskTextName, null, taskPsiDir); + } + catch (Exception e) { + LOG.error(e); + } + } + } + + @Override + public void update(AnActionEvent e) { + DataContext dataContext = e.getDataContext(); + Presentation presentation = e.getPresentation(); + presentation.setEnabledAndVisible(false); + VirtualFile virtualFile = CommonDataKeys.VIRTUAL_FILE.getData(dataContext); + Editor editor = CommonDataKeys.EDITOR.getData(dataContext); + Project project = CommonDataKeys.PROJECT.getData(dataContext); + if (virtualFile == null || project == null || editor == null) { + return; + } + if (CCUtils.isCourseCreator(project) && StudyUtils.getTaskForFile(project, virtualFile) != null) { + presentation.setEnabledAndVisible(true); + } + } +} \ No newline at end of file diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCShowPreview.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCShowPreview.java index f312436106c2..fcf44cf76434 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCShowPreview.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCShowPreview.java @@ -52,6 +52,7 @@ import javax.swing.border.EmptyBorder; import java.awt.*; import java.text.SimpleDateFormat; import java.util.Calendar; +import java.util.Collections; public class CCShowPreview extends DumbAwareAction { public static final String SHOW_PREVIEW = "Show Preview"; @@ -108,7 +109,7 @@ public class CCShowPreview extends DumbAwareAction { } - if (taskFile.getAnswerPlaceholders().isEmpty()) { + if (taskFile.getActivePlaceholders().isEmpty()) { Messages.showInfoMessage("Preview is available for task files with answer placeholders only", "No Preview for This File"); return; } @@ -123,7 +124,7 @@ public class CCShowPreview extends DumbAwareAction { @Override public void run() { Pair pair = - EduUtils.createStudentFile(this, project, virtualFile, generatedFilesFolder, null); + EduUtils.createStudentFile(this, project, virtualFile, generatedFilesFolder, null, taskFile.getTask().getActiveSubtaskIndex()); if (pair != null) { showPreviewDialog(project, pair.getFirst(), pair.getSecond()); } @@ -146,8 +147,12 @@ public class CCShowPreview extends DumbAwareAction { factory.releaseEditor(createdEditor); } }); - for (AnswerPlaceholder answerPlaceholder : taskFile.getAnswerPlaceholders()) { - answerPlaceholder.setUseLength(true); + for (AnswerPlaceholder answerPlaceholder : taskFile.getActivePlaceholders()) { + if (answerPlaceholder.getActiveSubtaskInfo().isNeedInsertText()) { + answerPlaceholder.setLength(answerPlaceholder.getTaskText().length()); + } + Integer minIndex = Collections.min(answerPlaceholder.getSubtaskInfos().keySet()); + answerPlaceholder.setUseLength(minIndex >= answerPlaceholder.getActiveSubtaskIndex()); EduAnswerPlaceholderPainter.drawAnswerPlaceholder(createdEditor, answerPlaceholder, JBColor.BLUE); } JPanel header = new JPanel(); diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCTaskFileActionBase.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCTaskFileActionBase.java index 649db137a19a..395e62e1c0b4 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCTaskFileActionBase.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCTaskFileActionBase.java @@ -28,7 +28,7 @@ public abstract class CCTaskFileActionBase extends AnAction { if (file == null) { return; } - VirtualFile taskVF = file.getParent(); + VirtualFile taskVF = StudyUtils.getTaskDir(file); if (taskVF == null) { return; } diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCActivatePlaceholder.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCActivatePlaceholder.java new file mode 100644 index 000000000000..0bb15cfbbe29 --- /dev/null +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCActivatePlaceholder.java @@ -0,0 +1,50 @@ +package com.jetbrains.edu.coursecreator.actions.placeholder; + +import com.intellij.openapi.util.TextRange; +import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; +import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholderSubtaskInfo; +import com.jetbrains.edu.learning.courseFormat.TaskFile; +import org.jetbrains.annotations.NotNull; + +public class CCActivatePlaceholder extends CCSubtaskPlaceholderAction { + + public static final String TITLE = "Activate"; + + protected CCActivatePlaceholder() { + super(TITLE, TITLE); + } + + @Override + protected AnswerPlaceholderSubtaskInfo getInfo(@NotNull CCState state, + int subtaskIndex, + @NotNull AnswerPlaceholder existingPlaceholder) { + int visibleLength = existingPlaceholder.getVisibleLength(subtaskIndex); + int placeholderOffset = existingPlaceholder.getOffset(); + String possibleAnswer = state.getEditor().getDocument().getText(TextRange.create(placeholderOffset, placeholderOffset + visibleLength)); + AnswerPlaceholderSubtaskInfo info = new AnswerPlaceholderSubtaskInfo(); + info.setPossibleAnswer(possibleAnswer); + return info; + } + + @Override + protected String getTitle() { + return TITLE; + } + + @Override + protected void redoAction(@NotNull AnswerPlaceholder existingPlaceholder, int subtaskIndex, @NotNull AnswerPlaceholderSubtaskInfo info) { + existingPlaceholder.getSubtaskInfos().put(subtaskIndex, info); + } + + @Override + protected void undoAction(@NotNull AnswerPlaceholder existingPlaceholder, int subtaskIndex, @NotNull AnswerPlaceholderSubtaskInfo info) { + existingPlaceholder.getSubtaskInfos().remove(subtaskIndex); + } + + @Override + protected boolean isAvailable(TaskFile taskFile, int offset) { + AnswerPlaceholder existingPlaceholder = StudyUtils.getAnswerPlaceholder(offset, taskFile.getAnswerPlaceholders()); + return existingPlaceholder != null && !existingPlaceholder.isActive(); + } +} diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCAddAnswerPlaceholder.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCAddAnswerPlaceholder.java similarity index 74% rename from python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCAddAnswerPlaceholder.java rename to python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCAddAnswerPlaceholder.java index b2f26711ac5a..9651adf4e9dc 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCAddAnswerPlaceholder.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCAddAnswerPlaceholder.java @@ -1,4 +1,4 @@ -package com.jetbrains.edu.coursecreator.actions; +package com.jetbrains.edu.coursecreator.actions.placeholder; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.Presentation; @@ -10,15 +10,13 @@ import com.intellij.openapi.editor.SelectionModel; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiFile; import com.intellij.ui.JBColor; import com.intellij.util.DocumentUtil; import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.core.EduAnswerPlaceholderPainter; -import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.core.EduUtils; import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; +import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholderSubtaskInfo; import com.jetbrains.edu.learning.courseFormat.TaskFile; import com.jetbrains.edu.learning.ui.CCCreateAnswerPlaceholderDialog; import org.jetbrains.annotations.NotNull; @@ -28,12 +26,12 @@ import java.util.List; public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction { public CCAddAnswerPlaceholder() { - super("Add/Delete Answer Placeholder", "Add/Delete answer placeholder"); + super("Add Answer Placeholder", "Add/Delete answer placeholder"); } private static boolean arePlaceholdersIntersect(@NotNull final TaskFile taskFile, int start, int end) { - List answerPlaceholders = taskFile.getAnswerPlaceholders(); + List answerPlaceholders = taskFile.getActivePlaceholders(); for (AnswerPlaceholder existingAnswerPlaceholder : answerPlaceholders) { int twStart = existingAnswerPlaceholder.getOffset(); int twEnd = existingAnswerPlaceholder.getPossibleAnswerLength() + twStart; @@ -48,28 +46,29 @@ public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction { private void addPlaceholder(@NotNull CCState state) { Editor editor = state.getEditor(); Project project = state.getProject(); - PsiFile file = state.getFile(); - - final Document document = PsiDocumentManager.getInstance(project).getDocument(file); - if (document == null) { - return; - } + Document document = editor.getDocument(); FileDocumentManager.getInstance().saveDocument(document); final SelectionModel model = editor.getSelectionModel(); final int offset = model.hasSelection() ? model.getSelectionStart() : editor.getCaretModel().getOffset(); + TaskFile taskFile = state.getTaskFile(); + int subtaskIndex = state.getTaskFile().getTask().getActiveSubtaskIndex(); final AnswerPlaceholder answerPlaceholder = new AnswerPlaceholder(); - + AnswerPlaceholderSubtaskInfo info = new AnswerPlaceholderSubtaskInfo(); + answerPlaceholder.getSubtaskInfos().put(subtaskIndex, info); + int index = taskFile.getAnswerPlaceholders().size(); + answerPlaceholder.setIndex(index); + answerPlaceholder.setTaskFile(taskFile); + taskFile.sortAnswerPlaceholders(); answerPlaceholder.setOffset(offset); answerPlaceholder.setUseLength(false); String defaultPlaceholderText = "type here"; - answerPlaceholder.setPossibleAnswer(model.hasSelection() ? model.getSelectedText() : defaultPlaceholderText); - CCCreateAnswerPlaceholderDialog dlg = createDialog(project, answerPlaceholder); if (!dlg.showAndGet()) { return; } String answerPlaceholderText = dlg.getTaskText(); + answerPlaceholder.setPossibleAnswer(model.hasSelection() ? model.getSelectedText() : defaultPlaceholderText); answerPlaceholder.setTaskText(StringUtil.notNullize(answerPlaceholderText)); answerPlaceholder.setLength(StringUtil.notNullize(answerPlaceholderText).length()); answerPlaceholder.setHints(dlg.getHints()); @@ -78,11 +77,6 @@ public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction { DocumentUtil.writeInRunUndoTransparentAction(() -> document.insertString(offset, defaultPlaceholderText)); } - TaskFile taskFile = state.getTaskFile(); - int index = taskFile.getAnswerPlaceholders().size(); - answerPlaceholder.setIndex(index); - answerPlaceholder.setTaskFile(taskFile); - taskFile.sortAnswerPlaceholders(); answerPlaceholder.setPossibleAnswer(model.hasSelection() ? model.getSelectedText() : defaultPlaceholderText); AddAction action = new AddAction(answerPlaceholder, taskFile, editor); EduUtils.runUndoableAction(project, "Add Answer Placeholder", action); @@ -106,7 +100,7 @@ public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction { if (answerPlaceholders.contains(myPlaceholder)) { answerPlaceholders.remove(myPlaceholder); myEditor.getMarkupModel().removeAllHighlighters(); - StudyUtils.drawAllWindows(myEditor, myTaskFile); + StudyUtils.drawAllAnswerPlaceholders(myEditor, myTaskFile); EduAnswerPlaceholderPainter.createGuardedBlocks(myEditor, myTaskFile); } } @@ -121,30 +115,7 @@ public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction { @Override protected void performAnswerPlaceholderAction(@NotNull CCState state) { - if (canAddPlaceholder(state)) { - addPlaceholder(state); - return; - } - if (canDeletePlaceholder(state)) { - deletePlaceholder(state); - } - } - - private static void deletePlaceholder(@NotNull CCState state) { - Project project = state.getProject(); - TaskFile taskFile = state.getTaskFile(); - AnswerPlaceholder answerPlaceholder = state.getAnswerPlaceholder(); - EduUtils.runUndoableAction(project, "Delete Answer Placeholder", new AddAction(answerPlaceholder, taskFile, state.getEditor()) { - @Override - public void undo() throws UnexpectedUndoException { - super.redo(); - } - - @Override - public void redo() throws UnexpectedUndoException { - super.undo(); - } - }); + addPlaceholder(state); } @Override @@ -158,9 +129,8 @@ public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction { } presentation.setVisible(true); - if (canAddPlaceholder(state) || canDeletePlaceholder(state)) { + if (canAddPlaceholder(state)) { presentation.setEnabled(true); - presentation.setText((state.getAnswerPlaceholder() == null ? "Add " : "Delete ") + EduNames.ANSWER_PLACEHOLDER); } } @@ -168,23 +138,19 @@ public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction { private static boolean canAddPlaceholder(@NotNull CCState state) { Editor editor = state.getEditor(); SelectionModel selectionModel = editor.getSelectionModel(); + TaskFile taskFile = state.getTaskFile(); if (selectionModel.hasSelection()) { int start = selectionModel.getSelectionStart(); int end = selectionModel.getSelectionEnd(); - return !arePlaceholdersIntersect(state.getTaskFile(), start, end); + return !arePlaceholdersIntersect(taskFile, start, end); } int offset = editor.getCaretModel().getOffset(); - return state.getTaskFile().getAnswerPlaceholder(offset) == null; - } - - private static boolean canDeletePlaceholder(@NotNull CCState state) { - if (state.getEditor().getSelectionModel().hasSelection()) { - return false; - } - return state.getAnswerPlaceholder() != null; + return StudyUtils.getAnswerPlaceholder(offset, taskFile.getAnswerPlaceholders()) == null; } protected CCCreateAnswerPlaceholderDialog createDialog(Project project, AnswerPlaceholder answerPlaceholder) { - return new CCCreateAnswerPlaceholderDialog(project, StringUtil.notNullize(answerPlaceholder.getTaskText()), answerPlaceholder.getHints()); + String answerPlaceholderText = StringUtil.notNullize(answerPlaceholder.getTaskText()); + return new CCCreateAnswerPlaceholderDialog(project, answerPlaceholderText.isEmpty() ? "type here" : answerPlaceholderText, + answerPlaceholder.getHints()); } } \ No newline at end of file diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCAnswerPlaceholderAction.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCAnswerPlaceholderAction.java similarity index 96% rename from python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCAnswerPlaceholderAction.java rename to python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCAnswerPlaceholderAction.java index ba3db4d16fb1..b19c0c60143d 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCAnswerPlaceholderAction.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCAnswerPlaceholderAction.java @@ -1,4 +1,4 @@ -package com.jetbrains.edu.coursecreator.actions; +package com.jetbrains.edu.coursecreator.actions.placeholder; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; @@ -42,9 +42,7 @@ abstract public class CCAnswerPlaceholderAction extends DumbAwareAction { if (taskFile == null) { return null; } - AnswerPlaceholder answerPlaceholder = taskFile.getAnswerPlaceholder( - editor.getCaretModel().getOffset() - ); + AnswerPlaceholder answerPlaceholder = taskFile.getAnswerPlaceholder(editor.getCaretModel().getOffset()); return new CCState(taskFile, answerPlaceholder, psiFile, editor, project); } diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCChangePlaceholderVisibility.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCChangePlaceholderVisibility.java new file mode 100644 index 000000000000..eb0c7dfde710 --- /dev/null +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCChangePlaceholderVisibility.java @@ -0,0 +1,106 @@ +package com.jetbrains.edu.coursecreator.actions.placeholder; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.command.undo.BasicUndoableAction; +import com.intellij.openapi.command.undo.UnexpectedUndoException; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.DocumentUtil; +import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.core.EduUtils; +import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; +import com.jetbrains.edu.learning.courseFormat.Task; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; + +public abstract class CCChangePlaceholderVisibility extends CCAnswerPlaceholderAction { + + protected CCChangePlaceholderVisibility(@Nullable String text, @Nullable String description) { + super(text, description); + } + + @Override + protected void performAnswerPlaceholderAction(@NotNull CCState state) { + AnswerPlaceholder placeholder = state.getAnswerPlaceholder(); + if (placeholder == null) { + return; + } + EduUtils.runUndoableAction(state.getProject(), getName(), new BasicUndoableAction(state.getEditor().getDocument()) { + @Override + public void undo() throws UnexpectedUndoException { + setVisible(placeholder, isVisible(), state); + } + + @Override + public void redo() throws UnexpectedUndoException { + setVisible(placeholder, !isVisible(), state); + } + }); + } + + private void setVisible(AnswerPlaceholder placeholder, boolean visible, CCState state) { + placeholder.getActiveSubtaskInfo().setNeedInsertText(visible); + saveIndent(placeholder, state, !visible); + int length = isVisible() ? placeholder.getTaskText().length() : 0; + placeholder.setLength(length); + StudyUtils.drawAllAnswerPlaceholders(state.getEditor(), state.getTaskFile()); + } + + private static void saveIndent(AnswerPlaceholder placeholder, CCState state, boolean visible) { + Document document = state.getEditor().getDocument(); + int offset = placeholder.getOffset(); + int lineNumber = document.getLineNumber(offset); + int nonSpaceCharOffset = DocumentUtil.getFirstNonSpaceCharOffset(document, lineNumber); + int newOffset = offset; + int endOffset = offset + placeholder.getRealLength(); + if (!visible && nonSpaceCharOffset == offset) { + newOffset = document.getLineStartOffset(lineNumber); + } + if (visible) { + newOffset = DocumentUtil.getFirstNonSpaceCharOffset(document, offset, endOffset); + } + placeholder.setOffset(newOffset); + int delta = offset - newOffset; + placeholder.setPossibleAnswer(document.getText(TextRange.create(newOffset, newOffset + delta + placeholder.getRealLength()))); + String oldTaskText = placeholder.getTaskText(); + if (delta >= 0) { + placeholder.setTaskText(StringUtil.repeat(" ", delta) + oldTaskText); + } + else { + String newTaskText = oldTaskText.substring(Math.abs(delta)); + placeholder.setTaskText(newTaskText); + } + } + + protected abstract String getName(); + + protected abstract boolean isVisible(); + + @Override + public void update(AnActionEvent e) { + Presentation presentation = e.getPresentation(); + presentation.setEnabledAndVisible(false); + CCState state = getState(e); + if (state == null) { + return; + } + AnswerPlaceholder placeholder = state.getAnswerPlaceholder(); + if (placeholder == null) { + return; + } + Task task = state.getTaskFile().getTask(); + if (!task.hasSubtasks()) { + return; + } + Integer minSubtaskIndex = Collections.min(placeholder.getSubtaskInfos().keySet()); + if (placeholder.isActive() && minSubtaskIndex != 0 && minSubtaskIndex == task.getActiveSubtaskIndex() && isAvailable(placeholder)) { + presentation.setEnabledAndVisible(true); + } + } + + protected abstract boolean isAvailable(AnswerPlaceholder placeholder); +} diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCDeactivatePlaceholder.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCDeactivatePlaceholder.java new file mode 100644 index 000000000000..12f462e09d98 --- /dev/null +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCDeactivatePlaceholder.java @@ -0,0 +1,45 @@ +package com.jetbrains.edu.coursecreator.actions.placeholder; + +import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; +import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholderSubtaskInfo; +import com.jetbrains.edu.learning.courseFormat.TaskFile; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; + +public class CCDeactivatePlaceholder extends CCSubtaskPlaceholderAction { + + public static final String TITLE = "Deactivate"; + + protected CCDeactivatePlaceholder() { + super(TITLE, TITLE); + } + + protected void undoAction(@NotNull AnswerPlaceholder existingPlaceholder, int subtaskIndex, @NotNull AnswerPlaceholderSubtaskInfo info) { + existingPlaceholder.getSubtaskInfos().put(subtaskIndex, info); + } + + @Override + protected AnswerPlaceholderSubtaskInfo getInfo(@NotNull CCState state, + int subtaskIndex, + @NotNull AnswerPlaceholder existingPlaceholder) { + return existingPlaceholder.getSubtaskInfos().get(subtaskIndex); + } + + @Override + protected String getTitle() { + return TITLE; + } + + protected void redoAction(@NotNull AnswerPlaceholder existingPlaceholder, int subtaskIndex, @NotNull AnswerPlaceholderSubtaskInfo info) { + existingPlaceholder.getSubtaskInfos().remove(subtaskIndex); + } + + protected boolean isAvailable(TaskFile taskFile, int offset) { + AnswerPlaceholder existingActivePlaceholder = taskFile.getAnswerPlaceholder(offset); + if (existingActivePlaceholder == null) { + return false; + } + return Collections.min(existingActivePlaceholder.getSubtaskInfos().keySet()) < taskFile.getTask().getActiveSubtaskIndex(); + } +} diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCDeleteAllAnswerPlaceholdersAction.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCDeleteAllAnswerPlaceholdersAction.java similarity index 94% rename from python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCDeleteAllAnswerPlaceholdersAction.java rename to python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCDeleteAllAnswerPlaceholdersAction.java index e36a410ee3eb..4c232db34d02 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCDeleteAllAnswerPlaceholdersAction.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCDeleteAllAnswerPlaceholdersAction.java @@ -1,4 +1,4 @@ -package com.jetbrains.edu.coursecreator.actions; +package com.jetbrains.edu.coursecreator.actions.placeholder; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; @@ -16,7 +16,6 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.jetbrains.edu.coursecreator.CCUtils; import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.core.EduUtils; import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; import com.jetbrains.edu.learning.courseFormat.TaskFile; @@ -27,7 +26,7 @@ import java.util.List; public class CCDeleteAllAnswerPlaceholdersAction extends DumbAwareAction { - public static final String ACTION_NAME = "Delete All " + EduNames.ANSWER_PLACEHOLDER + "s"; + public static final String ACTION_NAME = "Delete All"; public CCDeleteAllAnswerPlaceholdersAction() { super(ACTION_NAME); @@ -65,7 +64,7 @@ public class CCDeleteAllAnswerPlaceholdersAction extends DumbAwareAction { private static void updateView(@NotNull final Editor editor, @NotNull final TaskFile taskFile) { editor.getMarkupModel().removeAllHighlighters(); - StudyUtils.drawAllWindows(editor, taskFile); + StudyUtils.drawAllAnswerPlaceholders(editor, taskFile); } @Override diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCDeleteAnswerPlaceholder.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCDeleteAnswerPlaceholder.java new file mode 100644 index 000000000000..508da6af06b0 --- /dev/null +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCDeleteAnswerPlaceholder.java @@ -0,0 +1,61 @@ +package com.jetbrains.edu.coursecreator.actions.placeholder; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.command.undo.UnexpectedUndoException; +import com.intellij.openapi.project.Project; +import com.jetbrains.edu.learning.core.EduUtils; +import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; +import com.jetbrains.edu.learning.courseFormat.TaskFile; +import org.jetbrains.annotations.NotNull; + +public class CCDeleteAnswerPlaceholder extends CCAnswerPlaceholderAction { + public CCDeleteAnswerPlaceholder() { + super("Delete", "Delete answer placeholder"); + } + + @Override + protected void performAnswerPlaceholderAction(@NotNull CCState state) { + deletePlaceholder(state); + } + + private static void deletePlaceholder(@NotNull CCState state) { + Project project = state.getProject(); + TaskFile taskFile = state.getTaskFile(); + AnswerPlaceholder answerPlaceholder = state.getAnswerPlaceholder(); + EduUtils.runUndoableAction(project, "Delete Answer Placeholder", + new CCAddAnswerPlaceholder.AddAction(answerPlaceholder, taskFile, state.getEditor()) { + @Override + public void undo() throws UnexpectedUndoException { + super.redo(); + } + + @Override + public void redo() throws UnexpectedUndoException { + super.undo(); + } + }); + } + + private static boolean canDeletePlaceholder(@NotNull CCState state) { + if (state.getEditor().getSelectionModel().hasSelection()) { + return false; + } + return state.getAnswerPlaceholder() != null; + } + + @Override + public void update(@NotNull AnActionEvent event) { + final Presentation presentation = event.getPresentation(); + presentation.setEnabledAndVisible(false); + + CCState state = getState(event); + if (state == null) { + return; + } + + if (canDeletePlaceholder(state)) { + presentation.setEnabledAndVisible(true); + } + } +} diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCEditAnswerPlaceholder.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCEditAnswerPlaceholder.java similarity index 85% rename from python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCEditAnswerPlaceholder.java rename to python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCEditAnswerPlaceholder.java index 5dfb491d060a..11fac1e9b018 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCEditAnswerPlaceholder.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCEditAnswerPlaceholder.java @@ -1,4 +1,4 @@ -package com.jetbrains.edu.coursecreator.actions; +package com.jetbrains.edu.coursecreator.actions.placeholder; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.Presentation; @@ -12,7 +12,7 @@ import org.jetbrains.annotations.NotNull; public class CCEditAnswerPlaceholder extends CCAnswerPlaceholderAction { public CCEditAnswerPlaceholder() { - super("Edit Answer Placeholder", "Edit answer placeholder"); + super("Edit", "Edit answer placeholder"); } @Override @@ -28,7 +28,10 @@ public class CCEditAnswerPlaceholder extends CCAnswerPlaceholderAction { } CCCreateAnswerPlaceholderDialog dlg = new CCCreateAnswerPlaceholderDialog(project, answerPlaceholder.getTaskText(), answerPlaceholder.getHints()); dlg.setTitle("Edit Answer Placeholder"); - dlg.show(); + if (dlg.showAndGet()) { + answerPlaceholder.setTaskText(dlg.getTaskText()); + answerPlaceholder.setHints(dlg.getHints()); + } } @Override diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCHidePlaceholderFromPrevSubtasks.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCHidePlaceholderFromPrevSubtasks.java new file mode 100644 index 000000000000..a8d5c02af9cb --- /dev/null +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCHidePlaceholderFromPrevSubtasks.java @@ -0,0 +1,27 @@ +package com.jetbrains.edu.coursecreator.actions.placeholder; + +import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; + +public class CCHidePlaceholderFromPrevSubtasks extends CCChangePlaceholderVisibility { + + public static final String TITLE = "Hide for Previous Subtasks"; + + public CCHidePlaceholderFromPrevSubtasks() { + super(TITLE, TITLE); + } + + @Override + protected String getName() { + return TITLE; + } + + @Override + protected boolean isVisible() { + return false; + } + + @Override + protected boolean isAvailable(AnswerPlaceholder placeholder) { + return !placeholder.getActiveSubtaskInfo().isNeedInsertText(); + } +} diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCMakeVisibleForPrevSubtasks.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCMakeVisibleForPrevSubtasks.java new file mode 100644 index 000000000000..e28169f8a2fb --- /dev/null +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCMakeVisibleForPrevSubtasks.java @@ -0,0 +1,27 @@ +package com.jetbrains.edu.coursecreator.actions.placeholder; + +import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; + +public class CCMakeVisibleForPrevSubtasks extends CCChangePlaceholderVisibility { + + public static final String TITLE = "Make Visible For Previous Subtasks"; + + protected CCMakeVisibleForPrevSubtasks() { + super(TITLE, TITLE); + } + + @Override + protected String getName() { + return TITLE; + } + + @Override + protected boolean isVisible() { + return true; + } + + @Override + protected boolean isAvailable(AnswerPlaceholder placeholder) { + return placeholder.getActiveSubtaskInfo().isNeedInsertText(); + } +} diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCSubtaskPlaceholderAction.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCSubtaskPlaceholderAction.java new file mode 100644 index 000000000000..550ad1a8e4d5 --- /dev/null +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCSubtaskPlaceholderAction.java @@ -0,0 +1,77 @@ +package com.jetbrains.edu.coursecreator.actions.placeholder; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.command.undo.BasicUndoableAction; +import com.intellij.openapi.command.undo.UnexpectedUndoException; +import com.intellij.openapi.editor.Editor; +import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.core.EduUtils; +import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; +import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholderSubtaskInfo; +import com.jetbrains.edu.learning.courseFormat.TaskFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public abstract class CCSubtaskPlaceholderAction extends CCAnswerPlaceholderAction { + protected CCSubtaskPlaceholderAction(@Nullable String text, @Nullable String description) { + super(text, description); + } + + @Override + protected void performAnswerPlaceholderAction(@NotNull CCState state) { + Editor editor = state.getEditor(); + final int offset = editor.getCaretModel().getOffset(); + TaskFile taskFile = state.getTaskFile(); + int subtaskIndex = state.getTaskFile().getTask().getActiveSubtaskIndex(); + AnswerPlaceholder existingPlaceholder = StudyUtils.getAnswerPlaceholder(offset, taskFile.getAnswerPlaceholders()); + if (existingPlaceholder == null) { + return; + } + AnswerPlaceholderSubtaskInfo info = getInfo(state, subtaskIndex, existingPlaceholder); + if (info == null) { + return; + } + EduUtils.runUndoableAction(state.getProject(), getTitle(), new BasicUndoableAction(state.getEditor().getDocument()) { + @Override + public void undo() throws UnexpectedUndoException { + undoAction(existingPlaceholder, subtaskIndex, info); + StudyUtils.drawAllAnswerPlaceholders(editor, taskFile); + } + + @Override + public void redo() throws UnexpectedUndoException { + redoAction(existingPlaceholder, subtaskIndex, info); + StudyUtils.drawAllAnswerPlaceholders(editor, taskFile); + } + }); + } + + protected abstract AnswerPlaceholderSubtaskInfo getInfo(@NotNull CCState state, int subtaskIndex, @NotNull AnswerPlaceholder existingPlaceholder); + + protected abstract String getTitle(); + + protected abstract void redoAction(@NotNull AnswerPlaceholder existingPlaceholder, int subtaskIndex, @NotNull AnswerPlaceholderSubtaskInfo info); + + protected abstract void undoAction(@NotNull AnswerPlaceholder existingPlaceholder, int subtaskIndex, @NotNull AnswerPlaceholderSubtaskInfo info); + + @Override + public void update(AnActionEvent e) { + Presentation presentation = e.getPresentation(); + presentation.setEnabledAndVisible(false); + CCState state = getState(e); + if (state == null) { + return; + } + TaskFile taskFile = state.getTaskFile(); + if (!taskFile.getTask().hasSubtasks()) { + return; + } + int offset = state.getEditor().getCaretModel().getOffset(); + if (isAvailable(taskFile, offset)) { + presentation.setEnabledAndVisible(true); + } + } + + protected abstract boolean isAvailable(TaskFile taskFile, int offset); +} diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/handlers/CCLessonRenameHandler.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/handlers/CCLessonRenameHandler.java index 0efe1110a8ca..ba817b628861 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/handlers/CCLessonRenameHandler.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/handlers/CCLessonRenameHandler.java @@ -2,6 +2,7 @@ package com.jetbrains.edu.coursecreator.handlers; import com.intellij.ide.TitledHandler; import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.Course; @@ -10,8 +11,8 @@ import org.jetbrains.annotations.NotNull; public class CCLessonRenameHandler extends CCRenameHandler implements TitledHandler { @Override - protected boolean isAvailable(String name) { - return name.contains(EduNames.LESSON); + protected boolean isAvailable(VirtualFile dir) { + return dir.getName().contains(EduNames.LESSON); } @Override diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/handlers/CCRenameHandler.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/handlers/CCRenameHandler.java index 9814a6631fe2..0be2ff5a9aa7 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/handlers/CCRenameHandler.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/handlers/CCRenameHandler.java @@ -30,10 +30,10 @@ public abstract class CCRenameHandler implements RenameHandler { return false; } VirtualFile directory = ((PsiDirectory)element).getVirtualFile(); - return isAvailable(directory.getName()); + return isAvailable(directory); } - protected abstract boolean isAvailable(String name); + protected abstract boolean isAvailable(VirtualFile directory); @Override public boolean isRenaming(DataContext dataContext) { diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/handlers/CCTaskRenameHandler.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/handlers/CCTaskRenameHandler.java index 98bcba87c6c8..6075fb5167f7 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/handlers/CCTaskRenameHandler.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/handlers/CCTaskRenameHandler.java @@ -2,6 +2,7 @@ package com.jetbrains.edu.coursecreator.handlers; import com.intellij.ide.TitledHandler; import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.Course; @@ -11,12 +12,25 @@ import org.jetbrains.annotations.NotNull; public class CCTaskRenameHandler extends CCRenameHandler implements TitledHandler { @Override - protected boolean isAvailable(String name) { - return name.contains(EduNames.TASK); + protected boolean isAvailable(VirtualFile dir) { + if (dir.getName().contains(EduNames.TASK)) { + return true; + } + VirtualFile parent = dir.getParent(); + if (parent != null && parent.getName().contains(EduNames.TASK)) { + return true; + } + return false; } @Override protected void rename(@NotNull Project project, @NotNull Course course, @NotNull PsiDirectory directory) { + if (directory.getName().equals(EduNames.SRC)) { + directory = directory.getParent(); + if (directory == null) { + return; + } + } PsiDirectory lessonDir = directory.getParent(); if (lessonDir == null || !lessonDir.getName().contains(EduNames.LESSON)) { return; diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCCourseDirectoryNode.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCCourseDirectoryNode.java new file mode 100644 index 000000000000..04cd3f4b051c --- /dev/null +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCCourseDirectoryNode.java @@ -0,0 +1,51 @@ +package com.jetbrains.edu.coursecreator.projectView; + +import com.intellij.ide.projectView.ViewSettings; +import com.intellij.ide.projectView.impl.nodes.PsiFileNode; +import com.intellij.ide.util.treeView.AbstractTreeNode; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtilRt; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDirectory; +import com.jetbrains.edu.learning.courseFormat.Course; +import com.jetbrains.edu.learning.courseFormat.Lesson; +import com.jetbrains.edu.learning.courseFormat.StudyItem; +import com.jetbrains.edu.learning.projectView.CourseDirectoryNode; +import com.jetbrains.edu.learning.projectView.StudyDirectoryNode; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class CCCourseDirectoryNode extends CourseDirectoryNode { + + public CCCourseDirectoryNode(@NotNull Project project, + PsiDirectory value, + ViewSettings viewSettings, + @NotNull Course course) { + super(project, value, viewSettings, course); + } + + @Nullable + @Override + public AbstractTreeNode modifyChildNode(AbstractTreeNode childNode) { + AbstractTreeNode node = super.modifyChildNode(childNode); + if (node != null) { + return node; + } + if (childNode instanceof PsiFileNode) { + VirtualFile virtualFile = ((PsiFileNode)childNode).getVirtualFile(); + if (virtualFile == null) { + return null; + } + if (FileUtilRt.getExtension(virtualFile.getName()).equals("iml")) { + return null; + } + return new CCStudentInvisibleFileNode(myProject, ((PsiFileNode)childNode).getValue(), myViewSettings); + } + return null; + } + + @Override + public StudyDirectoryNode createChildDirectoryNode(StudyItem item, PsiDirectory directory) { + return new CCLessonDirectoryNode(myProject, directory, myViewSettings, ((Lesson)item)); + } +} diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCLessonDirectoryNode.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCLessonDirectoryNode.java new file mode 100644 index 000000000000..1be30f7613cb --- /dev/null +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCLessonDirectoryNode.java @@ -0,0 +1,25 @@ +package com.jetbrains.edu.coursecreator.projectView; + +import com.intellij.ide.projectView.ViewSettings; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDirectory; +import com.jetbrains.edu.learning.courseFormat.Lesson; +import com.jetbrains.edu.learning.courseFormat.StudyItem; +import com.jetbrains.edu.learning.courseFormat.Task; +import com.jetbrains.edu.learning.projectView.LessonDirectoryNode; +import com.jetbrains.edu.learning.projectView.StudyDirectoryNode; +import org.jetbrains.annotations.NotNull; + +public class CCLessonDirectoryNode extends LessonDirectoryNode { + public CCLessonDirectoryNode(@NotNull Project project, + PsiDirectory value, + ViewSettings viewSettings, + @NotNull Lesson lesson) { + super(project, value, viewSettings, lesson); + } + + @Override + public StudyDirectoryNode createChildDirectoryNode(StudyItem item, PsiDirectory directory) { + return new CCTaskDirectoryNode(myProject, directory, myViewSettings, ((Task)item)); + } +} diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCStudentInvisibleFileNode.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCStudentInvisibleFileNode.java index 94376a13ae3d..e163eee2b238 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCStudentInvisibleFileNode.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCStudentInvisibleFileNode.java @@ -10,20 +10,29 @@ import com.intellij.ui.SimpleTextAttributes; /** * represents a file which is invisible for student in student mode */ -public class CCStudentInvisibleFileNode extends PsiFileNode{ +public class CCStudentInvisibleFileNode extends PsiFileNode { + private final String myName; public CCStudentInvisibleFileNode(Project project, PsiFile value, ViewSettings viewSettings) { super(project, value, viewSettings); + myName = value.getName(); + } + + public CCStudentInvisibleFileNode(Project project, + PsiFile value, + ViewSettings viewSettings, + String name) { + super(project, value, viewSettings); + myName = name; } @Override protected void updateImpl(PresentationData data) { super.updateImpl(data); - String text = data.getPresentableText(); data.clearText(); - data.addText(text, SimpleTextAttributes.GRAY_ATTRIBUTES); + data.addText(myName, SimpleTextAttributes.GRAY_ATTRIBUTES); } } diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCTaskDirectoryNode.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCTaskDirectoryNode.java new file mode 100644 index 000000000000..69a7b664b840 --- /dev/null +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCTaskDirectoryNode.java @@ -0,0 +1,98 @@ +package com.jetbrains.edu.coursecreator.projectView; + +import com.intellij.ide.projectView.ViewSettings; +import com.intellij.ide.util.treeView.AbstractTreeNode; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiNamedElement; +import com.jetbrains.edu.coursecreator.CCUtils; +import com.jetbrains.edu.learning.StudyLanguageManager; +import com.jetbrains.edu.learning.StudyTaskManager; +import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.core.EduNames; +import com.jetbrains.edu.learning.courseFormat.Course; +import com.jetbrains.edu.learning.courseFormat.Task; +import com.jetbrains.edu.learning.projectView.TaskDirectoryNode; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class CCTaskDirectoryNode extends TaskDirectoryNode { + public CCTaskDirectoryNode(@NotNull Project project, + PsiDirectory value, + ViewSettings viewSettings, + @NotNull Task task) { + super(project, value, viewSettings, task); + } + + @Nullable + @Override + public AbstractTreeNode modifyChildNode(AbstractTreeNode childNode) { + AbstractTreeNode node = super.modifyChildNode(childNode); + if (node != null) { + return node; + } + Object value = childNode.getValue(); + if (value instanceof PsiElement) { + PsiElement psiElement = (PsiElement) value; + PsiFile psiFile = psiElement.getContainingFile(); + VirtualFile virtualFile = psiFile.getVirtualFile(); + if (virtualFile == null) { + return null; + } + if (StudyUtils.isTaskDescriptionFile(virtualFile.getName())) { + return null; + } + Course course = StudyTaskManager.getInstance(myProject).getCourse(); + if (course == null) { + return null; + } + StudyLanguageManager manager = StudyUtils.getLanguageManager(course); + if (manager == null) { + return new CCStudentInvisibleFileNode(myProject, psiFile, myViewSettings); + } + if (!CCUtils.isTestsFile(myProject, virtualFile)) { + return new CCStudentInvisibleFileNode(myProject, psiFile, myViewSettings); + } + if (!myTask.hasSubtasks()) { + return new CCStudentInvisibleFileNode(myProject, psiFile, myViewSettings, getTestNodeName(manager, psiElement)); + } + String testFileName = getTestNodeName(manager, psiElement); + return isActiveSubtaskTest(virtualFile) ? new CCStudentInvisibleFileNode(myProject, psiFile, myViewSettings, testFileName) : null; + } + return null; + } + + @NotNull + private static String getTestNodeName(StudyLanguageManager manager, PsiElement psiElement) { + String defaultTestName = manager.getTestFileName(); + if (psiElement instanceof PsiFile) { + return defaultTestName; + } + if (psiElement instanceof PsiNamedElement) { + String name = ((PsiNamedElement)psiElement).getName(); + return name != null ? name : defaultTestName; + } + return defaultTestName; + } + + private boolean isActiveSubtaskTest(VirtualFile virtualFile) { + if (!myTask.hasSubtasks()) { + return true; + } + + boolean isSubtaskTestFile = virtualFile.getName().contains(EduNames.SUBTASK_MARKER); + if (myTask.getActiveSubtaskIndex() == 0) { + return !isSubtaskTestFile; + } + if (!isSubtaskTestFile) { + return false; + } + String nameWithoutExtension = virtualFile.getNameWithoutExtension(); + int stepMarkerStart = nameWithoutExtension.indexOf(EduNames.SUBTASK_MARKER); + int stepIndex = Integer.valueOf(nameWithoutExtension.substring(EduNames.SUBTASK_MARKER.length() + stepMarkerStart)); + return stepIndex == myTask.getActiveSubtaskIndex(); + } +} diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCTreeStructureProvider.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCTreeStructureProvider.java index 2a4249929538..5dd486b48235 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCTreeStructureProvider.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/projectView/CCTreeStructureProvider.java @@ -1,64 +1,24 @@ package com.jetbrains.edu.coursecreator.projectView; import com.intellij.ide.projectView.ViewSettings; -import com.intellij.ide.projectView.impl.nodes.PsiFileNode; import com.intellij.ide.util.treeView.AbstractTreeNode; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.io.FileUtilRt; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiFile; import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.courseFormat.Course; +import com.jetbrains.edu.learning.projectView.CourseDirectoryNode; import com.jetbrains.edu.learning.projectView.StudyTreeStructureProvider; import org.jetbrains.annotations.NotNull; -import java.util.Collection; - public class CCTreeStructureProvider extends StudyTreeStructureProvider { - @NotNull @Override - public Collection modify(@NotNull AbstractTreeNode parent, - @NotNull Collection children, - ViewSettings settings) { - if (!needModify(parent)) { - return children; - } - Collection modifiedChildren = super.modify(parent, children, settings); - - for (AbstractTreeNode node : children) { - Project project = node.getProject(); - if (project == null) { - continue; - } - if (node.getValue() instanceof PsiDirectory) { - String name = ((PsiDirectory)node.getValue()).getName(); - if ("zip".equals(FileUtilRt.getExtension(name))) { - modifiedChildren.add(node); - continue; - } - } - if (node instanceof PsiFileNode) { - PsiFileNode fileNode = (PsiFileNode)node; - VirtualFile virtualFile = fileNode.getVirtualFile(); - if (virtualFile == null) { - continue; - } - if (StudyUtils.getTaskFile(project, virtualFile) != null || StudyUtils.isTaskDescriptionFile(virtualFile.getName())) { - continue; - } - PsiFile psiFile = ((PsiFileNode)node).getValue(); - modifiedChildren.add(new CCStudentInvisibleFileNode(project, psiFile, settings)); - } - } - return modifiedChildren; - } - - protected boolean needModify(@NotNull final AbstractTreeNode parent) { - Project project = parent.getProject(); - if (project == null) { - return false; - } + protected boolean shouldModify(@NotNull Project project) { return CCUtils.isCourseCreator(project); } + + @NotNull + @Override + protected CourseDirectoryNode createCourseNode(Project project, AbstractTreeNode node, ViewSettings settings, Course course) { + return new CCCourseDirectoryNode(project, ((PsiDirectory)node.getValue()), settings, course); + } } diff --git a/python/educational-core/course-creator/testSrc/com/jetbrains/edu/coursecreator/CCTestCase.java b/python/educational-core/course-creator/testSrc/com/jetbrains/edu/coursecreator/CCTestCase.java index 8a16347581ff..902a09ebc9f8 100644 --- a/python/educational-core/course-creator/testSrc/com/jetbrains/edu/coursecreator/CCTestCase.java +++ b/python/educational-core/course-creator/testSrc/com/jetbrains/edu/coursecreator/CCTestCase.java @@ -25,6 +25,7 @@ import org.junit.ComparisonFailure; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -44,7 +45,7 @@ public abstract class CCTestCase extends CodeInsightFixtureTestCase { } protected static void checkHighlighters(TaskFile taskFile, MarkupModel markupModel) { - for (AnswerPlaceholder answerPlaceholder : taskFile.getAnswerPlaceholders()) { + for (AnswerPlaceholder answerPlaceholder : taskFile.getActivePlaceholders()) { if (getHighlighter(markupModel, answerPlaceholder) == null) { throw new AssertionError("No highlighter for placeholder: " + CCTestsUtil.getPlaceholderPresentation(answerPlaceholder)); } @@ -54,16 +55,16 @@ public abstract class CCTestCase extends CodeInsightFixtureTestCase { public void checkByFile(TaskFile taskFile, String fileName, boolean useLength) { Pair> placeholders = getPlaceholders(fileName, useLength, true); String message = "Placeholders don't match"; - if (taskFile.getAnswerPlaceholders().size() != placeholders.second.size()) { + if (taskFile.getActivePlaceholders().size() != placeholders.second.size()) { throw new ComparisonFailure(message, - CCTestsUtil.getPlaceholdersPresentation(taskFile.getAnswerPlaceholders()), + CCTestsUtil.getPlaceholdersPresentation(taskFile.getActivePlaceholders()), CCTestsUtil.getPlaceholdersPresentation(placeholders.second)); } for (AnswerPlaceholder answerPlaceholder : placeholders.getSecond()) { AnswerPlaceholder placeholder = taskFile.getAnswerPlaceholder(answerPlaceholder.getOffset()); if (!CCTestsUtil.comparePlaceholders(placeholder, answerPlaceholder)) { throw new ComparisonFailure(message, - CCTestsUtil.getPlaceholdersPresentation(taskFile.getAnswerPlaceholders()), + CCTestsUtil.getPlaceholdersPresentation(taskFile.getActivePlaceholders()), CCTestsUtil.getPlaceholdersPresentation(placeholders.second)); } } @@ -122,7 +123,7 @@ public abstract class CCTestCase extends CodeInsightFixtureTestCase { taskFile.addAnswerPlaceholder(placeholder); } taskFile.sortAnswerPlaceholders(); - StudyUtils.drawAllWindows(myFixture.getEditor(), taskFile); + StudyUtils.drawAllAnswerPlaceholders(myFixture.getEditor(), taskFile); CCUtils.createResourceFile(file, StudyTaskManager.getInstance(getProject()).getCourse(), file.getParent()); return file; } @@ -140,6 +141,8 @@ public abstract class CCTestCase extends CodeInsightFixtureTestCase { int pos = 0; while (openingMatcher.find(pos)) { AnswerPlaceholder answerPlaceholder = new AnswerPlaceholder(); + AnswerPlaceholderSubtaskInfo subtaskInfo = new AnswerPlaceholderSubtaskInfo(); + answerPlaceholder.getSubtaskInfos().put(0, subtaskInfo); answerPlaceholder.setUseLength(useLength); String taskText = openingMatcher.group(2); if (taskText != null) { @@ -152,7 +155,7 @@ public abstract class CCTestCase extends CodeInsightFixtureTestCase { } String hint = openingMatcher.group(6); if (hint != null) { - answerPlaceholder.setHint(hint); + answerPlaceholder.setHints(Collections.singletonList(hint)); } answerPlaceholder.setOffset(openingMatcher.start()); if (!closingMatcher.find(openingMatcher.end())) { diff --git a/python/educational-core/course-creator/testSrc/com/jetbrains/edu/coursecreator/actions/CCAnswerPlaceholderActionTest.java b/python/educational-core/course-creator/testSrc/com/jetbrains/edu/coursecreator/actions/CCAnswerPlaceholderActionTest.java index 9ca2aa19733f..ba28df0f9817 100644 --- a/python/educational-core/course-creator/testSrc/com/jetbrains/edu/coursecreator/actions/CCAnswerPlaceholderActionTest.java +++ b/python/educational-core/course-creator/testSrc/com/jetbrains/edu/coursecreator/actions/CCAnswerPlaceholderActionTest.java @@ -1,5 +1,6 @@ package com.jetbrains.edu.coursecreator.actions; +import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.command.undo.UndoManager; import com.intellij.openapi.fileEditor.FileEditorManager; @@ -7,6 +8,8 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.jetbrains.edu.coursecreator.CCTestCase; import com.jetbrains.edu.coursecreator.CCTestsUtil; +import com.jetbrains.edu.coursecreator.actions.placeholder.CCAddAnswerPlaceholder; +import com.jetbrains.edu.coursecreator.actions.placeholder.CCDeleteAnswerPlaceholder; import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; import com.jetbrains.edu.learning.courseFormat.TaskFile; @@ -39,11 +42,11 @@ public class CCAnswerPlaceholderActionTest extends CCTestCase { } public void testPlaceholderWithSelection() { - doTest("onePlaceholder"); + doTest("onePlaceholder", new CCTestAction()); } public void testPlaceholderWithoutSelection() { - doTest("withoutSelection"); + doTest("withoutSelection", new CCTestAction()); } public void testPlaceholderIntersection() { @@ -53,12 +56,12 @@ public class CCAnswerPlaceholderActionTest extends CCTestCase { } public void testPlaceholderDeleted() { - doTest("deletePlaceholder"); + doTest("deletePlaceholder", new CCDeleteAnswerPlaceholder()); } - private void doTest(String name) { + private void doTest(String name, AnAction action) { VirtualFile virtualFile = configureByTaskFile(name + CCTestsUtil.BEFORE_POSTFIX); - myFixture.testAction(new CCTestAction()); + myFixture.testAction(action); TaskFile taskFile = StudyUtils.getTaskFile(getProject(), virtualFile); checkByFile(taskFile, name + CCTestsUtil.AFTER_POSTFIX, false); checkHighlighters(taskFile, myFixture.getEditor().getMarkupModel()); diff --git a/python/educational-core/student/resources/META-INF/plugin.xml b/python/educational-core/student/resources/META-INF/plugin.xml index 6f842f044c9f..f8de6c5cf790 100644 --- a/python/educational-core/student/resources/META-INF/plugin.xml +++ b/python/educational-core/student/resources/META-INF/plugin.xml @@ -45,7 +45,8 @@ - > + + @@ -107,6 +108,7 @@ + diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/StudySerializationUtils.java b/python/educational-core/student/src/com/jetbrains/edu/learning/StudySerializationUtils.java index d0cd98e3c28c..279fad1248fc 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/StudySerializationUtils.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/StudySerializationUtils.java @@ -9,18 +9,21 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.hash.HashMap; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.StudyStatus; -import com.jetbrains.edu.learning.courseFormat.TaskFile; +import com.jetbrains.edu.learning.stepic.EduStepicConnector; +import com.jetbrains.edu.learning.stepic.StepicWrappers; import org.jdom.Attribute; import org.jdom.Element; import org.jdom.output.XMLOutputter; import java.io.File; import java.lang.reflect.Type; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -71,7 +74,6 @@ public class StudySerializationUtils { public static final String MY_LINE = "myLine"; public static final String MY_START = "myStart"; public static final String MY_LENGTH = "myLength"; - public static final String HINTS = "hints"; public static final String HINT = "hint"; public static final String AUTHOR_TITLED = "Author"; public static final String FIRST_NAME = "first_name"; @@ -82,6 +84,13 @@ public class StudySerializationUtils { public static final String TASK_WINDOWS = "taskWindows"; public static final String RESOURCE_PATH = "resourcePath"; public static final String COURSE_DIRECTORY = "courseDirectory"; + public static final String SUBTASK_INFO = "AnswerPlaceholderSubtaskInfo"; + public static final String SUBTASK_INFOS = "subtaskInfos"; + public static final String ADDITIONAL_HINTS = "additionalHints"; + public static final String POSSIBLE_ANSWER = "possibleAnswer"; + public static final String SELECTED = "selected"; + public static final String TASK_TEXT = "taskText"; + public static final String PLACEHOLDER_TEXT = "placeholderText"; private Xml() { } @@ -199,6 +208,37 @@ public class StudySerializationUtils { return state; } + public static Element convertToForthVersion(Element state) throws StudyUnrecognizedFormatException { + Element taskManagerElement = state.getChild(MAIN_ELEMENT); + Element courseElement = getChildWithName(taskManagerElement, COURSE).getChild(COURSE_TITLED); + for (Element lesson : getChildList(courseElement, LESSONS)) { + for (Element task : getChildList(lesson, TASK_LIST)) { + Map taskFiles = getChildMap(task, TASK_FILES); + for (Map.Entry entry : taskFiles.entrySet()) { + Element taskFileElement = entry.getValue(); + for (Element placeholder : getChildList(taskFileElement, ANSWER_PLACEHOLDERS)) { + Element valueElement = new Element(SUBTASK_INFO); + addChildMap(placeholder, SUBTASK_INFOS, Collections.singletonMap(String.valueOf(0), valueElement)); + for (String childName : ContainerUtil.list(HINT, ADDITIONAL_HINTS, POSSIBLE_ANSWER, SELECTED, STATUS, TASK_TEXT)) { + Element child = getChildWithName(placeholder, childName); + valueElement.addContent(child.clone()); + } + renameElement(getChildWithName(valueElement, TASK_TEXT), PLACEHOLDER_TEXT); + List additionalHints = ContainerUtil.map(getChildList(valueElement, ADDITIONAL_HINTS), Element::clone); + Element hint = getChildWithName(valueElement, HINT); + Element firstHint = new Element(OPTION).setAttribute(VALUE, hint.getAttributeValue(VALUE)); + List newHints = new ArrayList<>(); + newHints.add(firstHint); + newHints.addAll(additionalHints); + addChildList(valueElement, "hints", newHints); + } + } + } + } + + return state; + } + public static String addStatus(XMLOutputter outputter, Map placeholderTextToStatus, String taskStatus, @@ -272,6 +312,20 @@ public class StudySerializationUtils { return addChildWithName(parent, name, listElement); } + public static Element addChildMap(Element parent, String name, Map value) { + Element mapElement = new Element(MAP); + for (Map.Entry entry : value.entrySet()) { + Element entryElement = new Element("entry"); + mapElement.addContent(entryElement); + String key = entry.getKey(); + entryElement.setAttribute("key", key); + Element valueElement = new Element("value"); + valueElement.addContent(entry.getValue()); + entryElement.addContent(valueElement); + } + return addChildWithName(parent, name, mapElement); + } + public static List getChildList(Element parent, String name) throws StudyUnrecognizedFormatException { return getChildList(parent, name, false); } @@ -333,6 +387,11 @@ public class StudySerializationUtils { public static final String TASK_LIST = "task_list"; public static final String TASK_FILES = "task_files"; + public static final String FILES = "files"; + public static final String HINTS = "hints"; + public static final String SUBTASK_INFOS = "subtask_infos"; + public static final String FORMAT_VERSION = "format_version"; + public static final String INDEX = "index"; private Json() { } @@ -368,90 +427,165 @@ public class StudySerializationUtils { JsonObject taskFileObject = taskFile.getValue().getAsJsonObject(); JsonArray placeholders = taskFileObject.getAsJsonArray(PLACEHOLDERS); for (JsonElement placeholder : placeholders) { - JsonObject placeholderObject = placeholder.getAsJsonObject(); - if (placeholderObject.getAsJsonPrimitive(OFFSET) != null) { - break; + convertToAbsoluteOffset(document, placeholder); + if (placeholder.getAsJsonObject().getAsJsonObject(SUBTASK_INFOS) == null) { + convertToSubtaskInfo(placeholder.getAsJsonObject()); } - int line = placeholderObject.getAsJsonPrimitive(LINE).getAsInt(); - int start = placeholderObject.getAsJsonPrimitive(START).getAsInt(); - int offset = document.getLineStartOffset(line) + start; - placeholderObject.addProperty(OFFSET, offset); } } } } return new GsonBuilder().create().fromJson(json, Course.class); } + + private static void convertToAbsoluteOffset(Document document, JsonElement placeholder) { + JsonObject placeholderObject = placeholder.getAsJsonObject(); + if (placeholderObject.getAsJsonPrimitive(OFFSET) != null) { + return; + } + int line = placeholderObject.getAsJsonPrimitive(LINE).getAsInt(); + int start = placeholderObject.getAsJsonPrimitive(START).getAsInt(); + int offset = document.getLineStartOffset(line) + start; + placeholderObject.addProperty(OFFSET, offset); + } } - public static class StepicTaskFileAdapter implements JsonDeserializer { - + public static class StepicStepOptionsAdapter implements JsonDeserializer { @Override - public TaskFile deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); - JsonObject taskFileObject = json.getAsJsonObject(); - JsonArray placeholders = taskFileObject.getAsJsonArray(PLACEHOLDERS); - for (JsonElement placeholder : placeholders) { - JsonObject placeholderObject = placeholder.getAsJsonObject(); - int line = placeholderObject.getAsJsonPrimitive(LINE).getAsInt(); - int start = placeholderObject.getAsJsonPrimitive(START).getAsInt(); - if (line == -1) { - placeholderObject.addProperty(OFFSET, start); - } - else { - Document document = EditorFactory.getInstance().createDocument(taskFileObject.getAsJsonPrimitive(TEXT).getAsString()); - placeholderObject.addProperty(OFFSET, document.getLineStartOffset(line) + start); - } - final String hintString = placeholderObject.getAsJsonPrimitive(HINT).getAsString(); - final JsonArray hintsArray = new JsonArray(); + public StepicWrappers.StepOptions deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + JsonObject stepOptionsJson = json.getAsJsonObject(); + JsonPrimitive versionJson = stepOptionsJson.getAsJsonPrimitive(FORMAT_VERSION); + int version = 1; + if (versionJson != null) { + version = versionJson.getAsInt(); + } + switch (version) { + case 1: + stepOptionsJson = convertToSecondVersion(stepOptionsJson); + // uncomment for future versions + //case 2: + // stepOptionsJson = convertToThirdVersion(stepOptionsJson); + } + convertSubtaskInfosToMap(stepOptionsJson); + StepicWrappers.StepOptions stepOptions = + new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create() + .fromJson(stepOptionsJson, StepicWrappers.StepOptions.class); + stepOptions.formatVersion = EduStepicConnector.CURRENT_VERSION; + return stepOptions; + } - try { - final Type listType = new TypeToken>() {}.getType(); - final List hints = gson.fromJson(hintString, listType); - if (hints != null && !hints.isEmpty()) { - for (int i = 0; i < hints.size(); i++) { - if (i == 0) { - placeholderObject.addProperty(HINT, hints.get(0)); - continue; - } - hintsArray.add(hints.get(i)); - } - placeholderObject.add(ADDITIONAL_HINTS, hintsArray); + private static JsonObject convertSubtaskInfosToMap(JsonObject stepOptionsJson) { + for (JsonElement taskFileElement : stepOptionsJson.getAsJsonArray(FILES)) { + JsonObject taskFileObject = taskFileElement.getAsJsonObject(); + JsonArray placeholders = taskFileObject.getAsJsonArray(PLACEHOLDERS); + for (JsonElement placeholder : placeholders) { + JsonObject placeholderObject = placeholder.getAsJsonObject(); + JsonArray infos = placeholderObject.getAsJsonArray(SUBTASK_INFOS); + Map objectsToInsert = new HashMap<>(); + for (JsonElement info : infos) { + JsonObject object = info.getAsJsonObject(); + int index = object.getAsJsonPrimitive(INDEX).getAsInt(); + objectsToInsert.put(index, object); } - else { - placeholderObject.addProperty(HINT, ""); + placeholderObject.remove(SUBTASK_INFOS); + JsonObject newInfos = new JsonObject(); + placeholderObject.add(SUBTASK_INFOS, newInfos); + for (Map.Entry entry : objectsToInsert.entrySet()) { + newInfos.add(entry.getKey().toString(), entry.getValue()); } } - catch (JsonParseException e) { - hintsArray.add(hintString); - } } - - return gson.fromJson(json, TaskFile.class); + return stepOptionsJson; } + + private static JsonObject convertToSecondVersion(JsonObject stepOptionsJson) { + Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); + for (JsonElement taskFileElement : stepOptionsJson.getAsJsonArray(FILES)) { + JsonObject taskFileObject = taskFileElement.getAsJsonObject(); + JsonArray placeholders = taskFileObject.getAsJsonArray(PLACEHOLDERS); + for (JsonElement placeholder : placeholders) { + JsonObject placeholderObject = placeholder.getAsJsonObject(); + convertToAbsoluteOffset(taskFileObject, placeholderObject); + convertMultipleHints(gson, placeholderObject); + convertToSubtaskInfo(placeholderObject); + } + } + return stepOptionsJson; + } + + private static void convertMultipleHints(Gson gson, JsonObject placeholderObject) { + final String hintString = placeholderObject.getAsJsonPrimitive(HINT).getAsString(); + final JsonArray hintsArray = new JsonArray(); + + try { + final Type listType = new TypeToken>() { + }.getType(); + final List hints = gson.fromJson(hintString, listType); + if (hints != null && !hints.isEmpty()) { + for (int i = 0; i < hints.size(); i++) { + if (i == 0) { + placeholderObject.addProperty(HINT, hints.get(0)); + continue; + } + hintsArray.add(hints.get(i)); + } + placeholderObject.add(ADDITIONAL_HINTS, hintsArray); + } + else { + placeholderObject.addProperty(HINT, ""); + } + } + catch (JsonParseException e) { + hintsArray.add(hintString); + } + } + + private static void convertToAbsoluteOffset(JsonObject taskFileObject, JsonObject placeholderObject) { + int line = placeholderObject.getAsJsonPrimitive(LINE).getAsInt(); + int start = placeholderObject.getAsJsonPrimitive(START).getAsInt(); + if (line == -1) { + placeholderObject.addProperty(OFFSET, start); + } + else { + Document document = EditorFactory.getInstance().createDocument(taskFileObject.getAsJsonPrimitive(TEXT).getAsString()); + placeholderObject.addProperty(OFFSET, document.getLineStartOffset(line) + start); + } + } + } + + private static void convertToSubtaskInfo(JsonObject placeholderObject) { + JsonArray subtaskInfos = new JsonArray(); + placeholderObject.add(SUBTASK_INFOS, subtaskInfos); + JsonArray hintsArray = new JsonArray(); + hintsArray.add(placeholderObject.getAsJsonPrimitive(HINT).getAsString()); + JsonArray additionalHints = placeholderObject.getAsJsonArray(ADDITIONAL_HINTS); + if (additionalHints != null) { + hintsArray.addAll(additionalHints); + } + JsonObject subtaskInfo = new JsonObject(); + subtaskInfos.add(subtaskInfo); + subtaskInfo.add(INDEX, new JsonPrimitive(0)); + subtaskInfo.add(HINTS, hintsArray); + subtaskInfo.addProperty(POSSIBLE_ANSWER, placeholderObject.getAsJsonPrimitive(POSSIBLE_ANSWER).getAsString()); } public static class StepicAnswerPlaceholderAdapter implements JsonSerializer { @Override - public JsonElement serialize(AnswerPlaceholder src, Type typeOfSrc, JsonSerializationContext context) { - final List hints = src.getHints(); - - final int length = src.getLength(); - final int start = src.getOffset(); - final String possibleAnswer = src.getPossibleAnswer(); - int line = -1; - - final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); - final JsonObject answerPlaceholder = new JsonObject(); - answerPlaceholder.addProperty(LINE, line); - answerPlaceholder.addProperty(START, start); - answerPlaceholder.addProperty(LENGTH, length); - answerPlaceholder.addProperty(POSSIBLE_ANSWER, possibleAnswer); - - final String jsonHints = gson.toJson(hints); - answerPlaceholder.addProperty(HINT, jsonHints); - - return answerPlaceholder; + public JsonElement serialize(AnswerPlaceholder placeholder, Type typeOfSrc, JsonSerializationContext context) { + Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create(); + JsonElement answerPlaceholderJson = gson.toJsonTree(placeholder); + JsonObject answerPlaceholderObject = answerPlaceholderJson.getAsJsonObject(); + JsonObject subtaskInfos = answerPlaceholderObject.getAsJsonObject(SUBTASK_INFOS); + JsonArray infosArray = new JsonArray(); + for (Map.Entry entry : subtaskInfos.entrySet()) { + JsonObject subtaskInfo = entry.getValue().getAsJsonObject(); + subtaskInfo.add(INDEX, new JsonPrimitive(Integer.valueOf(entry.getKey()))); + infosArray.add(subtaskInfo); + } + answerPlaceholderObject.remove(SUBTASK_INFOS); + answerPlaceholderObject.add(SUBTASK_INFOS, infosArray); + return answerPlaceholderJson; } } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/StudySubtaskChangeListener.java b/python/educational-core/student/src/com/jetbrains/edu/learning/StudySubtaskChangeListener.java new file mode 100644 index 000000000000..8ea064faa128 --- /dev/null +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/StudySubtaskChangeListener.java @@ -0,0 +1,12 @@ +package com.jetbrains.edu.learning; + +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.project.Project; +import com.jetbrains.edu.learning.courseFormat.Task; +import org.jetbrains.annotations.NotNull; + +public interface StudySubtaskChangeListener { + ExtensionPointName EP_NAME = ExtensionPointName.create("Edu.studySubtaskChangeListener"); + + void subtaskChanged(@NotNull Project project, @NotNull Task task, int oldSubtaskNumber, int newSubtaskNumber); +} diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/StudySubtaskUtils.java b/python/educational-core/student/src/com/jetbrains/edu/learning/StudySubtaskUtils.java new file mode 100644 index 000000000000..f595eaaa7183 --- /dev/null +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/StudySubtaskUtils.java @@ -0,0 +1,133 @@ +package com.jetbrains.edu.learning; + +import com.intellij.ide.projectView.ProjectView; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.io.FileUtilRt; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.problems.WolfTheProblemSolver; +import com.intellij.ui.EditorNotifications; +import com.jetbrains.edu.learning.checker.StudyCheckUtils; +import com.jetbrains.edu.learning.core.EduNames; +import com.jetbrains.edu.learning.core.EduUtils; +import com.jetbrains.edu.learning.courseFormat.*; +import com.jetbrains.edu.learning.ui.StudyToolWindow; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.Map; + +public class StudySubtaskUtils { + private static final Logger LOG = Logger.getInstance(StudySubtaskUtils.class); + + private StudySubtaskUtils() { + } + + /*** + * @param toSubtaskIndex from 0 to subtaskNum - 1 + */ + public static void switchStep(@NotNull Project project, @NotNull Task task, int toSubtaskIndex) { + if (toSubtaskIndex == task.getActiveSubtaskIndex()) { + return; + } + VirtualFile taskDir = task.getTaskDir(project); + if (taskDir == null) { + return; + } + VirtualFile srcDir = taskDir.findChild(EduNames.SRC); + if (srcDir != null) { + taskDir = srcDir; + } + int fromSubtaskIndex = task.getActiveSubtaskIndex(); + for (Map.Entry entry : task.getTaskFiles().entrySet()) { + String name = entry.getKey(); + VirtualFile virtualFile = taskDir.findChild(name); + if (virtualFile == null) { + continue; + } + TaskFile taskFile = entry.getValue(); + Document document = FileDocumentManager.getInstance().getDocument(virtualFile); + if (document == null) { + continue; + } + updatePlaceholderTexts(project, document, taskFile, fromSubtaskIndex, toSubtaskIndex); + EditorNotifications.getInstance(project).updateNotifications(virtualFile); + if (StudyUtils.isStudentProject(project)) { + WolfTheProblemSolver.getInstance(project).clearProblems(virtualFile); + taskFile.setHighlightErrors(false); + } + } + transformTestFile(project, toSubtaskIndex, taskDir); + task.setActiveSubtaskIndex(toSubtaskIndex); + updateUI(project, task, taskDir); + + for (StudySubtaskChangeListener listener : Extensions.getExtensions(StudySubtaskChangeListener.EP_NAME)) { + listener.subtaskChanged(project, task, fromSubtaskIndex, toSubtaskIndex); + } + } + + private static void transformTestFile(@NotNull Project project, int toSubtaskIndex, VirtualFile taskDir) { + Course course = StudyTaskManager.getInstance(project).getCourse(); + if (course == null) { + return; + } + StudyLanguageManager manager = StudyUtils.getLanguageManager(course); + if (manager == null) { + return; + } + String defaultTestFileName = manager.getTestFileName(); + String nameWithoutExtension = FileUtil.getNameWithoutExtension(defaultTestFileName); + String extension = FileUtilRt.getExtension(defaultTestFileName); + String subtaskTestFileName = nameWithoutExtension + EduNames.SUBTASK_MARKER + toSubtaskIndex; + VirtualFile subtaskTestFile = taskDir.findChild(subtaskTestFileName + ".txt"); + if (subtaskTestFile != null) { + ApplicationManager.getApplication().runWriteAction(() -> { + try { + subtaskTestFile.rename(project, subtaskTestFileName + "." + extension); + } + catch (IOException e) { + LOG.error(e); + } + }); + } + } + + public static void updateUI(@NotNull Project project, @NotNull Task task, VirtualFile taskDir) { + StudyCheckUtils.drawAllPlaceholders(project, task, taskDir); + ProjectView.getInstance(project).refresh(); + StudyToolWindow toolWindow = StudyUtils.getStudyToolWindow(project); + if (toolWindow != null) { + String text = StudyUtils.getTaskTextFromTask(taskDir, task); + if (text == null) { + toolWindow.setEmptyText(project); + return; + } + toolWindow.setTaskText(text, taskDir, project); + } + } + + private static void updatePlaceholderTexts(@NotNull Project project, + @NotNull Document document, + @NotNull TaskFile taskFile, + int fromSubtaskIndex, + int toSubtaskIndex) { + taskFile.setTrackLengths(false); + for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) { + placeholder.switchSubtask(project, document, fromSubtaskIndex, toSubtaskIndex); + } + taskFile.setTrackLengths(true); + } + + public static void refreshPlaceholder(@NotNull Project project, @NotNull Editor editor, @NotNull AnswerPlaceholder placeholder) { + int prevSubtaskIndex = placeholder.getActiveSubtaskIndex() - 1; + AnswerPlaceholderSubtaskInfo info = placeholder.getSubtaskInfos().get(prevSubtaskIndex); + String replacementText = info != null ? info.getAnswer() : placeholder.getTaskText(); + EduUtils.replaceAnswerPlaceholder(project, editor.getDocument(), placeholder, placeholder.getRealLength(), replacementText); + } +} \ No newline at end of file diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyTaskManager.java b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyTaskManager.java index e2fd2c33246b..eed7a8e9d6ff 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyTaskManager.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyTaskManager.java @@ -34,10 +34,10 @@ import java.util.Map; @State(name = "StudySettings", storages = @Storage("study_project.xml")) public class StudyTaskManager implements PersistentStateComponent, DumbAware { private static final Logger LOG = Logger.getInstance(StudyTaskManager.class); - public static final int CURRENT_VERSION = 3; + public static final int CURRENT_VERSION = 4; private StepicUser myUser = new StepicUser(); private Course myCourse; - public int VERSION = 3; + public int VERSION = 4; public Map> myUserTests = new HashMap<>(); public List myInvisibleFiles = new ArrayList<>(); @@ -97,6 +97,9 @@ public class StudyTaskManager implements PersistentStateComponent, Dumb } public JBColor getColor(@NotNull final AnswerPlaceholder placeholder) { + if (!placeholder.getUseLength() && placeholder.isActive() && placeholder.getActiveSubtaskInfo().isNeedInsertText()) { + return JBColor.LIGHT_GRAY; + } final StudyStatus status = placeholder.getStatus(); if (status == StudyStatus.Solved) { return JBColor.GREEN; @@ -108,7 +111,7 @@ public class StudyTaskManager implements PersistentStateComponent, Dumb } public boolean hasFailedAnswerPlaceholders(@NotNull final TaskFile taskFile) { - return taskFile.getAnswerPlaceholders().size() > 0 && taskFile.hasFailedPlaceholders(); + return taskFile.getActivePlaceholders().size() > 0 && taskFile.hasFailedPlaceholders(); } @Nullable @@ -137,8 +140,10 @@ public class StudyTaskManager implements PersistentStateComponent, Dumb state = StudySerializationUtils.Xml.convertToSecondVersion(state); case 2: state = StudySerializationUtils.Xml.convertToThirdVersion(state, myProject); + case 3: + state = StudySerializationUtils.Xml.convertToForthVersion(state); //uncomment for future versions - //case 3: + //case 4: //state = StudySerializationUtils.Xml.convertToForthVersion(state, myProject); } XmlSerializer.deserializeInto(this, state.getChild(StudySerializationUtils.Xml.MAIN_ELEMENT)); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyTypeHandlerDelegate.java b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyTypeHandlerDelegate.java new file mode 100644 index 000000000000..643692757621 --- /dev/null +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyTypeHandlerDelegate.java @@ -0,0 +1,44 @@ +package com.jetbrains.edu.learning; + +import com.intellij.codeInsight.editorActions.TypedHandlerDelegate; +import com.intellij.codeInsight.hint.HintManager; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiFile; +import com.jetbrains.edu.learning.courseFormat.TaskFile; +import org.jetbrains.annotations.NotNull; + +public class StudyTypeHandlerDelegate extends TypedHandlerDelegate { + + @Override + public Result checkAutoPopup(char charTyped, Project project, Editor editor, PsiFile file) { + return handleTyping(project, editor, file); + } + + @Override + public Result beforeCharTyped(char c, + Project project, + Editor editor, + PsiFile file, + FileType fileType) { + return handleTyping(project, editor, file); + } + + @NotNull + private static Result handleTyping(Project project, Editor editor, PsiFile file) { + if (!StudyUtils.isStudentProject(project)) { + return Result.CONTINUE; + } + TaskFile taskFile = StudyUtils.getTaskFile(project, file.getVirtualFile()); + if (taskFile == null || !taskFile.getTask().hasSubtasks()) { + return Result.CONTINUE; + } + int offset = editor.getCaretModel().getOffset(); + boolean insidePlaceholder = taskFile.getAnswerPlaceholder(offset) != null; + if (!insidePlaceholder) { + HintManager.getInstance().showInformationHint(editor, "Text outside of placeholders is not editable in this task"); + } + return insidePlaceholder ? Result.CONTINUE : Result.STOP; + } +} diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java index 698d34b17d9e..9dcf390cd048 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java @@ -31,6 +31,7 @@ import com.intellij.openapi.ui.popup.Balloon; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtil; @@ -347,7 +348,7 @@ public class StudyUtils { return null; } - public static void drawAllWindows(Editor editor, TaskFile taskFile) { + public static void drawAllAnswerPlaceholders(Editor editor, TaskFile taskFile) { editor.getMarkupModel().removeAllHighlighters(); final Project project = editor.getProject(); if (project == null) return; @@ -356,6 +357,7 @@ public class StudyUtils { final JBColor color = taskManager.getColor(answerPlaceholder); EduAnswerPlaceholderPainter.drawAnswerPlaceholder(editor, answerPlaceholder, color); } + final Document document = editor.getDocument(); EditorActionManager.getInstance() .setReadonlyFragmentModificationHandler(document, new EduAnswerPlaceholderDeleteHandler(editor)); @@ -477,39 +479,53 @@ public class StudyUtils { @Nullable public static String getTaskTextFromTask(@Nullable final VirtualFile taskDirectory, @Nullable final Task task) { - if (task == null) { + if (task == null || task.getLesson() == null || task.getLesson().getCourse() == null) { return null; } - String text = task.getText(); - final Lesson lesson = task.getLesson(); - if (lesson == null) return null; - final Course course = lesson.getCourse(); - if (course != null && course.isAdaptive()) { - text += "\n\nNote: Use standard input to obtain input for the task."; - } + final Course course = task.getLesson().getCourse(); + String text = task.getText() != null ? task.getText() : getTaskTextFromTaskName(taskDirectory, EduNames.TASK_MD); + + if (text == null) return null; + if (course.isAdaptive()) text = wrapAdaptiveCourseText(text); if (text != null && !text.isEmpty()) { return wrapTextToDisplayLatex(text); } if (taskDirectory != null) { - final String taskTextFileHtml = getTaskTextFromTaskName(taskDirectory, EduNames.TASK_HTML); + String fileNameWithoutExtension = FileUtil.getNameWithoutExtension(EduNames.TASK_HTML); + int activeStepIndex = task.getActiveSubtaskIndex(); + if (activeStepIndex != 0) { + fileNameWithoutExtension += EduNames.SUBTASK_MARKER + activeStepIndex; + } + final String taskTextFileHtml = getTaskTextFromTaskName(taskDirectory, getTaskDescriptionName(fileNameWithoutExtension, EduNames.TASK_HTML)); if (taskTextFileHtml != null) return wrapTextToDisplayLatex(taskTextFileHtml); - - final String taskTextFileMd = getTaskTextFromTaskName(taskDirectory, EduNames.TASK_MD); + + final String taskTextFileMd = getTaskTextFromTaskName(taskDirectory, getTaskDescriptionName(fileNameWithoutExtension, EduNames.TASK_MD)); if (taskTextFileMd != null) return wrapTextToDisplayLatex(convertToHtml(taskTextFileMd)); } return null; } + private static String wrapAdaptiveCourseText(@NotNull String text) { + return text + "\n\nNote: Use standard input to obtain input for the task."; + } + + @NotNull + private static String getTaskDescriptionName(String fileNameWithoutExtension, String defaultName) { + return fileNameWithoutExtension + "." + FileUtilRt.getExtension(defaultName); + } + public static String wrapTextToDisplayLatex(String taskTextFileHtml) { final String prefix = String.format(ourPrefix, EditorColorsManager.getInstance().getGlobalScheme().getEditorFontSize()); return prefix + taskTextFileHtml + ourPostfix; } @Nullable - private static String getTaskTextFromTaskName(@NotNull VirtualFile taskDirectory, @NotNull String taskTextFilename) { + private static String getTaskTextFromTaskName(@Nullable VirtualFile taskDirectory, @NotNull String taskTextFilename) { + if (taskDirectory == null) return null; taskDirectory.refresh(false, true); - VirtualFile taskTextFile = taskDirectory.findChild(taskTextFilename); + VirtualFile taskTextFile = ObjectUtils.chooseNotNull(taskDirectory.findChild(EduNames.TASK_HTML), + taskDirectory.findChild(EduNames.TASK_MD)); if (taskTextFile == null) { VirtualFile srcDir = taskDirectory.findChild(EduNames.SRC); if (srcDir != null) { @@ -579,6 +595,14 @@ public class StudyUtils { return StudyTaskManager.getInstance(project).getCourse() != null; } + public static boolean isStudentProject(@NotNull Project project) { + Course course = StudyTaskManager.getInstance(project).getCourse(); + if (course == null) { + return false; + } + return EduNames.STUDY.equals(course.getCourseMode()); + } + @Nullable public static Project getStudyProject() { Project studyProject = null; @@ -696,12 +720,29 @@ public class StudyUtils { } public static boolean isTaskDescriptionFile(@NotNull final String fileName) { - return EduNames.TASK_HTML.equals(fileName) || EduNames.TASK_MD.equals(fileName); + if (EduNames.TASK_HTML.equals(fileName) || EduNames.TASK_MD.equals(fileName)) { + return true; + } + String extension = FileUtilRt.getExtension(fileName); + if (!extension.equals(FileUtilRt.getExtension(EduNames.TASK_HTML)) && !extension.equals(FileUtilRt.getExtension(EduNames.TASK_MD))) { + return false; + } + return fileName.contains(EduNames.TASK) && fileName.contains(EduNames.SUBTASK_MARKER); } @Nullable - public static VirtualFile findTaskDescriptionVirtualFile(@NotNull VirtualFile taskDir) { - return ObjectUtils.chooseNotNull(taskDir.findChild(EduNames.TASK_HTML), taskDir.findChild(EduNames.TASK_MD)); + public static VirtualFile findTaskDescriptionVirtualFile(@NotNull Project project, @NotNull VirtualFile taskDir) { + Task task = getTask(project, taskDir.getName().contains(EduNames.TASK) ? taskDir: taskDir.getParent()); + if (task == null) { + return null; + } + String fileNameWithoutExtension = FileUtil.getNameWithoutExtension(EduNames.TASK_HTML); + int activeStepIndex = task.getActiveSubtaskIndex(); + if (activeStepIndex != 0) { + fileNameWithoutExtension += EduNames.SUBTASK_MARKER + activeStepIndex; + } + return ObjectUtils.chooseNotNull(taskDir.findChild(getTaskDescriptionName(fileNameWithoutExtension, EduNames.TASK_HTML)), + taskDir.findChild(getTaskDescriptionName(fileNameWithoutExtension, EduNames.TASK_MD))); } @NotNull @@ -732,7 +773,7 @@ public class StudyUtils { if (studyEditor == null) return; final Editor editor = studyEditor.getEditor(); IdeFocusManager.getInstance(project).requestFocus(editor.getContentComponent(), true); - final List placeholders = studyEditor.getTaskFile().getAnswerPlaceholders(); + final List placeholders = studyEditor.getTaskFile().getActivePlaceholders(); if (placeholders.isEmpty()) return; final AnswerPlaceholder placeholder = placeholders.get(0); int startOffset = placeholder.getOffset(); @@ -757,4 +798,15 @@ public class StudyUtils { toolWindowManager.registerToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW, true, ToolWindowAnchor.RIGHT, project, true); } } + + @Nullable public static AnswerPlaceholder getAnswerPlaceholder(int offset, List placeholders) { + for (AnswerPlaceholder placeholder : placeholders) { + int placeholderStart = placeholder.getOffset(); + int placeholderEnd = placeholderStart + placeholder.getRealLength(); + if (placeholderStart <= offset && offset <= placeholderEnd) { + return placeholder; + } + } + return null; + } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyFillPlaceholdersAction.java b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyFillPlaceholdersAction.java index 831167d66ff4..4bd0daa96cb0 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyFillPlaceholdersAction.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyFillPlaceholdersAction.java @@ -3,17 +3,16 @@ package com.jetbrains.edu.learning.actions; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.Project; +import com.jetbrains.edu.learning.StudyState; import com.jetbrains.edu.learning.StudyTaskManager; +import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.core.EduNames; +import com.jetbrains.edu.learning.core.EduUtils; import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.StudyState; -import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.editor.StudyEditor; public class StudyFillPlaceholdersAction extends AnAction { @@ -28,18 +27,13 @@ public class StudyFillPlaceholdersAction extends AnAction { } TaskFile taskFile = studyState.getTaskFile(); final Document document = studyState.getEditor().getDocument(); - final TaskFile realTaskFile = taskFile; - CommandProcessor.getInstance().runUndoTransparentAction(() -> ApplicationManager.getApplication().runWriteAction(() -> { - for (AnswerPlaceholder placeholder : realTaskFile.getAnswerPlaceholders()) { - String answer = placeholder.getPossibleAnswer(); - if (answer == null) { - continue; - } - int offset = placeholder.getOffset(); - document.deleteString(offset, offset + placeholder.getRealLength()); - document.insertString(offset, answer); + for (AnswerPlaceholder placeholder : taskFile.getActivePlaceholders()) { + String answer = placeholder.getPossibleAnswer(); + if (answer == null) { + continue; } - })); + EduUtils.replaceAnswerPlaceholder(project, document, placeholder, placeholder.getRealLength(), answer); + } } } @@ -63,7 +57,7 @@ public class StudyFillPlaceholdersAction extends AnAction { return; } TaskFile taskFile = studyState.getTaskFile(); - if (taskFile.getAnswerPlaceholders().isEmpty()) { + if (taskFile.getActivePlaceholders().isEmpty()) { presentation.setEnabledAndVisible(false); } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyNextWindowAction.java b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyNextWindowAction.java index 040cae0b206d..b6f53e9be156 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyNextWindowAction.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyNextWindowAction.java @@ -24,7 +24,7 @@ public class StudyNextWindowAction extends StudyWindowNavigationAction { @Override protected AnswerPlaceholder getTargetPlaceholder(@NotNull final TaskFile taskFile, int offset) { final AnswerPlaceholder selectedAnswerPlaceholder = taskFile.getAnswerPlaceholder(offset); - final List placeholders = taskFile.getAnswerPlaceholders(); + final List placeholders = taskFile.getActivePlaceholders(); if (selectedAnswerPlaceholder == null) { for (AnswerPlaceholder placeholder : placeholders) { if (placeholder.getOffset() > offset) { diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyPrevWindowAction.java b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyPrevWindowAction.java index 28444d14b67d..68f18d7da411 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyPrevWindowAction.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyPrevWindowAction.java @@ -27,7 +27,7 @@ public class StudyPrevWindowAction extends StudyWindowNavigationAction { @Override protected AnswerPlaceholder getTargetPlaceholder(@NotNull final TaskFile taskFile, int offset) { final AnswerPlaceholder selectedAnswerPlaceholder = taskFile.getAnswerPlaceholder(offset); - final List placeholders = taskFile.getAnswerPlaceholders(); + final List placeholders = taskFile.getActivePlaceholders(); if (selectedAnswerPlaceholder == null) { for (int i = placeholders.size() - 1; i >= 0; i--) { final AnswerPlaceholder placeholder = placeholders.get(i); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java index b2b7c86aadf0..3c45e8e701e0 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java @@ -11,14 +11,11 @@ import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; -import com.jetbrains.edu.learning.StudyActionListener; -import com.jetbrains.edu.learning.StudyTaskManager; +import com.jetbrains.edu.learning.*; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.StudyState; -import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.editor.StudyEditor; import org.jetbrains.annotations.Nullable; @@ -45,6 +42,10 @@ public class StudyRefreshAnswerPlaceholder extends DumbAwareAction { } StudyEditor studyEditor = StudyUtils.getSelectedStudyEditor(project); final StudyState studyState = new StudyState(studyEditor); + if (answerPlaceholder.getTaskFile().getTask().hasSubtasks()) { + StudySubtaskUtils.refreshPlaceholder(project, studyState.getEditor(), answerPlaceholder); + return; + } Document patternDocument = StudyUtils.getPatternDocument(answerPlaceholder.getTaskFile(), studyState.getVirtualFile().getName()); if (patternDocument == null) { return; diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java index c4046496b736..08bd1056b968 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java @@ -20,10 +20,7 @@ import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.problems.WolfTheProblemSolver; -import com.jetbrains.edu.learning.StudyActionListener; -import com.jetbrains.edu.learning.StudyState; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.*; import com.jetbrains.edu.learning.core.EduAnswerPlaceholderPainter; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; @@ -44,7 +41,8 @@ public class StudyRefreshTaskFileAction extends StudyActionWithShortcut { private static final Logger LOG = Logger.getInstance(StudyRefreshTaskFileAction.class.getName()); public StudyRefreshTaskFileAction() { - super("Reset Task File (" + KeymapUtil.getShortcutText(new KeyboardShortcut(KeyStroke.getKeyStroke(SHORTCUT), null)) + ")", "Refresh current task", + super("Reset Task File (" + KeymapUtil.getShortcutText(new KeyboardShortcut(KeyStroke.getKeyStroke(SHORTCUT), null)) + ")", + "Refresh current task", InteractiveLearningIcons.ResetTaskFile); } @@ -63,13 +61,20 @@ public class StudyRefreshTaskFileAction extends StudyActionWithShortcut { private static void refreshFile(@NotNull final StudyState studyState, @NotNull final Project project) { final Editor editor = studyState.getEditor(); final TaskFile taskFile = studyState.getTaskFile(); - if (!resetTaskFile(editor.getDocument(), project, taskFile, studyState.getVirtualFile().getName())) { - Messages.showInfoMessage("The initial text of task file is unavailable", "Failed to Refresh Task File"); - return; + if (taskFile.getTask().hasSubtasks()) { + for (AnswerPlaceholder placeholder : taskFile.getActivePlaceholders()) { + StudySubtaskUtils.refreshPlaceholder(project, editor, placeholder); + } + } + else { + if (!resetTaskFile(editor.getDocument(), project, taskFile, studyState.getVirtualFile().getName())) { + Messages.showInfoMessage("The initial text of task file is unavailable", "Failed to Refresh Task File"); + return; + } } WolfTheProblemSolver.getInstance(project).clearProblems(studyState.getVirtualFile()); taskFile.setHighlightErrors(false); - StudyUtils.drawAllWindows(editor, taskFile); + StudyUtils.drawAllAnswerPlaceholders(editor, taskFile); EduAnswerPlaceholderPainter.createGuardedBlocks(editor, taskFile); ApplicationManager.getApplication().invokeLater( () -> IdeFocusManager.getInstance(project).requestFocus(editor.getContentComponent(), true)); @@ -104,7 +109,7 @@ public class StudyRefreshTaskFileAction extends StudyActionWithShortcut { private static void resetAnswerPlaceholders(TaskFile selectedTaskFile, Project project) { final StudyTaskManager taskManager = StudyTaskManager.getInstance(project); - for (AnswerPlaceholder answerPlaceholder : selectedTaskFile.getAnswerPlaceholders()) { + for (AnswerPlaceholder answerPlaceholder : selectedTaskFile.getActivePlaceholders()) { answerPlaceholder.reset(); taskManager.setStatus(answerPlaceholder, StudyStatus.Unchecked); } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyTaskNavigationAction.java b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyTaskNavigationAction.java index e926cc6a0d31..b4c451447245 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyTaskNavigationAction.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyTaskNavigationAction.java @@ -1,30 +1,16 @@ package com.jetbrains.edu.learning.actions; -import com.intellij.ide.projectView.ProjectView; import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.project.Project; -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.util.ui.tree.TreeUtil; import com.jetbrains.edu.learning.StudyState; import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.Task; -import com.jetbrains.edu.learning.courseFormat.TaskFile; import com.jetbrains.edu.learning.editor.StudyEditor; import com.jetbrains.edu.learning.navigation.StudyNavigator; -import com.jetbrains.edu.learning.statistics.EduUsagesCollector; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import javax.swing.tree.TreePath; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; abstract public class StudyTaskNavigationAction extends StudyActionWithShortcut { @@ -42,80 +28,9 @@ abstract public class StudyTaskNavigationAction extends StudyActionWithShortcut if (targetTask == null) { return; } - for (VirtualFile file : FileEditorManager.getInstance(project).getOpenFiles()) { - FileEditorManager.getInstance(project).closeFile(file); - } - int nextTaskIndex = targetTask.getIndex(); - int lessonIndex = targetTask.getLesson().getIndex(); - Map nextTaskFiles = targetTask.getTaskFiles(); - VirtualFile projectDir = project.getBaseDir(); - String lessonDirName = EduNames.LESSON + String.valueOf(lessonIndex); - if (projectDir == null) { - return; - } - VirtualFile lessonDir = projectDir.findChild(lessonDirName); - if (lessonDir == null) { - return; - } - String taskDirName = EduNames.TASK + String.valueOf(nextTaskIndex); - VirtualFile taskDir = lessonDir.findChild(taskDirName); - if (taskDir == null) { - return; - } - if (nextTaskFiles.isEmpty()) { - ProjectView.getInstance(project).select(taskDir, taskDir, false); - return; - } - EduUsagesCollector.taskNavigation(); - VirtualFile shouldBeActive = getFileToActivate(project, nextTaskFiles, taskDir); - updateProjectView(project, shouldBeActive); + StudyNavigator.navigateToTask(project, targetTask); - StudyUtils.selectFirstAnswerPlaceholder(StudyUtils.getSelectedStudyEditor(project), project); - ToolWindow runToolWindow = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.RUN); - if (runToolWindow != null) { - runToolWindow.hide(null); - } - } - - public static void updateProjectView(@NotNull Project project, VirtualFile shouldBeActive) { - JTree tree = ProjectView.getInstance(project).getCurrentProjectViewPane().getTree(); - if (shouldBeActive != null) { - ProjectView.getInstance(project).selectCB(shouldBeActive, shouldBeActive, false).doWhenDone(() -> { - List paths = TreeUtil.collectExpandedPaths(tree); - List toCollapse = new ArrayList<>(); - TreePath selectedPath = tree.getSelectionPath(); - for (TreePath treePath : paths) { - if (treePath.isDescendant(selectedPath)) { - continue; - } - if (toCollapse.isEmpty()) { - toCollapse.add(treePath); - continue; - } - for (int i = 0; i < toCollapse.size(); i++) { - TreePath path = toCollapse.get(i); - if (treePath.isDescendant(path)) { - toCollapse.set(i, treePath); - } else { - if (!path.isDescendant(treePath)) { - toCollapse.add(treePath); - } - } - } - } - for (TreePath path : toCollapse) { - tree.collapsePath(path); - tree.fireTreeCollapsed(path); - } - }); - FileEditorManager.getInstance(project).openFile(shouldBeActive, true); - } - } - - @Nullable - protected VirtualFile getFileToActivate(@NotNull Project project, Map nextTaskFiles, VirtualFile taskDir) { - return StudyNavigator.getFileToActivate(project, nextTaskFiles, taskDir); } @Override diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java index 36b81208eb80..6e11bc7a7b24 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java @@ -5,27 +5,27 @@ import com.intellij.execution.process.ProcessOutput; import com.intellij.ide.projectView.ProjectView; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.TextRange; import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.StudyPluginConfigurator; -import com.jetbrains.edu.learning.StudyState; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.*; import com.jetbrains.edu.learning.actions.StudyAfterCheckAction; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.core.EduUtils; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.StudyStatus; -import com.jetbrains.edu.learning.courseFormat.Task; +import com.jetbrains.edu.learning.courseFormat.*; import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector; import com.jetbrains.edu.learning.stepic.EduStepicConnector; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Map; + public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroundable { private static final Logger LOG = Logger.getInstance(StudyCheckTask.class); @@ -189,8 +189,47 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun }); } else { - ApplicationManager.getApplication() - .invokeLater(() -> StudyCheckUtils.showTestResultPopUp(message, MessageType.INFO.getPopupBackground(), myProject)); + boolean hasMoreSubtasks = myTask.hasSubtasks() && myTask.getActiveSubtaskIndex() != myTask.getLastSubtaskIndex(); + int visibleSubtaskIndex = myTask.getActiveSubtaskIndex() + 1; + ApplicationManager.getApplication().invokeLater(() -> { + int subtaskSize = myTask.getLastSubtaskIndex() + 1; + String resultMessage = !hasMoreSubtasks ? message : "Subtask " + visibleSubtaskIndex + "/" + subtaskSize + " solved"; + StudyCheckUtils.showTestResultPopUp(resultMessage, MessageType.INFO.getPopupBackground(), myProject); + if (hasMoreSubtasks) { + int nextSubtaskIndex = myTask.getActiveSubtaskIndex() + 1; + StudySubtaskUtils.switchStep(myProject, myTask, nextSubtaskIndex); + rememberAnswers(nextSubtaskIndex); + } + }); + } + } + } + + private void rememberAnswers(int nextSubtaskIndex) { + VirtualFile taskDir = myTask.getTaskDir(myProject); + if (taskDir == null) { + return; + } + VirtualFile srcDir = taskDir.findChild(EduNames.SRC); + if (srcDir != null) { + taskDir = srcDir; + } + for (Map.Entry entry : myTask.getTaskFiles().entrySet()) { + TaskFile taskFile = entry.getValue(); + VirtualFile virtualFile = taskDir.findChild(entry.getKey()); + if (virtualFile == null) { + continue; + } + Document document = FileDocumentManager.getInstance().getDocument(virtualFile); + if (document == null) { + continue; + } + for (AnswerPlaceholder placeholder : taskFile.getActivePlaceholders()) { + if (placeholder.getSubtaskInfos().containsKey(nextSubtaskIndex - 1)) { + int offset = placeholder.getOffset(); + String answer = document.getText(TextRange.create(offset, offset + placeholder.getRealLength())); + placeholder.getSubtaskInfos().get(nextSubtaskIndex - 1).setAnswer(answer); + } } } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java index 73812b9be5bc..520e5d87d2cf 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java @@ -59,7 +59,7 @@ public class StudyCheckUtils { FileEditor fileEditor = FileEditorManager.getInstance(project).getSelectedEditor(virtualFile); if (fileEditor instanceof StudyEditor) { StudyEditor studyEditor = (StudyEditor)fileEditor; - StudyUtils.drawAllWindows(studyEditor.getEditor(), taskFile); + StudyUtils.drawAllAnswerPlaceholders(studyEditor.getEditor(), taskFile); } } } @@ -114,17 +114,20 @@ public class StudyCheckUtils { public static void runSmartTestProcess(@NotNull final VirtualFile taskDir, @NotNull final StudyTestRunner testRunner, - final String taskFileName, + @NotNull final String taskFileName, @NotNull final TaskFile taskFile, @NotNull final Project project) { - final TaskFile answerTaskFile = new TaskFile(); - answerTaskFile.name = taskFileName; final VirtualFile virtualFile = taskDir.findChild(taskFileName); if (virtualFile == null) { return; } - final VirtualFile answerFile = getCopyWithAnswers(taskDir, virtualFile, taskFile, answerTaskFile); - for (final AnswerPlaceholder answerPlaceholder : answerTaskFile.getAnswerPlaceholders()) { + Pair pair = getCopyWithAnswers(taskDir, virtualFile, taskFile); + if (pair == null) { + return; + } + VirtualFile answerFile = pair.getFirst(); + TaskFile answerTaskFile = pair.getSecond(); + for (final AnswerPlaceholder answerPlaceholder : answerTaskFile.getActivePlaceholders()) { final Document document = FileDocumentManager.getInstance().getDocument(virtualFile); if (document == null) { continue; @@ -136,33 +139,34 @@ public class StudyCheckUtils { } - private static VirtualFile getCopyWithAnswers(@NotNull final VirtualFile taskDir, + private static Pair getCopyWithAnswers(@NotNull final VirtualFile taskDir, @NotNull final VirtualFile file, - @NotNull final TaskFile source, - @NotNull final TaskFile target) { - VirtualFile copy = null; + @NotNull final TaskFile source) { try { - - copy = file.copy(taskDir, taskDir, file.getNameWithoutExtension() + EduNames.ANSWERS_POSTFIX + "." + file.getExtension()); + VirtualFile answerFile = file.copy(taskDir, taskDir, file.getNameWithoutExtension() + EduNames.ANSWERS_POSTFIX + "." + file.getExtension()); final FileDocumentManager documentManager = FileDocumentManager.getInstance(); - final Document document = documentManager.getDocument(copy); + final Document document = documentManager.getDocument(answerFile); if (document != null) { - TaskFile.copy(source, target); - EduDocumentListener listener = new EduDocumentListener(target); + TaskFile answerTaskFile = source.getTask().copy().getTaskFile(file.getName()); + if (answerTaskFile == null) { + return null; + } + EduDocumentListener listener = new EduDocumentListener(answerTaskFile); document.addDocumentListener(listener); - for (AnswerPlaceholder answerPlaceholder : target.getAnswerPlaceholders()) { + for (AnswerPlaceholder answerPlaceholder : answerTaskFile.getActivePlaceholders()) { final int start = answerPlaceholder.getOffset(); final int end = start + answerPlaceholder.getRealLength(); final String text = answerPlaceholder.getPossibleAnswer(); document.replaceString(start, end, text); } ApplicationManager.getApplication().runWriteAction(() -> documentManager.saveDocument(document)); + return Pair.create(answerFile, answerTaskFile); } } catch (IOException e) { LOG.error(e); } - return copy; + return null; } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudySmartChecker.java b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudySmartChecker.java index e67c2b62b227..11b94b633933 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudySmartChecker.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudySmartChecker.java @@ -50,13 +50,15 @@ public class StudySmartChecker { if (windowDocument != null) { final File resourceFile = StudyUtils.copyResourceFile(virtualFile.getName(), windowCopy.getName(), project, usersTaskFile.getTask()); - final TaskFile windowTaskFile = new TaskFile(); - TaskFile.copy(answerTaskFile, windowTaskFile); + TaskFile windowTaskFile = answerTaskFile.getTask().copy().getTaskFile(virtualFile.getName()); + if (windowTaskFile == null) { + return; + } EduDocumentListener listener = new EduDocumentListener(windowTaskFile); windowDocument.addDocumentListener(listener); int start = placeholder.getOffset(); int end = start + placeholder.getRealLength(); - final AnswerPlaceholder userAnswerPlaceholder = usersTaskFile.getAnswerPlaceholders().get(placeholder.getIndex()); + final AnswerPlaceholder userAnswerPlaceholder = usersTaskFile.getActivePlaceholders().get(placeholder.getIndex()); int userStart = userAnswerPlaceholder.getOffset(); int userEnd = userStart + userAnswerPlaceholder.getRealLength(); String text = usersDocument.getText(new TextRange(userStart, userEnd)); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduAnswerPlaceholderPainter.java b/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduAnswerPlaceholderPainter.java index 5c83a89af989..6d05809fcba0 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduAnswerPlaceholderPainter.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduAnswerPlaceholderPainter.java @@ -11,9 +11,12 @@ import com.intellij.openapi.editor.markup.*; import com.intellij.openapi.project.Project; import com.intellij.ui.JBColor; import com.intellij.ui.SimpleTextAttributes; +import com.intellij.util.DocumentUtil; import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; +import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholderSubtaskInfo; import com.jetbrains.edu.learning.courseFormat.TaskFile; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.awt.*; import java.util.List; @@ -31,41 +34,65 @@ public class EduAnswerPlaceholderPainter { @NotNull final JBColor color) { EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); final TextAttributes textAttributes = new TextAttributes(scheme.getDefaultForeground(), scheme.getDefaultBackground(), null, - EffectType.BOXED, Font.PLAIN); + EffectType.BOXED, Font.PLAIN); textAttributes.setEffectColor(color); - drawAnswerPlaceholder(editor, placeholder, textAttributes, PLACEHOLDERS_LAYER); + if (placeholder.isActive()) { + drawAnswerPlaceholder(editor, placeholder, textAttributes, PLACEHOLDERS_LAYER); + } + else if (!placeholder.getUseLength()) { + int offset = placeholder.getOffset(); + drawAnswerPlaceholderFromPrevStep(editor, offset, offset + placeholder.getVisibleLength(placeholder.getActiveSubtaskIndex())); + } } public static void drawAnswerPlaceholder(@NotNull Editor editor, @NotNull AnswerPlaceholder placeholder, - TextAttributes textAttributes, + @Nullable TextAttributes textAttributes, int placeholdersLayer) { - final Project project = editor.getProject(); - assert project != null; - final int startOffset = placeholder.getOffset(); - if (startOffset == - 1) { + int startOffset = placeholder.getOffset(); + if (startOffset == -1) { return; } final int length = placeholder.getRealLength(); - final int endOffset = startOffset + length; - RangeHighlighter - highlighter = editor.getMarkupModel().addRangeHighlighter(startOffset, endOffset, placeholdersLayer, - textAttributes, HighlighterTargetArea.EXACT_RANGE); + int delta = 0; + AnswerPlaceholderSubtaskInfo info = placeholder.getActiveSubtaskInfo(); + if (info != null && info.isNeedInsertText()) { + Document document = editor.getDocument(); + int nonSpaceCharOffset = DocumentUtil.getFirstNonSpaceCharOffset(document, startOffset, startOffset + length); + if (nonSpaceCharOffset != startOffset) { + delta = startOffset - nonSpaceCharOffset; + startOffset = nonSpaceCharOffset; + } + } + final int endOffset = startOffset + length + delta; + drawAnswerPlaceholder(editor, startOffset, endOffset, textAttributes, placeholdersLayer); + } + + public static void drawAnswerPlaceholder(@NotNull Editor editor, + int start, + int end, + @Nullable TextAttributes textAttributes, + int placeholdersLayer) { + final Project project = editor.getProject(); + assert project != null; + if (start == -1) { + return; + } + RangeHighlighter highlighter = editor.getMarkupModel().addRangeHighlighter(start, end, placeholdersLayer, + textAttributes, HighlighterTargetArea.EXACT_RANGE); highlighter.setGreedyToLeft(true); highlighter.setGreedyToRight(true); } - public static void drawAnswerPlaceholderFromPrevStep(@NotNull Editor editor, @NotNull AnswerPlaceholder placeholder) { - if (placeholder.getUseLength()) { - return; - } + + public static void drawAnswerPlaceholderFromPrevStep(@NotNull Editor editor, int start, int end) { EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); Color color = scheme.getColor(EditorColors.TEARLINE_COLOR); SimpleTextAttributes attributes = SimpleTextAttributes.GRAY_ATTRIBUTES; final TextAttributes textAttributes = new TextAttributes(attributes.getFgColor(), color, null, null, attributes.getFontStyle()); - drawAnswerPlaceholder(editor, placeholder, textAttributes, HighlighterLayer.LAST); + drawAnswerPlaceholder(editor, start, end, textAttributes, HighlighterLayer.LAST); } public static void createGuardedBlock(Editor editor, List blocks, int start, int end) { @@ -76,7 +103,7 @@ public class EduAnswerPlaceholderPainter { public static void createGuardedBlocks(@NotNull final Editor editor, TaskFile taskFile) { - for (AnswerPlaceholder answerPlaceholder : taskFile.getAnswerPlaceholders()) { + for (AnswerPlaceholder answerPlaceholder : taskFile.getActivePlaceholders()) { createGuardedBlocks(editor, answerPlaceholder); } } @@ -97,5 +124,4 @@ public class EduAnswerPlaceholderPainter { } } } - } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduDocumentListener.java b/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduDocumentListener.java index 11e7aa349952..eeb126b9dfca 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduDocumentListener.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduDocumentListener.java @@ -73,7 +73,9 @@ public class EduDocumentListener extends DocumentAdapter { if (answerPlaceholder.getUseLength()) { answerPlaceholder.setLength(length); } else { - answerPlaceholder.setPossibleAnswer(document.getText(TextRange.create(twStart, twStart + length))); + if (answerPlaceholder.isActive() && myTaskFile.isTrackLengths()) { + answerPlaceholder.setPossibleAnswer(document.getText(TextRange.create(twStart, twStart + length))); + } } } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduNames.java b/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduNames.java index 9361f75c093b..b20719033090 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduNames.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduNames.java @@ -49,6 +49,9 @@ public class EduNames { public static final String ANSWER_PLACEHOLDER = "Answer Placeholder"; public static final String SRC = "src"; + public static final String SUBTASK_MARKER = "_subtask"; + public static final String SUBTASK = "subtask"; + private EduNames() { } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduUtils.java b/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduUtils.java index fc6effb6ea93..499b25f13492 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduUtils.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/core/EduUtils.java @@ -20,6 +20,7 @@ import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.psi.PsiDirectory; +import com.intellij.util.containers.ContainerUtil; import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.courseFormat.*; import org.jetbrains.annotations.NonNls; @@ -87,7 +88,7 @@ public class EduUtils { try { fileWindows = taskDir.createChildData(taskFile, name); printWriter = new PrintWriter(new FileOutputStream(fileWindows.getPath())); - for (AnswerPlaceholder answerPlaceholder : taskFile.getAnswerPlaceholders()) { + for (AnswerPlaceholder answerPlaceholder : taskFile.getActivePlaceholders()) { int length = answerPlaceholder.getRealLength(); int start = answerPlaceholder.getOffset(); final String windowDescription = document.getText(new TextRange(start, start + length)); @@ -139,7 +140,8 @@ public class EduUtils { Project project, VirtualFile answerFile, VirtualFile parentDir, - @Nullable Task task) { + @Nullable Task task, + int targetSubtaskIndex) { VirtualFile studentFile = copyFile(requestor, parentDir, answerFile); if (studentFile == null) { @@ -162,21 +164,43 @@ public class EduUtils { } EduDocumentListener listener = new EduDocumentListener(taskFile, false); studentDocument.addDocumentListener(listener); - + taskFile.setTrackLengths(false); for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) { - replaceAnswerPlaceholder(project, studentDocument, placeholder); + int fromSubtask = task.getActiveSubtaskIndex(); + placeholder.switchSubtask(project, studentDocument, fromSubtask, targetSubtaskIndex); } + for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) { + replaceWithTaskText(project, studentDocument, placeholder, targetSubtaskIndex); + } + taskFile.setTrackChanges(true); studentDocument.removeDocumentListener(listener); return Pair.create(studentFile, taskFile); } - private static void replaceAnswerPlaceholder(@NotNull final Project project, - @NotNull final Document document, - @NotNull final AnswerPlaceholder answerPlaceholder) { - final String taskText = answerPlaceholder.getTaskText(); + private static void replaceWithTaskText(Project project, Document studentDocument, AnswerPlaceholder placeholder, int toSubtaskIndex) { + AnswerPlaceholderSubtaskInfo info = placeholder.getSubtaskInfos().get(toSubtaskIndex); + if (info == null) { + return; + } + String replacementText; + if (Collections.min(placeholder.getSubtaskInfos().keySet()) == toSubtaskIndex) { + replacementText = info.getPlaceholderText(); + } + else { + Integer max = Collections.max(ContainerUtil.filter(placeholder.getSubtaskInfos().keySet(), i -> i < toSubtaskIndex)); + replacementText = placeholder.getSubtaskInfos().get(max).getPossibleAnswer(); + } + replaceAnswerPlaceholder(project, studentDocument, placeholder, placeholder.getVisibleLength(toSubtaskIndex), replacementText); + } + + public static void replaceAnswerPlaceholder(@NotNull final Project project, + @NotNull final Document document, + @NotNull final AnswerPlaceholder answerPlaceholder, + int length, + String replacementText) { final int offset = answerPlaceholder.getOffset(); CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction(() -> { - document.replaceString(offset, offset + answerPlaceholder.getRealLength(), taskText); + document.replaceString(offset, offset + length, replacementText); FileDocumentManager.getInstance().saveDocument(document); }), "Replace Answer Placeholders", "Replace Answer Placeholders"); } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholder.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholder.java index c3975ba5524a..6cfb41eeb03b 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholder.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholder.java @@ -2,56 +2,51 @@ package com.jetbrains.edu.learning.courseFormat; import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; -import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.project.Project; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.hash.HashMap; import com.intellij.util.xmlb.annotations.Transient; +import com.jetbrains.edu.learning.core.EduUtils; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Set; /** * Implementation of windows which user should type in */ public class AnswerPlaceholder { - private static final Logger LOG = Logger.getInstance(AnswerPlaceholder.class); - - @SerializedName("hint") - @Expose private String myHint = ""; - - @SerializedName("additional_hints") - @Expose private List myAdditionalHints = new ArrayList<>(); - - @SerializedName("possible_answer") - @Expose private String possibleAnswer = ""; @SerializedName("offset") @Expose private int myOffset = -1; - @Expose private int length = -1; + @SerializedName("length") + @Expose private int myLength = -1; private int myIndex = -1; - private String myTaskText; private MyInitialState myInitialState; - private StudyStatus myStatus = StudyStatus.Unchecked; private boolean mySelected = false; private boolean myUseLength = true; - @Transient private TaskFile myTaskFile; + @SerializedName("subtask_infos") + @Expose private Map mySubtaskInfos = new HashMap<>(); public AnswerPlaceholder() { } public void initAnswerPlaceholder(final TaskFile file, boolean isRestarted) { - if (!isRestarted) { - setInitialState(new MyInitialState(myOffset, length)); - myStatus = file.getTask().getStatus(); - } - setTaskFile(file); + if (!isRestarted) { + setInitialState(new MyInitialState(myOffset, myLength)); + for (AnswerPlaceholderSubtaskInfo info : getSubtaskInfos().values()) { + info.setStatus(file.getTask().getStatus()); + } + } } public int getIndex() { @@ -66,28 +61,21 @@ public class AnswerPlaceholder { * in actions {@link AnswerPlaceholder#getRealLength()} should be used */ public int getLength() { - return length; + return myLength; } public void setLength(int length) { - this.length = length; - } - - @NotNull - public List getAdditionalHints() { - return myAdditionalHints; - } - - public void setAdditionalHints(@Nullable final List additionalHints) { - myAdditionalHints = additionalHints; + myLength = length; } + @Transient public String getPossibleAnswer() { - return possibleAnswer; + return getActiveSubtaskInfo().getPossibleAnswer(); } + @Transient public void setPossibleAnswer(String possibleAnswer) { - this.possibleAnswer = possibleAnswer; + getActiveSubtaskInfo().setPossibleAnswer(possibleAnswer); } public MyInitialState getInitialState() { @@ -98,12 +86,14 @@ public class AnswerPlaceholder { myInitialState = initialState; } + @Transient public String getTaskText() { - return myTaskText; + return getActiveSubtaskInfo().getPlaceholderText(); } + @Transient public void setTaskText(String taskText) { - myTaskText = taskText; + getActiveSubtaskInfo().setPlaceholderText(taskText); } @Transient @@ -117,7 +107,7 @@ public class AnswerPlaceholder { } public int getPossibleAnswerLength() { - return possibleAnswer.length(); + return getPossibleAnswer().length(); } /** @@ -125,18 +115,18 @@ public class AnswerPlaceholder { */ public void reset() { myOffset = myInitialState.getOffset(); - length = myInitialState.getLength(); - if (!myUseLength) { - possibleAnswer = myTaskText; - } + myLength = myInitialState.getLength(); } + @Transient public StudyStatus getStatus() { - return myStatus; + AnswerPlaceholderSubtaskInfo info = getActiveSubtaskInfo(); + return info != null ? info.getStatus() : StudyStatus.Unchecked; } + @Transient public void setStatus(StudyStatus status) { - myStatus = status; + getActiveSubtaskInfo().setStatus(status); } public boolean getSelected() { @@ -148,7 +138,7 @@ public class AnswerPlaceholder { } public void init() { - setInitialState(new MyInitialState(myOffset, myTaskText.length())); + setInitialState(new MyInitialState(myOffset, getTaskText().length())); } public boolean getUseLength() { @@ -159,7 +149,7 @@ public class AnswerPlaceholder { * @return length or possible answer length */ public int getRealLength() { - return myUseLength ? getLength() : getPossibleAnswerLength(); + return myUseLength ? getLength() : getVisibleLength(getActiveSubtaskIndex()); } public void setUseLength(boolean useLength) { @@ -174,65 +164,34 @@ public class AnswerPlaceholder { myOffset = offset; } - public String getHint() { - return myHint; - } - - public void setHint(String hint) { - myHint = hint; - } - @Transient public List getHints() { - if (myHint.isEmpty() && myAdditionalHints.isEmpty()) return Collections.emptyList(); - final ArrayList result = new ArrayList<>(); - result.add(myHint); - result.addAll(myAdditionalHints); - return result; + return getActiveSubtaskInfo().getHints(); } @Transient public void setHints(@NotNull final List hints) { - if (hints.isEmpty()) { - myHint = ""; - myAdditionalHints.clear(); - } - else { - myHint = hints.get(0); - myAdditionalHints = hints.subList(1, hints.size()); - } - } - - public void setHintByIndex(int i, @NotNull final String text) { - if (i == 0) { - myHint = text; - } - else { - myAdditionalHints.set(i - 1, text); - } + getActiveSubtaskInfo().setHints(hints); } public void addHint(@NotNull final String text) { - if (myHint.isEmpty() && myAdditionalHints.isEmpty()) { - myHint = text; - } - else { - myAdditionalHints.add(text); - } + getActiveSubtaskInfo().addHint(text); } public void removeHint(int i) { - if (i == 0) { - myHint = ""; - } - else { - if (i - 1 getSubtaskInfos() { + return mySubtaskInfos; + } + + public void setSubtaskInfos(Map subtaskInfos) { + mySubtaskInfos = subtaskInfos; + } + + public boolean isActive() { + return getActiveSubtaskInfo() != null; } public static class MyInitialState { @@ -263,4 +222,62 @@ public class AnswerPlaceholder { this.offset = offset; } } + + public AnswerPlaceholderSubtaskInfo getActiveSubtaskInfo() { + return mySubtaskInfos.get(getActiveSubtaskIndex()); + } + + public int getActiveSubtaskIndex() { + if (myTaskFile == null || myTaskFile.getTask() == null) { + return 0; + } + return myTaskFile.getTask().getActiveSubtaskIndex(); + } + + public int getVisibleLength(int subtaskIndex) { + int minIndex = Collections.min(mySubtaskInfos.keySet()); + AnswerPlaceholderSubtaskInfo minInfo = mySubtaskInfos.get(minIndex); + if (minIndex == subtaskIndex) { + return getUseLength() ? myLength : minInfo.getPossibleAnswer().length(); + } + if (minIndex > subtaskIndex) { + return minInfo.isNeedInsertText() ? 0 : minInfo.getPlaceholderText().length(); + } + int maxIndex = Collections.max(ContainerUtil.filter(mySubtaskInfos.keySet(), i -> i <= subtaskIndex)); + return getUseLength() ? myLength : mySubtaskInfos.get(maxIndex).getPossibleAnswer().length(); + } + + public void switchSubtask(@NotNull Project project, @NotNull Document document, int fromSubtask, int toSubtask) { + Set indexes = mySubtaskInfos.keySet(); + int visibleLength = getVisibleLength(fromSubtask); + if (indexes.contains(fromSubtask) && indexes.contains(toSubtask)) { + if (!myUseLength) { + String replacementText = mySubtaskInfos.get(toSubtask).getPossibleAnswer(); + EduUtils.replaceAnswerPlaceholder(project, document, this, visibleLength, replacementText); + } + return; + } + Integer minIndex = Collections.min(indexes); + if (fromSubtask < toSubtask) { + if (minIndex > fromSubtask && minIndex <= toSubtask) { + Integer maxIndex = Collections.max(ContainerUtil.filter(indexes, integer -> integer <= toSubtask)); + AnswerPlaceholderSubtaskInfo maxInfo = mySubtaskInfos.get(maxIndex); + String replacementText = myUseLength ? maxInfo.getPlaceholderText() : maxInfo.getPossibleAnswer(); + EduUtils.replaceAnswerPlaceholder(project, document, this, visibleLength, replacementText); + return; + } + } + if (fromSubtask > toSubtask) { + if (minIndex > toSubtask && minIndex <= fromSubtask) { + AnswerPlaceholderSubtaskInfo minInfo = mySubtaskInfos.get(minIndex); + if (minInfo.isNeedInsertText()) { + EduUtils.replaceAnswerPlaceholder(project, document, this, visibleLength, ""); + } + else { + String replacementText = minInfo.getPlaceholderText(); + EduUtils.replaceAnswerPlaceholder(project, document, this, visibleLength, replacementText); + } + } + } + } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholderSubtaskInfo.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholderSubtaskInfo.java new file mode 100644 index 000000000000..3c3c931d9d4e --- /dev/null +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholderSubtaskInfo.java @@ -0,0 +1,109 @@ +package com.jetbrains.edu.learning.courseFormat; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; +import com.intellij.openapi.diagnostic.Logger; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public class AnswerPlaceholderSubtaskInfo { + private static final Logger LOG = Logger.getInstance(AnswerPlaceholderSubtaskInfo.class); + + @SerializedName("hints") + @Expose private List myHints = new ArrayList<>(); + + @SerializedName("possible_answer") + @Expose private String myPossibleAnswer = ""; + + @SerializedName("placeholder_text") + @Expose private String myPlaceholderText; + + private String myAnswer = ""; + private boolean mySelected = false; + private StudyStatus myStatus = StudyStatus.Unchecked; + + @SerializedName("has_frame") + @Expose private boolean myHasFrame = true; + + @SerializedName("need_insert_text") + @Expose private boolean myNeedInsertText = false; + + public StudyStatus getStatus() { + return myStatus; + } + + public void setStatus(StudyStatus status) { + myStatus = status; + } + + public void addHint(@NotNull final String text) { + myHints.add(text); + } + + public void removeHint(int i) { + if (i < myHints.size()) { + myHints.remove(i); + } + else { + LOG.warn("Trying to remove nonexistent hint. Hint to remove number: " + i + " number of hints: " + myHints.size()); + } + } + + public List getHints() { + return myHints; + } + + public void setHints(List hints) { + myHints = hints; + } + + public String getPossibleAnswer() { + return myPossibleAnswer; + } + + public void setPossibleAnswer(String possibleAnswer) { + myPossibleAnswer = possibleAnswer; + } + + public String getPlaceholderText() { + return myPlaceholderText; + } + + public void setPlaceholderText(String placeholderText) { + myPlaceholderText = placeholderText; + } + + public boolean getSelected() { + return mySelected; + } + + public void setSelected(boolean selected) { + mySelected = selected; + } + + public boolean isHasFrame() { + return myHasFrame; + } + + public void setHasFrame(boolean hasFrame) { + myHasFrame = hasFrame; + } + + public boolean isNeedInsertText() { + return myNeedInsertText; + } + + public void setNeedInsertText(boolean needInsertText) { + myNeedInsertText = needInsertText; + } + + public String getAnswer() { + return myAnswer; + } + + public void setAnswer(String answer) { + myAnswer = answer; + } +} diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Task.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Task.java index 378b68e24afc..81512cb9ac0b 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Task.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Task.java @@ -42,6 +42,9 @@ public class Task implements StudyItem { @Transient private Lesson myLesson; @Expose @SerializedName("update_date") private Date myUpdateDate; + private int myActiveSubtaskIndex = 0; + @Expose private int myLastSubtaskIndex = 0; + public Task() {} public Task(@NotNull final String name) { @@ -141,7 +144,12 @@ public class Task implements StudyItem { if (courseDir != null) { VirtualFile lessonDir = courseDir.findChild(lessonDirName); if (lessonDir != null) { - return lessonDir.findChild(taskDirName); + VirtualFile taskDir = lessonDir.findChild(taskDirName); + if (taskDir == null) { + return null; + } + VirtualFile srcDir = taskDir.findChild(EduNames.SRC); + return srcDir != null ? srcDir : taskDir; } } return null; @@ -152,7 +160,7 @@ public class Task implements StudyItem { if (!StringUtil.isEmptyOrSpaces(text)) return text; final VirtualFile taskDir = getTaskDir(project); if (taskDir != null) { - final VirtualFile file = StudyUtils.findTaskDescriptionVirtualFile(taskDir); + final VirtualFile file = StudyUtils.findTaskDescriptionVirtualFile(project, taskDir); if (file == null) return ""; final Document document = FileDocumentManager.getInstance().getDocument(file); if (document != null) { @@ -217,12 +225,15 @@ public class Task implements StudyItem { } public void setStatus(StudyStatus status) { - myStatus = status; for (TaskFile taskFile : taskFiles.values()) { - for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) { + for (AnswerPlaceholder placeholder : taskFile.getActivePlaceholders()) { placeholder.setStatus(status); } } + if (status == StudyStatus.Solved && hasSubtasks() && getActiveSubtaskIndex() != getLastSubtaskIndex()) { + return; + } + myStatus = status; } public Task copy() { @@ -250,4 +261,24 @@ public class Task implements StudyItem { if (myUpdateDate == null) return false; return !date.after(myUpdateDate); } + + public int getActiveSubtaskIndex() { + return myActiveSubtaskIndex; + } + + public void setActiveSubtaskIndex(int activeSubtaskIndex) { + myActiveSubtaskIndex = activeSubtaskIndex; + } + + public int getLastSubtaskIndex() { + return myLastSubtaskIndex; + } + + public void setLastSubtaskIndex(int lastSubtaskIndex) { + myLastSubtaskIndex = lastSubtaskIndex; + } + + public boolean hasSubtasks() { + return myLastSubtaskIndex > 0; + } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/TaskFile.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/TaskFile.java index 26dd6cdafc08..cd6ec7377689 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/TaskFile.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/TaskFile.java @@ -3,6 +3,7 @@ package com.jetbrains.edu.learning.courseFormat; import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; import com.intellij.util.xmlb.annotations.Transient; +import com.jetbrains.edu.learning.StudyUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -21,6 +22,7 @@ public class TaskFile { private int myIndex = -1; private boolean myUserCreated = false; private boolean myTrackChanges = true; + private boolean myTrackLengths = true; private boolean myHighlightErrors = false; @Expose @SerializedName("placeholders") private List myAnswerPlaceholders = new ArrayList<>(); @Transient private Task myTask; @@ -44,6 +46,16 @@ public class TaskFile { return myAnswerPlaceholders; } + public List getActivePlaceholders() { + List result = new ArrayList<>(); + for (AnswerPlaceholder placeholder : myAnswerPlaceholders) { + if (placeholder.getActiveSubtaskInfo() != null) { + result.add(placeholder); + } + } + return result; + } + public void setAnswerPlaceholders(List answerPlaceholders) { this.myAnswerPlaceholders = answerPlaceholders; } @@ -76,19 +88,19 @@ public class TaskFile { */ @Nullable public AnswerPlaceholder getAnswerPlaceholder(int offset) { - for (AnswerPlaceholder placeholder : myAnswerPlaceholders) { - int placeholderStart = placeholder.getOffset(); - int placeholderEnd = placeholderStart + placeholder.getRealLength(); - if (placeholderStart <= offset && offset <= placeholderEnd) { - return placeholder; - } - } - return null; + return StudyUtils.getAnswerPlaceholder(offset, getActivePlaceholders()); } + public boolean isTrackLengths() { + return myTrackLengths; + } + + public void setTrackLengths(boolean trackLengths) { + myTrackLengths = trackLengths; + } public static void copy(@NotNull final TaskFile source, @NotNull final TaskFile target) { - List sourceAnswerPlaceholders = source.getAnswerPlaceholders(); + List sourceAnswerPlaceholders = source.getActivePlaceholders(); List answerPlaceholdersCopy = new ArrayList<>(sourceAnswerPlaceholders.size()); for (AnswerPlaceholder answerPlaceholder : sourceAnswerPlaceholders) { AnswerPlaceholder answerPlaceholderCopy = new AnswerPlaceholder(); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java index 5df925d276f8..6c714792132e 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java @@ -184,7 +184,7 @@ public class StudyProjectGenerator { final VirtualFile virtualFile = ((VirtualDirectoryImpl)taskDir).refreshAndFindChild(name); if (virtualFile != null) { FileEditorManager.getInstance(project).openFile(virtualFile, true); - if (!taskFile.getAnswerPlaceholders().isEmpty()) { + if (!taskFile.getActivePlaceholders().isEmpty()) { activeVirtualFile = virtualFile; } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/editor/StudyEditorFactoryListener.java b/python/educational-core/student/src/com/jetbrains/edu/learning/editor/StudyEditorFactoryListener.java index 73133964ff33..288bc157b012 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/editor/StudyEditorFactoryListener.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/editor/StudyEditorFactoryListener.java @@ -81,7 +81,7 @@ public class StudyEditorFactoryListener implements EditorFactoryListener { if (!taskFile.getAnswerPlaceholders().isEmpty()) { StudyNavigator.navigateToFirstAnswerPlaceholder(editor, taskFile); boolean isStudyProject = EduNames.STUDY.equals(course.getCourseMode()); - StudyUtils.drawAllWindows(editor, taskFile); + StudyUtils.drawAllAnswerPlaceholders(editor, taskFile); if (isStudyProject) { editor.addEditorMouseListener(new WindowSelectionListener(taskFile)); } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java b/python/educational-core/student/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java index 3749d196cc2d..525719dd95de 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java @@ -6,18 +6,24 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.project.Project; 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.util.ui.tree.TreeUtil; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.*; +import com.jetbrains.edu.learning.statistics.EduUsagesCollector; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.swing.*; +import javax.swing.tree.TreePath; +import java.util.ArrayList; import java.util.List; import java.util.Map; -import static com.jetbrains.edu.learning.actions.StudyTaskNavigationAction.updateProjectView; - public class StudyNavigator { private StudyNavigator() { @@ -74,7 +80,7 @@ public class StudyNavigator { public static void navigateToFirstFailedAnswerPlaceholder(@NotNull final Editor editor, @NotNull final TaskFile taskFile) { final Project project = editor.getProject(); if (project == null) return; - for (AnswerPlaceholder answerPlaceholder : taskFile.getAnswerPlaceholders()) { + for (AnswerPlaceholder answerPlaceholder : taskFile.getActivePlaceholders()) { if (answerPlaceholder.getStatus() != StudyStatus.Failed) { continue; } @@ -94,8 +100,8 @@ public class StudyNavigator { public static void navigateToFirstAnswerPlaceholder(@NotNull final Editor editor, @NotNull final TaskFile taskFile) { - if (!taskFile.getAnswerPlaceholders().isEmpty()) { - AnswerPlaceholder firstAnswerPlaceholder = StudyUtils.getFirst(taskFile.getAnswerPlaceholders()); + if (!taskFile.getActivePlaceholders().isEmpty()) { + AnswerPlaceholder firstAnswerPlaceholder = StudyUtils.getFirst(taskFile.getActivePlaceholders()); if (firstAnswerPlaceholder == null) return; navigateToAnswerPlaceholder(editor, firstAnswerPlaceholder); } @@ -103,22 +109,22 @@ public class StudyNavigator { @Nullable public static VirtualFile getFileToActivate(@NotNull Project project, Map nextTaskFiles, VirtualFile taskDir) { - VirtualFile shouldBeActive = null; + VirtualFile fileToActivate = null; for (Map.Entry entry : nextTaskFiles.entrySet()) { String name = entry.getKey(); TaskFile taskFile = entry.getValue(); VirtualFile srcDir = taskDir.findChild(EduNames.SRC); VirtualFile vf = srcDir == null ? taskDir.findChild(name) : srcDir.findChild(name); if (vf != null) { - if (shouldBeActive != null) { + if (fileToActivate != null) { FileEditorManager.getInstance(project).openFile(vf, true); } - if (shouldBeActive == null && !taskFile.getAnswerPlaceholders().isEmpty()) { - shouldBeActive = vf; + if (fileToActivate == null && !taskFile.getActivePlaceholders().isEmpty()) { + fileToActivate = vf; } } } - return shouldBeActive != null ? shouldBeActive : getFirstTaskFile(taskDir, project); + return fileToActivate != null ? fileToActivate : getFirstTaskFile(taskDir, project); } @Nullable @@ -144,33 +150,77 @@ public class StudyNavigator { if (task == null) { return; } - ApplicationManager.getApplication().invokeLater(() -> { - for (VirtualFile file : FileEditorManager.getInstance(project).getOpenFiles()) { - FileEditorManager.getInstance(project).closeFile(file); - } - int nextTaskIndex = task.getIndex(); - int lessonIndex = task.getLesson().getIndex(); - Map nextTaskFiles = task.getTaskFiles(); - VirtualFile projectDir = project.getBaseDir(); - String lessonDirName = EduNames.LESSON + String.valueOf(lessonIndex); - if (projectDir == null) { - return; - } - VirtualFile lessonDir = projectDir.findChild(lessonDirName); - if (lessonDir == null) { - return; - } - String taskDirName = EduNames.TASK + String.valueOf(nextTaskIndex); - VirtualFile taskDir = lessonDir.findChild(taskDirName); - if (taskDir == null) { - return; - } - if (nextTaskFiles.isEmpty()) { - ProjectView.getInstance(project).select(taskDir, taskDir, false); - } - VirtualFile toActivate = getFileToActivate(project, nextTaskFiles, taskDir); + ApplicationManager.getApplication().invokeLater(() -> navigateToTask(project, task)); + } - updateProjectView(project, toActivate); + public static void navigateToTask(@NotNull Project project, @NotNull Task task) { + for (VirtualFile file : FileEditorManager.getInstance(project).getOpenFiles()) { + FileEditorManager.getInstance(project).closeFile(file); + } + Map nextTaskFiles = task.getTaskFiles(); + VirtualFile taskDir = task.getTaskDir(project); + if (taskDir == null) { + return; + } + VirtualFile srcDir = taskDir.findChild(EduNames.SRC); + if (srcDir != null) { + taskDir = srcDir; + } + if (nextTaskFiles.isEmpty()) { + ProjectView.getInstance(project).select(taskDir, taskDir, false); + return; + } + for (String name : nextTaskFiles.keySet()) { + VirtualFile virtualFile = taskDir.findChild(name); + if (virtualFile == null) { + continue; + } + FileEditorManager.getInstance(project).openFile(virtualFile, true); + } + EduUsagesCollector.taskNavigation(); + VirtualFile fileToActivate = getFileToActivate(project, nextTaskFiles, taskDir); + if (fileToActivate != null) { + updateProjectView(project, fileToActivate); + } + + StudyUtils.selectFirstAnswerPlaceholder(StudyUtils.getSelectedStudyEditor(project), project); + ToolWindow runToolWindow = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.RUN); + if (runToolWindow != null) { + runToolWindow.hide(null); + } + } + + private static void updateProjectView(@NotNull Project project, @NotNull VirtualFile fileToActivate) { + JTree tree = ProjectView.getInstance(project).getCurrentProjectViewPane().getTree(); + ProjectView.getInstance(project).selectCB(fileToActivate, fileToActivate, false).doWhenDone(() -> { + List paths = TreeUtil.collectExpandedPaths(tree); + List toCollapse = new ArrayList<>(); + TreePath selectedPath = tree.getSelectionPath(); + for (TreePath treePath : paths) { + if (treePath.isDescendant(selectedPath)) { + continue; + } + if (toCollapse.isEmpty()) { + toCollapse.add(treePath); + continue; + } + for (int i = 0; i < toCollapse.size(); i++) { + TreePath path = toCollapse.get(i); + if (treePath.isDescendant(path)) { + toCollapse.set(i, treePath); + } + else { + if (!path.isDescendant(treePath)) { + toCollapse.add(treePath); + } + } + } + } + for (TreePath path : toCollapse) { + tree.collapsePath(path); + tree.fireTreeCollapsed(path); + } }); + FileEditorManager.getInstance(project).openFile(fileToActivate, true); } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/CourseDirectoryNode.java b/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/CourseDirectoryNode.java new file mode 100644 index 000000000000..18923a03d0fa --- /dev/null +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/CourseDirectoryNode.java @@ -0,0 +1,55 @@ +package com.jetbrains.edu.learning.projectView; + +import com.intellij.ide.projectView.PresentationData; +import com.intellij.ide.projectView.ViewSettings; +import com.intellij.ide.util.treeView.AbstractTreeNode; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDirectory; +import com.jetbrains.edu.learning.core.EduNames; +import com.jetbrains.edu.learning.courseFormat.Course; +import com.jetbrains.edu.learning.courseFormat.Lesson; +import com.jetbrains.edu.learning.courseFormat.StudyItem; +import icons.InteractiveLearningIcons; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + + +public class CourseDirectoryNode extends StudyDirectoryNode { + @NotNull protected final Project myProject; + protected final ViewSettings myViewSettings; + private final Course myCourse; + + public CourseDirectoryNode(@NotNull Project project, + PsiDirectory value, + ViewSettings viewSettings, + @NotNull Course course) { + super(project, value, viewSettings); + myProject = project; + myViewSettings = viewSettings; + myCourse = course; + } + + @Override + protected void updateImpl(PresentationData data) { + setPresentation(data, myCourse.getName(), InteractiveLearningIcons.Course); + } + + @Nullable + public AbstractTreeNode modifyChildNode(AbstractTreeNode childNode) { + Object value = childNode.getValue(); + if (value instanceof PsiDirectory) { + PsiDirectory directory = (PsiDirectory)value; + if (EduNames.SANDBOX_DIR.equals(directory.getName())) { + return new SandboxDirectoryNode(myProject, directory, myViewSettings); + } + Lesson lesson = myCourse.getLesson(directory.getName()); + return lesson != null ? createChildDirectoryNode(lesson, directory) : null; + } + return null; + } + + @Override + public StudyDirectoryNode createChildDirectoryNode(StudyItem item, PsiDirectory directory) { + return new LessonDirectoryNode(myProject, directory, myViewSettings, (Lesson)item); + } +} diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/LessonDirectoryNode.java b/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/LessonDirectoryNode.java new file mode 100644 index 000000000000..03b81f24c40d --- /dev/null +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/LessonDirectoryNode.java @@ -0,0 +1,77 @@ +package com.jetbrains.edu.learning.projectView; + +import com.intellij.ide.projectView.PresentationData; +import com.intellij.ide.projectView.ViewSettings; +import com.intellij.ide.util.treeView.AbstractTreeNode; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiManager; +import com.intellij.ui.JBColor; +import com.jetbrains.edu.learning.core.EduNames; +import com.jetbrains.edu.learning.courseFormat.Lesson; +import com.jetbrains.edu.learning.courseFormat.StudyItem; +import com.jetbrains.edu.learning.courseFormat.StudyStatus; +import com.jetbrains.edu.learning.courseFormat.Task; +import icons.InteractiveLearningIcons; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +public class LessonDirectoryNode extends StudyDirectoryNode { + @NotNull protected final Project myProject; + protected final ViewSettings myViewSettings; + @NotNull protected final Lesson myLesson; + + public LessonDirectoryNode(@NotNull Project project, + PsiDirectory value, + ViewSettings viewSettings, + @NotNull Lesson lesson) { + super(project, value, viewSettings); + myProject = project; + myViewSettings = viewSettings; + myLesson = lesson; + } + + @Override + protected void updateImpl(PresentationData data) { + StudyStatus status = myLesson.getStatus(); + boolean isSolved = status != StudyStatus.Solved; + JBColor color = isSolved ? JBColor.BLACK : LIGHT_GREEN; + Icon icon = isSolved ? InteractiveLearningIcons.Lesson : InteractiveLearningIcons.LessonCompl; + updatePresentation(data, myLesson.getName(), color, icon, null); + } + + @Override + public int getWeight() { + return myLesson.getIndex(); + } + + @Nullable + @Override + public AbstractTreeNode modifyChildNode(AbstractTreeNode childNode) { + Object value = childNode.getValue(); + if (value instanceof PsiDirectory) { + PsiDirectory directory = (PsiDirectory)value; + Task task = myLesson.getTask(directory.getName()); + if (task == null) { + return null; + } + VirtualFile srcDir = directory.getVirtualFile().findChild(EduNames.SRC); + if (srcDir != null) { + directory = PsiManager.getInstance(myProject).findDirectory(srcDir); + if (directory == null) { + return null; + } + } + return createChildDirectoryNode(task, directory); + } + return null; + } + + @Override + public StudyDirectoryNode createChildDirectoryNode(StudyItem item, PsiDirectory directory) { + return new TaskDirectoryNode(myProject, directory, myViewSettings, ((Task)item)); + } +} diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/SandboxDirectoryNode.java b/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/SandboxDirectoryNode.java new file mode 100644 index 000000000000..fd43760eee24 --- /dev/null +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/SandboxDirectoryNode.java @@ -0,0 +1,41 @@ +package com.jetbrains.edu.learning.projectView; + +import com.intellij.ide.projectView.PresentationData; +import com.intellij.ide.projectView.ViewSettings; +import com.intellij.ide.util.treeView.AbstractTreeNode; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDirectory; +import com.jetbrains.edu.learning.core.EduNames; +import com.jetbrains.edu.learning.courseFormat.StudyItem; +import icons.InteractiveLearningIcons; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class SandboxDirectoryNode extends StudyDirectoryNode { + public SandboxDirectoryNode(@NotNull Project project, + PsiDirectory value, + ViewSettings viewSettings) { + super(project, value, viewSettings); + } + + @Nullable + @Override + public AbstractTreeNode modifyChildNode(AbstractTreeNode childNode) { + return childNode; + } + + @Override + public StudyDirectoryNode createChildDirectoryNode(StudyItem item, PsiDirectory value) { + return null; + } + + @Override + protected void updateImpl(PresentationData data) { + setPresentation(data, EduNames.SANDBOX_DIR, InteractiveLearningIcons.Sandbox); + } + + @Override + public int getWeight() { + return Integer.MAX_VALUE; + } +} diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/StudyDirectoryNode.java b/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/StudyDirectoryNode.java index 5461d8051577..96ef6c64c501 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/StudyDirectoryNode.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/StudyDirectoryNode.java @@ -1,194 +1,41 @@ package com.jetbrains.edu.learning.projectView; import com.intellij.ide.projectView.PresentationData; -import com.intellij.ide.projectView.ProjectView; import com.intellij.ide.projectView.ViewSettings; import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode; -import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.ide.util.treeView.AbstractTreeNode; import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiElement; import com.intellij.ui.JBColor; import com.intellij.ui.SimpleTextAttributes; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.core.EduUtils; import com.jetbrains.edu.learning.courseFormat.*; -import icons.InteractiveLearningIcons; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; -import java.util.Map; -public class StudyDirectoryNode extends PsiDirectoryNode { - public static final JBColor LIGHT_GREEN = new JBColor(new Color(0, 134, 0), new Color(98, 150, 85)); - protected final PsiDirectory myValue; - protected final Project myProject; +public abstract class StudyDirectoryNode extends PsiDirectoryNode { + protected static final JBColor LIGHT_GREEN = new JBColor(new Color(0, 134, 0), new Color(98, 150, 85)); public StudyDirectoryNode(@NotNull final Project project, PsiDirectory value, ViewSettings viewSettings) { super(project, value, viewSettings); - myValue = value; - myProject = project; } - @Override - protected void updateImpl(PresentationData data) { - String valueName = myValue.getName(); - StudyTaskManager studyTaskManager = StudyTaskManager.getInstance(myProject); - Course course = studyTaskManager.getCourse(); - if (course == null) { - return; - } - if (valueName.equals(myProject.getBaseDir().getName())) { - data.clearText(); - data.setIcon(InteractiveLearningIcons.Course); - data.addText(course.getName(), new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, JBColor.BLACK)); - } - else if (valueName.contains(EduNames.TASK)) { - VirtualFile taskVirtualFile = myValue.getVirtualFile(); - VirtualFile lessonVirtualFile = taskVirtualFile.getParent(); - if (lessonVirtualFile != null) { - Lesson lesson = course.getLesson(lessonVirtualFile.getName()); - if (lesson != null) { - Task task = lesson.getTask(taskVirtualFile.getName()); - if (task != null) { - setStudyAttributes(task, data, task.getName()); - } - } - } - } - else if (valueName.contains(EduNames.LESSON)) { - int lessonIndex = Integer.parseInt(valueName.substring(EduNames.LESSON.length())) - 1; - Lesson lesson = course.getLessons().get(lessonIndex); - setStudyAttributes(lesson, data, lesson.getName()); - data.setPresentableText(valueName); - } - else if (valueName.contains(EduNames.SANDBOX_DIR)) { - if (myValue.getParent() != null) { - final String parentName = myValue.getParent().getName(); - if (!parentName.contains(EduNames.SANDBOX_DIR)) { - data.setPresentableText(EduNames.SANDBOX_DIR); - data.setIcon(InteractiveLearningIcons.Sandbox); - } - } - } - data.setPresentableText(valueName); - } - - @Override - public int getTypeSortWeight(boolean sortByType) { - String name = myValue.getName(); - if (name.contains(EduNames.LESSON) || name.contains(EduNames.TASK)) { - String logicalName = name.contains(EduNames.LESSON) ? EduNames.LESSON : EduNames.TASK; - return EduUtils.getIndex(name, logicalName) + 1; - } - return name.contains(EduNames.SANDBOX_DIR) ? 0 : 3; - } - - private static void setStudyAttributes(Lesson lesson, PresentationData data, String additionalName) { - switch (lesson.getStatus()) { - case Unchecked: { - updatePresentation(data, additionalName, JBColor.BLACK, InteractiveLearningIcons.Lesson); - break; - } - case Solved: { - updatePresentation(data, additionalName, LIGHT_GREEN, InteractiveLearningIcons.LessonCompl); - break; - } - case Failed: { - updatePresentation(data, additionalName, JBColor.RED, InteractiveLearningIcons.Lesson); - } - } - } - - protected void setStudyAttributes(Task task, PresentationData data, String name) { - StudyStatus taskStatus = task.getStatus(); - switch (taskStatus) { - case Unchecked: { - updatePresentation(data, name, JBColor.BLACK, InteractiveLearningIcons.Task); - break; - } - case Solved: { - updatePresentation(data, name, LIGHT_GREEN, InteractiveLearningIcons.TaskCompl); - break; - } - case Failed: { - updatePresentation(data, name, JBColor.RED, InteractiveLearningIcons.TaskProbl); - } - } - } - - protected static void updatePresentation(PresentationData data, String name, JBColor color, Icon icon) { - data.clearText(); - data.addText(name, new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, color)); + protected void setPresentation(PresentationData data, String name, Icon icon) { + data.setPresentableText(name); data.setIcon(icon); } - @Override - public boolean canNavigate() { - return true; - } - - @Override - public boolean canNavigateToSource() { - return true; - } - - @Override - public void navigate(boolean requestFocus) { - final String myValueName = myValue.getName(); - if (myValueName.contains(EduNames.TASK)) { - TaskFile taskFile = null; - VirtualFile virtualFile = null; - for (PsiElement child : myValue.getChildren()) { - VirtualFile childFile = child.getContainingFile().getVirtualFile(); - taskFile = StudyUtils.getTaskFile(myProject, childFile); - if (taskFile != null) { - virtualFile = childFile; - break; - } - } - if (taskFile != null) { - VirtualFile taskDir = virtualFile.getParent(); - Task task = taskFile.getTask(); - for (VirtualFile openFile : FileEditorManager.getInstance(myProject).getOpenFiles()) { - FileEditorManager.getInstance(myProject).closeFile(openFile); - } - VirtualFile child = null; - for (Map.Entry entry: task.getTaskFiles().entrySet()) { - VirtualFile file = taskDir.findChild(entry.getKey()); - if (file != null) { - FileEditorManager.getInstance(myProject).openFile(file, true); - } - if (!entry.getValue().getAnswerPlaceholders().isEmpty()) { - child = file; - } - } - if (child != null) { - ProjectView.getInstance(myProject).select(child, child, false); - FileEditorManager.getInstance(myProject).openFile(child, true); - } else { - VirtualFile[] children = taskDir.getChildren(); - if (children.length > 0) { - ProjectView.getInstance(myProject).select(children[0], children[0], false); - } - } - } + protected static void updatePresentation(PresentationData data, String name, JBColor color, Icon icon, @Nullable String additionalInfo) { + data.clearText(); + data.addText(name, new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, color)); + if (additionalInfo != null) { + data.addText(" (" + additionalInfo + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES); } - } - - @Override - public boolean expandOnDoubleClick() { - final String myValueName = myValue.getName(); - if (myValueName.contains(EduNames.TASK)) { - return false; - } - return super.expandOnDoubleClick(); + data.setIcon(icon); } @Override @@ -196,8 +43,9 @@ public class StudyDirectoryNode extends PsiDirectoryNode { return false; } - @Override - public String getNavigateActionText(boolean focusEditor) { - return null; - } + @Nullable + public abstract AbstractTreeNode modifyChildNode(AbstractTreeNode childNode); + + public abstract StudyDirectoryNode createChildDirectoryNode(StudyItem item, PsiDirectory value); + } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/StudyTreeStructureProvider.java b/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/StudyTreeStructureProvider.java index 4b1e88f8dd65..2cdcdc0afaa1 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/StudyTreeStructureProvider.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/StudyTreeStructureProvider.java @@ -2,15 +2,13 @@ package com.jetbrains.edu.learning.projectView; import com.intellij.ide.projectView.TreeStructureProvider; import com.intellij.ide.projectView.ViewSettings; -import com.intellij.ide.projectView.impl.nodes.PsiFileNode; +import com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode; +import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode; import com.intellij.ide.util.treeView.AbstractTreeNode; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; -import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.TaskFile; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.StudyUtils; import org.jetbrains.annotations.NotNull; @@ -25,77 +23,37 @@ public class StudyTreeStructureProvider implements TreeStructureProvider, DumbAw public Collection modify(@NotNull AbstractTreeNode parent, @NotNull Collection children, ViewSettings settings) { - if (!needModify(parent)) { + Project project = parent.getProject(); + if (project == null || !shouldModify(project)) { return children; } - Collection nodes = new ArrayList<>(); + Course course = StudyTaskManager.getInstance(project).getCourse(); + if (course == null) { + return children; + } + Collection modifiedNodes = new ArrayList<>(); for (AbstractTreeNode node : children) { - final Project project = node.getProject(); - if (project != null) { - if (node.getValue() instanceof PsiDirectory) { - final PsiDirectory nodeValue = (PsiDirectory)node.getValue(); - final String name = nodeValue.getName(); - if (!name.contains(EduNames.USER_TESTS) && !name.startsWith(".") && !"lib".equals(name)) { - AbstractTreeNode newNode = createStudyDirectoryNode(settings, project, nodeValue); - nodes.add(newNode); - } - } - else { - if (parent instanceof StudyDirectoryNode && node instanceof PsiFileNode) { - final PsiFileNode psiFileNode = (PsiFileNode)node; - final VirtualFile virtualFile = psiFileNode.getVirtualFile(); - if (virtualFile == null) { - return nodes; - } - final TaskFile taskFile = StudyUtils.getTaskFile(project, virtualFile); - if (taskFile != null) { - nodes.add(node); - } - final String parentName = parent.getName(); - if (parentName != null) { - if (parentName.equals(EduNames.SANDBOX_DIR)) { - nodes.add(node); - } - if (parentName.startsWith(EduNames.TASK)) { - addNonInvisibleFiles(nodes, node, project, virtualFile); - } - } - } + if (parent instanceof ProjectViewProjectNode && node instanceof PsiDirectoryNode) { + modifiedNodes.add(createCourseNode(project, node, settings, course)); + continue; + } + if (parent instanceof StudyDirectoryNode) { + AbstractTreeNode modifiedNode = ((StudyDirectoryNode)parent).modifyChildNode(node); + if (modifiedNode != null) { + modifiedNodes.add(modifiedNode); } } } - return nodes; + return modifiedNodes; } @NotNull - protected AbstractTreeNode createStudyDirectoryNode(ViewSettings settings, Project project, PsiDirectory nodeValue) { - return new StudyDirectoryNode(project, nodeValue, settings); + protected CourseDirectoryNode createCourseNode(Project project, AbstractTreeNode node, ViewSettings settings, Course course) { + return new CourseDirectoryNode(project, ((PsiDirectory)node.getValue()), settings, course); } - private static void addNonInvisibleFiles(@NotNull final Collection nodes, - @NotNull final AbstractTreeNode node, - @NotNull final Project project, - @NotNull final VirtualFile virtualFile) { - if (!StudyTaskManager.getInstance(project).isInvisibleFile(virtualFile.getPath())) { - String fileName = virtualFile.getName(); - if (!fileName.contains(EduNames.WINDOW_POSTFIX) && !fileName.contains(EduNames.WINDOWS_POSTFIX) - && !StudyUtils.isTestsFile(project, fileName) && !StudyUtils.isTaskDescriptionFile(fileName)) { - nodes.add(node); - } - } - } - - protected boolean needModify(@NotNull final AbstractTreeNode parent) { - final Project project = parent.getProject(); - if (project == null) { - return false; - } - final StudyTaskManager studyTaskManager = StudyTaskManager.getInstance(project); - Course course = studyTaskManager.getCourse(); - if (course == null) { - return false; - } - return EduNames.STUDY.equals(course.getCourseMode()); + protected boolean shouldModify(@NotNull final Project project) { + return StudyUtils.isStudentProject(project); } @Nullable diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/TaskDirectoryNode.java b/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/TaskDirectoryNode.java new file mode 100644 index 000000000000..22577d618fa2 --- /dev/null +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/projectView/TaskDirectoryNode.java @@ -0,0 +1,98 @@ +package com.jetbrains.edu.learning.projectView; + +import com.intellij.ide.projectView.PresentationData; +import com.intellij.ide.projectView.ViewSettings; +import com.intellij.ide.util.treeView.AbstractTreeNode; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.ui.JBColor; +import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.core.EduNames; +import com.jetbrains.edu.learning.courseFormat.StudyItem; +import com.jetbrains.edu.learning.courseFormat.StudyStatus; +import com.jetbrains.edu.learning.courseFormat.Task; +import com.jetbrains.edu.learning.navigation.StudyNavigator; +import icons.InteractiveLearningIcons; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +public class TaskDirectoryNode extends StudyDirectoryNode { + @NotNull protected final Project myProject; + protected final ViewSettings myViewSettings; + @NotNull protected final Task myTask; + + public TaskDirectoryNode(@NotNull Project project, + PsiDirectory value, + ViewSettings viewSettings, + @NotNull Task task) { + super(project, value, viewSettings); + myProject = project; + myViewSettings = viewSettings; + myTask = task; + } + + @Override + public int getWeight() { + return myTask.getIndex(); + } + + @Override + protected void updateImpl(PresentationData data) { + StudyStatus status = myTask.getStatus(); + String subtaskInfo = myTask.hasSubtasks() ? getSubtaskInfo() : null; + if (status == StudyStatus.Unchecked) { + updatePresentation(data, myTask.getName(), JBColor.BLACK, InteractiveLearningIcons.Task, subtaskInfo); + return; + } + boolean isSolved = status == StudyStatus.Solved; + JBColor color = isSolved ? LIGHT_GREEN : JBColor.RED; + Icon icon = isSolved ? InteractiveLearningIcons.TaskCompl : InteractiveLearningIcons.TaskProbl; + updatePresentation(data, myTask.getName(), color, icon, subtaskInfo); + } + + private String getSubtaskInfo() { + int index = myTask.getActiveSubtaskIndex() + 1; + int subtasksNum = myTask.getLastSubtaskIndex() + 1; + return EduNames.SUBTASK + " " + index + "/" + subtasksNum; + } + + @Override + public boolean expandOnDoubleClick() { + return false; + } + + @Override + public boolean canNavigate() { + return true; + } + + @Override + public void navigate(boolean requestFocus) { + StudyNavigator.navigateToTask(myProject, myTask); + } + + @Nullable + @Override + public AbstractTreeNode modifyChildNode(AbstractTreeNode childNode) { + Object value = childNode.getValue(); + if (value instanceof PsiElement) { + PsiFile psiFile = ((PsiElement) value).getContainingFile(); + VirtualFile virtualFile = psiFile.getVirtualFile(); + if (virtualFile == null) { + return null; + } + return StudyUtils.getTaskFile(myProject, virtualFile) != null ? childNode : null; + } + return null; + } + + @Override + public StudyDirectoryNode createChildDirectoryNode(StudyItem item, PsiDirectory value) { + return null; + } +} diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CCStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CCStepicConnector.java index 47e4d59cad77..3821f4bc8af5 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CCStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CCStepicConnector.java @@ -120,7 +120,7 @@ public class CCStepicConnector { public boolean accept(VirtualFile file) { final String name = file.getName(); return !name.contains(EduNames.LESSON) && !name.equals(EduNames.COURSE_META_FILE) && !name.equals(EduNames.HINTS) && - !"pyc".equals(file.getExtension()) && !file.isDirectory() && !name.equals(EduNames.TEST_HELPER) && !name.startsWith("."); + !"pyc".equals(file.getExtension()) && !file.isDirectory() && !name.equals(EduNames.TEST_HELPER) && !name.startsWith("."); } }); @@ -219,8 +219,7 @@ public class CCStepicConnector { final int lessonId = lesson.getId(); final HttpPut request = new HttpPut(EduStepicNames.STEPIC_API_URL + "/step-sources/" + String.valueOf(task.getStepId())); - final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation(). - registerTypeAdapter(AnswerPlaceholder.class, new StudySerializationUtils.Json.StepicAnswerPlaceholderAdapter()).create(); + final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create(); ApplicationManager.getApplication().invokeLater(() -> { task.addTestsTexts("tests.py", task.getTestsText(project)); final String requestBody = gson.toJson(new StepicWrappers.StepSourceWrapper(project, task, lessonId)); @@ -331,7 +330,6 @@ public class CCStepicConnector { public static void postTask(final Project project, @NotNull final Task task, final int lessonId) { final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/step-sources"); - //TODO: register type adapter for task files here? final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation(). registerTypeAdapter(AnswerPlaceholder.class, new StudySerializationUtils.Json.StepicAnswerPlaceholderAdapter()).create(); ApplicationManager.getApplication().invokeLater(() -> { diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CourseInfo.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CourseInfo.java index d7fb980d5a6c..94accb19510d 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CourseInfo.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CourseInfo.java @@ -27,7 +27,7 @@ public class CourseInfo { List myAuthors = new ArrayList<>(); @SerializedName("summary") private String myDescription; - @SerializedName("course_format") private String myType = "pycharm Python"; //course type in format "pycharm " + @SerializedName("course_format") private String myType = "pycharm" + EduStepicConnector.CURRENT_VERSION + " Python"; //course type in format "pycharm " @Nullable private String username; @SerializedName("update_date") private Date updateDate; diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicClient.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicClient.java index 3ed86017f1ee..8d7b29ac7acb 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicClient.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicClient.java @@ -7,8 +7,6 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.util.net.HttpConfigurable; import com.intellij.util.net.ssl.CertificateManager; import com.jetbrains.edu.learning.StudySerializationUtils; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.TaskFile; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.HttpStatus; @@ -69,8 +67,11 @@ public class EduStepicClient { if (statusLine.getStatusCode() != HttpStatus.SC_OK) { throw new IOException("Stepic returned non 200 status code " + responseString); } - Gson gson = new GsonBuilder().registerTypeAdapter(TaskFile.class, new StudySerializationUtils.Json.StepicTaskFileAdapter()). - registerTypeAdapter(AnswerPlaceholder.class, new StudySerializationUtils.Json.StepicAnswerPlaceholderAdapter()). + return deserializeStepicResponse(container, responseString); + } + + static T deserializeStepicResponse(Class container, String responseString) { + Gson gson = new GsonBuilder().registerTypeAdapter(StepicWrappers.StepOptions.class, new StudySerializationUtils.Json.StepicStepOptionsAdapter()). setDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"). setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); return gson.fromJson(responseString, container); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java index 243e30213000..13184eea6432 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java @@ -29,6 +29,7 @@ import java.util.*; public class EduStepicConnector { private static final Logger LOG = Logger.getInstance(EduStepicConnector.class.getName()); + public static final int CURRENT_VERSION = 2; //this prefix indicates that course can be opened by educational plugin public static final String PYCHARM_PREFIX = "pycharm"; private static final String ADAPTIVE_NOTE = @@ -118,33 +119,60 @@ public class EduStepicConnector { final URI url; try { url = new URIBuilder(EduStepicNames.COURSES).addParameter("is_idea_compatible", "true"). - addParameter("page", String.valueOf(pageNumber)).build(); + addParameter("page", String.valueOf(pageNumber)).build(); } catch (URISyntaxException e) { LOG.error(e.getMessage()); return false; } - final StepicWrappers.CoursesContainer coursesContainer = EduStepicClient.getFromStepic(url.toString(), StepicWrappers.CoursesContainer.class); + final StepicWrappers.CoursesContainer coursesContainer = + EduStepicClient.getFromStepic(url.toString(), StepicWrappers.CoursesContainer.class); + addAvailableCourses(result, coursesContainer); + return coursesContainer.meta.containsKey("has_next") && coursesContainer.meta.get("has_next") == Boolean.TRUE; + } + + static void addAvailableCourses(List result, StepicWrappers.CoursesContainer coursesContainer) throws IOException { final List courseInfos = coursesContainer.courses; for (CourseInfo info : courseInfos) { - final String courseType = info.getType(); - if (!info.isAdaptive() && StringUtil.isEmptyOrSpaces(courseType)) continue; - final List typeLanguage = StringUtil.split(courseType, " "); - if (info.isAdaptive() || (typeLanguage.size() == 2 && PYCHARM_PREFIX.equals(typeLanguage.get(0)))) { + if (!info.isAdaptive() && StringUtil.isEmptyOrSpaces(info.getType())) continue; + if (canBeOpened(info)) { for (Integer instructor : info.instructors) { final StepicUser author = EduStepicClient.getFromStepic(EduStepicNames.USERS + "/" + String.valueOf(instructor), - StepicWrappers.AuthorWrapper.class).users.get(0); + StepicWrappers.AuthorWrapper.class).users.get(0); info.addAuthor(author); } - + if (info.isAdaptive()) { info.setDescription("This is a Stepik Adaptive course.\n\n" + info.getDescription() + ADAPTIVE_NOTE); } - + result.add(info); } } - return coursesContainer.meta.containsKey("has_next") && coursesContainer.meta.get("has_next") == Boolean.TRUE; + } + + static boolean canBeOpened(CourseInfo courseInfo) { + if (courseInfo.isAdaptive) { + return true; + } + String courseType = courseInfo.getType(); + final List typeLanguage = StringUtil.split(courseType, " "); + String prefix = typeLanguage.get(0); + if (typeLanguage.size() != 2 || !prefix.startsWith(PYCHARM_PREFIX)) { + return false; + } + String versionString = prefix.substring(PYCHARM_PREFIX.length()); + if (versionString.isEmpty()) { + return true; + } + try { + Integer version = Integer.valueOf(versionString); + return version <= CURRENT_VERSION; + } + catch (NumberFormatException e) { + LOG.info("Wrong version format", e); + return false; + } } public static Course getCourse(@NotNull final Project project, @NotNull final CourseInfo info) { @@ -154,11 +182,12 @@ public class EduStepicConnector { course.setAdaptive(info.isAdaptive()); course.setId(info.getId()); course.setUpdateDate(getCourseUpdateDate(info.getId())); - + if (!course.isAdaptive()) { String courseType = info.getType(); course.setName(info.getName()); - course.setLanguage(courseType.substring(PYCHARM_PREFIX.length() + 1)); + String language = courseType.split(" ")[1]; + course.setLanguage(language); try { for (Integer section : info.sections) { course.addLessons(getLessons(section)); @@ -189,7 +218,8 @@ public class EduStepicConnector { public static List getLessons(int sectionId) throws IOException { final StepicWrappers.SectionContainer - sectionContainer = EduStepicClient.getFromStepic(EduStepicNames.SECTIONS + String.valueOf(sectionId), StepicWrappers.SectionContainer.class); + sectionContainer = + EduStepicClient.getFromStepic(EduStepicNames.SECTIONS + String.valueOf(sectionId), StepicWrappers.SectionContainer.class); List unitIds = sectionContainer.sections.get(0).units; final List lessons = new ArrayList<>(); for (Integer unitId : unitIds) { @@ -197,14 +227,16 @@ public class EduStepicConnector { unit = EduStepicClient.getFromStepic(EduStepicNames.UNITS + "/" + String.valueOf(unitId), StepicWrappers.UnitContainer.class); int lessonID = unit.units.get(0).lesson; StepicWrappers.LessonContainer - lessonContainer = EduStepicClient.getFromStepic(EduStepicNames.LESSONS + String.valueOf(lessonID), StepicWrappers.LessonContainer.class); + lessonContainer = + EduStepicClient.getFromStepic(EduStepicNames.LESSONS + String.valueOf(lessonID), StepicWrappers.LessonContainer.class); Lesson lesson = lessonContainer.lessons.get(0); lesson.taskList = new ArrayList<>(); for (Integer s : lesson.steps) { createTask(lesson, s); } - if (!lesson.taskList.isEmpty()) + if (!lesson.taskList.isEmpty()) { lessons.add(lesson); + } } return lessons; @@ -213,11 +245,11 @@ public class EduStepicConnector { private static void createTask(Lesson lesson, Integer stepicId) throws IOException { final StepicWrappers.StepSource step = getStep(stepicId); final StepicWrappers.Step block = step.block; - if (!block.name.equals(PYCHARM_PREFIX)) return; + if (!block.name.startsWith(PYCHARM_PREFIX)) return; final Task task = new Task(); task.setStepId(stepicId); task.setUpdateDate(step.update_date); - task.setName(block.options != null ? block.options.title : PYCHARM_PREFIX); + task.setName(block.options != null ? block.options.title : (PYCHARM_PREFIX + CURRENT_VERSION)); task.setText(block.text); for (StepicWrappers.TestFileWrapper wrapper : block.options.test) { task.addTestsTexts(wrapper.name, wrapper.text); @@ -233,7 +265,8 @@ public class EduStepicConnector { } public static StepicWrappers.StepSource getStep(Integer step) throws IOException { - return EduStepicClient.getFromStepic(EduStepicNames.STEPS + "/" + String.valueOf(step), StepicWrappers.StepContainer.class).steps.get(0); + return EduStepicClient.getFromStepic(EduStepicNames.STEPS + "/" + String.valueOf(step), StepicWrappers.StepContainer.class).steps + .get(0); } public static void postAttempt(@NotNull final Task task, boolean passed, @NotNull final Project project) { @@ -255,7 +288,8 @@ public class EduStepicConnector { if (statusLine.getStatusCode() != HttpStatus.SC_CREATED) { LOG.warn("Failed to make attempt " + attemptResponseString); } - final StepicWrappers.AttemptWrapper.Attempt attempt = new Gson().fromJson(attemptResponseString, StepicWrappers.AttemptContainer.class).attempts.get(0); + final StepicWrappers.AttemptWrapper.Attempt attempt = + new Gson().fromJson(attemptResponseString, StepicWrappers.AttemptContainer.class).attempts.get(0); final Map taskFiles = task.getTaskFiles(); final ArrayList files = new ArrayList<>(); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java index 01336b30eb05..b357190931e0 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java @@ -1,6 +1,7 @@ package com.jetbrains.edu.learning.stepic; import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; @@ -51,9 +52,14 @@ public class StepicWrappers { @Expose Integer executionMemoryLimit; @Expose Integer executionTimeLimit; @Expose CodeTemplatesWrapper codeTemplates; + @SerializedName("format_version") + @Expose public int formatVersion = 2; + @SerializedName("last_subtask_index") + @Expose int lastSubtaskIndex = 0; public static StepOptions fromTask(final Project project, @NotNull final Task task) { final StepOptions source = new StepOptions(); + task.setLastSubtaskIndex(source.lastSubtaskIndex); setTests(task, source, project); source.files = new ArrayList<>(); source.title = task.getName(); @@ -78,7 +84,7 @@ public class StepicWrappers { } String name = entry.getKey(); VirtualFile answerFile = taskDir.findChild(name); - Pair pair = EduUtils.createStudentFile(StepicWrappers.class, project, answerFile, stepicDir, null); + Pair pair = EduUtils.createStudentFile(StepicWrappers.class, project, answerFile, stepicDir, null, 0); if (pair == null) { return; } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java index 545be6aface7..e9b8b4fc8873 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java @@ -211,12 +211,12 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D LOG.info("Failed to enter editing mode for StudyToolWindow"); return; } - VirtualFile taskTextFile = StudyUtils.findTaskDescriptionVirtualFile(taskDirectory); + VirtualFile taskTextFile = StudyUtils.findTaskDescriptionVirtualFile(project, taskDirectory); enterEditingMode(taskTextFile, project); StudyTaskManager.getInstance(project).setTurnEditingMode(false); } if (taskDirectory != null && StudyTaskManager.getInstance(project).getToolWindowMode() == StudyToolWindowMode.EDITING) { - VirtualFile taskTextFile = StudyUtils.findTaskDescriptionVirtualFile(taskDirectory); + VirtualFile taskTextFile = StudyUtils.findTaskDescriptionVirtualFile(project, taskDirectory); enterEditingMode(taskTextFile, project); } else { diff --git a/python/educational-core/student/student.iml b/python/educational-core/student/student.iml index 830451861a0f..74bb142082e8 100644 --- a/python/educational-core/student/student.iml +++ b/python/educational-core/student/student.iml @@ -5,6 +5,7 @@ + @@ -24,5 +25,16 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/python/educational-core/student/testData/migration/3.xml b/python/educational-core/student/testData/migration/3.xml new file mode 100644 index 000000000000..b347959a91f0 --- /dev/null +++ b/python/educational-core/student/testData/migration/3.xml @@ -0,0 +1,110 @@ + + + + + + + + \ No newline at end of file diff --git a/python/educational-core/student/testData/migration/4.xml b/python/educational-core/student/testData/migration/4.xml new file mode 100644 index 000000000000..944ffd640b2e --- /dev/null +++ b/python/educational-core/student/testData/migration/4.xml @@ -0,0 +1,136 @@ + + + + + + + + \ No newline at end of file diff --git a/python/educational-core/student/testData/stepic/1.json b/python/educational-core/student/testData/stepic/1.json new file mode 100644 index 000000000000..9a40915cf279 --- /dev/null +++ b/python/educational-core/student/testData/stepic/1.json @@ -0,0 +1,72 @@ +{ + "meta": { + "page": 1, + "has_next": false, + "has_previous": false + }, + "steps": [ + { + "id": 98626, + "lesson": 13416, + "position": 1, + "status": "ready", + "block": { + "name": "pycharm", + "text": "\nTraditionally the first program you write in any programming language is \"Hello World!\".\n

    \nIntroduce yourself to the World.\n
    \n", + "video": null, + "animation": null, + "options": { + "test": [ + { + "text": "from test_helper import run_common_tests, failed, passed, get_answer_placeholders\n\n\ndef test_ASCII():\n windows = get_answer_placeholders()\n for window in windows:\n all_ascii = all(ord(c) < 128 for c in window)\n if not all_ascii:\n failed(\"Please use only English characters this time.\")\n return\n passed()\n\n\ndef test_is_alpha():\n window = get_answer_placeholders()[0]\n is_multiline = window.find(\"\\n\")\n if is_multiline != -1:\n window = window[:is_multiline-1]\n splitted = window.split()\n for s in splitted:\n if not s.isalpha():\n failed(\"Please use only English characters this time.\")\n return\n\n passed()\n\n\nif __name__ == '__main__':\n test_ASCII()\n run_common_tests(\"You should enter your name\")\n test_is_alpha()\n\n\n", + "name": "tests.py" + } + ], + "files": [ + { + "placeholders": [ + { + "hint": "[\"Type your name here.\"]", + "start": 32, + "length": 14, + "possible_answer": "Liana", + "line": -1 + } + ], + "text": "print(\"Hello, world! My name is type your name\")\n", + "name": "hello_world.py" + } + ], + "title": "Our first program" + }, + "subtitle_files": [] + }, + "actions": { + "submit": "#" + }, + "progress": "77-98626", + "subscriptions": [ + "31-77-98626", + "30-77-98626" + ], + "instruction": null, + "session": null, + "instruction_type": null, + "viewed_by": 46, + "passed_by": 2433, + "correct_ratio": 0.7805243445692884, + "worth": null, + "is_solutions_unlocked": false, + "solutions_unlocked_attempts": 3, + "has_submissions_restrictions": false, + "max_submissions_count": 3, + "create_date": "2016-04-11T15:25:39Z", + "update_date": "2016-08-03T11:05:13Z", + "discussions_count": 0, + "discussion_proxy": "77-98626-1", + "discussion_threads": [ + "77-98626-1" + ] + } + ] +} \ No newline at end of file diff --git a/python/educational-core/student/testData/stepic/2.json b/python/educational-core/student/testData/stepic/2.json new file mode 100644 index 000000000000..ad4485b852b8 --- /dev/null +++ b/python/educational-core/student/testData/stepic/2.json @@ -0,0 +1,78 @@ +{ + "meta": { + "page": 1, + "has_next": false, + "has_previous": false + }, + "steps": [ + { + "id": 98626, + "lesson": 13416, + "position": 1, + "status": "ready", + "block": { + "name": "pycharm", + "text": "\nTraditionally the first program you write in any programming language is \"Hello World!\".\n

    \nIntroduce yourself to the World.\n
    \n", + "video": null, + "animation": null, + "options": { + "test": [ + { + "text": "from test_helper import run_common_tests, failed, passed, get_answer_placeholders\n\n\ndef test_ASCII():\n windows = get_answer_placeholders()\n for window in windows:\n all_ascii = all(ord(c) < 128 for c in window)\n if not all_ascii:\n failed(\"Please use only English characters this time.\")\n return\n passed()\n\n\ndef test_is_alpha():\n window = get_answer_placeholders()[0]\n is_multiline = window.find(\"\\n\")\n if is_multiline != -1:\n window = window[:is_multiline-1]\n splitted = window.split()\n for s in splitted:\n if not s.isalpha():\n failed(\"Please use only English characters this time.\")\n return\n\n passed()\n\n\nif __name__ == '__main__':\n test_ASCII()\n run_common_tests(\"You should enter your name\")\n test_is_alpha()\n\n\n", + "name": "tests.py" + } + ], + "files": [ + { + "placeholders": [ + { + "hint": "[\"Type your name here.\"]", + "offset": 32, + "length": 14, + "subtask_infos": [{ + "index" : 0, + "hints": ["Type your name here."], + "possible_answer": "Liana" + } + ] + } + ], + "text": "print(\"Hello, world! My name is type your name\")\n", + "name": "hello_world.py" + } + ], + "title": "Our first program", + "format_version": 2, + "last_subtask_index": 0 + }, + "subtitle_files": [] + }, + "actions": { + "submit": "#" + }, + "progress": "77-98626", + "subscriptions": [ + "31-77-98626", + "30-77-98626" + ], + "instruction": null, + "session": null, + "instruction_type": null, + "viewed_by": 46, + "passed_by": 2433, + "correct_ratio": 0.7805243445692884, + "worth": null, + "is_solutions_unlocked": false, + "solutions_unlocked_attempts": 3, + "has_submissions_restrictions": false, + "max_submissions_count": 3, + "create_date": "2016-04-11T15:25:39Z", + "update_date": "2016-08-03T11:05:13Z", + "discussions_count": 0, + "discussion_proxy": "77-98626-1", + "discussion_threads": [ + "77-98626-1" + ] + } + ] +} \ No newline at end of file diff --git a/python/educational-core/student/testData/stepic/3.json b/python/educational-core/student/testData/stepic/3.json new file mode 100644 index 000000000000..147620c7efef --- /dev/null +++ b/python/educational-core/student/testData/stepic/3.json @@ -0,0 +1,86 @@ +{ + "meta": { + "page": 1, + "has_next": false, + "has_previous": false + }, + "steps": [ + { + "id": 98626, + "lesson": 13416, + "position": 1, + "status": "ready", + "block": { + "name": "pycharm", + "text": "\nTraditionally the first program you write in any programming language is \"Hello World!\".\n

    \nIntroduce yourself to the World.\n
    \n", + "video": null, + "animation": null, + "options": { + "test": [ + { + "text": "from test_helper import run_common_tests, failed, passed, get_answer_placeholders\n\n\ndef test_ASCII():\n windows = get_answer_placeholders()\n for window in windows:\n all_ascii = all(ord(c) < 128 for c in window)\n if not all_ascii:\n failed(\"Please use only English characters this time.\")\n return\n passed()\n\n\ndef test_is_alpha():\n window = get_answer_placeholders()[0]\n is_multiline = window.find(\"\\n\")\n if is_multiline != -1:\n window = window[:is_multiline-1]\n splitted = window.split()\n for s in splitted:\n if not s.isalpha():\n failed(\"Please use only English characters this time.\")\n return\n\n passed()\n\n\nif __name__ == '__main__':\n test_ASCII()\n run_common_tests(\"You should enter your name\")\n test_is_alpha()\n\n\n", + "name": "tests.py" + } + ], + "files": [ + { + "placeholders": [ + { + "hint": "[\"Type your name here.\"]", + "offset": 32, + "length": 14, + "subtask_infos": [ + { + "index": 0, + "hints": [ + "Type your name here." + ], + "possible_answer": "Liana" + }, + { + "index": 1, + "hints": [], + "possible_answer": "miss X" + } + ] + } + ], + "text": "print(\"Hello, world! My name is type your name\")\n", + "name": "hello_world.py" + } + ], + "title": "Our first program", + "format_version": 2, + "last_subtask_index": 1 + }, + "subtitle_files": [] + }, + "actions": { + "submit": "#" + }, + "progress": "77-98626", + "subscriptions": [ + "31-77-98626", + "30-77-98626" + ], + "instruction": null, + "session": null, + "instruction_type": null, + "viewed_by": 46, + "passed_by": 2433, + "correct_ratio": 0.7805243445692884, + "worth": null, + "is_solutions_unlocked": false, + "solutions_unlocked_attempts": 3, + "has_submissions_restrictions": false, + "max_submissions_count": 3, + "create_date": "2016-04-11T15:25:39Z", + "update_date": "2016-08-03T11:05:13Z", + "discussions_count": 0, + "discussion_proxy": "77-98626-1", + "discussion_threads": [ + "77-98626-1" + ] + } + ] +} \ No newline at end of file diff --git a/python/educational-core/student/testData/stepic/courses.json b/python/educational-core/student/testData/stepic/courses.json new file mode 100644 index 000000000000..e4ab45223869 --- /dev/null +++ b/python/educational-core/student/testData/stepic/courses.json @@ -0,0 +1,330 @@ +{ + "meta": { + "page": 1, + "has_next": false, + "has_previous": false + }, + "courses": [ + { + "id": 568, + "summary": "Adaptive problem set to learn Python. \r\nJoin the course and you can try out the first prototype of the adaptive engine! ", + "workload": "", + "cover": "/media/covers/py-en.png", + "intro": "", + "course_format": "Adaptive course with only programming assignments for Python", + "target_audience": "", + "certificate_footer": null, + "certificate_cover_org": null, + "is_certificate_auto_issued": false, + "certificate_regular_threshold": null, + "certificate_distinction_threshold": null, + "instructors": [], + "certificate": "", + "requirements": "

    \r\n\r\nThe desire to test your knowledge of the Python language. 

    ", + "description": "

    The course consists of few hundreds of programming assignments for Python, ranging from basics up to complex topics.

    \n

    \n\nRight now Stepik.org is developing an adaptive learning engine which chooses content for each learner individually \u2013 by his/her level and knowledge gaps. In this course you can try out the first prototype of this engine\n\n

    Learn more \u2014 in the https://stepic.zendesk.com/hc/en-us/articles/210007905\ufeff.

    ", + "sections": [ + 1785, + 1786 + ], + "total_units": 347, + "enrollment": null, + "is_favorite": false, + "actions": {}, + "progress": null, + "certificate_link": null, + "certificate_regular_link": null, + "certificate_distinction_link": null, + "schedule_link": null, + "schedule_long_link": null, + "first_deadline": null, + "last_deadline": null, + "subscriptions": [ + "31-78-568", + "30-78-568" + ], + "announcements": [], + "is_contest": false, + "is_adaptive": true, + "is_idea_compatible": true, + "last_step": "78-568", + "intro_video": null, + "social_providers": [], + "authors": [ + 17813950 + ], + "tags": [ + 147 + ], + "has_tutors": false, + "is_enabled": true, + "review_summary": 295, + "owner": 17813950, + "language": "en", + "is_featured": true, + "is_public": true, + "title": "Adaptive Python", + "slug": "Adaptive-Python-568", + "begin_date": null, + "end_date": null, + "soft_deadline": null, + "hard_deadline": null, + "grading_policy": "halved", + "begin_date_source": null, + "end_date_source": null, + "soft_deadline_source": null, + "hard_deadline_source": null, + "grading_policy_source": "halved", + "is_active": true, + "create_date": "2016-03-17T13:57:04Z", + "update_date": "2016-10-20T12:25:01Z", + "learners_group": null, + "testers_group": null, + "moderators_group": null, + "teachers_group": null, + "admins_group": null, + "discussions_count": 0, + "discussion_proxy": null, + "discussion_threads": [] + }, + { + "id": 238, + "summary": "Introduction course to Python", + "workload": "", + "cover": null, + "intro": "", + "course_format": "pycharm Python", + "target_audience": "", + "certificate_footer": null, + "certificate_cover_org": null, + "is_certificate_auto_issued": false, + "certificate_regular_threshold": 0, + "certificate_distinction_threshold": 0, + "instructors": [ + 1794841 + ], + "certificate": "", + "requirements": "", + "description": "", + "sections": [ + 662 + ], + "total_units": 10, + "enrollment": null, + "is_favorite": false, + "actions": {}, + "progress": null, + "certificate_link": null, + "certificate_regular_link": null, + "certificate_distinction_link": null, + "schedule_link": null, + "schedule_long_link": null, + "first_deadline": null, + "last_deadline": null, + "subscriptions": [ + "31-78-238", + "30-78-238" + ], + "announcements": [], + "is_contest": false, + "is_adaptive": false, + "is_idea_compatible": true, + "last_step": "78-238", + "intro_video": null, + "social_providers": [], + "authors": [ + 625832 + ], + "tags": [], + "has_tutors": false, + "is_enabled": true, + "review_summary": 135, + "owner": 625832, + "language": "en", + "is_featured": false, + "is_public": true, + "title": "Introduction to Python", + "slug": "Introduction-to-Python-238", + "begin_date": null, + "end_date": null, + "soft_deadline": null, + "hard_deadline": null, + "grading_policy": "halved", + "begin_date_source": null, + "end_date_source": null, + "soft_deadline_source": null, + "hard_deadline_source": null, + "grading_policy_source": "halved", + "is_active": true, + "create_date": "2015-08-25T08:57:04Z", + "update_date": "2016-08-10T13:14:02Z", + "learners_group": null, + "testers_group": null, + "moderators_group": null, + "teachers_group": null, + "admins_group": null, + "discussions_count": 0, + "discussion_proxy": null, + "discussion_threads": [] + }, + { + "id": 163, + "summary": "Test PyCharm course in 2 format", + "workload": "", + "cover": null, + "intro": "", + "course_format": "pycharm2 Python", + "target_audience": "", + "certificate_footer": null, + "certificate_cover_org": null, + "is_certificate_auto_issued": false, + "certificate_regular_threshold": 0, + "certificate_distinction_threshold": 0, + "instructors": [ + 1777801 + ], + "certificate": "", + "requirements": "", + "description": "", + "sections": [ + 460 + ], + "total_units": 6, + "enrollment": null, + "is_favorite": false, + "actions": {}, + "progress": null, + "certificate_link": null, + "certificate_regular_link": null, + "certificate_distinction_link": null, + "schedule_link": null, + "schedule_long_link": null, + "first_deadline": null, + "last_deadline": null, + "subscriptions": [ + "31-78-163", + "30-78-163" + ], + "announcements": [], + "is_contest": false, + "is_adaptive": false, + "is_idea_compatible": true, + "last_step": "78-163", + "intro_video": null, + "social_providers": [], + "authors": [ + 625832 + ], + "tags": [], + "has_tutors": false, + "is_enabled": true, + "review_summary": 113, + "owner": 625832, + "language": "en", + "is_featured": false, + "is_public": true, + "title": "format2", + "slug": "Introduction-to-Classic-Ciphers-163", + "begin_date": null, + "end_date": null, + "soft_deadline": null, + "hard_deadline": null, + "grading_policy": "halved", + "begin_date_source": null, + "end_date_source": null, + "soft_deadline_source": null, + "hard_deadline_source": null, + "grading_policy_source": "halved", + "is_active": true, + "create_date": "2015-07-01T17:07:48Z", + "update_date": "2016-08-05T09:26:50Z", + "learners_group": null, + "testers_group": null, + "moderators_group": null, + "teachers_group": null, + "admins_group": null, + "discussions_count": 0, + "discussion_proxy": null, + "discussion_threads": [] + }, + { + "id": 162, + "summary": "Test Pycharm future format", + "workload": "", + "cover": null, + "intro": "", + "course_format": "pycharm3 Python", + "target_audience": "", + "certificate_footer": null, + "certificate_cover_org": null, + "is_certificate_auto_issued": false, + "certificate_regular_threshold": 0, + "certificate_distinction_threshold": 0, + "instructors": [], + "certificate": "", + "requirements": "", + "description": "", + "sections": [ + 459, + 2510 + ], + "total_units": 9, + "enrollment": null, + "is_favorite": false, + "actions": {}, + "progress": null, + "certificate_link": null, + "certificate_regular_link": null, + "certificate_distinction_link": null, + "schedule_link": null, + "schedule_long_link": null, + "first_deadline": null, + "last_deadline": null, + "subscriptions": [ + "31-78-162", + "30-78-162" + ], + "announcements": [], + "is_contest": false, + "is_adaptive": false, + "is_idea_compatible": true, + "last_step": "78-162", + "intro_video": null, + "social_providers": [], + "authors": [ + 625832 + ], + "tags": [], + "has_tutors": false, + "is_enabled": true, + "review_summary": 112, + "owner": 625832, + "language": "en", + "is_featured": false, + "is_public": true, + "title": "format3", + "slug": "Logging-in-Python-162", + "begin_date": null, + "end_date": null, + "soft_deadline": null, + "hard_deadline": null, + "grading_policy": "halved", + "begin_date_source": null, + "end_date_source": null, + "soft_deadline_source": null, + "hard_deadline_source": null, + "grading_policy_source": "halved", + "is_active": true, + "create_date": "2015-07-01T16:21:09Z", + "update_date": "2016-09-07T15:22:29Z", + "learners_group": null, + "testers_group": null, + "moderators_group": null, + "teachers_group": null, + "admins_group": null, + "discussions_count": 0, + "discussion_proxy": null, + "discussion_threads": [] + } + ], + "enrollments": [] +} \ No newline at end of file diff --git a/python/educational-core/student/testData/stepic/placeholder.json b/python/educational-core/student/testData/stepic/placeholder.json new file mode 100644 index 000000000000..0c4799bdd165 --- /dev/null +++ b/python/educational-core/student/testData/stepic/placeholder.json @@ -0,0 +1,28 @@ +{ + "offset": 1, + "length": 10, + "subtask_infos": [ + { + "hints": [ + "hint 1", + "hint 2" + ], + "possible_answer": "answer1", + "placeholder_text": "type here", + "has_frame": true, + "need_insert_text": true, + "index": 0 + }, + { + "hints": [ + "hint 11", + "hint 22" + ], + "possible_answer": "answer2", + "placeholder_text": "type here1", + "has_frame": true, + "need_insert_text": true, + "index": 1 + } + ] +} \ No newline at end of file diff --git a/python/educational-core/student/testSrc/com/jetbrains/edu/learning/StudyMigrationTest.java b/python/educational-core/student/testSrc/com/jetbrains/edu/learning/StudyMigrationTest.java new file mode 100644 index 000000000000..2d21fd58ed8d --- /dev/null +++ b/python/educational-core/student/testSrc/com/jetbrains/edu/learning/StudyMigrationTest.java @@ -0,0 +1,26 @@ +package com.jetbrains.edu.learning; + +import com.intellij.openapi.util.JDOMUtil; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.testFramework.PlatformTestUtil; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +public class StudyMigrationTest { + + @Test + public void testFromThirdToForth() throws JDOMException, IOException, StudySerializationUtils.StudyUnrecognizedFormatException { + Element element = JDOMUtil.load(new File(FileUtil.join(getTestDataPath(), "3.xml"))); + Element actual = StudySerializationUtils.Xml.convertToForthVersion(element); + Element expected = JDOMUtil.load(new File(FileUtil.join(getTestDataPath()), "4.xml")); + PlatformTestUtil.assertElementsEqual(expected, actual); + } + + protected String getTestDataPath() { + return FileUtil.join(PlatformTestUtil.getCommunityPath(), "python/educational-core/student/testData/migration"); + } +} diff --git a/python/educational-core/student/testSrc/com/jetbrains/edu/learning/stepic/StudyStepicFormatTest.java b/python/educational-core/student/testSrc/com/jetbrains/edu/learning/stepic/StudyStepicFormatTest.java new file mode 100644 index 000000000000..f333d28933c5 --- /dev/null +++ b/python/educational-core/student/testSrc/com/jetbrains/edu/learning/stepic/StudyStepicFormatTest.java @@ -0,0 +1,101 @@ +package com.jetbrains.edu.learning.stepic; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.testFramework.PlatformTestUtil; +import com.intellij.util.containers.ContainerUtil; +import com.jetbrains.edu.learning.StudySerializationUtils; +import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; +import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholderSubtaskInfo; +import com.jetbrains.edu.learning.courseFormat.TaskFile; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.*; + + +public class StudyStepicFormatTest { + + @Test + public void fromFirstVersion() throws IOException { + doStepOptionsCreationTest("1.json"); + } + + @Test + public void fromSecondVersion() throws IOException { + doStepOptionsCreationTest("2.json"); + } + + @Test + public void testWithSubtasks() throws IOException { + StepicWrappers.StepOptions stepOptions = doStepOptionsCreationTest("3.json"); + assertEquals(1, stepOptions.lastSubtaskIndex); + } + + + private static StepicWrappers.StepOptions doStepOptionsCreationTest(String fileName) throws IOException { + String responseString = + FileUtil.loadFile(new File(getTestDataPath(), fileName)); + StepicWrappers.StepSource stepSource = + EduStepicClient.deserializeStepicResponse(StepicWrappers.StepContainer.class, responseString).steps.get(0); + StepicWrappers.StepOptions options = stepSource.block.options; + List files = options.files; + assertTrue("Wrong number of task files", files.size() == 1); + List placeholders = files.get(0).getAnswerPlaceholders(); + assertTrue("Wrong number of placeholders", placeholders.size() == 1); + Map infos = placeholders.get(0).getSubtaskInfos(); + assertNotNull(infos); + assertEquals(Collections.singletonList("Type your name here."), infos.get(0).getHints()); + assertEquals("Liana", infos.get(0).getPossibleAnswer()); + return options; + } + + @Test + public void testAvailableCourses() throws IOException { + String responseString = FileUtil.loadFile(new File(getTestDataPath(), "courses.json")); + StepicWrappers.CoursesContainer container = + EduStepicClient.deserializeStepicResponse(StepicWrappers.CoursesContainer.class, responseString); + assertNotNull(container.courses); + assertTrue("Incorrect number of courses", container.courses.size() == 4); + List filtered = ContainerUtil.filter(container.courses, info -> EduStepicConnector.canBeOpened(info)); + assertEquals(ContainerUtil.newArrayList("Adaptive Python", "Introduction to Python", "format2"), + ContainerUtil.map(filtered, CourseInfo::getName)); + } + + @Test + public void placeholderSerialization() throws IOException { + final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation(). + registerTypeAdapter(AnswerPlaceholder.class, new StudySerializationUtils.Json.StepicAnswerPlaceholderAdapter()).create(); + AnswerPlaceholder answerPlaceholder = new AnswerPlaceholder(); + answerPlaceholder.setOffset(1); + answerPlaceholder.setLength(10); + AnswerPlaceholderSubtaskInfo info1 = createSubtaskInfo("type here", "answer1", ContainerUtil.list("hint 1", "hint 2")); + AnswerPlaceholderSubtaskInfo info2 = createSubtaskInfo("type here1", "answer2", ContainerUtil.list("hint 11", "hint 22")); + answerPlaceholder.setSubtaskInfos(ContainerUtil.newHashMap(ContainerUtil.list(0, 1), ContainerUtil.list(info1, info2))); + final String placeholderSerialization = gson.toJson(answerPlaceholder); + String expected = FileUtil.loadFile(new File(getTestDataPath(), "placeholder.json")); + assertEquals(expected, placeholderSerialization); + + } + + private static AnswerPlaceholderSubtaskInfo createSubtaskInfo(String placeholderText, String possibleAnswer, List hints) { + AnswerPlaceholderSubtaskInfo info = new AnswerPlaceholderSubtaskInfo(); + info.setPlaceholderText(placeholderText); + info.setPossibleAnswer(possibleAnswer); + info.setHints(hints); + info.setNeedInsertText(true); + return info; + } + + @NotNull + private static String getTestDataPath() { + return FileUtil.join(PlatformTestUtil.getCommunityPath(), "python/educational-core/student/testData/stepic"); + } +} diff --git a/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/PyCCLanguageManager.java b/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/PyCCLanguageManager.java index 93e6abfcc5fb..fd731c7cbd59 100644 --- a/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/PyCCLanguageManager.java +++ b/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/PyCCLanguageManager.java @@ -2,15 +2,25 @@ package com.jetbrains.edu.coursecreator; import com.intellij.ide.fileTemplates.FileTemplate; import com.intellij.ide.fileTemplates.FileTemplateManager; +import com.intellij.ide.fileTemplates.FileTemplateUtil; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiManager; import com.jetbrains.edu.learning.core.EduNames; +import com.jetbrains.edu.learning.courseFormat.Task; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; +import java.io.IOException; public class PyCCLanguageManager implements CCLanguageManager { + private static final Logger LOG = Logger.getInstance(PyCCLanguageManager.class); @Nullable @Override @@ -45,6 +55,44 @@ public class PyCCLanguageManager implements CCLanguageManager { @Override public boolean isTestFile(VirtualFile file) { - return EduNames.TESTS_FILE.equals(file.getName()); + String name = file.getName(); + if (EduNames.TESTS_FILE.equals(name)) { + return true; + } + return name.contains(FileUtil.getNameWithoutExtension(EduNames.TESTS_FILE)) && name.contains(EduNames.SUBTASK_MARKER); } -} + + @Override + public void createTestsForNewSubtask(@NotNull Project project, @NotNull Task task) { + VirtualFile taskDir = task.getTaskDir(project); + if (taskDir == null) { + return; + } + int nextSubtaskIndex = task.getLastSubtaskIndex() + 1; + String nextSubtaskTestsFileName = getSubtaskTestsFileName(nextSubtaskIndex); + ApplicationManager.getApplication().runWriteAction(() -> { + try { + PsiDirectory taskPsiDir = PsiManager.getInstance(project).findDirectory(taskDir); + FileTemplate testsTemplate = getTestsTemplate(project); + if (taskPsiDir == null || testsTemplate == null) { + return; + } + FileTemplateUtil.createFromTemplate(testsTemplate, nextSubtaskTestsFileName, null, taskPsiDir); + } + catch (IOException e) { + LOG.error(e); + } + catch (Exception e) { + LOG.error(e); + } + }); + } + + @NotNull + public static String getSubtaskTestsFileName(int index) { + return index == 0 ? EduNames.TESTS_FILE : FileUtil.getNameWithoutExtension(EduNames.TESTS_FILE) + + EduNames.SUBTASK_MARKER + + index + "." + + FileUtilRt.getExtension(EduNames.TESTS_FILE); + } +} \ No newline at end of file diff --git a/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCCommandLineState.java b/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCCommandLineState.java index 3b1104243c6f..8185e689834c 100644 --- a/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCCommandLineState.java +++ b/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCCommandLineState.java @@ -72,7 +72,7 @@ public class PyCCCommandLineState extends PythonCommandLineState { private String getFirstTaskFilePath() { for (Map.Entry entry : myTask.getTaskFiles().entrySet()) { String path = getTaskFilePath(entry.getKey()); - if (!entry.getValue().getAnswerPlaceholders().isEmpty()) { + if (!entry.getValue().getActivePlaceholders().isEmpty()) { return path; } VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(path); diff --git a/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationProducer.java b/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationProducer.java index f58104fb37e2..c61ecd00d982 100644 --- a/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationProducer.java +++ b/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationProducer.java @@ -10,6 +10,7 @@ import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.jetbrains.edu.coursecreator.CCUtils; +import com.jetbrains.edu.coursecreator.PyCCLanguageManager; import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.Task; @@ -59,7 +60,12 @@ public class PyCCRunTestsConfigurationProducer extends RunConfigurationProducer< if (task == null) { return null; } - return task.getLesson().getName() + "/" + task.getName(); + String generatedName = task.getLesson().getName() + "/" + task.getName(); + if (task.hasSubtasks()) { + int index = task.getActiveSubtaskIndex() + 1; + generatedName += " " + index; + } + return generatedName; } @Nullable @@ -81,7 +87,7 @@ public class PyCCRunTestsConfigurationProducer extends RunConfigurationProducer< if (task == null) { return null; } - String testsFileName = EduNames.TESTS_FILE; + String testsFileName = PyCCLanguageManager.getSubtaskTestsFileName(task.getActiveSubtaskIndex()); String taskDirPath = FileUtil.toSystemDependentName(taskDir.getPath()); String testsPath = taskDir.findChild(EduNames.SRC) != null ? FileUtil.join(taskDirPath, EduNames.SRC, testsFileName) : diff --git a/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java b/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java index 9bea1b5a731a..9adf69d88376 100644 --- a/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java +++ b/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java @@ -101,7 +101,7 @@ public class PyStudyCheckAction extends StudyCheckAction { for (Map.Entry entry : myTask.getTaskFiles().entrySet()) { final String name = entry.getKey(); final TaskFile taskFile = entry.getValue(); - if (taskFile.getAnswerPlaceholders().size() < 2) { + if (taskFile.getActivePlaceholders().size() < 2) { continue; } final Course course = myTaskManger.getCourse(); @@ -141,7 +141,7 @@ public class PyStudyCheckAction extends StudyCheckAction { TaskFile taskFile = entry.getValue(); VirtualFile virtualFile = taskDir.findChild(name); if (virtualFile != null) { - if (!taskFile.getAnswerPlaceholders().isEmpty()) { + if (!taskFile.getActivePlaceholders().isEmpty()) { taskVirtualFile = virtualFile; } } diff --git a/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyInterpreterInspection.java b/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyInterpreterInspection.java index 5d8d2458c9db..5f14689e6268 100644 --- a/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyInterpreterInspection.java +++ b/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyInterpreterInspection.java @@ -19,7 +19,6 @@ import com.intellij.codeInspection.LocalInspectionToolSession; import com.intellij.codeInspection.LocalQuickFix; import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.codeInspection.ProblemsHolder; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.options.ShowSettingsUtil; @@ -113,12 +112,14 @@ public class PyStudyInterpreterInspection extends PyInspection { return "Configure Python Interpreter"; } + @Override + public boolean startInWriteAction() { + return false; + } + @Override public void applyFix(@NotNull final Project project, @NotNull ProblemDescriptor descriptor) { - ApplicationManager.getApplication().invokeLater(() -> { - // outside of read action - ShowSettingsUtil.getInstance().showSettingsDialog(project, "Project Interpreter"); - }); + ShowSettingsUtil.getInstance().showSettingsDialog(project, "Project Interpreter"); } } } diff --git a/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyTestRunner.java b/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyTestRunner.java index f7c64d1099f1..e3cf99aecb0f 100644 --- a/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyTestRunner.java +++ b/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyTestRunner.java @@ -7,8 +7,10 @@ import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.vfs.VirtualFile; import com.jetbrains.edu.learning.checker.StudyTestRunner; +import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.Task; import com.jetbrains.python.sdk.PythonSdkType; @@ -35,6 +37,11 @@ public class PyStudyTestRunner extends StudyTestRunner { } String testsFileName = manager.getTestFileName(); + if (myTask.hasSubtasks() && myTask.getActiveSubtaskIndex() != 0) { + testsFileName = FileUtil.getNameWithoutExtension(testsFileName); + int index = myTask.getActiveSubtaskIndex(); + testsFileName += EduNames.SUBTASK_MARKER + index + "." + FileUtilRt.getExtension(manager.getTestFileName()); + } final File testRunner = new File(myTaskDir.getPath(), testsFileName); final GeneralCommandLine commandLine = new GeneralCommandLine(); commandLine.withWorkDirectory(myTaskDir.getPath()); diff --git a/python/helpers/pycharm_generator_utils/constants.py b/python/helpers/pycharm_generator_utils/constants.py index 68aa57ff7fea..1d6416b90fcd 100644 --- a/python/helpers/pycharm_generator_utils/constants.py +++ b/python/helpers/pycharm_generator_utils/constants.py @@ -6,7 +6,7 @@ import string import time # !!! Don't forget to update VERSION and required_gen_version if necessary !!! -VERSION = "1.144" +VERSION = "1.145" OUT_ENCODING = 'utf-8' @@ -765,6 +765,7 @@ KNOWN_DECORATORS = { ("bytearray", "maketrans"): "staticmethod", ("bytes", "maketrans"): "staticmethod", ("int", "from_bytes"): "classmethod", + ("float", "fromhex"): "staticmethod", } classobj_txt = ( #TODO: user-skeleton diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py index e04eb5f22942..7815566bdcb8 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py @@ -379,7 +379,7 @@ def change_attr_expression(thread_id, frame_id, attr, expression, dbg, value=SEN try: expression = expression.replace('@LINE@', '\n') - if dbg.plugin and value is not SENTINEL_VALUE: + if dbg.plugin and value is SENTINEL_VALUE: result = dbg.plugin.change_variable(frame, attr, expression) if result: return result diff --git a/python/helpers/pydev/pydevd_concurrency_analyser/pydevd_concurrency_logger.py b/python/helpers/pydev/pydevd_concurrency_analyser/pydevd_concurrency_logger.py index 6fe9fe06a72a..b3cc4ca9a2fe 100644 --- a/python/helpers/pydev/pydevd_concurrency_analyser/pydevd_concurrency_logger.py +++ b/python/helpers/pydev/pydevd_concurrency_analyser/pydevd_concurrency_logger.py @@ -1,8 +1,7 @@ -import time from pydevd_concurrency_analyser.pydevd_thread_wrappers import ObjectWrapper, wrap_attr import pydevd_file_utils -from _pydevd_bundle import pydevd_vars +from _pydevd_bundle import pydevd_xml from _pydev_bundle._pydev_filesystem_encoding import getfilesystemencoding from _pydevd_bundle.pydevd_constants import dict_contains, get_thread_id, IS_PY3K @@ -72,10 +71,10 @@ def get_text_list_for_frame(frame): #print "line is ", myLine #the variables are all gotten 'on-demand' - #variables = pydevd_vars.frame_vars_to_xml(curFrame.f_locals) + #variables = pydevd_xml.frame_vars_to_xml(curFrame.f_locals) variables = '' - cmdTextList.append('' % (quote(myFile, '/>_= \t'), myLine)) cmdTextList.append(variables) cmdTextList.append("") @@ -91,17 +90,17 @@ def send_message(event_class, time, name, thread_id, type, event, file, line, fr cmdTextList = [''] cmdTextList.append('<' + event_class) - cmdTextList.append(' time="%s"' % pydevd_vars.make_valid_xml_value(str(time))) - cmdTextList.append(' name="%s"' % pydevd_vars.make_valid_xml_value(name)) - cmdTextList.append(' thread_id="%s"' % pydevd_vars.make_valid_xml_value(thread_id)) - cmdTextList.append(' type="%s"' % pydevd_vars.make_valid_xml_value(type)) + cmdTextList.append(' time="%s"' % pydevd_xml.make_valid_xml_value(str(time))) + cmdTextList.append(' name="%s"' % pydevd_xml.make_valid_xml_value(name)) + cmdTextList.append(' thread_id="%s"' % pydevd_xml.make_valid_xml_value(thread_id)) + cmdTextList.append(' type="%s"' % pydevd_xml.make_valid_xml_value(type)) if type == "lock": - cmdTextList.append(' lock_id="%s"' % pydevd_vars.make_valid_xml_value(str(lock_id))) + cmdTextList.append(' lock_id="%s"' % pydevd_xml.make_valid_xml_value(str(lock_id))) if parent is not None: - cmdTextList.append(' parent="%s"' % pydevd_vars.make_valid_xml_value(parent)) - cmdTextList.append(' event="%s"' % pydevd_vars.make_valid_xml_value(event)) - cmdTextList.append(' file="%s"' % pydevd_vars.make_valid_xml_value(file)) - cmdTextList.append(' line="%s"' % pydevd_vars.make_valid_xml_value(str(line))) + cmdTextList.append(' parent="%s"' % pydevd_xml.make_valid_xml_value(parent)) + cmdTextList.append(' event="%s"' % pydevd_xml.make_valid_xml_value(event)) + cmdTextList.append(' file="%s"' % pydevd_xml.make_valid_xml_value(file)) + cmdTextList.append(' line="%s"' % pydevd_xml.make_valid_xml_value(str(line))) cmdTextList.append('>') cmdTextList += get_text_list_for_frame(frame) diff --git a/python/helpers/python-skeletons/__builtin__.py b/python/helpers/python-skeletons/__builtin__.py index 9d79c37be4c8..5276f485f2b3 100644 --- a/python/helpers/python-skeletons/__builtin__.py +++ b/python/helpers/python-skeletons/__builtin__.py @@ -209,6 +209,16 @@ def map(function, sequence, *sequence_1): pass +def min(*args, key=None, default=None): + """Return the smallest item in an iterable or the smallest of two or more + arguments. + + :type args: tuple[T] + :rtype: T + """ + pass + + def next(iterator, default=None): """Return the next item from the iterator. @@ -1072,7 +1082,7 @@ class float(object): return 0.0 @staticmethod - def fromhex(cls, string): + def fromhex(string): """Create a floating-point number from a hexadecimal string. :type string: str diff --git a/python/helpers/python-skeletons/builtins.py b/python/helpers/python-skeletons/builtins.py index 73cd84c7eee7..07a50074679a 100644 --- a/python/helpers/python-skeletons/builtins.py +++ b/python/helpers/python-skeletons/builtins.py @@ -1,6 +1,9 @@ """Skeletons for Python 3 built-in symbols.""" +import os + + def abs(number): """Return the absolute value of the argument. @@ -222,7 +225,8 @@ def min(*args, key=None, default=None): """Return the smallest item in an iterable or the smallest of two or more arguments. - :rtype: object | unknown + :type args: tuple[T] + :rtype: T """ pass @@ -249,7 +253,7 @@ def open(name, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=None, opener=None): """Open a file, returns a file object. - :type name: str + :type name: str | os.PathLike :type mode: str :type buffering: numbers.Integral :type encoding: str | None @@ -805,7 +809,7 @@ class float(object): return 0.0 @staticmethod - def fromhex(cls, string): + def fromhex(string): """Create a floating-point number from a hexadecimal string. :type string: str diff --git a/python/helpers/python-skeletons/os/__init__.py b/python/helpers/python-skeletons/os/__init__.py index 3d8867d67d98..058383bad3db 100644 --- a/python/helpers/python-skeletons/os/__init__.py +++ b/python/helpers/python-skeletons/os/__init__.py @@ -2,6 +2,9 @@ from __future__ import unicode_literals +import io +import os +import subprocess import sys @@ -311,11 +314,41 @@ def popen(command, mode='r', bufsize=-1): :type command: string :type mode: string :type bufsize: int - :rtype: io.FileIO[bytes] + :rtype: os._wrap_close """ pass +class _wrap_close(io.TextIOWrapper[unicode]): + def __init__(self, stream, proc): + """ + :type stream: io.TextIOWrapper[unicode] + :type proc: subprocess.Popen + """ + pass + + def close(self): + """ + :rtype: int | None + """ + pass + + def __enter__(self): + """ + :rtype: os._wrap_close + """ + pass + + def __exit__(self, *args): + pass + + def __iter__(self): + """ + :rtype: collections.Iterator[unicode] + """ + pass + + def tmpfile(): """Return a new file object opened in update mode (w+b). diff --git a/python/helpers/python-skeletons/os/path.py b/python/helpers/python-skeletons/os/path.py index 2dbfd4cf7928..0e0c93409994 100644 --- a/python/helpers/python-skeletons/os/path.py +++ b/python/helpers/python-skeletons/os/path.py @@ -8,8 +8,8 @@ import os def abspath(path): """Return a normalized absolutized version of the pathname path. - :type path: T <= bytes | unicode - :rtype: T + :type path: bytes | unicode | os.PathLike + :rtype: bytes | unicode """ return path @@ -17,8 +17,8 @@ def abspath(path): def basename(path): """Return the base name of pathname path. - :type path: T <= bytes | unicode - :rtype: T + :type path: bytes | unicode | os.PathLike + :rtype: bytes | unicode """ return path @@ -27,8 +27,8 @@ def commonprefix(list): """Return the longest path prefix (taken character-by-character) that is a prefix of all paths in list. - :type list: collections.Iterable[T <= bytes | unicode] - :rtype T + :type list: collections.Iterable[bytes | unicode | os.PathLike] + :rtype: bytes | unicode """ pass @@ -36,8 +36,8 @@ def commonprefix(list): def dirname(path): """Return the directory name of pathname path. - :type path: T <= bytes | unicode - :rtype: T + :type path: bytes | unicode | os.PathLike + :rtype: bytes | unicode """ return path @@ -46,7 +46,7 @@ def exists(path): """Return True if path refers to an existing path. Returns False for broken symbolic links. - :type path: bytes | unicode + :type path: bytes | unicode | os.PathLike :rtype: bool """ return False @@ -56,7 +56,7 @@ def lexists(path): """Return True if path refers to an existing path. Returns True for broken symbolic links. - :type path: bytes | unicode + :type path: bytes | unicode | os.PathLike :rtype: bool """ return False @@ -66,8 +66,8 @@ def expanduser(path): """On Unix and Windows, return the argument with an initial component of ~ or ~user replaced by that user's home directory. - :type path: T <= bytes | unicode - :rtype: T + :type path: bytes | unicode | os.PathLike + :rtype: bytes | unicode """ return path @@ -75,8 +75,8 @@ def expanduser(path): def expandvars(path): """Return the argument with environment variables expanded. - :type path: T <= bytes | unicode - :rtype: T + :type path: bytes | unicode | os.PathLike + :rtype: bytes | unicode """ return path @@ -84,7 +84,7 @@ def expandvars(path): def getatime(path): """Return the time of last access of path. - :type path: bytes | unicode + :type path: bytes | unicode | os.PathLike :rtype: float """ return 0.0 @@ -93,7 +93,7 @@ def getatime(path): def getmtime(path): """Return the time of last modification of path. - :type path: bytes | unicode + :type path: bytes | unicode | os.PathLike :rtype: float """ return 0.0 @@ -102,7 +102,7 @@ def getmtime(path): def getctime(path): """Return the system's ctime. - :type path: bytes | unicode + :type path: bytes | unicode | os.PathLike :rtype: float """ return 0.0 @@ -111,7 +111,7 @@ def getctime(path): def getsize(path): """Return the size, in bytes, of path. - :type path: bytes | unicode + :type path: bytes | unicode | os.PathLike :rtype: int """ return 0 @@ -120,7 +120,7 @@ def getsize(path): def isabs(path): """Return True if path is an absolute pathname. - :type path: bytes | unicode + :type path: bytes | unicode | os.PathLike :rtype: bool """ return False @@ -129,7 +129,7 @@ def isabs(path): def isfile(path): """Return True if path is an existing regular file. - :type path: bytes | unicode + :type path: bytes | unicode | os.PathLike :rtype: bool """ return False @@ -138,7 +138,7 @@ def isfile(path): def isdir(path): """Return True if path is an existing directory. - :type path: bytes | unicode + :type path: bytes | unicode | os.PathLike :rtype: bool """ return False @@ -147,7 +147,7 @@ def isdir(path): def islink(path): """Return True if path refers to a directory entry that is a symbolic link. - :type path: bytes | unicode + :type path: bytes | unicode | os.PathLike :rtype: bool """ return False @@ -157,7 +157,7 @@ def ismount(path): """Return True if pathname path is a mount point: a point in a file system where a different file system has been mounted. - :type path: bytes | unicode + :type path: bytes | unicode | os.PathLike :rtype: bool """ return False @@ -166,9 +166,9 @@ def ismount(path): def join(path, *paths): """Join one or more path components intelligently. - :type path: T <= bytes | unicode - :type paths: collections.Iterable[T] - :rtype: T + :type path: bytes | unicode | os.PathLike + :type paths: collections.Iterable[bytes | unicode | os.PathLike] + :rtype: bytes | unicode """ return path @@ -176,8 +176,8 @@ def join(path, *paths): def normcase(path): """Normalize the case of a pathname. - :type path: T <= bytes | unicode - :rtype: T + :type path: bytes | unicode | os.PathLike + :rtype: bytes | unicode """ return path @@ -186,8 +186,8 @@ def normpath(path): """Normalize a pathname by collapsing redundant separators and up-level references. - :type path: T <= bytes | unicode - :rtype: T + :type path: bytes | unicode | os.PathLike + :rtype: bytes | unicode """ return path @@ -196,8 +196,8 @@ def realpath(path): """Return the canonical path of the specified filename, eliminating any symbolic links encountered in the path. - :type path: T <= bytes | unicode - :rtype: T + :type path: bytes | unicode | os.PathLike + :rtype: bytes | unicode """ return path @@ -206,9 +206,9 @@ def relpath(path, start=os.curdir): """Return a relative filepath to path either from the current directory or from an optional start directory. - :type path: T <= bytes | unicode - :type start: T - :rtype: T + :type path: bytes | unicode | os.PathLike + :type start: bytes | unicode | os.PathLike + :rtype: bytes | unicode """ return path @@ -217,8 +217,8 @@ def samefile(path1, path2): """Return True if both pathname arguments refer to the same file or directory. - :type path1: bytes | unicode - :type path2: bytes | unicode + :type path1: bytes | unicode | os.PathLike + :type path2: bytes | unicode | os.PathLike :rtype: bool """ return False @@ -247,8 +247,8 @@ def samestat(stat1, stat2): def split(path): """Split the pathname path into a pair, (head, tail). - :type path: T <= bytes | unicode - :rtype: (T, T) + :type path: bytes | unicode | os.PathLike + :rtype: (bytes | unicode, bytes | unicode) """ return path, path @@ -256,8 +256,8 @@ def split(path): def splitdrive(path): """Split the pathname path into a pair (drive, tail). - :type path: T <= bytes | unicode - :rtype: (T, T) + :type path: bytes | unicode | os.PathLike + :rtype: (bytes | unicode, bytes | unicode) """ return path, path @@ -265,8 +265,8 @@ def splitdrive(path): def splitext(path): """Split the pathname path into a pair (root, ext). - :type path: T <= bytes | unicode - :rtype: (T, T) + :type path: bytes | unicode | os.PathLike + :rtype: (bytes | unicode, bytes | unicode) """ return path, path @@ -274,8 +274,8 @@ def splitext(path): def splitunc(path): """Split the pathname path into a pair (unc, rest). - :type path: T <= bytes | unicode - :rtype: (T, T) + :type path: bytes | unicode | os.PathLike + :rtype: (bytes | unicode, bytes | unicode) """ return path, path diff --git a/python/helpers/python-skeletons/pathlib.py b/python/helpers/python-skeletons/pathlib.py index 7159886d81ac..7bcdf963f018 100644 --- a/python/helpers/python-skeletons/pathlib.py +++ b/python/helpers/python-skeletons/pathlib.py @@ -1,11 +1,13 @@ """Skeleton for 'pathlib' stdlib module.""" import pathlib +import os class PurePath(object): def __new__(cls, *pathsegments): """ + :type pathsegments: tuple[str | bytes | os.PathLike] :rtype: pathlib.PurePath """ return cls.__new__(*pathsegments) @@ -130,6 +132,12 @@ class PurePath(object): """ return pathlib.PurePath() + def __fspath__(self): + """ + :rtype: str + """ + pass + class PurePosixPath(pathlib.PurePath): pass @@ -141,6 +149,7 @@ class PureWindowsPath(pathlib.PurePath): class Path(pathlib.PurePath): def __new__(cls, *pathsegments): """ + :type pathsegments: tuple[str | bytes | os.PathLike] :rtype: pathlib.Path """ return cls.__new__(*pathsegments) diff --git a/python/helpers/required_gen_version b/python/helpers/required_gen_version index 74e41292c809..5fdf44547934 100644 --- a/python/helpers/required_gen_version +++ b/python/helpers/required_gen_version @@ -6,7 +6,7 @@ (default) 1.127 # anything not explicitly marked -(built-in) 1.144 # skeletons of all built-in modules are built together +(built-in) 1.145 # skeletons of all built-in modules are built together # Note: modules like itertools, etc are "(built-in)" and are ignored if given separately _fileio 1.127 diff --git a/python/psi-api/src/com/jetbrains/python/PyNames.java b/python/psi-api/src/com/jetbrains/python/PyNames.java index 13c091ba06c0..43f3138b2676 100644 --- a/python/psi-api/src/com/jetbrains/python/PyNames.java +++ b/python/psi-api/src/com/jetbrains/python/PyNames.java @@ -143,6 +143,7 @@ public class PyNames { public static final String MUTABLE_MAPPING = "MutableMapping"; public static final String ABC_SET = "Set"; public static final String ABC_MUTABLE_SET = "MutableSet"; + public static final String PATH_LIKE = "PathLike"; public static final String AWAITABLE = "Awaitable"; public static final String ASYNC_ITERABLE = "AsyncIterable"; @@ -173,6 +174,7 @@ public class PyNames { public static final String AEXIT = "__aexit__"; public static final String SIZEOF = "__sizeof__"; public static final String INIT_SUBCLASS = "__init_subclass__"; + public static final String FSPATH = "__fspath__"; public static final String NAME = "__name__"; public static final String ENTER = "__enter__"; @@ -439,6 +441,7 @@ public class PyNames { .putAll(PY35_BUILTIN_METHODS) .put(INIT_SUBCLASS, new BuiltinDescription("(cls, **kwargs)")) .put("__set_name__", new BuiltinDescription("(self, owner, name)")) + .put("__fspath__", _only_self_descr) .build(); public static ImmutableMap getBuiltinMethods(LanguageLevel level) { diff --git a/platform/vcs-api/src/com/intellij/vcsUtil/ActionStateConsumer.java b/python/psi-api/src/com/jetbrains/python/psi/resolve/PyOverridingReferenceResolveProvider.java similarity index 68% rename from platform/vcs-api/src/com/intellij/vcsUtil/ActionStateConsumer.java rename to python/psi-api/src/com/jetbrains/python/psi/resolve/PyOverridingReferenceResolveProvider.java index 6cd3dd0a5c6f..c8e9948c4c05 100644 --- a/platform/vcs-api/src/com/intellij/vcsUtil/ActionStateConsumer.java +++ b/python/psi-api/src/com/jetbrains/python/psi/resolve/PyOverridingReferenceResolveProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * 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. @@ -13,16 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.vcsUtil; +package com.jetbrains.python.psi.resolve; -/** - * Created with IntelliJ IDEA. - * User: Irina.Chernushina - * Date: 7/6/12 - * Time: 7:40 PM - */ -public interface ActionStateConsumer { - void hide(); - void disable(); - void enable(); -} +public interface PyOverridingReferenceResolveProvider extends PyReferenceResolveProvider { +} \ No newline at end of file diff --git a/python/psi-api/src/com/jetbrains/python/psi/resolve/PyReferenceResolveProvider.java b/python/psi-api/src/com/jetbrains/python/psi/resolve/PyReferenceResolveProvider.java index 9a917f48cec6..81faf04eda70 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/resolve/PyReferenceResolveProvider.java +++ b/python/psi-api/src/com/jetbrains/python/psi/resolve/PyReferenceResolveProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * 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. @@ -17,6 +17,7 @@ package com.jetbrains.python.psi.resolve; import com.intellij.openapi.extensions.ExtensionPointName; import com.jetbrains.python.psi.PyQualifiedExpression; +import com.jetbrains.python.psi.types.TypeEvalContext; import org.jetbrains.annotations.NotNull; import java.util.List; @@ -32,5 +33,5 @@ public interface PyReferenceResolveProvider { * Allows to provide a custom resolve result for qualified expression */ @NotNull - List resolveName(@NotNull final PyQualifiedExpression element); + List resolveName(@NotNull PyQualifiedExpression element, @NotNull TypeEvalContext context); } diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/transport/BaseDebuggerReader.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/transport/BaseDebuggerReader.java index 7b53407098b9..4006832f173f 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/transport/BaseDebuggerReader.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/transport/BaseDebuggerReader.java @@ -54,10 +54,12 @@ public abstract class BaseDebuggerReader extends BaseOutputReader { } finally { close(); - myDebugger.fireExitEvent(); + onExit(); } } + protected abstract void onExit(); + protected abstract void onCommunicationError(); @NotNull diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/transport/BaseDebuggerTransport.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/transport/BaseDebuggerTransport.java index d1bef3683246..1b99f746f705 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/transport/BaseDebuggerTransport.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/transport/BaseDebuggerTransport.java @@ -30,8 +30,7 @@ public abstract class BaseDebuggerTransport implements DebuggerTransport { return sendMessageImpl(packed); } catch (SocketException se) { - myDebugger.disconnect(); - myDebugger.fireCommunicationError(); + onSocketException(); } catch (IOException e) { LOG.debug(e); @@ -41,6 +40,8 @@ public abstract class BaseDebuggerTransport implements DebuggerTransport { protected abstract boolean sendMessageImpl(byte[] packed) throws IOException; + protected abstract void onSocketException(); + public static void logFrame(ProtocolFrame frame, boolean out) { if (LOG.isDebugEnabled()) { LOG.debug(String.format("%1$tH:%1$tM:%1$tS.%1$tL %2$s %3$s\n", new Date(), (out ? "<<<" : ">>>"), frame)); diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/transport/ClientModeDebuggerTransport.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/transport/ClientModeDebuggerTransport.java index abf46f6fa114..c6677a6bcfe3 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/transport/ClientModeDebuggerTransport.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/transport/ClientModeDebuggerTransport.java @@ -17,6 +17,7 @@ import java.net.ConnectException; import java.net.InetSocketAddress; import java.net.Socket; import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; /** * {@link DebuggerTransport} implementation that expects a debugging script to behave as a server. The main process of the debugging script @@ -73,10 +74,6 @@ public class ClientModeDebuggerTransport extends BaseDebuggerTransport { "Inappropriate state of Python debugger for connecting to Python debugger: " + myState + "; " + State.INIT + " is expected"); } - doConnect(); - } - - private void doConnect() throws IOException { synchronized (mySocketObject) { if (mySocket != null) { try { @@ -126,6 +123,7 @@ public class ClientModeDebuggerTransport extends BaseDebuggerTransport { beforeHandshake.countDown(); try { myDebugger.handshake(); + myDebuggerReader.connectionApproved(); return true; } catch (PyDebuggerException e) { @@ -194,6 +192,14 @@ public class ClientModeDebuggerTransport extends BaseDebuggerTransport { } } + @Override + protected void onSocketException() { + myDebugger.disconnect(); + if (myState == State.APPROVED) { + myDebugger.fireCommunicationError(); + } + } + @Override public void close() { try { @@ -246,16 +252,33 @@ public class ClientModeDebuggerTransport extends BaseDebuggerTransport { DISCONNECTED } - public class DebuggerReader extends BaseDebuggerReader { + public static class DebuggerReader extends BaseDebuggerReader { + /** + * Indicates that the debugger connection has been approved within this {@link DebuggerReader}. + */ + private final AtomicBoolean myConnectionApproved = new AtomicBoolean(false); + public DebuggerReader(@NotNull RemoteDebugger debugger, @NotNull InputStream stream) throws IOException { super(stream, CharsetToolkit.UTF8_CHARSET, debugger); //TODO: correct encoding? start(getClass().getName()); } + @Override + protected void onExit() { + if (myConnectionApproved.get()) { + getDebugger().fireExitEvent(); + } + } + + @Override protected void onCommunicationError() { - if (myState == State.APPROVED) { + if (myConnectionApproved.get()) { getDebugger().fireCommunicationError(); } } + + public void connectionApproved() { + myConnectionApproved.set(true); + } } } diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/transport/ServerModeDebuggerTransport.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/transport/ServerModeDebuggerTransport.java index 0224bf59bb3a..ef7add81517d 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/transport/ServerModeDebuggerTransport.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/transport/ServerModeDebuggerTransport.java @@ -108,12 +108,24 @@ public class ServerModeDebuggerTransport extends BaseDebuggerTransport { } } + @Override + protected void onSocketException() { + myDebugger.disconnect(); + myDebugger.fireCommunicationError(); + } + public static class DebuggerReader extends BaseDebuggerReader { public DebuggerReader(@NotNull RemoteDebugger debugger, @NotNull InputStream stream) throws IOException { super(stream, CharsetToolkit.UTF8_CHARSET, debugger); //TODO: correct encoding? start(getClass().getName()); } + @Override + protected void onExit() { + getDebugger().fireExitEvent(); + } + + @Override protected void onCommunicationError() {getDebugger().fireCommunicationError();} } } diff --git a/python/python-community-configure/src/com/jetbrains/python/configuration/PythonSdkDetailsDialog.java b/python/python-community-configure/src/com/jetbrains/python/configuration/PythonSdkDetailsDialog.java index 9fbec16b0f34..7c90492371b3 100644 --- a/python/python-community-configure/src/com/jetbrains/python/configuration/PythonSdkDetailsDialog.java +++ b/python/python-community-configure/src/com/jetbrains/python/configuration/PythonSdkDetailsDialog.java @@ -26,8 +26,6 @@ import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; import com.intellij.openapi.module.Module; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.projectRoots.SdkModel; @@ -388,7 +386,7 @@ public class PythonSdkDetailsDialog extends DialogWrapper { final Sdk currentSdk = getSelectedSdk(); if (currentSdk != null) { final Sdk sdk = myProjectSdksModel.findSdk(currentSdk); - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_MODAL, () -> SdkConfigurationUtil.removeSdk(sdk)); + SdkConfigurationUtil.removeSdk(sdk); myProjectSdksModel.removeSdk(sdk); myProjectSdksModel.removeSdk(currentSdk); diff --git a/python/python-rest/src/com/jetbrains/rest/quickfixes/AddIgnoredRoleFix.java b/python/python-rest/src/com/jetbrains/rest/quickfixes/AddIgnoredRoleFix.java index c25ac297b7d6..6e503842cec6 100644 --- a/python/python-rest/src/com/jetbrains/rest/quickfixes/AddIgnoredRoleFix.java +++ b/python/python-rest/src/com/jetbrains/rest/quickfixes/AddIgnoredRoleFix.java @@ -48,6 +48,11 @@ public class AddIgnoredRoleFix implements LocalQuickFix, LowPriorityAction { return "Ignore undefined role"; } + @Override + public boolean startInWriteAction() { + return false; + } + @Override public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { if (!myInspection.ignoredRoles.contains(myRole)) { diff --git a/python/python-terminal/src/com/jetbrains/python/sdk/PyVirtualEnvTerminalCustomizer.kt b/python/python-terminal/src/com/jetbrains/python/sdk/PyVirtualEnvTerminalCustomizer.kt index b7573141b42a..35cf284455c6 100644 --- a/python/python-terminal/src/com/jetbrains/python/sdk/PyVirtualEnvTerminalCustomizer.kt +++ b/python/python-terminal/src/com/jetbrains/python/sdk/PyVirtualEnvTerminalCustomizer.kt @@ -53,7 +53,7 @@ class PyVirtualEnvTerminalCustomizer : LocalTerminalCustomizer() { //for other shells we read envs from activate script by the default shell and pass them to the process val reader = PyVirtualEnvReader(path) reader.activate?.let { - envs.putAll(reader.readShellEnv().filterKeys { k -> k in arrayOf("PATH", "PS1", "VIRTUAL_ENV", "PYTHONHOME") }) + envs.putAll(reader.readShellEnv().filterKeys { k -> k in arrayOf("PATH", "PS1", "VIRTUAL_ENV", "PYTHONHOME", "PROMPT") }) } } } diff --git a/python/setup-test-environment/build.gradle b/python/setup-test-environment/build.gradle index 6b04e96b8d4e..185cf26ff584 100644 --- a/python/setup-test-environment/build.gradle +++ b/python/setup-test-environment/build.gradle @@ -1,7 +1,7 @@ import org.apache.tools.ant.taskdefs.condition.Os plugins { - id "com.jetbrains.python.envs" version "0.0.8" + id "com.jetbrains.python.envs" version "0.0.9" } @@ -15,21 +15,18 @@ envs { conda "django19", "2.7", ["django==1.9", "tox", "nose", "pytest", "behave", "lettuce>=0.2.22"], true textfile "django19/tags.txt", "django\nnose\npytest\nbehave\nlettuce\npackaging\ntox" - conda "django18", "2.7", ["django==1.6"], false - textfile "django18/tags.txt", "django" + conda "django110", "3.4", ["django==1.10"], false + textfile "django110/tags.txt", "django\nskeletons" -// conda "django17", "2.7", ["django==1.7"], false -// textfile "django17/tags.txt", "django\nskeletons" - - conda "python34", "3.4", ["ipython==2.1", "django==1.6", "behave", "jinja2", "tox>=2.0", "pandas"], true + conda "python34", "3.4", ["ipython==2.1", "django==1.8", "behave", "jinja2", "tox>=2.0", "pandas"], true textfile "python34/tags.txt", "python3\nipython\nipython200\nskeletons\ndjango\nbehave\ntox\njinja2\npython34\npackaging\npandas" + conda "pyqt_env", "2.7", [install("pyqt=5.6")], true + textfile "pyqt_env/tags.txt", "pyqt5" + if (!Os.isFamily(Os.FAMILY_WINDOWS)) { // jython "jython25", [] // textfile "jython25/tags.txt", "jython" - -// conda "pyqt5", "2.7", ["python-qt5"] -// textfile "pyqt5/tags.txt", "pyqt5" } conda "django_latest", "3.5", ["django"], true @@ -43,8 +40,12 @@ if (new File(envs.envsDirectory, "django_latest").exists() && delete new File(envs.envsDirectory, "django_latest") } -if (System.getenv("NO_CLEAN") == null && envs.envsDirectory.exists() && - envs.envsDirectory.lastModified() - + + @@ -180,7 +183,7 @@ com.jetbrains.python.codeInsight.intentions.PyConvertStaticMethodToFunctionIntention Python - + com.jetbrains.python.codeInsight.intentions.SpecifyTypeInDocstringIntention Python @@ -310,7 +313,7 @@ com.jetbrains.python.codeInsight.intentions.PyYieldFromIntention Python - + com.jetbrains.python.codeInsight.intentions.convertToFString.PyConvertToFStringIntention Python @@ -570,7 +573,7 @@ - @@ -595,7 +598,7 @@ - + @@ -686,6 +689,7 @@ + diff --git a/python/src/com/jetbrains/NotNullPredicate.java b/python/src/com/jetbrains/NotNullPredicate.java index a309482929a5..13fb25a680ed 100644 --- a/python/src/com/jetbrains/NotNullPredicate.java +++ b/python/src/com/jetbrains/NotNullPredicate.java @@ -23,7 +23,9 @@ import org.jetbrains.annotations.Nullable; * Filters out nullable elements allowing children to filter not-null elements * * @author Ilya.Kazakevich + * @deprecated use java 8 primitives instead */ +@Deprecated public class NotNullPredicate implements Predicate { /** * Simply filters nulls diff --git a/python/src/com/jetbrains/python/actions/CreatePackageAction.java b/python/src/com/jetbrains/python/actions/CreatePackageAction.java index 91a62e32d14e..acc01d8e36eb 100644 --- a/python/src/com/jetbrains/python/actions/CreatePackageAction.java +++ b/python/src/com/jetbrains/python/actions/CreatePackageAction.java @@ -27,8 +27,6 @@ import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.LangDataKeys; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.psi.PsiDirectory; @@ -63,10 +61,9 @@ public class CreatePackageAction extends DumbAwareAction { } } }; - DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, - () -> Messages.showInputDialog(project, IdeBundle.message("prompt.enter.new.package.name"), - IdeBundle.message("title.new.package"), - Messages.getQuestionIcon(), "", validator)); + Messages.showInputDialog(project, IdeBundle.message("prompt.enter.new.package.name"), + IdeBundle.message("title.new.package"), + Messages.getQuestionIcon(), "", validator); final PsiFileSystemItem result = validator.getCreatedElement(); if (result != null) { view.selectElement(result); diff --git a/python/src/com/jetbrains/python/console/PydevConsoleRunner.java b/python/src/com/jetbrains/python/console/PydevConsoleRunner.java index 272b1e997a3b..ed552d30cc9c 100644 --- a/python/src/com/jetbrains/python/console/PydevConsoleRunner.java +++ b/python/src/com/jetbrains/python/console/PydevConsoleRunner.java @@ -151,7 +151,7 @@ public interface PydevConsoleRunner { } })); - return command.replace(PydevConsoleRunnerImpl.WORKING_DIR_AND_PY_PATHS_ENV, path); + return command.replace(PydevConsoleRunnerImpl.WORKING_DIR_AND_PYTHON_PATHS, path); } static Map addDefaultEnvironments(Sdk sdk, Map envs, @NotNull Project project) { diff --git a/python/src/com/jetbrains/python/console/PydevConsoleRunnerImpl.java b/python/src/com/jetbrains/python/console/PydevConsoleRunnerImpl.java index 5b9385486be0..119a5db33531 100644 --- a/python/src/com/jetbrains/python/console/PydevConsoleRunnerImpl.java +++ b/python/src/com/jetbrains/python/console/PydevConsoleRunnerImpl.java @@ -127,9 +127,9 @@ import static com.intellij.execution.runners.AbstractConsoleRunnerWithHistory.re * @author traff, oleg */ public class PydevConsoleRunnerImpl implements PydevConsoleRunner { - public static final String WORKING_DIR_AND_PY_PATHS_ENV = "WORKING_DIR_AND_PYTHON_PATHS"; + public static final String WORKING_DIR_AND_PYTHON_PATHS = "WORKING_DIR_AND_PYTHON_PATHS"; public static final String CONSOLE_START_COMMAND = "import sys; print('Python %s on %s' % (sys.version, sys.platform))\n" + - "sys.path.extend([" + WORKING_DIR_AND_PY_PATHS_ENV + "])\n"; + "sys.path.extend([" + WORKING_DIR_AND_PYTHON_PATHS + "])\n"; private static final Logger LOG = Logger.getInstance(PydevConsoleRunnerImpl.class.getName()); @SuppressWarnings("SpellCheckingInspection") public static final String PYDEV_PYDEVCONSOLE_PY = "pydev/pydevconsole.py"; diff --git a/python/src/com/jetbrains/python/console/PythonConsoleView.java b/python/src/com/jetbrains/python/console/PythonConsoleView.java index 2cafcee76f66..31f3663f68e1 100644 --- a/python/src/com/jetbrains/python/console/PythonConsoleView.java +++ b/python/src/com/jetbrains/python/console/PythonConsoleView.java @@ -89,7 +89,6 @@ public class PythonConsoleView extends LanguageConsoleImpl implements Observable private final PyHighlighter myPyHighlighter; private final EditorColorsScheme myScheme; private boolean myHyperlink; - private boolean myFirstRun = true; private XStandaloneVariablesView mySplitView; private ActionCallback myInitialized = new ActionCallback(); @@ -144,21 +143,16 @@ public class PythonConsoleView extends LanguageConsoleImpl implements Observable myExecuteActionHandler = consoleExecuteActionHandler; } + public PydevConsoleExecuteActionHandler getExecuteActionHandler() { + return myExecuteActionHandler; + } + public void setConsoleEnabled(boolean flag) { if (myExecuteActionHandler != null) { myExecuteActionHandler.setEnabled(flag); } } - public void showStartMessageForFirstExecution(String startCommand) { - if (myFirstRun && myExecuteActionHandler != null) { - setPrompt(""); - executeStatement(startCommand + "\n", ProcessOutputTypes.SYSTEM); - myFirstRun = false; - } - } - - public void inputRequested() { if (myExecuteActionHandler != null) { final ConsoleCommunication consoleCommunication = myExecuteActionHandler.getConsoleCommunication(); diff --git a/python/src/com/jetbrains/python/console/PythonDebugLanguageConsoleView.java b/python/src/com/jetbrains/python/console/PythonDebugLanguageConsoleView.java index 04129d41ce12..d2d5257c3e5c 100644 --- a/python/src/com/jetbrains/python/console/PythonDebugLanguageConsoleView.java +++ b/python/src/com/jetbrains/python/console/PythonDebugLanguageConsoleView.java @@ -18,6 +18,7 @@ package com.jetbrains.python.console; import com.intellij.execution.console.DuplexConsoleView; import com.intellij.execution.filters.TextConsoleBuilderFactory; import com.intellij.execution.impl.ConsoleViewImpl; +import com.intellij.execution.process.ProcessOutputTypes; import com.intellij.execution.ui.ConsoleView; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; @@ -34,6 +35,7 @@ import org.jetbrains.annotations.Nullable; public class PythonDebugLanguageConsoleView extends DuplexConsoleView implements PyCodeExecutor { public static final String DEBUG_CONSOLE_START_COMMAND = "import sys; print('Python %s on %s' % (sys.version, sys.platform))"; + private boolean myDebugConsoleInitialized = false; public PythonDebugLanguageConsoleView(final Project project, Sdk sdk, ConsoleView consoleView) { super(consoleView, new PythonConsoleView(project, "Python Console", sdk)); @@ -67,14 +69,22 @@ public class PythonDebugLanguageConsoleView extends DuplexConsoleView { - // outside of read action - ShowSettingsUtil.getInstance().showSettingsDialog(project, "Project Interpreter"); - }); + ShowSettingsUtil.getInstance().showSettingsDialog(project, "Project Interpreter"); } } } diff --git a/python/src/com/jetbrains/python/inspections/PyPackageRequirementsInspection.java b/python/src/com/jetbrains/python/inspections/PyPackageRequirementsInspection.java index 2ffbdfea7873..16a543172ce1 100644 --- a/python/src/com/jetbrains/python/inspections/PyPackageRequirementsInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyPackageRequirementsInspection.java @@ -272,6 +272,11 @@ public class PyPackageRequirementsInspection extends PyInspection { return myName; } + @Override + public boolean startInWriteAction() { + return false; + } + @Override public void applyFix(@NotNull final Project project, @NotNull ProblemDescriptor descriptor) { boolean installManagement = false; @@ -356,6 +361,11 @@ public class PyPackageRequirementsInspection extends PyInspection { return "Install and import package " + myPackageName; } + @Override + public boolean startInWriteAction() { + return false; + } + @Override public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) { final PyPackageManagerUI ui = new PyPackageManagerUI(project, mySdk, new UIListener(myModule) { @@ -411,6 +421,11 @@ public class PyPackageRequirementsInspection extends PyInspection { return String.format("Ignore requirement%s", plural ? "s" : ""); } + @Override + public boolean startInWriteAction() { + return false; + } + @Override public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { final PsiElement element = descriptor.getPsiElement(); @@ -444,6 +459,11 @@ public class PyPackageRequirementsInspection extends PyInspection { myLanguageLevel = languageLevel; } + @Override + public boolean startInWriteAction() { + return false; + } + @NotNull @Override public String getFamilyName() { diff --git a/python/src/com/jetbrains/python/inspections/PyShadowingBuiltinsInspection.java b/python/src/com/jetbrains/python/inspections/PyShadowingBuiltinsInspection.java index d09d1df4d64e..d44851a5b36d 100644 --- a/python/src/com/jetbrains/python/inspections/PyShadowingBuiltinsInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyShadowingBuiltinsInspection.java @@ -128,6 +128,11 @@ public class PyShadowingBuiltinsInspection extends PyInspection { return getFamilyName() + " \"" + myName + "\""; } + @Override + public boolean startInWriteAction() { + return false; + } + @NotNull @Override public String getFamilyName() { diff --git a/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java b/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java index 0ff3c667f448..bef8c9c0f5ba 100644 --- a/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java @@ -31,6 +31,7 @@ import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; import com.jetbrains.python.documentation.PythonDocumentationProvider; import com.jetbrains.python.inspections.quickfix.PyMakeFunctionReturnTypeQuickFix; import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.types.*; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; @@ -203,19 +204,19 @@ public class PyTypeCheckerInspection extends PyInspection { for (Map.Entry entry : mapping.entrySet()) { final PyNamedParameter param = entry.getValue(); final PyExpression arg = entry.getKey(); - if (param.isPositionalContainer() || param.isKeywordContainer()) { + if (param.isKeywordContainer()) { continue; } - final PyType paramType = myTypeEvalContext.getType(param); - if (paramType == null) { + final PyType expectedArgType = getExpectedArgumentType(param); + if (expectedArgType == null) { continue; } - final PyType argType = myTypeEvalContext.getType(arg); + final PyType actualArgType = myTypeEvalContext.getType(arg); if (!genericsCollected) { substitutions.putAll(PyTypeChecker.unifyReceiver(receiver, myTypeEvalContext)); genericsCollected = true; } - final Pair problem = checkTypes(paramType, argType, myTypeEvalContext, substitutions); + final Pair problem = checkTypes(expectedArgType, actualArgType, myTypeEvalContext, substitutions); if (problem != null) { problems.put(arg, problem); } @@ -223,6 +224,22 @@ public class PyTypeCheckerInspection extends PyInspection { return problems; } + @Nullable + private PyType getExpectedArgumentType(@NotNull PyNamedParameter param) { + final PyType paramType = myTypeEvalContext.getType(param); + + if (param.isPositionalContainer()) { + if (paramType == PyBuiltinCache.getInstance(param).getTupleType()) { + return null; + } + else if (paramType instanceof PyCollectionType) { + return ((PyCollectionType)paramType).getIteratedItemType(); + } + } + + return paramType; + } + @Nullable private static Pair checkTypes(@Nullable PyType expected, @Nullable PyType actual, diff --git a/python/src/com/jetbrains/python/inspections/quickfix/AddIgnoredIdentifierQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/AddIgnoredIdentifierQuickFix.java index 65348b8bfcee..c9c08cfee2cc 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/AddIgnoredIdentifierQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/AddIgnoredIdentifierQuickFix.java @@ -56,6 +56,11 @@ public class AddIgnoredIdentifierQuickFix implements LocalQuickFix, LowPriorityA return "Ignore unresolved reference"; } + @Override + public boolean startInWriteAction() { + return false; + } + @Override public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { final PsiElement context = descriptor.getPsiElement(); diff --git a/python/src/com/jetbrains/python/inspections/quickfix/GenerateBinaryStubsFix.java b/python/src/com/jetbrains/python/inspections/quickfix/GenerateBinaryStubsFix.java index d891741aa0b2..6505ccadd3b8 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/GenerateBinaryStubsFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/GenerateBinaryStubsFix.java @@ -110,6 +110,11 @@ public class GenerateBinaryStubsFix implements LocalQuickFix { return "Generate binary stubs"; } + @Override + public boolean startInWriteAction() { + return false; + } + @Override public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) { final PsiFile file = descriptor.getPsiElement().getContainingFile(); diff --git a/python/src/com/jetbrains/python/psi/impl/PyFunctionBuilder.java b/python/src/com/jetbrains/python/psi/impl/PyFunctionBuilder.java index e2456210d268..e4469a12ade4 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFunctionBuilder.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFunctionBuilder.java @@ -87,7 +87,7 @@ public class PyFunctionBuilder { */ public PyFunctionBuilder(@NotNull String name, @NotNull PsiElement settingsAnchor) { myName = name; - myDocStringGenerator = PyDocstringGenerator.create(DocStringUtil.getConfiguredDocStringFormat(settingsAnchor), + myDocStringGenerator = PyDocstringGenerator.create(DocStringUtil.getConfiguredDocStringFormatOrPlain(settingsAnchor), PyIndentUtil.getIndentFromSettings(settingsAnchor.getProject()), settingsAnchor); } diff --git a/python/src/com/jetbrains/python/psi/impl/references/PyReferenceImpl.java b/python/src/com/jetbrains/python/psi/impl/references/PyReferenceImpl.java index d1dd18822472..c00465c00877 100644 --- a/python/src/com/jetbrains/python/psi/impl/references/PyReferenceImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/references/PyReferenceImpl.java @@ -187,20 +187,15 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference ret.poke(definition, getRate(definition, context)); } } + final ResolveResultList results = new ResolveResultList(); for (RatedResolveResult r : ret) { final PsiElement e = r.getElement(); - if (e == element) { + if (e == element || element instanceof PyTargetExpression && e != null && PyPsiUtils.isBefore(element, e)) { continue; } - if (element instanceof PyTargetExpression && e != null && PyPsiUtils.isBefore(element, e)) { - continue; - } - else { - results.add(r); - } + results.add(r); } - return results; } @@ -228,37 +223,16 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference */ @NotNull protected List resolveInner() { - final ResolveResultList ret = new ResolveResultList(); - - final String referencedName = myElement.getReferencedName(); - if (referencedName == null) return ret; - - if (myElement instanceof PyTargetExpression) { - if (PsiTreeUtil.getParentOfType(myElement, PyComprehensionElement.class) != null) { - ret.poke(myElement, getRate(myElement, myContext.getTypeEvalContext())); - return ret; - } + final ResolveResultList overriddenResult = resolveByOverridingReferenceResolveProviders(); + if (!overriddenResult.isEmpty()) { + return overriddenResult; } - // resolve implicit __class__ inside class function - if (myElement instanceof PyReferenceExpression && - PyNames.__CLASS__.equals(referencedName) && - LanguageLevel.forElement(myElement).isAtLeast(LanguageLevel.PYTHON30)) { - final PyFunction containingFunction = PsiTreeUtil.getParentOfType(myElement, PyFunction.class); + final String referencedName = myElement.getReferencedName(); + if (referencedName == null) return Collections.emptyList(); - if (containingFunction != null) { - final PyClass containingClass = containingFunction.getContainingClass(); - - if (containingClass != null) { - final PyResolveProcessor processor = new PyResolveProcessor(referencedName); - PyResolveUtil.scopeCrawlUp(processor, myElement, referencedName, containingFunction); - - if (processor.getElements().isEmpty()) { - ret.add(new RatedResolveResult(RatedResolveResult.RATE_NORMAL, containingClass)); - return ret; - } - } - } + if (myElement instanceof PyTargetExpression && PsiTreeUtil.getParentOfType(myElement, PyComprehensionElement.class) != null) { + return ResolveResultList.to(myElement); } // here we have an unqualified expr. it may be defined: @@ -271,21 +245,7 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference final PsiElement roof = findResolveRoof(referencedName, realContext); PyResolveUtil.scopeCrawlUp(processor, myElement, referencedName, roof); - final List resultsFromProcessor = getResultsFromProcessor(referencedName, processor, realContext, roof); - - // resolve to module __doc__ - if (resultsFromProcessor.isEmpty() && referencedName.equals(PyNames.DOC)) { - ret.addAll( - Optional - .ofNullable(PyBuiltinCache.getInstance(myElement).getObjectType()) - .map(type -> type.resolveMember(referencedName, myElement, AccessDirection.of(myElement), myContext)) - .orElse(Collections.emptyList()) - ); - - return ret; - } - - return resultsFromProcessor; + return getResultsFromProcessor(referencedName, processor, realContext, roof); } protected List getResultsFromProcessor(@NotNull String referencedName, @@ -378,11 +338,29 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference return true; } + @NotNull + private ResolveResultList resolveByOverridingReferenceResolveProviders() { + final ResolveResultList results = new ResolveResultList(); + final TypeEvalContext context = myContext.getTypeEvalContext(); + + Arrays + .stream(Extensions.getExtensions(PyReferenceResolveProvider.EP_NAME)) + .filter(PyOverridingReferenceResolveProvider.class::isInstance) + .map(provider -> provider.resolveName(myElement, context)) + .forEach(results::addAll); + + return results; + } + @NotNull private ResolveResultList resolveByReferenceResolveProviders() { final ResolveResultList results = new ResolveResultList(); + final TypeEvalContext context = myContext.getTypeEvalContext(); for (PyReferenceResolveProvider provider : Extensions.getExtensions(PyReferenceResolveProvider.EP_NAME)) { - results.addAll(provider.resolveName(myElement)); + if (provider instanceof PyOverridingReferenceResolveProvider) { + continue; + } + results.addAll(provider.resolveName(myElement, context)); } return results; } @@ -643,9 +621,8 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference final List ret = Lists.newArrayList(); // Use real context here to enable correct completion and resolve in case of PyExpressionCodeFragment!!! - final PsiElement originalElement = CompletionUtil.getOriginalElement(myElement); - final PyQualifiedExpression element = originalElement instanceof PyQualifiedExpression ? - (PyQualifiedExpression)originalElement : myElement; + final PyQualifiedExpression originalElement = CompletionUtil.getOriginalElement(myElement); + final PyQualifiedExpression element = originalElement != null ? originalElement : myElement; final PsiElement realContext = PyPsiUtils.getRealContext(element); // include our own names @@ -668,12 +645,19 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference if (underscores >= 2) { // if we're a normal module, add module's attrs - PsiFile f = realContext.getContainingFile(); - if (f instanceof PyFile) { + if (realContext.getContainingFile() instanceof PyFile) { for (String name : PyModuleType.getPossibleInstanceMembers()) { ret.add(LookupElementBuilder.create(name).withIcon(PlatformIcons.FIELD_ICON)); } } + + // if we're inside method, add implicit __class__ + if (LanguageLevel.forElement(myElement).isAtLeast(LanguageLevel.PYTHON30)) { + Optional + .ofNullable(PsiTreeUtil.getParentOfType(myElement, PyFunction.class)) + .map(PyFunction::getContainingClass) + .ifPresent(pyClass -> ret.add(LookupElementBuilder.create(PyNames.__CLASS__))); + } } ret.addAll(getOriginalElements(processor)); diff --git a/python/src/com/jetbrains/python/psi/resolve/PythonBuiltinReferenceResolveProvider.java b/python/src/com/jetbrains/python/psi/resolve/PythonBuiltinReferenceResolveProvider.java index 48358136b2f1..bc36e822c1df 100644 --- a/python/src/com/jetbrains/python/psi/resolve/PythonBuiltinReferenceResolveProvider.java +++ b/python/src/com/jetbrains/python/psi/resolve/PythonBuiltinReferenceResolveProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * 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. @@ -16,6 +16,8 @@ package com.jetbrains.python.psi.resolve; import com.intellij.psi.PsiElement; +import com.jetbrains.python.PyNames; +import com.jetbrains.python.psi.AccessDirection; import com.jetbrains.python.psi.PyFile; import com.jetbrains.python.psi.PyQualifiedExpression; import com.jetbrains.python.psi.PyUtil; @@ -24,33 +26,63 @@ import com.jetbrains.python.psi.impl.PyPsiUtils; import com.jetbrains.python.psi.impl.references.PyReferenceImpl; import com.jetbrains.python.psi.types.TypeEvalContext; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Optional; /** * User : ktisha */ public class PythonBuiltinReferenceResolveProvider implements PyReferenceResolveProvider { + @NotNull @Override - public List resolveName(@NotNull PyQualifiedExpression element) { - final List result = new ArrayList<>(); - final PsiElement realContext = PyPsiUtils.getRealContext(element); + public List resolveName(@NotNull PyQualifiedExpression element, @NotNull TypeEvalContext context) { final String referencedName = element.getReferencedName(); - final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(realContext); - // ...as a builtin symbol - final PyFile bfile = builtinCache.getBuiltinsFile(); - if (bfile != null && !PyUtil.isClassPrivateName(referencedName)) { - PsiElement resultElement = bfile.getElementNamed(referencedName); - if (resultElement == null && "__builtins__".equals(referencedName)) { - resultElement = bfile; // resolve __builtins__ reference - } - if (resultElement != null) { - final TypeEvalContext typeEvalContext = TypeEvalContext.codeInsightFallback(element.getProject()); - result.add(new ImportedResolveResult(resultElement, PyReferenceImpl.getRate(resultElement, typeEvalContext), null)); - } + if (referencedName == null) { + return Collections.emptyList(); } + + final List result = new ArrayList<>(); + final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(PyPsiUtils.getRealContext(element)); + + // resolve to module __doc__ + if (PyNames.DOC.equals(referencedName)) { + result.addAll( + Optional + .ofNullable(builtinCache.getObjectType()) + .map(type -> type.resolveMember(referencedName, element, AccessDirection.of(element), + PyResolveContext.noImplicits().withTypeEvalContext(context))) + .orElse(Collections.emptyList()) + ); + } + + // ...as a builtin symbol + if (!PyUtil.isClassPrivateName(referencedName)) { + result.addAll( + Optional + .ofNullable(builtinCache.getBuiltinsFile()) + .map(builtinsFile -> resolveNameInBuiltins(referencedName, builtinsFile)) + .map(resultElement -> new ImportedResolveResult(resultElement, PyReferenceImpl.getRate(resultElement, context), null)) + .map(Collections::singletonList) + .orElse(Collections.emptyList()) + ); + } + return result; } + + @Nullable + private static PsiElement resolveNameInBuiltins(@NotNull String referencedName, @NotNull PyFile builtinsFile) { + final PsiElement resultElement = builtinsFile.getElementNamed(referencedName); + + if (resultElement == null && "__builtins__".equals(referencedName)) { + return builtinsFile; // resolve __builtins__ reference + } + + return resultElement; + } } diff --git a/python/src/com/jetbrains/python/psi/resolve/PythonOverridingBuiltinReferenceResolveProvider.java b/python/src/com/jetbrains/python/psi/resolve/PythonOverridingBuiltinReferenceResolveProvider.java new file mode 100644 index 000000000000..e176d30d4937 --- /dev/null +++ b/python/src/com/jetbrains/python/psi/resolve/PythonOverridingBuiltinReferenceResolveProvider.java @@ -0,0 +1,56 @@ +/* + * 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.jetbrains.python.psi.resolve; + +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.python.PyNames; +import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.types.TypeEvalContext; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; + +public class PythonOverridingBuiltinReferenceResolveProvider implements PyOverridingReferenceResolveProvider { + + @NotNull + @Override + public List resolveName(@NotNull PyQualifiedExpression element, @NotNull TypeEvalContext context) { + final String referencedName = element.getReferencedName(); + + // resolve implicit __class__ inside class function + if (element instanceof PyReferenceExpression && + PyNames.__CLASS__.equals(referencedName) && + LanguageLevel.forElement(element).isAtLeast(LanguageLevel.PYTHON30)) { + final PyFunction containingFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class); + + if (containingFunction != null) { + final PyClass containingClass = containingFunction.getContainingClass(); + + if (containingClass != null) { + final PyResolveProcessor processor = new PyResolveProcessor(referencedName); + PyResolveUtil.scopeCrawlUp(processor, element, referencedName, containingFunction); + + if (processor.getElements().isEmpty()) { + return Collections.singletonList(new RatedResolveResult(RatedResolveResult.RATE_NORMAL, containingClass)); + } + } + } + } + + return Collections.emptyList(); + } +} diff --git a/python/src/com/jetbrains/python/psi/types/PyABCUtil.java b/python/src/com/jetbrains/python/psi/types/PyABCUtil.java index a659e62106ab..8f6d73ec924c 100644 --- a/python/src/com/jetbrains/python/psi/types/PyABCUtil.java +++ b/python/src/com/jetbrains/python/psi/types/PyABCUtil.java @@ -97,6 +97,9 @@ public class PyABCUtil { if (PyNames.ASYNC_ITERABLE.equals(superClassName)) { return hasMethod(subClass, PyNames.AITER, inherited, context); } + if (PyNames.PATH_LIKE.equals(superClassName)) { + return hasMethod(subClass, PyNames.FSPATH, inherited, context); + } return false; } diff --git a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java index f609a68f64b8..1fa26e2e302d 100644 --- a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java +++ b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java @@ -38,6 +38,7 @@ import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.impl.PyResolveResultRater; import com.jetbrains.python.psi.impl.ResolveResultList; +import com.jetbrains.python.psi.impl.references.PyReferenceImpl; import com.jetbrains.python.psi.resolve.CompletionVariantsProcessor; import com.jetbrains.python.psi.resolve.PyResolveContext; import com.jetbrains.python.psi.resolve.PyResolveProcessor; @@ -243,13 +244,10 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { } } - if (inherited && isDefinition() && myClass.isNewStyleClass(context)) { - final PyClassLikeType typeType = getMetaClassType(context, true); - if (typeType != null) { - List typeMembers = typeType.resolveMember(name, location, direction, resolveContext); - if (typeMembers != null && !typeMembers.isEmpty()) { - return typeMembers; - } + if (inherited) { + final List typeMembers = resolveMetaClassMember(name, location, direction, resolveContext); + if (typeMembers != null) { + return typeMembers; } } @@ -280,6 +278,46 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { return Collections.emptyList(); } + @Nullable + private List resolveMetaClassMember(@NotNull String name, + @Nullable PyExpression location, + @NotNull AccessDirection direction, + @NotNull PyResolveContext resolveContext) { + final TypeEvalContext context = resolveContext.getTypeEvalContext(); + if (!myClass.isNewStyleClass(context)) { + return null; + } + + final PyClassLikeType typeType = getMetaClassType(context, true); + if (typeType == null) { + return null; + } + + if (isDefinition()) { + final List typeMembers = typeType.resolveMember(name, location, direction, resolveContext); + if (!ContainerUtil.isEmpty(typeMembers)) { + return typeMembers; + } + + final List typeInstanceMembers = + typeType.toInstance().resolveMember(name, location, direction, resolveContext); + + if (!ContainerUtil.isEmpty(typeInstanceMembers)) { + return typeInstanceMembers; + } + } + else if (typeType instanceof PyClassType) { + final List typeInstanceAttributes = ((PyClassType)typeType).getPyClass().getInstanceAttributes(); + + if (!ContainerUtil.isEmpty(typeInstanceAttributes)) { + return ContainerUtil + .map(typeInstanceAttributes, member -> new RatedResolveResult(PyReferenceImpl.getRate(member, context), member)); + } + } + + return null; + } + private Ref findProperty(String name, AccessDirection direction, boolean inherited, @@ -560,16 +598,36 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { } } - if (isDefinition() && myClass.isNewStyleClass(typeEvalContext)) { - final PyClassLikeType typeType = getMetaClassType(typeEvalContext, true); - if (typeType != null) { - Collections.addAll(ret, typeType.getCompletionVariants(prefix, location, context)); - } - } + Collections.addAll(ret, getMetaClassCompletionVariants(prefix, location, context, typeEvalContext)); return ret.toArray(); } + @NotNull + private Object[] getMetaClassCompletionVariants(@Nullable String prefix, + @Nullable PsiElement location, + @NotNull ProcessingContext processingContext, + @NotNull TypeEvalContext typeEvalContext) { + if (!myClass.isNewStyleClass(typeEvalContext)) { + return ArrayUtil.EMPTY_OBJECT_ARRAY; + } + + final PyClassLikeType typeType = getMetaClassType(typeEvalContext, true); + if (typeType == null) { + return ArrayUtil.EMPTY_OBJECT_ARRAY; + } + + if (isDefinition()) { + return typeType.getCompletionVariants(prefix, location, processingContext); + } + else if (typeType instanceof PyClassType) { + final List typeInstanceAttributes = ((PyClassType)typeType).getPyClass().getInstanceAttributes(); + return ContainerUtil.map2Array(typeInstanceAttributes, LookupElementBuilder::create); + } + + return ArrayUtil.EMPTY_OBJECT_ARRAY; + } + @Override public void visitMembers(@NotNull final Processor processor, final boolean inherited, diff --git a/python/src/com/jetbrains/python/sdk/AbstractCreateVirtualEnvDialog.java b/python/src/com/jetbrains/python/sdk/AbstractCreateVirtualEnvDialog.java index af2757ee2e07..ee7a42c4d7ca 100644 --- a/python/src/com/jetbrains/python/sdk/AbstractCreateVirtualEnvDialog.java +++ b/python/src/com/jetbrains/python/sdk/AbstractCreateVirtualEnvDialog.java @@ -23,8 +23,6 @@ import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; -import com.intellij.openapi.project.DumbModePermission; -import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil; @@ -263,8 +261,7 @@ public abstract class AbstractCreateVirtualEnvDialog extends IdeaDialog { @Override public void onSuccess() { if (myPath != null) { - ApplicationManager.getApplication().invokeLater(() -> DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, - () -> setupVirtualEnvSdk(myPath, associateWithProject(), callback))); + ApplicationManager.getApplication().invokeLater(() -> setupVirtualEnvSdk(myPath, associateWithProject(), callback)); } } }; diff --git a/python/src/com/jetbrains/python/sdk/flavors/PythonSdkFlavor.java b/python/src/com/jetbrains/python/sdk/flavors/PythonSdkFlavor.java index 991f0863a704..5eacbe58be07 100644 --- a/python/src/com/jetbrains/python/sdk/flavors/PythonSdkFlavor.java +++ b/python/src/com/jetbrains/python/sdk/flavors/PythonSdkFlavor.java @@ -18,6 +18,7 @@ package com.jetbrains.python.sdk.flavors; import com.google.common.collect.Lists; import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.process.ProcessOutput; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.projectRoots.Sdk; @@ -80,7 +81,7 @@ public abstract class PythonSdkFlavor { List result = new ArrayList<>(); if (SystemInfo.isWindows) { - result.add(WinPythonSdkFlavor.INSTANCE); + result.add(ServiceManager.getService(WinPythonSdkFlavor.class)); } else if (SystemInfo.isMac) { result.add(MacPythonSdkFlavor.INSTANCE); diff --git a/python/src/com/jetbrains/python/sdk/flavors/WinPythonSdkFlavor.java b/python/src/com/jetbrains/python/sdk/flavors/WinPythonSdkFlavor.java index 4ebc19d7aa92..500e41dca53f 100644 --- a/python/src/com/jetbrains/python/sdk/flavors/WinPythonSdkFlavor.java +++ b/python/src/com/jetbrains/python/sdk/flavors/WinPythonSdkFlavor.java @@ -17,29 +17,34 @@ package com.jetbrains.python.sdk.flavors; import com.google.common.collect.ImmutableMap; import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.io.WindowsRegistryUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.newvfs.NewVirtualFile; import com.jetbrains.python.PythonHelpersLocator; +import org.jetbrains.annotations.NotNull; import java.io.File; import java.util.*; /** + * This class knows how to find python in Windows Registry according to + * PEP 514 + * * @author yole */ -public class WinPythonSdkFlavor extends CPythonSdkFlavor { - public static WinPythonSdkFlavor INSTANCE = new WinPythonSdkFlavor(); - private static Map ourRegistryMap = - ImmutableMap.of("HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore", "python.exe", - "HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Python\\PythonCore", "python.exe", - "HKEY_LOCAL_MACHINE\\SOFTWARE\\IronPython", "ipy.exe"); +public final class WinPythonSdkFlavor extends CPythonSdkFlavor { + private static final String[] REG_ROOTS = {"HKEY_LOCAL_MACHINE", "HKEY_CURRENT_USER"}; + private static final Map REGISTRY_MAP = + ImmutableMap.of("Python", "python.exe", + "IronPython", "ipy.exe"); - private static Set ourRegistryCache; + private static volatile Set ourRegistryCache; + @NotNull + private final WinRegistryService myWinRegService; - private WinPythonSdkFlavor() { + WinPythonSdkFlavor(@NotNull final WinRegistryService winRegistryService) { + myWinRegService = winRegistryService; } @Override @@ -50,14 +55,21 @@ public class WinPythonSdkFlavor extends CPythonSdkFlavor { return candidates; } - private static void findInCandidatePaths(Set candidates, String... exe_names) { + private void findInCandidatePaths(Set candidates, String... exe_names) { for (String name : exe_names) { findInstallations(candidates, name, "C:\\", "C:\\Program Files\\"); findInPath(candidates, name); + + findInRegistry(candidates); } } + void findInRegistry(@NotNull final Collection candidates) { + fillRegistryCache(myWinRegService); + candidates.addAll(ourRegistryCache); + } + private static void findInstallations(Set candidates, String exe_name, String... roots) { for (String root : roots) { findSubdirInstallations(candidates, root, FileUtil.getNameWithoutExtension(exe_name), exe_name); @@ -79,25 +91,36 @@ public class WinPythonSdkFlavor extends CPythonSdkFlavor { } } - public static void findInRegistry(Collection candidates) { - fillRegistryCache(); - candidates.addAll(ourRegistryCache); - } + private static void fillRegistryCache(@NotNull final WinRegistryService registryService) { + if (ourRegistryCache != null) { + return; + } + ourRegistryCache = new HashSet<>(); - private static void fillRegistryCache() { - if (ourRegistryCache == null) { - ourRegistryCache = new HashSet<>(); - for (Map.Entry entry : ourRegistryMap.entrySet()) { - final String prefix = entry.getKey(); + /* + Check https://www.python.org/dev/peps/pep-0514/ for windows registry layout to understand + this method + */ + for (final String regRoot : REG_ROOTS) { + for (final Map.Entry entry : REGISTRY_MAP.entrySet()) { + final String productId = entry.getKey(); final String exePath = entry.getValue(); - List strings = WindowsRegistryUtil.readRegistryBranch(prefix); - for (String string : strings) { - final String path = WindowsRegistryUtil.readRegistryDefault(prefix + "\\" + string + - "\\InstallPath"); - if (path != null) { - File f = new File(path, exePath); - if (f.exists()) { - ourRegistryCache.add(FileUtil.toSystemDependentName(f.getPath())); + final String companiesPath = String.format("%s\\SOFTWARE\\%s", regRoot, productId); + final String companiesPathWow = String.format("%s\\SOFTWARE\\Wow6432Node\\%s", regRoot, productId); + + for (final String path : new String[]{companiesPath, companiesPathWow}) { + final List companies = registryService.listBranches(path); + for (final String company : companies) { + final String pathToCompany = path + '\\' + company; + final List versions = registryService.listBranches(pathToCompany); + for (final String version : versions) { + final String folder = registryService.getDefaultKey(pathToCompany + '\\' + version + "\\InstallPath"); + if (folder != null) { + final File interpreter = new File(folder, exePath); + if (interpreter.exists()) { + ourRegistryCache.add(FileUtil.toSystemDependentName(interpreter.getPath())); + } + } } } } diff --git a/python/src/com/jetbrains/python/sdk/flavors/WinRegistryService.kt b/python/src/com/jetbrains/python/sdk/flavors/WinRegistryService.kt new file mode 100644 index 000000000000..600e9253ed97 --- /dev/null +++ b/python/src/com/jetbrains/python/sdk/flavors/WinRegistryService.kt @@ -0,0 +1,43 @@ +/* + * 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.jetbrains.python.sdk.flavors + +import com.intellij.openapi.util.io.WindowsRegistryUtil + +/** + * Win registry access service + * + * @author Ilya.Kazakevich + */ +interface WinRegistryService { + /** + * @param basePath path like "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node" + * @return list of subbranches + */ + fun listBranches(basePath: String): List + + /** + * @param path path like "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node" + * @return its (Default) value + */ + fun getDefaultKey(path: String): String? +} + +class WinRegistryServiceImpl : WinRegistryService { + override fun listBranches(basePath: String): List = WindowsRegistryUtil.readRegistryBranch(basePath) + + override fun getDefaultKey(path: String): String? = WindowsRegistryUtil.readRegistryDefault(path) +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/spellchecker/python.dic b/python/src/com/jetbrains/python/spellchecker/python.dic index de9d358620a7..221b9adf44ad 100644 --- a/python/src/com/jetbrains/python/spellchecker/python.dic +++ b/python/src/com/jetbrains/python/spellchecker/python.dic @@ -595,6 +595,7 @@ frozentable frsize fsblkcnt fsfilcnt +fspath fstring ftext ftpcache diff --git a/python/src/com/jetbrains/python/structureView/PyStructureViewElement.java b/python/src/com/jetbrains/python/structureView/PyStructureViewElement.java index 9962f6ee00e3..fa283359c4a9 100644 --- a/python/src/com/jetbrains/python/structureView/PyStructureViewElement.java +++ b/python/src/com/jetbrains/python/structureView/PyStructureViewElement.java @@ -68,9 +68,10 @@ public class PyStructureViewElement implements StructureViewTreeElement { return new PyStructureViewElement(element, visibility, inherited, field); } + @Nullable @Override public PyElement getValue() { - return myElement; + return myElement.isValid() ? myElement : null; } public boolean isInherited() { @@ -83,17 +84,20 @@ public class PyStructureViewElement implements StructureViewTreeElement { @Override public void navigate(boolean requestFocus) { - myElement.navigate(requestFocus); + final PyElement element = getValue(); + if (element != null) { + myElement.navigate(requestFocus); + } } @Override public boolean canNavigate() { - return myElement.canNavigate(); + return myElement.isValid() && myElement.canNavigate(); } @Override public boolean canNavigateToSource() { - return myElement.canNavigateToSource(); + return myElement.isValid() && myElement.canNavigateToSource(); } public void setIcon(Icon icon) { @@ -120,13 +124,18 @@ public class PyStructureViewElement implements StructureViewTreeElement { @NotNull public StructureViewTreeElement[] getChildren() { + final PyElement element = getValue(); + if (element == null) { + return EMPTY_ARRAY; + } + final Collection children = new ArrayList<>(); - for (PyElement e : getElementChildren(myElement)) { + for (PyElement e : getElementChildren(element)) { children.add(createChild(e, getElementVisibility(e), false, elementIsField(e))); } - PyPsiUtils.assertValid(myElement); - if (myElement instanceof PyClass) { - for (PyClass c : ((PyClass)myElement).getAncestorClasses(null)) { + PyPsiUtils.assertValid(element); + if (element instanceof PyClass) { + for (PyClass c : ((PyClass)element).getAncestorClasses(null)) { for (PyElement e: getElementChildren(c)) { final StructureViewTreeElement inherited = createChild(e, getElementVisibility(e), true, elementIsField(e)); if (!children.contains(inherited)) { @@ -231,13 +240,15 @@ public class PyStructureViewElement implements StructureViewTreeElement { @NotNull @Override public ItemPresentation getPresentation() { - final ItemPresentation presentation = myElement.getPresentation(); + final PyElement element = getValue(); + final ItemPresentation presentation = element != null ? element.getPresentation() : null; + return new ColoredItemPresentation() { @Nullable @Override public String getPresentableText() { - if (myElement instanceof PyFile) { - return myElement.getName(); + if (element instanceof PyFile) { + return element.getName(); } return presentation != null ? presentation.getPresentableText() : PyNames.UNNAMED_ELEMENT; } @@ -260,7 +271,11 @@ public class PyStructureViewElement implements StructureViewTreeElement { @Nullable @Override public Icon getIcon(boolean open) { - Icon normal_icon = myElement.getIcon(0); + if (element == null) { + return null; + } + + Icon normal_icon = element.getIcon(0); if (myIcon != null) normal_icon = myIcon; // override normal if (myVisibility == Visibility.NORMAL) { return normal_icon; diff --git a/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java b/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java index 470cd7f2134e..d7a9db40811d 100644 --- a/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java +++ b/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java @@ -89,7 +89,7 @@ public abstract class PythonTestCommandLineStateBase extends PythonCommandLineSt } @Override - public GeneralCommandLine generateCommandLine() { + public GeneralCommandLine generateCommandLine() { GeneralCommandLine cmd = super.generateCommandLine(); setWorkingDirectory(cmd); @@ -103,14 +103,11 @@ public abstract class PythonTestCommandLineStateBase extends PythonCommandLineSt } protected void setWorkingDirectory(@NotNull final GeneralCommandLine cmd) { - final String workingDirectory = myConfiguration.getWorkingDirectory(); - if (!StringUtil.isEmptyOrSpaces(workingDirectory)) { - cmd.withWorkDirectory(workingDirectory); - } - else if (myConfiguration instanceof AbstractPythonTestRunConfiguration) { - final AbstractPythonTestRunConfiguration configuration = (AbstractPythonTestRunConfiguration)myConfiguration; - cmd.withWorkDirectory(configuration.getWorkingDirectorySafe()); + String workingDirectory = myConfiguration.getWorkingDirectory(); + if (StringUtil.isEmptyOrSpaces(workingDirectory)) { + workingDirectory = myConfiguration.getWorkingDirectorySafe(); } + cmd.withWorkDirectory(workingDirectory); } @Override @@ -134,10 +131,11 @@ public abstract class PythonTestCommandLineStateBase extends PythonCommandLineSt return executionResult; } - protected void addBeforeParameters(GeneralCommandLine cmd) {} + protected void addBeforeParameters(GeneralCommandLine cmd) {} + protected void addAfterParameters(GeneralCommandLine cmd) {} - protected void addTestRunnerParameters(GeneralCommandLine cmd) { + protected void addTestRunnerParameters(GeneralCommandLine cmd) { ParamsGroup scriptParams = cmd.getParametersList().getParamsGroup(GROUP_SCRIPT); assert scriptParams != null; getRunner().addToGroup(scriptParams, cmd); @@ -153,6 +151,7 @@ public abstract class PythonTestCommandLineStateBase extends PythonCommandLineSt } protected abstract HelperPackage getRunner(); + @NotNull protected abstract List getTestSpecs(); } diff --git a/python/testData/MockSdk2.7/python_stubs/__builtin__.py b/python/testData/MockSdk2.7/python_stubs/__builtin__.py index 01f138ec56bc..67e2121de1f1 100644 --- a/python/testData/MockSdk2.7/python_stubs/__builtin__.py +++ b/python/testData/MockSdk2.7/python_stubs/__builtin__.py @@ -1,7 +1,7 @@ # encoding: utf-8 # module __builtin__ # from (built-in) -# by generator 1.138 +# by generator 1.145 from __future__ import print_function """ Built-in functions, exceptions, and other objects. @@ -2930,7 +2930,8 @@ class float(object): """ Return self, the complex conjugate of any float. """ pass - def fromhex(self, string): # real signature unknown; restored from __doc__ + @staticmethod # known case + def fromhex(string): # real signature unknown; restored from __doc__ """ float.fromhex(string) -> float diff --git a/python/testData/MockSdk3.4/Lib/abc.py b/python/testData/MockSdk3.4/Lib/abc.py index a63732d230a2..79850b48538d 100644 --- a/python/testData/MockSdk3.4/Lib/abc.py +++ b/python/testData/MockSdk3.4/Lib/abc.py @@ -7,5 +7,12 @@ class ABCMeta: pass +class ABC(metaclass=ABCMeta): + """Helper class that provides a standard way to create an ABC using + inheritance. + """ + pass + + def abstractmethod(foo): pass \ No newline at end of file diff --git a/python/testData/MockSdk3.4/python_stubs/builtins.py b/python/testData/MockSdk3.4/python_stubs/builtins.py index 7d2a8470c747..259e9a1fff1a 100644 --- a/python/testData/MockSdk3.4/python_stubs/builtins.py +++ b/python/testData/MockSdk3.4/python_stubs/builtins.py @@ -1,7 +1,7 @@ # encoding: utf-8 # module builtins # from (built-in) -# by generator 1.144 +# by generator 1.145 """ Built-in functions, exceptions, and other objects. @@ -24,7 +24,7 @@ def abs(*args, **kwargs): # real signature unknown def all(*args, **kwargs): # real signature unknown """ Return True if bool(x) is True for all values x in the iterable. - + If the iterable is empty, return True. """ pass @@ -32,7 +32,7 @@ def all(*args, **kwargs): # real signature unknown def any(*args, **kwargs): # real signature unknown """ Return True if bool(x) is True for any x in the iterable. - + If the iterable is empty, return False. """ pass @@ -48,7 +48,7 @@ def ascii(*args, **kwargs): # real signature unknown """ pass -def bin(*args, **kwargs): # real signature unknown; NOTE: unreliably restored from __doc__ +def bin(*args, **kwargs): # real signature unknown; NOTE: unreliably restored from __doc__ """ Return the binary representation of an integer. @@ -60,7 +60,7 @@ def bin(*args, **kwargs): # real signature unknown; NOTE: unreliably restored fr def callable(i_e_, some_kind_of_function): # real signature unknown; restored from __doc__ """ Return whether the object is callable (i.e., some kind of function). - + Note that classes are callable, as are instances of classes with a __call__() method. """ @@ -133,7 +133,7 @@ def divmod(x, y): # known case of builtins.divmod def eval(*args, **kwargs): # real signature unknown """ Evaluate the given source in the context of globals and locals. - + The source may be a string representing a Python expression or a code object as returned by compile(). The globals must be a dictionary and locals can be any mapping, @@ -178,7 +178,7 @@ def getattr(object, name, default=None): # known special case of getattr def globals(*args, **kwargs): # real signature unknown """ Return the dictionary containing the current scope's global variables. - + NOTE: Updates to this dictionary *will* affect name lookups in the current global scope and vice-versa. """ @@ -187,7 +187,7 @@ def globals(*args, **kwargs): # real signature unknown def hasattr(*args, **kwargs): # real signature unknown """ Return whether the object has an attribute with the given name. - + This is done by calling getattr(obj, name) and catching AttributeError. """ pass @@ -213,7 +213,7 @@ def help(): # real signature unknown; restored from __doc__ """ pass -def hex(*args, **kwargs): # real signature unknown; NOTE: unreliably restored from __doc__ +def hex(*args, **kwargs): # real signature unknown; NOTE: unreliably restored from __doc__ """ Return the hexadecimal representation of an integer. @@ -234,10 +234,10 @@ def id(*args, **kwargs): # real signature unknown def input(*args, **kwargs): # real signature unknown """ Read a string from standard input. The trailing newline is stripped. - + The prompt string, if given, is printed to standard output without a trailing newline before reading input. - + If the user hits EOF (*nix: Ctrl-D, Windows: Ctrl-Z+Return), raise EOFError. On *nix systems, readline is used if available. """ @@ -246,7 +246,7 @@ def input(*args, **kwargs): # real signature unknown def isinstance(x, A_tuple): # real signature unknown; restored from __doc__ """ Return whether an object is an instance of a class or of a subclass thereof. - + A tuple, as in ``isinstance(x, (A, B, ...))``, may be given as the target to check against. This is equivalent to ``isinstance(x, A) or isinstance(x, B) or ...`` etc. @@ -328,7 +328,7 @@ def next(iterator, default=None): # real signature unknown; restored from __doc_ """ pass -def oct(*args, **kwargs): # real signature unknown; NOTE: unreliably restored from __doc__ +def oct(*args, **kwargs): # real signature unknown; NOTE: unreliably restored from __doc__ """ Return the octal representation of an integer. @@ -467,7 +467,7 @@ def ord(*args, **kwargs): # real signature unknown def pow(*args, **kwargs): # real signature unknown """ Equivalent to x**y (with two arguments) or x**y % z (with three arguments) - + Some types, such as ints, are able to use a more efficient algorithm when invoked using the three argument form. """ @@ -492,7 +492,7 @@ def quit(*args, **kwargs): # real signature unknown def repr(obj): # real signature unknown; restored from __doc__ """ Return the canonical string representation of the object. - + For many object types, including most builtins, eval(repr(obj)) == obj. """ pass @@ -518,7 +518,7 @@ def setattr(x, y, v): # real signature unknown; restored from __doc__ def sorted(*args, **kwargs): # real signature unknown """ Return a new list containing all items from the iterable in ascending order. - + A custom key function can be supplied to customise the sort order, and the reverse flag can be set to request the result in descending order. """ @@ -1476,10 +1476,10 @@ class bytearray(object): return 0 @classmethod # known case - def fromhex(cls, *args, **kwargs): # real signature unknown; NOTE: unreliably restored from __doc__ + def fromhex(cls, *args, **kwargs): # real signature unknown; NOTE: unreliably restored from __doc__ """ Create a bytearray object from a string of hexadecimal numbers. - + Spaces between two numbers are accepted. Example: bytearray.fromhex('B9 01EF') -> bytearray(b'\\xb9\\x01\\xef') """ @@ -1488,7 +1488,7 @@ class bytearray(object): def hex(self): # real signature unknown; restored from __doc__ """ B.hex() -> string - + Create a string of hexadecimal numbers from a bytearray object. Example: bytearray([0xb9, 0x01, 0xef]).hex() -> 'b901ef'. """ @@ -1505,7 +1505,7 @@ class bytearray(object): def insert(self, *args, **kwargs): # real signature unknown """ Insert a single item into the bytearray before the given index. - + index The index where the value is to be inserted. item @@ -1583,7 +1583,7 @@ class bytearray(object): Concatenate any number of bytes/bytearray objects. The bytearray whose method is called is inserted in between each pair. - + The result is returned as a new bytearray object. """ pass @@ -1620,7 +1620,7 @@ class bytearray(object): The returned table will be one where each byte in frm is mapped to the byte at the same position in to. - + The bytes objects frm and to must be of the same length. """ pass @@ -1632,7 +1632,7 @@ class bytearray(object): This will search for the separator sep in the bytearray. If the separator is found, returns a 3-tuple containing the part before the separator, the separator itself, and the part after it. - + If the separator is not found, returns a 3-tuple containing the original bytearray object and two empty bytearray objects. """ @@ -1662,11 +1662,11 @@ class bytearray(object): def replace(self, *args, **kwargs): # real signature unknown """ Return a copy with all occurrences of substring old replaced by new. - + count Maximum number of occurrences to replace. -1 (the default value) means replace all occurrences. - + If the optional argument count is given, only the first count occurrences are replaced. """ @@ -1712,7 +1712,7 @@ class bytearray(object): This will search for the separator sep in the bytearray, starting and the end. If the separator is found, returns a 3-tuple containing the part before the separator, the separator itself, and the part after it. - + If the separator is not found, returns a 3-tuple containing two empty bytearray objects and the original bytearray object. """ @@ -1729,7 +1729,7 @@ class bytearray(object): maxsplit Maximum number of splits to do. -1 (the default value) means no limit. - + Splitting is done starting at the end of the bytearray and working to the front. """ pass @@ -1809,7 +1809,7 @@ class bytearray(object): table Translation table, which must be a bytes object of length 256. - + All characters occurring in the optional argument deletechars are removed. The remaining characters are mapped through the given translation table. """ @@ -2056,10 +2056,10 @@ class bytes(object): return 0 @classmethod # known case - def fromhex(cls, *args, **kwargs): # real signature unknown; NOTE: unreliably restored from __doc__ + def fromhex(cls, *args, **kwargs): # real signature unknown; NOTE: unreliably restored from __doc__ """ Create a bytes object from a string of hexadecimal numbers. - + Spaces between two numbers are accepted. Example: bytes.fromhex('B9 01EF') -> b'\\xb9\\x01\\xef'. """ @@ -2068,7 +2068,7 @@ class bytes(object): def hex(self): # real signature unknown; restored from __doc__ """ B.hex() -> string - + Create a string of hexadecimal numbers from a bytes object. Example: b'\xb9\x01\xef'.hex() -> 'b901ef'. """ @@ -2147,14 +2147,14 @@ class bytes(object): """ return False - def join(self, *args, **kwargs): # real signature unknown; NOTE: unreliably restored from __doc__ + def join(self, *args, **kwargs): # real signature unknown; NOTE: unreliably restored from __doc__ """ Concatenate any number of bytes objects. The bytes whose method is called is inserted in between each pair. - + The result is returned as a new bytes object. - + Example: b'.'.join([b'ab', b'pq', b'rs']) -> b'ab.pq.rs'. """ pass @@ -2179,7 +2179,7 @@ class bytes(object): def lstrip(self, *args, **kwargs): # real signature unknown """ Strip leading bytes contained in the argument. - + If the argument is omitted or None, strip leading ASCII whitespace. """ pass @@ -2191,7 +2191,7 @@ class bytes(object): The returned table will be one where each byte in frm is mapped to the byte at the same position in to. - + The bytes objects frm and to must be of the same length. """ pass @@ -2203,7 +2203,7 @@ class bytes(object): This will search for the separator sep in the bytes. If the separator is found, returns a 3-tuple containing the part before the separator, the separator itself, and the part after it. - + If the separator is not found, returns a 3-tuple containing the original bytes object and two empty bytes objects. """ @@ -2216,7 +2216,7 @@ class bytes(object): count Maximum number of occurrences to replace. -1 (the default value) means replace all occurrences. - + If the optional argument count is given, only the first count occurrences are replaced. """ @@ -2258,7 +2258,7 @@ class bytes(object): This will search for the separator sep in the bytes, starting and the end. If the separator is found, returns a 3-tuple containing the part before the separator, the separator itself, and the part after it. - + If the separator is not found, returns a 3-tuple containing two empty bytes objects and the original bytes object. """ @@ -2275,7 +2275,7 @@ class bytes(object): maxsplit Maximum number of splits to do. -1 (the default value) means no limit. - + Splitting is done starting at the end of the bytes and working to the front. """ pass @@ -2283,7 +2283,7 @@ class bytes(object): def rstrip(self, *args, **kwargs): # real signature unknown """ Strip trailing bytes contained in the argument. - + If the argument is omitted or None, strip trailing ASCII whitespace. """ pass @@ -2325,7 +2325,7 @@ class bytes(object): def strip(self, *args, **kwargs): # real signature unknown """ Strip leading and trailing bytes contained in the argument. - + If the argument is omitted or None, strip leading and trailing ASCII whitespace. """ pass @@ -2355,7 +2355,7 @@ class bytes(object): table Translation table, which must be a bytes object of length 256. - + All characters occurring in the optional argument deletechars are removed. The remaining characters are mapped through the given translation table. """ @@ -3027,7 +3027,8 @@ class float(object): """ Return self, the complex conjugate of any float. """ pass - def fromhex(self, string): # real signature unknown; restored from __doc__ + @staticmethod # known case + def fromhex(string): # real signature unknown; restored from __doc__ """ float.fromhex(string) -> float @@ -5748,7 +5749,7 @@ class __loader__(object): def load_module(self, *args, **kwargs): # real signature unknown """ Load the specified module into sys.modules and return it. - + This method is deprecated. Use loader.exec_module instead. """ pass diff --git a/python/testData/inspections/PyArgumentListInspection/floatFromhex.py b/python/testData/inspections/PyArgumentListInspection/floatFromhex.py new file mode 100644 index 000000000000..733df8c4e7dc --- /dev/null +++ b/python/testData/inspections/PyArgumentListInspection/floatFromhex.py @@ -0,0 +1 @@ +float.fromhex("0.5") \ No newline at end of file diff --git a/python/testData/inspections/PyCallByClassInspection/test.py b/python/testData/inspections/PyCallByClassInspection/test.py index e346a34686cf..902e32dea3de 100644 --- a/python/testData/inspections/PyCallByClassInspection/test.py +++ b/python/testData/inspections/PyCallByClassInspection/test.py @@ -16,4 +16,8 @@ class B(A): def __init__(self): A.__init__(self) # pass -A.method(B()) \ No newline at end of file +A.method(B()) + + +# PY-21083 +float.fromhex('A') \ No newline at end of file diff --git a/python/testData/inspections/PyNoneFunctionAssignmentInspection/OsPopenClose/a.py b/python/testData/inspections/PyNoneFunctionAssignmentInspection/OsPopenClose/a.py new file mode 100644 index 000000000000..d46a5cac74a6 --- /dev/null +++ b/python/testData/inspections/PyNoneFunctionAssignmentInspection/OsPopenClose/a.py @@ -0,0 +1,2 @@ +from os import popen +ret = popen("non-existent-command").close() \ No newline at end of file diff --git a/python/testData/inspections/PyNoneFunctionAssignmentInspection/OsPopenClose/os.py b/python/testData/inspections/PyNoneFunctionAssignmentInspection/OsPopenClose/os.py new file mode 100644 index 000000000000..be3e93c6ac47 --- /dev/null +++ b/python/testData/inspections/PyNoneFunctionAssignmentInspection/OsPopenClose/os.py @@ -0,0 +1,6 @@ +def popen(command, mode='r', bufsize=-1): + pass + +class _wrap_close: + def close(self): + pass \ No newline at end of file diff --git a/python/testData/inspections/PyTypeCheckerInspection/PathLikePassedToStdlibFunctions/a.py b/python/testData/inspections/PyTypeCheckerInspection/PathLikePassedToStdlibFunctions/a.py new file mode 100644 index 000000000000..dc0b35b13f33 --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/PathLikePassedToStdlibFunctions/a.py @@ -0,0 +1,55 @@ +import os +import os.path +from pathlib import Path, PurePath + + +# os.PathLike +class A: + def __fspath__(self): + pass + +a = A() + +open(a) + +os.fspath(a) +os.fsencode(a) +os.fsdecode(a) + +Path(a) +PurePath(a) + +os.path.abspath(a) + + +# not os.PathLike +class B: + pass + +b = B() + +open(b) + +os.fspath(b) # TODO fail +os.fsencode(b) # TODO fail +os.fsdecode(b) # TODO fail + +Path(b) +PurePath(b) + +os.path.abspath(b) + + +# pathlib.PurePath +p = Path(".") + +open(p) + +os.fspath(p) +os.fsencode(p) +os.fsdecode(p) + +Path(p) +PurePath(p) + +os.path.abspath(p) \ No newline at end of file diff --git a/python/testData/inspections/PyTypeCheckerInspection/PathLikePassedToStdlibFunctions/os.py b/python/testData/inspections/PyTypeCheckerInspection/PathLikePassedToStdlibFunctions/os.py new file mode 100644 index 000000000000..c777cdf8ff54 --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/PathLikePassedToStdlibFunctions/os.py @@ -0,0 +1,16 @@ +import abc +import posixpath as path + + +class PathLike(abc.ABC): + + """Abstract base class for implementing the file system path protocol.""" + + @abc.abstractmethod + def __fspath__(self): + """Return the file system path representation of the object.""" + raise NotImplementedError + + @classmethod + def __subclasshook__(cls, subclass): + return hasattr(subclass, '__fspath__') \ No newline at end of file diff --git a/python/testData/inspections/PyTypeCheckerInspection/PathLikePassedToStdlibFunctions/pathlib.py b/python/testData/inspections/PyTypeCheckerInspection/PathLikePassedToStdlibFunctions/pathlib.py new file mode 100644 index 000000000000..5848accabb32 --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/PathLikePassedToStdlibFunctions/pathlib.py @@ -0,0 +1,11 @@ +class PurePath(object): + def __new__(cls, *pathsegments): + pass + + def __fspath__(self): + pass + + +class Path(PurePath): + def __new__(cls, *pathsegments): + pass \ No newline at end of file diff --git a/python/testData/inspections/PyTypeCheckerInspection/PathLikePassedToStdlibFunctions/posixpath.py b/python/testData/inspections/PyTypeCheckerInspection/PathLikePassedToStdlibFunctions/posixpath.py new file mode 100644 index 000000000000..0432fd6b9f4c --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/PathLikePassedToStdlibFunctions/posixpath.py @@ -0,0 +1,2 @@ +def abspath(path): + pass \ No newline at end of file diff --git a/python/testData/inspections/PyTypeCheckerInspection/PositionalArguments.py b/python/testData/inspections/PyTypeCheckerInspection/PositionalArguments.py new file mode 100644 index 000000000000..1d2eb2d5daf7 --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/PositionalArguments.py @@ -0,0 +1,8 @@ +def foo(*args): + """ + :type args: tuple[str] + """ + pass + + +foo(1, '1') diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/metaclassAttribute.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/metaclassAttribute.py new file mode 100644 index 000000000000..9d64bd1ad047 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/metaclassAttribute.py @@ -0,0 +1,12 @@ +class Meta(type): + def __init__(self, what, bases, dict): + self.meta_attr = "attr" + super().__init__(what, bases, dict) + + +class A(metaclass=Meta): + pass + + +print(A.meta_attr) +print(A().meta_attr) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/env/PyEnvTestCase.java b/python/testSrc/com/jetbrains/env/PyEnvTestCase.java index 4f06d6353c2a..36d221f3591f 100644 --- a/python/testSrc/com/jetbrains/env/PyEnvTestCase.java +++ b/python/testSrc/com/jetbrains/env/PyEnvTestCase.java @@ -39,7 +39,13 @@ public abstract class PyEnvTestCase { private static final Logger LOG = Logger.getInstance(PyEnvTestCase.class.getName()); private static final String TAGS_FILE = "tags.txt"; + /** + * Platform-specific separated list of python interpreters + */ private static final String PYCHARM_PYTHON_ENVS = "PYCHARM_PYTHON_ENVS"; + /** + * Folder with virtual envs (python interpreters). + */ private static final String PYCHARM_PYTHON_VIRTUAL_ENVS = "PYCHARM_PYTHON_VIRTUAL_ENVS"; protected static final boolean IS_ENV_CONFIGURATION = System.getProperty("pycharm.env") != null; diff --git a/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java b/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java index 9ccf7a2a3e6d..5b5e4352f892 100644 --- a/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java +++ b/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java @@ -740,10 +740,6 @@ public class PythonDebuggerTest extends PyEnvTestCase { @Test @Staging public void testPyQtQThreadInheritor() throws Exception { - if (UsefulTestCase.IS_UNDER_TEAMCITY && SystemInfo.isWindows) { - return; //Don't run under Windows - } - runPythonTest(new PyDebuggerTask("/debug", "test_pyqt1.py") { @Override protected void init() { @@ -782,10 +778,6 @@ public class PythonDebuggerTest extends PyEnvTestCase { @Test @Staging public void testPyQtMoveToThread() throws Exception { - if (UsefulTestCase.IS_UNDER_TEAMCITY && SystemInfo.isWindows) { - return; //Don't run under Windows - } - runPythonTest(new PyDebuggerTask("/debug", "test_pyqt2.py") { @Override protected void init() { @@ -825,10 +817,6 @@ public class PythonDebuggerTest extends PyEnvTestCase { @Test @Staging public void testPyQtQRunnableInheritor() throws Exception { - if (UsefulTestCase.IS_UNDER_TEAMCITY && SystemInfo.isWindows) { - return; //Don't run under Windows - } - runPythonTest(new PyDebuggerTask("/debug", "test_pyqt3.py") { @Override protected void init() { diff --git a/python/testSrc/com/jetbrains/python/Py3CompletionTest.java b/python/testSrc/com/jetbrains/python/Py3CompletionTest.java index 2af14073d216..437acbafe852 100644 --- a/python/testSrc/com/jetbrains/python/Py3CompletionTest.java +++ b/python/testSrc/com/jetbrains/python/Py3CompletionTest.java @@ -19,6 +19,8 @@ import com.intellij.codeInsight.completion.impl.CamelHumpMatcher; import com.intellij.testFramework.LightProjectDescriptor; import com.jetbrains.python.fixtures.PyTestCase; import com.jetbrains.python.psi.LanguageLevel; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.List; @@ -76,7 +78,8 @@ public class Py3CompletionTest extends PyTestCase { myFixture.checkResultByFile(getTestName(true) + "/a.after.py"); } - private List doTestByText(String text) { + @Nullable + private List doTestByText(@NotNull String text) { myFixture.configureByText(PythonFileType.INSTANCE, text); myFixture.completeBasic(); return myFixture.getLookupElementStrings(); @@ -144,7 +147,7 @@ public class Py3CompletionTest extends PyTestCase { // PY-15390 public void testMatMul() { - runWithLanguageLevel(LanguageLevel.PYTHON35, () -> doTest()); + runWithLanguageLevel(LanguageLevel.PYTHON35, this::doTest); } // PY-11214 @@ -153,11 +156,11 @@ public class Py3CompletionTest extends PyTestCase { } public void testAsync() { - runWithLanguageLevel(LanguageLevel.PYTHON35, () -> doTest()); + runWithLanguageLevel(LanguageLevel.PYTHON35, this::doTest); } public void testAwait() { - runWithLanguageLevel(LanguageLevel.PYTHON35, () -> doTest()); + runWithLanguageLevel(LanguageLevel.PYTHON35, this::doTest); } // PY-17828 @@ -165,6 +168,28 @@ public class Py3CompletionTest extends PyTestCase { runWithLanguageLevel(LanguageLevel.PYTHON30, this::doTest); } + // PY-20279 + public void testImplicitDunderClass() { + final List inClassMethod = doTestByText("class First:\n" + + " def foo(self):\n" + + " print(__cl)"); + assertNotNull(inClassMethod); + assertContainsElements(inClassMethod, PyNames.__CLASS__); + + final List inStaticMethod = doTestByText("class First:\n" + + " @staticmethod\n" + + " def foo():\n" + + " print(__cl)"); + assertNotNull(inStaticMethod); + assertContainsElements(inStaticMethod, PyNames.__CLASS__); + + assertNull(doTestByText("class First:\n" + + " print(__cl)")); + + assertNull(doTestByText("def abc():\n" + + " print(__cl)")); + } + // PY-20770 public void testAsyncGenerator() { runWithLanguageLevel( @@ -205,6 +230,58 @@ public class Py3CompletionTest extends PyTestCase { runWithLanguageLevel(LanguageLevel.PYTHON35, this::doTest); } + // PY-19702 + public void testMetaclassAttributeOnDefinition() { + final List suggested = doTestByText("class Meta(type):\n" + + " def __init__(self, what, bases, dict):\n" + + " self.meta_attr = \"attr\"\n" + + " super().__init__(what, bases, dict)\n" + + "class A(metaclass=Meta):\n" + + " pass\n" + + "print(A.)"); + + assertNotNull(suggested); + assertContainsElements(suggested, "meta_attr"); + } + + // PY-19702 + public void testMetaclassAttributeOnInstance() { + final List suggested = doTestByText("class Meta(type):\n" + + " def __init__(self, what, bases, dict):\n" + + " self.meta_attr = \"attr\"\n" + + " super().__init__(what, bases, dict)\n" + + "class A(metaclass=Meta):\n" + + " pass\n" + + "print(A().)"); + + assertNotNull(suggested); + assertContainsElements(suggested, "meta_attr"); + } + + public void testMetaclassMethodOnDefinition() { + final List suggested = doTestByText("class Meta(type):\n" + + " def meta_method(cls):\n" + + " pass\n" + + "class A(metaclass=Meta):\n" + + " pass\n" + + "print(A.)"); + + assertNotNull(suggested); + assertContainsElements(suggested, "meta_method"); + } + + public void testMetaclassMethodOnInstance() { + final List suggested = doTestByText("class Meta(type):\n" + + " def meta_method(cls):\n" + + " pass\n" + + "class A(metaclass=Meta):\n" + + " pass\n" + + "print(A().)"); + + assertNotNull(suggested); + assertDoesntContain(suggested, "meta_method"); + } + @Override protected String getTestDataPath() { return super.getTestDataPath() + "/completion"; diff --git a/python/testSrc/com/jetbrains/python/Py3TypeTest.java b/python/testSrc/com/jetbrains/python/Py3TypeTest.java index 46bc84cf6ef7..c8cb9818b708 100644 --- a/python/testSrc/com/jetbrains/python/Py3TypeTest.java +++ b/python/testSrc/com/jetbrains/python/Py3TypeTest.java @@ -448,6 +448,17 @@ public class Py3TypeTest extends PyTestCase { " expr = x"); } + // PY-20757 + public void testMinElseNone() { + doTest("Union[None, Any]", + "def get_value(v):\n" + + " if v:\n" + + " return min(v)\n" + + " else:\n" + + " return None\n" + + "expr = get_value([])"); + } + private void doTest(final String expectedType, final String text) { myFixture.configureByText(PythonFileType.INSTANCE, text); final PyExpression expr = myFixture.findElementByText("expr", PyExpression.class); diff --git a/python/testSrc/com/jetbrains/python/PyStructureViewTest.java b/python/testSrc/com/jetbrains/python/PyStructureViewTest.java index d30bba454b52..30ec32af1548 100644 --- a/python/testSrc/com/jetbrains/python/PyStructureViewTest.java +++ b/python/testSrc/com/jetbrains/python/PyStructureViewTest.java @@ -15,9 +15,13 @@ */ package com.jetbrains.python; -import com.intellij.ide.structureView.newStructureView.StructureViewComponent; -import com.intellij.util.Consumer; +import com.intellij.navigation.ItemPresentation; +import com.intellij.openapi.command.WriteCommandAction; import com.jetbrains.python.fixtures.PyTestCase; +import com.jetbrains.python.psi.PyFunction; +import com.jetbrains.python.structureView.PyStructureViewElement; + +import javax.swing.*; import static com.intellij.testFramework.PlatformTestUtil.assertTreeEqual; @@ -96,6 +100,21 @@ public class PyStructureViewTest extends PyTestCase { true); } + // EA-83566 + public void testInvalidatedElement() { + myFixture.configureByText("a.py", + "def f():\n" + + " pass"); + final PyFunction function = myFixture.findElementByText("f", PyFunction.class); + final PyStructureViewElement node = new PyStructureViewElement(function); + WriteCommandAction.runWriteCommandAction(myFixture.getProject(), function::delete); + assertNull(node.getValue()); + final ItemPresentation presentation = node.getPresentation(); + assertNotNull(presentation); + final Icon icon = presentation.getIcon(false); + assertNull(icon); + } + private void doTest(final String expected, final boolean inherited) { myFixture.testStructureView(component -> { component.setActionActive("SHOW_INHERITED", !inherited); diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index 29ba56c4cfbb..259eee353529 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -1398,6 +1398,17 @@ public class PyTypeTest extends PyTestCase { "expr = float.fromhex(\"0.5\")"); } + // PY-20757 + public void testMinOrNone() { + doTest("Union[None, Any]", + "def get_value(v):\n" + + " if v:\n" + + " return min(v)\n" + + " else:\n" + + " return None\n" + + "expr = get_value([])"); + } + 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/PythonCompletionTest.java b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java index c36c0b839008..7e8285f71a7e 100644 --- a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java +++ b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java @@ -27,6 +27,7 @@ import com.jetbrains.python.documentation.PyDocumentationSettings; import com.jetbrains.python.documentation.docstrings.DocStringFormat; import com.jetbrains.python.fixtures.PyTestCase; import com.jetbrains.python.psi.LanguageLevel; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Arrays; @@ -50,7 +51,8 @@ public class PythonCompletionTest extends PyTestCase { myFixture.checkResultByFile(getTestName(true) + "/a.after.py"); } - private List doTestByText(String text) { + @Nullable + private List doTestByText(@NotNull String text) { myFixture.configureByText(PythonFileType.INSTANCE, text); myFixture.completeBasic(); return myFixture.getLookupElementStrings(); @@ -436,12 +438,12 @@ public class PythonCompletionTest extends PyTestCase { // PY-16991 public void testSecondSectionNameInGoogleDocstring() { - runWithDocStringFormat(DocStringFormat.GOOGLE, () -> doTest()); + runWithDocStringFormat(DocStringFormat.GOOGLE, this::doTest); } // PY-16877 public void testTwoWordsSectionNameInGoogleDocstring() throws Exception { - runWithDocStringFormat(DocStringFormat.GOOGLE, () -> doTest()); + runWithDocStringFormat(DocStringFormat.GOOGLE, this::doTest); } // PY-16870 @@ -473,7 +475,7 @@ public class PythonCompletionTest extends PyTestCase { // PY-16870, PY-16972 public void testClassNameInDocstring() { - runWithDocStringFormat(DocStringFormat.EPYTEXT, () -> doTest()); + runWithDocStringFormat(DocStringFormat.EPYTEXT, this::doTest); } // PY-17002 @@ -752,8 +754,8 @@ public class PythonCompletionTest extends PyTestCase { // PY-9342 public void testUnboundMethodSpecialAttributes() { - runWithLanguageLevel(LanguageLevel.PYTHON27, () -> assertUnderscoredMethodSpecialAttributesSuggested()); - runWithLanguageLevel(LanguageLevel.PYTHON32, () -> assertUnderscoredFunctionAttributesSuggested()); + runWithLanguageLevel(LanguageLevel.PYTHON27, this::assertUnderscoredMethodSpecialAttributesSuggested); + runWithLanguageLevel(LanguageLevel.PYTHON32, this::assertUnderscoredFunctionAttributesSuggested); } // PY-9342 @@ -893,7 +895,7 @@ public class PythonCompletionTest extends PyTestCase { public void testFormatStringWithFormatModifier() { doTest(); } - + // PY-3077 public void testPercentStringWithDictLiteralArg() { doTest(); @@ -903,7 +905,7 @@ public class PythonCompletionTest extends PyTestCase { public void testPercentStringWithDictCallArg() { doTest(); } - + // PY-3077 public void testPercentStringWithParenDictCallArg() { doTest(); @@ -918,17 +920,17 @@ public class PythonCompletionTest extends PyTestCase { public void testPercentStringDictLiteralStringKey() { doTest(); } - + // PY-3077 public void testPercentStringDictCallStringKey() { doTest(); } - + // PY-3077 public void testPercentStringDictLiteralArgument() { doTest(); } - + // PY-19839 public void testPercentStringDictRefKeys() { final List variants = doTestByFile(); @@ -1015,6 +1017,32 @@ public class PythonCompletionTest extends PyTestCase { doTest(); } + // PY-20279 + public void testImplicitDunderClass() { + final List inClassMethod = doTestByText("class First:\n" + + " def foo(self):\n" + + " print(__cl)"); + assertNotNull(inClassMethod); + assertDoesntContain(inClassMethod, PyNames.__CLASS__); + + final List inStaticMethod = doTestByText("class First:\n" + + " @staticmethod\n" + + " def foo():\n" + + " print(__cl)"); + assertNotNull(inStaticMethod); + assertDoesntContain(inStaticMethod, PyNames.__CLASS__); + + final List inClass = doTestByText("class First:\n" + + " print(__cl)"); + assertNotNull(inClass); + assertEmpty(inClass); + + final List inFunction = doTestByText("def abc():\n" + + " print(__cl)"); + assertNotNull(inFunction); + assertEmpty(inClass); + } + // PY-20768 public void testInitSubclassBuiltinMethod() { runWithLanguageLevel(LanguageLevel.PYTHON36, @@ -1037,6 +1065,17 @@ public class PythonCompletionTest extends PyTestCase { }); } + // PY-20769 + public void testFsPathBuiltinMethod() { + runWithLanguageLevel(LanguageLevel.PYTHON36, + () -> { + doTestByText("class Cl(object):\n" + + " def __fspa"); + myFixture.checkResult("class Cl(object):\n" + + " def __fspath__(self):"); + }); + } + @Override protected String getTestDataPath() { return super.getTestDataPath() + "/completion"; diff --git a/python/testSrc/com/jetbrains/python/inspections/Py3NoneFunctionAssignmentInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/Py3NoneFunctionAssignmentInspectionTest.java new file mode 100644 index 000000000000..933729ffdb18 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/inspections/Py3NoneFunctionAssignmentInspectionTest.java @@ -0,0 +1,41 @@ +/* + * 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.jetbrains.python.inspections; + +import com.intellij.testFramework.LightProjectDescriptor; +import com.jetbrains.python.fixtures.PyTestCase; +import org.jetbrains.annotations.Nullable; + +public class Py3NoneFunctionAssignmentInspectionTest extends PyTestCase { + + @Nullable + @Override + protected LightProjectDescriptor getProjectDescriptor() { + return ourPy3Descriptor; + } + + // PY-21040 + public void testOsPopenClose() { + doMultiFileTest(); + } + + private void doMultiFileTest() { + myFixture.copyDirectoryToProject("inspections/PyNoneFunctionAssignmentInspection/" + getTestName(false) + "/", ""); + myFixture.configureByFile("a.py"); + myFixture.enableInspections(PyNoneFunctionAssignmentInspection.class); + myFixture.checkHighlighting(false, false, true); + } +} diff --git a/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java index bfff81641f52..d6c0f4eaa6ce 100644 --- a/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java @@ -159,6 +159,11 @@ public class Py3TypeCheckerInspectionTest extends PyTestCase { doTest(); } + // PY-20769 + public void testPathLikePassedToStdlibFunctions() { + doMultiFileTest(); + } + // PY-21048 public void testAsyncFunctionReturnType() { doTest(); diff --git a/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java index 1d0a91458282..a864796fe868 100644 --- a/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java @@ -95,6 +95,11 @@ public class Py3UnresolvedReferencesInspectionTest extends PyTestCase { doTest(); } + // PY-19702 + public void testMetaclassAttribute() { + doTest(); + } + public void testMetaclassStub() { doMultiFileTest("a.py"); final Project project = myFixture.getProject(); diff --git a/python/testSrc/com/jetbrains/python/inspections/PyArgumentListInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyArgumentListInspectionTest.java index 0f146dc20950..67a147fabb24 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyArgumentListInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyArgumentListInspectionTest.java @@ -217,6 +217,11 @@ public class PyArgumentListInspectionTest extends PyTestCase { doMultiFileTest(); } + // PY-21083 + public void testFloatFromhex() { + doTest(); + } + private void doMultiFileTest() { final String folderPath = "inspections/PyArgumentListInspection/" + getTestName(false) + "/"; diff --git a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java index b73f19a9c8ed..32f4f7ff39a1 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java @@ -346,4 +346,8 @@ public class PyTypeCheckerInspectionTest extends PyTestCase { public void testMapArgumentsInOppositeOrder() { doTest(); } + + public void testPositionalArguments() { + doTest(); + } } diff --git a/python/testSrc/com/jetbrains/python/sdk/flavors/WinPythonSdkFlavorTest.java b/python/testSrc/com/jetbrains/python/sdk/flavors/WinPythonSdkFlavorTest.java new file mode 100644 index 000000000000..707d0e4ffe06 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/sdk/flavors/WinPythonSdkFlavorTest.java @@ -0,0 +1,90 @@ +/* + * 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.jetbrains.python.sdk.flavors; + +import com.intellij.openapi.util.io.FileUtil; +import org.easymock.EasyMock; +import org.easymock.IMocksControl; +import org.easymock.MockType; +import org.hamcrest.Matchers; +import org.jetbrains.annotations.NotNull; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * @author Ilya.Kazakevich + */ +public final class WinPythonSdkFlavorTest { + + private IMocksControl myControl; + private WinRegistryService myMock; + + @Before + public void setUp() throws Exception { + myControl = EasyMock.createControl(MockType.NICE); + myMock = myControl.createMock(WinRegistryService.class); + } + + /** + * Check PyCharm can find python using registry and pep-514 + */ + @Test + public void testFindPythonUsingRegistry() throws Exception { + + + EasyMock.expect(myMock.listBranches(caseInsensitive("HKEY_CURRENT_USER\\Software\\Python"))) + .andReturn(Arrays.asList("PythonCore", "CompanyFoo")).anyTimes(); + + + EasyMock.expect(myMock.listBranches(caseInsensitive("HKEY_CURRENT_USER\\Software\\Python\\PythonCore"))) + .andReturn(Collections.singletonList("3.5")).anyTimes(); + + final String tempDir = File.createTempFile("foo", "bar").getParent(); + final File pythonFile = new File(tempDir, "python.exe"); + FileUtil.appendToFile(pythonFile, ""); + assert pythonFile.exists() : "Failed to create file"; + + EasyMock.expect(myMock.getDefaultKey(caseInsensitive("HKEY_CURRENT_USER\\Software\\Python\\PythonCore\\3.5\\InstallPath"))) + .andReturn(tempDir).anyTimes(); + + + EasyMock.expect(myMock.listBranches(EasyMock.anyString())).andReturn(Collections.emptyList()).anyTimes(); + + myControl.replay(); + + final WinPythonSdkFlavor systemUnderTest = new WinPythonSdkFlavor(myMock); + final List result = new ArrayList<>(); + systemUnderTest.findInRegistry(result); + Assert.assertThat("Python should be found by registry, but failed to do so", result, + Matchers.containsInAnyOrder(pythonFile.getAbsolutePath())); + } + + /** + * @return case insensitive matcher for string + */ + private String caseInsensitive(@NotNull final String source) { + // Never returns null: lame EasyMock api + //noinspection ConstantConditions + return EasyMock.matches(String.format("(?i)%s", source.replace("\\", "\\\\"))); + } +} \ No newline at end of file diff --git a/resources-en/src/search/searchableOptions.xml b/resources-en/src/search/searchableOptions.xml index 09a006cdf69a..76ad41d006d4 100644 --- a/resources-en/src/search/searchableOptions.xml +++ b/resources-en/src/search/searchableOptions.xml @@ -22958,7 +22958,6 @@